mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-26 13:11:53 +08:00
Fix credential cleanup and explicit tuning flags
This commit is contained in:
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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 时默认值也应视为显式")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 约束
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 重试逻辑测试
|
// 重试逻辑测试
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user