fix: 大规模扫描全局超时过短导致提前终止 (#588)

4 万 IP 全端口扫描默认 -gt 180s 完全不够用,3 分钟后报
"解析目标失败: context deadline exceeded" 误导用户。

1. 自适应全局超时:用户未显式指定 -gt 时,根据端口数和是否有
   hosts 文件自动调大超时(最高 24h),并输出调整日志
2. 修正超时错误信息:context deadline exceeded 不再包装为
   "解析目标失败",改为提示用户调大 -gt 或设为 0 禁用
This commit is contained in:
ZacharyZcR
2026-06-27 16:42:32 +08:00
parent a3ccc2827b
commit 1980504007
8 changed files with 64 additions and 2 deletions
+5
View File
@@ -88,6 +88,11 @@ func (s *AliveScanStrategy) performAliveScan(ctx context.Context, info common.Ho
for {
hosts, err := iter.NextBatch(ctx, targetHostBatchSize(session.Config))
if err != nil {
if ctx.Err() != nil {
session.LogError(i18n.Tr("global_timeout_exceeded",
int(session.Config.GlobalTimeout.Seconds())))
return
}
session.LogError(i18n.Tr("parse_target_failed", err))
return
}
+39
View File
@@ -13,6 +13,7 @@ import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/parsers"
"github.com/shadow1ng/fscan/plugins"
"github.com/shadow1ng/fscan/webscan/lib"
)
@@ -96,6 +97,15 @@ func RunScan(ctx context.Context, info common.HostInfo, session *common.ScanSess
start := time.Now()
config := session.Config
// 全局超时自适应:用户未显式指定 -gt 时,根据扫描规模自动调大
if !config.GlobalTimeoutExplicit && config.GlobalTimeout > 0 {
if adjusted := estimateGlobalTimeout(config, session); adjusted > config.GlobalTimeout {
session.LogInfo(i18n.Tr("global_timeout_adjusted",
int(config.GlobalTimeout.Seconds()), int(adjusted.Seconds())))
config.GlobalTimeout = adjusted
}
}
// 全局超时:-gt 参数设置整个扫描的硬性截止时间
var cancel context.CancelFunc
if config.GlobalTimeout > 0 {
@@ -489,3 +499,32 @@ func addCommonDetails(result *plugins.Result, details map[string]interface{}) {
details["server"] = result.Server
}
}
func estimateGlobalTimeout(config *common.Config, session *common.ScanSession) time.Duration {
portCount := len(parsers.ParsePort(config.Target.Ports))
if portCount == 0 {
portCount = len(parsers.ParsePort("21,22,80,443,445,1433,3306,3389,6379,8080"))
}
hasHostFile := session.Params != nil && session.Params.HostsFile != ""
// 启发式:端口数越多、有文件输入(目标可能很多),超时越大
switch {
case portCount > 10000 && hasHostFile:
return 24 * time.Hour
case portCount > 10000:
return 6 * time.Hour
case portCount > 1000 && hasHostFile:
return 6 * time.Hour
case portCount > 1000:
return 1 * time.Hour
case portCount > 100 && hasHostFile:
return 1 * time.Hour
case portCount > 100:
return 30 * time.Minute
case hasHostFile:
return 30 * time.Minute
default:
return config.GlobalTimeout
}
}
+5
View File
@@ -161,6 +161,11 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm
for {
hosts, err := iter.NextBatch(ctx, targetHostBatchSize(config))
if err != nil {
if ctx.Err() != nil {
session.LogError(i18n.Tr("global_timeout_exceeded",
int(config.GlobalTimeout.Seconds())))
return
}
session.LogError(fmt.Sprintf("%s: %v", i18n.GetText("parse_target_failed"), err))
return
}