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 }