Files
fscan/common/state.go
ZacharyZcR 71b92d4408 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)
2026-01-11 20:16:23 +08:00

458 lines
13 KiB
Go
Raw Permalink 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 common
import (
"encoding/json"
"sync"
"sync/atomic"
"time"
"github.com/juju/ratelimit"
)
/*
state.go - 运行时状态管理
可变状态,有明确的所有权和线程安全保护。
所有修改通过方法进行,原子操作保证并发安全。
*/
// =============================================================================
// State - 可变运行时状态
// =============================================================================
// State 扫描器运行时状态 - 线程安全
type State struct {
// 计数器 - 原子操作
packetCount int64
tcpPacketCount int64
tcpSuccessPacketCount int64
tcpFailedPacketCount int64
udpPacketCount int64
httpPacketCount int64
resourceExhaustedCount int64
// 任务计数
end int64
num int64
// 时间
startTime time.Time
// 输出互斥锁
outputMutex sync.Mutex
// 限速器 - 统一使用令牌桶算法
icmpLimiter *ratelimit.Bucket // ICMP包限速秒级平滑
packetLimiter *ratelimit.Bucket // 通用发包限速
icmpInitOnce sync.Once
packetInitOnce sync.Once
// 运行时目标数据(解析后填充)
urls []string
hostPorts []string
urlsMu sync.RWMutex
// Shell状态插件设置
forwardShellActive int32 // 使用int32以便原子操作
reverseShellActive int32
socks5ProxyActive int32
}
// NewState 创建新的状态对象
func NewState() *State {
return &State{
startTime: time.Now(),
}
}
// =============================================================================
// 包计数器方法 - 原子操作
// =============================================================================
// IncrementPacketCount 增加总包计数
func (s *State) IncrementPacketCount() int64 {
return atomic.AddInt64(&s.packetCount, 1)
}
// IncrementTCPSuccessPacketCount 增加TCP成功连接包计数
func (s *State) IncrementTCPSuccessPacketCount() int64 {
atomic.AddInt64(&s.tcpSuccessPacketCount, 1)
atomic.AddInt64(&s.tcpPacketCount, 1)
return atomic.AddInt64(&s.packetCount, 1)
}
// IncrementTCPFailedPacketCount 增加TCP失败连接包计数
func (s *State) IncrementTCPFailedPacketCount() int64 {
atomic.AddInt64(&s.tcpFailedPacketCount, 1)
atomic.AddInt64(&s.tcpPacketCount, 1)
return atomic.AddInt64(&s.packetCount, 1)
}
// IncrementUDPPacketCount 增加UDP包计数
func (s *State) IncrementUDPPacketCount() int64 {
atomic.AddInt64(&s.udpPacketCount, 1)
return atomic.AddInt64(&s.packetCount, 1)
}
// IncrementHTTPPacketCount 增加HTTP包计数
func (s *State) IncrementHTTPPacketCount() int64 {
atomic.AddInt64(&s.httpPacketCount, 1)
return atomic.AddInt64(&s.packetCount, 1)
}
// IncrementResourceExhaustedCount 增加资源耗尽错误计数
func (s *State) IncrementResourceExhaustedCount() {
atomic.AddInt64(&s.resourceExhaustedCount, 1)
}
// =============================================================================
// 获取计数器方法 - 原子操作
// =============================================================================
// GetPacketCount 获取总包计数
func (s *State) GetPacketCount() int64 {
return atomic.LoadInt64(&s.packetCount)
}
// GetTCPPacketCount 获取TCP包计数
func (s *State) GetTCPPacketCount() int64 {
return atomic.LoadInt64(&s.tcpPacketCount)
}
// GetTCPSuccessPacketCount 获取TCP成功连接包计数
func (s *State) GetTCPSuccessPacketCount() int64 {
return atomic.LoadInt64(&s.tcpSuccessPacketCount)
}
// GetTCPFailedPacketCount 获取TCP失败连接包计数
func (s *State) GetTCPFailedPacketCount() int64 {
return atomic.LoadInt64(&s.tcpFailedPacketCount)
}
// GetUDPPacketCount 获取UDP包计数
func (s *State) GetUDPPacketCount() int64 {
return atomic.LoadInt64(&s.udpPacketCount)
}
// GetHTTPPacketCount 获取HTTP包计数
func (s *State) GetHTTPPacketCount() int64 {
return atomic.LoadInt64(&s.httpPacketCount)
}
// GetResourceExhaustedCount 获取资源耗尽错误计数
func (s *State) GetResourceExhaustedCount() int64 {
return atomic.LoadInt64(&s.resourceExhaustedCount)
}
// ResetPacketCounters 重置所有包计数器
func (s *State) ResetPacketCounters() {
atomic.StoreInt64(&s.packetCount, 0)
atomic.StoreInt64(&s.tcpPacketCount, 0)
atomic.StoreInt64(&s.tcpSuccessPacketCount, 0)
atomic.StoreInt64(&s.tcpFailedPacketCount, 0)
atomic.StoreInt64(&s.udpPacketCount, 0)
atomic.StoreInt64(&s.httpPacketCount, 0)
atomic.StoreInt64(&s.resourceExhaustedCount, 0)
}
// =============================================================================
// 任务计数器方法
// =============================================================================
// GetEnd 获取结束计数
func (s *State) GetEnd() int64 {
return atomic.LoadInt64(&s.end)
}
// GetNum 获取数量计数
func (s *State) GetNum() int64 {
return atomic.LoadInt64(&s.num)
}
// IncrementEnd 增加结束计数
func (s *State) IncrementEnd() int64 {
return atomic.AddInt64(&s.end, 1)
}
// IncrementNum 增加数量计数
func (s *State) IncrementNum() int64 {
return atomic.AddInt64(&s.num, 1)
}
// SetEnd 设置结束计数
func (s *State) SetEnd(val int64) {
atomic.StoreInt64(&s.end, val)
}
// SetNum 设置数量计数
func (s *State) SetNum(val int64) {
atomic.StoreInt64(&s.num, val)
}
// =============================================================================
// 时间和进度方法
// =============================================================================
// GetStartTime 获取开始时间
func (s *State) GetStartTime() time.Time {
return s.startTime
}
// =============================================================================
// 输出互斥锁方法
// =============================================================================
// LockOutput 锁定输出
func (s *State) LockOutput() {
s.outputMutex.Lock()
}
// UnlockOutput 解锁输出
func (s *State) UnlockOutput() {
s.outputMutex.Unlock()
}
// GetOutputMutex 获取输出互斥锁指针
func (s *State) GetOutputMutex() *sync.Mutex {
return &s.outputMutex
}
// =============================================================================
// ICMP 限速器方法
// =============================================================================
// GetICMPLimiter 获取 ICMP 令牌桶限速器(延迟初始化)
func (s *State) GetICMPLimiter(icmpRate float64) *ratelimit.Bucket {
s.icmpInitOnce.Do(func() {
const (
maxRate = 1.0 * 1024 * 1024 // 1MB/s 基准速率
packetSize = 70 // ICMP 包平均大小
)
adjustedRate := maxRate * icmpRate
packetsPerSecond := adjustedRate / float64(packetSize)
if packetsPerSecond < 1 {
packetsPerSecond = 1
}
bucketLimit := int64(packetsPerSecond)
packetTime := time.Second / time.Duration(packetsPerSecond)
s.icmpLimiter = ratelimit.NewBucketWithQuantum(
packetTime,
bucketLimit,
int64(1),
)
})
return s.icmpLimiter
}
// =============================================================================
// 性能统计导出
// =============================================================================
// PerfStatsData 性能统计数据结构
type PerfStatsData struct {
TotalPackets int64 `json:"total_packets"`
TCPPackets int64 `json:"tcp_packets"`
TCPSuccess int64 `json:"tcp_success"`
TCPFailed int64 `json:"tcp_failed"`
UDPPackets int64 `json:"udp_packets"`
HTTPPackets int64 `json:"http_packets"`
ResourceExhausted int64 `json:"resource_exhausted"`
ScanDurationMs int64 `json:"scan_duration_ms"`
PacketsPerSecond float64 `json:"packets_per_second"`
SuccessRate float64 `json:"success_rate"`
TargetsScanned int64 `json:"targets_scanned"`
}
// GetPerfStats 获取性能统计数据
func (s *State) GetPerfStats() PerfStatsData {
duration := time.Since(s.startTime)
durationMs := duration.Milliseconds()
totalPackets := atomic.LoadInt64(&s.packetCount)
tcpSuccess := atomic.LoadInt64(&s.tcpSuccessPacketCount)
tcpFailed := atomic.LoadInt64(&s.tcpFailedPacketCount)
tcpTotal := atomic.LoadInt64(&s.tcpPacketCount)
var pps float64
if durationMs > 0 {
pps = float64(totalPackets) / (float64(durationMs) / 1000.0)
}
var successRate float64
if tcpTotal > 0 {
successRate = float64(tcpSuccess) / float64(tcpTotal) * 100.0
}
return PerfStatsData{
TotalPackets: totalPackets,
TCPPackets: tcpTotal,
TCPSuccess: tcpSuccess,
TCPFailed: tcpFailed,
UDPPackets: atomic.LoadInt64(&s.udpPacketCount),
HTTPPackets: atomic.LoadInt64(&s.httpPacketCount),
ResourceExhausted: atomic.LoadInt64(&s.resourceExhaustedCount),
ScanDurationMs: durationMs,
PacketsPerSecond: pps,
SuccessRate: successRate,
TargetsScanned: atomic.LoadInt64(&s.num),
}
}
// GetPerfStatsJSON 获取性能统计 JSON 字符串
func (s *State) GetPerfStatsJSON() string {
stats := s.GetPerfStats()
data, err := json.Marshal(stats)
if err != nil {
return "{}"
}
return string(data)
}
// =============================================================================
// 运行时目标数据方法
// =============================================================================
// GetURLs 获取URL列表
func (s *State) GetURLs() []string {
s.urlsMu.RLock()
defer s.urlsMu.RUnlock()
return s.urls
}
// SetURLs 设置URL列表
func (s *State) SetURLs(urls []string) {
s.urlsMu.Lock()
defer s.urlsMu.Unlock()
s.urls = urls
}
// GetHostPorts 获取主机端口列表
func (s *State) GetHostPorts() []string {
s.urlsMu.RLock()
defer s.urlsMu.RUnlock()
return s.hostPorts
}
// SetHostPorts 设置主机端口列表
func (s *State) SetHostPorts(hostPorts []string) {
s.urlsMu.Lock()
defer s.urlsMu.Unlock()
s.hostPorts = hostPorts
}
// ClearHostPorts 清空主机端口列表
func (s *State) ClearHostPorts() {
s.urlsMu.Lock()
defer s.urlsMu.Unlock()
s.hostPorts = nil
}
// =============================================================================
// Shell状态方法
// =============================================================================
// IsForwardShellActive 检查正向Shell是否活跃
func (s *State) IsForwardShellActive() bool {
return atomic.LoadInt32(&s.forwardShellActive) == 1
}
// SetForwardShellActive 设置正向Shell活跃状态
func (s *State) SetForwardShellActive(active bool) {
if active {
atomic.StoreInt32(&s.forwardShellActive, 1)
} else {
atomic.StoreInt32(&s.forwardShellActive, 0)
}
}
// IsReverseShellActive 检查反向Shell是否活跃
func (s *State) IsReverseShellActive() bool {
return atomic.LoadInt32(&s.reverseShellActive) == 1
}
// SetReverseShellActive 设置反向Shell活跃状态
func (s *State) SetReverseShellActive(active bool) {
if active {
atomic.StoreInt32(&s.reverseShellActive, 1)
} else {
atomic.StoreInt32(&s.reverseShellActive, 0)
}
}
// IsSocks5ProxyActive 检查SOCKS5代理是否活跃
func (s *State) IsSocks5ProxyActive() bool {
return atomic.LoadInt32(&s.socks5ProxyActive) == 1
}
// SetSocks5ProxyActive 设置SOCKS5代理活跃状态
func (s *State) SetSocks5ProxyActive(active bool) {
if active {
atomic.StoreInt32(&s.socks5ProxyActive, 1)
} else {
atomic.StoreInt32(&s.socks5ProxyActive, 0)
}
}
// =============================================================================
// 发包频率控制方法 - 统一使用令牌桶算法
// =============================================================================
// GetPacketLimiter 获取通用发包限速器(延迟初始化)
// rateLimit: 每分钟允许的包数,转换为令牌桶的秒级速率
func (s *State) GetPacketLimiter(rateLimit int64) *ratelimit.Bucket {
s.packetInitOnce.Do(func() {
if rateLimit <= 0 {
return
}
// 将每分钟包数转换为每秒速率
packetsPerSecond := float64(rateLimit) / 60.0
if packetsPerSecond < 1 {
packetsPerSecond = 1
}
// 令牌填充间隔
fillInterval := time.Second / time.Duration(packetsPerSecond)
// 桶容量设为每秒速率的2倍允许小突发
bucketCapacity := int64(packetsPerSecond * 2)
if bucketCapacity < 1 {
bucketCapacity = 1
}
s.packetLimiter = ratelimit.NewBucketWithQuantum(
fillInterval,
bucketCapacity,
1,
)
})
return s.packetLimiter
}
// CheckAndIncrementPacketRate 检查并消耗发包令牌
// 返回: (可以发包, 错误)
// 使用令牌桶算法统一与ICMP限速器的实现方式
func (s *State) CheckAndIncrementPacketRate(rateLimit int64) (bool, error) {
if rateLimit <= 0 {
return true, nil
}
limiter := s.GetPacketLimiter(rateLimit)
if limiter == nil {
return true, nil
}
// 尝试获取一个令牌(非阻塞)
if limiter.TakeAvailable(1) < 1 {
return false, &PacketLimitError{
Sentinel: ErrPacketRateLimited,
Limit: rateLimit,
}
}
return true, nil
}