fix: 修复高并发下自适应超时过低导致开放端口漏扫 (#598)

* 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)
This commit is contained in:
逸航
2026-07-16 01:07:27 +08:00
committed by GitHub
parent fdf836f003
commit 9d0010927e
5 changed files with 36 additions and 7 deletions
+8 -1
View File
@@ -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,
}
+13 -3
View File
@@ -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
}
// 记录资源耗尽错误
+7 -3
View File
@@ -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 收敛到 %vlocalhost,正常)", 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)
}
// =============================================================================