Skip to content

Commit 9d00109

Browse files
authored
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)
1 parent fdf836f commit 9d00109

5 files changed

Lines changed: 36 additions & 7 deletions

File tree

common/i18n/locales/en.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,10 @@ port_open_http:
429429
other: "Port open {{.Arg1}} [http](HTTP probe)"
430430
port_scan_no_alive_subnet:
431431
other: "Subnet probe found no alive subnets, skipping port scan"
432+
port_scan_task_dropped:
433+
other: "[PortScan] task dropped: {{.Arg1}} ({{.Arg2}}), port may be missed"
434+
port_scan_tasks_dropped_total:
435+
other: "[PortScan] {{.Arg1}} tasks dropped total, these ports may be missed"
432436
network_rate_limited_pattern:
433437
other: "Rate limited"
434438
port_scan_debug_start:

common/i18n/locales/zh.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,10 @@ port_open_http:
429429
other: "端口开放 {{.Arg1}} [http](HTTP探测)"
430430
port_scan_no_alive_subnet:
431431
other: "网段预筛未发现存活子网,跳过端口扫描"
432+
port_scan_task_dropped:
433+
other: "[PortScan] 任务被丢弃: {{.Arg1}} ({{.Arg2}}),该端口可能被漏扫"
434+
port_scan_tasks_dropped_total:
435+
other: "[PortScan] 共有 {{.Arg1}} 个任务被丢弃,这些端口可能被漏扫"
432436
network_rate_limited_pattern:
433437
other: "发包受限"
434438
port_scan_debug_start:

core/adaptive_timeout.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,17 @@ type AdaptiveTimeout struct {
2525
// NewAdaptiveTimeout 创建自适应超时计算器
2626
// maxTimeout: 用户配置的超时上限(即原始固定超时)
2727
func NewAdaptiveTimeout(maxTimeout time.Duration) *AdaptiveTimeout {
28+
// minTO: 自适应超时下限,取 max(500ms, maxTimeout/5)
29+
// 依据:高并发下 TCP 握手存在尾延迟(OS 调度抖动、backlog 溢出、端口竞争),
30+
// 过低的下限会导致开放端口被误判为关闭(issue #503)
31+
minTO := maxTimeout / 5
32+
if minTO < 500*time.Millisecond {
33+
minTO = 500 * time.Millisecond
34+
}
2835
return &AdaptiveTimeout{
2936
samples: make([]float64, 64),
3037
size: 64,
31-
minTO: 100 * time.Millisecond,
38+
minTO: minTO,
3239
maxTO: maxTimeout,
3340
warmup: 10,
3441
}

core/port_scan.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout
290290
// slidingWindowSchedule 滑动窗口调度器
291291
// ants.PoolWithFunc.Invoke 在池满时阻塞,天然提供反压,无需额外 semaphore
292292
func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.WaitGroup) {
293+
var dropped int64
293294
for {
294295
host, port, ok := iter.Next()
295296
if !ok {
@@ -304,11 +305,17 @@ func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.Wa
304305
}
305306
if err := pool.Invoke(task); err != nil {
306307
wg.Done()
308+
dropped++
309+
common.LogError(i18n.Tr("port_scan_task_dropped", task.addr, err))
307310
}
308311
}
309312

310313
// 等待所有任务完成
311314
wg.Wait()
315+
316+
if dropped > 0 {
317+
common.LogError(i18n.Tr("port_scan_tasks_dropped_total", dropped))
318+
}
312319
}
313320

314321
// fmtPort 无分配的端口号格式化
@@ -327,7 +334,10 @@ func fmtPort(port int) string {
327334
return string(buf[i:])
328335
}
329336

330-
// connectWithRetry 带重试的TCP连接 - 只对资源耗尽错误重试
337+
// connectWithRetry 带重试的TCP连接
338+
// - 资源耗尽错误:指数退避重试(maxRetries 次)
339+
// - 其他错误(如 connection refused、timeout):直接返回
340+
// timeout 是正常的扫描结果(防火墙 drop / filtered),不盲目重试
331341
func connectWithRetry(ctx context.Context, session *common.ScanSession, addr string, timeout time.Duration, maxRetries int) (net.Conn, error) {
332342
var lastErr error
333343

@@ -340,9 +350,9 @@ func connectWithRetry(ctx context.Context, session *common.ScanSession, addr str
340350

341351
lastErr = err
342352

343-
// 只对资源耗尽类错误重试,端口关闭直接返回
353+
// 只对资源耗尽类错误重试,端口关闭或超时直接返回
344354
if !isResourceExhaustedError(err) {
345-
return nil, err
355+
return nil, lastErr
346356
}
347357

348358
// 记录资源耗尽错误

core/real_network_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -440,11 +440,15 @@ func TestReal_AdaptiveTimeout_Convergence(t *testing.T) {
440440
if converged >= 3*time.Second {
441441
t.Errorf("采样后 Timeout = %v, 应该 < 3s", converged)
442442
}
443-
if converged < 100*time.Millisecond {
444-
t.Logf("Timeout 收敛到 %v(localhost,正常)", converged)
443+
444+
// minTO 下限断言:max(500ms, 3s/5) = 600ms
445+
// localhost RTT 极低,收敛值应贴在地板上(issue #503)
446+
minFloor := 600 * time.Millisecond
447+
if converged < minFloor {
448+
t.Errorf("收敛后 Timeout = %v, 不应低于 minTO 下限 %v", converged, minFloor)
445449
}
446450

447-
t.Logf("AdaptiveTimeout 收敛: 3s -> %v (%d 个样本)", converged, 20)
451+
t.Logf("AdaptiveTimeout 收敛: 3s -> %v (%d 个样本), minTO=%v", converged, 20, minFloor)
448452
}
449453

450454
// =============================================================================

0 commit comments

Comments
 (0)