feat: 新增发包频率控制功能

- 添加-rate参数:控制每分钟最大发包次数
- 添加-maxpkts参数:控制整个程序最大发包总数
- 在所有网络操作点集成发包限制检查
- 支持端口扫描、Web检测、服务插件、POC扫描等场景
- 默认不限制,保持向后兼容性
This commit is contained in:
ZacharyZcR 2025-09-02 11:24:09 +00:00
parent a36767c158
commit d19abcac36
9 changed files with 148 additions and 0 deletions

View File

@ -63,6 +63,10 @@ var (
DownloadURL string // 下载文件的URL
DownloadSavePath string // 下载文件保存路径
// 发包频率控制相关变量
PacketRateLimit int64 // 每分钟最大发包次数 (0表示不限制)
MaxPacketCount int64 // 整个程序最大发包总数 (0表示不限制)
// Parse.go 使用的变量
HostPort []string
URLs []string
@ -199,6 +203,12 @@ func Flag(Info *HostInfo) {
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
// ═════════════════════════════════════════════════
// 发包频率控制参数
// ═════════════════════════════════════════════════
flag.Int64Var(&PacketRateLimit, "rate", 0, i18n.GetText("flag_packet_rate_limit"))
flag.Int64Var(&MaxPacketCount, "maxpkts", 0, i18n.GetText("flag_max_packet_count"))
// ═════════════════════════════════════════════════
// 输出与显示控制参数
// ═════════════════════════════════════════════════

View File

@ -1,6 +1,8 @@
package common
import (
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
@ -110,6 +112,11 @@ var (
UDPPacketCount int64 // UDP包数量
HTTPPacketCount int64 // HTTP请求数量
// 发包频率控制变量
PacketRateStartTime int64 // 当前分钟的开始时间戳
PacketRateCurrentCount int64 // 当前分钟内的发包数量
PacketRateMutex sync.Mutex // 发包频率控制互斥锁
// =============================================================================
// 初始化控制
// =============================================================================
@ -256,4 +263,71 @@ func ResetPacketCounters() {
atomic.StoreInt64(&TCPFailedPacketCount, 0)
atomic.StoreInt64(&UDPPacketCount, 0)
atomic.StoreInt64(&HTTPPacketCount, 0)
}
// =============================================================================
// 发包频率控制功能
// =============================================================================
// CanSendPacket 检查是否可以发包 - 同时检查频率限制和总数限制
// 返回值: (可以发包, 错误信息)
func CanSendPacket() (bool, string) {
// 检查总数限制
if MaxPacketCount > 0 {
currentTotal := atomic.LoadInt64(&PacketCount)
if currentTotal >= MaxPacketCount {
return false, fmt.Sprintf("已达到最大发包数量限制: %d", MaxPacketCount)
}
}
// 检查频率限制
if PacketRateLimit > 0 {
PacketRateMutex.Lock()
defer PacketRateMutex.Unlock()
currentTime := time.Now().Unix() / 60 // 当前分钟
// 如果是新的分钟,重置计数器
if PacketRateStartTime != currentTime {
PacketRateStartTime = currentTime
atomic.StoreInt64(&PacketRateCurrentCount, 0)
}
// 检查当前分钟的发包数是否超限
currentCount := atomic.LoadInt64(&PacketRateCurrentCount)
if currentCount >= PacketRateLimit {
return false, fmt.Sprintf("已达到每分钟发包频率限制: %d", PacketRateLimit)
}
// 增加当前分钟的发包计数
atomic.AddInt64(&PacketRateCurrentCount, 1)
}
return true, ""
}
// WaitForPacketLimit 等待直到可以发包 - 智能等待策略
func WaitForPacketLimit() error {
maxWaitTime := 60 * time.Second // 最大等待1分钟
startWait := time.Now()
for {
canSend, reason := CanSendPacket()
if canSend {
return nil
}
// 如果是总数限制,直接返回错误
if MaxPacketCount > 0 && strings.Contains(reason, "最大发包数量") {
return fmt.Errorf(reason)
}
// 如果等待超时,返回错误
if time.Since(startWait) > maxWaitTime {
return fmt.Errorf("等待发包权限超时: %s", reason)
}
// 短暂休眠后重试
time.Sleep(100 * time.Millisecond)
}
}

View File

@ -187,6 +187,14 @@ var FlagMessages = map[string]map[string]string{
LangZH: "最大重试次数",
LangEN: "Maximum retries",
},
"flag_packet_rate_limit": {
LangZH: "每分钟最大发包次数 (0表示不限制)",
LangEN: "Maximum packets per minute (0 means no limit)",
},
"flag_max_packet_count": {
LangZH: "整个程序最大发包总数 (0表示不限制)",
LangEN: "Maximum total packet count for entire program (0 means no limit)",
},
"flag_output_file": {
LangZH: "输出文件",
LangEN: "Output file",

View File

@ -77,6 +77,12 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
common.UpdateProgressBar(1)
}()
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("端口扫描 %s 受限: %s", addr, reason))
return nil
}
// 连接测试 - 支持SOCKS5代理
conn, err := common.WrapperTcpWithTimeout("tcp", addr, to)

View File

@ -253,6 +253,12 @@ func (w *WebPortDetector) tryHTTPConnectionDirect(ctx context.Context, host stri
req.Header.Set("User-Agent", "fscan-web-detector/2.2")
req.Header.Set("Accept", "*/*")
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), reason))
return false
}
resp, err := client.Do(req)
if err != nil {

View File

@ -121,6 +121,12 @@ func (p *RedisPlugin) testCredential(ctx context.Context, info *common.HostInfo,
err error
}
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("Redis连接 %s 受限: %s", target, reason))
return nil
}
connChan := make(chan connResult, 1)
go func() {
@ -355,6 +361,16 @@ func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("Redis服务识别 %s 受限: %s", target, reason))
return &ScanResult{
Success: false,
Service: "redis",
Error: fmt.Errorf("发包受限: %s", reason),
}
}
// 尝试连接Redis服务
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {

View File

@ -62,6 +62,12 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResul
func (p *SNMPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("SNMP请求 %s 受限: %s", target, reason))
return false
}
conn, err := net.DialTimeout("udp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return false

View File

@ -164,6 +164,12 @@ func (p *SSHPlugin) testCredential(ctx context.Context, info *common.HostInfo, c
err error
}
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("SSH连接 %s 受限: %s", target, reason))
return nil
}
resultChan := make(chan sshResult, 1)
go func() {
client, err := ssh.Dial("tcp", target, config)
@ -201,6 +207,16 @@ func (p *SSHPlugin) executeCommand(client *ssh.Client, cmd string) (string, erro
func (p *SSHPlugin) identifyService(info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("SSH服务识别 %s 受限: %s", target, reason))
return &ScanResult{
Success: false,
Service: "ssh",
Error: fmt.Errorf("发包受限: %s", reason),
}
}
// 尝试连接获取SSH Banner
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {

View File

@ -675,6 +675,12 @@ func DoRequest(req *http.Request, redirect bool) (*Response, error) {
}
// 执行请求
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("POC HTTP请求 %s 受限: %s", req.URL.String(), reason))
return nil, fmt.Errorf("发包受限: %s", reason)
}
var (
oResp *http.Response
err error