mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-25 04:31:52 +08:00
feat: v2.1.0 核心重构与功能增强
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n
## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池
## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)
## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建
## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
JSON 输出格式、Redis 检测、Context 超时等
## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
)
|
||||
|
||||
// AdaptivePool 自适应线程池
|
||||
// 封装 ants.PoolWithFunc,支持根据资源耗尽率动态调整线程数
|
||||
type AdaptivePool struct {
|
||||
pool *ants.PoolWithFunc
|
||||
state *common.State
|
||||
|
||||
initialSize int
|
||||
minSize int
|
||||
maxSize int
|
||||
currentSize int32 // 原子操作
|
||||
|
||||
// 监控参数
|
||||
checkInterval time.Duration
|
||||
lastCheck time.Time
|
||||
lastExhaustedCount int64
|
||||
lastPacketCount int64
|
||||
|
||||
// 阈值
|
||||
exhaustedThreshold float64 // 资源耗尽率阈值(触发降级)
|
||||
recoveryThreshold float64 // 恢复阈值(允许升级)
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewAdaptivePool 创建自适应线程池
|
||||
func NewAdaptivePool(size int, fn func(interface{}), state *common.State) (*AdaptivePool, error) {
|
||||
pool, err := ants.NewPoolWithFunc(size, fn, ants.WithPreAlloc(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
minSize := size / 4
|
||||
if minSize < 10 {
|
||||
minSize = 10
|
||||
}
|
||||
|
||||
return &AdaptivePool{
|
||||
pool: pool,
|
||||
state: state,
|
||||
initialSize: size,
|
||||
minSize: minSize,
|
||||
maxSize: size,
|
||||
currentSize: int32(size),
|
||||
checkInterval: time.Second,
|
||||
exhaustedThreshold: 0.10, // 10% 资源耗尽率触发降级
|
||||
recoveryThreshold: 0.02, // 2% 以下允许恢复
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Invoke 提交任务,并在适当时机检查是否需要调整线程数
|
||||
func (ap *AdaptivePool) Invoke(task interface{}) error {
|
||||
ap.maybeAdjust()
|
||||
return ap.pool.Invoke(task)
|
||||
}
|
||||
|
||||
// maybeAdjust 检查并可能调整线程池大小
|
||||
func (ap *AdaptivePool) maybeAdjust() {
|
||||
now := time.Now()
|
||||
|
||||
ap.mu.Lock()
|
||||
if now.Sub(ap.lastCheck) < ap.checkInterval {
|
||||
ap.mu.Unlock()
|
||||
return
|
||||
}
|
||||
ap.lastCheck = now
|
||||
|
||||
// 获取当前计数
|
||||
currentExhausted := ap.state.GetResourceExhaustedCount()
|
||||
currentPackets := ap.state.GetPacketCount()
|
||||
|
||||
// 计算增量(本周期内的耗尽率)
|
||||
deltaExhausted := currentExhausted - ap.lastExhaustedCount
|
||||
deltaPackets := currentPackets - ap.lastPacketCount
|
||||
|
||||
ap.lastExhaustedCount = currentExhausted
|
||||
ap.lastPacketCount = currentPackets
|
||||
ap.mu.Unlock()
|
||||
|
||||
// 需要足够的样本才能判断
|
||||
if deltaPackets < 100 {
|
||||
return
|
||||
}
|
||||
|
||||
rate := float64(deltaExhausted) / float64(deltaPackets)
|
||||
currentSize := int(atomic.LoadInt32(&ap.currentSize))
|
||||
|
||||
if rate > ap.exhaustedThreshold && currentSize > ap.minSize {
|
||||
// 降级:减少 20% 线程
|
||||
newSize := int(float64(currentSize) * 0.8)
|
||||
if newSize < ap.minSize {
|
||||
newSize = ap.minSize
|
||||
}
|
||||
ap.tune(newSize)
|
||||
common.LogInfo(fmt.Sprintf("[AdaptivePool] 资源耗尽率 %.1f%%, 线程数 %d -> %d", rate*100, currentSize, newSize))
|
||||
} else if rate < ap.recoveryThreshold && currentSize < ap.maxSize {
|
||||
// 恢复:增加 10% 线程(保守恢复)
|
||||
newSize := int(float64(currentSize) * 1.1)
|
||||
if newSize > ap.maxSize {
|
||||
newSize = ap.maxSize
|
||||
}
|
||||
if newSize > currentSize {
|
||||
ap.tune(newSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tune 调整线程池大小
|
||||
func (ap *AdaptivePool) tune(newSize int) {
|
||||
ap.pool.Tune(newSize)
|
||||
atomic.StoreInt32(&ap.currentSize, int32(newSize))
|
||||
}
|
||||
|
||||
// Running 返回当前运行中的 goroutine 数量
|
||||
func (ap *AdaptivePool) Running() int {
|
||||
return ap.pool.Running()
|
||||
}
|
||||
|
||||
// Cap 返回当前池容量
|
||||
func (ap *AdaptivePool) Cap() int {
|
||||
return int(atomic.LoadInt32(&ap.currentSize))
|
||||
}
|
||||
|
||||
// Release 释放线程池
|
||||
func (ap *AdaptivePool) Release() {
|
||||
ap.pool.Release()
|
||||
}
|
||||
|
||||
// Wait 等待所有任务完成
|
||||
func (ap *AdaptivePool) Wait() {
|
||||
// ants 没有原生 Wait,通过 Running() == 0 轮询
|
||||
for ap.pool.Running() > 0 {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user