mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-25 12:41:53 +08:00
Expand i18n coverage
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
)
|
||||
|
||||
// AdaptivePool 自适应线程池
|
||||
@@ -106,7 +107,7 @@ func (ap *AdaptivePool) maybeAdjust() {
|
||||
newSize = ap.minSize
|
||||
}
|
||||
ap.tune(newSize)
|
||||
common.LogInfo(fmt.Sprintf("[AdaptivePool] 资源耗尽率 %.1f%%, 线程数 %d -> %d", rate*100, currentSize, newSize))
|
||||
common.LogInfo(i18n.Tr("adaptive_pool_resource_exhausted", fmt.Sprintf("%.1f", rate*100), currentSize, newSize))
|
||||
} else if rate < ap.recoveryThreshold && currentSize < ap.maxSize {
|
||||
// 恢复:增加 10% 线程(保守恢复)
|
||||
newSize := int(float64(currentSize) * 1.1)
|
||||
|
||||
@@ -38,7 +38,7 @@ type AliveStats struct {
|
||||
// NewAliveScanStrategy 创建新的存活探测扫描策略
|
||||
func NewAliveScanStrategy() *AliveScanStrategy {
|
||||
return &AliveScanStrategy{
|
||||
BaseScanStrategy: NewBaseScanStrategy("存活探测", FilterNone),
|
||||
BaseScanStrategy: NewBaseScanStrategy(i18n.GetText("scan_strategy_alive_name"), FilterNone),
|
||||
startTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ func formatPluginList(plugins []string) string {
|
||||
if len(plugins) <= 5 {
|
||||
return strings.Join(plugins, ", ")
|
||||
}
|
||||
return fmt.Sprintf("%s ... 等%d个", strings.Join(plugins[:5], ", "), len(plugins))
|
||||
return i18n.Tr("plugin_list_summary", strings.Join(plugins[:5], ", "), len(plugins))
|
||||
}
|
||||
|
||||
// ValidateConfiguration 验证扫描配置
|
||||
|
||||
+3
-4
@@ -286,13 +286,13 @@ func waitAdaptive(hostslist []string, aliveHosts *[]string, aliveHostsMu *sync.M
|
||||
|
||||
// 条件1:所有主机都已响应,立即结束
|
||||
if aliveCount >= totalHosts {
|
||||
common.LogDebug(fmt.Sprintf("[ICMP] 全部响应,耗时 %v", elapsed.Round(time.Millisecond)))
|
||||
common.LogDebug(i18n.Tr("icmp_debug_all_responded", elapsed.Round(time.Millisecond)))
|
||||
break
|
||||
}
|
||||
|
||||
// 条件2:超过最大等待时间,兜底结束
|
||||
if elapsed >= maxWait {
|
||||
common.LogDebug(fmt.Sprintf("[ICMP] 达到最大等待时间 %v,存活 %d/%d", maxWait, aliveCount, totalHosts))
|
||||
common.LogDebug(i18n.Tr("icmp_debug_max_wait", maxWait, aliveCount, totalHosts))
|
||||
break
|
||||
}
|
||||
|
||||
@@ -305,8 +305,7 @@ func waitAdaptive(hostslist []string, aliveHosts *[]string, aliveHostsMu *sync.M
|
||||
lastAliveCount = aliveCount
|
||||
} else if time.Since(lastChangeTime) >= icmpStableThreshold {
|
||||
// 连续 500ms 没有新响应,认为响应已稳定,提前结束
|
||||
common.LogDebug(fmt.Sprintf("[ICMP] 响应稳定,提前结束,耗时 %v,存活 %d/%d",
|
||||
elapsed.Round(time.Millisecond), aliveCount, totalHosts))
|
||||
common.LogDebug(i18n.Tr("icmp_debug_stable_done", elapsed.Round(time.Millisecond), aliveCount, totalHosts))
|
||||
break
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@ type LocalScanStrategy struct {
|
||||
// NewLocalScanStrategy 创建新的本地扫描策略
|
||||
func NewLocalScanStrategy() *LocalScanStrategy {
|
||||
return &LocalScanStrategy{
|
||||
BaseScanStrategy: NewBaseScanStrategy("本地扫描", FilterLocal),
|
||||
BaseScanStrategy: NewBaseScanStrategy(i18n.GetText("scan_strategy_local_name"), FilterLocal),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-22
@@ -35,7 +35,8 @@ var resourceExhaustedPatterns = []string{
|
||||
"no buffer space available",
|
||||
"cannot assign requested address",
|
||||
"connection reset by peer",
|
||||
"发包受限",
|
||||
i18n.GetText("network_rate_limited_pattern"),
|
||||
"rate limited",
|
||||
}
|
||||
|
||||
// closedPatterns 连接已关闭的错误模式
|
||||
@@ -143,7 +144,7 @@ func (f *failedPortCollector) Count() int {
|
||||
func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout int64, session *common.ScanSession, stream chan<- string) []string {
|
||||
config := session.Config
|
||||
state := session.State
|
||||
session.LogDebug(fmt.Sprintf("[PortScan] 开始: %d个主机, 线程数=%d", len(hosts), config.ThreadNum))
|
||||
session.LogDebug(i18n.Tr("port_scan_debug_start", len(hosts), config.ThreadNum))
|
||||
|
||||
// 大规模扫描预筛:跨多个 /24 时先做网段探活,跳过空网段
|
||||
if len(hosts) > subnetProbeThreshold {
|
||||
@@ -166,7 +167,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout
|
||||
}
|
||||
return nil
|
||||
}
|
||||
session.LogDebug(fmt.Sprintf("[PortScan] 端口解析完成: %d个端口", len(portList)))
|
||||
session.LogDebug(i18n.Tr("port_scan_debug_ports_parsed", len(portList)))
|
||||
|
||||
// 使用config中的排除端口配置
|
||||
excludePorts := parsers.ParsePort(config.Target.ExcludePorts)
|
||||
@@ -177,34 +178,34 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout
|
||||
|
||||
// 检查代理可靠性,如果存在全回显问题则警告
|
||||
if session.ProxyEnabled() && !session.ProxyReliable() {
|
||||
session.LogError("检测到代理存在全回显问题,端口扫描结果可能不准确")
|
||||
session.LogError(i18n.GetText("proxy_echo_warning"))
|
||||
}
|
||||
|
||||
// 创建流式迭代器(O(1) 内存,端口喷洒策略)
|
||||
iter := NewSocketIterator(hosts, portList, exclude)
|
||||
totalTasks := iter.Total()
|
||||
session.LogDebug(fmt.Sprintf("[PortScan] 总任务数: %d", totalTasks))
|
||||
session.LogDebug(i18n.Tr("port_scan_debug_total_tasks", totalTasks))
|
||||
|
||||
// 使用传入的配置
|
||||
threadNum := config.ThreadNum
|
||||
|
||||
// 大规模扫描警告和线程数自动调整
|
||||
if totalTasks > 100000 {
|
||||
session.LogInfo(fmt.Sprintf("大规模扫描: %d 个目标 (%d主机 × %d端口)", totalTasks, len(hosts), len(portList)))
|
||||
session.LogInfo(i18n.Tr("large_scan_notice", totalTasks, len(hosts), len(portList)))
|
||||
// 如果任务数超过100万且线程数大于300,自动降低线程数
|
||||
if totalTasks > 1000000 && threadNum > 300 {
|
||||
oldThreadNum := threadNum
|
||||
threadNum = 300
|
||||
session.LogInfo(fmt.Sprintf("自动调整线程数: %d -> %d (大规模扫描优化)", oldThreadNum, threadNum))
|
||||
session.LogInfo(i18n.Tr("large_scan_thread_adjusted", oldThreadNum, threadNum))
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化端口扫描进度条
|
||||
if totalTasks > 0 && config.Output.ShowProgress {
|
||||
description := fmt.Sprintf("端口扫描中(%d线程)", threadNum)
|
||||
description := i18n.Tr("port_scan_progress_description", threadNum)
|
||||
common.InitProgressBar(int64(totalTasks), description)
|
||||
}
|
||||
session.LogDebug("[PortScan] 进度条初始化完成")
|
||||
session.LogDebug(i18n.GetText("port_scan_debug_progress_ready"))
|
||||
|
||||
// 初始化并发控制
|
||||
to := time.Duration(timeout) * time.Second
|
||||
@@ -214,7 +215,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout
|
||||
failedCollector := &failedPortCollector{}
|
||||
var wg sync.WaitGroup
|
||||
|
||||
session.LogDebug(fmt.Sprintf("[PortScan] 开始创建线程池, size=%d", threadNum))
|
||||
session.LogDebug(i18n.Tr("port_scan_debug_pool_create", threadNum))
|
||||
// 创建自适应线程池(支持动态调整)
|
||||
pool, err := NewAdaptivePool(threadNum, func(task interface{}) {
|
||||
taskInfo, ok := task.(portScanTask)
|
||||
@@ -236,13 +237,13 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout
|
||||
}
|
||||
return nil
|
||||
}
|
||||
session.LogDebug("[PortScan] 线程池创建成功")
|
||||
session.LogDebug(i18n.GetText("port_scan_debug_pool_created"))
|
||||
defer pool.Release()
|
||||
|
||||
session.LogDebug("[PortScan] 开始滑动窗口调度")
|
||||
session.LogDebug(i18n.GetText("port_scan_debug_schedule_start"))
|
||||
// 滑动窗口调度:维护固定数量的"飞行中"任务
|
||||
slidingWindowSchedule(iter, pool, &wg, threadNum)
|
||||
session.LogDebug("[PortScan] 滑动窗口调度完成")
|
||||
session.LogDebug(i18n.GetText("port_scan_debug_schedule_done"))
|
||||
|
||||
// 收集结果
|
||||
aliveAddrs := collector.GetAll()
|
||||
@@ -467,7 +468,7 @@ func scanSinglePort(ctx context.Context, host string, port int, addr string, ada
|
||||
// 步骤1.5:代理连接深度验证(防止透明代理/全回显代理的假连接问题)
|
||||
valid, verifyMethod := verifyProxyConnectionDeep(conn, addr, session)
|
||||
if !valid {
|
||||
session.LogDebug(fmt.Sprintf("代理验证失败 %s: %s", addr, verifyMethod))
|
||||
session.LogDebug(i18n.Tr("proxy_verify_failed", addr, verifyMethod))
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
@@ -541,7 +542,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS
|
||||
|
||||
if n > 0 {
|
||||
if isProxyErrorResponse(buf[:n]) {
|
||||
common.LogDebug(fmt.Sprintf("代理返回错误响应 %s", addr))
|
||||
common.LogDebug(i18n.Tr("proxy_error_response", addr))
|
||||
return false, "proxy_error"
|
||||
}
|
||||
return true, "banner"
|
||||
@@ -558,7 +559,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS
|
||||
_ = conn.SetWriteDeadline(time.Time{})
|
||||
|
||||
if writeErr != nil && isConnectionClosed(writeErr) {
|
||||
common.LogDebug(fmt.Sprintf("探测写入失败 %s: %v", addr, writeErr))
|
||||
common.LogDebug(i18n.Tr("proxy_probe_write_failed", addr, writeErr))
|
||||
return false, "write_failed"
|
||||
}
|
||||
|
||||
@@ -570,7 +571,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS
|
||||
|
||||
if n > 0 {
|
||||
if isProxyErrorResponse(buf[:n]) {
|
||||
common.LogDebug(fmt.Sprintf("代理探测返回错误 %s", addr))
|
||||
common.LogDebug(i18n.Tr("proxy_probe_error_response", addr))
|
||||
return false, "proxy_error"
|
||||
}
|
||||
return true, "probe"
|
||||
@@ -581,7 +582,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS
|
||||
errStr := readErr.Error()
|
||||
for _, pattern := range proxyFailurePatterns {
|
||||
if containsFold(errStr, pattern) {
|
||||
common.LogDebug(fmt.Sprintf("代理连接被拒绝 %s: %v", addr, readErr))
|
||||
common.LogDebug(i18n.Tr("proxy_connection_rejected", addr, readErr))
|
||||
return false, "proxy_reject"
|
||||
}
|
||||
}
|
||||
@@ -591,7 +592,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS
|
||||
// 在透明代理环境下,ProxyReliable 检测可能被污染,不可信
|
||||
// 因此采用更保守的策略:无响应一律判定为关闭
|
||||
// 这样可以避免透明代理导致的全端口误报问题
|
||||
common.LogDebug(fmt.Sprintf("代理连接无响应,判定为端口关闭 %s", addr))
|
||||
common.LogDebug(i18n.Tr("proxy_no_response_closed", addr))
|
||||
return false, "no_response"
|
||||
}
|
||||
|
||||
@@ -790,7 +791,7 @@ func probeSubnets(ctx context.Context, hosts []string, timeout time.Duration, se
|
||||
return hosts
|
||||
}
|
||||
|
||||
session.LogInfo(fmt.Sprintf("网段预筛: %d 个 /24 子网, %d 个主机", len(subnets), len(hosts)))
|
||||
session.LogInfo(i18n.Tr("subnet_prefilter_start", len(subnets), len(hosts)))
|
||||
|
||||
aliveSubnets := sync.Map{}
|
||||
var wg sync.WaitGroup
|
||||
@@ -872,8 +873,7 @@ done:
|
||||
}
|
||||
|
||||
skipped := len(subnets) - aliveCount
|
||||
session.LogInfo(fmt.Sprintf("网段预筛完成: %d 个存活 (网关命中 %d), %d 个跳过, 剩余 %d 主机",
|
||||
aliveCount, gwHits, skipped, len(result)))
|
||||
session.LogInfo(i18n.Tr("subnet_prefilter_done", aliveCount, gwHits, skipped, len(result)))
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
)
|
||||
|
||||
// BytesToRegexSafeString 将字节切片转换为 Go regexp 安全的正则表达式模式字符串
|
||||
@@ -43,7 +45,7 @@ func (p *Probe) parseMatchDirective(data, prefix string, isSoft bool) (Match, er
|
||||
// 分割文本获取pattern和版本信息
|
||||
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
|
||||
if len(textSplited) == 0 {
|
||||
return match, fmt.Errorf("无效的%s指令格式", prefix)
|
||||
return match, fmt.Errorf("%s", i18n.Tr("portfinger_match_directive_invalid", prefix))
|
||||
}
|
||||
|
||||
pattern := textSplited[0]
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
)
|
||||
|
||||
// 解析指令语法,返回指令结构
|
||||
@@ -37,12 +39,12 @@ func (p *Probe) parseProbeInfo(probeStr string) error {
|
||||
|
||||
// 验证协议类型
|
||||
if proto != "TCP " && proto != "UDP " {
|
||||
return fmt.Errorf("探测器协议必须是 TCP 或 UDP")
|
||||
return fmt.Errorf("%s", i18n.GetText("portfinger_probe_protocol_invalid"))
|
||||
}
|
||||
|
||||
// 验证其他信息不为空
|
||||
if len(other) == 0 {
|
||||
return fmt.Errorf("nmap-service-probes - 探测器名称无效")
|
||||
return fmt.Errorf("%s", i18n.GetText("portfinger_probe_name_invalid"))
|
||||
}
|
||||
|
||||
// 解析指令
|
||||
@@ -64,7 +66,7 @@ func (p *Probe) fromString(data string) error {
|
||||
data = strings.TrimSpace(data)
|
||||
lines := strings.Split(data, "\n")
|
||||
if len(lines) == 0 {
|
||||
return fmt.Errorf("输入数据为空")
|
||||
return fmt.Errorf("%s", i18n.GetText("portfinger_input_empty"))
|
||||
}
|
||||
|
||||
probeStr := lines[0]
|
||||
@@ -172,7 +174,7 @@ func (v *VScan) parseProbesFromContent(content string) error {
|
||||
|
||||
// 验证文件内容
|
||||
if len(lines) == 0 {
|
||||
return fmt.Errorf("读取nmap-service-probes文件失败: 内容为空")
|
||||
return fmt.Errorf("%s", i18n.GetText("portfinger_probe_file_empty"))
|
||||
}
|
||||
|
||||
// 检查Exclude指令
|
||||
@@ -182,14 +184,14 @@ func (v *VScan) parseProbesFromContent(content string) error {
|
||||
excludeCount++
|
||||
}
|
||||
if excludeCount > 1 {
|
||||
return fmt.Errorf("nmap-service-probes文件中只允许有一个Exclude指令")
|
||||
return fmt.Errorf("%s", i18n.GetText("portfinger_probe_exclude_duplicate"))
|
||||
}
|
||||
}
|
||||
|
||||
// 验证第一行格式
|
||||
firstLine := lines[0]
|
||||
if !strings.HasPrefix(firstLine, "Exclude ") && !strings.HasPrefix(firstLine, "Probe ") {
|
||||
return fmt.Errorf("解析错误: 首行必须以\"Probe \"或\"Exclude \"开头")
|
||||
return fmt.Errorf("%s", i18n.GetText("portfinger_probe_first_line_invalid"))
|
||||
}
|
||||
|
||||
// 处理Exclude指令
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
"github.com/shadow1ng/fscan/core/portfinger"
|
||||
)
|
||||
|
||||
@@ -171,7 +172,6 @@ func (s *SmartPortInfoScanner) tryInitialBanner() ([]byte, error) {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
|
||||
// smartProbeStrategy 智能探测策略
|
||||
// 改进版:使用 nmap-service-probes.txt 中的 ports 字段和 rarity 排序
|
||||
func (s *SmartPortInfoScanner) smartProbeStrategy() {
|
||||
@@ -392,7 +392,7 @@ func (i *Info) tryProbes(response []byte, probes []*Probe) bool {
|
||||
func (i *Info) GetInfo(response []byte, probe *Probe) {
|
||||
// 响应数据有效性检查
|
||||
if len(response) <= 0 {
|
||||
common.LogDebug("响应数据为空")
|
||||
common.LogDebug(i18n.GetText("service_probe_empty_response"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -460,12 +460,12 @@ func (i *Info) handleHardMatch(response []byte, match *Match) {
|
||||
|
||||
// 特殊处理 microsoft-ds 服务
|
||||
if result.Service.Name == "microsoft-ds" {
|
||||
common.LogDebug("特殊处理 microsoft-ds 服务")
|
||||
common.LogDebug(i18n.GetText("service_probe_microsoft_ds"))
|
||||
result.Service.Extras["hostname"] = result.Banner
|
||||
}
|
||||
|
||||
i.Found = true
|
||||
common.LogDebug(fmt.Sprintf("服务识别结果: %s, Banner: %s", result.Service.Name, result.Banner))
|
||||
common.LogDebug(i18n.Tr("service_probe_identified", result.Service.Name, result.Banner))
|
||||
}
|
||||
|
||||
// handleNoMatch 处理未找到匹配的情况
|
||||
@@ -477,10 +477,10 @@ func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, so
|
||||
bannerLower := strings.ToLower(result.Banner)
|
||||
if strings.Contains(bannerLower, "http/") ||
|
||||
strings.Contains(bannerLower, "html") {
|
||||
common.LogDebug("识别为HTTP服务")
|
||||
common.LogDebug(i18n.GetText("service_probe_http_identified"))
|
||||
result.Service.Name = "http"
|
||||
} else {
|
||||
common.LogDebug("未知服务")
|
||||
common.LogDebug(i18n.GetText("service_probe_unknown"))
|
||||
result.Service.Name = "unknown"
|
||||
}
|
||||
} else {
|
||||
@@ -488,7 +488,7 @@ func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, so
|
||||
result.Service.Extras = extras.ToMap()
|
||||
result.Service.Name = softMatch.Service
|
||||
i.Found = true
|
||||
common.LogDebug(fmt.Sprintf("软匹配服务: %s", result.Service.Name))
|
||||
common.LogDebug(i18n.Tr("service_probe_soft_match", result.Service.Name))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ type ServiceScanStrategy struct {
|
||||
// NewServiceScanStrategy 创建新的服务扫描策略
|
||||
func NewServiceScanStrategy() *ServiceScanStrategy {
|
||||
return &ServiceScanStrategy{
|
||||
BaseScanStrategy: NewBaseScanStrategy("服务扫描", FilterService),
|
||||
BaseScanStrategy: NewBaseScanStrategy(i18n.GetText("scan_strategy_service_name"), FilterService),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -303,7 +303,7 @@ type WebScanStrategy struct {
|
||||
// NewWebScanStrategy 创建新的Web扫描策略
|
||||
func NewWebScanStrategy() *WebScanStrategy {
|
||||
return &WebScanStrategy{
|
||||
BaseScanStrategy: NewBaseScanStrategy("Web扫描", FilterWeb),
|
||||
BaseScanStrategy: NewBaseScanStrategy(i18n.GetText("scan_strategy_web_name"), FilterWeb),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user