mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-02-09 10:19:19 +08:00
## 架构重构
- 全局变量消除,迁移至 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)
458 lines
13 KiB
Go
458 lines
13 KiB
Go
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
|
||
}
|