mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
feat: stream large host scans
This commit is contained in:
+27
-25
@@ -63,52 +63,54 @@ func (s *AliveScanStrategy) Execute(ctx context.Context, session *common.ScanSes
|
||||
|
||||
// 执行存活探测
|
||||
s.performAliveScan(ctx, info, session)
|
||||
|
||||
// 输出统计信息
|
||||
s.outputStats(session)
|
||||
}
|
||||
|
||||
// performAliveScan 执行存活探测
|
||||
func (s *AliveScanStrategy) performAliveScan(ctx context.Context, info common.HostInfo, session *common.ScanSession) {
|
||||
// 解析目标主机
|
||||
hosts, err := parsers.ParseIP(info.Host, session.Params.HostsFile, session.Params.ExcludeHosts)
|
||||
iter, err := parsers.NewHostIterator(info.Host, session.Params.HostsFile, session.Params.ExcludeHosts)
|
||||
if err != nil {
|
||||
session.LogError(i18n.Tr("parse_target_failed", err))
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = iter.Close()
|
||||
}()
|
||||
|
||||
if len(hosts) == 0 {
|
||||
s.stats.TotalHosts = 0
|
||||
s.stats.AliveHosts = 0
|
||||
s.stats.DeadHosts = 0
|
||||
|
||||
for {
|
||||
hosts, err := iter.NextBatch(ctx, targetHostBatchSize(session.Config))
|
||||
if err != nil {
|
||||
session.LogError(i18n.Tr("parse_target_failed", err))
|
||||
return
|
||||
}
|
||||
if len(hosts) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
s.stats.TotalHosts += len(hosts)
|
||||
aliveList := CheckLive(ctx, hosts, false, session)
|
||||
s.stats.AliveHosts += len(aliveList)
|
||||
for _, host := range aliveList {
|
||||
session.LogSuccess(fmt.Sprintf("alive %s", host))
|
||||
}
|
||||
}
|
||||
|
||||
if s.stats.TotalHosts == 0 {
|
||||
session.LogError(i18n.GetText("parse_error_no_hosts"))
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化统计信息
|
||||
s.stats.TotalHosts = len(hosts)
|
||||
s.stats.AliveHosts = 0
|
||||
s.stats.DeadHosts = 0
|
||||
|
||||
// 执行存活检测
|
||||
aliveList := CheckLive(ctx, hosts, false, session) // 使用ICMP探测
|
||||
|
||||
// 更新统计信息
|
||||
s.stats.AliveHosts = len(aliveList)
|
||||
s.stats.DeadHosts = s.stats.TotalHosts - s.stats.AliveHosts
|
||||
s.stats.ScanDuration = time.Since(s.startTime)
|
||||
s.stats.AliveHostList = aliveList // 存储存活主机列表
|
||||
|
||||
if s.stats.TotalHosts > 0 {
|
||||
s.stats.SuccessRate = float64(s.stats.AliveHosts) / float64(s.stats.TotalHosts) * 100
|
||||
}
|
||||
}
|
||||
|
||||
// outputStats 输出统计信息(精简版)
|
||||
func (s *AliveScanStrategy) outputStats(session *common.ScanSession) {
|
||||
// 只输出存活主机列表,不输出冗余统计
|
||||
for _, host := range s.stats.AliveHostList {
|
||||
session.LogSuccess(fmt.Sprintf("alive %s", host))
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareTargets 存活探测不需要准备扫描目标
|
||||
func (s *AliveScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo {
|
||||
// 存活探测不需要返回目标列表,因为它不进行后续扫描
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/common/parsers"
|
||||
)
|
||||
|
||||
const maxHostBatchSize = 65536
|
||||
|
||||
func targetHostBatchSize(config *common.Config) int {
|
||||
size := parsers.DefaultHostBatchSize
|
||||
if config != nil && config.ThreadNum > 0 {
|
||||
threadWindow := config.ThreadNum * 8
|
||||
if threadWindow > size {
|
||||
size = threadWindow
|
||||
}
|
||||
}
|
||||
if size > maxHostBatchSize {
|
||||
return maxHostBatchSize
|
||||
}
|
||||
return size
|
||||
}
|
||||
+2
-2
@@ -437,7 +437,7 @@ func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) s
|
||||
info = append(info, fmt.Sprintf("Version:%s", serviceInfo.Version))
|
||||
}
|
||||
if len(info) > 0 {
|
||||
msg.WriteString(fmt.Sprintf(" [%s]", strings.Join(info, " ||")))
|
||||
fmt.Fprintf(&msg, " [%s]", strings.Join(info, " ||"))
|
||||
}
|
||||
|
||||
// Banner 信息
|
||||
@@ -446,7 +446,7 @@ func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) s
|
||||
if len(banner) > 80 {
|
||||
banner = banner[:80] + "..."
|
||||
}
|
||||
msg.WriteString(fmt.Sprintf(" Banner:(%s)", banner))
|
||||
fmt.Fprintf(&msg, " Banner:(%s)", banner)
|
||||
}
|
||||
|
||||
return msg.String()
|
||||
|
||||
@@ -15,7 +15,7 @@ func BytesToRegexSafeString(b []byte) string {
|
||||
for _, c := range b {
|
||||
if c < 32 || c >= 128 {
|
||||
// 控制字符和高位字节转换为 \x{NN} 格式
|
||||
result.WriteString(fmt.Sprintf("\\x{%02x}", c))
|
||||
fmt.Fprintf(&result, "\\x{%02x}", c)
|
||||
} else {
|
||||
result.WriteByte(c)
|
||||
}
|
||||
|
||||
+55
-36
@@ -145,46 +145,76 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm
|
||||
config := session.Config
|
||||
state := session.State
|
||||
|
||||
// 解析目标主机
|
||||
hosts, err := parsers.ParseIP(info.Host, session.Params.HostsFile, session.Params.ExcludeHosts)
|
||||
iter, err := parsers.NewHostIterator(info.Host, session.Params.HostsFile, session.Params.ExcludeHosts)
|
||||
if err != nil {
|
||||
session.LogError(fmt.Sprintf("%s: %v", i18n.GetText("parse_target_failed"), err))
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = iter.Close()
|
||||
}()
|
||||
|
||||
// 主机存活检测
|
||||
if s.shouldPerformLivenessCheck(hosts, config) {
|
||||
hosts = CheckLive(ctx, hosts, false, session)
|
||||
session.LogInfo(i18n.Tr("alive_hosts_count_info", len(hosts)))
|
||||
pluginsToRun, isCustomMode := s.GetPlugins(config)
|
||||
totalAlive := 0
|
||||
sawHosts := false
|
||||
|
||||
for {
|
||||
hosts, err := iter.NextBatch(ctx, targetHostBatchSize(config))
|
||||
if err != nil {
|
||||
session.LogError(fmt.Sprintf("%s: %v", i18n.GetText("parse_target_failed"), err))
|
||||
return
|
||||
}
|
||||
if len(hosts) == 0 {
|
||||
break
|
||||
}
|
||||
sawHosts = true
|
||||
|
||||
if s.shouldPerformLivenessCheck(hosts, config) {
|
||||
hosts = CheckLive(ctx, hosts, false, session)
|
||||
}
|
||||
totalAlive += len(hosts)
|
||||
if len(hosts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg)
|
||||
s.scanHostBatch(ctx, session, hosts, info, pluginsToRun, isCustomMode, ch, wg)
|
||||
}
|
||||
|
||||
if len(hosts) == 0 && len(state.GetHostPorts()) == 0 {
|
||||
if sawHosts && s.shouldReportAliveCount(config) {
|
||||
session.LogInfo(i18n.Tr("alive_hosts_count_info", totalAlive))
|
||||
}
|
||||
|
||||
if !sawHosts && len(state.GetHostPorts()) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// UDP 插件并行分发:直接对存活主机发协议探测包,不走端口扫描
|
||||
if len(hosts) > 0 {
|
||||
s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg)
|
||||
// 合并预设的 host:port
|
||||
hostPorts := state.GetHostPorts()
|
||||
if len(hostPorts) > 0 {
|
||||
merged := mergeHostPorts(nil, hostPorts)
|
||||
targets := s.convertToTargetInfos(merged, info)
|
||||
for _, target := range targets {
|
||||
for _, pluginName := range pluginsToRun {
|
||||
if s.IsPluginApplicableByName(pluginName, target.Host, target.Port, isCustomMode, config) {
|
||||
executeScanTask(ctx, session, pluginName, target, ch, wg)
|
||||
}
|
||||
}
|
||||
}
|
||||
state.ClearHostPorts()
|
||||
}
|
||||
}
|
||||
|
||||
// 流式 channel:端口扫描发现开放端口后立即通知插件执行
|
||||
func (s *ServiceScanStrategy) scanHostBatch(ctx context.Context, session *common.ScanSession, hosts []string, info common.HostInfo, pluginsToRun []string, isCustomMode bool, ch chan struct{}, wg *sync.WaitGroup) {
|
||||
config := session.Config
|
||||
stream := make(chan string, 64)
|
||||
|
||||
// 启动端口扫描 goroutine
|
||||
go func() {
|
||||
if len(hosts) > 0 {
|
||||
EnhancedPortScan(ctx, hosts, config.Target.Ports, int64(config.Timeout.Seconds()), session, stream)
|
||||
} else {
|
||||
close(stream)
|
||||
}
|
||||
}()
|
||||
go EnhancedPortScan(ctx, hosts, config.Target.Ports, int64(config.Timeout.Seconds()), session, stream)
|
||||
|
||||
// pipeline 消费:边收开放端口边执行插件
|
||||
pluginsToRun, isCustomMode := s.GetPlugins(config)
|
||||
cancelled := false
|
||||
for addr := range stream {
|
||||
if cancelled {
|
||||
continue // ctx 已取消,排空 stream 防止写端阻塞
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -202,21 +232,10 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 合并预设的 host:port
|
||||
hostPorts := state.GetHostPorts()
|
||||
if len(hostPorts) > 0 {
|
||||
merged := mergeHostPorts(nil, hostPorts)
|
||||
targets := s.convertToTargetInfos(merged, info)
|
||||
for _, target := range targets {
|
||||
for _, pluginName := range pluginsToRun {
|
||||
if s.IsPluginApplicableByName(pluginName, target.Host, target.Port, isCustomMode, config) {
|
||||
executeScanTask(ctx, session, pluginName, target, ch, wg)
|
||||
}
|
||||
}
|
||||
}
|
||||
state.ClearHostPorts()
|
||||
}
|
||||
func (s *ServiceScanStrategy) shouldReportAliveCount(config *common.Config) bool {
|
||||
return !config.DisablePing
|
||||
}
|
||||
|
||||
// dispatchUDPPlugins 分发UDP协议插件,跳过TCP端口扫描链路
|
||||
|
||||
Reference in New Issue
Block a user