From 9d0010927e2b779b4dc43143a13552f5a9e670fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=80=B8=E8=88=AA?= Date: Thu, 16 Jul 2026 01:07:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=AB=98=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E4=B8=8B=E8=87=AA=E9=80=82=E5=BA=94=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E8=BF=87=E4=BD=8E=E5=AF=BC=E8=87=B4=E5=BC=80=E6=94=BE=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3=E6=BC=8F=E6=89=AB=20(#598)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修复高并发下自适应超时过低导致开放端口漏扫 (#503) 扫描本机/低 RTT 目标时,AdaptiveTimeout 在 10 次采样后迅速收敛到 100ms 下限。高并发(600+ 线程)下 TCP 握手尾延迟可能超过 100ms,加上超时错误不会重试,导致开放端口被误判为关闭。 - AdaptiveTimeout 下限从 100ms 提升至 max(500ms, maxTimeout/5) - connectWithRetry 对超时错误用完整超时重试一次 - slidingWindowSchedule 任务丢弃时记录日志,便于排查漏扫 * refactor: 按 review 意见移除无条件超时重试,补充 minTO 下限测试 根据 #598 review 反馈: 1. 移除 connectWithRetry 中 timeout->full maxTO 无条件重试 - filtered/无响应端口占超时大头,盲目重试只烧时间 - #503 主场景靠 minTO 抬升已足够覆盖 2. 移除不再使用的 MaxTimeout() 方法和 port_scan_timeout_retry i18n 条目 3. AdaptiveTimeout 收敛测试补充 minTO 下限断言(3s->600ms) --- common/i18n/locales/en.yaml | 4 ++++ common/i18n/locales/zh.yaml | 4 ++++ core/adaptive_timeout.go | 9 ++++++++- core/port_scan.go | 16 +++++++++++++--- core/real_network_test.go | 10 +++++++--- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index 7e7d2ac..dfe4b96 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -429,6 +429,10 @@ port_open_http: other: "Port open {{.Arg1}} [http](HTTP probe)" port_scan_no_alive_subnet: other: "Subnet probe found no alive subnets, skipping port scan" +port_scan_task_dropped: + other: "[PortScan] task dropped: {{.Arg1}} ({{.Arg2}}), port may be missed" +port_scan_tasks_dropped_total: + other: "[PortScan] {{.Arg1}} tasks dropped total, these ports may be missed" network_rate_limited_pattern: other: "Rate limited" port_scan_debug_start: diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index 9e40f41..035e476 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -429,6 +429,10 @@ port_open_http: other: "端口开放 {{.Arg1}} [http](HTTP探测)" port_scan_no_alive_subnet: other: "网段预筛未发现存活子网,跳过端口扫描" +port_scan_task_dropped: + other: "[PortScan] 任务被丢弃: {{.Arg1}} ({{.Arg2}}),该端口可能被漏扫" +port_scan_tasks_dropped_total: + other: "[PortScan] 共有 {{.Arg1}} 个任务被丢弃,这些端口可能被漏扫" network_rate_limited_pattern: other: "发包受限" port_scan_debug_start: diff --git a/core/adaptive_timeout.go b/core/adaptive_timeout.go index f7d98c5..51ce5bd 100644 --- a/core/adaptive_timeout.go +++ b/core/adaptive_timeout.go @@ -25,10 +25,17 @@ type AdaptiveTimeout struct { // NewAdaptiveTimeout 创建自适应超时计算器 // maxTimeout: 用户配置的超时上限(即原始固定超时) func NewAdaptiveTimeout(maxTimeout time.Duration) *AdaptiveTimeout { + // minTO: 自适应超时下限,取 max(500ms, maxTimeout/5) + // 依据:高并发下 TCP 握手存在尾延迟(OS 调度抖动、backlog 溢出、端口竞争), + // 过低的下限会导致开放端口被误判为关闭(issue #503) + minTO := maxTimeout / 5 + if minTO < 500*time.Millisecond { + minTO = 500 * time.Millisecond + } return &AdaptiveTimeout{ samples: make([]float64, 64), size: 64, - minTO: 100 * time.Millisecond, + minTO: minTO, maxTO: maxTimeout, warmup: 10, } diff --git a/core/port_scan.go b/core/port_scan.go index 9a95820..3a34b6a 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -290,6 +290,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout // slidingWindowSchedule 滑动窗口调度器 // ants.PoolWithFunc.Invoke 在池满时阻塞,天然提供反压,无需额外 semaphore func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.WaitGroup) { + var dropped int64 for { host, port, ok := iter.Next() if !ok { @@ -304,11 +305,17 @@ func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.Wa } if err := pool.Invoke(task); err != nil { wg.Done() + dropped++ + common.LogError(i18n.Tr("port_scan_task_dropped", task.addr, err)) } } // 等待所有任务完成 wg.Wait() + + if dropped > 0 { + common.LogError(i18n.Tr("port_scan_tasks_dropped_total", dropped)) + } } // fmtPort 无分配的端口号格式化 @@ -327,7 +334,10 @@ func fmtPort(port int) string { return string(buf[i:]) } -// connectWithRetry 带重试的TCP连接 - 只对资源耗尽错误重试 +// connectWithRetry 带重试的TCP连接 +// - 资源耗尽错误:指数退避重试(maxRetries 次) +// - 其他错误(如 connection refused、timeout):直接返回 +// timeout 是正常的扫描结果(防火墙 drop / filtered),不盲目重试 func connectWithRetry(ctx context.Context, session *common.ScanSession, addr string, timeout time.Duration, maxRetries int) (net.Conn, error) { var lastErr error @@ -340,9 +350,9 @@ func connectWithRetry(ctx context.Context, session *common.ScanSession, addr str lastErr = err - // 只对资源耗尽类错误重试,端口关闭直接返回 + // 只对资源耗尽类错误重试,端口关闭或超时直接返回 if !isResourceExhaustedError(err) { - return nil, err + return nil, lastErr } // 记录资源耗尽错误 diff --git a/core/real_network_test.go b/core/real_network_test.go index 74b396b..df95647 100644 --- a/core/real_network_test.go +++ b/core/real_network_test.go @@ -440,11 +440,15 @@ func TestReal_AdaptiveTimeout_Convergence(t *testing.T) { if converged >= 3*time.Second { t.Errorf("采样后 Timeout = %v, 应该 < 3s", converged) } - if converged < 100*time.Millisecond { - t.Logf("Timeout 收敛到 %v(localhost,正常)", converged) + + // minTO 下限断言:max(500ms, 3s/5) = 600ms + // localhost RTT 极低,收敛值应贴在地板上(issue #503) + minFloor := 600 * time.Millisecond + if converged < minFloor { + t.Errorf("收敛后 Timeout = %v, 不应低于 minTO 下限 %v", converged, minFloor) } - t.Logf("AdaptiveTimeout 收敛: 3s -> %v (%d 个样本)", converged, 20) + t.Logf("AdaptiveTimeout 收敛: 3s -> %v (%d 个样本), minTO=%v", converged, 20, minFloor) } // =============================================================================