diff --git a/common/parsers/host_iterator.go b/common/parsers/host_iterator.go index f937462..654cef6 100644 --- a/common/parsers/host_iterator.go +++ b/common/parsers/host_iterator.go @@ -488,3 +488,90 @@ func ipToUint32(ip net.IP) (uint32, bool) { func uint32ToIP(v uint32) string { return fmt.Sprintf("%d.%d.%d.%d", byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) } + +// EstimateHostCount 快速估算主机总数(不消费 iterator) +func EstimateHostCount(host string, filename string) int64 { + var total int64 + + if filename != "" { + if f, err := os.Open(filename); err == nil { + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + total += estimateHostEntry(line) + } + _ = f.Close() + } + } + + for _, h := range strings.Split(host, ",") { + h = strings.TrimSpace(h) + if h != "" { + total += estimateHostEntry(h) + } + } + + return total +} + +func estimateHostEntry(entry string) int64 { + switch { + case entry == "192": + return 65536 // /16 + case entry == "172": + return 1 << 20 // /12 + case entry == "10": + return 1 << 24 // /8 + case strings.Contains(entry, "/"): + _, ipNet, err := net.ParseCIDR(entry) + if err != nil { + return 1 + } + ones, bits := ipNet.Mask.Size() + if bits != 32 { + return 1 + } + size := int64(1) << uint(32-ones) + if size > 2 { + size -= 2 + } + return size + case strings.Contains(entry, "-") && !strings.Contains(entry, ":") && looksLikeIPRange(entry): + parts := strings.SplitN(entry, "-", 2) + startIP := net.ParseIP(strings.TrimSpace(parts[0])) + if startIP == nil { + return 1 + } + startU, ok := ipToUint32(startIP) + if !ok { + return 1 + } + endStr := strings.TrimSpace(parts[1]) + var endU uint32 + if len(endStr) < 4 || !strings.Contains(endStr, ".") { + n, err := strconv.Atoi(endStr) + if err != nil || n > 255 { + return 1 + } + endU = (startU & 0xFFFFFF00) | uint32(n) + } else { + endIP := net.ParseIP(endStr) + if endIP == nil { + return 1 + } + endU, ok = ipToUint32(endIP) + if !ok { + return 1 + } + } + if endU < startU { + return 1 + } + return int64(endU-startU) + 1 + default: + return 1 + } +} diff --git a/core/scanner.go b/core/scanner.go index 4f1e76f..5a20436 100644 --- a/core/scanner.go +++ b/core/scanner.go @@ -501,30 +501,50 @@ func addCommonDetails(result *plugins.Result, details map[string]interface{}) { } func estimateGlobalTimeout(config *common.Config, session *common.ScanSession) time.Duration { - portCount := len(parsers.ParsePort(config.Target.Ports)) + portCount := int64(len(parsers.ParsePort(config.Target.Ports))) if portCount == 0 { - portCount = len(parsers.ParsePort("21,22,80,443,445,1433,3306,3389,6379,8080")) + portCount = 10 } - 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 + var hostFile string + var hostStr string + if session.Params != nil { + hostFile = session.Params.HostsFile + hostStr = session.Params.Host } + hostCount := parsers.EstimateHostCount(hostStr, hostFile) + if hostCount <= 0 { + hostCount = 1 + } + + totalTasks := hostCount * portCount + threads := int64(config.ThreadNum) + if threads <= 0 { + threads = 600 + } + + // 端口扫描:平均每个任务约 50ms(大部分连接快速失败) + portScanSec := float64(totalTasks) * 0.05 / float64(threads) + + // 插件扫描:开放率随端口数下降(全端口约 0.1%,少量端口约 5%) + openRate := 0.05 + if portCount > 1000 { + openRate = 0.002 + } else if portCount > 100 { + openRate = 0.01 + } + moduleThreads := float64(config.ModuleThreadNum) + if moduleThreads <= 0 { + moduleThreads = 20 + } + pluginSec := float64(totalTasks) * openRate * 2.0 / moduleThreads + // 总估算 + 20% 余量 + estimatedSec := (portScanSec + pluginSec) * 1.2 + + const maxTimeout = 2 * time.Hour + estimated := time.Duration(estimatedSec) * time.Second + if estimated > maxTimeout { + estimated = maxTimeout + } + return estimated }