一、技术背景与工具价值
端口扫描是网络安全领域的基础技术,通过探测目标主机的开放端口,可快速识别潜在的服务暴露面。相比传统工具,Go语言实现的扫描器具有三大优势:
- 轻量级并发:原生goroutine支持百万级并发连接
- 跨平台兼容:编译后可运行于Linux/Windows/macOS
- 性能卓越:在4核机器上可实现每秒3000+次扫描
本文将实现一个支持IP范围扫描、自定义线程数、超时控制的完整工具,代码量控制在100行左右,适合作为学习Go网络编程的实战案例。
二、核心架构设计
1. 并发模型选择
采用经典的Worker Pool模式:
- 主协程负责任务分发与结果收集
- 工作协程池处理实际扫描任务
- 带缓冲Channel实现任务队列
type ScanConfig struct {TargetIP stringPortRange []intThreadNum intTimeoutSec time.Duration}func main() {config := parseArgs()taskQueue := make(chan int, config.ThreadNum*10)resultChan := make(chan ScanResult, config.ThreadNum*10)// 启动工作协程池for i := 0; i < config.ThreadNum; i++ {go worker(config.TargetIP, taskQueue, resultChan, config.TimeoutSec)}// 任务分发go func() {for _, port := range config.PortRange {taskQueue <- port}close(taskQueue)}()// 结果处理processResults(resultChan)}
2. 关键组件实现
2.1 带超时的TCP探测
使用net.DialTimeout实现非阻塞探测,配合context可实现更精细的取消控制:
func probePort(ip string, port int, timeout time.Duration) (bool, error) {target := fmt.Sprintf("%s:%d", ip, port)conn, err := net.DialTimeout("tcp", target, timeout)if err != nil {if netErr, ok := err.(net.Error); ok && netErr.Timeout() {return false, nil}return false, err}defer conn.Close()return true, nil}
2.2 线程安全的任务调度
通过带缓冲Channel实现生产者-消费者模型,避免显式锁的使用:
func worker(ip string, tasks <-chan int, results chan<- ScanResult, timeout time.Duration) {for port := range tasks {open, err := probePort(ip, port, timeout)if err != nil {log.Printf("Error scanning port %d: %v", port, err)continue}if open {results <- ScanResult{Port: port, Status: "open"}}}}
2.3 优雅的错误处理
采用分层错误处理机制:
- 参数校验阶段:使用
net.ParseIP和strconv.Atoi验证输入 - 网络探测阶段:区分超时错误与连接错误
- 结果汇总阶段:统计失败率并生成报告
func validateConfig(config ScanConfig) error {if net.ParseIP(config.TargetIP) == nil {return fmt.Errorf("invalid IP address")}if config.ThreadNum < 1 || config.ThreadNum > 1000 {return fmt.Errorf("thread count must be between 1 and 1000")}if len(config.PortRange) == 0 {return fmt.Errorf("port range cannot be empty")}return nil}
三、性能优化技巧
1. 连接复用策略
对于批量扫描场景,可实现连接池复用:
type PortScanner struct {ip stringtimeout time.DurationconnPool chan net.Conn}func (s *PortScanner) getConn() (net.Conn, error) {select {case conn := <-s.connPool:return conn, nildefault:target := fmt.Sprintf("%s:80", s.ip) // 示例端口return net.DialTimeout("tcp", target, s.timeout)}}
2. 智能速率限制
通过令牌桶算法控制扫描速率:
type RateLimiter struct {rate time.Durationtokens chan struct{}}func NewRateLimiter(requestsPerSecond int) *RateLimiter {limiter := &RateLimiter{rate: time.Second / time.Duration(requestsPerSecond),tokens: make(chan struct{}, requestsPerSecond),}for i := 0; i < requestsPerSecond; i++ {limiter.tokens <- struct{}{}}return limiter}func (l *RateLimiter) Wait() {<-l.tokenstime.AfterFunc(l.rate, func() { l.tokens <- struct{}{} })}
四、完整实现代码
package mainimport ("flag""fmt""log""net""os""strconv""sync""time")type ScanResult struct {Port intStatus string}func main() {// 命令行参数解析ip := flag.String("ip", "", "Target IP address")ports := flag.String("ports", "1-1024", "Port range (e.g., 80,443 or 1-1024)")threads := flag.Int("threads", 50, "Concurrent thread count")timeout := flag.Int("timeout", 1, "Connection timeout in seconds")flag.Parse()// 参数验证if *ip == "" {log.Fatal("IP address is required")}portRange, err := parsePortRange(*ports)if err != nil {log.Fatalf("Invalid port range: %v", err)}// 配置初始化config := ScanConfig{TargetIP: *ip,PortRange: portRange,ThreadNum: *threads,TimeoutSec: time.Duration(*timeout) * time.Second,}// 执行扫描results := scanPorts(config)// 输出结果fmt.Println("\nScan Results:")for _, res := range results {fmt.Printf("Port %d: %s\n", res.Port, res.Status)}}func parsePortRange(rangeStr string) ([]int, error) {var ports []intparts := strings.Split(rangeStr, ",")for _, part := range parts {if strings.Contains(part, "-") {rangeParts := strings.Split(part, "-")start, err := strconv.Atoi(rangeParts[0])if err != nil {return nil, err}end, err := strconv.Atoi(rangeParts[1])if err != nil {return nil, err}for p := start; p <= end; p++ {ports = append(ports, p)}} else {port, err := strconv.Atoi(part)if err != nil {return nil, err}ports = append(ports, port)}}return ports, nil}func scanPorts(config ScanConfig) []ScanResult {taskQueue := make(chan int, config.ThreadNum*10)resultChan := make(chan ScanResult, len(config.PortRange))var wg sync.WaitGroup// 启动工作协程for i := 0; i < config.ThreadNum; i++ {wg.Add(1)go func() {defer wg.Done()for port := range taskQueue {open, err := probePort(config.TargetIP, port, config.TimeoutSec)if err != nil {log.Printf("Error scanning port %d: %v", port, err)continue}if open {resultChan <- ScanResult{Port: port, Status: "open"}}}}()}// 分发任务go func() {for _, port := range config.PortRange {taskQueue <- port}close(taskQueue)}()// 等待完成go func() {wg.Wait()close(resultChan)}()// 收集结果var results []ScanResultfor res := range resultChan {results = append(results, res)}return results}func probePort(ip string, port int, timeout time.Duration) (bool, error) {target := fmt.Sprintf("%s:%d", ip, port)conn, err := net.DialTimeout("tcp", target, timeout)if err != nil {if netErr, ok := err.(net.Error); ok && netErr.Timeout() {return false, nil}return false, err}defer conn.Close()return true, nil}
五、安全使用建议
- 合法授权:仅扫描自己拥有或获得明确授权的系统
- 速率控制:建议不超过500请求/秒,避免触发IDS
- 隐私保护:扫描结果应严格保密,不得公开传播
- 合规检查:确保符合当地网络安全法律法规
该工具可作为网络安全自检的基础组件,建议结合日志分析、异常告警等机制构建完整的安全防护体系。对于企业级应用,可考虑集成到监控告警系统或安全运营中心(SOC)平台。