Fix credential cleanup and explicit tuning flags

This commit is contained in:
ZacharyZcR
2026-06-12 15:30:44 +08:00
parent 52f872b8d1
commit 5b7e72e56e
8 changed files with 205 additions and 86 deletions
+5
View File
@@ -23,9 +23,11 @@ config_struct.go - 配置结构体定义
type Config struct { type Config struct {
// 高频访问字段 - 平铺到顶层 // 高频访问字段 - 平铺到顶层
Timeout time.Duration // 通用超时 Timeout time.Duration // 通用超时
TimeoutExplicit bool // 用户显式指定了 -time
ThreadNum int // 主线程数 ThreadNum int // 主线程数
ThreadNumExplicit bool // 用户显式指定了 -t ThreadNumExplicit bool // 用户显式指定了 -t
ModuleThreadNum int // 模块线程数 ModuleThreadNum int // 模块线程数
ModuleThreadNumExplicit bool // 用户显式指定了 -mt
DisableBrute bool // 禁用暴力破解 DisableBrute bool // 禁用暴力破解
DisablePing bool // 禁用Ping检测 DisablePing bool // 禁用Ping检测
DisableTcpProbe bool // 禁用TCP补充探测 DisableTcpProbe bool // 禁用TCP补充探测
@@ -36,6 +38,7 @@ type Config struct {
LocalPlugin string // 本地插件名 LocalPlugin string // 本地插件名
AliveOnly bool // 仅存活检测 AliveOnly bool // 仅存活检测
MaxRetries int // 最大重试次数 MaxRetries int // 最大重试次数
MaxRetriesExplicit bool // 用户显式指定了 -retry
// 高级功能(从AdvancedConfig合并) // 高级功能(从AdvancedConfig合并)
Shellcode string // Shellcode Shellcode string // Shellcode
@@ -89,6 +92,7 @@ type NetworkConfig struct {
PacketRateLimit int64 PacketRateLimit int64
MaxPacketCount int64 MaxPacketCount int64
ICMPRate float64 ICMPRate float64
ICMPRateExplicit bool
} }
// OutputConfig 输出相关配置 // OutputConfig 输出相关配置
@@ -111,6 +115,7 @@ type POCConfig struct {
PocName string // 指定POC名称 PocName string // 指定POC名称
Full bool // 完整POC扫描 Full bool // 完整POC扫描
Num int // POC并发数 Num int // POC并发数
NumExplicit bool // 用户显式指定了 -num
Disabled bool // 禁用POC扫描 Disabled bool // 禁用POC扫描
} }
+12 -1
View File
@@ -215,8 +215,19 @@ func Flag(Info *HostInfo) error {
// 检测用户是否显式指定了 -t // 检测用户是否显式指定了 -t
flag.Visit(func(f *flag.Flag) { flag.Visit(func(f *flag.Flag) {
if f.Name == "t" { switch f.Name {
case "t":
fv.ThreadNumExplicit = true fv.ThreadNumExplicit = true
case "time":
fv.TimeoutExplicit = true
case "mt":
fv.ModuleThreadNumExplicit = true
case "retry":
fv.MaxRetriesExplicit = true
case "icmp-rate":
fv.ICMPRateExplicit = true
case "num":
fv.PocNumExplicit = true
} }
}) })
+10
View File
@@ -34,7 +34,9 @@ type FlagVars struct {
ThreadNum int ThreadNum int
ThreadNumExplicit bool // 用户显式指定了 -t ThreadNumExplicit bool // 用户显式指定了 -t
ModuleThreadNum int ModuleThreadNum int
ModuleThreadNumExplicit bool
TimeoutSec int64 // 秒,需转换为 time.Duration TimeoutSec int64 // 秒,需转换为 time.Duration
TimeoutExplicit bool
GlobalTimeout int64 GlobalTimeout int64
DisablePing bool DisablePing bool
DisableTcpProbe bool DisableTcpProbe bool
@@ -42,6 +44,7 @@ type FlagVars struct {
AliveOnly bool AliveOnly bool
DisableBrute bool DisableBrute bool
MaxRetries int MaxRetries int
MaxRetriesExplicit bool
// 认证凭据 // 认证凭据
Username string Username string
@@ -74,6 +77,7 @@ type FlagVars struct {
PocFull bool PocFull bool
DNSLog bool DNSLog bool
PocNum int PocNum int
PocNumExplicit bool
DisablePocScan bool DisablePocScan bool
// Redis利用 // Redis利用
@@ -88,6 +92,7 @@ type FlagVars struct {
PacketRateLimit int64 PacketRateLimit int64
MaxPacketCount int64 MaxPacketCount int64
ICMPRate float64 ICMPRate float64
ICMPRateExplicit bool
// 输出控制 // 输出控制
Outputfile string Outputfile string
@@ -136,9 +141,11 @@ func BuildConfigFromFlags(fv *FlagVars) *Config {
return &Config{ return &Config{
// 高频字段 // 高频字段
Timeout: time.Duration(fv.TimeoutSec) * time.Second, Timeout: time.Duration(fv.TimeoutSec) * time.Second,
TimeoutExplicit: fv.TimeoutExplicit,
ThreadNum: fv.ThreadNum, ThreadNum: fv.ThreadNum,
ThreadNumExplicit: fv.ThreadNumExplicit, ThreadNumExplicit: fv.ThreadNumExplicit,
ModuleThreadNum: fv.ModuleThreadNum, ModuleThreadNum: fv.ModuleThreadNum,
ModuleThreadNumExplicit: fv.ModuleThreadNumExplicit,
DisableBrute: fv.DisableBrute, DisableBrute: fv.DisableBrute,
DisablePing: fv.DisablePing, DisablePing: fv.DisablePing,
DisableTcpProbe: fv.DisableTcpProbe, DisableTcpProbe: fv.DisableTcpProbe,
@@ -149,6 +156,7 @@ func BuildConfigFromFlags(fv *FlagVars) *Config {
LocalPlugin: fv.LocalPlugin, LocalPlugin: fv.LocalPlugin,
AliveOnly: fv.AliveOnly, AliveOnly: fv.AliveOnly,
MaxRetries: fv.MaxRetries, MaxRetries: fv.MaxRetries,
MaxRetriesExplicit: fv.MaxRetriesExplicit,
// 高级功能 // 高级功能
Shellcode: fv.Shellcode, Shellcode: fv.Shellcode,
@@ -181,6 +189,7 @@ func BuildConfigFromFlags(fv *FlagVars) *Config {
PacketRateLimit: fv.PacketRateLimit, PacketRateLimit: fv.PacketRateLimit,
MaxPacketCount: fv.MaxPacketCount, MaxPacketCount: fv.MaxPacketCount,
ICMPRate: fv.ICMPRate, ICMPRate: fv.ICMPRate,
ICMPRateExplicit: fv.ICMPRateExplicit,
}, },
Output: OutputConfig{ Output: OutputConfig{
File: fv.Outputfile, File: fv.Outputfile,
@@ -199,6 +208,7 @@ func BuildConfigFromFlags(fv *FlagVars) *Config {
PocName: fv.PocName, PocName: fv.PocName,
Full: fv.PocFull, Full: fv.PocFull,
Num: fv.PocNum, Num: fv.PocNum,
NumExplicit: fv.PocNumExplicit,
Disabled: fv.DisablePocScan, Disabled: fv.DisablePocScan,
}, },
Redis: RedisConfig{ Redis: RedisConfig{
+12
View File
@@ -636,4 +636,16 @@ func TestIsExplicit(t *testing.T) {
if !isExplicit(config, "t") { if !isExplicit(config, "t") {
t.Error("ThreadNumExplicit=true 应视为显式") t.Error("ThreadNumExplicit=true 应视为显式")
} }
config = makeDefaultConfig()
config.TimeoutExplicit = true
config.ModuleThreadNumExplicit = true
config.MaxRetriesExplicit = true
config.Network.ICMPRateExplicit = true
config.POC.NumExplicit = true
if !isExplicit(config, "time") || !isExplicit(config, "mt") ||
!isExplicit(config, "retry") || !isExplicit(config, "icmp-rate") ||
!isExplicit(config, "num") {
t.Error("显式标记为 true 时默认值也应视为显式")
}
} }
+7 -7
View File
@@ -180,22 +180,22 @@ func computeICMPRate(net *NetworkProfile, sys *SystemProfile) float64 {
return base return base
} }
// isExplicit 检查参数是否被用户显式指定 // isExplicit 检查参数是否被用户显式指定。
// 目前只有 ThreadNum 有 explicit 标记,其他参数通过检查是否为默认值来判断 // 显式标记来自 CLI flag.Visit;值比较保留 SDK/测试里直接构造 Config 的旧行为。
func isExplicit(config *common.Config, flagName string) bool { func isExplicit(config *common.Config, flagName string) bool {
switch flagName { switch flagName {
case "t": case "t":
return config.ThreadNumExplicit return config.ThreadNumExplicit
case "time": case "time":
return config.Timeout != 3*time.Second // 默认值 return config.TimeoutExplicit || config.Timeout != 3*time.Second
case "mt": case "mt":
return config.ModuleThreadNum != 20 // 默认值 return config.ModuleThreadNumExplicit || config.ModuleThreadNum != 20
case "retry": case "retry":
return config.MaxRetries != 3 // 默认值 return config.MaxRetriesExplicit || config.MaxRetries != 3
case "icmp-rate": case "icmp-rate":
return config.Network.ICMPRate != 0.1 // 默认值 return config.Network.ICMPRateExplicit || config.Network.ICMPRate != 0.1
case "num": case "num":
return config.POC.Num != 20 // 默认值 return config.POC.NumExplicit || config.POC.Num != 20
} }
return false return false
} }
+39
View File
@@ -252,6 +252,45 @@ func TestTuneConfig_ExplicitOverride(t *testing.T) {
} }
} }
func TestTuneConfig_ExplicitDefaultValues(t *testing.T) {
config := makeDefaultConfig()
config.TimeoutExplicit = true
config.ModuleThreadNumExplicit = true
config.MaxRetriesExplicit = true
config.Network.ICMPRateExplicit = true
config.POC.NumExplicit = true
session := makeTestSession(config)
ep := &EnvironmentProfile{
Net: NetworkProfile{
Env: EnvLAN,
RTTMedian: 1 * time.Millisecond,
RTTStddev: 500 * time.Microsecond,
LossRate: 0.0,
Samples: 30,
},
System: SystemProfile{FDLimit: 65536, NumCPU: 8},
}
ep.TuneConfig(config, session)
if config.Timeout != 3*time.Second {
t.Errorf("显式默认 Timeout 被覆盖: %v", config.Timeout)
}
if config.ModuleThreadNum != 20 {
t.Errorf("显式默认 ModuleThreadNum 被覆盖: %d", config.ModuleThreadNum)
}
if config.MaxRetries != 3 {
t.Errorf("显式默认 MaxRetries 被覆盖: %d", config.MaxRetries)
}
if config.Network.ICMPRate != 0.1 {
t.Errorf("显式默认 ICMPRate 被覆盖: %.2f", config.Network.ICMPRate)
}
if config.POC.Num != 20 {
t.Errorf("显式默认 PocNum 被覆盖: %d", config.POC.Num)
}
}
// ============================================================================= // =============================================================================
// 集成测试:fd limit 约束 // 集成测试:fd limit 约束
// ============================================================================= // =============================================================================
+10 -3
View File
@@ -61,6 +61,8 @@ type AuthFunc func(ctx context.Context, cred Credential) *AuthResult
// ErrorClassifier 错误分类函数 // ErrorClassifier 错误分类函数
type ErrorClassifier func(err error) ErrorType type ErrorClassifier func(err error) ErrorType
var authCleanupWait = 2 * time.Second
// ============================================================================= // =============================================================================
// 单凭据测试(解决 goroutine 泄漏) // 单凭据测试(解决 goroutine 泄漏)
// ============================================================================= // =============================================================================
@@ -79,13 +81,18 @@ func TestSingleCredential(ctx context.Context, cred Credential, authFn AuthFunc)
case result := <-resultChan: case result := <-resultChan:
return result return result
case <-ctx.Done(): case <-ctx.Done():
// context 被取消,等待 authFn goroutine 返回并清理连接 // context 被取消后只做有界等待,避免 authFn 卡死时清理 goroutine 也永久泄漏。
// 各插件的 authFn 应在 context 取消时关闭底层连接使 goroutine 快速退出
go func() { go func() {
result := <-resultChan timer := time.NewTimer(authCleanupWait)
defer timer.Stop()
select {
case result := <-resultChan:
if result != nil && result.Conn != nil { if result != nil && result.Conn != nil {
_ = result.Conn.Close() _ = result.Conn.Close()
} }
case <-timer.C:
}
}() }()
return &AuthResult{ return &AuthResult{
Success: false, Success: false,
@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"io" "io"
"runtime"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time" "time"
@@ -401,6 +402,40 @@ func TestTestSingleCredential_ContextCancel(t *testing.T) {
} }
} }
func TestTestSingleCredential_ContextCancelCleanupIsBounded(t *testing.T) {
oldWait := authCleanupWait
authCleanupWait = 20 * time.Millisecond
defer func() { authCleanupWait = oldWait }()
authStarted := make(chan struct{})
releaseAuth := make(chan struct{})
authFn := func(ctx context.Context, cred Credential) *AuthResult {
close(authStarted)
<-releaseAuth
return nil
}
ctx, cancel := context.WithCancel(context.Background())
go func() {
<-authStarted
cancel()
}()
before := runtime.NumGoroutine()
result := TestSingleCredential(ctx, Credential{Username: "admin", Password: "admin"}, authFn)
if result.Success {
t.Error("context取消后不应该返回成功")
}
time.Sleep(100 * time.Millisecond)
after := runtime.NumGoroutine()
close(releaseAuth)
if after > before+1 {
t.Fatalf("清理 goroutine 疑似泄漏: before=%d after=%d", before, after)
}
}
// ============================================================================= // =============================================================================
// 重试逻辑测试 // 重试逻辑测试
// ============================================================================= // =============================================================================