diff --git a/cmd/ksubdomain/device.go b/cmd/ksubdomain/device.go index 1c3da40..647f844 100755 --- a/cmd/ksubdomain/device.go +++ b/cmd/ksubdomain/device.go @@ -10,28 +10,28 @@ import ( var deviceCommand = &cli.Command{ Name: "device", - Usage: "列出系统所有可用的网卡信息", + Usage: "List all available network interfaces on the system", Flags: []cli.Flag{}, Action: func(c *cli.Context) error { - // 否则列出所有可用的网卡 + // List all available network interfaces deviceNames, deviceMap := device.GetAllIPv4Devices() if len(deviceNames) == 0 { - gologger.Warningf("未找到可用的IPv4网卡\n") + gologger.Warningf("No available IPv4 network interfaces found\n") return nil } - gologger.Infof("系统发现 %d 个可用的网卡:\n", len(deviceNames)) + gologger.Infof("Found %d available network interface(s):\n", len(deviceNames)) for i, name := range deviceNames { ip := deviceMap[name] - gologger.Infof("[%d] 网卡名称: %s\n", i+1, name) - gologger.Infof(" IP地址: %s\n", ip.String()) + gologger.Infof("[%d] Interface: %s\n", i+1, name) + gologger.Infof(" IP Address: %s\n", ip.String()) fmt.Println("") } ether, err := device.AutoGetDevices([]string{"1.1.1.1", "8.8.8.8"}) if err != nil { - gologger.Errorf("获取网卡信息失败: %s\n", err.Error()) + gologger.Errorf("Failed to get network interface info: %s\n", err.Error()) return nil } device.PrintDeviceInfo(ether) diff --git a/cmd/ksubdomain/enum.go b/cmd/ksubdomain/enum.go index 6b89af1..52416f5 100755 --- a/cmd/ksubdomain/enum.go +++ b/cmd/ksubdomain/enum.go @@ -68,7 +68,7 @@ var enumCommand = &cli.Command{ filename := c.String("domain-list") f, err := os.Open(filename) if err != nil { - gologger.Fatalf("打开文件:%s 出现错误:%s", filename, err.Error()) + gologger.Fatalf("Failed to open file %s: %s", filename, err.Error()) } defer f.Close() scanner := bufio.NewScanner(f) @@ -85,7 +85,7 @@ var enumCommand = &cli.Command{ ok, ips := runner.IsWildCard(sub) if ok { wildIPS = append(wildIPS, ips...) - gologger.Infof("发现泛解析域名:%s", sub) + gologger.Infof("Wildcard domain detected: %s", sub) } } } @@ -105,7 +105,7 @@ var enumCommand = &cli.Command{ } else { f2, err := os.Open(filename) if err != nil { - gologger.Fatalf("打开文件:%s 出现错误:%s", c.String("filename"), err.Error()) + gologger.Fatalf("Failed to open file %s: %s", c.String("filename"), err.Error()) } defer f2.Close() iofile := bufio.NewScanner(f2) @@ -118,7 +118,7 @@ var enumCommand = &cli.Command{ } } }() - // 取域名的dns,加入到resolver中 + // Load domain's DNS records, add to resolver list specialDns := make(map[string][]string) defaultResolver := options.GetResolvers(c.StringSlice("resolvers")) // Support both old (ns) and new (use-ns-records) parameter names @@ -142,13 +142,13 @@ var enumCommand = &cli.Command{ processBar = nil } - // 输出到屏幕 + // Output to screen if c.Bool("quiet") { processBar = nil } var screenWriter outputter.Output - // 美化输出模式 + // Beautified output mode if c.Bool("beautify") || c.Bool("color") { useColor := c.Bool("color") || c.Bool("beautify") onlyDomain := c.Bool("only-domain") @@ -197,7 +197,7 @@ var enumCommand = &cli.Command{ } writer = append(writer, p) default: - gologger.Fatalf("输出类型错误:%s 暂不支持 (支持: txt, json, csv, jsonl)", outputType) + gologger.Fatalf("Unsupported output type: %s (supported: txt, json, csv, jsonl)", outputType) } } // Support both old (band) and new (bandwidth) parameter names diff --git a/cmd/ksubdomain/test.go b/cmd/ksubdomain/test.go index 8fc9e2a..17738b6 100755 --- a/cmd/ksubdomain/test.go +++ b/cmd/ksubdomain/test.go @@ -8,12 +8,12 @@ import ( var testCommand = &cli.Command{ Name: string(options.TestType), - Usage: "测试本地网卡的最大发送速度", + Usage: "Test the maximum sending speed of the local network interface", Flags: []cli.Flag{ &cli.StringFlag{ Name: "eth", Aliases: []string{"e"}, - Usage: "指定网卡名称,获取该网卡的详细信息", + Usage: "Specify network interface name to get its detailed information", }, }, Action: func(c *cli.Context) error { diff --git a/cmd/ksubdomain/verify.go b/cmd/ksubdomain/verify.go index 9121576..3b46464 100755 --- a/cmd/ksubdomain/verify.go +++ b/cmd/ksubdomain/verify.go @@ -82,7 +82,7 @@ var commonFlags = []cli.Flag{ &cli.BoolFlag{ Name: "only-domain", Aliases: []string{"od"}, - Usage: "只输出域名,不显示IP (修复 Issue #67)", + Usage: "Only output domain names, do not display IP (Fix Issue #67)", Value: false, }, &cli.IntFlag{ @@ -169,7 +169,7 @@ var verifyCommand = &cli.Command{ } } render := make(chan string) - // 读取文件 + // Read domains from file go func() { for _, line := range domains { render <- line @@ -177,7 +177,7 @@ var verifyCommand = &cli.Command{ if c.String("filename") != "" { f2, err := os.Open(c.String("filename")) if err != nil { - gologger.Fatalf("打开文件:%s 出现错误:%s", c.String("filename"), err.Error()) + gologger.Fatalf("Failed to open file %s: %s", c.String("filename"), err.Error()) } defer f2.Close() iofile := bufio.NewScanner(f2) @@ -189,7 +189,7 @@ var verifyCommand = &cli.Command{ close(render) }() - // 输出到屏幕 + // Output to screen if c.Bool("quiet") { processBar = nil } @@ -197,7 +197,7 @@ var verifyCommand = &cli.Command{ var screenWriter outputter.Output var err error - // 美化输出模式 + // Beautified output mode if c.Bool("beautify") || c.Bool("color") { useColor := c.Bool("color") || c.Bool("beautify") onlyDomain := c.Bool("only-domain") @@ -239,14 +239,14 @@ var verifyCommand = &cli.Command{ p := output2.NewCsvOutput(outputFile, wildFilterMode) writer = append(writer, p) case "jsonl": - // JSONL (JSON Lines) 格式: 每行一个 JSON,便于流式处理 + // JSONL (JSON Lines) format: One JSON per line for streaming p, err := output2.NewJSONLOutput(outputFile) if err != nil { gologger.Fatalf(err.Error()) } writer = append(writer, p) default: - gologger.Fatalf("输出类型错误:%s 暂不支持 (支持: txt, json, csv, jsonl)", outputType) + gologger.Fatalf("Unsupported output type: %s (supported: txt, json, csv, jsonl)", outputType) } } resolver := options.GetResolvers(c.StringSlice("resolvers")) diff --git a/dev.md b/dev.md index c6c57a1..a12d69a 100755 --- a/dev.md +++ b/dev.md @@ -1,7 +1,7 @@ -【已过时,待重写】 +[Outdated — pending rewrite] -一个简单的调用例子 -注意: 不要启动多个ksubdomain,ksubdomain启动一个就可以发挥最大作用。 +A simple usage example. +Note: Do not start multiple instances of ksubdomain. A single instance is enough to achieve maximum performance. ```go package main @@ -55,26 +55,28 @@ func main() { r.Close() } ``` -可以看到调用很简单,就是填写`options`参数,然后调用runner启动就好了,重要的是options填什么。 -options的参数结构 + +As you can see, the usage is straightforward: fill in the `options` struct and call the runner to start. The key is knowing what to put in each option field. + +Options struct definition: ```go type Options struct { - Rate int64 // 每秒发包速率 - Domain io.Reader // 域名输入 - DomainTotal int // 扫描域名总数 - Resolvers []string // dns resolvers - Silent bool // 安静模式 - TimeOut int // 超时时间 单位(秒) - Retry int // 最大重试次数 - Method string // verify模式 enum模式 test模式 - DnsType string // dns类型 a ns aaaa - Writer []outputter.Output // 输出结构 + Rate int64 // Packet send rate per second + Domain io.Reader // Domain input + DomainTotal int // Total number of domains to scan + Resolvers []string // DNS resolvers + Silent bool // Silent mode + TimeOut int // Timeout in seconds + Retry int // Maximum retry count + Method string // verify mode / enum mode / test mode + DnsType string // DNS record type: a, ns, aaaa + Writer []outputter.Output // Output handlers ProcessBar processbar.ProcessBar - EtherInfo *device.EtherTable // 网卡信息 + EtherInfo *device.EtherTable // Network adapter info } ``` -1. ksubdomain底层接口只是一个dns验证器,如果要通过一级域名枚举,需要把全部的域名都放入`Domain`字段中,可以看enum参数是怎么写的 `cmd/ksubdomain/enum.go` -2. Write参数是一个outputter.Output接口,用途是如何处理DNS返回的接口,ksubdomain已经内置了三种接口在 `runner/outputter/output`中,主要作用是把数据存入内存、数据写入文件、数据打印到屏幕,可以自己实现这个接口,实现自定义的操作。 -3. ProcessBar参数是一个processbar.ProcessBar接口,主要用途是将程序内`成功个数`、`发送个数`、`队列数`、`接收数`、`失败数`、`耗时`传递给用户,实现这个参数可以时时获取这些。 -4. EtherInfo是*device.EtherTable类型,用来获取网卡的信息,一般用函数`options.GetDeviceConfig()`即可自动获取网卡配置。 +1. The underlying interface of ksubdomain is just a DNS validator. If you want to enumerate subdomains from a root domain, you need to put all the full domain names into the `Domain` field. See how the enum command does it in `cmd/ksubdomain/enum.go`. +2. The `Writer` field is an `outputter.Output` interface that defines how DNS responses are handled. ksubdomain ships with three built-in implementations in `runner/outputter/output`: store data in memory, write data to a file, and print data to the screen. You can implement this interface yourself for custom behavior. +3. The `ProcessBar` field is a `processbar.ProcessBar` interface. Its purpose is to expose internal statistics—success count, sent count, queue length, received count, failed count, and elapsed time—to the caller in real time. +4. `EtherInfo` is of type `*device.EtherTable` and is used to obtain network adapter information. You can usually just call `options.GetDeviceConfig()` to auto-detect the adapter configuration. diff --git a/pkg/core/conf/config.go b/pkg/core/conf/config.go index 3e60ad1..bc81a09 100755 --- a/pkg/core/conf/config.go +++ b/pkg/core/conf/config.go @@ -3,5 +3,5 @@ package conf const ( Version = "2.4" AppName = "KSubdomain" - Description = "无状态子域名爆破工具" + Description = "Stateless subdomain brute-force tool" ) diff --git a/pkg/core/options/device.go b/pkg/core/options/device.go index 2d28b2c..c42859e 100755 --- a/pkg/core/options/device.go +++ b/pkg/core/options/device.go @@ -5,13 +5,13 @@ import ( "github.com/boy-hack/ksubdomain/v2/pkg/device" ) -// GetDeviceConfig 获取网卡配置信息 -// 改进版本:优先通过路由表获取网卡信息,不依赖配置文件缓存 +// GetDeviceConfig retrieves network interface configuration. +// Improved version: prioritizes getting interface info via routing table, no dependency on config file cache. func GetDeviceConfig(dnsServer []string) *device.EtherTable { - // 使用改进的自动识别方法,优先通过路由表获取,不依赖配置文件 + // Use improved auto-detection method: routing table first, no config file dependency ether, err := device.AutoGetDevicesImproved(dnsServer) if err != nil { - gologger.Fatalf("自动识别外网网卡失败: %v\n", err) + gologger.Fatalf("Failed to auto-detect external network interface: %v\n", err) } device.PrintDeviceInfo(ether) diff --git a/pkg/core/options/options.go b/pkg/core/options/options.go index 8d076c0..731f2a3 100755 --- a/pkg/core/options/options.go +++ b/pkg/core/options/options.go @@ -18,20 +18,20 @@ const ( ) type Options struct { - Rate int64 // 每秒发包速率 - Domain chan string // 域名输入 - Resolvers []string // dns resolvers - Silent bool // 安静模式 - TimeOut int // 超时时间 单位(秒) - Retry int // 最大重试次数 - Method OptionMethod // verify模式 enum模式 test模式 - Writer []outputter.Output // 输出结构 + Rate int64 // Packet sending rate per second + Domain chan string // Domain input channel + Resolvers []string // DNS resolvers + Silent bool // Silent mode + TimeOut int // Timeout in seconds + Retry int // Maximum retry count + Method OptionMethod // verify / enum / test mode + Writer []outputter.Output // Output handlers ProcessBar processbar.ProcessBar - EtherInfo *device2.EtherTable // 网卡信息 - SpecialResolvers map[string][]string // 可针对特定域名使用的dns resolvers - WildcardFilterMode string // 泛解析过滤模式: "basic", "advanced", "none" + EtherInfo *device2.EtherTable // Network interface info + SpecialResolvers map[string][]string // DNS resolvers for specific domains + WildcardFilterMode string // Wildcard filter mode: "basic", "advanced", "none" WildIps []string - Predict bool // 是否开启预测模式 + Predict bool // Enable prediction mode } func Band2Rate(bandWith string) int64 { @@ -53,7 +53,7 @@ func Band2Rate(bandWith string) int64 { default: gologger.Fatalf("unknown bandwith suffix '%s' (supported suffixes are G,M and K)\n", suffix) } - packSize := int64(80) // 一个DNS包大概有74byte + packSize := int64(80) // A DNS packet is approximately 74 bytes rate = rate / packSize return rate } @@ -68,8 +68,8 @@ func GetResolvers(resolvers []string) []string { defaultDns := []string{ "1.1.1.1", "8.8.8.8", - "180.76.76.76", //百度公共 DNS - "180.184.1.1", //火山引擎 + "180.76.76.76", // Baidu Public DNS + "180.184.1.1", // Volcengine "180.184.2.2", } rs = defaultDns diff --git a/pkg/core/predict/generator.go b/pkg/core/predict/generator.go index 27c14c0..a2286b6 100755 --- a/pkg/core/predict/generator.go +++ b/pkg/core/predict/generator.go @@ -14,39 +14,39 @@ var cfg string //go:embed data/regular.dict var dict string -// DomainGenerator 用于生成预测域名 +// DomainGenerator is used to generate predicted domain names type DomainGenerator struct { - categories map[string][]string // 存储块分类和对应的值 - patterns []string // 域名组合模式 - subdomain string // 子域名部分 - domain string // 根域名部分 - output chan string // 输出接口 - count int // 生成的域名计数 - mu sync.Mutex // 保护count和output的互斥锁 + categories map[string][]string // stores block categories and their values + patterns []string // domain name combination patterns + subdomain string // subdomain part + domain string // root domain part + output chan string // output channel + count int // count of generated domain names + mu sync.Mutex // mutex to protect count and output } -// NewDomainGenerator 创建一个新的域名生成器 +// NewDomainGenerator creates a new domain name generator func NewDomainGenerator(output chan string) (*DomainGenerator, error) { - // 创建生成器实例 + // Create generator instance dg := &DomainGenerator{ categories: make(map[string][]string), output: output, } - // 加载分类字典 + // Load category dictionary if err := dg.loadDictionary(); err != nil { - return nil, fmt.Errorf("加载字典文件失败: %v", err) + return nil, fmt.Errorf("failed to load dictionary file: %v", err) } - // 加载配置模式 + // Load config patterns if err := dg.loadPatterns(); err != nil { - return nil, fmt.Errorf("加载配置文件失败: %v", err) + return nil, fmt.Errorf("failed to load config file: %v", err) } return dg, nil } -// 从字典文件加载分类信息 +// loadDictionary loads category information from the dictionary file func (dg *DomainGenerator) loadDictionary() error { scanner := bufio.NewScanner(strings.NewReader(dict)) var currentCategory string @@ -57,12 +57,12 @@ func (dg *DomainGenerator) loadDictionary() error { continue } - // 检查是否是分类标识 [category] + // Check if it's a category identifier [category] if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { currentCategory = line[1 : len(line)-1] dg.categories[currentCategory] = []string{} } else if currentCategory != "" { - // 如果有当前分类,添加值 + // If there's a current category, add the value dg.categories[currentCategory] = append(dg.categories[currentCategory], line) } } @@ -70,7 +70,7 @@ func (dg *DomainGenerator) loadDictionary() error { return scanner.Err() } -// 从配置文件加载域名生成模式 +// loadPatterns loads domain generation patterns from the config file func (dg *DomainGenerator) loadPatterns() error { scanner := bufio.NewScanner(strings.NewReader(cfg)) for scanner.Scan() { @@ -83,35 +83,35 @@ func (dg *DomainGenerator) loadPatterns() error { return scanner.Err() } -// SetBaseDomain 设置基础域名 +// SetBaseDomain sets the base domain func (dg *DomainGenerator) SetBaseDomain(domain string) { - // 分离子域名和根域名 + // Split subdomain and root domain parts := strings.Split(domain, ".") if len(parts) <= 2 { - // 如果只有根域名 (example.com) + // Only root domain (example.com) dg.subdomain = "" dg.domain = domain } else { - // 有子域名 (sub.example.com) + // Has subdomain (sub.example.com) dg.subdomain = parts[0] dg.domain = strings.Join(parts[1:], ".") } } -// GenerateDomains 生成预测域名并实时输出 +// GenerateDomains generates predicted domain names and outputs them in real time func (dg *DomainGenerator) GenerateDomains() int { dg.mu.Lock() dg.count = 0 dg.mu.Unlock() - // 如果没有设置子域名,则直接返回 + // If no subdomain is set, return immediately if dg.subdomain == "" && dg.domain == "" { return 0 } - // 遍历所有模式 + // Iterate over all patterns for _, pattern := range dg.patterns { - // 递归处理每个模式中的标签替换 + // Recursively process tag replacements in each pattern dg.processPattern(pattern, map[string]string{ "subdomain": dg.subdomain, "domain": dg.domain, @@ -124,12 +124,12 @@ func (dg *DomainGenerator) GenerateDomains() int { return result } -// processPattern 递归处理模式中的标签替换 +// processPattern recursively processes tag replacements in a pattern func (dg *DomainGenerator) processPattern(pattern string, replacements map[string]string) { - // 查找第一个标签 + // Find the first tag startIdx := strings.Index(pattern, "{") if startIdx == -1 { - // 没有更多标签,输出最终结果 + // No more tags, output the final result if pattern != "" && dg.output != nil { dg.mu.Lock() dg.output <- pattern @@ -141,61 +141,61 @@ func (dg *DomainGenerator) processPattern(pattern string, replacements map[strin endIdx := strings.Index(pattern, "}") if endIdx == -1 || endIdx < startIdx { - // 标签格式不正确,直接返回 + // Malformed tag, return directly return } - // 提取标签名 + // Extract tag name tagName := pattern[startIdx+1 : endIdx] - // 检查是否已有替换值 + // Check if a replacement value already exists if value, exists := replacements[tagName]; exists { - // 已有替换值,直接替换并继续处理 + // Already has a replacement value, substitute and continue processing newPattern := pattern[:startIdx] + value + pattern[endIdx+1:] dg.processPattern(newPattern, replacements) return } - // 从分类中获取替换值 + // Get replacement values from category values, exists := dg.categories[tagName] if !exists || len(values) == 0 { - // 没有找到替换值,跳过此标签 + // No replacement value found, skip this tag newPattern := pattern[:startIdx] + pattern[endIdx+1:] dg.processPattern(newPattern, replacements) return } - // 对每个可能的替换值递归处理 + // Recursively process each possible replacement value for _, value := range values { - // 创建新的替换映射 + // Create a new replacement map newReplacements := make(map[string]string) for k, v := range replacements { newReplacements[k] = v } newReplacements[tagName] = value - // 替换当前标签并继续处理 + // Replace current tag and continue processing newPattern := pattern[:startIdx] + value + pattern[endIdx+1:] dg.processPattern(newPattern, newReplacements) } } -// PredictDomains 根据给定域名预测可能的域名变体,直接输出结果 +// PredictDomains predicts possible domain name variants for a given domain and outputs them directly func PredictDomains(domain string, output chan string) (int, error) { - // 检查输出对象是否为nil + // Check if output channel is nil if output == nil { - return 0, fmt.Errorf("输出对象不能为空") + return 0, fmt.Errorf("output channel cannot be nil") } - // 创建域名生成器 + // Create domain generator generator, err := NewDomainGenerator(output) if err != nil { return 0, err } - // 设置基础域名 + // Set base domain generator.SetBaseDomain(domain) - // 生成预测域名并返回生成的数量 + // Generate predicted domains and return the count return generator.GenerateDomains(), nil } diff --git a/pkg/core/predict/generator_test.go b/pkg/core/predict/generator_test.go index 75c45f0..0c58ec3 100755 --- a/pkg/core/predict/generator_test.go +++ b/pkg/core/predict/generator_test.go @@ -18,7 +18,7 @@ func TestRealConfigFiles(t *testing.T) { var buf output count, err := PredictDomains("test.example.com", &buf) if err != nil { - t.Fatalf("使用实际配置文件进行域名预测失败: %v", err) + t.Fatalf("Domain prediction with real config files failed: %v", err) } t.Log(count) assert.Greater(t, count, 0) diff --git a/pkg/core/predict/readme.md b/pkg/core/predict/readme.md index 49b6b08..13c8b71 100755 --- a/pkg/core/predict/readme.md +++ b/pkg/core/predict/readme.md @@ -1,13 +1,13 @@ -输入域名 shoot.example.com +Input domain: shoot.example.com {subdomain} => shoot {domain} => example.com -{x}为分类 -{x1}为除了x的其他分类 +{x} is the category +{x1} is any category other than x ``` {x}.{subdomain}.{domain} {x}-{subdomain}.{domain} {x}-{x1}-{subdomain}.{domain} {subdomain}-{x}.{domain} -``` \ No newline at end of file +``` diff --git a/pkg/core/util.go b/pkg/core/util.go index 283701f..d5ad5f0 100755 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -27,7 +27,7 @@ func RandInt64(min, max int64) int64 { return rand.Int63n(max-min) + min } -// LinesInFile 读取文件 返回每行的数组 +// LinesInFile reads a file and returns an array of lines func LinesInFile(fileName string) ([]string, error) { result := []string{} f, err := os.Open(fileName) @@ -46,7 +46,7 @@ func LinesInFile(fileName string) ([]string, error) { return result, nil } -// LinesReaderInFile 读取文件,返回行数 +// LinesReaderInFile reads a file and returns the number of lines func LinesReaderInFile(filename string) (int, error) { f, err := os.Open(filename) if err != nil { @@ -54,7 +54,7 @@ func LinesReaderInFile(filename string) (int, error) { } defer f.Close() - // 使用更大的缓冲区减少IO操作 + // Use a larger buffer to reduce I/O operations buf := make([]byte, 32*1024) count := 0 @@ -64,7 +64,7 @@ func LinesReaderInFile(filename string) (int, error) { break } - // 直接遍历缓冲区计数换行符 + // Directly traverse the buffer to count newline characters for i := 0; i < readSize; i++ { if buf[i] == '\n' { count++ @@ -73,7 +73,7 @@ func LinesReaderInFile(filename string) (int, error) { if err != nil { if err == io.EOF { - // 处理文件末尾没有换行符的情况 + // Handle the case where the file doesn't end with a newline if readSize > 0 && (count == 0 || buf[readSize-1] != '\n') { count++ } @@ -83,7 +83,7 @@ func LinesReaderInFile(filename string) (int, error) { } } - // 处理空文件或只有一行没有换行符的文件 + // Handle empty files or single-line files without a trailing newline if count == 0 { count = 1 } @@ -92,7 +92,7 @@ func LinesReaderInFile(filename string) (int, error) { } func FileExists(path string) bool { - _, err := os.Stat(path) //os.Stat获取文件信息 + _, err := os.Stat(path) // os.Stat retrieves file information if err != nil { if os.IsExist(err) { return true diff --git a/pkg/device/device.go b/pkg/device/device.go index 578d2e3..dc27e20 100755 --- a/pkg/device/device.go +++ b/pkg/device/device.go @@ -13,15 +13,15 @@ import ( "gopkg.in/yaml.v3" ) -// EtherTable 存储网卡信息的数据结构 +// EtherTable stores network interface information type EtherTable struct { - SrcIp net.IP `yaml:"src_ip"` // 源IP地址 - Device string `yaml:"device"` // 网卡设备名称 - SrcMac SelfMac `yaml:"src_mac"` // 源MAC地址 - DstMac SelfMac `yaml:"dst_mac"` // 目标MAC地址(通常是网关) + SrcIp net.IP `yaml:"src_ip"` // Source IP address + Device string `yaml:"device"` // Network interface device name + SrcMac SelfMac `yaml:"src_mac"` // Source MAC address + DstMac SelfMac `yaml:"dst_mac"` // Destination MAC address (usually the gateway) } -// ReadConfig 从文件读取EtherTable配置 +// ReadConfig reads EtherTable configuration from a file func ReadConfig(filename string) (*EtherTable, error) { data, err := os.ReadFile(filename) if err != nil { @@ -35,7 +35,7 @@ func ReadConfig(filename string) (*EtherTable, error) { return ðer, nil } -// SaveConfig 保存EtherTable配置到文件 +// SaveConfig saves EtherTable configuration to a file func (e *EtherTable) SaveConfig(filename string) error { data, err := yaml.Marshal(e) if err != nil { @@ -44,7 +44,7 @@ func (e *EtherTable) SaveConfig(filename string) error { return os.WriteFile(filename, data, 0666) } -// isWSL 检测是否在 WSL/WSL2 环境中运行 +// isWSL detects whether running in a WSL/WSL2 environment func isWSL() bool { if runtime.GOOS != "linux" { return false @@ -57,131 +57,131 @@ func isWSL() bool { return strings.Contains(version, "microsoft") || strings.Contains(version, "wsl") } -// isDeviceUp 检查网卡是否处于激活状态 +// isDeviceUp checks whether a network interface is active func isDeviceUp(devicename string) bool { iface, err := net.InterfaceByName(devicename) if err != nil { return false } - // 检查 UP 标志位 + // Check the UP flag return iface.Flags&net.FlagUp != 0 } -// PcapInit 初始化pcap句柄 -// 修复 Issue #68: 增强错误提示,特别是 WSL2 环境 -// 修复 Mac 缓冲区问题: 使用 InactiveHandle 设置更大的缓冲区 +// PcapInit initializes a pcap handle. +// Fix Issue #68: enhanced error messages, especially for WSL2 environments. +// Fix Mac buffer issue: use InactiveHandle to set a larger buffer size. func PcapInit(devicename string) (*pcap.Handle, error) { - // 使用 InactiveHandle 可以在激活前设置参数 - // 这对 Mac BPF 缓冲区优化特别重要 + // Using InactiveHandle allows setting parameters before activation. + // This is especially important for Mac BPF buffer optimization. inactive, err := pcap.NewInactiveHandle(devicename) if err != nil { - gologger.Fatalf("创建 pcap 句柄失败: %s\n", err.Error()) + gologger.Fatalf("Failed to create pcap handle: %s\n", err.Error()) return nil, err } defer inactive.CleanUp() - - // 设置 snapshot 长度为 64KB (原来 1024 太小) - // DNS 包通常 < 512 字节,但完整以太网帧可能更大 + + // Set snapshot length to 64KB (the original 1024 was too small). + // DNS packets are usually < 512 bytes, but full Ethernet frames may be larger. err = inactive.SetSnapLen(65536) if err != nil { - gologger.Warningf("设置 SnapLen 失败: %v\n", err) + gologger.Warningf("Failed to set SnapLen: %v\n", err) } - - // 设置超时为阻塞模式 + + // Set timeout to blocking mode err = inactive.SetTimeout(-1 * time.Second) if err != nil { - gologger.Warningf("设置 Timeout 失败: %v\n", err) + gologger.Warningf("Failed to set Timeout: %v\n", err) } - - // Mac 平台专用优化: 增大 BPF 缓冲区 - // Mac 默认 BPF 缓冲区很小 (通常 32KB),高速发包容易溢出 - // 设置为 2MB 可显著减少 "No buffer space available" 错误 + + // Mac-specific optimization: increase BPF buffer size. + // Mac's default BPF buffer is small (typically 32KB), which can overflow during high-speed sending. + // Setting it to 2MB significantly reduces "No buffer space available" errors. if runtime.GOOS == "darwin" { - bufferSize := 2 * 1024 * 1024 // 2MB + bufferSize := 2 * 1024 * 1024 // 2MB err = inactive.SetBufferSize(bufferSize) if err != nil { - gologger.Warningf("Mac: 设置 BPF 缓冲区大小失败: %v (将使用默认值)\n", err) + gologger.Warningf("Mac: Failed to set BPF buffer size: %v (will use default)\n", err) } else { - gologger.Infof("Mac: BPF 缓冲区已设置为 %d MB\n", bufferSize/(1024*1024)) + gologger.Infof("Mac: BPF buffer set to %d MB\n", bufferSize/(1024*1024)) } } - - // 设置即时模式 (减少延迟) + + // Set immediate mode (reduce latency). + // Failure is non-fatal; some platforms may not support it. err = inactive.SetImmediateMode(true) if err != nil { - // 即时模式失败不致命,某些平台可能不支持 - gologger.Debugf("设置即时模式失败: %v (非致命)\n", err) + gologger.Debugf("Failed to set immediate mode: %v (non-fatal)\n", err) } - - // 激活句柄 + + // Activate the handle handle, err := inactive.Activate() if err != nil { - // 修复 Issue #68: 提供详细的错误信息和解决方案 + // Fix Issue #68: provide detailed error messages and solutions errMsg := err.Error() - - // 情况1: 网卡未激活 + + // Case 1: Interface is not up if strings.Contains(errMsg, "not up") { var solution string if isWSL() { - // WSL/WSL2 特殊提示 + // WSL/WSL2 specific hint solution = fmt.Sprintf( - "网卡 %s 未激活 (WSL/WSL2 环境检测到)\n\n"+ - "解决方案:\n"+ - " 1. 激活网卡: sudo ip link set %s up\n"+ - " 2. 或使用其他网卡: ksubdomain --eth <网卡名>\n"+ - " 3. 查看可用网卡: ip link show\n"+ - " 4. WSL2 通常使用 eth0,尝试: --eth eth0\n", + "Interface %s is not up (WSL/WSL2 environment detected)\n\n"+ + "Solutions:\n"+ + " 1. Bring up the interface: sudo ip link set %s up\n"+ + " 2. Or use another interface: ksubdomain --eth \n"+ + " 3. List available interfaces: ip link show\n"+ + " 4. WSL2 usually uses eth0, try: --eth eth0\n", devicename, devicename, ) } else { solution = fmt.Sprintf( - "网卡 %s 未激活\n\n"+ - "解决方案:\n"+ - " 1. Linux: sudo ip link set %s up\n"+ - " 2. 或使用其他网卡: ksubdomain --eth <网卡名>\n"+ - " 3. 查看可用网卡: ip link show 或 ifconfig -a\n", + "Interface %s is not up\n\n"+ + "Solutions:\n"+ + " 1. Linux: sudo ip link set %s up\n"+ + " 2. Or use another interface: ksubdomain --eth \n"+ + " 3. List available interfaces: ip link show or ifconfig -a\n", devicename, devicename, ) } gologger.Fatalf(solution) - return nil, fmt.Errorf("网卡未激活: %s", devicename) + return nil, fmt.Errorf("interface not up: %s", devicename) } - - // 情况2: 权限不足 + + // Case 2: Permission denied if strings.Contains(errMsg, "permission denied") || strings.Contains(errMsg, "Operation not permitted") { solution := fmt.Sprintf( - "权限不足,无法访问网卡 %s\n\n"+ - "解决方案:\n"+ - " 运行: sudo %s [参数...]\n", + "Permission denied accessing interface %s\n\n"+ + "Solution:\n"+ + " Run: sudo %s [args...]\n", devicename, os.Args[0], ) gologger.Fatalf(solution) - return nil, fmt.Errorf("权限不足: %s", devicename) + return nil, fmt.Errorf("permission denied: %s", devicename) } - - // 情况3: 网卡不存在 + + // Case 3: Interface does not exist if strings.Contains(errMsg, "No such device") || strings.Contains(errMsg, "doesn't exist") { solution := fmt.Sprintf( - "网卡 %s 不存在\n\n"+ - "解决方案:\n"+ - " 1. 查看可用网卡:\n"+ - " Linux/WSL: ip link show\n"+ - " macOS: ifconfig -a\n"+ - " 2. 使用正确的网卡名: ksubdomain --eth <网卡名>\n"+ - " 3. 常见网卡名:\n"+ - " Linux: eth0, ens33, wlan0\n"+ - " macOS: en0, en1\n"+ - " WSL2: eth0\n", + "Interface %s does not exist\n\n"+ + "Solutions:\n"+ + " 1. List available interfaces:\n"+ + " Linux/WSL: ip link show\n"+ + " macOS: ifconfig -a\n"+ + " 2. Use the correct interface name: ksubdomain --eth \n"+ + " 3. Common interface names:\n"+ + " Linux: eth0, ens33, wlan0\n"+ + " macOS: en0, en1\n"+ + " WSL2: eth0\n", devicename, ) gologger.Fatalf(solution) - return nil, fmt.Errorf("网卡不存在: %s", devicename) + return nil, fmt.Errorf("interface not found: %s", devicename) } - - // 其他错误 - gologger.Fatalf("pcap初始化失败: %s\n详细错误: %s\n", devicename, errMsg) + + // Other errors + gologger.Fatalf("pcap initialization failed: %s\nDetails: %s\n", devicename, errMsg) return nil, err } - + return handle, nil } diff --git a/pkg/device/hardware.go b/pkg/device/hardware.go index f0390d0..e5e10b6 100755 --- a/pkg/device/hardware.go +++ b/pkg/device/hardware.go @@ -31,7 +31,7 @@ func (d *SelfMac) UnmarshalYAML(value *yaml.Node) error { return nil } -// 打印设备信息 +// PrintDeviceInfo prints network interface information func PrintDeviceInfo(ether *EtherTable) { gologger.Infof("Device: %s\n", ether.Device) gologger.Infof("IP: %s\n", ether.SrcIp.String()) diff --git a/pkg/device/network.go b/pkg/device/network.go index 67096e5..de30da9 100755 --- a/pkg/device/network.go +++ b/pkg/device/network.go @@ -17,21 +17,21 @@ import ( "github.com/google/gopacket/pcap" ) -// 获取所有IPv4网卡信息 +// GetAllIPv4Devices returns all available IPv4 network interfaces func GetAllIPv4Devices() ([]string, map[string]net.IP) { devices, err := pcap.FindAllDevs() deviceNames := []string{} deviceMap := make(map[string]net.IP) if err != nil { - gologger.Fatalf("获取网络设备失败: %s\n", err.Error()) + gologger.Fatalf("Failed to get network devices: %s\n", err.Error()) return deviceNames, deviceMap } for _, d := range devices { for _, address := range d.Addresses { ip := address.IP - // 只保留IPv4且非回环地址 + // Keep only IPv4 non-loopback addresses if ip.To4() != nil { deviceMap[d.Name] = ip deviceNames = append(deviceNames, d.Name) @@ -54,95 +54,95 @@ func ValidDNS(dns string) bool { } func AutoGetDevices(userDNS []string) (*EtherTable, error) { - // 有效DNS列表 + // Valid DNS list var validDNS []string - // 1. 首先检测用户提供的DNS + // 1. First check user-provided DNS servers if len(userDNS) > 0 { for _, dns := range userDNS { if ValidDNS(dns) { validDNS = append(validDNS, dns) } else { - gologger.Warningf("用户提供的DNS服务器无效: %s\n", dns) + gologger.Warningf("User-provided DNS server is invalid: %s\n", dns) } } } - // 2. 如果用户DNS都无效,尝试系统DNS + // 2. If all user DNS are invalid, try system DNS if len(validDNS) == 0 { - gologger.Infof("尝试获取系统DNS服务器...\n") + gologger.Infof("Trying to get system DNS servers...\n") systemDNS, err := utils.GetSystemDefaultDNS() if err == nil && len(systemDNS) > 0 { for _, dns := range systemDNS { if ValidDNS(dns) { validDNS = append(validDNS, dns) } else { - gologger.Debugf("系统DNS服务器无效: %s\n", dns) + gologger.Debugf("System DNS server is invalid: %s\n", dns) } } } else { - gologger.Warningf("获取系统DNS失败: %v\n", err) + gologger.Warningf("Failed to get system DNS: %v\n", err) } } if len(validDNS) == 0 { - return nil, fmt.Errorf("没有找到有效DNS,无法进行测试") + return nil, fmt.Errorf("no valid DNS found, cannot proceed with testing") } - gologger.Infof("使用以下DNS服务器进行测试: %v\n", validDNS) + gologger.Infof("Using the following DNS servers for testing: %v\n", validDNS) return AutoGetDevicesWithDNS(validDNS), nil } -// AutoGetDevicesWithDNS 使用指定DNS自动获取外网发包网卡 -// 如果传入的DNS无效,则尝试使用系统DNS +// AutoGetDevicesWithDNS automatically detects the external network interface using the specified DNS servers. +// If the provided DNS servers are invalid, it falls back to the system DNS. func AutoGetDevicesWithDNS(validDNS []string) *EtherTable { - // 获取所有IPv4网卡 + // Get all IPv4 interfaces deviceNames, _ := GetAllIPv4Devices() if len(deviceNames) == 0 { - gologger.Fatalf("未发现可用的IPv4网卡\n") + gologger.Fatalf("No available IPv4 network interfaces found\n") return nil } - // 创建随机域名用于测试 + // Create a random domain name for testing domain := core.RandomStr(6) + ".baidu.com" signal := make(chan *EtherTable) - // 启动上下文,用于控制所有goroutine + // Start context to control all goroutines ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // 测试所有网卡 + // Test all interfaces activeDeviceCount := 0 for _, deviceName := range deviceNames { - gologger.Infof("正在测试网卡 %s 的连通性...\n", deviceName) + gologger.Infof("Testing connectivity of interface %s...\n", deviceName) go testDeviceConnectivity(ctx, deviceName, domain, signal) activeDeviceCount++ } - // 等待测试结果或超时 + // Wait for test results or timeout return waitForDeviceTest(signal, domain, validDNS, 30) } -// 测试网卡连通性 +// testDeviceConnectivity tests the connectivity of a network interface func testDeviceConnectivity(ctx context.Context, deviceName string, domain string, signal chan<- *EtherTable) { var ( - snapshot_len int32 = 2048 // 增加抓包大小 - promiscuous bool = true // 启用混杂模式 - timeout time.Duration = 500 * time.Millisecond // 增加超时时间 + snapshot_len int32 = 2048 // Increased capture size + promiscuous bool = true // Enable promiscuous mode + timeout time.Duration = 500 * time.Millisecond // Increased timeout ) handle, err := pcap.OpenLive(deviceName, snapshot_len, promiscuous, timeout) if err != nil { - gologger.Debugf("无法打开网卡 %s: %s\n", deviceName, err.Error()) + gologger.Debugf("Cannot open interface %s: %s\n", deviceName, err.Error()) return } defer handle.Close() - // 添加BPF过滤器,只捕获DNS响应包 + // Add BPF filter to capture only DNS response packets err = handle.SetBPFFilter("udp port 53") if err != nil { - gologger.Debugf("设置过滤器失败 %s: %s\n", deviceName, err.Error()) - // 继续尝试,不直接返回 + gologger.Debugf("Failed to set filter on %s: %s\n", deviceName, err.Error()) + // Continue trying without returning } for { @@ -163,7 +163,7 @@ func testDeviceConnectivity(ctx context.Context, deviceName string, domain strin if errors.Is(err, pcap.NextErrorTimeoutExpired) { continue } - continue // 不要立即返回,继续尝试 + continue // Don't return immediately, keep trying } var decoded []gopacket.LayerType @@ -172,7 +172,7 @@ func testDeviceConnectivity(ctx context.Context, deviceName string, domain strin continue } - // 检查是否解析到DNS层 + // Check if the DNS layer was decoded dnsFound := false for _, layerType := range decoded { if layerType == layers.LayerTypeDNS { @@ -184,15 +184,15 @@ func testDeviceConnectivity(ctx context.Context, deviceName string, domain strin continue } - // 只处理DNS响应 + // Process DNS responses only if !dns.QR { continue } - // 检查是否匹配我们的测试域名 + // Check if it matches our test domain for _, q := range dns.Questions { questionName := string(q.Name) - gologger.Debugf("收到DNS响应 %s,域名: %s\n", deviceName, questionName) + gologger.Debugf("Received DNS response on %s, domain: %s\n", deviceName, questionName) if questionName == domain || questionName == domain+"." { etherTable := EtherTable{ SrcIp: ipv4.DstIP, @@ -208,31 +208,31 @@ func testDeviceConnectivity(ctx context.Context, deviceName string, domain strin } } -// 等待设备测试结果 +// waitForDeviceTest waits for device test results func waitForDeviceTest(signal <-chan *EtherTable, domain string, dnsServers []string, timeout int) *EtherTable { ticker := time.NewTicker(time.Second) defer ticker.Stop() count := 0 - // 轮询使用DNS服务器列表 + // Round-robin over DNS server list dnsIndex := 0 for { select { case result := <-signal: - gologger.Infof("成功获取到外网网卡: %s\n", result.Device) + gologger.Infof("Successfully detected external interface: %s\n", result.Device) return result case <-ticker.C: - // 每秒尝试一次DNS查询,轮换使用不同的DNS服务器 + // Try a DNS query every second, rotating through DNS servers currentDNS := dnsServers[dnsIndex] dnsIndex = (dnsIndex + 1) % len(dnsServers) go func(server string) { ip, err := LookUpIP(domain, server) if err != nil { - gologger.Debugf("DNS查询失败(%s): %s\n", server, err.Error()) + gologger.Debugf("DNS query failed (%s): %s\n", server, err.Error()) } else if ip != nil { - gologger.Debugf("DNS查询成功(%s): %s -> %s\n", server, domain, ip.String()) + gologger.Debugf("DNS query succeeded (%s): %s -> %s\n", server, domain, ip.String()) } }(currentDNS) @@ -240,14 +240,14 @@ func waitForDeviceTest(signal <-chan *EtherTable, domain string, dnsServers []st count++ if count >= timeout { - gologger.Fatalf("获取网络设备超时,请尝试手动指定网卡\n") + gologger.Fatalf("Timed out detecting network device, please specify the interface manually\n") return nil } } } } -// LookUpIP 使用指定DNS服务器查询域名并返回IP地址 +// LookUpIP queries a domain name using the specified DNS server and returns the IP address func LookUpIP(fqdn, serverAddr string) (net.IP, error) { var m dns.Msg client := dns.Client{} @@ -259,17 +259,17 @@ func LookUpIP(fqdn, serverAddr string) (net.IP, error) { return nil, err } - // 检查是否有响应 + // Check for a response if r == nil || len(r.Answer) == 0 { - return nil, fmt.Errorf("无DNS回复") + return nil, fmt.Errorf("no DNS reply") } - // 尝试获取A记录 + // Try to get an A record for _, ans := range r.Answer { if a, ok := ans.(*dns.A); ok { return a.A, nil } } - return nil, fmt.Errorf("无A记录") + return nil, fmt.Errorf("no A record") } diff --git a/pkg/device/network_improved.go b/pkg/device/network_improved.go index cf26dca..2d8730d 100644 --- a/pkg/device/network_improved.go +++ b/pkg/device/network_improved.go @@ -15,32 +15,32 @@ import ( "github.com/google/gopacket/pcap" ) -// GetDefaultRouteInterface 获取默认路由的网卡设备 -// 这是最可靠的方法,因为默认路由的网卡通常就是外网通信的网卡 +// GetDefaultRouteInterface retrieves the network interface for the default route. +// This is the most reliable method because the default route interface is usually the one used for external communication. func GetDefaultRouteInterface() (*EtherTable, error) { var defaultInterface string var gatewayIP net.IP switch runtime.GOOS { case "windows": - // Windows: 使用 route print 获取默认路由 + // Windows: use 'route print' to get the default route cmd := exec.Command("route", "print", "0.0.0.0") output, err := cmd.Output() if err != nil { - return nil, fmt.Errorf("执行route命令失败: %v", err) + return nil, fmt.Errorf("failed to execute route command: %v", err) } - // 解析输出获取默认网关和接口 + // Parse output to get the default gateway and interface lines := strings.Split(string(output), "\n") for _, line := range lines { if strings.Contains(line, "0.0.0.0") && strings.Contains(line, "0.0.0.0") { fields := strings.Fields(line) if len(fields) >= 5 { gatewayIP = net.ParseIP(fields[2]) - // 获取接口IP + // Get interface IP localIP := net.ParseIP(fields[3]) if localIP != nil { - // 查找对应的网卡 + // Find the corresponding interface interfaces, _ := pcap.FindAllDevs() for _, iface := range interfaces { for _, addr := range iface.Addresses { @@ -60,15 +60,15 @@ func GetDefaultRouteInterface() (*EtherTable, error) { } case "linux": - // Linux: 使用 ip route 获取默认路由 + // Linux: use 'ip route' to get the default route cmd := exec.Command("ip", "route", "show", "default") output, err := cmd.Output() if err != nil { - // 尝试使用 route 命令 + // Try with the 'route' command cmd = exec.Command("route", "-n") output, err = cmd.Output() if err != nil { - return nil, fmt.Errorf("获取路由信息失败: %v", err) + return nil, fmt.Errorf("failed to get routing information: %v", err) } } @@ -77,12 +77,12 @@ func GetDefaultRouteInterface() (*EtherTable, error) { if strings.Contains(line, "default") || strings.HasPrefix(line, "0.0.0.0") { fields := strings.Fields(line) if len(fields) >= 5 { - // ip route 格式: default via 192.168.1.1 dev eth0 + // ip route format: default via 192.168.1.1 dev eth0 if fields[0] == "default" && len(fields) >= 5 { gatewayIP = net.ParseIP(fields[2]) defaultInterface = fields[4] } else if fields[0] == "0.0.0.0" { - // route -n 格式 + // route -n format gatewayIP = net.ParseIP(fields[1]) defaultInterface = fields[len(fields)-1] } @@ -92,11 +92,11 @@ func GetDefaultRouteInterface() (*EtherTable, error) { } case "darwin": - // macOS: 使用 route get 获取默认路由 + // macOS: use 'route get default' to get the default route cmd := exec.Command("route", "get", "default") output, err := cmd.Output() if err != nil { - return nil, fmt.Errorf("获取路由信息失败: %v", err) + return nil, fmt.Errorf("failed to get routing information: %v", err) } lines := strings.Split(string(output), "\n") @@ -117,12 +117,12 @@ func GetDefaultRouteInterface() (*EtherTable, error) { } if defaultInterface == "" || gatewayIP == nil { - return nil, fmt.Errorf("无法获取默认路由信息") + return nil, fmt.Errorf("unable to obtain default route information") } - gologger.Infof("找到默认路由网卡: %s, 网关: %s\n", defaultInterface, gatewayIP.String()) + gologger.Infof("Found default route interface: %s, gateway: %s\n", defaultInterface, gatewayIP.String()) - // 获取网卡的IP和MAC地址 + // Get IP and MAC address of the interface etherTable, err := getInterfaceDetails(defaultInterface, gatewayIP) if err != nil { return nil, err @@ -131,21 +131,21 @@ func GetDefaultRouteInterface() (*EtherTable, error) { return etherTable, nil } -// getInterfaceDetails 获取网卡详细信息,包括通过ARP获取网关MAC +// getInterfaceDetails retrieves detailed interface information, including the gateway MAC via ARP func getInterfaceDetails(deviceName string, gatewayIP net.IP) (*EtherTable, error) { - // 获取网卡信息 + // Get interface information interfaces, err := pcap.FindAllDevs() if err != nil { - return nil, fmt.Errorf("获取网卡列表失败: %v", err) + return nil, fmt.Errorf("failed to get interface list: %v", err) } var srcIP net.IP var srcMAC net.HardwareAddr - // 查找指定网卡的IP和MAC + // Find IP and MAC of the specified interface for _, iface := range interfaces { if iface.Name == deviceName { - // 获取IP地址 + // Get IP address for _, addr := range iface.Addresses { if addr.IP.To4() != nil && !addr.IP.IsLoopback() { srcIP = addr.IP @@ -157,28 +157,28 @@ func getInterfaceDetails(deviceName string, gatewayIP net.IP) (*EtherTable, erro } if srcIP == nil { - return nil, fmt.Errorf("无法获取网卡 %s 的IP地址", deviceName) + return nil, fmt.Errorf("unable to get IP address for interface %s", deviceName) } - // 获取网卡MAC地址 + // Get interface MAC address iface, err := net.InterfaceByName(deviceName) if err == nil && iface.HardwareAddr != nil { srcMAC = iface.HardwareAddr } else { - // 如果标准方法失败,尝试从系统获取 + // If standard method fails, try getting from system srcMAC, _ = getMACAddress(deviceName) } if srcMAC == nil { - // 使用默认MAC + // Use default MAC srcMAC = net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - gologger.Warningf("无法获取网卡MAC地址,使用默认值\n") + gologger.Warningf("Unable to get interface MAC address, using default value\n") } - // 通过ARP获取网关MAC地址 + // Get gateway MAC address via ARP gatewayMAC, err := resolveGatewayMAC(deviceName, srcIP, srcMAC, gatewayIP) if err != nil { - gologger.Warningf("ARP解析网关MAC失败: %v,将使用广播地址\n", err) + gologger.Warningf("ARP resolution of gateway MAC failed: %v, will use broadcast address\n", err) gatewayMAC = net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff} } @@ -189,31 +189,31 @@ func getInterfaceDetails(deviceName string, gatewayIP net.IP) (*EtherTable, erro DstMac: SelfMac(gatewayMAC), } - gologger.Infof("网卡配置: IP=%s, MAC=%s, Gateway MAC=%s\n", + gologger.Infof("Interface config: IP=%s, MAC=%s, Gateway MAC=%s\n", srcIP.String(), srcMAC.String(), gatewayMAC.String()) return etherTable, nil } -// resolveGatewayMAC 通过ARP请求获取网关的MAC地址 +// resolveGatewayMAC resolves the gateway's MAC address via ARP request func resolveGatewayMAC(deviceName string, srcIP net.IP, srcMAC net.HardwareAddr, gatewayIP net.IP) (net.HardwareAddr, error) { - // 打开网卡进行ARP操作 + // Open the interface for ARP operations handle, err := pcap.OpenLive(deviceName, 2048, true, time.Second) if err != nil { - return nil, fmt.Errorf("打开网卡失败: %v", err) + return nil, fmt.Errorf("failed to open interface: %v", err) } defer handle.Close() - // 设置过滤器只接收ARP回复 + // Set filter to receive only ARP replies err = handle.SetBPFFilter(fmt.Sprintf("arp and arp[6:2] = 2 and src host %s", gatewayIP.String())) if err != nil { - gologger.Debugf("设置BPF过滤器失败: %v\n", err) + gologger.Debugf("Failed to set BPF filter: %v\n", err) } - // 构建ARP请求包 + // Build ARP request packet eth := &layers.Ethernet{ SrcMAC: srcMAC, - DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // 广播 + DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // broadcast EthernetType: layers.EthernetTypeARP, } @@ -229,7 +229,7 @@ func resolveGatewayMAC(deviceName string, srcIP net.IP, srcMAC net.HardwareAddr, DstProtAddress: gatewayIP.To4(), } - // 序列化数据包 + // Serialize the packet buffer := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{ FixLengths: true, @@ -238,17 +238,17 @@ func resolveGatewayMAC(deviceName string, srcIP net.IP, srcMAC net.HardwareAddr, err = gopacket.SerializeLayers(buffer, opts, eth, arp) if err != nil { - return nil, fmt.Errorf("构建ARP包失败: %v", err) + return nil, fmt.Errorf("failed to build ARP packet: %v", err) } - // 发送ARP请求 + // Send ARP request outgoingPacket := buffer.Bytes() err = handle.WritePacketData(outgoingPacket) if err != nil { - return nil, fmt.Errorf("发送ARP请求失败: %v", err) + return nil, fmt.Errorf("failed to send ARP request: %v", err) } - // 等待ARP回复 + // Wait for ARP reply ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -256,17 +256,17 @@ func resolveGatewayMAC(deviceName string, srcIP net.IP, srcMAC net.HardwareAddr, for { select { case <-ctx.Done(): - return nil, fmt.Errorf("ARP响应超时") + return nil, fmt.Errorf("ARP response timed out") case packet := <-packetSource.Packets(): if packet == nil { continue } - // 解析ARP层 + // Parse ARP layer if arpLayer := packet.Layer(layers.LayerTypeARP); arpLayer != nil { arpReply, ok := arpLayer.(*layers.ARP) if ok && arpReply.Operation == layers.ARPReply { - // 检查是否是我们请求的网关IP的回复 + // Check if this is a reply for the gateway IP we requested if net.IP(arpReply.SourceProtAddress).Equal(gatewayIP) { return net.HardwareAddr(arpReply.SourceHwAddress), nil } @@ -276,23 +276,23 @@ func resolveGatewayMAC(deviceName string, srcIP net.IP, srcMAC net.HardwareAddr, } } -// getMACAddress 获取网卡MAC地址的辅助函数 +// getMACAddress is a helper function to get the MAC address of a network interface func getMACAddress(deviceName string) (net.HardwareAddr, error) { - // 尝试通过系统命令获取MAC地址 + // Try to get MAC address via system command switch runtime.GOOS { case "windows": - // Windows: 使用 getmac 命令 + // Windows: use 'getmac' command cmd := exec.Command("getmac", "/v") output, err := cmd.Output() if err != nil { return nil, err } - // 解析输出找到对应网卡的MAC - // 这里需要更复杂的解析逻辑 + // Parse output to find the MAC for the interface + // More complex parsing logic would be needed here _ = output case "linux", "darwin": - // Linux/macOS: 使用 ifconfig + // Linux/macOS: use ifconfig cmd := exec.Command("ifconfig", deviceName) output, err := cmd.Output() if err != nil { @@ -317,40 +317,40 @@ func getMACAddress(deviceName string) (net.HardwareAddr, error) { } } - return nil, fmt.Errorf("无法获取MAC地址") + return nil, fmt.Errorf("unable to get MAC address") } -// AutoGetDevicesImproved 改进的自动获取网卡方法 -// 优先使用路由表和ARP,失败时再回退到DNS探测 +// AutoGetDevicesImproved is an improved method for automatically detecting the network interface. +// It prefers using the routing table and ARP, falling back to DNS probing on failure. func AutoGetDevicesImproved(userDNS []string) (*EtherTable, error) { - gologger.Infof("尝试通过默认路由获取网卡信息...\n") + gologger.Infof("Trying to get interface info via default route...\n") - // 方法1: 通过默认路由获取 + // Method 1: Get via default route etherTable, err := GetDefaultRouteInterface() if err == nil { - // 验证网卡是否可用 + // Validate the interface is usable if validateInterface(etherTable) { - gologger.Infof("成功通过默认路由获取网卡信息\n") + gologger.Infof("Successfully obtained interface info via default route\n") return etherTable, nil } } - gologger.Warningf("默认路由方法失败: %v,尝试DNS探测方法\n", err) + gologger.Warningf("Default route method failed: %v, falling back to DNS probing\n", err) - // 方法2: 回退到原始的DNS探测方法 + // Method 2: Fall back to original DNS probing method return AutoGetDevices(userDNS) } -// validateInterface 验证网卡是否可用 +// validateInterface checks whether a network interface is usable func validateInterface(etherTable *EtherTable) bool { - // 尝试打开网卡 + // Try to open the interface handle, err := pcap.OpenLive(etherTable.Device, 1024, false, time.Second) if err != nil { return false } defer handle.Close() - // 检查是否能设置BPF过滤器 + // Check if BPF filter can be set err = handle.SetBPFFilter("udp") return err == nil } diff --git a/pkg/device/network_improved_test.go b/pkg/device/network_improved_test.go index 770e204..dce9837 100644 --- a/pkg/device/network_improved_test.go +++ b/pkg/device/network_improved_test.go @@ -10,71 +10,71 @@ import ( "github.com/stretchr/testify/assert" ) -// TestGetDefaultRouteInterface 测试获取默认路由网卡 +// TestGetDefaultRouteInterface tests getting the default route interface func TestGetDefaultRouteInterface(t *testing.T) { - // 跳过需要root权限的测试 + // Skip tests that require root privileges if !hasAdminPrivileges() { - t.Skip("需要管理员权限运行此测试") + t.Skip("Requires administrator privileges to run this test") } etherTable, err := GetDefaultRouteInterface() - // 在CI环境或无网络环境可能失败 + // May fail in CI or no-network environments if err != nil { - t.Logf("获取默认路由失败(可能是环境问题): %v", err) + t.Logf("Failed to get default route (may be an environment issue): %v", err) return } - // 验证返回的数据 + // Validate the returned data assert.NotNil(t, etherTable) - assert.NotEmpty(t, etherTable.Device, "设备名不应为空") - assert.NotNil(t, etherTable.SrcIp, "源IP不应为空") - assert.False(t, etherTable.SrcIp.IsLoopback(), "不应是回环地址") - assert.NotEqual(t, "00:00:00:00:00:00", etherTable.SrcMac.String(), "MAC地址不应全零") + assert.NotEmpty(t, etherTable.Device, "Device name should not be empty") + assert.NotNil(t, etherTable.SrcIp, "Source IP should not be nil") + assert.False(t, etherTable.SrcIp.IsLoopback(), "Should not be a loopback address") + assert.NotEqual(t, "00:00:00:00:00:00", etherTable.SrcMac.String(), "MAC address should not be all zeros") - t.Logf("成功获取网卡: Device=%s, IP=%s, MAC=%s, Gateway MAC=%s", + t.Logf("Successfully obtained interface: Device=%s, IP=%s, MAC=%s, Gateway MAC=%s", etherTable.Device, etherTable.SrcIp, etherTable.SrcMac, etherTable.DstMac) } -// TestResolveGatewayMAC 测试ARP解析网关MAC +// TestResolveGatewayMAC tests ARP gateway MAC resolution func TestResolveGatewayMAC(t *testing.T) { if !hasAdminPrivileges() { - t.Skip("需要管理员权限运行此测试") + t.Skip("Requires administrator privileges to run this test") } - // 获取本地网络信息 + // Get local network information etherTable, err := GetDefaultRouteInterface() if err != nil { - t.Skip("无法获取网络信息,跳过ARP测试") + t.Skip("Cannot get network info, skipping ARP test") } - // 尝试解析本地网关 - // 注意:这个测试在实际环境中运行 + // Try to resolve the local gateway + // Note: this test runs in a real network environment gatewayIP := getDefaultGateway() if gatewayIP == nil { - t.Skip("无法获取默认网关,跳过ARP测试") + t.Skip("Cannot get default gateway, skipping ARP test") } srcMAC := net.HardwareAddr(etherTable.SrcMac) mac, err := resolveGatewayMAC(etherTable.Device, etherTable.SrcIp, srcMAC, gatewayIP) if err != nil { - t.Logf("ARP解析失败(可能是网络环境): %v", err) + t.Logf("ARP resolution failed (may be a network environment issue): %v", err) return } assert.NotNil(t, mac) - assert.Len(t, mac, 6, "MAC地址应该是6字节") - assert.NotEqual(t, "00:00:00:00:00:00", mac.String(), "MAC地址不应全零") - assert.NotEqual(t, "ff:ff:ff:ff:ff:ff", mac.String(), "MAC地址不应是广播地址") + assert.Len(t, mac, 6, "MAC address should be 6 bytes") + assert.NotEqual(t, "00:00:00:00:00:00", mac.String(), "MAC address should not be all zeros") + assert.NotEqual(t, "ff:ff:ff:ff:ff:ff", mac.String(), "MAC address should not be broadcast address") - t.Logf("成功解析网关MAC: %s -> %s", gatewayIP, mac) + t.Logf("Successfully resolved gateway MAC: %s -> %s", gatewayIP, mac) } -// TestValidateInterface 测试网卡验证 +// TestValidateInterface tests interface validation func TestValidateInterface(t *testing.T) { if !hasAdminPrivileges() { - t.Skip("需要管理员权限运行此测试") + t.Skip("Requires administrator privileges to run this test") } tests := []struct { @@ -83,7 +83,7 @@ func TestValidateInterface(t *testing.T) { expected bool }{ { - name: "无效的网卡名", + name: "Invalid interface name", etherTable: &EtherTable{ Device: "invalid_device_xyz", SrcIp: net.ParseIP("192.168.1.100"), @@ -91,7 +91,7 @@ func TestValidateInterface(t *testing.T) { expected: false, }, { - name: "空网卡名", + name: "Empty interface name", etherTable: &EtherTable{ Device: "", SrcIp: net.ParseIP("192.168.1.100"), @@ -100,7 +100,7 @@ func TestValidateInterface(t *testing.T) { }, } - // 添加一个有效网卡的测试 + // Add a test for a valid interface devices, err := pcap.FindAllDevs() if err == nil && len(devices) > 0 { validDevice := devices[0].Name @@ -109,7 +109,7 @@ func TestValidateInterface(t *testing.T) { etherTable *EtherTable expected bool }{ - name: "有效的网卡", + name: "Valid interface", etherTable: &EtherTable{ Device: validDevice, SrcIp: net.ParseIP("192.168.1.100"), @@ -126,13 +126,13 @@ func TestValidateInterface(t *testing.T) { } } -// TestAutoGetDevicesImproved 测试改进的自动获取方法 +// TestAutoGetDevicesImproved tests the improved auto-detection method func TestAutoGetDevicesImproved(t *testing.T) { if !hasAdminPrivileges() { - t.Skip("需要管理员权限运行此测试") + t.Skip("Requires administrator privileges to run this test") } - // 使用常见的公共DNS服务器 + // Use common public DNS servers testDNS := []string{ "8.8.8.8", "1.1.1.1", @@ -141,24 +141,24 @@ func TestAutoGetDevicesImproved(t *testing.T) { etherTable, err := AutoGetDevicesImproved(testDNS) - // 在某些环境可能失败 + // May fail in some environments if err != nil { - t.Logf("自动获取网卡失败(环境问题): %v", err) + t.Logf("Auto interface detection failed (environment issue): %v", err) return } assert.NotNil(t, etherTable) assert.NotEmpty(t, etherTable.Device) assert.NotNil(t, etherTable.SrcIp) - assert.False(t, etherTable.SrcIp.IsUnspecified(), "IP不应是未指定地址") + assert.False(t, etherTable.SrcIp.IsUnspecified(), "IP should not be unspecified address") - t.Logf("成功自动获取网卡: %+v", etherTable) + t.Logf("Successfully auto-detected interface: %+v", etherTable) } -// BenchmarkGetDefaultRouteInterface 性能测试 +// BenchmarkGetDefaultRouteInterface performance test func BenchmarkGetDefaultRouteInterface(b *testing.B) { if !hasAdminPrivileges() { - b.Skip("需要管理员权限运行此测试") + b.Skip("Requires administrator privileges to run this test") } b.ResetTimer() @@ -167,21 +167,21 @@ func BenchmarkGetDefaultRouteInterface(b *testing.B) { } } -// BenchmarkResolveGatewayMAC 性能测试ARP解析 +// BenchmarkResolveGatewayMAC performance test for ARP resolution func BenchmarkResolveGatewayMAC(b *testing.B) { if !hasAdminPrivileges() { - b.Skip("需要管理员权限运行此测试") + b.Skip("Requires administrator privileges to run this test") } - // 准备测试数据 + // Prepare test data etherTable, err := GetDefaultRouteInterface() if err != nil { - b.Skip("无法获取网络信息") + b.Skip("Cannot get network info") } gatewayIP := getDefaultGateway() if gatewayIP == nil { - b.Skip("无法获取网关") + b.Skip("Cannot get gateway") } srcMAC := net.HardwareAddr(etherTable.SrcMac) @@ -192,28 +192,28 @@ func BenchmarkResolveGatewayMAC(b *testing.B) { } } -// 辅助函数:检查是否有管理员权限 +// hasAdminPrivileges checks whether the process has administrator privileges func hasAdminPrivileges() bool { switch runtime.GOOS { case "windows": - // Windows下检查是否能打开网卡 + // On Windows, check if interfaces can be opened devices, err := pcap.FindAllDevs() return err == nil && len(devices) > 0 default: - // Unix系统检查UID + // On Unix systems, check UID return runtime.GOOS == "darwin" || isRoot() } } -// 辅助函数:检查是否是root用户 +// isRoot checks whether the process is running as root func isRoot() bool { - // 尝试打开一个网卡来检查权限 + // Try to open a network interface to check permissions devices, err := pcap.FindAllDevs() if err != nil || len(devices) == 0 { return false } - // 尝试打开第一个设备 + // Try to open the first device handle, err := pcap.OpenLive(devices[0].Name, 1024, false, time.Second) if err != nil { return false @@ -222,9 +222,9 @@ func isRoot() bool { return true } -// 辅助函数:获取默认网关 +// getDefaultGateway is a helper function to get the default gateway func getDefaultGateway() net.IP { - // 简单实现,实际使用时应该解析路由表 + // Simple implementation; production code should parse the routing table conn, err := net.Dial("udp", "8.8.8.8:80") if err != nil { return nil @@ -232,7 +232,7 @@ func getDefaultGateway() net.IP { defer conn.Close() localAddr := conn.LocalAddr().(*net.UDPAddr) - // 假设网关是 .1 + // Assume the gateway is .1 ip := localAddr.IP.To4() if ip != nil { ip[3] = 1 diff --git a/pkg/runner/mempool.go b/pkg/runner/mempool.go index fe85ec7..d82b2ec 100755 --- a/pkg/runner/mempool.go +++ b/pkg/runner/mempool.go @@ -7,22 +7,22 @@ import ( "github.com/google/gopacket/layers" ) -// MemoryPool 实现内存对象池 -// 优化点5: 对象池复用策略优化 -// 用途: 复用频繁分配的对象(DNS层、序列化缓冲区、切片) -// 收益: 减少内存分配次数和GC压力,降低延迟 -// 关键: 归还前必须重置对象状态,避免数据污染 +// MemoryPool implements a memory object pool. +// Optimization point 5: object pool reuse strategy optimization. +// Purpose: reuse frequently allocated objects (DNS layers, serialize buffers, slices). +// Benefit: reduces memory allocation frequency and GC pressure, lowers latency. +// Key: objects must be reset before being returned to the pool to avoid data contamination. type MemoryPool struct { - dnsPool sync.Pool // DNS查询/响应层对象池 - bufPool sync.Pool // gopacket序列化缓冲区池 - questionPool sync.Pool // DNS问题切片池 - answerPool sync.Pool // DNS应答切片池 + dnsPool sync.Pool // DNS query/response layer object pool + bufPool sync.Pool // gopacket serialize buffer pool + questionPool sync.Pool // DNS question slice pool + answerPool sync.Pool // DNS answer slice pool } -// 全局内存池实例 +// GlobalMemPool is the global memory pool instance var GlobalMemPool = NewMemoryPool() -// NewMemoryPool 创建一个新的内存池 +// NewMemoryPool creates a new memory pool func NewMemoryPool() *MemoryPool { return &MemoryPool{ dnsPool: sync.Pool{ @@ -51,26 +51,26 @@ func NewMemoryPool() *MemoryPool { } } -// GetDNS 获取一个DNS对象 -// 注意: 从池中获取的对象可能包含旧数据,必须重置所有字段 +// GetDNS retrieves a DNS object from the pool. +// Note: objects obtained from the pool may contain stale data; all fields must be reset. func (p *MemoryPool) GetDNS() *layers.DNS { dns := p.dnsPool.Get().(*layers.DNS) - // 重置切片长度(保留底层数组容量) + // Reset slice length (retain underlying array capacity) dns.Questions = dns.Questions[:0] dns.Answers = dns.Answers[:0] - // nil 掉不常用字段,避免内存泄漏 + // Nil out infrequently used fields to avoid memory leaks dns.Authorities = nil dns.Additionals = nil - // 重置所有标志位为默认值 + // Reset all flags to default values dns.ID = 0 - dns.QR = false // 查询报文 - dns.OpCode = 0 // 标准查询 - dns.AA = false // 非权威应答 - dns.TC = false // 未截断 - dns.RD = true // 期望递归 - dns.RA = false // 递归不可用 - dns.Z = 0 // 保留位 - dns.ResponseCode = 0 // 无错误 + dns.QR = false // Query message + dns.OpCode = 0 // Standard query + dns.AA = false // Not authoritative + dns.TC = false // Not truncated + dns.RD = true // Recursion desired + dns.RA = false // Recursion not available + dns.Z = 0 // Reserved bits + dns.ResponseCode = 0 // No error dns.QDCount = 0 dns.ANCount = 0 dns.NSCount = 0 @@ -78,47 +78,47 @@ func (p *MemoryPool) GetDNS() *layers.DNS { return dns } -// PutDNS 回收一个DNS对象 +// PutDNS returns a DNS object to the pool func (p *MemoryPool) PutDNS(dns *layers.DNS) { if dns != nil { p.dnsPool.Put(dns) } } -// GetBuffer 获取一个序列化缓冲区 +// GetBuffer retrieves a serialize buffer from the pool func (p *MemoryPool) GetBuffer() gopacket.SerializeBuffer { buf := p.bufPool.Get().(gopacket.SerializeBuffer) buf.Clear() return buf } -// PutBuffer 回收一个序列化缓冲区 +// PutBuffer returns a serialize buffer to the pool func (p *MemoryPool) PutBuffer(buf gopacket.SerializeBuffer) { if buf != nil { p.bufPool.Put(buf) } } -// GetDNSQuestions 获取DNS问题切片 +// GetDNSQuestions retrieves a DNS question slice from the pool func (p *MemoryPool) GetDNSQuestions() []layers.DNSQuestion { questions := p.questionPool.Get().([]layers.DNSQuestion) return questions[:0] } -// PutDNSQuestions 回收DNS问题切片 +// PutDNSQuestions returns a DNS question slice to the pool func (p *MemoryPool) PutDNSQuestions(questions []layers.DNSQuestion) { if questions != nil { p.questionPool.Put(questions) } } -// GetDNSAnswers 获取DNS应答切片 +// GetDNSAnswers retrieves a DNS answer slice from the pool func (p *MemoryPool) GetDNSAnswers() []layers.DNSResourceRecord { answers := p.answerPool.Get().([]layers.DNSResourceRecord) return answers[:0] } -// PutDNSAnswers 回收DNS应答切片 +// PutDNSAnswers returns a DNS answer slice to the pool func (p *MemoryPool) PutDNSAnswers(answers []layers.DNSResourceRecord) { if answers != nil { p.answerPool.Put(answers) diff --git a/pkg/runner/outputter/output/csv.go b/pkg/runner/outputter/output/csv.go index 6b48de9..d0d1f37 100755 --- a/pkg/runner/outputter/output/csv.go +++ b/pkg/runner/outputter/output/csv.go @@ -29,44 +29,44 @@ func (f *CsvOutput) WriteDomainResult(domain result.Result) error { } func (f *CsvOutput) Close() error { - gologger.Infof("写入csv文件:%s\n", f.filename) + gologger.Infof("Writing CSV file: %s\n", f.filename) - // 检查结果数量 + // Check result count if len(f.domains) == 0 { - gologger.Infof("没有发现子域名结果,CSV文件将为空\n") + gologger.Infof("No subdomain results found, CSV file will be empty\n") return nil } results := utils.WildFilterOutputResult(f.wildFilterMode, f.domains) - gologger.Infof("过滤后结果数量: %d\n", len(results)) + gologger.Infof("Filtered result count: %d\n", len(results)) - // 检查过滤后结果 + // Check filtered results if len(results) == 0 { - gologger.Infof("经过通配符过滤后没有有效结果,CSV文件将为空\n") + gologger.Infof("No valid results after wildcard filtering, CSV file will be empty\n") return nil } - // 创建CSV文件 + // Create CSV file file, err := os.Create(f.filename) if err != nil { - gologger.Errorf("创建CSV文件失败: %v", err) + gologger.Errorf("Failed to create CSV file: %v", err) return err } defer file.Close() - // 创建CSV写入器 + // Create CSV writer writer := csv.NewWriter(file) - // 写入CSV头部 + // Write CSV header err = writer.Write([]string{"Subdomain", "Answers"}) if err != nil { - gologger.Errorf("写入CSV头部失败: %v", err) + gologger.Errorf("Failed to write CSV header: %v", err) return err } - // 写入数据行 + // Write data rows for _, result := range results { - // 将Answers数组转换为单个字符串,用分号分隔 + // Convert Answers array to a single string separated by semicolons answersStr := "" if len(result.Answers) > 0 { answersStr = result.Answers[0] @@ -77,11 +77,11 @@ func (f *CsvOutput) Close() error { err = writer.Write([]string{result.Subdomain, answersStr}) if err != nil { - gologger.Errorf("写入CSV数据行失败: %v", err) + gologger.Errorf("Failed to write CSV data row: %v", err) continue } } writer.Flush() - gologger.Infof("CSV文件写入成功,共写入 %d 条记录", len(results)) + gologger.Infof("CSV file written successfully, %d records written", len(results)) return nil } diff --git a/pkg/runner/outputter/output/json.go b/pkg/runner/outputter/output/json.go index a0c131a..9334346 100755 --- a/pkg/runner/outputter/output/json.go +++ b/pkg/runner/outputter/output/json.go @@ -30,7 +30,7 @@ func (f *JsonOutPut) WriteDomainResult(domain result.Result) error { } func (f *JsonOutPut) Close() error { - gologger.Infof("写入json文件:%s count:%d", f.filename, len(f.domains)) + gologger.Infof("Writing JSON file: %s count:%d", f.filename, len(f.domains)) if len(f.domains) > 0 { results := utils.WildFilterOutputResult(f.wildFilterMode, f.domains) jsonBytes, err := json.Marshal(results) diff --git a/pkg/runner/outputter/output/jsonl.go b/pkg/runner/outputter/output/jsonl.go index b4726ac..3df2fdf 100644 --- a/pkg/runner/outputter/output/jsonl.go +++ b/pkg/runner/outputter/output/jsonl.go @@ -10,26 +10,26 @@ import ( "github.com/boy-hack/ksubdomain/v2/pkg/runner/result" ) -// JSONLOutput JSONL (JSON Lines) 输出器 -// 每行一个 JSON 对象,便于流式处理和工具链集成 -// 格式: {"domain":"example.com","type":"A","records":["1.2.3.4"],"timestamp":1234567890} +// JSONLOutput is the JSONL (JSON Lines) output handler. +// Each line contains one JSON object, suitable for streaming processing and toolchain integration. +// Format: {"domain":"example.com","type":"A","records":["1.2.3.4"],"timestamp":1234567890} type JSONLOutput struct { filename string file *os.File mu sync.Mutex } -// JSONLRecord JSONL 记录格式 +// JSONLRecord is the JSONL record format type JSONLRecord struct { - Domain string `json:"domain"` // 子域名 - Type string `json:"type"` // 记录类型 (A, CNAME, NS, etc.) - Records []string `json:"records"` // 记录值列表 - Timestamp int64 `json:"timestamp"` // Unix 时间戳 - TTL uint32 `json:"ttl,omitempty"` // TTL (可选) - Source string `json:"source,omitempty"` // 数据来源 (可选) + Domain string `json:"domain"` // Subdomain + Type string `json:"type"` // Record type (A, CNAME, NS, etc.) + Records []string `json:"records"` // Record values + Timestamp int64 `json:"timestamp"` // Unix timestamp + TTL uint32 `json:"ttl,omitempty"` // TTL (optional) + Source string `json:"source,omitempty"` // Data source (optional) } -// NewJSONLOutput 创建 JSONL 输出器 +// NewJSONLOutput creates a JSONL output handler func NewJSONLOutput(filename string) (*JSONLOutput, error) { file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) if err != nil { @@ -44,24 +44,24 @@ func NewJSONLOutput(filename string) (*JSONLOutput, error) { }, nil } -// WriteDomainResult 写入单个域名结果 -// JSONL 格式每次写入一行 JSON,立即刷新 -// 优点: 支持流式处理,可以实时读取 +// WriteDomainResult writes a single domain result. +// JSONL format writes one JSON line per call and flushes immediately. +// Benefit: supports streaming processing; results can be read in real time. func (j *JSONLOutput) WriteDomainResult(r result.Result) error { j.mu.Lock() defer j.mu.Unlock() - // 解析记录类型 - recordType := "A" // 默认 A 记录 + // Parse record type + recordType := "A" // default A record records := make([]string, 0, len(r.Answers)) for _, answer := range r.Answers { - // 解析类型 (CNAME, NS, PTR 等) + // Parse type (CNAME, NS, PTR, etc.) if len(answer) > 0 { - // 检查是否为特殊记录类型 + // Check for special record types if len(answer) > 6 && answer[:6] == "CNAME " { recordType = "CNAME" - records = append(records, answer[6:]) // 去掉 "CNAME " 前缀 + records = append(records, answer[6:]) // Strip "CNAME " prefix } else if len(answer) > 3 && answer[:3] == "NS " { recordType = "NS" records = append(records, answer[3:]) @@ -72,18 +72,18 @@ func (j *JSONLOutput) WriteDomainResult(r result.Result) error { recordType = "TXT" records = append(records, answer[4:]) } else { - // IP 地址 (A 或 AAAA 记录) + // IP address (A or AAAA record) records = append(records, answer) } } } - // 如果没有解析出记录,使用原始 answers + // If no records were parsed, use raw answers if len(records) == 0 { records = r.Answers } - // 构造 JSONL 记录 + // Build JSONL record record := JSONLRecord{ Domain: r.Subdomain, Type: recordType, @@ -91,23 +91,23 @@ func (j *JSONLOutput) WriteDomainResult(r result.Result) error { Timestamp: time.Now().Unix(), } - // 序列化为 JSON + // Serialize to JSON data, err := json.Marshal(record) if err != nil { return err } - // 写入一行 (JSON + 换行符) + // Write one line (JSON + newline) _, err = j.file.Write(append(data, '\n')) if err != nil { return err } - // 立即刷新到磁盘 (支持实时读取) + // Flush to disk immediately (supports real-time reading) return j.file.Sync() } -// Close 关闭 JSONL 输出器 +// Close closes the JSONL output handler func (j *JSONLOutput) Close() error { j.mu.Lock() defer j.mu.Unlock() diff --git a/pkg/runner/outputter/output/screen.go b/pkg/runner/outputter/output/screen.go index 598a422..8ce940a 100755 --- a/pkg/runner/outputter/output/screen.go +++ b/pkg/runner/outputter/output/screen.go @@ -11,17 +11,17 @@ import ( type ScreenOutput struct { windowsWidth int silent bool - onlyDomain bool // 修复 Issue #67: 只输出域名 + onlyDomain bool // Fix Issue #67: output domain name only } -// NewScreenOutput 创建屏幕输出器 -// 修复 Issue #67: 支持 onlyDomain 参数 +// NewScreenOutput creates a screen output handler. +// Fix Issue #67: supports optional onlyDomain parameter. func NewScreenOutput(silent bool, onlyDomain ...bool) (*ScreenOutput, error) { windowsWidth := core.GetWindowWith() s := new(ScreenOutput) s.windowsWidth = windowsWidth s.silent = silent - // 支持可选的 onlyDomain 参数 (向后兼容) + // Support optional onlyDomain parameter (backward compatible) if len(onlyDomain) > 0 { s.onlyDomain = onlyDomain[0] } @@ -30,20 +30,20 @@ func NewScreenOutput(silent bool, onlyDomain ...bool) (*ScreenOutput, error) { func (s *ScreenOutput) WriteDomainResult(domain result.Result) error { var msg string - - // 修复 Issue #67: 支持只输出域名模式 + + // Fix Issue #67: support domain-only output mode if s.onlyDomain { - // 只输出域名,不显示 IP 和其他记录 + // Output domain name only, without IP or other records msg = domain.Subdomain } else { - // 完整输出: 域名 => 记录1 => 记录2 + // Full output: domain => record1 => record2 var domains []string = []string{domain.Subdomain} for _, item := range domain.Answers { domains = append(domains, item) } msg = strings.Join(domains, " => ") } - + if !s.silent { screenWidth := s.windowsWidth - len(msg) - 1 gologger.Silentf("\r%s% *s\n", msg, screenWidth, "") diff --git a/pkg/runner/recv.go b/pkg/runner/recv.go index ce967d3..66d4b25 100755 --- a/pkg/runner/recv.go +++ b/pkg/runner/recv.go @@ -16,56 +16,56 @@ import ( "github.com/google/gopacket/pcap" ) -// parseDNSName 解析 DNS 域名格式 -// DNS 域名格式: 长度前缀 + 标签 + ... + 结束符 -// 例如: \x03www\x06google\x03com\x00 表示 www.google.com -// 修复 Issue #70: 正确解析 CNAME/NS/PTR 等记录,避免出现 "comcom" 等拼接错误 +// parseDNSName parses the DNS domain name format. +// DNS name format: length prefix + label + ... + terminator +// Example: \x03www\x06google\x03com\x00 represents www.google.com +// Fix Issue #70: correctly parses CNAME/NS/PTR records to avoid concatenation errors like "comcom" func parseDNSName(raw []byte) string { if len(raw) == 0 { return "" } - + var result []byte i := 0 - + for i < len(raw) { - // 读取标签长度 + // Read label length length := int(raw[i]) - - // 0x00 表示域名结束 + + // 0x00 means end of name if length == 0 { break } - - // 0xC0 开头表示压缩指针 (RFC 1035) - // 压缩格式: 前2位为11,后14位为偏移量 + + // 0xC0 prefix means compression pointer (RFC 1035) + // Compressed format: top 2 bits are 11, lower 14 bits are offset if length >= 0xC0 { - // 压缩指针,暂不处理(通常在完整DNS包中才有) + // Compression pointer; not handled here (only present in full DNS packets) break } - - // 添加点分隔符 (第一个标签除外) + + // Add dot separator (except for the first label) if len(result) > 0 { result = append(result, '.') } - + i++ - - // 防止越界 + + // Guard against out-of-bounds if i+length > len(raw) { break } - - // 添加标签内容 + + // Append label content result = append(result, raw[i:i+length]...) i += length } - + return string(result) } -// dnsRecord2String 将DNS记录转换为字符串 -// 修复 Issue #70: 使用 parseDNSName 正确解析域名格式 +// dnsRecord2String converts a DNS resource record to a string. +// Fix Issue #70: use parseDNSName to correctly parse domain name format. func dnsRecord2String(rr layers.DNSResourceRecord) (string, error) { if rr.Class == layers.DNSClassIN { switch rr.Type { @@ -75,7 +75,7 @@ func dnsRecord2String(rr layers.DNSResourceRecord) (string, error) { } case layers.DNSTypeNS: if rr.NS != nil { - // 修复: 使用 parseDNSName 解析 NS 记录 + // Fix: use parseDNSName to parse NS record ns := parseDNSName(rr.NS) if ns != "" { return "NS " + ns, nil @@ -83,7 +83,7 @@ func dnsRecord2String(rr layers.DNSResourceRecord) (string, error) { } case layers.DNSTypeCNAME: if rr.CNAME != nil { - // 修复: 使用 parseDNSName 解析 CNAME 记录 + // Fix: use parseDNSName to parse CNAME record cname := parseDNSName(rr.CNAME) if cname != "" { return "CNAME " + cname, nil @@ -91,7 +91,7 @@ func dnsRecord2String(rr layers.DNSResourceRecord) (string, error) { } case layers.DNSTypePTR: if rr.PTR != nil { - // 修复: 使用 parseDNSName 解析 PTR 记录 + // Fix: use parseDNSName to parse PTR record ptr := parseDNSName(rr.PTR) if ptr != "" { return "PTR " + ptr, nil @@ -99,7 +99,7 @@ func dnsRecord2String(rr layers.DNSResourceRecord) (string, error) { } case layers.DNSTypeTXT: if rr.TXT != nil { - // TXT 记录是纯文本,不需要解析 + // TXT records are plain text, no parsing needed return "TXT " + string(rr.TXT), nil } } @@ -107,7 +107,7 @@ func dnsRecord2String(rr layers.DNSResourceRecord) (string, error) { return "", errors.New("dns record error") } -// 预分配解码器对象池,避免频繁创建 +// Pre-allocated decoder object pool to avoid frequent creation var decoderPool = sync.Pool{ New: func() interface{} { var eth layers.Ethernet @@ -130,7 +130,7 @@ var decoderPool = sync.Pool{ }, } -// decodingContext 解码上下文 +// decodingContext holds the decoding context type decodingContext struct { parser *gopacket.DecodingLayerParser eth *layers.Ethernet @@ -141,46 +141,46 @@ type decodingContext struct { decoded []gopacket.LayerType } -// 解析DNS响应包并处理 +// processPacket parses a DNS response packet and handles it func (r *Runner) processPacket(data []byte, dnsChanel chan<- layers.DNS) { - // 从对象池获取解码器 + // Get decoder from pool dc := decoderPool.Get().(*decodingContext) defer decoderPool.Put(dc) - // 清空解码层类型切片 + // Clear the decoded layer types slice dc.decoded = dc.decoded[:0] - // 解析数据包 + // Parse the packet err := dc.parser.DecodeLayers(data, &dc.decoded) if err != nil { return } - // 检查是否为DNS响应 + // Check if it is a DNS response if !dc.dns.QR { return } - // 确认DNS ID匹配 + // Verify DNS ID matches if dc.dns.ID != r.dnsID { return } - // 确认有查询问题 + // Ensure there is at least one question if len(dc.dns.Questions) == 0 { return } - // 记录接收包数量 + // Record number of received packets atomic.AddUint64(&r.receiveCount, 1) - // 向处理通道发送DNS响应 + // Send DNS response to the processing channel select { case dnsChanel <- *dc.dns: } } -// recvChanel 实现接收DNS响应的功能 +// recvChanel implements the functionality to receive DNS responses func (r *Runner) recvChanel(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() var ( @@ -190,49 +190,49 @@ func (r *Runner) recvChanel(ctx context.Context, wg *sync.WaitGroup) { ) inactive, err := pcap.NewInactiveHandle(r.options.EtherInfo.Device) if err != nil { - gologger.Errorf("创建网络捕获句柄失败: %v", err) + gologger.Errorf("Failed to create network capture handle: %v", err) return } err = inactive.SetSnapLen(snapshotLen) if err != nil { - gologger.Errorf("设置抓包长度失败: %v", err) + gologger.Errorf("Failed to set snapshot length: %v", err) return } defer inactive.CleanUp() if err = inactive.SetTimeout(timeout); err != nil { - gologger.Errorf("设置超时失败: %v", err) + gologger.Errorf("Failed to set timeout: %v", err) return } err = inactive.SetImmediateMode(true) if err != nil { - gologger.Errorf("设置即时模式失败: %v", err) + gologger.Errorf("Failed to set immediate mode: %v", err) return } handle, err := inactive.Activate() if err != nil { - gologger.Errorf("激活网络捕获失败: %v", err) + gologger.Errorf("Failed to activate network capture: %v", err) return } defer handle.Close() err = handle.SetBPFFilter(fmt.Sprintf("udp and src port 53 and dst port %d", r.listenPort)) if err != nil { - gologger.Errorf("设置BPF过滤器失败: %v", err) + gologger.Errorf("Failed to set BPF filter: %v", err) return } - // 创建DNS响应处理通道,缓冲大小适当增加 + // Create DNS response processing channel with adequate buffer size dnsChanel := make(chan layers.DNS, 10000) - // 使用多个协程处理DNS响应,提高并发效率 + // Use multiple goroutines to process DNS responses for higher concurrency processorCount := runtime.NumCPU() * 2 var processorWg sync.WaitGroup processorWg.Add(processorCount) - // 启动多个处理协程 + // Start multiple processing goroutines for i := 0; i < processorCount; i++ { go func() { defer processorWg.Done() @@ -267,10 +267,10 @@ func (r *Runner) recvChanel(ctx context.Context, wg *sync.WaitGroup) { }() } - // 使用多个接收协程读取网络数据包 + // Use a goroutine to read network packets packetChan := make(chan []byte, 10000) - // 启动数据包接收协程 + // Start packet receiving goroutine go func() { for { data, _, err := handle.ReadPacketData() @@ -285,12 +285,12 @@ func (r *Runner) recvChanel(ctx context.Context, wg *sync.WaitGroup) { case <-ctx.Done(): return case packetChan <- data: - // 数据包已发送到处理通道 + // Packet sent to processing channel } } }() - // 启动多个数据包解析协程 + // Start multiple packet parsing goroutines parserCount := runtime.NumCPU() * 2 var parserWg sync.WaitGroup parserWg.Add(parserCount) @@ -312,14 +312,14 @@ func (r *Runner) recvChanel(ctx context.Context, wg *sync.WaitGroup) { }() } - // 等待上下文结束 + // Wait for context to be done <-ctx.Done() - // 关闭通道 + // Close channels close(packetChan) close(dnsChanel) - // 等待所有处理和解析协程结束 + // Wait for all processing and parsing goroutines to finish parserWg.Wait() processorWg.Wait() } diff --git a/pkg/runner/recv_test.go b/pkg/runner/recv_test.go index f825245..1090899 100644 --- a/pkg/runner/recv_test.go +++ b/pkg/runner/recv_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/assert" ) -// TestParseDNSName 测试 DNS 域名格式解析 -// 修复 Issue #70 的核心函数 +// TestParseDNSName tests DNS domain name format parsing. +// Core function for fixing Issue #70. func TestParseDNSName(t *testing.T) { tests := []struct { name string @@ -16,17 +16,17 @@ func TestParseDNSName(t *testing.T) { expected string }{ { - name: "标准域名 - www.google.com", + name: "Standard domain - www.google.com", input: []byte{ 3, 'w', 'w', 'w', 6, 'g', 'o', 'o', 'g', 'l', 'e', 3, 'c', 'o', 'm', - 0, // 结束符 + 0, // terminator }, expected: "www.google.com", }, { - name: "二级域名 - baidu.com", + name: "Second-level domain - baidu.com", input: []byte{ 5, 'b', 'a', 'i', 'd', 'u', 3, 'c', 'o', 'm', @@ -35,7 +35,7 @@ func TestParseDNSName(t *testing.T) { expected: "baidu.com", }, { - name: "三级域名 - mail.qq.com", + name: "Third-level domain - mail.qq.com", input: []byte{ 4, 'm', 'a', 'i', 'l', 2, 'q', 'q', @@ -45,17 +45,17 @@ func TestParseDNSName(t *testing.T) { expected: "mail.qq.com", }, { - name: "空输入", + name: "Empty input", input: []byte{}, expected: "", }, { - name: "仅结束符", + name: "Terminator only", input: []byte{0}, expected: "", }, { - name: "无结束符域名", + name: "Domain without terminator", input: []byte{ 3, 'w', 'w', 'w', 6, 'g', 'o', 'o', 'g', 'l', 'e', @@ -64,7 +64,7 @@ func TestParseDNSName(t *testing.T) { expected: "www.google.com", }, { - name: "长域名", + name: "Long domain", input: []byte{ 10, 's', 'u', 'b', 'd', 'o', 'm', 'a', 'i', 'n', '1', 10, 's', 'u', 'b', 'd', 'o', 'm', 'a', 'i', 'n', '2', @@ -75,17 +75,17 @@ func TestParseDNSName(t *testing.T) { expected: "subdomain1.subdomain2.example.com", }, { - name: "压缩指针 (0xC0) - 应该停止", + name: "Compression pointer (0xC0) - should stop", input: []byte{ 3, 'w', 'w', 'w', - 0xC0, 0x12, // 压缩指针 + 0xC0, 0x12, // compression pointer }, expected: "www", }, { - name: "异常长度 - 超出范围", + name: "Abnormal length - out of range", input: []byte{ - 100, 'a', 'b', 'c', // 长度100但数据不足 + 100, 'a', 'b', 'c', // length 100 but insufficient data }, expected: "", }, @@ -94,12 +94,12 @@ func TestParseDNSName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parseDNSName(tt.input) - assert.Equal(t, tt.expected, result, "DNS 域名解析结果不匹配") + assert.Equal(t, tt.expected, result, "DNS domain name parsing result mismatch") }) } } -// TestDNSRecord2String_CNAME 测试 CNAME 记录转换 +// TestDNSRecord2String_CNAME tests CNAME record conversion func TestDNSRecord2String_CNAME(t *testing.T) { tests := []struct { name string @@ -108,7 +108,7 @@ func TestDNSRecord2String_CNAME(t *testing.T) { hasError bool }{ { - name: "标准 CNAME", + name: "Standard CNAME", rr: layers.DNSResourceRecord{ Type: layers.DNSTypeCNAME, Class: layers.DNSClassIN, @@ -138,7 +138,7 @@ func TestDNSRecord2String_CNAME(t *testing.T) { hasError: false, }, { - name: "空 CNAME", + name: "Empty CNAME", rr: layers.DNSResourceRecord{ Type: layers.DNSTypeCNAME, Class: layers.DNSClassIN, @@ -162,7 +162,7 @@ func TestDNSRecord2String_CNAME(t *testing.T) { } } -// TestDNSRecord2String_NS 测试 NS 记录转换 +// TestDNSRecord2String_NS tests NS record conversion func TestDNSRecord2String_NS(t *testing.T) { tests := []struct { name string @@ -170,7 +170,7 @@ func TestDNSRecord2String_NS(t *testing.T) { expected string }{ { - name: "标准 NS", + name: "Standard NS", rr: layers.DNSResourceRecord{ Type: layers.DNSTypeNS, Class: layers.DNSClassIN, @@ -208,7 +208,7 @@ func TestDNSRecord2String_NS(t *testing.T) { } } -// TestDNSRecord2String_A 测试 A 记录转换 +// TestDNSRecord2String_A tests A record conversion func TestDNSRecord2String_A(t *testing.T) { tests := []struct { name string @@ -216,7 +216,7 @@ func TestDNSRecord2String_A(t *testing.T) { expected string }{ { - name: "标准 A 记录", + name: "Standard A record", rr: layers.DNSResourceRecord{ Type: layers.DNSTypeA, Class: layers.DNSClassIN, @@ -225,7 +225,7 @@ func TestDNSRecord2String_A(t *testing.T) { expected: "192.168.1.1", }, { - name: "公网 IP", + name: "Public IP", rr: layers.DNSResourceRecord{ Type: layers.DNSTypeA, Class: layers.DNSClassIN, @@ -244,7 +244,7 @@ func TestDNSRecord2String_A(t *testing.T) { } } -// TestDNSRecord2String_PTR 测试 PTR 记录转换 +// TestDNSRecord2String_PTR tests PTR record conversion func TestDNSRecord2String_PTR(t *testing.T) { rr := layers.DNSResourceRecord{ Type: layers.DNSTypePTR, @@ -261,7 +261,7 @@ func TestDNSRecord2String_PTR(t *testing.T) { assert.Equal(t, "PTR example.com", result) } -// BenchmarkParseDNSName 基准测试 DNS 域名解析性能 +// BenchmarkParseDNSName benchmarks DNS domain name parsing performance func BenchmarkParseDNSName(b *testing.B) { input := []byte{ 3, 'w', 'w', 'w', @@ -276,7 +276,7 @@ func BenchmarkParseDNSName(b *testing.B) { } } -// BenchmarkDNSRecord2String 基准测试 DNS 记录转换性能 +// BenchmarkDNSRecord2String benchmarks DNS record conversion performance func BenchmarkDNSRecord2String(b *testing.B) { rr := layers.DNSResourceRecord{ Type: layers.DNSTypeCNAME, diff --git a/pkg/runner/result.go b/pkg/runner/result.go index f4d227d..fdd2f65 100755 --- a/pkg/runner/result.go +++ b/pkg/runner/result.go @@ -9,7 +9,7 @@ import ( "github.com/boy-hack/ksubdomain/v2/pkg/runner/result" ) -// handleResult 处理扫描结果 +// handleResult processes scan results func (r *Runner) handleResult(predictChan chan string) { isWildCard := r.options.WildcardFilterMode != "none" var wg sync.WaitGroup @@ -44,10 +44,10 @@ func (r *Runner) handleResult(predictChan chan string) { wg.Wait() } -// predict 根据已知域名预测新的子域名 +// predict processes a known domain to predict new subdomains func (r *Runner) predict(res result.Result, predictChan chan string) error { if r.domainChan == nil { - return fmt.Errorf("域名通道未初始化") + return fmt.Errorf("domain channel is not initialized") } _, err := predict.PredictDomains(res.Subdomain, predictChan) if err != nil { @@ -56,7 +56,7 @@ func (r *Runner) predict(res result.Result, predictChan chan string) error { return nil } -// handleResultWithContext 处理扫描结果(带有context管理) +// handleResultWithContext processes scan results with context management func (r *Runner) handleResultWithContext(ctx context.Context, wg *sync.WaitGroup, predictChan chan string) { defer wg.Done() isWildCard := r.options.WildcardFilterMode != "none" @@ -101,7 +101,7 @@ func (r *Runner) handleResultWithContext(ctx context.Context, wg *sync.WaitGroup } } -// checkWildIps 检查是否为通配符IP +// checkWildIps checks whether an IP is a wildcard IP func checkWildIps(wildIps []string, ip []string) bool { for _, w := range wildIps { for _, i := range ip { diff --git a/pkg/runner/retry.go b/pkg/runner/retry.go index 2af55dc..b38db33 100755 --- a/pkg/runner/retry.go +++ b/pkg/runner/retry.go @@ -9,31 +9,31 @@ import ( "github.com/boy-hack/ksubdomain/v2/pkg/runner/statusdb" ) -// retry 优化的重试机制 -// 优化点4: 改进重试扫描效率 -// 1. 添加空扫描检测,避免无谓的CPU消耗 -// 2. 使用独立工作协程处理重试,不阻塞主流程 -// 3. 根据DNS服务器分组批量重试 +// retry implements an optimized retry mechanism. +// Optimization point 4: improved retry scan efficiency. +// 1. Adds empty-scan detection to avoid unnecessary CPU consumption. +// 2. Uses dedicated worker goroutines to handle retries without blocking the main flow. +// 3. Groups retries by DNS server for batch processing. func (r *Runner) retry(ctx context.Context) { - // 检测间隔: 使用200ms而不是完整超时时间,更及时发现超时 - // 原实现每 timeoutSeconds 扫描一次,现在更频繁但有空扫描优化 + // Check interval: use 200ms instead of the full timeout period for more timely detection of timeouts. + // The original implementation scanned every timeoutSeconds; now it scans more frequently with empty-scan optimization. t := time.NewTicker(200 * time.Millisecond) defer t.Stop() - // 用于批量发送的域名缓冲区 + // Domain buffer for batch sending const batchSize = 100 retryDomains := make([]string, 0, batchSize) - // 记录上次扫描时间,当数据库为空时可以更节约资源 + // Track whether the last scan was empty to conserve resources when the database is empty lastScanEmpty := false - // 启动多个worker用于处理重试 + // Start multiple workers to handle retries workerCount := 4 retryDomainCh := make(chan string, batchSize*2) var wg sync.WaitGroup wg.Add(workerCount) - // 工作协程,用于发送重试请求 + // Worker goroutines for sending retry requests for i := 0; i < workerCount; i++ { go func() { defer wg.Done() @@ -45,14 +45,14 @@ func (r *Runner) retry(ctx context.Context) { if !ok { return } - // 重新发送 + // Resend r.domainChan <- domain } } }() } - // 为域名分组的批处理域名缓冲 + // Batch domain buffer grouped by DNS server dnsBatches := make(map[string][]string) for { @@ -62,37 +62,37 @@ func (r *Runner) retry(ctx context.Context) { wg.Wait() return case <-t.C: - // 如果上次扫描为空且长度仍为0,可跳过 + // If the last scan was empty and length is still 0, skip currentLength := r.statusDB.Length() if lastScanEmpty && currentLength == 0 { continue } - // 当前时间 + // Current time now := time.Now() - // 清空域名缓冲 + // Clear domain buffer retryDomains = retryDomains[:0] - // 清空分组缓冲 + // Clear group buffer for k := range dnsBatches { dnsBatches[k] = dnsBatches[k][:0] } - // 收集需要重试的域名 + // Collect domains that need retrying r.statusDB.Scan(func(key string, v statusdb.Item) error { - // 超过最大重试次数则放弃 + // Abandon if maximum retry count exceeded if r.maxRetryCount > 0 && v.Retry > r.maxRetryCount { r.statusDB.Del(key) atomic.AddUint64(&r.failedCount, 1) return nil } - // 检查是否超时 + // Check if timed out if int64(now.Sub(v.Time).Seconds()) >= r.timeoutSeconds { - // 将域名添加到重试列表,或者使用批量发送通道 + // Add domain to retry list or use batch sending channel retryDomains = append(retryDomains, key) - // 根据DNS服务器分组,以便批量发送 + // Group by DNS server for batch sending dns := r.selectDNSServer(key) if _, ok := dnsBatches[dns]; !ok { dnsBatches[dns] = make([]string, 0, batchSize) @@ -102,19 +102,19 @@ func (r *Runner) retry(ctx context.Context) { return nil }) - // 记录扫描状态 + // Record scan state lastScanEmpty = len(retryDomains) == 0 - // 如果有需要重试的域名 + // If there are domains to retry if len(retryDomains) > 0 { - // 向工作协程发送重试域名 + // Send retry domains to worker goroutines for _, domain := range retryDomains { - // 非阻塞发送 + // Non-blocking send select { case retryDomainCh <- domain: - // 发送成功 + // Sent successfully default: - // 通道满了,直接发送 + // Channel full, send directly r.domainChan <- domain } } diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 0b870fe..30ab904 100755 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -21,33 +21,33 @@ import ( "go.uber.org/ratelimit" ) -// Runner 表示子域名扫描的运行时结构 +// Runner represents the runtime structure for subdomain scanning type Runner struct { - statusDB *statusdb.StatusDb // 状态数据库 - options *options.Options // 配置选项 - rateLimiter ratelimit.Limiter // 速率限制器 - pcapHandle *pcap.Handle // 网络抓包句柄 - successCount uint64 // 成功数量 - sendCount uint64 // 发送数量 - receiveCount uint64 // 接收数量 - failedCount uint64 // 失败数量 - domainChan chan string // 域名发送通道 - resultChan chan result.Result // 结果接收通道 - listenPort int // 监听端口 - dnsID uint16 // DNS请求ID - maxRetryCount int // 最大重试次数 - timeoutSeconds int64 // 超时秒数 - initialLoadDone chan struct{} // 初始加载完成信号 - predictLoadDone chan struct{} // predict加载完成信号 - startTime time.Time // 开始时间 - stopSignal chan struct{} // 停止信号 + statusDB *statusdb.StatusDb // Status database + options *options.Options // Configuration options + rateLimiter ratelimit.Limiter // Rate limiter + pcapHandle *pcap.Handle // Network capture handle + successCount uint64 // Success count + sendCount uint64 // Send count + receiveCount uint64 // Receive count + failedCount uint64 // Failed count + domainChan chan string // Domain sending channel + resultChan chan result.Result // Result receiving channel + listenPort int // Listening port + dnsID uint16 // DNS request ID + maxRetryCount int // Maximum retry count + timeoutSeconds int64 // Timeout in seconds + initialLoadDone chan struct{} // Initial load done signal + predictLoadDone chan struct{} // Predict load done signal + startTime time.Time // Start time + stopSignal chan struct{} // Stop signal } func init() { rand.New(rand.NewSource(time.Now().UnixNano())) } -// New 创建一个新的Runner实例 +// New creates a new Runner instance func New(opt *options.Options) (*Runner, error) { var err error version := pcap.Version() @@ -56,52 +56,52 @@ func New(opt *options.Options) (*Runner, error) { r.options = opt r.statusDB = statusdb.CreateMemoryDB() - // 记录DNS服务器信息 - gologger.Infof("默认DNS服务器: %s\n", core.SliceToString(opt.Resolvers)) + // Log DNS server information + gologger.Infof("Default DNS servers: %s\n", core.SliceToString(opt.Resolvers)) if len(opt.SpecialResolvers) > 0 { var keys []string for k := range opt.SpecialResolvers { keys = append(keys, k) } - gologger.Infof("特殊DNS服务器: %s\n", core.SliceToString(keys)) + gologger.Infof("Special DNS servers: %s\n", core.SliceToString(keys)) } - // 初始化网络设备 + // Initialize network device r.pcapHandle, err = device.PcapInit(opt.EtherInfo.Device) if err != nil { return nil, err } - // 设置速率限制 + // Set rate limit cpuLimit := float64(runtime.NumCPU() * 10000) rateLimit := int(math.Min(cpuLimit, float64(opt.Rate))) - - // Mac 平台优化: BPF 缓冲区限制较严格 - // 建议速率 < 50000 pps 以避免缓冲区溢出 + + // Mac platform optimization: BPF buffer has stricter limits. + // Recommended rate < 50000 pps to avoid buffer overflow. if runtime.GOOS == "darwin" && rateLimit > 50000 { - gologger.Warningf("Mac 平台检测到: 当前速率 %d pps 可能导致缓冲区问题\n", rateLimit) - gologger.Warningf("建议: 使用 -b 参数限制带宽 (如 -b 5m) 或降低速率\n") - gologger.Warningf("提示: Mac BPF 缓冲区已优化至 2MB,但仍建议速率 < 50000 pps\n") + gologger.Warningf("Mac platform detected: current rate %d pps may cause buffer issues\n", rateLimit) + gologger.Warningf("Suggestion: use -b flag to limit bandwidth (e.g., -b 5m) or lower the rate\n") + gologger.Warningf("Note: Mac BPF buffer has been optimized to 2MB, but rate < 50000 pps is still recommended\n") } - + r.rateLimiter = ratelimit.New(rateLimit) - gologger.Infof("速率限制: %d pps\n", rateLimit) + gologger.Infof("Rate limit: %d pps\n", rateLimit) - // 初始化通道 + // Initialize channels r.domainChan = make(chan string, 50000) r.resultChan = make(chan result.Result, 5000) r.stopSignal = make(chan struct{}) - // 获取空闲端口 + // Get a free port freePort, err := freeport.GetFreePort() if err != nil { return nil, err } r.listenPort = freePort - gologger.Infof("监听端口: %d\n", freePort) + gologger.Infof("Listening port: %d\n", freePort) - // 设置其他参数 - r.dnsID = 0x2021 // ksubdomain的生日 + // Set other parameters + r.dnsID = 0x2021 // ksubdomain's birthday r.maxRetryCount = opt.Retry r.timeoutSeconds = int64(opt.TimeOut) r.initialLoadDone = make(chan struct{}) @@ -110,12 +110,12 @@ func New(opt *options.Options) (*Runner, error) { return r, nil } -// selectDNSServer 根据域名智能选择DNS服务器 +// selectDNSServer intelligently selects a DNS server based on the domain name func (r *Runner) selectDNSServer(domain string) string { dnsServers := r.options.Resolvers specialDNSServers := r.options.SpecialResolvers - // 根据域名后缀选择特定DNS服务器 + // Select a specific DNS server based on domain suffix if len(specialDNSServers) > 0 { for suffix, servers := range specialDNSServers { if strings.HasSuffix(domain, suffix) { @@ -125,17 +125,17 @@ func (r *Runner) selectDNSServer(domain string) string { } } - // 随机选择一个DNS服务器 + // Randomly select a DNS server idx := getRandomIndex() % len(dnsServers) return dnsServers[idx] } -// getRandomIndex 获取随机索引 +// getRandomIndex returns a random index func getRandomIndex() int { return int(rand.Int31()) } -// updateStatusBar 更新进度条状态 +// updateStatusBar updates the progress bar status func (r *Runner) updateStatusBar() { if r.options.ProcessBar != nil { queueLength := r.statusDB.Length() @@ -152,18 +152,18 @@ func (r *Runner) updateStatusBar() { } } -// loadDomainsFromSource 从源加载域名 +// loadDomainsFromSource loads domains from the source channel func (r *Runner) loadDomainsFromSource(wg *sync.WaitGroup) { defer wg.Done() - // 从域名源加载域名 + // Load domains from the domain source for domain := range r.options.Domain { r.domainChan <- domain } - // 通知初始加载完成 + // Notify that the initial load is complete r.initialLoadDone <- struct{}{} } -// monitorProgress 监控扫描进度 +// monitorProgress monitors the scan progress func (r *Runner) monitorProgress(ctx context.Context, cancelFunc context.CancelFunc, wg *sync.WaitGroup) { var initialLoadCompleted bool = false var initialLoadPredict bool = false @@ -173,20 +173,20 @@ func (r *Runner) monitorProgress(ctx context.Context, cancelFunc context.CancelF for { select { case <-ticker.C: - // 更新状态栏 + // Update status bar r.updateStatusBar() - // 检查是否完成 + // Check if scan is complete if initialLoadCompleted && initialLoadPredict { queueLength := r.statusDB.Length() if queueLength <= 0 { gologger.Printf("\n") - gologger.Infof("扫描完毕") - cancelFunc() // 使用传递的cancelFunc + gologger.Infof("Scan completed") + cancelFunc() // Use the passed cancelFunc return } } case <-r.initialLoadDone: - // 初始加载完成后启动重试机制 + // Start retry mechanism after initial load is complete go r.retry(ctx) initialLoadCompleted = true case <-r.predictLoadDone: @@ -197,7 +197,7 @@ func (r *Runner) monitorProgress(ctx context.Context, cancelFunc context.CancelF } } -// processPredictedDomains 处理预测的域名 +// processPredictedDomains handles predicted domain names func (r *Runner) processPredictedDomains(ctx context.Context, wg *sync.WaitGroup, predictChan chan string) { defer wg.Done() for { @@ -210,72 +210,72 @@ func (r *Runner) processPredictedDomains(ctx context.Context, wg *sync.WaitGroup } } -// RunEnumeration 开始子域名枚举过程 +// RunEnumeration starts the subdomain enumeration process func (r *Runner) RunEnumeration(ctx context.Context) { - // 创建可取消的上下文 + // Create a cancellable context ctx, cancelFunc := context.WithCancel(ctx) defer cancelFunc() - // 创建等待组,现在需要等待5个goroutine(添加了sendCycle和handleResult) + // Create wait group; need to wait for 5 goroutines (recv, send, monitor, result, load) wg := &sync.WaitGroup{} wg.Add(5) - // 启动接收处理 + // Start receive processing go r.recvChanel(ctx, wg) - // 启动发送处理(加入waitgroup管理) + // Start send processing (under waitgroup management) go r.sendCycleWithContext(ctx, wg) - // 监控进度 + // Monitor progress go r.monitorProgress(ctx, cancelFunc, wg) - // 创建预测域名通道 + // Create predicted domain channel predictChan := make(chan string, 1000) if r.options.Predict { wg.Add(1) - // 启动预测域名处理 + // Start predicted domain processing go r.processPredictedDomains(ctx, wg, predictChan) } else { r.predictLoadDone <- struct{}{} } - // 启动结果处理(加入waitgroup管理) + // Start result processing (under waitgroup management) go r.handleResultWithContext(ctx, wg, predictChan) - // 从源加载域名 + // Load domains from source go r.loadDomainsFromSource(wg) - // 等待所有协程完成 + // Wait for all goroutines to complete wg.Wait() - // 关闭所有通道 + // Close all channels close(predictChan) - // 安全关闭通道 + // Safely close channels close(r.resultChan) close(r.domainChan) } -// Close 关闭Runner并释放资源 +// Close closes the Runner and releases resources func (r *Runner) Close() { - // 关闭网络抓包句柄 + // Close network capture handle if r.pcapHandle != nil { r.pcapHandle.Close() } - // 关闭状态数据库 + // Close status database if r.statusDB != nil { r.statusDB.Close() } - // 关闭所有输出器 + // Close all output handlers for _, out := range r.options.Writer { err := out.Close() if err != nil { - gologger.Errorf("关闭输出器失败: %v", err) + gologger.Errorf("Failed to close output handler: %v", err) } } - // 关闭进度条 + // Close progress bar if r.options.ProcessBar != nil { r.options.ProcessBar.Close() } diff --git a/pkg/runner/send.go b/pkg/runner/send.go index 2ff3e36..196aa7c 100755 --- a/pkg/runner/send.go +++ b/pkg/runner/send.go @@ -16,7 +16,7 @@ import ( "github.com/google/gopacket/pcap" ) -// packetTemplate DNS请求包模板 +// packetTemplate is a DNS request packet template type packetTemplate struct { eth *layers.Ethernet ip *layers.IPv4 @@ -26,23 +26,23 @@ type packetTemplate struct { dnsip net.IP } -// templateCache 全局DNS服务器模板缓存 -// 优化说明: DNS服务器数量有限(通常<10个),每次创建模板开销较大 -// 使用 sync.Map 缓存模板,避免重复创建以太网/IP/UDP层 -// 预期性能提升: 5-10% (减少内存分配和IP解析开销) +// templateCache is the global DNS server template cache. +// Optimization note: the number of DNS servers is limited (usually <10), and creating a template each time is expensive. +// Uses sync.Map to cache templates, avoiding repeated creation of Ethernet/IP/UDP layers. +// Expected performance gain: 5-10% (reduced memory allocation and IP parsing overhead). var templateCache sync.Map -// getOrCreate 获取或创建DNS服务器的数据包模板 -// 优化: 添加模板缓存,同一DNS服务器只创建一次模板 +// getOrCreate retrieves or creates a packet template for the given DNS server. +// Optimization: adds template caching so the template is only created once per DNS server. func getOrCreate(dnsname string, ether *device.EtherTable, freeport uint16) *packetTemplate { - // 优化点1: 先尝试从缓存获取,避免重复创建 - // key格式: dnsname_freeport (同一DNS可能使用不同源端口) + // Optimization point 1: try to get from cache first to avoid repeated creation. + // Key format: dnsname_freeport (same DNS may use different source ports) cacheKey := dnsname + "_" + string(rune(freeport)) if cached, ok := templateCache.Load(cacheKey); ok { return cached.(*packetTemplate) } - // 缓存未命中,创建新模板 + // Cache miss, create a new template DstIp := net.ParseIP(dnsname).To4() eth := &layers.Ethernet{ SrcMAC: ether.SrcMac.HardwareAddr(), @@ -84,14 +84,14 @@ func getOrCreate(dnsname string, ether *device.EtherTable, freeport uint16) *pac buf: gopacket.NewSerializeBuffer(), } - // 存入缓存供后续复用 + // Store in cache for later reuse templateCache.Store(cacheKey, template) return template } -// sendCycle 实现发送域名请求的循环 +// sendCycle implements the loop for sending domain requests func (r *Runner) sendCycle() { - // 从发送通道接收域名,分发给工作协程 + // Receive domains from sending channel and dispatch to worker goroutines for domain := range r.domainChan { r.rateLimiter.Take() v, ok := r.statusDB.Get(domain) @@ -115,65 +115,65 @@ func (r *Runner) sendCycle() { } } -// sendCycleWithContext 实现带有context管理的发送域名请求循环 -// 优化: 添加批量发送机制,减少系统调用次数 +// sendCycleWithContext implements the domain request sending loop with context management. +// Optimization: adds a batch sending mechanism to reduce the number of system calls. func (r *Runner) sendCycleWithContext(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() - // 优化点2: 批量发送机制 - // 批量大小: 每次收集100个域名后一起处理 - // 收益: 减少系统调用次数,提升发包吞吐量 20-30% + // Optimization point 2: batch sending mechanism. + // Batch size: collect 100 domains before processing them together. + // Benefit: reduces system call count, improves send throughput by 20-30%. const batchSize = 100 batch := make([]string, 0, batchSize) batchItems := make([]statusdb.Item, 0, batchSize) - // 定时器: 确保即使凑不满批次也能及时发送 + // Timer: ensures timely sending even if the batch is not full ticker := time.NewTicker(10 * time.Millisecond) defer ticker.Stop() - // 批量发送函数 + // Batch send function sendBatch := func() { if len(batch) == 0 { return } - // 批量发送所有域名 + // Send all domains in the batch for i, domain := range batch { send(domain, batchItems[i].Dns, r.options.EtherInfo, r.dnsID, uint16(r.listenPort), r.pcapHandle, layers.DNSTypeA) } - // 原子更新发送计数 + // Atomically update send count atomic.AddUint64(&r.sendCount, uint64(len(batch))) - // 清空批次,复用底层数组 + // Clear batch, reuse underlying array batch = batch[:0] batchItems = batchItems[:0] } - // 主循环: 收集域名并批量发送 + // Main loop: collect domains and send in batches for { select { case <-ctx.Done(): - // 退出前发送剩余批次 + // Send remaining batch before exiting sendBatch() return case <-ticker.C: - // 定时发送,避免批次未满时延迟过高 + // Timed send to avoid high latency when batch is not full sendBatch() case domain, ok := <-r.domainChan: if !ok { - // 通道关闭,发送剩余批次后退出 + // Channel closed, send remaining batch and exit sendBatch() return } - // 速率限制 + // Rate limiting r.rateLimiter.Take() - // 获取或创建域名状态 + // Get or create domain status v, ok := r.statusDB.Get(domain) if !ok { v = statusdb.Item{ @@ -191,11 +191,11 @@ func (r *Runner) sendCycleWithContext(ctx context.Context, wg *sync.WaitGroup) { r.statusDB.Set(domain, v) } - // 添加到批次 + // Add to batch batch = append(batch, domain) batchItems = append(batchItems, v) - // 批次已满,立即发送 + // Batch is full, send immediately if len(batch) >= batchSize { sendBatch() } @@ -203,25 +203,25 @@ func (r *Runner) sendCycleWithContext(ctx context.Context, wg *sync.WaitGroup) { } } -// send 发送单个DNS查询包 +// send sends a single DNS query packet func send(domain string, dnsname string, ether *device.EtherTable, dnsid uint16, freeport uint16, handle *pcap.Handle, dnsType layers.DNSType) { - // 复用DNS服务器的包模板 + // Reuse the DNS server's packet template template := getOrCreate(dnsname, ether, freeport) - // 从内存池获取DNS层对象 + // Get a DNS layer object from the memory pool dns := GlobalMemPool.GetDNS() defer GlobalMemPool.PutDNS(dns) - // 设置DNS查询参数 + // Set DNS query parameters dns.ID = dnsid dns.QDCount = 1 - dns.RD = true // 递归查询标识 + dns.RD = true // Recursion desired flag - // 从内存池获取questions切片 + // Get questions slice from memory pool questions := GlobalMemPool.GetDNSQuestions() defer GlobalMemPool.PutDNSQuestions(questions) - // 添加查询问题 + // Add query question questions = append(questions, layers.DNSQuestion{ Name: []byte(domain), Type: dnsType, @@ -229,54 +229,54 @@ func send(domain string, dnsname string, ether *device.EtherTable, dnsid uint16, }) dns.Questions = questions - // 从内存池获取序列化缓冲区 + // Get serialize buffer from memory pool buf := GlobalMemPool.GetBuffer() defer GlobalMemPool.PutBuffer(buf) - // 序列化数据包 + // Serialize the packet err := gopacket.SerializeLayers( buf, template.opts, template.eth, template.ip, template.udp, dns, ) if err != nil { - gologger.Warningf("SerializeLayers faild:%s\n", err.Error()) + gologger.Warningf("SerializeLayers failed: %s\n", err.Error()) return } - // 发送数据包 - // 修复 Mac 缓冲区问题: 增加重试机制,使用指数退避 + // Send the packet. + // Fix Mac buffer issue: add retry mechanism with exponential backoff. const maxRetries = 3 for retry := 0; retry < maxRetries; retry++ { err = handle.WritePacketData(buf.Bytes()) if err == nil { - return // 发送成功 + return // Sent successfully } - + errMsg := err.Error() - - // 检查是否为缓冲区错误 (Mac/Linux 常见) + + // Check if it is a buffer error (common on Mac/Linux). // Mac BPF: "No buffer space available" (ENOBUFS) - // Linux: 可能有类似错误 + // Linux: similar errors may occur isBufferError := strings.Contains(errMsg, "No buffer space available") || strings.Contains(errMsg, "ENOBUFS") || strings.Contains(errMsg, "buffer") - + if isBufferError { - // 缓冲区满,需要重试 + // Buffer full, need to retry if retry < maxRetries-1 { - // 指数退避: 10ms, 20ms, 40ms + // Exponential backoff: 10ms, 20ms, 40ms backoff := time.Millisecond * time.Duration(10*(1<= 900 { tickIndex := index / tickTime - gologger.Printf("\r %ds 总发送:%d Packet 平均每秒速度:%dpps", tickTime, index, tickIndex) + gologger.Printf("\r %ds Total sent:%d Packet Average speed:%dpps", tickTime, index, tickIndex) } } now = time.Now().UnixNano() / 1e6 tickTime := (now - start) / 1000 tickIndex := index / tickTime - gologger.Printf("\r %ds 总发送:%d Packet 平均每秒速度:%dpps\n", tickTime, index, tickIndex) + gologger.Printf("\r %ds Total sent:%d Packet Average speed:%dpps\n", tickTime, index, tickIndex) } diff --git a/pkg/utils/wildcard.go b/pkg/utils/wildcard.go index 04b021b..fc26e15 100755 --- a/pkg/utils/wildcard.go +++ b/pkg/utils/wildcard.go @@ -29,7 +29,7 @@ func sortMapByValue(m map[string]int) PairList { return p } -// WildFilterOutputResult 泛解析过滤结果 +// WildFilterOutputResult filters wildcard DNS results func WildFilterOutputResult(outputType string, results []result.Result) []result.Result { if outputType == "none" { return results @@ -41,27 +41,27 @@ func WildFilterOutputResult(outputType string, results []result.Result) []result return nil } -// FilterWildCard 基于Result类型数据过滤泛解析 -// 传入参数为[]result.Result,返回过滤后的[]result.Result -// 通过分析整体结果,对解析记录中相同的ip进行阈值判断,超过则丢弃该结果 +// FilterWildCard filters wildcard DNS results based on Result type data. +// Input: []result.Result, Output: filtered []result.Result. +// Analyzes overall results and applies threshold-based filtering on repeated IP addresses. func FilterWildCard(results []result.Result) []result.Result { if len(results) == 0 { return results } - gologger.Debugf("泛解析处理中,共 %d 条记录...\n", len(results)) + gologger.Debugf("Processing wildcard filter, total %d records...\n", len(results)) - // 统计每个IP出现的次数 + // Count IP occurrence frequency ipFrequency := make(map[string]int) - // 记录IP到域名的映射关系 + // Record IP-to-domain mappings ipToDomains := make(map[string][]string) - // 域名计数 + // Total domain count totalDomains := len(results) - // 第一遍扫描,统计IP频率 + // First pass: count IP frequency for _, res := range results { for _, answer := range res.Answers { - // 跳过非IP的记录(CNAME等) + // Skip non-IP records (CNAME, etc.) if !strings.HasPrefix(answer, "CNAME ") && !strings.HasPrefix(answer, "NS ") && !strings.HasPrefix(answer, "TXT ") && !strings.HasPrefix(answer, "PTR ") { ipFrequency[answer]++ @@ -70,61 +70,61 @@ func FilterWildCard(results []result.Result) []result.Result { } } - // 按出现频率排序IP + // Sort IPs by occurrence frequency sortedIPs := sortMapByValue(ipFrequency) - // 确定疑似泛解析的IP列表 - // 使用两个标准: - // 1. IP解析超过总域名数量的特定百分比(动态阈值) - // 2. 该IP解析的子域名数量超过特定阈值 + // Determine suspicious wildcard IP list. + // Uses two criteria: + // 1. IP resolution exceeds a certain percentage of total domains (dynamic threshold) + // 2. Number of subdomains resolved by this IP exceeds a specific threshold suspiciousIPs := make(map[string]bool) for _, pair := range sortedIPs { ip := pair.Key count := pair.Value - // 计算该IP解析占总体的百分比 + // Calculate the percentage of total domains this IP resolves percentage := float64(count) / float64(totalDomains) * 100 - // 动态阈值:根据总域名数量调整 - // 域名数量少时阈值较高,域名数量多时阈值较低 + // Dynamic threshold: adjust based on total domain count. + // Higher threshold for fewer domains, lower threshold for more domains. var threshold float64 if totalDomains < 100 { - threshold = 30 // 如果域名总数小于100,阈值设为30% + threshold = 30 // If total domains < 100, threshold is 30% } else if totalDomains < 1000 { - threshold = 20 // 如果域名总数在100-1000,阈值设为20% + threshold = 20 // If total domains 100-1000, threshold is 20% } else { - threshold = 10 // 如果域名总数超过1000,阈值设为10% + threshold = 10 // If total domains > 1000, threshold is 10% } - // 绝对数量阈值 + // Absolute count threshold absoluteThreshold := 70 - // 如果超过阈值,标记为可疑IP + // Mark as suspicious if threshold is exceeded if percentage > threshold || count > absoluteThreshold { - gologger.Debugf("发现可疑泛解析IP: %s (解析了 %d 个域名, %.2f%%)\n", + gologger.Debugf("Suspicious wildcard IP detected: %s (resolved %d domains, %.2f%%)\n", ip, count, percentage) suspiciousIPs[ip] = true } } - // 第二遍扫描,过滤结果 + // Second pass: filter results var filteredResults []result.Result for _, res := range results { - // 检查该域名的所有IP是否均为可疑IP - // 如果有不可疑的IP,保留该记录 + // Check if all IPs for this domain are suspicious. + // Keep the record if there is at least one non-suspicious IP. validRecord := false var filteredAnswers []string for _, answer := range res.Answers { - // 保留所有非IP记录(如CNAME) + // Keep all non-IP records (e.g., CNAME) if strings.HasPrefix(answer, "CNAME ") || strings.HasPrefix(answer, "NS ") || strings.HasPrefix(answer, "TXT ") || strings.HasPrefix(answer, "PTR ") { validRecord = true filteredAnswers = append(filteredAnswers, answer) } else if !suspiciousIPs[answer] { - // 保留不在可疑IP列表中的IP + // Keep IPs not in the suspicious list validRecord = true filteredAnswers = append(filteredAnswers, answer) } @@ -139,40 +139,40 @@ func FilterWildCard(results []result.Result) []result.Result { } } - gologger.Infof("泛解析过滤完成,从 %d 条记录中过滤出 %d 条有效记录\n", - totalDomains, len(filteredResults)) + gologger.Infof("Wildcard filtering completed, filtered %d valid records from %d total\n", + len(filteredResults), totalDomains) return filteredResults } -// FilterWildCardAdvanced 提供更高级的泛解析检测算法 -// 使用多种启发式方法和特征检测来识别泛解析 +// FilterWildCardAdvanced provides a more advanced wildcard detection algorithm. +// Uses multiple heuristics and feature detection to identify wildcard DNS. func FilterWildCardAdvanced(results []result.Result) []result.Result { if len(results) == 0 { return results } - gologger.Debugf("高级泛解析检测开始,共 %d 条记录...\n", len(results)) + gologger.Debugf("Advanced wildcard detection started, total %d records...\n", len(results)) - // 统计IP出现频率 + // Count IP occurrence frequency ipFrequency := make(map[string]int) - // 统计每个IP解析的不同子域名前缀数量 + // Count the variety of subdomain prefixes resolved by each IP ipPrefixVariety := make(map[string]map[string]bool) - // 统计IP解析的不同顶级域数量 + // Count the variety of TLDs resolved by each IP ipTLDVariety := make(map[string]map[string]bool) - // 记录IP到域名的映射 + // Record IP-to-domain mappings ipToDomains := make(map[string][]string) - // 记录CNAME信息 + // Record CNAME information cnameRecords := make(map[string][]string) totalDomains := len(results) - // 第一轮:收集统计信息 + // First round: collect statistics for _, res := range results { subdomain := res.Subdomain parts := strings.Split(subdomain, ".") - // 提取顶级域和前缀 + // Extract TLD and prefix prefix := "" tld := "" if len(parts) > 1 { @@ -185,7 +185,7 @@ func FilterWildCardAdvanced(results []result.Result) []result.Result { for _, answer := range res.Answers { if strings.HasPrefix(answer, "CNAME ") { - // 提取CNAME目标 + // Extract CNAME target cnameParts := strings.SplitN(answer, " ", 2) if len(cnameParts) == 2 { cnameTarget := cnameParts[1] @@ -194,14 +194,14 @@ func FilterWildCardAdvanced(results []result.Result) []result.Result { continue } - // 只处理IP记录 + // Process IP records only if !strings.HasPrefix(answer, "NS ") && !strings.HasPrefix(answer, "TXT ") && !strings.HasPrefix(answer, "PTR ") { - // 计数IP频率 + // Count IP frequency ipFrequency[answer]++ - // 初始化IP的前缀集合和TLD集合 + // Initialize prefix and TLD sets for this IP if ipPrefixVariety[answer] == nil { ipPrefixVariety[answer] = make(map[string]bool) } @@ -209,41 +209,41 @@ func FilterWildCardAdvanced(results []result.Result) []result.Result { ipTLDVariety[answer] = make(map[string]bool) } - // 记录这个IP解析了哪些不同的前缀和TLD + // Record which prefixes and TLDs this IP resolves ipPrefixVariety[answer][prefix] = true ipTLDVariety[answer][tld] = true - // 记录IP到域名的映射 + // Record IP-to-domain mapping ipToDomains[answer] = append(ipToDomains[answer], subdomain) } } } - // 按照IP频率排序 + // Sort IPs by frequency sortedIPs := sortMapByValue(ipFrequency) - // 识别可疑IP列表 - suspiciousIPs := make(map[string]float64) // IP -> 可疑度分数(0-100) + // Identify suspicious IPs + suspiciousIPs := make(map[string]float64) // IP -> suspicion score (0-100) for _, pair := range sortedIPs { ip := pair.Key count := pair.Value - // 初始可疑度分数 + // Initial suspicion score suspiciousScore := 0.0 - // 因子1: IP频率百分比 + // Factor 1: IP frequency percentage freqPercentage := float64(count) / float64(totalDomains) * 100 - // 因子2: 前缀多样性 + // Factor 2: prefix variety prefixVariety := len(ipPrefixVariety[ip]) prefixVarietyRatio := float64(prefixVariety) / float64(count) * 100 - // 因子3: TLD多样性 + // Factor 3: TLD variety tldVariety := len(ipTLDVariety[ip]) - // 计算可疑度分数 - // 1. 频率因子 + // Calculate suspicion score + // 1. Frequency factor if freqPercentage > 30 { suspiciousScore += 40 } else if freqPercentage > 10 { @@ -252,15 +252,15 @@ func FilterWildCardAdvanced(results []result.Result) []result.Result { suspiciousScore += 10 } - // 2. 前缀多样性因子 - // 如果一个IP解析了大量不同前缀的域名,可能是CDN或者泛解析 + // 2. Prefix variety factor + // If an IP resolves many different subdomain prefixes, it may be CDN or wildcard DNS if prefixVarietyRatio > 90 && prefixVariety > 10 { suspiciousScore += 30 } else if prefixVarietyRatio > 70 && prefixVariety > 5 { suspiciousScore += 20 } - // 3. 绝对数量因子 + // 3. Absolute count factor if count > 100 { suspiciousScore += 20 } else if count > 50 { @@ -269,25 +269,25 @@ func FilterWildCardAdvanced(results []result.Result) []result.Result { suspiciousScore += 5 } - // 4. TLD多样性因子 - 如果一个IP解析了多个不同TLD,更可能是合法的 + // 4. TLD variety factor - if an IP resolves multiple different TLDs, it's more likely legitimate if tldVariety > 3 { suspiciousScore -= 20 } else if tldVariety > 1 { suspiciousScore -= 10 } - // 只有当可疑度分数超过阈值时,才标记为可疑IP + // Mark as suspicious only if the suspicion score exceeds the threshold if suspiciousScore >= 35 { - gologger.Debugf("可疑IP: %s (解析域名数: %d, 占比: %.2f%%, 前缀多样性: %d/%d, 可疑度: %.2f)\n", + gologger.Debugf("Suspicious IP: %s (resolved domains: %d, percentage: %.2f%%, prefix variety: %d/%d, suspicion score: %.2f)\n", ip, count, freqPercentage, prefixVariety, count, suspiciousScore) suspiciousIPs[ip] = suspiciousScore } } - // 第二轮:过滤结果 + // Second round: filter results var filteredResults []result.Result - // CNAME聚类分析:检测指向相同目标的多个CNAME记录 + // CNAME clustering analysis: detect multiple CNAME records pointing to the same target cnameTargetCount := make(map[string]int) for _, targets := range cnameRecords { for _, target := range targets { @@ -295,18 +295,18 @@ func FilterWildCardAdvanced(results []result.Result) []result.Result { } } - // 识别可疑CNAME目标 + // Identify suspicious CNAME targets suspiciousCnames := make(map[string]bool) for cname, count := range cnameTargetCount { if count > 5 && float64(count)/float64(totalDomains)*100 > 10 { - gologger.Debugf("可疑CNAME目标: %s (指向次数: %d)\n", cname, count) + gologger.Debugf("Suspicious CNAME target: %s (pointed to %d times)\n", cname, count) suspiciousCnames[cname] = true } } - // 过滤结果 + // Filter results for _, res := range results { - // 检查是否含有可疑CNAME + // Check if it contains a suspicious CNAME hasSuspiciousCname := false if targets, ok := cnameRecords[res.Subdomain]; ok { for _, target := range targets { @@ -320,28 +320,28 @@ func FilterWildCardAdvanced(results []result.Result) []result.Result { validRecord := !hasSuspiciousCname var filteredAnswers []string - // 处理所有回答 + // Process all answers for _, answer := range res.Answers { isIP := !strings.HasPrefix(answer, "CNAME ") && !strings.HasPrefix(answer, "NS ") && !strings.HasPrefix(answer, "TXT ") && !strings.HasPrefix(answer, "PTR ") - // 保留所有非IP记录但排除可疑CNAME + // Keep all non-IP records but exclude suspicious CNAMEs if !isIP { if strings.HasPrefix(answer, "CNAME ") { cnameParts := strings.SplitN(answer, " ", 2) if len(cnameParts) == 2 && suspiciousCnames[cnameParts[1]] { - continue // 跳过可疑CNAME + continue // Skip suspicious CNAME } } validRecord = true filteredAnswers = append(filteredAnswers, answer) } else { - // 针对IP记录,根据可疑度评分过滤 + // For IP records, filter based on suspicion score suspiciousScore, isSuspicious := suspiciousIPs[answer] - // 如果不在可疑IP列表中,或者可疑度较低,则保留 + // Keep if not in suspicious IP list or suspicion score is low if !isSuspicious || suspiciousScore < 50 { validRecord = true filteredAnswers = append(filteredAnswers, answer) @@ -349,7 +349,7 @@ func FilterWildCardAdvanced(results []result.Result) []result.Result { } } - // 只添加有效记录 + // Add only valid records if validRecord && len(filteredAnswers) > 0 { filteredRes := result.Result{ Subdomain: res.Subdomain, @@ -359,8 +359,8 @@ func FilterWildCardAdvanced(results []result.Result) []result.Result { } } - gologger.Infof("高级泛解析过滤完成,从 %d 条记录中过滤出 %d 条有效记录\n", - totalDomains, len(filteredResults)) + gologger.Infof("Advanced wildcard filtering completed, filtered %d valid records from %d total\n", + len(filteredResults), totalDomains) return filteredResults } diff --git a/readme.md b/readme.md index ed736c5..459d484 100755 --- a/readme.md +++ b/readme.md @@ -1,195 +1,196 @@ -# KSubdomain: 极速无状态子域名爆破工具 +# KSubdomain: Ultra-Fast Stateless Subdomain Enumeration Tool [![Release](https://img.shields.io/github/release/boy-hack/ksubdomain.svg)](https://github.com/boy-hack/ksubdomain/releases) [![Go Report Card](https://goreportcard.com/badge/github.com/boy-hack/ksubdomain)](https://goreportcard.com/report/github.com/boy-hack/ksubdomain) [![License](https://img.shields.io/github/license/boy-hack/ksubdomain)](https://github.com/boy-hack/ksubdomain/blob/main/LICENSE) -**KSubdomain 是一款基于无状态技术的子域名爆破工具,带来前所未有的扫描速度和极低的内存占用。** 告别传统工具的效率瓶颈,体验闪电般的 DNS 查询,同时拥有可靠的状态表重发机制,确保结果的完整性。 KSubdomain 支持 Windows、Linux 和 macOS,是进行大规模DNS资产探测的理想选择。 +**KSubdomain is a stateless subdomain enumeration tool that delivers unprecedented scanning speed with extremely low memory consumption.** Say goodbye to traditional tool bottlenecks and experience lightning-fast DNS queries, backed by a reliable state-table retransmission mechanism that ensures result completeness. KSubdomain supports Windows, Linux, and macOS, making it the ideal choice for large-scale DNS asset discovery. ![](image.gif) -## 🚀 核心优势 - -* **闪电般的速度:** 采用无状态扫描技术,直接操作网络适配器进行原始套接字发包,绕过系统内核的网络协议栈,实现惊人的发包速率。通过 `test` 命令可探测本地网卡的最大发送速度。 -* **极低的资源消耗:** 创新的内存管理机制,包括对象池和全局内存池,显著降低内存分配和 GC 压力,即使处理海量域名也能保持低内存占用。 -* **无状态设计:** 类似 Masscan 的无状态扫描,不从系统维护状态表,自建轻量状态表,从根本上解决了传统扫描工具的内存瓶颈和性能限制,以及解决了无状态扫描漏包问题。 -* **可靠的重发:** 内建智能重发机制,有效应对网络抖动和丢包,确保结果的准确性和完整性。 -* **跨平台支持:** 完美兼容 Windows, Linux, macOS。 -* **易于使用:** 简洁的命令行接口,提供验证 (verify) 和枚举 (enum) 两种模式,并内置常用字典。 - -## ⚡ 性能亮点 - -KSubdomain 在速度和效率上远超同类工具。以下是在 4 核 CPU、5M 带宽网络环境下,使用 10 万字典进行的对比测试: - -| 工具 | 扫描模式 | 发包方式 | 命令 | 耗时 | 成功个数 | 备注 | -| ------------ | -------- | ------------ | -------------------------------------------------------------------------- | -------------- | -------- | ------------------------- | -| **KSubdomain** | 验证 | pcap 网卡发包 | `time ./ksubdomain v -b 5m -f d2.txt -o k.txt -r dns.txt --retry 3 --np` | **~30 秒** | 1397 | `--np` 关闭实时打印 | -| massdns | 验证 | pcap/socket | `time ./massdns -r dns.txt -t A -w m.txt d2.txt --root -o L` | ~3 分 29 秒 | 1396 | | -| dnsx | 验证 | socket | `time ./dnsx -a -o d.txt -r dns.txt -l d2.txt -retry 3 -t 5000` | ~5 分 26 秒 | 1396 | `-t 5000` 设置 5000 并发 | - -**结论:** KSubdomain 的速度是 massdns 的 **7 倍**,是 dnsx 的 **10 倍** 以上! -## 🛠️ 技术革新 (v2.0) - -KSubdomain 2.0 版本引入了多项底层优化,进一步压榨性能潜力: - -1. **状态表优化:** - * **分片锁 (Sharded Lock):** 替代全局锁,大幅减少锁竞争,提高并发写入效率。 - * **高效哈希:** 优化键值存储,均匀分布域名,提升查找速度。 -2. **发包机制优化:** - * **对象池:** 复用 DNS 包结构体,减少内存分配和 GC 开销。 - * **模板缓存:** 为相同 DNS 服务器复用以太网/IP/UDP 层数据,减少重复构建开销。 - * **并行发送:** 多协程并行发包,充分利用多核 CPU 性能。 - * **批量处理:** 批量发送域名请求,减少系统调用和上下文切换。 -3. **接收机制优化:** - * **对象池:** 复用解析器和缓冲区,降低内存消耗。 - * **并行处理管道:** 接收 → 解析 → 处理三阶段并行,提高处理流水线效率。 - * **缓冲区优化:** 增加内部 Channel 缓冲区大小,避免处理阻塞。 - * **高效过滤:** 优化 BPF 过滤规则和包处理逻辑,快速丢弃无效数据包。 -4. **内存管理优化:** - * **全局内存池:** 引入 `sync.Pool` 管理常用数据结构,减少内存分配和碎片。 - * **结构复用:** 复用 DNS 查询结构和序列化缓冲区。 -5. **架构与并发优化:** - * **动态并发:** 根据 CPU 核心数自动调整协程数量。 - * **高效随机数:** 使用性能更优的随机数生成器。 - * **自适应速率:** 根据网络状况和系统负载动态调整发包速率。 - * **批量加载:** 批量加载和处理域名,降低单个域名处理的固定开销。 - -## 📦 安装 - -1. **下载预编译二进制文件:** 前往 [Releases](https://github.com/boy-hack/ksubdomain/releases) 页面下载对应系统的最新版本。 -2. **安装 `libpcap` 依赖:** - * **Windows:** 下载并安装 [Npcap](https://npcap.com/) 驱动 (WinPcap 可能无效)。 - * **Linux:** 已静态编译打包 `libpcap`,通常无需额外操作。若遇问题,请尝试安装 `libpcap-dev` 或 `libcap-devel` 包。 - * **macOS:** 系统自带 `libpcap`,无需安装。 -3. **赋予执行权限 (Linux/macOS):** `chmod +x ksubdomain` -4. **运行!** - -### 源码编译 (可选) - -确保您已安装 Go 1.23 版本和 `libpcap` 环境。 +## 🚀 Core Advantages + +* **Lightning-Fast Speed:** Utilizes stateless scanning technology to directly operate network adapters for raw socket packet sending, bypassing the system kernel's network protocol stack to achieve astonishing packet rates. Use the `test` command to probe your local network adapter's maximum sending speed. +* **Extremely Low Resource Consumption:** Innovative memory management mechanisms—including object pools and global memory pools—significantly reduce memory allocation and GC pressure, maintaining a low memory footprint even when processing massive domain lists. +* **Stateless Design:** Similar to Masscan's stateless scanning, it does not maintain a state table in the system. Instead it builds a lightweight state table of its own, fundamentally solving traditional scanning tools' memory bottlenecks and performance limitations, as well as the packet-loss problem inherent in stateless scanning. +* **Reliable Retransmission:** A built-in intelligent retransmission mechanism effectively handles network jitter and packet loss, ensuring result accuracy and completeness. +* **Cross-Platform Support:** Perfect compatibility with Windows, Linux, and macOS. +* **Easy to Use:** A clean command-line interface with verify and enum modes, plus built-in common dictionaries. + +## ⚡ Performance Highlights + +KSubdomain far exceeds similar tools in speed and efficiency. The following comparison was conducted on a 4-core CPU with 5 M bandwidth using a 100k dictionary: + +| Tool | Mode | Method | Command | Time | Success | Notes | +| ------------ | ------ | ------------- | ----------------------------------------------------------------------------- | -------------- | ------- | ------------------------------- | +| **KSubdomain** | Verify | pcap NIC send | `time ./ksubdomain v -b 5m -f d2.txt -o k.txt -r dns.txt --retry 3 --np` | **~30 sec** | 1397 | `--np` disables real-time print | +| massdns | Verify | pcap/socket | `time ./massdns -r dns.txt -t A -w m.txt d2.txt --root -o L` | ~3 min 29 sec | 1396 | | +| dnsx | Verify | socket | `time ./dnsx -a -o d.txt -r dns.txt -l d2.txt -retry 3 -t 5000` | ~5 min 26 sec | 1396 | `-t 5000` sets 5000 concurrency | + +**Conclusion:** KSubdomain is **7× faster** than massdns and **10× faster** than dnsx! + +## 🛠️ Technical Innovations (v2.0) + +KSubdomain 2.0 introduces multiple low-level optimizations to squeeze out even more performance: + +1. **State Table Optimization:** + * **Sharded Locks:** Replace the global lock, dramatically reducing lock contention and improving concurrent write throughput. + * **Efficient Hashing:** Optimized key-value storage with even domain distribution for faster lookups. +2. **Packet-Sending Optimization:** + * **Object Pools:** Reuse DNS packet structs to reduce memory allocation and GC overhead. + * **Template Caching:** Reuse Ethernet/IP/UDP layer data for the same DNS server, eliminating redundant construction. + * **Parallel Sending:** Multiple goroutines send packets concurrently, fully leveraging multi-core CPUs. + * **Batch Processing:** Send domain requests in batches to reduce system calls and context switches. +3. **Receive Optimization:** + * **Object Pools:** Reuse parsers and buffers to lower memory consumption. + * **Parallel Processing Pipeline:** Receive → Parse → Process three-stage parallelism improves pipeline throughput. + * **Buffer Optimization:** Larger internal Channel buffers prevent processing blockage. + * **Efficient Filtering:** Optimized BPF filter rules and packet-processing logic quickly discard invalid packets. +4. **Memory Management Optimization:** + * **Global Memory Pool:** `sync.Pool` manages common data structures to reduce allocation and fragmentation. + * **Structure Reuse:** Reuse DNS query structures and serialization buffers. +5. **Architecture and Concurrency Optimization:** + * **Dynamic Concurrency:** Automatically adjusts goroutine count based on CPU core count. + * **Efficient Random Numbers:** Uses a higher-performance random-number generator. + * **Adaptive Rate:** Dynamically adjusts packet-sending rate based on network conditions and system load. + * **Batch Loading:** Loads and processes domains in batches to reduce per-domain fixed overhead. + +## 📦 Installation + +1. **Download Pre-compiled Binary:** Visit the [Releases](https://github.com/boy-hack/ksubdomain/releases) page and download the latest version for your platform. +2. **Install `libpcap` Dependency:** + * **Windows:** Download and install the [Npcap](https://npcap.com/) driver (WinPcap may not work). + * **Linux:** `libpcap` is statically linked in the release binary; no extra steps are usually needed. If you encounter issues, try installing the `libpcap-dev` or `libcap-devel` package. + * **macOS:** `libpcap` ships with the OS; no installation required. +3. **Grant Execute Permission (Linux/macOS):** `chmod +x ksubdomain` +4. **Run!** + +### Build from Source (Optional) + +Ensure Go 1.23 and `libpcap` are installed. ```bash go install -v github.com/boy-hack/ksubdomain/v2/cmd/ksubdomain@latest -# 二进制文件通常位于 $GOPATH/bin 或 $HOME/go/bin +# The binary is usually placed in $GOPATH/bin or $HOME/go/bin ``` -## 📖 使用说明 +## 📖 Usage ```bash -KSubdomain - 极速无状态子域名爆破工具 +KSubdomain - Ultra-Fast Stateless Subdomain Enumeration Tool -用法: - ksubdomain [全局选项] 命令 [命令选项] [参数...] +Usage: + ksubdomain [global options] command [command options] [arguments...] -版本: - 查看版本信息: ksubdomain --version +Version: + Check version: ksubdomain --version -命令: - enum, e 枚举模式: 提供主域名进行爆破 - verify, v 验证模式: 提供域名列表进行验证 - test 测试本地网卡最大发包速度 - help, h 显示命令列表或某个命令的帮助 +Commands: + enum, e Enumeration mode: provide a root domain for brute-forcing + verify, v Verification mode: provide a domain list to verify + test Test the maximum packet-sending speed of the local NIC + help, h Show the command list or help for a specific command -全局选项: - --help, -h 显示帮助 (默认: false) - --version, -v 打印版本信息 (默认: false) +Global Options: + --help, -h Show help (default: false) + --version, -v Print version information (default: false) ``` -### 验证模式 (Verify) +### Verification Mode (Verify) -验证模式用于快速检查提供的域名列表的存活状态。 +Verification mode quickly checks the alive status of a provided domain list. ```bash -./ksubdomain verify -h # 查看验证模式帮助,可缩写 ksubdomain v +./ksubdomain verify -h # View verify-mode help; can be abbreviated as ksubdomain v USAGE: ksubdomain verify [command options] [arguments...] OPTIONS: - --filename value, -f value 验证域名的文件路径 - --domain value, -d value 域名 - --band value, -b value 宽带的下行速度,可以5M,5K,5G (default: "3m") - --resolvers value, -r value dns服务器,默认会使用内置dns - --output value, -o value 输出文件名 - --output-type value, --oy value 输出文件类型: json, txt, csv (default: "txt") - --silent 使用后屏幕将仅输出域名 (default: false) - --retry value 重试次数,当为-1时将一直重试 (default: 3) - --timeout value 超时时间 (default: 6) - --stdin 接受stdin输入 (default: false) - --not-print, --np 不打印域名结果 (default: false) - --eth value, -e value 指定网卡名称 - --wild-filter-mode value 泛解析过滤模式[从最终结果过滤泛解析域名]: basic(基础), advanced(高级), none(不过滤ne") - --predict 启用预测域名模式 (default: false) + --filename value, -f value Path to the file containing domains to verify + --domain value, -d value Domain name + --band value, -b value Downstream bandwidth, e.g. 5M, 5K, 5G (default: "3m") + --resolvers value, -r value DNS servers; uses built-in DNS by default + --output value, -o value Output filename + --output-type value, --oy value Output file type: json, txt, csv (default: "txt") + --silent Only print domain names to screen (default: false) + --retry value Retry count; -1 to retry indefinitely (default: 3) + --timeout value Timeout in seconds (default: 6) + --stdin Accept input from stdin (default: false) + --not-print, --np Do not print domain results (default: false) + --eth value, -e value Specify network adapter name + --wild-filter-mode value Wildcard filter mode [filter wildcard domains from final results]: basic, advanced, none (default: "none") + --predict Enable domain prediction mode (default: false) --help, -h show help (default: false) -# 示例: -# 验证多个域名解析 +# Examples: +# Verify multiple domains ./ksubdomain v -d xx1.example.com -d xx2example.com -# 从文件读取域名进行验证,保存为 output.txt +# Read domains from a file and save results to output.txt ./ksubdomain v -f domains.txt -o output.txt -# 从标准输入读取域名,带宽限制为 10M +# Read domains from stdin with a bandwidth limit of 10 M cat domains.txt | ./ksubdomain v --stdin -b 10M -# 启用预测模式,泛解析过滤,保存为csv +# Enable prediction mode with advanced wildcard filtering; save as CSV ./ksubdomain v -f domains.txt --predict --wild-filter-mode advanced --oy csv -o output.csv ``` -### 枚举模式 (Enum) +### Enumeration Mode (Enum) -枚举模式基于字典和预测算法爆破指定域名下的子域名。 +Enumeration mode brute-forces subdomains under a given domain using a dictionary and a prediction algorithm. ```bash -./ksubdomain enum -h # 查看枚举模式帮助,可简写 ksubdomain e +./ksubdomain enum -h # View enum-mode help; can be abbreviated as ksubdomain e USAGE: ksubdomain enum [command options] [arguments...] OPTIONS: - --domain value, -d value 域名 - --band value, -b value 宽带的下行速度,可以5M,5K,5G (default: "3m") - --resolvers value, -r value dns服务器,默认会使用内置dns - --output value, -o value 输出文件名 - --output-type value, --oy value 输出文件类型: json, txt, csv (default: "txt") - --silent 使用后屏幕将仅输出域名 (default: false) - --retry value 重试次数,当为-1时将一直重试 (default: 3) - --timeout value 超时时间 (default: 6) - --stdin 接受stdin输入 (default: false) - --not-print, --np 不打印域名结果 (default: false) - --eth value, -e value 指定网卡名称 - --wild-filter-mode value 泛解析过滤模式[从最终结果过滤泛解析域名]: basic(基础), advanced(高级), none(不过滤) (default: "none") - --predict 启用预测域名模式 (default: false) - --filename value, -f value 字典路径 - --ns 读取域名ns记录并加入到ns解析器中 (default: false) + --domain value, -d value Domain name + --band value, -b value Downstream bandwidth, e.g. 5M, 5K, 5G (default: "3m") + --resolvers value, -r value DNS servers; uses built-in DNS by default + --output value, -o value Output filename + --output-type value, --oy value Output file type: json, txt, csv (default: "txt") + --silent Only print domain names to screen (default: false) + --retry value Retry count; -1 to retry indefinitely (default: 3) + --timeout value Timeout in seconds (default: 6) + --stdin Accept input from stdin (default: false) + --not-print, --np Do not print domain results (default: false) + --eth value, -e value Specify network adapter name + --wild-filter-mode value Wildcard filter mode [filter wildcard domains from final results]: basic, advanced, none (default: "none") + --predict Enable domain prediction mode (default: false) + --filename value, -f value Dictionary file path + --ns Read the domain's NS records and add them to the resolver list (default: false) --help, -h show help (default: false) -# 示例: -# 枚举多个域名 +# Examples: +# Enumerate multiple domains ./ksubdomain e -d example.com -d hacker.com -# 从文件读取字典枚举,保存为 output.txt +# Use a dictionary file and save results to output.txt ./ksubdomain e -f sub.dict -o output.txt -# 从标准输入读取域名,带宽限制为 10M +# Read domains from stdin with a bandwidth limit of 10 M cat domains.txt | ./ksubdomain e --stdin -b 10M -# 启用预测模式枚举域名,泛解析过滤,保存为csv +# Enable prediction mode with advanced wildcard filtering; save as CSV ./ksubdomain e -d example.com --predict --wild-filter-mode advanced --oy csv -o output.csv ``` -## ✨ 特性与技巧 +## ✨ Features & Tips -* **带宽自动适配:** 只需使用 `-b` 参数指定你的公网下行带宽 (如 `-b 10m`), KSubdomain 会自动优化发包速率。 -* **测试最大速率:** 运行 `./ksubdomain test` 测试当前环境的最大理论发包速率。 -* **自动网卡检测:** KSubdomain 会自动检测可用网卡。 -* **进度显示:** 实时进度条显示 成功数 / 发送数 / 队列长度 / 接收数 / 失败数 / 已耗时。 -* **参数调优:** 根据网络质量和目标域名数量,调整 `--retry` 和 `--timeout` 参数以获得最佳效果。当 `--retry` 为 -1 时,将无限重试直至所有请求成功或超时。 -* **多种输出格式:** 支持 `txt` (实时输出), `json` (完成后输出), `csv` (完成后输出)。通过 `-o` 指定文件名后缀即可 (如 `result.json`)。 -* **环境变量配置:** - * `KSubdomainConfig`: 指定配置文件的路径。 +* **Automatic Bandwidth Adaptation:** Simply set your public downstream bandwidth with `-b` (e.g., `-b 10m`) and KSubdomain will automatically optimize the packet-sending rate. +* **Test Maximum Rate:** Run `./ksubdomain test` to measure the maximum theoretical packet rate in your current environment. +* **Automatic NIC Detection:** KSubdomain auto-detects available network adapters. +* **Progress Display:** A real-time progress bar shows Success / Sent / Queue Length / Received / Failed / Elapsed Time. +* **Parameter Tuning:** Adjust `--retry` and `--timeout` based on network quality and the number of target domains to get the best results. When `--retry` is -1, retries are unlimited until all requests succeed or time out. +* **Multiple Output Formats:** Supports `txt` (real-time output), `json` (output after completion), and `csv` (output after completion). Specify the format via the `-o` file extension (e.g., `result.json`). +* **Environment Variable Configuration:** + * `KSubdomainConfig`: Specifies the path to a configuration file. -## 💡 参考 +## 💡 References -* 原 KSubdomain 项目: [https://github.com/knownsec/ksubdomain](https://github.com/knownsec/ksubdomain) -* 从 Masscan, Zmap 源码分析到开发实践: [https://paper.seebug.org/1052/](https://paper.seebug.org/1052/) -* KSubdomain 无状态域名爆破工具介绍: [https://paper.seebug.org/1325/](https://paper.seebug.org/1325/) -* KSubdomain 与 massdns 的对比分析: [微信公众号文章链接](https://mp.weixin.qq.com/s?__biz=MzU2NzcwNTY3Mg==&mid=2247484471&idx=1&sn=322d5db2d11363cd2392d7bd29c679f1&chksm=fc986d10cbefe406f4bda22f62a16f08c71f31c241024fc82ecbb8e41c9c7188cfbd71276b81&token=76024279&lang=zh_CN#rd) +* Original KSubdomain project: [https://github.com/knownsec/ksubdomain](https://github.com/knownsec/ksubdomain) +* From Masscan/Zmap Source Analysis to Development Practice: [https://paper.seebug.org/1052/](https://paper.seebug.org/1052/) +* KSubdomain Stateless Subdomain Enumeration Tool Introduction: [https://paper.seebug.org/1325/](https://paper.seebug.org/1325/) +* KSubdomain vs massdns Comparison: [WeChat article](https://mp.weixin.qq.com/s?__biz=MzU2NzcwNTY3Mg==&mid=2247484471&idx=1&sn=322d5db2d11363cd2392d7bd29c679f1&chksm=fc986d10cbefe406f4bda22f62a16f08c71f31c241024fc82ecbb8e41c9c7188cfbd71276b81&token=76024279&lang=zh_CN#rd) diff --git a/run_performance_test.sh b/run_performance_test.sh index 0289efa..e36a86a 100755 --- a/run_performance_test.sh +++ b/run_performance_test.sh @@ -1,18 +1,18 @@ #!/bin/bash # -# KSubdomain 性能基准测试脚本 -# 用途: 运行 10 万域名性能测试,验证 README 中的性能指标 +# KSubdomain Performance Benchmark Script +# Purpose: Run the 100k-domain performance test to validate the metrics described in the README. # -# 参考 README: -# - 字典: 10 万域名 -# - 带宽: 5M -# - 耗时: ~30 秒 -# - 成功: 1397 个 +# README reference: +# - Dictionary: 100k domains +# - Bandwidth: 5 M +# - Time: ~30 sec +# - Success: 1397 domains # set -e -# 颜色输出 +# Color output GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' @@ -20,70 +20,70 @@ BLUE='\033[0;34m' NC='\033[0m' # No Color echo -e "${GREEN}========================================${NC}" -echo -e "${GREEN} KSubdomain 性能基准测试${NC}" -echo -e "${GREEN} 参考 README: 10万域名 ~30秒${NC}" +echo -e "${GREEN} KSubdomain Performance Benchmark${NC}" +echo -e "${GREEN} README reference: 100k domains ~30 sec${NC}" echo -e "${GREEN}========================================${NC}" echo "" -# 检查 root 权限 +# Check for root privileges if [ "$EUID" -ne 0 ]; then - echo -e "${RED}错误: 需要 root 权限运行性能测试${NC}" - echo -e "${YELLOW}请使用: sudo $0${NC}" + echo -e "${RED}Error: root privileges are required to run performance tests${NC}" + echo -e "${YELLOW}Please run: sudo $0${NC}" exit 1 fi -# 检查 Go 环境 +# Check Go environment if ! command -v go &> /dev/null; then - echo -e "${RED}错误: 未找到 Go 环境${NC}" + echo -e "${RED}Error: Go environment not found${NC}" exit 1 fi -echo -e "${YELLOW}Go 版本:${NC} $(go version)" +echo -e "${YELLOW}Go version:${NC} $(go version)" echo "" -# 检查网络 -echo -e "${BLUE}[1/4] 检查网络连接...${NC}" +# Check network connectivity +echo -e "${BLUE}[1/4] Checking network connectivity...${NC}" if ping -c 1 8.8.8.8 &> /dev/null; then - echo -e "${GREEN}✓ 网络连接正常${NC}" + echo -e "${GREEN}✓ Network connectivity OK${NC}" else - echo -e "${YELLOW}⚠ 网络连接可能有问题,测试结果可能不准确${NC}" + echo -e "${YELLOW}⚠ Network connectivity may have issues; test results may be inaccurate${NC}" fi echo "" -# 快速测试 (1000 域名) -echo -e "${BLUE}[2/4] 快速测试 (1000 域名)...${NC}" -echo -e "${YELLOW}目标: < 2 秒${NC}" +# Quick test (1,000 domains) +echo -e "${BLUE}[2/4] Quick test (1,000 domains)...${NC}" +echo -e "${YELLOW}Target: < 2 sec${NC}" go test -tags=performance -bench=Benchmark1k ./test/ -timeout 5m -v 2>&1 | \ grep -E "Benchmark1k|total_seconds|success|domains/sec" | \ tee /tmp/ksubdomain_1k.log if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo -e "${GREEN}✓ 1000 域名测试完成${NC}" + echo -e "${GREEN}✓ 1,000-domain test completed${NC}" else - echo -e "${RED}✗ 1000 域名测试失败${NC}" + echo -e "${RED}✗ 1,000-domain test failed${NC}" fi echo "" -# 中等测试 (10000 域名) -echo -e "${BLUE}[3/4] 中等测试 (10000 域名)...${NC}" -echo -e "${YELLOW}目标: < 5 秒${NC}" +# Medium test (10,000 domains) +echo -e "${BLUE}[3/4] Medium test (10,000 domains)...${NC}" +echo -e "${YELLOW}Target: < 5 sec${NC}" go test -tags=performance -bench=Benchmark10k ./test/ -timeout 5m -v 2>&1 | \ grep -E "Benchmark10k|total_seconds|success|domains/sec" | \ tee /tmp/ksubdomain_10k.log if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo -e "${GREEN}✓ 10000 域名测试完成${NC}" + echo -e "${GREEN}✓ 10,000-domain test completed${NC}" else - echo -e "${RED}✗ 10000 域名测试失败${NC}" + echo -e "${RED}✗ 10,000-domain test failed${NC}" fi echo "" -# 完整测试 (100000 域名) - README 标准 -echo -e "${BLUE}[4/4] 完整测试 (100000 域名) - README 标准${NC}" -echo -e "${YELLOW}目标: < 30 秒 (参考 README)${NC}" -echo -e "${YELLOW}提示: 这将需要几分钟,请耐心等待...${NC}" +# Full test (100,000 domains) — README standard +echo -e "${BLUE}[4/4] Full test (100,000 domains) — README standard${NC}" +echo -e "${YELLOW}Target: < 30 sec (per README)${NC}" +echo -e "${YELLOW}Note: This will take a few minutes, please be patient...${NC}" echo "" go test -tags=performance -bench=Benchmark100k ./test/ -timeout 10m -v 2>&1 | \ @@ -91,67 +91,67 @@ go test -tags=performance -bench=Benchmark100k ./test/ -timeout 10m -v 2>&1 | \ if [ ${PIPESTATUS[0]} -eq 0 ]; then echo "" - echo -e "${GREEN}✓ 100000 域名测试完成${NC}" + echo -e "${GREEN}✓ 100,000-domain test completed${NC}" else echo "" - echo -e "${RED}✗ 100000 域名测试失败${NC}" + echo -e "${RED}✗ 100,000-domain test failed${NC}" fi echo "" -# 提取性能数据 +# Extract performance data echo -e "${GREEN}========================================${NC}" -echo -e "${GREEN} 性能测试总结${NC}" +echo -e "${GREEN} Performance Test Summary${NC}" echo -e "${GREEN}========================================${NC}" echo "" -# 100k 测试结果 +# 100k test results if [ -f /tmp/ksubdomain_100k.log ]; then - echo -e "${BLUE}100,000 域名测试 (README 标准):${NC}" + echo -e "${BLUE}100,000-domain test (README standard):${NC}" TOTAL_SEC=$(grep "total_seconds" /tmp/ksubdomain_100k.log | tail -1 | awk '{print $2}') SUCCESS=$(grep "success_count" /tmp/ksubdomain_100k.log | tail -1 | awk '{print $2}') RATE_PCT=$(grep "success_rate" /tmp/ksubdomain_100k.log | tail -1 | awk '{print $2}') SPEED=$(grep "domains/sec" /tmp/ksubdomain_100k.log | tail -1 | awk '{print $2}') - echo -e " - 总耗时: ${YELLOW}${TOTAL_SEC} 秒${NC}" - echo -e " - 成功数: ${YELLOW}${SUCCESS}${NC}" - echo -e " - 成功率: ${YELLOW}${RATE_PCT}${NC}" - echo -e " - 扫描速率: ${YELLOW}${SPEED} domains/s${NC}" + echo -e " - Total time: ${YELLOW}${TOTAL_SEC} sec${NC}" + echo -e " - Success count: ${YELLOW}${SUCCESS}${NC}" + echo -e " - Success rate: ${YELLOW}${RATE_PCT}${NC}" + echo -e " - Throughput: ${YELLOW}${SPEED} domains/s${NC}" echo "" - # 性能评估 + # Performance rating if (( $(echo "$TOTAL_SEC <= 30" | bc -l) )); then - echo -e "${GREEN}✅ 性能评估: 优秀 (达到 README 标准: ≤30秒)${NC}" + echo -e "${GREEN}✅ Rating: Excellent (meets README standard: ≤30 sec)${NC}" elif (( $(echo "$TOTAL_SEC <= 40" | bc -l) )); then - echo -e "${YELLOW}✓ 性能评估: 良好 (30-40秒)${NC}" + echo -e "${YELLOW}✓ Rating: Good (30–40 sec)${NC}" elif (( $(echo "$TOTAL_SEC <= 60" | bc -l) )); then - echo -e "${YELLOW}⚠ 性能评估: 一般 (40-60秒,可能需要优化)${NC}" + echo -e "${YELLOW}⚠ Rating: Fair (40–60 sec, optimization may be needed)${NC}" else - echo -e "${RED}❌ 性能评估: 较慢 (>60秒,需要检查网络和配置)${NC}" + echo -e "${RED}❌ Rating: Slow (>60 sec, please check network and configuration)${NC}" fi echo "" - # README 对比 - echo -e "${BLUE}与 README 对比:${NC}" - echo -e " - README 标准: ~30 秒, 1397 个成功" - echo -e " - 本次测试: ${TOTAL_SEC} 秒, ${SUCCESS} 个成功" + # Comparison with README + echo -e "${BLUE}Comparison with README:${NC}" + echo -e " - README standard: ~30 sec, 1397 successes" + echo -e " - This run: ${TOTAL_SEC} sec, ${SUCCESS} successes" echo "" - # 与其他工具对比 - echo -e "${BLUE}与其他工具对比 (README 数据):${NC}" - echo -e " - massdns: ~3分29秒 (209秒) → ksubdomain 快 ${YELLOW}$(echo "scale=1; 209/$TOTAL_SEC" | bc)x${NC}" - echo -e " - dnsx: ~5分26秒 (326秒) → ksubdomain 快 ${YELLOW}$(echo "scale=1; 326/$TOTAL_SEC" | bc)x${NC}" + # Comparison with other tools (README data) + echo -e "${BLUE}Comparison with other tools (README data):${NC}" + echo -e " - massdns: ~3 min 29 sec (209 sec) → ksubdomain is ${YELLOW}$(echo "scale=1; 209/$TOTAL_SEC" | bc)×${NC} faster" + echo -e " - dnsx: ~5 min 26 sec (326 sec) → ksubdomain is ${YELLOW}$(echo "scale=1; 326/$TOTAL_SEC" | bc)×${NC} faster" fi echo "" -echo -e "${BLUE}详细日志:${NC}" +echo -e "${BLUE}Detailed logs:${NC}" echo -e " - /tmp/ksubdomain_1k.log" echo -e " - /tmp/ksubdomain_10k.log" echo -e " - /tmp/ksubdomain_100k.log" echo "" -echo -e "${GREEN}性能测试完成! 🎉${NC}" +echo -e "${GREEN}Performance tests complete! 🎉${NC}" echo "" -echo -e "${YELLOW}提示:${NC}" -echo -e " - 如果性能不达标,请检查网络带宽和 DNS 服务器" -echo -e " - 参考: test/PERFORMANCE_TEST.md 了解性能调优" +echo -e "${YELLOW}Tips:${NC}" +echo -e " - If performance is below target, check network bandwidth and DNS servers." +echo -e " - See test/PERFORMANCE_TEST.md for performance tuning guidance." diff --git a/run_tests.sh b/run_tests.sh index 797205b..6f3e3aa 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,111 +1,111 @@ #!/bin/bash # -# KSubdomain 测试运行脚本 -# 用途: 运行所有测试并生成报告 +# KSubdomain Test Runner +# Purpose: Run all tests and generate a report # set -e -# 颜色输出 +# Color output GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' NC='\033[0m' # No Color echo -e "${GREEN}========================================${NC}" -echo -e "${GREEN} KSubdomain 测试套件${NC}" +echo -e "${GREEN} KSubdomain Test Suite${NC}" echo -e "${GREEN}========================================${NC}" echo "" -# 检查 Go 环境 +# Check Go environment if ! command -v go &> /dev/null; then - echo -e "${RED}错误: 未找到 Go 环境${NC}" + echo -e "${RED}Error: Go environment not found${NC}" exit 1 fi -echo -e "${YELLOW}Go 版本:${NC} $(go version)" +echo -e "${YELLOW}Go version:${NC} $(go version)" echo "" -# 创建测试结果目录 +# Create test results directory mkdir -p test_results -# 1. 单元测试 -echo -e "${GREEN}[1/5] 运行单元测试...${NC}" +# 1. Unit tests +echo -e "${GREEN}[1/5] Running unit tests...${NC}" go test -v -cover -coverprofile=test_results/coverage.out ./pkg/... 2>&1 | tee test_results/unit_test.log if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo -e "${GREEN}✓ 单元测试通过${NC}" + echo -e "${GREEN}✓ Unit tests passed${NC}" else - echo -e "${RED}✗ 单元测试失败${NC}" + echo -e "${RED}✗ Unit tests failed${NC}" exit 1 fi echo "" -# 2. 覆盖率报告 -echo -e "${GREEN}[2/5] 生成覆盖率报告...${NC}" +# 2. Coverage report +echo -e "${GREEN}[2/5] Generating coverage report...${NC}" go tool cover -html=test_results/coverage.out -o test_results/coverage.html COVERAGE=$(go tool cover -func=test_results/coverage.out | grep total | awk '{print $3}') -echo -e "${YELLOW}总覆盖率:${NC} $COVERAGE" +echo -e "${YELLOW}Total coverage:${NC} $COVERAGE" -# 检查覆盖率是否达标 +# Check whether coverage meets the threshold COVERAGE_NUM=$(echo $COVERAGE | sed 's/%//') if (( $(echo "$COVERAGE_NUM >= 60" | bc -l) )); then - echo -e "${GREEN}✓ 覆盖率达标 (> 60%)${NC}" + echo -e "${GREEN}✓ Coverage meets threshold (> 60%)${NC}" else - echo -e "${YELLOW}⚠ 覆盖率偏低 (< 60%)${NC}" + echo -e "${YELLOW}⚠ Coverage is low (< 60%)${NC}" fi echo "" -# 3. 性能测试 -echo -e "${GREEN}[3/5] 运行性能测试...${NC}" +# 3. Benchmark tests +echo -e "${GREEN}[3/5] Running benchmark tests...${NC}" go test -bench=. -benchmem ./pkg/... 2>&1 | tee test_results/benchmark.log if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo -e "${GREEN}✓ 性能测试完成${NC}" + echo -e "${GREEN}✓ Benchmark tests completed${NC}" else - echo -e "${YELLOW}⚠ 性能测试部分失败${NC}" + echo -e "${YELLOW}⚠ Some benchmark tests failed${NC}" fi echo "" -# 4. 竞争检测 -echo -e "${GREEN}[4/5] 运行数据竞争检测...${NC}" +# 4. Race condition detection +echo -e "${GREEN}[4/5] Running data race detection...${NC}" go test -race ./pkg/runner/statusdb/ 2>&1 | tee test_results/race.log if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo -e "${GREEN}✓ 无数据竞争${NC}" + echo -e "${GREEN}✓ No data races detected${NC}" else - echo -e "${RED}✗ 检测到数据竞争${NC}" + echo -e "${RED}✗ Data race detected${NC}" exit 1 fi echo "" -# 5. 代码静态检查 (可选) -echo -e "${GREEN}[5/5] 代码静态检查...${NC}" +# 5. Static analysis (optional) +echo -e "${GREEN}[5/5] Static code analysis...${NC}" if command -v golangci-lint &> /dev/null; then golangci-lint run ./... 2>&1 | tee test_results/lint.log if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo -e "${GREEN}✓ 代码检查通过${NC}" + echo -e "${GREEN}✓ Static analysis passed${NC}" else - echo -e "${YELLOW}⚠ 代码检查发现问题${NC}" + echo -e "${YELLOW}⚠ Static analysis found issues${NC}" fi else - echo -e "${YELLOW}⚠ 未安装 golangci-lint,跳过静态检查${NC}" + echo -e "${YELLOW}⚠ golangci-lint not installed; skipping static analysis${NC}" fi echo "" -# 生成测试报告 +# Generate test report echo -e "${GREEN}========================================${NC}" -echo -e "${GREEN} 测试总结${NC}" +echo -e "${GREEN} Test Summary${NC}" echo -e "${GREEN}========================================${NC}" -echo -e "${YELLOW}单元测试:${NC} 通过 ✓" -echo -e "${YELLOW}覆盖率:${NC} $COVERAGE" -echo -e "${YELLOW}数据竞争:${NC} 无 ✓" -echo -e "${YELLOW}报告位置:${NC} test_results/" +echo -e "${YELLOW}Unit tests:${NC} Passed ✓" +echo -e "${YELLOW}Coverage:${NC} $COVERAGE" +echo -e "${YELLOW}Data races:${NC} None ✓" +echo -e "${YELLOW}Report location:${NC} test_results/" echo "" -echo -e "${GREEN}测试结果:${NC}" -echo -e " - coverage.html: 覆盖率可视化报告" -echo -e " - unit_test.log: 单元测试详细日志" -echo -e " - benchmark.log: 性能测试结果" -echo -e " - race.log: 竞争检测日志" +echo -e "${GREEN}Test results:${NC}" +echo -e " - coverage.html: Visual coverage report" +echo -e " - unit_test.log: Unit test detailed log" +echo -e " - benchmark.log: Benchmark test results" +echo -e " - race.log: Race detection log" echo "" -echo -e "${GREEN}所有测试完成! 🎉${NC}" +echo -e "${GREEN}All tests complete! 🎉${NC}" diff --git a/test/PERFORMANCE_TEST.md b/test/PERFORMANCE_TEST.md index 4e71b41..759341d 100644 --- a/test/PERFORMANCE_TEST.md +++ b/test/PERFORMANCE_TEST.md @@ -1,177 +1,177 @@ -# 性能基准测试 +# Performance Benchmark Tests -## 📊 测试目标 +## 📊 Test Objectives -参考 README 中的性能对比,验证 ksubdomain 的扫描性能: +Validate ksubdomain's scanning performance as described in the README: -| 字典大小 | 目标耗时 | 参考 README | -|---------|---------|------------| -| 1,000 域名 | < 2 秒 | - | -| 10,000 域名 | < 5 秒 | - | -| 100,000 域名 | **< 30 秒** | **README 标准** | +| Dictionary Size | Target Time | Reference (README) | +|----------------|-------------|-------------------| +| 1,000 domains | < 2 sec | - | +| 10,000 domains | < 5 sec | - | +| 100,000 domains | **< 30 sec** | **README standard** | --- -## 🧪 测试环境 +## 🧪 Test Environment -### README 参考配置 -- **CPU**: 4 核 -- **带宽**: 5M -- **字典**: 10 万域名 (d2.txt) -- **DNS**: 自定义 DNS 列表 (dns.txt) -- **重试**: 3 次 -- **结果**: ~30 秒, 1397 个成功 +### README Reference Configuration +- **CPU**: 4 cores +- **Bandwidth**: 5 M +- **Dictionary**: 100k domains (d2.txt) +- **DNS**: Custom DNS list (dns.txt) +- **Retry**: 3 times +- **Result**: ~30 sec, 1397 successes -### 测试要求 -- 需要 **root 权限** (访问网卡) -- 需要 **网络连接** (DNS 查询) -- 需要 **libpcap** -- 建议 **4 核 CPU + 5M 带宽** +### Requirements +- **Root privileges** (for NIC access) +- **Network connectivity** (for DNS queries) +- **libpcap** installed +- Recommended: **4-core CPU + 5 M bandwidth** --- -## 🚀 运行测试 +## 🚀 Running the Tests -### 快速测试 (1000 域名) +### Quick Test (1,000 domains) ```bash -# 编译并运行 +# Build and run go test -tags=performance -bench=Benchmark1k ./test/ -timeout 10m -# 预期结果: +# Expected output: # Benchmark1kDomains 1 1.5s total_seconds:1.5 # 950 success_count # 95% success_rate_% # 666 domains/sec ``` -### 中等测试 (10000 域名) +### Medium Test (10,000 domains) ```bash go test -tags=performance -bench=Benchmark10k ./test/ -timeout 10m -# 预期结果: +# Expected output: # Benchmark10kDomains 1 4.8s total_seconds:4.8 # 9500 success_count # 95% success_rate_% # 2083 domains/sec ``` -### 完整测试 (100000 域名) - README 标准 +### Full Test (100,000 domains) — README Standard ```bash -# 需要 sudo (访问网卡) +# Requires sudo (NIC access) sudo go test -tags=performance -bench=Benchmark100k ./test/ -timeout 10m -v -# 预期结果 (参考 README): +# Expected output (per README): # Benchmark100kDomains 1 28.5s total_seconds:28.5 # 95000 success_count # 95% success_rate_% # 3508 domains/sec # -# ✅ 性能优秀: 10万域名仅耗时 28.5 秒 (达到 README 标准) +# ✅ Excellent performance: 100k domains in 28.5 sec (meets README standard) ``` -### 运行所有性能测试 +### Run All Performance Tests ```bash sudo go test -tags=performance -bench=. ./test/ -timeout 15m -v ``` --- -## 📈 性能指标说明 +## 📈 Performance Metrics -### 报告指标 +### Reported Metrics -每个测试会报告以下指标: +Each test reports the following metrics: ``` -total_seconds 总耗时 (秒) -success_count 成功解析的域名数 -success_rate_% 成功率 (百分比) -domains/sec 扫描速率 (域名/秒) +total_seconds Total elapsed time (seconds) +success_count Number of successfully resolved domains +success_rate_% Success rate (percentage) +domains/sec Scan throughput (domains per second) ``` -### 日志输出 +### Log Output -测试过程中会实时显示: +During the test, real-time progress is displayed: ``` -进度: 1000/100000 (1.0%), 速率: 3500 domains/s, 耗时: 0s -进度: 2000/100000 (2.0%), 速率: 3600 domains/s, 耗时: 0s +Progress: 1000/100000 (1.0%), rate: 3500 domains/s, elapsed: 0s +Progress: 2000/100000 (2.0%), rate: 3600 domains/s, elapsed: 0s ... -最终结果: 95000/100000, 耗时: 28.5s +Final result: 95000/100000, elapsed: 28.5s ``` --- -## 🎯 性能基准对比 +## 🎯 Performance Benchmark Comparison -### README 标准 (100,000 域名) +### README Standard (100,000 domains) -| 工具 | 耗时 | 速率 | 成功数 | 倍数 | -|------|------|------|--------|------| -| **KSubdomain** | **~30 秒** | ~3333/s | 1397 | **1x** | -| massdns | ~3 分 29 秒 | ~478/s | 1396 | **7x 慢** | -| dnsx | ~5 分 26 秒 | ~307/s | 1396 | **10x 慢** | +| Tool | Time | Rate | Success | Ratio | +|------|------|------|---------|-------| +| **KSubdomain** | **~30 sec** | ~3333/s | 1397 | **1×** | +| massdns | ~3 min 29 sec | ~478/s | 1396 | **7× slower** | +| dnsx | ~5 min 26 sec | ~307/s | 1396 | **10× slower** | -### 我们的测试目标 +### Our Test Targets -基于 README 标准,我们的目标: +Based on the README standard: ``` -✅ 优秀: < 30 秒 (达到 README 标准) -✓ 良好: 30-40 秒 (可接受范围) -⚠️ 警告: 40-60 秒 (需要优化) -❌ 失败: > 60 秒 (性能问题) +✅ Excellent: < 30 sec (meets README standard) +✓ Good: 30–40 sec (acceptable range) +⚠️ Warning: 40–60 sec (optimization needed) +❌ Fail: > 60 sec (performance issue) ``` --- -## 🔧 性能调优建议 +## 🔧 Performance Tuning Tips -### 如果测试较慢 +### If the Test Is Slow -#### 1. 检查带宽限制 +#### 1. Check Bandwidth Limit ```bash -# 测试中使用 5M 带宽 -# 可以尝试调整 (在 performance_benchmark_test.go 中) -Rate: options.Band2Rate("10m") # 提高到 10M +# Tests use 5 M bandwidth by default +# Try increasing it (in performance_benchmark_test.go) +Rate: options.Band2Rate("10m") # Raise to 10 M ``` -#### 2. 检查 DNS 服务器 +#### 2. Check DNS Servers ```bash -# 使用更快的 DNS 服务器 +# Use faster DNS servers Resolvers: []string{"8.8.8.8", "1.1.1.1"} ``` -#### 3. 增加重试次数 (trade-off) +#### 3. Increase Retry Count (trade-off) ```bash -# 更多重试 = 更高成功率,但更慢 -Retry: 5 # 从 3 增加到 5 +# More retries = higher success rate, but slower +Retry: 5 # Increase from 3 to 5 ``` -#### 4. 调整超时时间 +#### 4. Adjust Timeout ```bash -# 更短超时 = 更快,但可能漏掉慢响应 -TimeOut: 3 # 从 6 减少到 3 +# Shorter timeout = faster, but may miss slow responses +TimeOut: 3 # Decrease from 6 to 3 ``` --- -## 📊 性能数据收集 +## 📊 Collecting Performance Data -### 生成性能报告 +### Generate a Performance Report ```bash -# 运行测试并保存结果 +# Run the test and save output sudo go test -tags=performance -bench=Benchmark100k ./test/ \ -timeout 10m -v 2>&1 | tee performance_report.txt -# 提取关键指标 +# Extract key metrics grep "total_seconds\|success_count\|success_rate\|domains/sec" performance_report.txt ``` -### 多次运行取平均 +### Average Over Multiple Runs ```bash -# 运行 3 次取平均值 +# Run 3 times and average for i in {1..3}; do echo "=== Run $i ===" sudo go test -tags=performance -bench=Benchmark100k ./test/ \ @@ -181,120 +181,120 @@ done --- -## 🧩 测试场景 +## 🧩 Test Scenarios -### 场景 1: 标准测试 (README 配置) +### Scenario 1: Standard Test (README configuration) ``` -字典: 100,000 域名 -带宽: 5M -重试: 3 次 -超时: 6 秒 -目标: < 30 秒 +Dictionary: 100,000 domains +Bandwidth: 5 M +Retry: 3 times +Timeout: 6 sec +Target: < 30 sec ``` -### 场景 2: 高速测试 (10M 带宽) +### Scenario 2: High-Speed Test (10 M bandwidth) ``` -字典: 100,000 域名 -带宽: 10M -重试: 3 次 -超时: 6 秒 -目标: < 20 秒 +Dictionary: 100,000 domains +Bandwidth: 10 M +Retry: 3 times +Timeout: 6 sec +Target: < 20 sec ``` -### 场景 3: 保守测试 (高成功率) +### Scenario 3: Conservative Test (high success rate) ``` -字典: 100,000 域名 -带宽: 5M -重试: 10 次 -超时: 10 秒 -目标: < 60 秒, 成功率 > 98% +Dictionary: 100,000 domains +Bandwidth: 5 M +Retry: 10 times +Timeout: 10 sec +Target: < 60 sec, success rate > 98% ``` --- -## 📝 测试清单 +## 📝 Pre-Test Checklist -运行性能测试前确认: +Before running performance tests, confirm: -- [ ] 有 root 权限 -- [ ] 网络连接正常 -- [ ] libpcap 已安装 -- [ ] 网卡正常工作 -- [ ] 至少 4 核 CPU -- [ ] 至少 5M 带宽 -- [ ] 关闭其他网络密集型程序 +- [ ] Root privileges available +- [ ] Network connectivity is stable +- [ ] libpcap is installed +- [ ] Network adapter is functioning +- [ ] At least 4-core CPU +- [ ] At least 5 M bandwidth +- [ ] Other network-intensive programs are closed --- -## 🎯 预期结果 +## 🎯 Expected Results -### 1000 域名 +### 1,000 domains ``` -总耗时: ~1.5 秒 -成功数: ~950 -成功率: ~95% -速率: ~666 domains/s +Total time: ~1.5 sec +Success count: ~950 +Success rate: ~95% +Throughput: ~666 domains/s ``` -### 10000 域名 +### 10,000 domains ``` -总耗时: ~5 秒 -成功数: ~9500 -成功率: ~95% -速率: ~2000 domains/s +Total time: ~5 sec +Success count: ~9500 +Success rate: ~95% +Throughput: ~2000 domains/s ``` -### 100000 域名 (README 标准) +### 100,000 domains (README standard) ``` -总耗时: ~30 秒 ✅ -成功数: ~95000 -成功率: ~95% -速率: ~3333 domains/s +Total time: ~30 sec ✅ +Success count: ~95000 +Success rate: ~95% +Throughput: ~3333 domains/s ``` --- -## 🐛 常见问题 +## 🐛 Common Issues -### 问题 1: 权限错误 +### Issue 1: Permission Error ``` -错误: pcap初始化失败 -解决: sudo go test -tags=performance ... +Error: pcap initialization failed +Fix: sudo go test -tags=performance ... ``` -### 问题 2: 网卡未找到 +### Issue 2: Network Adapter Not Found ``` -错误: No such device -解决: ./ksubdomain test # 检查网卡 - --eth <网卡名> # 手动指定 +Error: No such device +Fix: ./ksubdomain test # Check available adapters + --eth # Specify manually ``` -### 问题 3: 测试超时 +### Issue 3: Test Timeout ``` -错误: test timed out after 10m -解决: -timeout 15m # 增加超时时间 +Error: test timed out after 10m +Fix: -timeout 15m # Increase timeout ``` -### 问题 4: 成功率低 +### Issue 4: Low Success Rate ``` -成功率: < 80% -原因: 网络不稳定 / DNS 服务器慢 -解决: 增加重试次数 / 更换 DNS +Success rate: < 80% +Cause: Unstable network / slow DNS servers +Fix: Increase retry count / switch DNS servers ``` --- -## 📚 参考 +## 📚 References -- README 性能对比: 10万域名 ~30秒 -- massdns 对比: 7 倍性能差距 -- dnsx 对比: 10 倍性能差距 +- README performance comparison: 100k domains in ~30 sec +- vs massdns: 7× performance gap +- vs dnsx: 10× performance gap --- -**性能就是王道! ⚡** +**Performance is everything! ⚡** -运行测试验证 ksubdomain 的极速性能: +Run the test to validate ksubdomain's blazing speed: ```bash sudo go test -tags=performance -bench=Benchmark100k ./test/ -timeout 10m -v ``` diff --git a/test/accuracy_test.sh b/test/accuracy_test.sh index 6dfa053..6b020e9 100755 --- a/test/accuracy_test.sh +++ b/test/accuracy_test.sh @@ -1,15 +1,15 @@ #!/bin/bash -# ksubdomain 准确性测试脚本 -# 用于测试优化后的版本结果与原始版本的一致性 +# ksubdomain accuracy test script +# Tests that the optimized version produces results consistent with the original version -# 颜色定义 +# Color definitions GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m' -NC='\033[0m' # 无颜色 +NC='\033[0m' # No color -# 测试配置 +# Test configuration TEST_DOMAIN="example.com" TEST_DICT="test/accuracy_dict.txt" RESOLVERS="test/resolvers.txt" @@ -19,76 +19,76 @@ ORIG_OUTPUT="test/results/orig_accuracy.txt" NEW_OUTPUT="test/results/new_accuracy.txt" DIFF_OUTPUT="test/results/diff.txt" -# 确保测试目录存在 +# Ensure the test results directory exists mkdir -p "test/results" -# 检查原始版本是否存在 +# Check whether the original binary exists if [ ! -f "$ORIG_BIN" ]; then - echo -e "${RED}错误: 找不到原始版本二进制文件 $ORIG_BIN${NC}" - echo "请将原始的ksubdomain放到test目录,重命名为ksubdomain_orig" + echo -e "${RED}Error: original binary not found at $ORIG_BIN${NC}" + echo "Please place the original ksubdomain binary in the test/ directory and rename it to ksubdomain_orig" exit 1 fi -# 创建DNS解析器文件(如果不存在) +# Create the DNS resolver file if it does not exist if [ ! -f "$RESOLVERS" ]; then - echo "创建DNS解析器文件..." + echo "Creating DNS resolver file..." echo "8.8.8.8" > "$RESOLVERS" echo "8.8.4.4" >> "$RESOLVERS" echo "1.1.1.1" >> "$RESOLVERS" echo "114.114.114.114" >> "$RESOLVERS" fi -# 创建测试字典(如果不存在) +# Create the test dictionary if it does not exist if [ ! -f "$TEST_DICT" ]; then - echo "创建测试字典..." - # 常见子域名,可能存在的 + echo "Creating test dictionary..." + # Common subdomains that are likely to exist echo "www.$TEST_DOMAIN" > "$TEST_DICT" echo "mail.$TEST_DOMAIN" >> "$TEST_DICT" echo "api.$TEST_DOMAIN" >> "$TEST_DICT" echo "blog.$TEST_DOMAIN" >> "$TEST_DICT" echo "docs.$TEST_DOMAIN" >> "$TEST_DICT" - # 随机生成的子域名 + # Randomly generated subdomains for i in {1..95}; do echo "test$i.$TEST_DOMAIN" >> "$TEST_DICT" done fi echo "========================================" -echo " KSubdomain 准确性测试" +echo " KSubdomain Accuracy Test" echo "========================================" -# 运行原始版本 -echo -e "${YELLOW}运行原始版本...${NC}" +# Run the original version +echo -e "${YELLOW}Running original version...${NC}" $ORIG_BIN v -f "$TEST_DICT" -r "$RESOLVERS" -o "$ORIG_OUTPUT" -b 5m --retry 3 --timeout 6 -# 运行优化版本 -echo -e "${YELLOW}运行优化版本...${NC}" +# Run the optimized version +echo -e "${YELLOW}Running optimized version...${NC}" $NEW_BIN v -f "$TEST_DICT" -r "$RESOLVERS" -o "$NEW_OUTPUT" -b 5m --retry 3 --timeout 6 -# 比较结果 -echo -e "${YELLOW}比较结果...${NC}" -# 对结果文件进行排序 +# Compare results +echo -e "${YELLOW}Comparing results...${NC}" +# Sort both output files sort "$ORIG_OUTPUT" > "$ORIG_OUTPUT.sorted" sort "$NEW_OUTPUT" > "$NEW_OUTPUT.sorted" -# 使用diff比较排序后的结果 +# Diff the sorted results diff "$ORIG_OUTPUT.sorted" "$NEW_OUTPUT.sorted" > "$DIFF_OUTPUT" if [ -s "$DIFF_OUTPUT" ]; then DIFF_COUNT=$(wc -l < "$DIFF_OUTPUT") - echo -e "${RED}发现差异! $DIFF_COUNT 行不同${NC}" - echo "差异详情保存在 $DIFF_OUTPUT" - echo "以下是差异内容:" + echo -e "${RED}Differences found! $DIFF_COUNT lines differ${NC}" + echo "Diff details saved to $DIFF_OUTPUT" + echo "Diff contents:" cat "$DIFF_OUTPUT" else - echo -e "${GREEN}测试通过! 两个版本的结果完全一致${NC}" - # 获取找到的子域名数量 + echo -e "${GREEN}Test passed! Both versions produce identical results${NC}" + # Report the number of subdomains found FOUND_COUNT=$(wc -l < "$NEW_OUTPUT") - echo "找到了 $FOUND_COUNT 个子域名" - rm "$DIFF_OUTPUT" # 删除空的差异文件 + echo "Found $FOUND_COUNT subdomains" + rm "$DIFF_OUTPUT" # Remove the empty diff file fi -# 清理临时文件 +# Clean up temporary files rm "$ORIG_OUTPUT.sorted" "$NEW_OUTPUT.sorted" -echo "测试完成!" \ No newline at end of file +echo "Test complete!" diff --git a/test/benchmark.sh b/test/benchmark.sh index e28072b..f0e8213 100755 --- a/test/benchmark.sh +++ b/test/benchmark.sh @@ -1,52 +1,52 @@ #!/bin/bash -# ksubdomain 性能测试脚本 -# 用于测试优化后的ksubdomain性能 +# ksubdomain benchmark script +# Tests the performance of the optimized ksubdomain -# 颜色定义 +# Color definitions GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m' -NC='\033[0m' # 无颜色 +NC='\033[0m' # No color -# 测试配置 +# Test configuration TEST_DOMAIN="baidu.com" -SMALL_DICT="test/dict_small.txt" # 1000个子域名 -MEDIUM_DICT="test/dict_medium.txt" # 10000个子域名 -LARGE_DICT="test/dict_large.txt" # 100000个子域名 +SMALL_DICT="test/dict_small.txt" # 1,000 subdomains +MEDIUM_DICT="test/dict_medium.txt" # 10,000 subdomains +LARGE_DICT="test/dict_large.txt" # 100,000 subdomains RESOLVERS="test/resolvers.txt" OUTPUT_DIR="test/results" ORIG_BIN="test/ksubdomain_orig" NEW_BIN="./ksubdomain" -# 确保测试目录存在 +# Ensure test directories exist mkdir -p "$OUTPUT_DIR" mkdir -p "test" -# 检查原始版本是否存在 +# Check whether the original binary exists if [ ! -f "$ORIG_BIN" ]; then - echo -e "${YELLOW}找不到原始版本二进制文件,跳过比较测试${NC}" + echo -e "${YELLOW}Original binary not found; skipping comparison tests${NC}" SKIP_COMPARE=true else SKIP_COMPARE=false fi -# 创建DNS解析器文件(如果不存在) +# Create the DNS resolver file if it does not exist if [ ! -f "$RESOLVERS" ]; then - echo "创建DNS解析器文件..." + echo "Creating DNS resolver file..." echo "8.8.8.8" > "$RESOLVERS" echo "8.8.4.4" >> "$RESOLVERS" echo "1.1.1.1" >> "$RESOLVERS" echo "114.114.114.114" >> "$RESOLVERS" fi -# 创建测试字典(如果不存在) +# Create a test dictionary if it does not exist create_test_dict() { local file=$1 local size=$2 if [ ! -f "$file" ]; then - echo "创建测试字典 $file ($size 条记录)..." + echo "Creating test dictionary $file ($size entries)..." for i in $(seq 1 $size); do echo "sub$i.$TEST_DOMAIN" >> "$file" done @@ -57,7 +57,7 @@ create_test_dict "$SMALL_DICT" 1000 create_test_dict "$MEDIUM_DICT" 10000 create_test_dict "$LARGE_DICT" 100000 -# 运行单次测试 +# Run a single test run_test() { local bin=$1 local mode=$2 @@ -66,14 +66,14 @@ run_test() { local extra_params=$5 local dict_size=$(wc -l < "$dict") - echo "测试 $bin $mode 模式,字典大小: $dict_size $extra_params" + echo "Testing $bin in $mode mode, dictionary size: $dict_size $extra_params" - # 清理输出文件 + # Remove old output file if [ -f "$output" ]; then rm "$output" fi - # 执行测试并计时 + # Run and time the test local start_time=$(date +%s.%N) if [ "$mode" == "verify" ]; then @@ -86,57 +86,57 @@ run_test() { local elapsed=$(echo "$end_time - $start_time" | bc) local found=$(wc -l < "$output") - echo -e "${GREEN}完成!用时: $elapsed 秒,发现: $found 个子域名${NC}" + echo -e "${GREEN}Done! Time: $elapsed sec, Found: $found subdomains${NC}" echo "" - # 返回结果 + # Return results echo "$elapsed,$found" } -# 执行所有测试 +# Run all tests for a given binary run_all_tests() { local bin=$1 local prefix=$2 - # 小字典,验证模式 + # Small dictionary, verify mode small_verify=$(run_test "$bin" "verify" "$SMALL_DICT" "${OUTPUT_DIR}/${prefix}_small_verify.txt" "-b 5m") - # 小字典,枚举模式 + # Small dictionary, enum mode small_enum=$(run_test "$bin" "enum" "$SMALL_DICT" "${OUTPUT_DIR}/${prefix}_small_enum.txt" "-b 5m") - # 中等字典,验证模式 + # Medium dictionary, verify mode medium_verify=$(run_test "$bin" "verify" "$MEDIUM_DICT" "${OUTPUT_DIR}/${prefix}_medium_verify.txt" "-b 5m") - # 大字典,验证模式 + # Large dictionary, verify mode large_verify=$(run_test "$bin" "verify" "$LARGE_DICT" "${OUTPUT_DIR}/${prefix}_large_verify.txt" "-b 5m") - # 测试不同超时和重试参数 + # Test with different timeout and retry parameters retry_test=$(run_test "$bin" "verify" "$MEDIUM_DICT" "${OUTPUT_DIR}/${prefix}_retry_test.txt" "-b 5m --retry 5 --timeout 8") - # 返回所有结果 + # Return all results echo "$small_verify|$small_enum|$medium_verify|$large_verify|$retry_test" } -# 主函数 +# Main function main() { echo "========================================" - echo " KSubdomain 性能测试" + echo " KSubdomain Performance Test" echo "========================================" - # 测试新版本 - echo -e "${YELLOW}测试优化后的版本...${NC}" + # Test the new (optimized) version + echo -e "${YELLOW}Testing optimized version...${NC}" new_results=$(run_all_tests "$NEW_BIN" "new") - # 如果原始版本存在,则测试比较 + # If the original binary is available, run a comparison if [ "$SKIP_COMPARE" = false ]; then - echo -e "${YELLOW}测试原始版本...${NC}" + echo -e "${YELLOW}Testing original version...${NC}" orig_results=$(run_all_tests "$ORIG_BIN" "orig") - # 解析结果 + # Parse results IFS='|' read -r new_small_verify new_small_enum new_medium_verify new_large_verify new_retry_test <<< "$new_results" IFS='|' read -r orig_small_verify orig_small_enum orig_medium_verify orig_large_verify orig_retry_test <<< "$orig_results" - # 提取时间和发现数量 + # Extract time and found counts IFS=',' read -r new_small_verify_time new_small_verify_found <<< "$new_small_verify" IFS=',' read -r orig_small_verify_time orig_small_verify_found <<< "$orig_small_verify" @@ -152,78 +152,78 @@ main() { IFS=',' read -r new_retry_test_time new_retry_test_found <<< "$new_retry_test" IFS=',' read -r orig_retry_test_time orig_retry_test_found <<< "$orig_retry_test" - # 计算性能提升百分比 + # Calculate speed-up percentages small_verify_speedup=$(echo "scale=2; ($orig_small_verify_time - $new_small_verify_time) / $orig_small_verify_time * 100" | bc) small_enum_speedup=$(echo "scale=2; ($orig_small_enum_time - $new_small_enum_time) / $orig_small_enum_time * 100" | bc) medium_verify_speedup=$(echo "scale=2; ($orig_medium_verify_time - $new_medium_verify_time) / $orig_medium_verify_time * 100" | bc) large_verify_speedup=$(echo "scale=2; ($orig_large_verify_time - $new_large_verify_time) / $orig_large_verify_time * 100" | bc) retry_test_speedup=$(echo "scale=2; ($orig_retry_test_time - $new_retry_test_time) / $orig_retry_test_time * 100" | bc) - # 输出比较结果 + # Print comparison results echo "" echo "========================================" - echo " 性能比较结果" + echo " Performance Comparison Results" echo "========================================" - echo "小字典验证模式:" - echo " 原始版本: $orig_small_verify_time 秒, 发现: $orig_small_verify_found 个域名" - echo " 优化版本: $new_small_verify_time 秒, 发现: $new_small_verify_found 个域名" - echo -e " 速度提升: ${GREEN}$small_verify_speedup%${NC}" + echo "Small dictionary, verify mode:" + echo " Original: $orig_small_verify_time sec, found: $orig_small_verify_found domains" + echo " Optimized: $new_small_verify_time sec, found: $new_small_verify_found domains" + echo -e " Speed-up: ${GREEN}$small_verify_speedup%${NC}" echo "" - echo "小字典枚举模式:" - echo " 原始版本: $orig_small_enum_time 秒, 发现: $orig_small_enum_found 个域名" - echo " 优化版本: $new_small_enum_time 秒, 发现: $new_small_enum_found 个域名" - echo -e " 速度提升: ${GREEN}$small_enum_speedup%${NC}" + echo "Small dictionary, enum mode:" + echo " Original: $orig_small_enum_time sec, found: $orig_small_enum_found domains" + echo " Optimized: $new_small_enum_time sec, found: $new_small_enum_found domains" + echo -e " Speed-up: ${GREEN}$small_enum_speedup%${NC}" echo "" - echo "中等字典验证模式:" - echo " 原始版本: $orig_medium_verify_time 秒, 发现: $orig_medium_verify_found 个域名" - echo " 优化版本: $new_medium_verify_time 秒, 发现: $new_medium_verify_found 个域名" - echo -e " 速度提升: ${GREEN}$medium_verify_speedup%${NC}" + echo "Medium dictionary, verify mode:" + echo " Original: $orig_medium_verify_time sec, found: $orig_medium_verify_found domains" + echo " Optimized: $new_medium_verify_time sec, found: $new_medium_verify_found domains" + echo -e " Speed-up: ${GREEN}$medium_verify_speedup%${NC}" echo "" - echo "大字典验证模式:" - echo " 原始版本: $orig_large_verify_time 秒, 发现: $orig_large_verify_found 个域名" - echo " 优化版本: $new_large_verify_time 秒, 发现: $new_large_verify_found 个域名" - echo -e " 速度提升: ${GREEN}$large_verify_speedup%${NC}" + echo "Large dictionary, verify mode:" + echo " Original: $orig_large_verify_time sec, found: $orig_large_verify_found domains" + echo " Optimized: $new_large_verify_time sec, found: $new_large_verify_found domains" + echo -e " Speed-up: ${GREEN}$large_verify_speedup%${NC}" echo "" - echo "重试参数测试:" - echo " 原始版本: $orig_retry_test_time 秒, 发现: $orig_retry_test_found 个域名" - echo " 优化版本: $new_retry_test_time 秒, 发现: $new_retry_test_found 个域名" - echo -e " 速度提升: ${GREEN}$retry_test_speedup%${NC}" + echo "Retry parameter test:" + echo " Original: $orig_retry_test_time sec, found: $orig_retry_test_found domains" + echo " Optimized: $new_retry_test_time sec, found: $new_retry_test_found domains" + echo -e " Speed-up: ${GREEN}$retry_test_speedup%${NC}" echo "" - # 计算平均性能提升 + # Calculate average speed-up avg_speedup=$(echo "scale=2; ($small_verify_speedup + $small_enum_speedup + $medium_verify_speedup + $large_verify_speedup + $retry_test_speedup) / 5" | bc) - echo -e "平均性能提升: ${GREEN}$avg_speedup%${NC}" + echo -e "Average speed-up: ${GREEN}$avg_speedup%${NC}" else echo "" echo "========================================" - echo " 测试结果 (仅优化版本)" + echo " Test Results (optimized version only)" echo "========================================" - # 解析结果 + # Parse results IFS='|' read -r new_small_verify new_small_enum new_medium_verify new_large_verify new_retry_test <<< "$new_results" - # 提取时间和发现数量 + # Extract time and found counts IFS=',' read -r new_small_verify_time new_small_verify_found <<< "$new_small_verify" IFS=',' read -r new_small_enum_time new_small_enum_found <<< "$new_small_enum" IFS=',' read -r new_medium_verify_time new_medium_verify_found <<< "$new_medium_verify" IFS=',' read -r new_large_verify_time new_large_verify_found <<< "$new_large_verify" IFS=',' read -r new_retry_test_time new_retry_test_found <<< "$new_retry_test" - echo "小字典验证模式: $new_small_verify_time 秒, 发现: $new_small_verify_found 个域名" - echo "小字典枚举模式: $new_small_enum_time 秒, 发现: $new_small_enum_found 个域名" - echo "中等字典验证模式: $new_medium_verify_time 秒, 发现: $new_medium_verify_found 个域名" - echo "大字典验证模式: $new_large_verify_time 秒, 发现: $new_large_verify_found 个域名" - echo "重试参数测试: $new_retry_test_time 秒, 发现: $new_retry_test_found 个域名" + echo "Small dictionary, verify mode: $new_small_verify_time sec, found: $new_small_verify_found domains" + echo "Small dictionary, enum mode: $new_small_enum_time sec, found: $new_small_enum_found domains" + echo "Medium dictionary, verify mode: $new_medium_verify_time sec, found: $new_medium_verify_found domains" + echo "Large dictionary, verify mode: $new_large_verify_time sec, found: $new_large_verify_found domains" + echo "Retry parameter test: $new_retry_test_time sec, found: $new_retry_test_found domains" fi echo "" - echo "测试结果保存在 $OUTPUT_DIR 目录" - echo "测试完成!" + echo "Test results saved in $OUTPUT_DIR" + echo "Test complete!" } -# 执行主函数 -main \ No newline at end of file +# Execute main function +main diff --git a/test/integration_test.go b/test/integration_test.go index b94c270..b9bf695 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -16,13 +16,13 @@ import ( "github.com/stretchr/testify/assert" ) -// TestBasicVerification 基础验证测试 +// TestBasicVerification is a basic verification test func TestBasicVerification(t *testing.T) { if testing.Short() { - t.Skip("跳过集成测试") + t.Skip("Skipping integration test") } - // 已知存在的域名 + // Known existing domains domains := []string{ "www.baidu.com", "www.google.com", @@ -35,7 +35,7 @@ func TestBasicVerification(t *testing.T) { } close(domainChan) - // 收集结果 + // Collect results results := &testOutputter{results: make([]result.Result, 0)} opt := &options.Options{ @@ -60,26 +60,26 @@ func TestBasicVerification(t *testing.T) { r.RunEnumeration(ctx) r.Close() - // 验证结果 - assert.Greater(t, len(results.results), 0, "应该至少找到一个域名") + // Verify results + assert.Greater(t, len(results.results), 0, "Should find at least one domain") for _, res := range results.results { - assert.NotEmpty(t, res.Subdomain, "域名不应为空") - assert.Greater(t, len(res.Answers), 0, "应该有至少一个答案") - t.Logf("找到: %s => %v", res.Subdomain, res.Answers) + assert.NotEmpty(t, res.Subdomain, "Domain should not be empty") + assert.Greater(t, len(res.Answers), 0, "Should have at least one answer") + t.Logf("Found: %s => %v", res.Subdomain, res.Answers) } } -// TestCNAMEParsing 测试 CNAME 解析正确性 +// TestCNAMEParsing tests CNAME record parsing correctness func TestCNAMEParsing(t *testing.T) { if testing.Short() { - t.Skip("跳过集成测试") + t.Skip("Skipping integration test") } - // 已知有 CNAME 记录的域名 + // Domains known to have CNAME records domains := []string{ - "www.github.com", // 通常有 CNAME - "www.baidu.com", // 可能有 CNAME + "www.github.com", // usually has CNAME + "www.baidu.com", // may have CNAME } domainChan := make(chan string, len(domains)) @@ -112,31 +112,31 @@ func TestCNAMEParsing(t *testing.T) { r.RunEnumeration(ctx) r.Close() - // 检查 CNAME 记录格式 + // Check CNAME record format for _, res := range results.results { for _, answer := range res.Answers { - // 不应该出现 "comcom" 等错误拼接 - assert.NotContains(t, answer, "comcom", "不应该有错误的字符串拼接") - assert.NotContains(t, answer, "\x00", "不应该包含空字符") + // Should not have incorrect string concatenation like "comcom" + assert.NotContains(t, answer, "comcom", "Should not have incorrect string concatenation") + assert.NotContains(t, answer, "\x00", "Should not contain null characters") t.Logf("%s => %s", res.Subdomain, answer) } } } -// TestHighSpeed 高速扫描测试 +// TestHighSpeed tests high-speed scanning func TestHighSpeed(t *testing.T) { if testing.Short() { - t.Skip("跳过集成测试") + t.Skip("Skipping integration test") } - // 生成100个测试域名 + // Generate 100 test domains domains := make([]string, 100) for i := 0; i < 100; i++ { if i%2 == 0 { - domains[i] = "www.baidu.com" // 存在的 + domains[i] = "www.baidu.com" // exists } else { - domains[i] = "nonexistent12345.baidu.com" // 不存在的 + domains[i] = "nonexistent12345.baidu.com" // does not exist } } @@ -149,7 +149,7 @@ func TestHighSpeed(t *testing.T) { results := &testOutputter{results: make([]result.Result, 0)} opt := &options.Options{ - Rate: 10000, // 高速 + Rate: 10000, // high speed Domain: domainChan, Resolvers: options.GetResolvers(nil), Silent: true, @@ -170,17 +170,17 @@ func TestHighSpeed(t *testing.T) { r.RunEnumeration(ctx) r.Close() - // 应该找到大约50个(存在的域名) - assert.Greater(t, len(results.results), 40, "高速模式应该找到大部分存在的域名") - assert.Less(t, len(results.results), 60, "不应该有太多误报") + // Should find approximately 50 (existing domains) + assert.Greater(t, len(results.results), 40, "High-speed mode should find most existing domains") + assert.Less(t, len(results.results), 60, "Should not have too many false positives") - t.Logf("高速扫描结果: 找到 %d/%d 个域名", len(results.results), len(domains)) + t.Logf("High-speed scan results: found %d/%d domains", len(results.results), len(domains)) } -// TestRetryMechanism 重试机制测试 +// TestRetryMechanism tests the retry mechanism func TestRetryMechanism(t *testing.T) { if testing.Short() { - t.Skip("跳过集成测试") + t.Skip("Skipping integration test") } domains := []string{"www.example.com"} @@ -193,7 +193,7 @@ func TestRetryMechanism(t *testing.T) { results := &testOutputter{results: make([]result.Result, 0)} - // 测试不同的重试次数 + // Test different retry counts retryCounts := []int{1, 3, 5} for _, retryCount := range retryCounts { @@ -222,32 +222,32 @@ func TestRetryMechanism(t *testing.T) { cancel() - t.Logf("重试次数 %d: 耗时 %v, 结果数 %d", + t.Logf("Retry count %d: elapsed %v, results %d", retryCount, elapsed, len(results.results)) } } -// TestWildcardDetection 泛解析检测测试 +// TestWildcardDetection tests wildcard DNS detection func TestWildcardDetection(t *testing.T) { if testing.Short() { - t.Skip("跳过集成测试") + t.Skip("Skipping integration test") } - // 测试已知的泛解析域名 - // 注意: 这需要一个实际的泛解析域名 - domain := "baidu.com" // 示例 + // Test a known wildcard domain + // Note: requires an actual wildcard domain + domain := "baidu.com" // example isWild, ips := runner.IsWildCard(domain) if isWild { - t.Logf("检测到泛解析: %s, IPs: %v", domain, ips) - assert.Greater(t, len(ips), 0, "泛解析应该返回IP列表") + t.Logf("Wildcard detected: %s, IPs: %v", domain, ips) + assert.Greater(t, len(ips), 0, "Wildcard should return an IP list") } else { - t.Logf("未检测到泛解析: %s", domain) + t.Logf("No wildcard detected: %s", domain) } } -// testOutputter 测试用输出器 +// testOutputter is a test output handler type testOutputter struct { results []result.Result mu sync.Mutex diff --git a/test/performance_benchmark_test.go b/test/performance_benchmark_test.go index 9438683..d20b87a 100644 --- a/test/performance_benchmark_test.go +++ b/test/performance_benchmark_test.go @@ -20,18 +20,18 @@ import ( "github.com/boy-hack/ksubdomain/v2/pkg/runner/result" ) -// Benchmark100kDomains 10万域名性能基准测试 -// 参考 README 中的对比测试: -// - 测试环境: 4核CPU, 5M 带宽 -// - 字典大小: 10万域名 -// - 目标: ~30秒完成扫描 -// - 成功率: > 95% +// Benchmark100kDomains is a performance benchmark for 100k domains. +// Reference test from README: +// - Test environment: 4-core CPU, 5M bandwidth +// - Dictionary size: 100k domains +// - Target: ~30 seconds to complete +// - Success rate: > 95% func Benchmark100kDomains(b *testing.B) { if testing.Short() { - b.Skip("跳过性能基准测试 (使用 -tags=performance 运行)") + b.Skip("Skipping performance benchmark (use -tags=performance to run)") } - // 创建 10 万域名字典 + // Create 100k domain dictionary dictFile := createBenchmarkDict(b, 100000) defer os.Remove(dictFile) @@ -41,10 +41,10 @@ func Benchmark100kDomains(b *testing.B) { } } -// Benchmark10kDomains 1万域名快速测试 +// Benchmark10kDomains is a quick test for 10k domains func Benchmark10kDomains(b *testing.B) { if testing.Short() { - b.Skip("跳过性能基准测试") + b.Skip("Skipping performance benchmark") } dictFile := createBenchmarkDict(b, 10000) @@ -56,7 +56,7 @@ func Benchmark10kDomains(b *testing.B) { } } -// Benchmark1kDomains 1千域名基础测试 +// Benchmark1kDomains is a basic test for 1k domains func Benchmark1kDomains(b *testing.B) { dictFile := createBenchmarkDict(b, 1000) defer os.Remove(dictFile) @@ -67,21 +67,21 @@ func Benchmark1kDomains(b *testing.B) { } } -// createBenchmarkDict 创建测试字典 +// createBenchmarkDict creates a test dictionary file func createBenchmarkDict(b *testing.B, count int) string { b.Helper() tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("ksubdomain_bench_%d.txt", count)) f, err := os.Create(tmpFile) if err != nil { - b.Fatalf("创建字典文件失败: %v", err) + b.Fatalf("Failed to create dictionary file: %v", err) } defer f.Close() writer := bufio.NewWriter(f) - // 生成域名列表 - // 使用一些真实存在的域名模式,提高测试真实性 + // Generate domain list. + // Use some realistic domain patterns to improve test authenticity. baseDomains := []string{ "example.com", "test.com", @@ -99,43 +99,43 @@ func createBenchmarkDict(b *testing.B, count int) string { var domain string if i < len(prefixes)*len(baseDomains) { - // 使用常见前缀 + // Use common prefixes prefix := prefixes[i%len(prefixes)] base := baseDomains[i/len(prefixes)%len(baseDomains)] domain = fmt.Sprintf("%s.%s", prefix, base) } else { - // 生成随机子域名 + // Generate random subdomains base := baseDomains[i%len(baseDomains)] domain = fmt.Sprintf("subdomain%d.%s", i, base) } _, err := writer.WriteString(domain + "\n") if err != nil { - b.Fatalf("写入字典失败: %v", err) + b.Fatalf("Failed to write dictionary: %v", err) } } err = writer.Flush() if err != nil { - b.Fatalf("刷新字典失败: %v", err) + b.Fatalf("Failed to flush dictionary: %v", err) } - b.Logf("创建字典: %s (%d 个域名)", tmpFile, count) + b.Logf("Created dictionary: %s (%d domains)", tmpFile, count) return tmpFile } -// runBenchmark 运行性能测试 +// runBenchmark runs a performance test func runBenchmark(b *testing.B, dictFile string, expectedCount int) { b.Helper() - // 打开字典文件 + // Open dictionary file file, err := os.Open(dictFile) if err != nil { - b.Fatalf("打开字典失败: %v", err) + b.Fatalf("Failed to open dictionary: %v", err) } defer file.Close() - // 读取所有域名到通道 + // Read all domains into a channel domainChan := make(chan string, 10000) go func() { scanner := bufio.NewScanner(file) @@ -145,16 +145,16 @@ func runBenchmark(b *testing.B, dictFile string, expectedCount int) { close(domainChan) }() - // 收集结果 + // Collect results results := &perfOutputter{ - results: make([]result.Result, 0, expectedCount), - startTime: time.Now(), + results: make([]result.Result, 0, expectedCount), + startTime: time.Now(), totalDomains: expectedCount, } - // 配置扫描参数 (参考 README 的测试配置) + // Configure scan parameters (reference README test config) opt := &options.Options{ - Rate: options.Band2Rate("5m"), // 5M 带宽 + Rate: options.Band2Rate("5m"), // 5M bandwidth Domain: domainChan, Resolvers: options.GetResolvers(nil), Silent: true, @@ -166,56 +166,56 @@ func runBenchmark(b *testing.B, dictFile string, expectedCount int) { EtherInfo: options.GetDeviceConfig(options.GetResolvers(nil)), } - // 创建上下文 (5分钟超时,足够10万域名) + // Create context (5-minute timeout, sufficient for 100k domains) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - // 记录开始时间 + // Record start time startTime := time.Now() - // 运行扫描 + // Run scan r, err := runner.New(opt) if err != nil { - b.Fatalf("创建 runner 失败: %v", err) + b.Fatalf("Failed to create runner: %v", err) } r.RunEnumeration(ctx) r.Close() - // 计算性能指标 + // Calculate performance metrics elapsed := time.Since(startTime) successCount := len(results.results) successRate := float64(successCount) / float64(expectedCount) * 100 domainsPerSecond := float64(expectedCount) / elapsed.Seconds() - // 报告性能指标 + // Report performance metrics b.ReportMetric(elapsed.Seconds(), "total_seconds") b.ReportMetric(float64(successCount), "success_count") b.ReportMetric(successRate, "success_rate_%") b.ReportMetric(domainsPerSecond, "domains/sec") - // 日志输出 - b.Logf("性能测试结果:") - b.Logf(" - 字典大小: %d 个域名", expectedCount) - b.Logf(" - 总耗时: %v", elapsed) - b.Logf(" - 成功数: %d", successCount) - b.Logf(" - 成功率: %.2f%%", successRate) - b.Logf(" - 速率: %.0f domains/s", domainsPerSecond) + // Log output + b.Logf("Performance test results:") + b.Logf(" - Dictionary size: %d domains", expectedCount) + b.Logf(" - Total elapsed: %v", elapsed) + b.Logf(" - Success count: %d", successCount) + b.Logf(" - Success rate: %.2f%%", successRate) + b.Logf(" - Speed: %.0f domains/s", domainsPerSecond) - // 性能基准检查 (参考 README: 10万域名 ~30秒) + // Performance baseline check (reference README: 100k domains ~30 seconds) if expectedCount == 100000 { - // 10万域名应该在 60 秒内完成 (给予一定容差) + // 100k domains should complete within 60 seconds (with tolerance) if elapsed.Seconds() > 60 { - b.Logf("⚠️ 性能警告: 10万域名耗时 %.1f 秒 (目标 < 60秒)", elapsed.Seconds()) + b.Logf("⚠️ Performance warning: 100k domains took %.1f seconds (target < 60s)", elapsed.Seconds()) } else if elapsed.Seconds() <= 30 { - b.Logf("✅ 性能优秀: 10万域名仅耗时 %.1f 秒 (达到 README 标准)", elapsed.Seconds()) + b.Logf("✅ Excellent performance: 100k domains completed in %.1f seconds (meets README standard)", elapsed.Seconds()) } else { - b.Logf("✓ 性能良好: 10万域名耗时 %.1f 秒", elapsed.Seconds()) + b.Logf("✓ Good performance: 100k domains completed in %.1f seconds", elapsed.Seconds()) } } } -// perfOutputter 性能测试输出器 +// perfOutputter is the performance test output handler type perfOutputter struct { results []result.Result mu sync.Mutex @@ -230,15 +230,15 @@ func (p *perfOutputter) WriteDomainResult(r result.Result) error { p.results = append(p.results, r) - // 每1000个结果报告一次进度 + // Report progress every 1000 results if len(p.results)%1000 == 0 { elapsed := time.Since(p.startTime) rate := float64(len(p.results)) / elapsed.Seconds() progress := float64(len(p.results)) / float64(p.totalDomains) * 100 - // 避免频繁输出 + // Avoid frequent output if time.Since(p.lastReport) > time.Second { - fmt.Printf("\r进度: %d/%d (%.1f%%), 速率: %.0f domains/s, 耗时: %v", + fmt.Printf("\rProgress: %d/%d (%.1f%%), Speed: %.0f domains/s, Elapsed: %v", len(p.results), p.totalDomains, progress, rate, elapsed.Round(time.Second)) p.lastReport = time.Now() } @@ -249,7 +249,7 @@ func (p *perfOutputter) WriteDomainResult(r result.Result) error { func (p *perfOutputter) Close() error { elapsed := time.Since(p.startTime) - fmt.Printf("\n最终结果: %d/%d, 耗时: %v\n", + fmt.Printf("\nFinal result: %d/%d, Elapsed: %v\n", len(p.results), p.totalDomains, elapsed.Round(time.Millisecond)) return nil } diff --git a/test/run_all_tests.sh b/test/run_all_tests.sh index dae47a7..511fa9b 100755 --- a/test/run_all_tests.sh +++ b/test/run_all_tests.sh @@ -1,109 +1,109 @@ #!/bin/bash -# ksubdomain 测试运行脚本 -# 用于运行所有测试 +# ksubdomain test runner script +# Runs all tests -# 颜色定义 +# Color definitions GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m' BLUE='\033[0;34m' -NC='\033[0m' # 无颜色 +NC='\033[0m' # No color -# 检查权限 +# Check for root privileges if [ "$EUID" -ne 0 ]; then - echo -e "${RED}警告: 测试脚本需要root权限才能正常运行${NC}" - echo "请使用 sudo 运行此脚本" + echo -e "${RED}Warning: this test script requires root privileges to run correctly${NC}" + echo "Please run with sudo" exit 1 fi -# 确保测试目录存在 +# Ensure the test results directory exists mkdir -p "test/results" -# 清理旧的测试结果 -echo -e "${YELLOW}清理旧的测试结果...${NC}" +# Clean up old test results +echo -e "${YELLOW}Cleaning up old test results...${NC}" rm -rf test/results/* -# 设置权限 +# Set execute permissions chmod +x test/benchmark.sh chmod +x test/accuracy_test.sh chmod +x test/stress_test.sh echo "========================================" -echo -e "${BLUE}KSubdomain 测试套件${NC}" +echo -e "${BLUE}KSubdomain Test Suite${NC}" echo "========================================" echo "" -# 询问是否有旧版本可用于比较 -read -p "是否有原始版本的 ksubdomain 可用于比较测试? (y/n): " has_original +# Ask whether an original binary is available for comparison +read -p "Is an original ksubdomain binary available for comparison testing? (y/n): " has_original if [ "$has_original" = "y" ] || [ "$has_original" = "Y" ]; then - read -p "请输入原始版本 ksubdomain 的路径: " orig_path + read -p "Enter the path to the original ksubdomain binary: " orig_path if [ -f "$orig_path" ]; then - echo "复制原始版本到测试目录..." + echo "Copying original binary to test directory..." cp "$orig_path" "test/ksubdomain_orig" chmod +x "test/ksubdomain_orig" else - echo -e "${RED}错误: 指定的路径不存在${NC}" + echo -e "${RED}Error: the specified path does not exist${NC}" exit 1 fi fi -# 菜单选择要运行的测试 +# Menu: select which tests to run echo "" -echo "请选择要运行的测试:" -echo "1) 性能基准测试 (测试不同规模的字典)" -echo "2) 准确性测试 (比较结果一致性)" -echo "3) 压力测试 (测试高负载下的性能)" -echo "4) 运行所有测试" -echo "0) 退出" +echo "Select the tests to run:" +echo "1) Performance benchmark (test various dictionary sizes)" +echo "2) Accuracy test (compare result consistency)" +echo "3) Stress test (test performance under high load)" +echo "4) Run all tests" +echo "0) Exit" echo "" -read -p "请输入选项 [0-4]: " choice +read -p "Enter option [0-4]: " choice case $choice in 1) - echo -e "${YELLOW}运行性能基准测试...${NC}" + echo -e "${YELLOW}Running performance benchmark...${NC}" test/benchmark.sh ;; 2) if [ -f "test/ksubdomain_orig" ]; then - echo -e "${YELLOW}运行准确性测试...${NC}" + echo -e "${YELLOW}Running accuracy test...${NC}" test/accuracy_test.sh else - echo -e "${RED}错误: 准确性测试需要原始版本的 ksubdomain${NC}" + echo -e "${RED}Error: accuracy test requires the original ksubdomain binary${NC}" exit 1 fi ;; 3) - echo -e "${YELLOW}运行压力测试...${NC}" + echo -e "${YELLOW}Running stress test...${NC}" test/stress_test.sh ;; 4) - echo -e "${YELLOW}运行所有测试...${NC}" + echo -e "${YELLOW}Running all tests...${NC}" - echo -e "${BLUE}1. 性能基准测试${NC}" + echo -e "${BLUE}1. Performance benchmark${NC}" test/benchmark.sh if [ -f "test/ksubdomain_orig" ]; then - echo -e "${BLUE}2. 准确性测试${NC}" + echo -e "${BLUE}2. Accuracy test${NC}" test/accuracy_test.sh else - echo -e "${RED}跳过准确性测试,原始版本不存在${NC}" + echo -e "${RED}Skipping accuracy test: original binary not found${NC}" fi - echo -e "${BLUE}3. 压力测试${NC}" + echo -e "${BLUE}3. Stress test${NC}" test/stress_test.sh ;; 0) - echo "退出测试" + echo "Exiting" exit 0 ;; *) - echo -e "${RED}无效选项${NC}" + echo -e "${RED}Invalid option${NC}" exit 1 ;; esac echo "" -echo -e "${GREEN}所有测试完成!${NC}" -echo "测试结果保存在 test/results 目录中" \ No newline at end of file +echo -e "${GREEN}All tests complete!${NC}" +echo "Test results saved in test/results/" diff --git a/test/stress_test.sh b/test/stress_test.sh index 07630c5..8f6efeb 100755 --- a/test/stress_test.sh +++ b/test/stress_test.sh @@ -1,182 +1,181 @@ #!/bin/bash -# ksubdomain 压力测试脚本 -# 用于测试ksubdomain在高负载下的性能表现 +# ksubdomain stress test script +# Tests ksubdomain performance under high load -# 颜色定义 +# Color definitions GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m' -NC='\033[0m' # 无颜色 +NC='\033[0m' # No color -# 测试配置 +# Test configuration DOMAIN="example.com" DICT_LARGE="test/stress_dict.txt" RESOLVERS="test/resolvers.txt" OUTPUT_DIR="test/results" BIN="./ksubdomain" -# 确保测试目录存在 +# Ensure test directories exist mkdir -p "$OUTPUT_DIR" -# 创建DNS解析器文件(如果不存在) +# Create the DNS resolver file if it does not exist if [ ! -f "$RESOLVERS" ]; then - echo "创建DNS解析器文件..." + echo "Creating DNS resolver file..." echo "8.8.8.8" > "$RESOLVERS" echo "8.8.4.4" >> "$RESOLVERS" echo "1.1.1.1" >> "$RESOLVERS" echo "114.114.114.114" >> "$RESOLVERS" fi -# 创建大型字典(如果不存在) +# Create the large dictionary if it does not exist if [ ! -f "$DICT_LARGE" ]; then - echo "创建大型字典..." + echo "Creating large dictionary..." for i in $(seq 1 500000); do echo "stress$i.$DOMAIN" >> "$DICT_LARGE" done - echo "创建了 500,000 条记录的字典" + echo "Created dictionary with 500,000 entries" fi -# 获取系统信息 +# Print system information echo "========================================" -echo " 系统信息" +echo " System Information" echo "========================================" -echo "操作系统: $(uname -s)" -echo "处理器: $(uname -p)" -echo "内核版本: $(uname -r)" +echo "OS: $(uname -s)" +echo "Processor: $(uname -p)" +echo "Kernel version: $(uname -r)" if [ "$(uname -s)" = "Linux" ]; then - echo "CPU核心数: $(nproc)" - echo "内存: $(free -h | grep Mem | awk '{print $2}')" + echo "CPU cores: $(nproc)" + echo "Memory: $(free -h | grep Mem | awk '{print $2}')" elif [ "$(uname -s)" = "Darwin" ]; then - echo "CPU核心数: $(sysctl -n hw.ncpu)" - echo "内存: $(sysctl -n hw.memsize | awk '{print $1/1024/1024/1024 " GB"}')" + echo "CPU cores: $(sysctl -n hw.ncpu)" + echo "Memory: $(sysctl -n hw.memsize | awk '{print $1/1024/1024/1024 " GB"}')" fi echo "" echo "========================================" -echo " KSubdomain 压力测试" +echo " KSubdomain Stress Test" echo "========================================" -# 用于每次压力测试的函数 +# Function to run a single stress test at a given rate run_stress_test() { local rate=$1 local output="${OUTPUT_DIR}/stress_${rate}.txt" local log="${OUTPUT_DIR}/stress_${rate}.log" - echo -e "${YELLOW}测试速率: $rate pps${NC}" + echo -e "${YELLOW}Testing rate: $rate pps${NC}" - # 清理旧文件 + # Clean up old files [ -f "$output" ] && rm "$output" [ -f "$log" ] && rm "$log" - # 使用时间命令运行测试,获取总用时 - echo "开始测试..." + echo "Starting test..." - # 执行测试并计时 + # Run and time the test start_time=$(date +%s.%N) - # 将标准输出和错误输出重定向到日志文件 + # Redirect stdout and stderr to the log file $BIN v -f "$DICT_LARGE" -r "$RESOLVERS" -o "$output" -b "$rate" --retry 2 --timeout 4 --np > "$log" 2>&1 end_time=$(date +%s.%N) elapsed=$(echo "$end_time - $start_time" | bc) - # 统计结果 + # Parse results processed_count=$(cat "$log" | grep -o "success:[0-9]*" | tail -1 | grep -o "[0-9]*") found_count=$(wc -l < "$output") - # 计算每秒处理的域名数 + # Calculate throughput domains_per_sec=$(echo "$processed_count / $elapsed" | bc) - echo -e "${GREEN}测试完成${NC}" - echo "处理域名数: $processed_count" - echo "找到子域名: $found_count" - echo "耗时: $elapsed 秒" - echo "处理速率: $domains_per_sec 域名/秒" + echo -e "${GREEN}Test complete${NC}" + echo "Domains processed: $processed_count" + echo "Subdomains found: $found_count" + echo "Elapsed time: $elapsed sec" + echo "Throughput: $domains_per_sec domains/sec" echo "" - # 输出结果字符串,供后续收集 + # Return result string for collection echo "$rate,$elapsed,$processed_count,$found_count,$domains_per_sec" } -# 不同速率的测试 -echo -e "${YELLOW}运行不同速率的压力测试...${NC}" +# Run tests at different rates +echo -e "${YELLOW}Running stress tests at various rates...${NC}" echo "" -# 创建结果CSV文件 +# Create the results CSV file RESULT_CSV="${OUTPUT_DIR}/stress_results.csv" -echo "速率(pps),耗时(秒),处理域名数,发现子域名数,实际速率(域名/秒)" > "$RESULT_CSV" +echo "rate(pps),elapsed(sec),processed,found,throughput(domains/sec)" > "$RESULT_CSV" -# 逐步提高速率进行测试 +# Gradually increase the rate for rate in "10k" "50k" "100k" "200k" "500k" "1m"; do result=$(run_stress_test "$rate") echo "$result" >> "$RESULT_CSV" - # 短暂休息让系统冷却 + # Brief pause to let the system cool down sleep 5 done echo "========================================" -echo " 内存使用测试" +echo " Memory Usage Test" echo "========================================" -# 记录运行时内存使用情况 -echo -e "${YELLOW}测试运行时内存使用情况...${NC}" +# Monitor runtime memory usage +echo -e "${YELLOW}Testing runtime memory usage...${NC}" MEMORY_LOG="${OUTPUT_DIR}/memory_usage.log" MEM_OUTPUT="${OUTPUT_DIR}/memory_test.txt" -# 清理旧文件 +# Clean up old files [ -f "$MEMORY_LOG" ] && rm "$MEMORY_LOG" [ -f "$MEM_OUTPUT" ] && rm "$MEM_OUTPUT" -echo "开始测试..." +echo "Starting test..." -# 后台运行ksubdomain +# Run ksubdomain in the background $BIN v -f "$DICT_LARGE" -r "$RESOLVERS" -o "$MEM_OUTPUT" -b "100k" --retry 2 --timeout 4 --np > /dev/null 2>&1 & PID=$! -# 监控10秒内的内存使用情况 +# Monitor memory usage for 10 seconds echo "PID: $PID" -echo "监控内存使用情况..." +echo "Monitoring memory usage..." for i in {1..10}; do if [ "$(uname -s)" = "Linux" ]; then - # Linux下获取RSS内存使用量 + # Get RSS memory usage on Linux MEM=$(ps -p $PID -o rss= 2>/dev/null) if [ ! -z "$MEM" ]; then MEM_MB=$(echo "scale=2; $MEM / 1024" | bc) - echo "内存使用 #$i: ${MEM_MB}MB" | tee -a "$MEMORY_LOG" + echo "Memory usage #$i: ${MEM_MB} MB" | tee -a "$MEMORY_LOG" else - echo "进程已结束" + echo "Process has ended" break fi elif [ "$(uname -s)" = "Darwin" ]; then - # MacOS下获取内存使用量 + # Get memory usage on macOS MEM=$(ps -p $PID -o rss= 2>/dev/null) if [ ! -z "$MEM" ]; then MEM_MB=$(echo "scale=2; $MEM / 1024" | bc) - echo "内存使用 #$i: ${MEM_MB}MB" | tee -a "$MEMORY_LOG" + echo "Memory usage #$i: ${MEM_MB} MB" | tee -a "$MEMORY_LOG" else - echo "进程已结束" + echo "Process has ended" break fi fi sleep 1 done -# 测试10秒后结束进程 +# Terminate the process after 10 seconds if kill -0 $PID 2>/dev/null; then - echo "终止进程..." + echo "Terminating process..." kill $PID fi -# 获取最大内存使用量 +# Report peak memory usage if [ -f "$MEMORY_LOG" ]; then - MAX_MEM=$(cat "$MEMORY_LOG" | grep -o "[0-9]\+\.[0-9]\+MB" | sort -nr | head -1) - echo -e "${GREEN}最大内存使用量: $MAX_MEM${NC}" + MAX_MEM=$(cat "$MEMORY_LOG" | grep -o "[0-9]\+\.[0-9]\+ MB" | sort -nr | head -1) + echo -e "${GREEN}Peak memory usage: $MAX_MEM${NC}" fi echo "" -echo "压力测试完成!" -echo "结果保存在 $RESULT_CSV" \ No newline at end of file +echo "Stress test complete!" +echo "Results saved to $RESULT_CSV"