From c16aa04e28b562c73b2c8aca9af25cc489b38160 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Mon, 18 May 2026 17:11:24 +0800 Subject: [PATCH] isolate session network checks --- common/session.go | 38 ++++++++++++++++++++ common/session_test.go | 81 ++++++++++++++++++++++++++++++++++++++++++ core/port_scan.go | 10 +++--- core/web_scanner.go | 9 +++-- 4 files changed, 128 insertions(+), 10 deletions(-) diff --git a/common/session.go b/common/session.go index 757a5c9..d7e81d2 100644 --- a/common/session.go +++ b/common/session.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "net/http" "strings" "sync" "time" @@ -119,6 +120,43 @@ func (s *ScanSession) DialTCP(ctx context.Context, network, address string, time return conn, nil } +// HTTPDo executes an HTTP request with the session's packet limits and counters. +func (s *ScanSession) HTTPDo(client *http.Client, req *http.Request) (*http.Response, error) { + if ok, err := CanSendPacketWith(s.Config, s.State); !ok { + s.LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), err.Error())) + return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", err.Error())) + } + + resp, err := client.Do(req) + if err != nil { + s.State.IncrementTCPFailedPacketCount() + return nil, err + } + s.State.IncrementTCPSuccessPacketCount() + return resp, nil +} + +// ProxyEnabled reports whether this scan session uses a network proxy. +func (s *ScanSession) ProxyEnabled() bool { + if s == nil || s.Config == nil { + return false + } + return s.Config.Network.Socks5Proxy != "" || s.Config.Network.HTTPProxy != "" +} + +// IsSOCKS5Proxy reports whether this scan session uses SOCKS5. +func (s *ScanSession) IsSOCKS5Proxy() bool { + return s != nil && s.Config != nil && s.Config.Network.Socks5Proxy != "" +} + +// ProxyReliable reports whether the session proxy should be treated as reliable. +func (s *ScanSession) ProxyReliable() bool { + if !s.ProxyEnabled() || !s.IsSOCKS5Proxy() { + return true + } + return proxy.IsProxyReliable() +} + func (s *ScanSession) getDialer(timeout time.Duration) (proxy.Dialer, error) { if timeout <= 0 { timeout = s.Config.Timeout diff --git a/common/session_test.go b/common/session_test.go index f0eb601..cb03d17 100644 --- a/common/session_test.go +++ b/common/session_test.go @@ -1,6 +1,9 @@ package common import ( + "io" + "net/http" + "strings" "testing" "time" ) @@ -65,3 +68,81 @@ func TestScanSessionDialerCacheIsTimeoutAware(t *testing.T) { t.Fatalf("proxy timeout = %v, want %v", got, shortTimeout) } } + +func TestScanSessionHTTPDoUsesSessionState(t *testing.T) { + previousState := GetGlobalState() + globalState := NewState() + SetGlobalState(globalState) + t.Cleanup(func() { SetGlobalState(previousState) }) + + sessionState := NewState() + session := NewScanSession(NewConfig(), sessionState, &FlagVars{}) + client := &http.Client{ + Transport: roundTripFunc(func(*http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusNoContent, + Body: io.NopCloser(strings.NewReader("")), + Header: make(http.Header), + }, nil + }), + } + req, err := http.NewRequest(http.MethodHead, "http://example.com", nil) + if err != nil { + t.Fatal(err) + } + + resp, err := session.HTTPDo(client, req) + if err != nil { + t.Fatal(err) + } + _ = resp.Body.Close() + + if got := sessionState.GetTCPSuccessPacketCount(); got != 1 { + t.Fatalf("session TCP success count = %d, want 1", got) + } + if got := globalState.GetTCPSuccessPacketCount(); got != 0 { + t.Fatalf("global TCP success count = %d, want 0", got) + } +} + +func TestScanSessionProxyStateComesFromConfig(t *testing.T) { + direct := NewScanSession(NewConfig(), NewState(), &FlagVars{}) + if direct.ProxyEnabled() { + t.Fatal("direct session should not report proxy enabled") + } + if direct.IsSOCKS5Proxy() { + t.Fatal("direct session should not report SOCKS5") + } + if !direct.ProxyReliable() { + t.Fatal("direct session should be reliable") + } + + httpCfg := NewConfig() + httpCfg.Network.HTTPProxy = "http://127.0.0.1:8080" + httpSession := NewScanSession(httpCfg, NewState(), &FlagVars{}) + if !httpSession.ProxyEnabled() { + t.Fatal("HTTP proxy session should report proxy enabled") + } + if httpSession.IsSOCKS5Proxy() { + t.Fatal("HTTP proxy session should not report SOCKS5") + } + if !httpSession.ProxyReliable() { + t.Fatal("HTTP proxy session should be reliable") + } + + socksCfg := NewConfig() + socksCfg.Network.Socks5Proxy = "127.0.0.1:1080" + socksSession := NewScanSession(socksCfg, NewState(), &FlagVars{}) + if !socksSession.ProxyEnabled() { + t.Fatal("SOCKS5 proxy session should report proxy enabled") + } + if !socksSession.IsSOCKS5Proxy() { + t.Fatal("SOCKS5 proxy session should report SOCKS5") + } +} + +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} diff --git a/core/port_scan.go b/core/port_scan.go index a2ec510..db81b5d 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -176,7 +176,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout } // 检查代理可靠性,如果存在全回显问题则警告 - if common.IsProxyEnabled() && !common.IsProxyReliable() { + if session.ProxyEnabled() && !session.ProxyReliable() { session.LogError("检测到代理存在全回显问题,端口扫描结果可能不准确") } @@ -465,7 +465,7 @@ func scanSinglePort(ctx context.Context, host string, port int, addr string, ada adaptiveTO.Record(time.Since(start)) // 步骤1.5:代理连接深度验证(防止透明代理/全回显代理的假连接问题) - valid, verifyMethod := verifyProxyConnectionDeep(conn, addr) + valid, verifyMethod := verifyProxyConnectionDeep(conn, addr, session) if !valid { session.LogDebug(fmt.Sprintf("代理验证失败 %s: %s", addr, verifyMethod)) _ = conn.Close() @@ -474,7 +474,7 @@ func scanSinglePort(ctx context.Context, host string, port int, addr string, ada // 步骤1.6:如果使用了代理且进行了数据交互,需要重建连接 // 因为验证阶段可能读取了Banner或发送了HTTP GET探测,污染了连接状态 - if common.IsProxyEnabled() && verifyMethod != "direct" { + if session.ProxyEnabled() && verifyMethod != "direct" { _ = conn.Close() // 重新建立干净的连接用于服务识别 conn, err = connectWithRetry(ctx, session, addr, timeout, 2) @@ -523,10 +523,10 @@ func handleConnectionFailure(err error, host string, port int, addr string, fail // 1. 快速 Banner 检测 (100ms) - 大部分服务会主动发送数据 // 2. 轻量探测 (发送 \r\n) - 触发某些服务响应,同时不污染协议状态 // 3. 短超时等待 (500ms) - 平衡准确性和性能 -func verifyProxyConnectionDeep(conn net.Conn, addr string) (bool, string) { +func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanSession) (bool, string) { // 无代理或SOCKS5代理:跳过深度验证 // SOCKS5协议层已验证连接可达性,连接成功即端口开放 - if !common.IsProxyEnabled() || common.IsSOCKS5Proxy() { + if !session.ProxyEnabled() || session.IsSOCKS5Proxy() { return true, "direct" } diff --git a/core/web_scanner.go b/core/web_scanner.go index ad3463e..dd3c3ab 100644 --- a/core/web_scanner.go +++ b/core/web_scanner.go @@ -136,12 +136,12 @@ func (w *WebPortDetector) DetectHTTPServiceOnly(host string, port int, config *c client := createHTTPClient(config, session) // 尝试HTTP - if w.tryHTTP(client, host, port, "http") { + if w.tryHTTP(client, session, host, port, "http") { return true } // 尝试HTTPS - if w.tryHTTP(client, host, port, "https") { + if w.tryHTTP(client, session, host, port, "https") { return true } @@ -163,7 +163,7 @@ func isPortReachable(host string, port int, config *common.Config, session *comm } // tryHTTP 尝试HTTP请求 - 简化的核心逻辑 -func (w *WebPortDetector) tryHTTP(client *http.Client, host string, port int, protocol string) bool { +func (w *WebPortDetector) tryHTTP(client *http.Client, session *common.ScanSession, host string, port int, protocol string) bool { // 构造URL var url string if (port == 80 && protocol == "http") || (port == 443 && protocol == "https") { @@ -181,8 +181,7 @@ func (w *WebPortDetector) tryHTTP(client *http.Client, host string, port int, pr req.Header.Set("User-Agent", "fscan-web-detector/2.1") req.Header.Set("Accept", "*/*") - // 使用统一的SafeHTTPDo以确保遵循限速策略和代理设置 - resp, err := common.SafeHTTPDo(client, req) + resp, err := session.HTTPDo(client, req) if err != nil { return false }