github.com/chainreactors/fingers@v1.2.1/compare_engines_test.go (about)

     1  package fingers
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net"
     9  	"net/http"
    10  	"sort"
    11  	"strings"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/chainreactors/fingers/resources"
    17  	"github.com/chainreactors/fingers/xray"
    18  )
    19  
    20  func TestCompareEngines(t *testing.T) {
    21  	if testing.Short() {
    22  		t.Skip("skipping live network test in short mode")
    23  	}
    24  	fullEngine, err := NewEngine(FingersEngine, FingerPrintEngine, EHoleEngine, GobyEngine)
    25  	if err != nil {
    26  		t.Fatalf("init engine: %v", err)
    27  	}
    28  	t.Logf("Engines: %s", fullEngine)
    29  
    30  	xrayEng, err := xray.NewXrayEngine(resources.XrayWebData)
    31  	if err != nil {
    32  		t.Fatalf("init xray: %v", err)
    33  	}
    34  	t.Logf("Xray templates: %d", xrayEng.Len())
    35  
    36  	var targets []string
    37  	for i := 1; i < 255; i++ {
    38  		ip := fmt.Sprintf("101.132.149.%d", i)
    39  		targets = append(targets, fmt.Sprintf("http://%s", ip))
    40  		targets = append(targets, fmt.Sprintf("https://%s", ip))
    41  	}
    42  
    43  	alive := probeAlive(targets, 3*time.Second, 50)
    44  	t.Logf("Alive: %d", len(alive))
    45  
    46  	client := &http.Client{
    47  		Timeout: 10 * time.Second,
    48  		Transport: &http.Transport{
    49  			TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
    50  			DisableKeepAlives: true,
    51  		},
    52  		CheckRedirect: func(req *http.Request, via []*http.Request) error {
    53  			if len(via) >= 3 {
    54  				return http.ErrUseLastResponse
    55  			}
    56  			return nil
    57  		},
    58  	}
    59  
    60  	type targetResult struct {
    61  		URL        string
    62  		OtherFP    []string
    63  		XrayFP     []string
    64  		MissInXray []string
    65  	}
    66  
    67  	var mu sync.Mutex
    68  	var results []targetResult
    69  	sem := make(chan struct{}, 20)
    70  	var wg sync.WaitGroup
    71  
    72  	for _, target := range alive {
    73  		wg.Add(1)
    74  		sem <- struct{}{}
    75  		go func(url string) {
    76  			defer wg.Done()
    77  			defer func() { <-sem }()
    78  
    79  			resp, err := client.Get(url)
    80  			if err != nil {
    81  				return
    82  			}
    83  			defer resp.Body.Close()
    84  			body, _ := ioutil.ReadAll(resp.Body)
    85  			raw := buildRawHTTP2(resp, body)
    86  
    87  			// Other engines (exclude xray and favicon)
    88  			otherFrames := fullEngine.WebMatchWithEngines(raw,
    89  				FingersEngine, FingerPrintEngine, EHoleEngine, GobyEngine)
    90  
    91  			// Xray engine
    92  			xrayFrames := xrayEng.WebMatch(raw)
    93  
    94  			if len(otherFrames) == 0 && len(xrayFrames) == 0 {
    95  				return
    96  			}
    97  
    98  			otherSet := map[string]bool{}
    99  			var otherNames []string
   100  			for _, f := range otherFrames {
   101  				otherSet[strings.ToLower(f.Name)] = true
   102  				otherNames = append(otherNames, f.Name)
   103  			}
   104  			sort.Strings(otherNames)
   105  
   106  			xraySet := map[string]bool{}
   107  			var xrayNames []string
   108  			for _, f := range xrayFrames {
   109  				xraySet[strings.ToLower(f.Name)] = true
   110  				xrayNames = append(xrayNames, f.Name)
   111  			}
   112  			sort.Strings(xrayNames)
   113  
   114  			var missInXray []string
   115  			for name := range otherSet {
   116  				if !xraySet[name] {
   117  					missInXray = append(missInXray, name)
   118  				}
   119  			}
   120  			sort.Strings(missInXray)
   121  
   122  			mu.Lock()
   123  			results = append(results, targetResult{
   124  				URL: url, OtherFP: otherNames, XrayFP: xrayNames, MissInXray: missInXray,
   125  			})
   126  			mu.Unlock()
   127  		}(target)
   128  	}
   129  	wg.Wait()
   130  	sort.Slice(results, func(i, j int) bool { return results[i].URL < results[j].URL })
   131  
   132  	// Aggregate
   133  	missCount := map[string]int{}
   134  	missTargets := 0
   135  	for _, r := range results {
   136  		if len(r.MissInXray) > 0 {
   137  			missTargets++
   138  		}
   139  		for _, name := range r.MissInXray {
   140  			missCount[name]++
   141  		}
   142  	}
   143  
   144  	t.Logf("\n=== Engine Comparison ===")
   145  	totalOther, totalXray := 0, 0
   146  	for _, r := range results {
   147  		if len(r.OtherFP) > 0 { totalOther++ }
   148  		if len(r.XrayFP) > 0 { totalXray++ }
   149  	}
   150  	t.Logf("Targets with other-engine FP: %d", totalOther)
   151  	t.Logf("Targets with xray FP:         %d", totalXray)
   152  	t.Logf("Targets with xray miss:       %d", missTargets)
   153  
   154  	t.Logf("\n--- Missed in Xray (detected by other engines) ---")
   155  	type kv struct{ k string; v int }
   156  	var sorted []kv
   157  	for k, v := range missCount {
   158  		sorted = append(sorted, kv{k, v})
   159  	}
   160  	sort.Slice(sorted, func(i, j int) bool { return sorted[i].v > sorted[j].v })
   161  	for _, s := range sorted {
   162  		t.Logf("  %4d  %s", s.v, s.k)
   163  	}
   164  
   165  	t.Logf("\n--- Per-Target Details (first 30 with misses) ---")
   166  	shown := 0
   167  	for _, r := range results {
   168  		if len(r.MissInXray) == 0 {
   169  			continue
   170  		}
   171  		t.Logf("  %-40s other=[%s] xray=[%s] miss=[%s]",
   172  			r.URL,
   173  			strings.Join(r.OtherFP, ","),
   174  			strings.Join(r.XrayFP, ","),
   175  			strings.Join(r.MissInXray, ","))
   176  		shown++
   177  		if shown >= 30 {
   178  			break
   179  		}
   180  	}
   181  }
   182  
   183  func buildRawHTTP2(resp *http.Response, body []byte) []byte {
   184  	var buf bytes.Buffer
   185  	buf.WriteString(fmt.Sprintf("%s %s\r\n", resp.Proto, resp.Status))
   186  	for k, vals := range resp.Header {
   187  		for _, v := range vals {
   188  			buf.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
   189  		}
   190  	}
   191  	buf.WriteString("\r\n")
   192  	buf.Write(body)
   193  	return buf.Bytes()
   194  }
   195  
   196  func probeAlive(targets []string, timeout time.Duration, conc int) []string {
   197  	var alive []string
   198  	var mu sync.Mutex
   199  	sem := make(chan struct{}, conc)
   200  	var wg sync.WaitGroup
   201  	for _, target := range targets {
   202  		wg.Add(1)
   203  		sem <- struct{}{}
   204  		go func(url string) {
   205  			defer wg.Done()
   206  			defer func() { <-sem }()
   207  			host := strings.TrimPrefix(strings.TrimPrefix(url, "http://"), "https://")
   208  			port := "80"
   209  			if strings.HasPrefix(url, "https://") {
   210  				port = "443"
   211  			}
   212  			conn, err := net.DialTimeout("tcp", host+":"+port, timeout)
   213  			if err != nil {
   214  				return
   215  			}
   216  			conn.Close()
   217  			mu.Lock()
   218  			alive = append(alive, url)
   219  			mu.Unlock()
   220  		}(target)
   221  	}
   222  	wg.Wait()
   223  	return alive
   224  }