fscan/core/Scanner.go
ZacharyZcR d981f0100f perf: 优化插件系统性能,消除重复实例化问题
- 添加plugins.Exists()函数,避免不必要的插件实例创建
- 合并PluginInfo数据结构,统一插件工厂和端口信息存储
- 修复Scanner中重复调用plugins.Get()的性能问题
- 优化BaseScanStrategy.pluginExists()实现效率

主要性能改进:
* 消除21×57=1197次不必要的本地插件实例化
* 提升插件存在性检查效率,从O(n)遍历优化为O(1)查找
* 改善数据内聚性,插件元数据集中管理
* 保持所有现有插件控制逻辑和功能完整性

测试验证:
* 无-local参数时不再创建本地插件实例
* 端口匹配、Web检测、互斥验证等功能正常
* 插件注册和执行逻辑保持向后兼容
2025-08-26 19:53:57 +08:00

199 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/webscan/lib"
"github.com/shadow1ng/fscan/plugins"
"strconv"
"sync"
"sync/atomic"
)
// ScanStrategy 定义扫描策略接口(简化版)
type ScanStrategy interface {
// 执行扫描的主要方法
Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup)
// 插件管理方法
GetPlugins() ([]string, bool)
IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool
IsPluginApplicableByName(pluginName string, targetHost string, targetPort int, isCustomMode bool) bool
}
// selectStrategy 根据扫描配置选择适当的扫描策略
func selectStrategy(info common.HostInfo) ScanStrategy {
switch {
case common.AliveOnly || common.ScanMode == "icmp":
common.LogBase(i18n.GetText("scan_mode_alive_selected"))
return NewAliveScanStrategy()
case common.LocalMode:
common.LogBase(i18n.GetText("scan_mode_local_selected"))
return NewLocalScanStrategy()
case len(common.URLs) > 0:
common.LogBase(i18n.GetText("scan_mode_web_selected"))
return NewWebScanStrategy()
default:
common.LogBase(i18n.GetText("scan_mode_service_selected"))
return NewServiceScanStrategy()
}
}
// RunScan 执行整体扫描流程(简化版,移除不必要的包装)
func RunScan(info common.HostInfo) {
common.LogBase(i18n.GetText("scan_info_start"))
lib.Inithttp()
// 选择策略
strategy := selectStrategy(info)
// 并发控制初始化
ch := make(chan struct{}, common.ThreadNum)
wg := sync.WaitGroup{}
// 执行策略
strategy.Execute(info, &ch, &wg)
// 等待所有扫描完成
wg.Wait()
// 检查是否有活跃的反弹Shell或SOCKS5代理
if common.ReverseShellActive {
common.LogBase("检测到活跃的反弹Shell保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持反弹Shell连接
select {} // 阻塞等待,直到收到系统信号
}
if common.Socks5ProxyActive {
common.LogBase("检测到活跃的SOCKS5代理保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持SOCKS5代理
select {} // 阻塞等待,直到收到系统信号
}
if common.ForwardShellActive {
common.LogBase("检测到活跃的正向Shell保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持正向Shell
select {} // 阻塞等待,直到收到系统信号
}
// 完成扫描
finishScan()
}
// finishScan 完成扫描并输出结果
func finishScan() {
// 确保进度条正确完成
if common.IsProgressActive() {
common.FinishProgressBar()
}
// 输出扫描完成信息
common.LogBase(i18n.GetText("scan_task_complete", common.End, common.Num))
}
// ExecuteScanTasks 任务执行通用框架(流式处理,无预构建任务列表)
func ExecuteScanTasks(targets []common.HostInfo, strategy ScanStrategy, ch *chan struct{}, wg *sync.WaitGroup) {
// 获取要执行的插件
pluginsToRun, isCustomMode := strategy.GetPlugins()
// 预计算任务数量用于进度条
taskCount := countApplicableTasks(targets, pluginsToRun, isCustomMode, strategy)
// 初始化进度条
if taskCount > 0 && common.ShowProgress {
description := i18n.GetText("progress_scanning_description")
common.InitProgressBar(int64(taskCount), description)
}
// 流式执行任务,避免预构建大量任务对象
for _, target := range targets {
targetPort := 0
if target.Ports != "" {
targetPort, _ = strconv.Atoi(target.Ports)
}
for _, pluginName := range pluginsToRun {
// 使用Exists检查避免不必要的插件实例创建
if !plugins.Exists(pluginName) {
continue
}
// 检查插件是否适用于当前目标
if strategy.IsPluginApplicableByName(pluginName, target.Host, targetPort, isCustomMode) {
executeScanTask(pluginName, target, ch, wg)
}
}
}
}
// countApplicableTasks 计算适用的任务数量(用于进度条初始化)
func countApplicableTasks(targets []common.HostInfo, pluginsToRun []string, isCustomMode bool, strategy ScanStrategy) int {
count := 0
for _, target := range targets {
targetPort := 0
if target.Ports != "" {
targetPort, _ = strconv.Atoi(target.Ports)
}
for _, pluginName := range pluginsToRun {
// 使用Exists检查避免不必要的插件实例创建
if plugins.Exists(pluginName) &&
strategy.IsPluginApplicableByName(pluginName, target.Host, targetPort, isCustomMode) {
count++
}
}
}
return count
}
// executeScanTask 执行单个扫描任务(合并原 scheduleScanTask 和 executeSingleScan
func executeScanTask(pluginName string, target common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
wg.Add(1)
*ch <- struct{}{} // 获取并发槽位
go func() {
// 开始监控插件任务
monitor := common.GetConcurrencyMonitor()
monitor.StartPluginTask()
defer func() {
// 捕获并记录任何可能的panic
if r := recover(); r != nil {
common.LogError(fmt.Sprintf(i18n.GetText("scan_plugin_panic"),
pluginName, target.Host, target.Ports, r))
}
// 完成任务,释放资源
monitor.FinishPluginTask()
wg.Done()
<-*ch // 释放并发槽位
}()
// 更新统计和进度
atomic.AddInt64(&common.Num, 1)
common.UpdateProgressBar(1)
// 执行扫描(使用统一插件系统)
plugin := plugins.Get(pluginName)
if plugin != nil {
result := plugin.Scan(context.Background(), &target)
if result != nil && result.Error != nil {
common.LogError(fmt.Sprintf(i18n.GetText("scan_plugin_error"), target.Host, target.Ports, result.Error))
}
}
}()
}
// 已移除未使用的 Scan 方法