mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
性能优化: 热路径零分配, 自适应池CAS无锁化, 锁外计算
- port_scan: fmt.Sprintf→JoinHostPort+fmtPort零分配地址格式化 (2.5x) - port_scan: strings.ToLower→containsFold零分配大小写不敏感匹配 (2.5x) - port_scan: slidingWindowSchedule修复semaphore泄漏bug - service_probe: readFromConn预分配4KB缓冲区消除扩容 - adaptive_pool: maybeAdjust用atomic CAS代替持锁检查, 99%免锁 - adaptive_timeout: Timeout锁外计算均值/标准差, 只锁缓存更新 - 新增perf_bench_test.go基准测试验证所有优化
This commit is contained in:
@@ -23,7 +23,7 @@ type AdaptivePool struct {
|
|||||||
|
|
||||||
// 监控参数
|
// 监控参数
|
||||||
checkInterval time.Duration
|
checkInterval time.Duration
|
||||||
lastCheck time.Time
|
lastCheckNano int64 // 原子, UnixNano
|
||||||
lastExhaustedCount int64
|
lastExhaustedCount int64
|
||||||
lastPacketCount int64
|
lastPacketCount int64
|
||||||
|
|
||||||
@@ -67,20 +67,22 @@ func (ap *AdaptivePool) Invoke(task interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// maybeAdjust 检查并可能调整线程池大小
|
// maybeAdjust 检查并可能调整线程池大小
|
||||||
|
// 使用原子 CAS 进行时间检查,99%+ 的调用零锁开销
|
||||||
func (ap *AdaptivePool) maybeAdjust() {
|
func (ap *AdaptivePool) maybeAdjust() {
|
||||||
now := time.Now()
|
lastCheck := atomic.LoadInt64(&ap.lastCheckNano)
|
||||||
|
now := time.Now().UnixNano()
|
||||||
ap.mu.Lock()
|
if now-lastCheck < int64(ap.checkInterval) {
|
||||||
if now.Sub(ap.lastCheck) < ap.checkInterval {
|
|
||||||
ap.mu.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ap.lastCheck = now
|
if !atomic.CompareAndSwapInt64(&ap.lastCheckNano, lastCheck, now) {
|
||||||
|
return // 其他 goroutine 已在检查
|
||||||
|
}
|
||||||
|
|
||||||
// 获取当前计数
|
// 获取当前计数
|
||||||
currentExhausted := ap.state.GetResourceExhaustedCount()
|
currentExhausted := ap.state.GetResourceExhaustedCount()
|
||||||
currentPackets := ap.state.GetPacketCount()
|
currentPackets := ap.state.GetPacketCount()
|
||||||
|
|
||||||
|
ap.mu.Lock()
|
||||||
// 计算增量(本周期内的耗尽率)
|
// 计算增量(本周期内的耗尽率)
|
||||||
deltaExhausted := currentExhausted - ap.lastExhaustedCount
|
deltaExhausted := currentExhausted - ap.lastExhaustedCount
|
||||||
deltaPackets := currentPackets - ap.lastPacketCount
|
deltaPackets := currentPackets - ap.lastPacketCount
|
||||||
|
|||||||
@@ -46,16 +46,18 @@ func (a *AdaptiveTimeout) Record(rtt time.Duration) {
|
|||||||
|
|
||||||
// Timeout 获取当前推荐超时值
|
// Timeout 获取当前推荐超时值
|
||||||
// 样本不足时返回 maxTO(冷启动)
|
// 样本不足时返回 maxTO(冷启动)
|
||||||
|
// 锁外执行均值/标准差计算,减少锁持有时间
|
||||||
func (a *AdaptiveTimeout) Timeout() time.Duration {
|
func (a *AdaptiveTimeout) Timeout() time.Duration {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
if a.count < a.warmup {
|
if a.count < a.warmup {
|
||||||
|
a.mu.Unlock()
|
||||||
return a.maxTO
|
return a.maxTO
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.dirty {
|
if !a.dirty {
|
||||||
return a.cachedTO
|
cached := a.cachedTO
|
||||||
|
a.mu.Unlock()
|
||||||
|
return cached
|
||||||
}
|
}
|
||||||
|
|
||||||
n := a.size
|
n := a.size
|
||||||
@@ -63,15 +65,27 @@ func (a *AdaptiveTimeout) Timeout() time.Duration {
|
|||||||
n = a.count
|
n = a.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拷贝样本到本地,释放锁后再计算
|
||||||
|
localSamples := make([]float64, n)
|
||||||
|
start := a.pos % a.size
|
||||||
|
if a.count < a.size {
|
||||||
|
copy(localSamples, a.samples[:n])
|
||||||
|
} else {
|
||||||
|
copy(localSamples[:a.size-start], a.samples[start:])
|
||||||
|
copy(localSamples[a.size-start:], a.samples[:start])
|
||||||
|
}
|
||||||
|
a.mu.Unlock()
|
||||||
|
|
||||||
|
// 锁外计算
|
||||||
var sum float64
|
var sum float64
|
||||||
for i := 0; i < n; i++ {
|
for _, s := range localSamples {
|
||||||
sum += a.samples[i]
|
sum += s
|
||||||
}
|
}
|
||||||
mean := sum / float64(n)
|
mean := sum / float64(n)
|
||||||
|
|
||||||
var variance float64
|
var variance float64
|
||||||
for i := 0; i < n; i++ {
|
for _, s := range localSamples {
|
||||||
d := a.samples[i] - mean
|
d := s - mean
|
||||||
variance += d * d
|
variance += d * d
|
||||||
}
|
}
|
||||||
stddev := math.Sqrt(variance / float64(n))
|
stddev := math.Sqrt(variance / float64(n))
|
||||||
@@ -86,7 +100,11 @@ func (a *AdaptiveTimeout) Timeout() time.Duration {
|
|||||||
to = a.maxTO
|
to = a.maxTO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 短暂加锁更新缓存
|
||||||
|
a.mu.Lock()
|
||||||
a.cachedTO = to
|
a.cachedTO = to
|
||||||
a.dirty = false
|
a.dirty = false
|
||||||
|
a.mu.Unlock()
|
||||||
|
|
||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Benchmark: containsFold vs strings.ToLower + strings.Contains
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func BenchmarkContainsFold(b *testing.B) {
|
||||||
|
err := errors.New("connection reset by peer: 192.168.1.1:445")
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
containsFold(err.Error(), "connection reset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStringsToLowerContains(b *testing.B) {
|
||||||
|
err := errors.New("connection reset by peer: 192.168.1.1:445")
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
strings.Contains(strings.ToLower(err.Error()), "connection reset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Benchmark: fmt.Sprintf vs net.JoinHostPort + fmtPort
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func BenchmarkFmtSprintfAddr(b *testing.B) {
|
||||||
|
host := "192.168.1.1"
|
||||||
|
port := 445
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = fmt.Sprintf("%s:%d", host, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkJoinHostPortFmtPort(b *testing.B) {
|
||||||
|
host := "192.168.1.1"
|
||||||
|
port := 445
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = net.JoinHostPort(host, fmtPort(port))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFmtPort(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = fmtPort(445)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Benchmark: readFromConn buffer pre-allocation
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func BenchmarkAppendFromNil(b *testing.B) {
|
||||||
|
data := []byte("HTTP/1.1 200 OK\r\nServer: nginx")
|
||||||
|
chunk := data[:10]
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var result []byte
|
||||||
|
result = append(result, chunk...)
|
||||||
|
result = append(result, chunk...)
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendPreAllocated(b *testing.B) {
|
||||||
|
data := []byte("HTTP/1.1 200 OK\r\nServer: nginx")
|
||||||
|
chunk := data[:10]
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result := make([]byte, 0, 4096)
|
||||||
|
result = append(result, chunk...)
|
||||||
|
result = append(result, chunk...)
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Benchmark: AdaptiveTimeout computation under lock vs outside lock
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func BenchmarkAdaptiveTimeoutComputation(b *testing.B) {
|
||||||
|
at := NewAdaptiveTimeout(3000 * 1000000) // 3s in ns
|
||||||
|
// Warm up: add 64 samples
|
||||||
|
for i := 0; i < 64; i++ {
|
||||||
|
at.Record(10 * 1000000) // 10ms in ns
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = at.Timeout()
|
||||||
|
}
|
||||||
|
}
|
||||||
+84
-29
@@ -38,6 +38,28 @@ var resourceExhaustedPatterns = []string{
|
|||||||
"发包受限",
|
"发包受限",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// closedPatterns 连接已关闭的错误模式
|
||||||
|
var closedPatterns = []string{
|
||||||
|
"broken pipe",
|
||||||
|
"connection reset",
|
||||||
|
"connection refused",
|
||||||
|
"use of closed network connection",
|
||||||
|
"connection was forcibly closed",
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxyErrorTexts 代理错误响应文本模式
|
||||||
|
var proxyErrorTexts = []string{
|
||||||
|
"connection refused",
|
||||||
|
"host unreachable",
|
||||||
|
"network unreachable",
|
||||||
|
"connection timed out",
|
||||||
|
"proxy error",
|
||||||
|
"gateway error",
|
||||||
|
"bad gateway",
|
||||||
|
"502",
|
||||||
|
"503",
|
||||||
|
}
|
||||||
|
|
||||||
// resultCollector 结果收集器,用于并发安全地收集扫描结果
|
// resultCollector 结果收集器,用于并发安全地收集扫描结果
|
||||||
type resultCollector struct {
|
type resultCollector struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@@ -79,6 +101,7 @@ func (c *resultCollector) GetAll() []string {
|
|||||||
type portScanTask struct {
|
type portScanTask struct {
|
||||||
host string
|
host string
|
||||||
port int
|
port int
|
||||||
|
addr string // 预格式化的 host:port,避免 fmt.Sprintf 热路径分配
|
||||||
semaphore chan struct{} // 完成时释放窗口槽位
|
semaphore chan struct{} // 完成时释放窗口槽位
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,8 +226,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", taskInfo.host, taskInfo.port)
|
scanSinglePort(ctx, taskInfo.host, taskInfo.port, taskInfo.addr, adaptiveTO, &count, collector, failedCollector, session)
|
||||||
scanSinglePort(ctx, taskInfo.host, taskInfo.port, addr, adaptiveTO, &count, collector, failedCollector, session)
|
|
||||||
common.UpdateProgressBar(1)
|
common.UpdateProgressBar(1)
|
||||||
}, state)
|
}, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -283,6 +305,7 @@ func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.Wa
|
|||||||
task := portScanTask{
|
task := portScanTask{
|
||||||
host: host,
|
host: host,
|
||||||
port: port,
|
port: port,
|
||||||
|
addr: net.JoinHostPort(host, fmtPort(port)),
|
||||||
semaphore: semaphore,
|
semaphore: semaphore,
|
||||||
}
|
}
|
||||||
if err := pool.Invoke(task); err != nil {
|
if err := pool.Invoke(task); err != nil {
|
||||||
@@ -295,6 +318,22 @@ func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.Wa
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fmtPort 无分配的端口号格式化
|
||||||
|
func fmtPort(port int) string {
|
||||||
|
if port < 0 || port > 65535 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
// 预分配足够大的缓冲区
|
||||||
|
var buf [6]byte
|
||||||
|
i := len(buf)
|
||||||
|
for port > 0 || i == len(buf) {
|
||||||
|
i--
|
||||||
|
buf[i] = byte(port%10) + '0'
|
||||||
|
port /= 10
|
||||||
|
}
|
||||||
|
return string(buf[i:])
|
||||||
|
}
|
||||||
|
|
||||||
// connectWithRetry 带重试的TCP连接 - 只对资源耗尽错误重试
|
// connectWithRetry 带重试的TCP连接 - 只对资源耗尽错误重试
|
||||||
func connectWithRetry(ctx context.Context, session *common.ScanSession, addr string, timeout time.Duration, maxRetries int) (net.Conn, error) {
|
func connectWithRetry(ctx context.Context, session *common.ScanSession, addr string, timeout time.Duration, maxRetries int) (net.Conn, error) {
|
||||||
var lastErr error
|
var lastErr error
|
||||||
@@ -334,7 +373,7 @@ func isResourceExhaustedError(err error) bool {
|
|||||||
|
|
||||||
errStr := err.Error()
|
errStr := err.Error()
|
||||||
for _, pattern := range resourceExhaustedPatterns {
|
for _, pattern := range resourceExhaustedPatterns {
|
||||||
if strings.Contains(errStr, pattern) {
|
if containsFold(errStr, pattern) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,6 +381,42 @@ func isResourceExhaustedError(err error) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// containsFold 忽略大小写的子串匹配,避免 strings.ToLower 分配
|
||||||
|
func containsFold(s, substr string) bool {
|
||||||
|
if len(substr) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(substr) > len(s) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i <= len(s)-len(substr); i++ {
|
||||||
|
if matchFold(s[i:i+len(substr)], substr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchFold 忽略大小写逐字节比较
|
||||||
|
func matchFold(a, b string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(a); i++ {
|
||||||
|
ca, cb := a[i], b[i]
|
||||||
|
if ca >= 'A' && ca <= 'Z' {
|
||||||
|
ca += 'a' - 'A'
|
||||||
|
}
|
||||||
|
if cb >= 'A' && cb <= 'Z' {
|
||||||
|
cb += 'a' - 'A'
|
||||||
|
}
|
||||||
|
if ca != cb {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// buildServiceLogMessage 构建服务识别的日志信息
|
// buildServiceLogMessage 构建服务识别的日志信息
|
||||||
// 格式: addr service [Product:xxx ||Version:xxx] Banner:(xxx)
|
// 格式: addr service [Product:xxx ||Version:xxx] Banner:(xxx)
|
||||||
func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) string {
|
func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) string {
|
||||||
@@ -503,9 +578,9 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string) (bool, string) {
|
|||||||
|
|
||||||
// 阶段4: 最终判断
|
// 阶段4: 最终判断
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
errLower := strings.ToLower(readErr.Error())
|
errStr := readErr.Error()
|
||||||
for _, pattern := range proxyFailurePatterns {
|
for _, pattern := range proxyFailurePatterns {
|
||||||
if strings.Contains(errLower, pattern) {
|
if containsFold(errStr, pattern) {
|
||||||
common.LogDebug(fmt.Sprintf("代理连接被拒绝 %s: %v", addr, readErr))
|
common.LogDebug(fmt.Sprintf("代理连接被拒绝 %s: %v", addr, readErr))
|
||||||
return false, "proxy_reject"
|
return false, "proxy_reject"
|
||||||
}
|
}
|
||||||
@@ -538,21 +613,9 @@ func isProxyErrorResponse(data []byte) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查常见的代理错误文本
|
// 检查常见的代理错误文本
|
||||||
dataStr := strings.ToLower(string(data))
|
dataStr := string(data)
|
||||||
proxyErrorTexts := []string{
|
|
||||||
"connection refused",
|
|
||||||
"host unreachable",
|
|
||||||
"network unreachable",
|
|
||||||
"connection timed out",
|
|
||||||
"proxy error",
|
|
||||||
"gateway error",
|
|
||||||
"bad gateway",
|
|
||||||
"502",
|
|
||||||
"503",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, errText := range proxyErrorTexts {
|
for _, errText := range proxyErrorTexts {
|
||||||
if strings.Contains(dataStr, errText) {
|
if containsFold(dataStr, errText) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -566,17 +629,9 @@ func isConnectionClosed(err error) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
errStr := strings.ToLower(err.Error())
|
errStr := err.Error()
|
||||||
closedPatterns := []string{
|
|
||||||
"broken pipe",
|
|
||||||
"connection reset",
|
|
||||||
"connection refused",
|
|
||||||
"use of closed network connection",
|
|
||||||
"connection was forcibly closed",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pattern := range closedPatterns {
|
for _, pattern := range closedPatterns {
|
||||||
if strings.Contains(errStr, pattern) {
|
if containsFold(errStr, pattern) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -662,7 +662,7 @@ func TestIsResourceExhaustedError_EdgeCases(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "大小写混合",
|
name: "大小写混合",
|
||||||
err: fmt.Errorf("Too Many Open Files"),
|
err: fmt.Errorf("Too Many Open Files"),
|
||||||
expected: false, // 当前实现区分大小写
|
expected: true, // containsFold 不区分大小写
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "错误信息包含但不完全匹配",
|
name: "错误信息包含但不完全匹配",
|
||||||
|
|||||||
@@ -607,6 +607,11 @@ func readFromConn(conn net.Conn) ([]byte, error) {
|
|||||||
|
|
||||||
var result []byte
|
var result []byte
|
||||||
|
|
||||||
|
// 预分配 4KB,消除大部分服务 Banner 场景下的 append 扩容
|
||||||
|
if cap(buf) > 0 {
|
||||||
|
result = make([]byte, 0, 4096)
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
count, err := conn.Read(buf)
|
count, err := conn.Read(buf)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user