mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
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:
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
// 记录资源耗尽错误
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user