From 849c28ede2de1f97313718dfea9ba5d6093f5b03 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Wed, 21 Jan 2026 19:17:49 +0800 Subject: [PATCH] =?UTF-8?q?perf(scan):=20=E5=AE=9E=E7=8E=B0=E5=90=AF?= =?UTF-8?q?=E5=8F=91=E5=BC=8F=E4=BC=98=E5=8C=96=E6=8F=90=E5=8D=87=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 端口优先级排序:高价值端口(80,443,22,3389等)优先扫描 - 用户能更快看到有意义的结果 - 不影响端口喷洒策略 2. TCP 补充探测:ICMP 响应率<10%时自动启用 - 对未响应主机用 TCP 80/443/22/445 补充探测 - 解决防火墙过滤 ICMP 导致漏检的问题 --- common/i18n/locales/en.yaml | 4 + common/i18n/locales/zh.yaml | 4 + core/icmp.go | 143 +++++++++++++++++++++++++++++++++++ core/socket_iterator.go | 65 +++++++++++++++- core/socket_iterator_test.go | 41 +++++++++- 5 files changed, 252 insertions(+), 5 deletions(-) diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index fbd3d1b..e3ce22d 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -269,6 +269,10 @@ segment_16_alive: other: "{{.Arg1}}.0.0/16 segment alive: {{.Arg2}}" segment_24_alive: other: "{{.Arg1}}.0/24 segment alive: {{.Arg2}}" +tcp_probe_low_icmp_rate: + other: "Low ICMP response rate ({{.Arg1}}), enabling TCP supplementary probe ({{.Arg2}} hosts)" +tcp_probe_found: + other: "TCP probe found {{.Arg1}} alive hosts" # ========================= Alive Scan Stats Messages ========================= parse_target_failed: diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index 46ed5be..3febd68 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -269,6 +269,10 @@ segment_16_alive: other: "{{.Arg1}}.0.0/16 网段存活: {{.Arg2}}" segment_24_alive: other: "{{.Arg1}}.0/24 网段存活: {{.Arg2}}" +tcp_probe_low_icmp_rate: + other: "ICMP响应率过低({{.Arg1}}),启用TCP补充探测({{.Arg2}}个主机)" +tcp_probe_found: + other: "TCP补充探测发现 {{.Arg1}} 个存活主机" # ========================= 存活扫描统计消息 ========================= parse_target_failed: diff --git a/core/icmp.go b/core/icmp.go index 1fedcba..8dcaf1c 100644 --- a/core/icmp.go +++ b/core/icmp.go @@ -38,6 +38,7 @@ var pingErrorKeywords = []string{ } // CheckLive 检测主机存活状态 +// 支持 ICMP/Ping 探测,并在响应率过低时自动启用 TCP 补充探测 func CheckLive(hostslist []string, Ping bool, config *common.Config, state *common.State) []string { // 创建局部WaitGroup var livewg sync.WaitGroup @@ -65,12 +66,53 @@ func CheckLive(hostslist []string, Ping bool, config *common.Config, state *comm livewg.Wait() close(chanHosts) + // TCP 补充探测:当 ICMP/Ping 响应率过低时自动启用 + // 这对防火墙过滤 ICMP 的环境特别有用 + aliveHosts = tcpSupplementaryProbe(hostslist, aliveHosts, config) + // 输出存活统计信息 printAliveStats(aliveHosts, hostslist) return aliveHosts } +// tcpSupplementaryProbe TCP 补充探测 +// 当 ICMP 响应率过低时(<10%),对未响应主机进行 TCP 探测 +func tcpSupplementaryProbe(allHosts []string, aliveHosts []string, config *common.Config) []string { + totalHosts := len(allHosts) + if totalHosts == 0 { + return aliveHosts + } + + // 计算 ICMP 响应率 + responseRate := float64(len(aliveHosts)) / float64(totalHosts) + + // 响应率高于阈值,无需补充探测 + if responseRate >= tcpProbeThreshold { + return aliveHosts + } + + // 获取未响应的主机 + unrespondedHosts := getUnrespondedHosts(allHosts, aliveHosts) + if len(unrespondedHosts) == 0 { + return aliveHosts + } + + // 提示用户正在进行 TCP 补充探测 + common.LogInfo(i18n.Tr("tcp_probe_low_icmp_rate", fmt.Sprintf("%.1f%%", responseRate*100), len(unrespondedHosts))) + + // 执行 TCP 补充探测 + tcpAliveHosts := runTcpProbeForHosts(unrespondedHosts, config) + + // 合并结果 + if len(tcpAliveHosts) > 0 { + aliveHosts = append(aliveHosts, tcpAliveHosts...) + common.LogInfo(i18n.Tr("tcp_probe_found", len(tcpAliveHosts))) + } + + return aliveHosts +} + // IsContain 检查切片中是否包含指定元素 func IsContain(items []string, item string) bool { for _, eachItem := range items { @@ -622,3 +664,104 @@ func ArrayCountValueTop(arrInit []string, length int, flag bool) (arrTop []strin return } + +// ============================================================================= +// TCP 补充探测 - 当 ICMP 响应率过低时自动启用 +// ============================================================================= + +// tcpProbeCommonPorts TCP 探测使用的常用端口 +// 这些端口在大多数服务器上至少有一个开放 +var tcpProbeCommonPorts = []int{80, 443, 22, 445} + +// tcpProbeTimeout TCP 探测超时时间(较短,只做存活判断) +const tcpProbeTimeout = 2 * time.Second + +// tcpProbeThreshold TCP 补充探测触发阈值 +// 当 ICMP 响应率低于此值时,自动启用 TCP 补充探测 +const tcpProbeThreshold = 0.1 // 10% + +// tcpProbeAlive 使用 TCP 探测主机是否存活 +// 尝试连接常用端口,任一端口响应即认为存活 +func tcpProbeAlive(host string) bool { + for _, port := range tcpProbeCommonPorts { + addr := fmt.Sprintf("%s:%d", host, port) + conn, err := common.WrapperTcpWithTimeout("tcp", addr, tcpProbeTimeout) + if err == nil { + _ = conn.Close() + return true + } + } + return false +} + +// runTcpProbeForHosts 对指定主机列表进行 TCP 补充探测 +// 返回存活的主机列表 +func runTcpProbeForHosts(hosts []string, config *common.Config) []string { + if len(hosts) == 0 { + return nil + } + + var wg sync.WaitGroup + var mu sync.Mutex + aliveHosts := make([]string, 0) + + // 并发控制,避免资源耗尽 + concurrency := 50 + if len(hosts) < concurrency { + concurrency = len(hosts) + } + limiter := make(chan struct{}, concurrency) + + for _, host := range hosts { + wg.Add(1) + limiter <- struct{}{} + + go func(h string) { + defer func() { + <-limiter + wg.Done() + }() + + if tcpProbeAlive(h) { + mu.Lock() + aliveHosts = append(aliveHosts, h) + mu.Unlock() + + // 保存结果 + result := &output.ScanResult{ + Time: time.Now(), + Type: output.TypeHost, + Target: h, + Status: "alive", + Details: map[string]interface{}{ + "protocol": "TCP", + }, + } + _ = common.SaveResult(result) + + if !config.Output.Silent { + common.LogInfo(i18n.Tr("host_alive", h, "TCP")) + } + } + }(host) + } + + wg.Wait() + return aliveHosts +} + +// getUnrespondedHosts 获取未响应的主机列表 +func getUnrespondedHosts(allHosts []string, aliveHosts []string) []string { + aliveSet := make(map[string]struct{}, len(aliveHosts)) + for _, h := range aliveHosts { + aliveSet[h] = struct{}{} + } + + unresponded := make([]string, 0, len(allHosts)-len(aliveHosts)) + for _, h := range allHosts { + if _, alive := aliveSet[h]; !alive { + unresponded = append(unresponded, h) + } + } + return unresponded +} diff --git a/core/socket_iterator.go b/core/socket_iterator.go index ecb85d5..f5868b0 100644 --- a/core/socket_iterator.go +++ b/core/socket_iterator.go @@ -1,9 +1,35 @@ package core import ( + "sort" "sync" ) +// highPriorityPorts 高价值端口优先级表 +// 数字越小优先级越高,用户最关心这些服务能快速出结果 +var highPriorityPorts = map[int]int{ + 80: 1, // HTTP + 443: 2, // HTTPS + 22: 3, // SSH + 3389: 4, // RDP + 445: 5, // SMB + 3306: 6, // MySQL + 1433: 7, // MSSQL + 6379: 8, // Redis + 21: 9, // FTP + 23: 10, // Telnet + 8080: 11, // HTTP-Alt + 8443: 12, // HTTPS-Alt + 5432: 13, // PostgreSQL + 27017: 14, // MongoDB + 1521: 15, // Oracle + 5900: 16, // VNC + 25: 17, // SMTP + 110: 18, // POP3 + 143: 19, // IMAP + 53: 20, // DNS +} + // SocketIterator 流式生成 host:port 组合 // 设计原则:O(1) 内存,按需生成 // 使用端口喷洒策略:Port1全IP -> Port2全IP -> ... @@ -18,15 +44,50 @@ type SocketIterator struct { } // NewSocketIterator 创建流式迭代器 +// 自动对端口进行智能排序:高价值端口优先,让用户更快看到有意义的结果 func NewSocketIterator(hosts []string, ports []int, exclude map[int]struct{}) *SocketIterator { validPorts := filterExcludedPorts(ports, exclude) + sortedPorts := sortPortsByPriority(validPorts) return &SocketIterator{ hosts: hosts, - ports: validPorts, - total: len(hosts) * len(validPorts), + ports: sortedPorts, + total: len(hosts) * len(sortedPorts), } } +// sortPortsByPriority 智能排序端口 +// 策略:高价值端口优先,其余按数字升序 +func sortPortsByPriority(ports []int) []int { + if len(ports) <= 1 { + return ports + } + + result := make([]int, len(ports)) + copy(result, ports) + + sort.Slice(result, func(i, j int) bool { + pi, pj := result[i], result[j] + priI, okI := highPriorityPorts[pi] + priJ, okJ := highPriorityPorts[pj] + + // 都有优先级:按优先级排序 + if okI && okJ { + return priI < priJ + } + // 只有一个有优先级:有优先级的排前面 + if okI { + return true + } + if okJ { + return false + } + // 都没有优先级:按端口号升序 + return pi < pj + }) + + return result +} + // Next 返回下一个 host:port 组合,ok=false 表示迭代结束 // 端口喷洒顺序:先遍历所有IP的同一端口,再换下一个端口 func (it *SocketIterator) Next() (string, int, bool) { diff --git a/core/socket_iterator_test.go b/core/socket_iterator_test.go index 850484b..2954785 100644 --- a/core/socket_iterator_test.go +++ b/core/socket_iterator_test.go @@ -127,7 +127,7 @@ func TestSocketIterator_ExcludePorts(t *testing.T) { it := NewSocketIterator(hosts, ports, exclude) - // 应该只有22和443 + // 应该只有22和443,按优先级排序:443(优先级2) 在 22(优先级3) 之前 var gotPorts []int for { _, port, ok := it.Next() @@ -140,8 +140,9 @@ func TestSocketIterator_ExcludePorts(t *testing.T) { if len(gotPorts) != 2 { t.Fatalf("期望2个端口, 实际 %d", len(gotPorts)) } - if gotPorts[0] != 22 || gotPorts[1] != 443 { - t.Errorf("期望 [22, 443], 实际 %v", gotPorts) + // 443优先级高于22,所以443在前 + if gotPorts[0] != 443 || gotPorts[1] != 22 { + t.Errorf("期望 [443, 22] (按优先级排序), 实际 %v", gotPorts) } // 验证Total也正确 @@ -181,6 +182,40 @@ func TestSocketIterator_EmptyInputs(t *testing.T) { }) } +// 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) {