mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-24 04:01:52 +08:00
feat: v2.1.0 核心重构与功能增强
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n
## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池
## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)
## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建
## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
JSON 输出格式、Redis 检测、Context 超时等
## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
network.go - 统一网络操作包装器
|
||||
|
||||
提供便捷的网络连接API,自动处理发包限制检查、代理和统计。
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/shadow1ng/fscan/common/proxy"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 全局代理管理器(复用连接,避免重复创建)
|
||||
// =============================================================================
|
||||
|
||||
var (
|
||||
globalProxyOnce sync.Once
|
||||
globalProxyDialer proxy.Dialer
|
||||
globalProxyInitErr error
|
||||
)
|
||||
|
||||
// getGlobalDialer 获取全局拨号器(线程安全,只初始化一次)
|
||||
func getGlobalDialer(timeout time.Duration) (proxy.Dialer, error) {
|
||||
globalProxyOnce.Do(func() {
|
||||
// 创建代理配置
|
||||
config := createProxyConfig(timeout)
|
||||
|
||||
// 创建代理管理器
|
||||
manager := proxy.NewProxyManager(config)
|
||||
|
||||
// 创建拨号器
|
||||
globalProxyDialer, globalProxyInitErr = manager.GetDialer()
|
||||
})
|
||||
|
||||
return globalProxyDialer, globalProxyInitErr
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 代理配置
|
||||
// =============================================================================
|
||||
|
||||
// parseProxyURL 解析代理URL,提取地址和认证信息
|
||||
func parseProxyURL(proxyURL, fallback string) (host, username, password string) {
|
||||
parsedURL, err := url.Parse(proxyURL)
|
||||
if err != nil {
|
||||
return fallback, "", ""
|
||||
}
|
||||
host = parsedURL.Host
|
||||
if parsedURL.User != nil {
|
||||
username = parsedURL.User.Username()
|
||||
password, _ = parsedURL.User.Password()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// createProxyConfig 根据全局设置创建代理配置
|
||||
func createProxyConfig(timeout time.Duration) *proxy.ProxyConfig {
|
||||
fv := GetFlagVars()
|
||||
config := proxy.DefaultProxyConfig()
|
||||
config.Timeout = timeout
|
||||
config.LocalAddr = fv.Iface // 设置本地网卡IP地址
|
||||
|
||||
// 优先使用SOCKS5代理
|
||||
if fv.Socks5Proxy != "" {
|
||||
config.Type = proxy.ProxyTypeSOCKS5
|
||||
// 确保有协议前缀以便解析
|
||||
socks5URL := fv.Socks5Proxy
|
||||
if !strings.HasPrefix(socks5URL, "socks5://") {
|
||||
socks5URL = "socks5://" + socks5URL
|
||||
}
|
||||
config.Address, config.Username, config.Password = parseProxyURL(socks5URL, fv.Socks5Proxy)
|
||||
return config
|
||||
}
|
||||
|
||||
// 其次使用HTTP代理
|
||||
if fv.HTTPProxy != "" {
|
||||
if strings.HasPrefix(fv.HTTPProxy, "https://") {
|
||||
config.Type = proxy.ProxyTypeHTTPS
|
||||
} else {
|
||||
config.Type = proxy.ProxyTypeHTTP
|
||||
}
|
||||
config.Address, config.Username, config.Password = parseProxyURL(fv.HTTPProxy, fv.HTTPProxy)
|
||||
return config
|
||||
}
|
||||
|
||||
// 无代理配置,使用直连
|
||||
config.Type = proxy.ProxyTypeNone
|
||||
return config
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TCP 连接
|
||||
// =============================================================================
|
||||
|
||||
// WrapperTcpWithTimeout TCP连接包装器,带超时
|
||||
// 支持通过代理管理器进行SOCKS5和HTTP代理连接,并集成发包控制
|
||||
// 使用全局拨号器复用连接,避免重复创建代理握手开销
|
||||
//
|
||||
//nolint:revive // 保持向后兼容性,避免破坏大量现有代码
|
||||
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||
// 检查发包限制 - 在代理连接前进行控制
|
||||
if canSend, reason := CanSendPacket(); !canSend {
|
||||
LogError(fmt.Sprintf("TCP连接 %s 受限: %s", address, reason))
|
||||
return nil, fmt.Errorf("发包受限: %s", reason)
|
||||
}
|
||||
|
||||
// 获取全局拨号器(复用,避免重复创建)
|
||||
dialer, err := getGlobalDialer(timeout)
|
||||
if err != nil {
|
||||
LogError(fmt.Sprintf("获取代理拨号器失败: %v", err))
|
||||
GetGlobalState().IncrementTCPFailedPacketCount()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 使用代理拨号器连接
|
||||
conn, err := dialer.DialContext(context.Background(), network, address)
|
||||
|
||||
// 统计TCP包数量 - 无论是否使用代理都要计数
|
||||
if err != nil {
|
||||
GetGlobalState().IncrementTCPFailedPacketCount()
|
||||
LogDebug(fmt.Sprintf("连接 %s 失败: %v", address, err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 连接成功,统计成功包
|
||||
GetGlobalState().IncrementTCPSuccessPacketCount()
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// SafeTCPDial TCP连接的便捷封装
|
||||
// 直接调用WrapperTcpWithTimeout,自动处理发包限制、代理和统计
|
||||
func SafeTCPDial(address string, timeout time.Duration) (net.Conn, error) {
|
||||
return WrapperTcpWithTimeout("tcp", address, timeout)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HTTP 请求
|
||||
// =============================================================================
|
||||
|
||||
// IsProxyEnabled 检查是否启用了代理(封装proxy包的函数)
|
||||
func IsProxyEnabled() bool {
|
||||
return proxy.IsProxyEnabled()
|
||||
}
|
||||
|
||||
// SafeHTTPDo 带发包控制的HTTP请求
|
||||
func SafeHTTPDo(client *http.Client, req *http.Request) (*http.Response, error) {
|
||||
// 检查发包限制
|
||||
if canSend, reason := CanSendPacket(); !canSend {
|
||||
LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), reason))
|
||||
return nil, fmt.Errorf("发包受限: %s", reason)
|
||||
}
|
||||
|
||||
// 执行HTTP请求
|
||||
resp, err := client.Do(req)
|
||||
|
||||
// 统计TCP包数量 (HTTP本质上是TCP)
|
||||
if err != nil {
|
||||
GetGlobalState().IncrementTCPFailedPacketCount()
|
||||
} else {
|
||||
GetGlobalState().IncrementTCPSuccessPacketCount()
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
Reference in New Issue
Block a user