From 5616cc180e1947bd2a1f966640e54da412c0e26d Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Tue, 28 Apr 2026 13:24:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BD=91=E6=AE=B5=E9=A2=84=E7=AD=9B?= =?UTF-8?q?=E4=BB=8E=E6=8A=BD=E6=A0=B7=E6=94=B9=E5=85=A8=E8=A6=86=E7=9B=96?= =?UTF-8?q?=EF=BC=8C=E6=AF=8F=E5=8F=B0=E4=B8=BB=E6=9C=BA=E5=8F=91=201=20?= =?UTF-8?q?=E4=B8=AA=E6=8E=A2=E6=B5=8B=E5=8C=85=EF=BC=8C=E6=B6=88=E9=99=A4?= =?UTF-8?q?=E6=BC=8F=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/port_scan.go | 127 ++++++++++++++++++---------------------------- 1 file changed, 48 insertions(+), 79 deletions(-) diff --git a/core/port_scan.go b/core/port_scan.go index 0e720aa..adbc82b 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -671,19 +671,20 @@ func tryHTTPFallbackDetection(host string, port int, addr string, config *common // subnetProbeThreshold 触发网段预筛的主机数阈值(超过 1 个 /24) const subnetProbeThreshold = 256 -// subnetProbePorts 网段探活用的端口(覆盖面广、响应快) -var subnetProbePorts = []int{80, 443, 22, 445, 3389} +// subnetProbePorts 网段探活用的端口(覆盖率优先) +var subnetProbePorts = []int{80, 443, 22, 445, 3389, 8080, 3306, 6379} // subnetProbeTimeout 每个探测的超时 -const subnetProbeTimeout = 1 * time.Second +const subnetProbeTimeout = 1500 * time.Millisecond -// subnetProbeConcurrency 网段探活并发数 -const subnetProbeConcurrency = 200 +// subnetProbeConcurrency 网段探活全局并发数 +const subnetProbeConcurrency = 500 -// probeSubnets 对每个 /24 网段做轻量探活,返回属于存活网段的主机列表 +// probeSubnets 对每个 /24 网段做全覆盖探活,返回属于存活网段的主机列表 +// 策略:每台主机打 1 个端口(端口在子网内轮换),子网内任一主机响应即保留 func probeSubnets(ctx context.Context, hosts []string, timeout time.Duration, session *common.ScanSession) []string { // 按 /24 分组 - subnets := make(map[string][]string) // "10.1.1" → [10.1.1.1, 10.1.1.2, ...] + subnets := make(map[string][]string) for _, h := range hosts { prefix := subnetPrefix(h) if prefix != "" { @@ -691,111 +692,79 @@ func probeSubnets(ctx context.Context, hosts []string, timeout time.Duration, se } } - // 只有 1 个 /24,不需要预筛 if len(subnets) <= 1 { return hosts } common.LogInfo(fmt.Sprintf("网段预筛: %d 个 /24 子网, %d 个主机", len(subnets), len(hosts))) - // 并发探测每个 /24 - aliveSubnets := make(map[string]bool) - var mu sync.Mutex + // 全局并发控制 + aliveSubnets := sync.Map{} var wg sync.WaitGroup limiter := make(chan struct{}, subnetProbeConcurrency) for prefix, subnetHosts := range subnets { - wg.Add(1) - limiter <- struct{}{} - - go func(pfx string, sHosts []string) { - defer func() { - <-limiter - wg.Done() - }() - + // 对子网内每台主机发 1 个探测包(端口轮换覆盖多个端口) + for i, host := range subnetHosts { select { case <-ctx.Done(): - return + goto done default: } - if probeSubnetAlive(ctx, sHosts, session) { - mu.Lock() - aliveSubnets[pfx] = true - mu.Unlock() + port := subnetProbePorts[i%len(subnetProbePorts)] + + // 如果这个子网已经确认存活,跳过剩余主机 + if _, alive := aliveSubnets.Load(prefix); alive { + break } - }(prefix, subnetHosts) + + wg.Add(1) + limiter <- struct{}{} + + go func(pfx, h string, p int) { + defer func() { + <-limiter + wg.Done() + }() + + addr := fmt.Sprintf("%s:%d", h, p) + conn, err := net.DialTimeout("tcp", addr, subnetProbeTimeout) + if err == nil { + _ = conn.Close() + aliveSubnets.Store(pfx, true) + } + }(prefix, host, port) + } } +done: wg.Wait() - if len(aliveSubnets) == 0 { + // 统计 + aliveCount := 0 + aliveSubnets.Range(func(_, _ interface{}) bool { + aliveCount++ + return true + }) + + if aliveCount == 0 { return nil } // 只保留存活网段的主机 result := make([]string, 0, len(hosts)) for _, h := range hosts { - if aliveSubnets[subnetPrefix(h)] { + if _, alive := aliveSubnets.Load(subnetPrefix(h)); alive { result = append(result, h) } } - skipped := len(subnets) - len(aliveSubnets) - common.LogInfo(fmt.Sprintf("网段预筛完成: %d 个存活, %d 个跳过, 剩余 %d 主机", len(aliveSubnets), skipped, len(result))) + skipped := len(subnets) - aliveCount + common.LogInfo(fmt.Sprintf("网段预筛完成: %d 个存活, %d 个跳过, 剩余 %d 主机", aliveCount, skipped, len(result))) return result } -// probeSubnetAlive 探测一个 /24 网段是否存活 -// 从网段中抽样最多 3 台主机,对每台并行打探测端口,任一响应即判定存活 -func probeSubnetAlive(ctx context.Context, hosts []string, session *common.ScanSession) bool { - // 抽样:取网段中最多 3 台主机(首、中、尾) - samples := sampleHosts(hosts, 3) - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - result := make(chan bool, len(samples)*len(subnetProbePorts)) - - total := 0 - for _, host := range samples { - for _, port := range subnetProbePorts { - total++ - go func(h string, p int) { - addr := fmt.Sprintf("%s:%d", h, p) - conn, err := net.DialTimeout("tcp", addr, subnetProbeTimeout) - if err == nil { - _ = conn.Close() - result <- true - return - } - result <- false - }(host, port) - } - } - - for i := 0; i < total; i++ { - if <-result { - return true - } - } - return false -} - -// sampleHosts 从列表中均匀抽样 n 台主机 -func sampleHosts(hosts []string, n int) []string { - if len(hosts) <= n { - return hosts - } - samples := make([]string, n) - step := len(hosts) / n - for i := 0; i < n; i++ { - samples[i] = hosts[i*step] - } - return samples -} - // subnetPrefix 提取 IP 的 /24 前缀(如 "10.1.1") func subnetPrefix(ip string) string { lastDot := strings.LastIndex(ip, ".")