diff --git a/common/config_struct.go b/common/config_struct.go index e40323c..f7f0b08 100644 --- a/common/config_struct.go +++ b/common/config_struct.go @@ -63,7 +63,8 @@ type Config struct { Target TargetConfig // 扫描目标配置 // 全局超时 - GlobalTimeout time.Duration + GlobalTimeout time.Duration + GlobalTimeoutExplicit bool // SOCKS5代理端口配置 Socks5ProxyPort int // SOCKS5代理端口 diff --git a/common/flag.go b/common/flag.go index a3f24d7..85ef4fa 100644 --- a/common/flag.go +++ b/common/flag.go @@ -226,6 +226,8 @@ func Flag(Info *HostInfo) error { fv.ModuleThreadNumExplicit = true case "retry": fv.MaxRetriesExplicit = true + case "gt": + fv.GlobalTimeoutExplicit = true case "icmp-rate": fv.ICMPRateExplicit = true case "num": diff --git a/common/flag_config.go b/common/flag_config.go index 79c23da..160e3df 100644 --- a/common/flag_config.go +++ b/common/flag_config.go @@ -39,6 +39,7 @@ type FlagVars struct { TimeoutSec int64 // 秒,需转换为 time.Duration TimeoutExplicit bool GlobalTimeout int64 + GlobalTimeoutExplicit bool DisablePing bool DisableTcpProbe bool DisableSubnetProbe bool @@ -171,7 +172,8 @@ func BuildConfigFromFlags(fv *FlagVars) *Config { DefaultMap: cloneStringSlice(config.DefaultProbeMap), // 全局超时 - GlobalTimeout: time.Duration(fv.GlobalTimeout) * time.Second, + GlobalTimeout: time.Duration(fv.GlobalTimeout) * time.Second, + GlobalTimeoutExplicit: fv.GlobalTimeoutExplicit, // SOCKS5代理端口 Socks5ProxyPort: fv.Socks5ProxyPort, diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index 8916b6b..7e7d2ac 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -26,6 +26,10 @@ flag_module_thread_num: other: "Module thread count" flag_global_timeout: other: "Global timeout" +global_timeout_adjusted: + other: "Large scan detected, global timeout adjusted from {{.V0}}s to {{.V1}}s (use -gt to override)" +global_timeout_exceeded: + other: "Global timeout reached (-gt {{.V0}}s), scan aborted. Use -gt to increase or set to 0 to disable" flag_disable_ping: other: "Disable ping detection" flag_disable_tcp_probe: diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index c4ee421..9e40f41 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -26,6 +26,10 @@ flag_module_thread_num: other: "模块线程数" flag_global_timeout: other: "全局超时时间" +global_timeout_adjusted: + other: "扫描规模较大,全局超时从 {{.V0}}s 自动调整为 {{.V1}}s(可用 -gt 手动指定)" +global_timeout_exceeded: + other: "全局超时已到(-gt {{.V0}}s),扫描被终止。大规模扫描请用 -gt 调大超时或设为 0 禁用" flag_disable_ping: other: "禁用ping探测" flag_disable_tcp_probe: diff --git a/core/alive_scanner.go b/core/alive_scanner.go index 1d2f250..6ad8a99 100644 --- a/core/alive_scanner.go +++ b/core/alive_scanner.go @@ -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 } diff --git a/core/scanner.go b/core/scanner.go index dd1bc9a..4f1e76f 100644 --- a/core/scanner.go +++ b/core/scanner.go @@ -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 + } +} diff --git a/core/service_scanner.go b/core/service_scanner.go index 320f271..4c62c92 100644 --- a/core/service_scanner.go +++ b/core/service_scanner.go @@ -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 }