Files
fscan/core/socket_iterator_test.go
ZacharyZcR 849c28ede2 perf(scan): 实现启发式优化提升扫描体验
1. 端口优先级排序:高价值端口(80,443,22,3389等)优先扫描
   - 用户能更快看到有意义的结果
   - 不影响端口喷洒策略

2. TCP 补充探测:ICMP 响应率<10%时自动启用
   - 对未响应主机用 TCP 80/443/22/445 补充探测
   - 解决防火墙过滤 ICMP 导致漏检的问题
2026-01-21 19:17:49 +08:00

235 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package core
import (
"fmt"
"sync"
"testing"
)
/*
socket_iterator_test.go - SocketIterator 高价值测试
测试重点:
1. 端口喷洒顺序 - 这是核心设计顺序错误会导致单IP限速
2. 并发安全性 - 多worker并发调用Next()不丢失不重复
3. 边界情况 - 空输入、单元素
不测试:
- getter方法Total- 太简单
- 内部状态 - 只关心外部行为
*/
// TestSocketIterator_PortSprayOrder 验证端口喷洒顺序
//
// 这是最重要的测试顺序必须是先遍历所有IP的同一端口再换端口
// 正确顺序Port1[IP1,IP2,IP3] → Port2[IP1,IP2,IP3]
// 错误顺序IP1[Port1,Port2,Port3] → IP2[Port1,Port2,Port3]
func TestSocketIterator_PortSprayOrder(t *testing.T) {
hosts := []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"}
ports := []int{80, 443}
it := NewSocketIterator(hosts, ports, nil)
// 期望的顺序先所有IP的80端口再所有IP的443端口
expected := []struct {
host string
port int
}{
{"192.168.1.1", 80},
{"192.168.1.2", 80},
{"192.168.1.3", 80},
{"192.168.1.1", 443},
{"192.168.1.2", 443},
{"192.168.1.3", 443},
}
for i, exp := range expected {
host, port, ok := it.Next()
if !ok {
t.Fatalf("第%d次迭代提前结束", i+1)
}
if host != exp.host || port != exp.port {
t.Errorf("第%d次迭代: 期望 %s:%d, 实际 %s:%d",
i+1, exp.host, exp.port, host, port)
}
}
// 验证迭代结束
_, _, ok := it.Next()
if ok {
t.Error("迭代应该已结束")
}
}
// TestSocketIterator_ConcurrentSafety 验证并发安全性
//
// 多个goroutine同时调用Next(),所有任务必须:
// 1. 不丢失 - 每个host:port组合只出现一次
// 2. 不重复 - 总数等于预期
func TestSocketIterator_ConcurrentSafety(t *testing.T) {
// 构造较大的测试集
hosts := make([]string, 100)
for i := range hosts {
hosts[i] = fmt.Sprintf("192.168.1.%d", i+1)
}
ports := []int{22, 80, 443, 3306, 6379}
it := NewSocketIterator(hosts, ports, nil)
expectedTotal := len(hosts) * len(ports)
// 记录所有结果
results := make(map[string]int)
var mu sync.Mutex
var wg sync.WaitGroup
// 启动10个并发worker
workers := 10
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
host, port, ok := it.Next()
if !ok {
return
}
key := fmt.Sprintf("%s:%d", host, port)
mu.Lock()
results[key]++
mu.Unlock()
}
}()
}
wg.Wait()
// 验证:每个组合只出现一次
if len(results) != expectedTotal {
t.Errorf("任务丢失或重复: 期望 %d 个唯一组合, 实际 %d", expectedTotal, len(results))
}
// 验证:没有重复
for key, count := range results {
if count != 1 {
t.Errorf("任务重复: %s 出现 %d 次", key, count)
}
}
}
// TestSocketIterator_ExcludePorts 验证端口过滤
func TestSocketIterator_ExcludePorts(t *testing.T) {
hosts := []string{"192.168.1.1"}
ports := []int{22, 80, 443, 3306}
exclude := map[int]struct{}{
80: {},
3306: {},
}
it := NewSocketIterator(hosts, ports, exclude)
// 应该只有22和443按优先级排序443(优先级2) 在 22(优先级3) 之前
var gotPorts []int
for {
_, port, ok := it.Next()
if !ok {
break
}
gotPorts = append(gotPorts, port)
}
if len(gotPorts) != 2 {
t.Fatalf("期望2个端口, 实际 %d", len(gotPorts))
}
// 443优先级高于22所以443在前
if gotPorts[0] != 443 || gotPorts[1] != 22 {
t.Errorf("期望 [443, 22] (按优先级排序), 实际 %v", gotPorts)
}
// 验证Total也正确
if it.Total() != 2 {
t.Errorf("Total() 应该是2, 实际 %d", it.Total())
}
}
// TestSocketIterator_EmptyInputs 验证边界情况
func TestSocketIterator_EmptyInputs(t *testing.T) {
t.Run("空hosts", func(t *testing.T) {
it := NewSocketIterator(nil, []int{80}, nil)
_, _, ok := it.Next()
if ok {
t.Error("空hosts应该立即返回false")
}
if it.Total() != 0 {
t.Errorf("Total() 应该是0, 实际 %d", it.Total())
}
})
t.Run("空ports", func(t *testing.T) {
it := NewSocketIterator([]string{"192.168.1.1"}, nil, nil)
_, _, ok := it.Next()
if ok {
t.Error("空ports应该立即返回false")
}
})
t.Run("全部被排除", func(t *testing.T) {
exclude := map[int]struct{}{80: {}, 443: {}}
it := NewSocketIterator([]string{"192.168.1.1"}, []int{80, 443}, exclude)
_, _, ok := it.Next()
if ok {
t.Error("全部端口被排除应该立即返回false")
}
})
}
// TestSocketIterator_PortPrioritySort 验证端口优先级排序
// 高价值端口80, 443, 22等应该排在前面
func TestSocketIterator_PortPrioritySort(t *testing.T) {
hosts := []string{"192.168.1.1"}
// 故意乱序输入,包含高优先级和普通端口
ports := []int{9999, 22, 8888, 80, 7777, 443, 3389, 1234}
it := NewSocketIterator(hosts, ports, nil)
var gotPorts []int
for {
_, port, ok := it.Next()
if !ok {
break
}
gotPorts = append(gotPorts, port)
}
// 期望顺序:高优先级端口按优先级排序,然后是普通端口按数字升序
// 80(优先级1), 443(2), 22(3), 3389(4), 然后 1234, 7777, 8888, 9999
expected := []int{80, 443, 22, 3389, 1234, 7777, 8888, 9999}
if len(gotPorts) != len(expected) {
t.Fatalf("端口数量不匹配: 期望 %d, 实际 %d", len(expected), len(gotPorts))
}
for i, exp := range expected {
if gotPorts[i] != exp {
t.Errorf("第%d个端口: 期望 %d, 实际 %d\n完整结果: %v", i, exp, gotPorts[i], gotPorts)
break
}
}
}
// TestSocketIterator_SingleElements 验证单元素情况
func TestSocketIterator_SingleElements(t *testing.T) {
t.Run("单IP单端口", func(t *testing.T) {
it := NewSocketIterator([]string{"10.0.0.1"}, []int{8080}, nil)
host, port, ok := it.Next()
if !ok || host != "10.0.0.1" || port != 8080 {
t.Errorf("期望 10.0.0.1:8080, 实际 %s:%d, ok=%v", host, port, ok)
}
_, _, ok = it.Next()
if ok {
t.Error("应该只有一个元素")
}
})
}