net.DialTimeout 更适合端口扫描,因其可精确控制连接超时(如500ms),避免默认30秒卡顿;Go 1.19+推荐复用net.Dialer并设Timeout字段;并发需限流,建议用带缓冲channel控制(如sem := make(chan struct{}, 100))。

为什么 net.DialTimeout 比 net.Dial 更适合端口扫描
直接用 net.Dial 扫描会卡住:TCP 握手失败时默认等待约 30 秒才超时,扫 65535 个端口可能要几天。而 net.DialTimeout 允许你精确控制连接尝试时长(比如 500ms),这是并发扫描能落地的前提。
注意:net.DialTimeout 底层仍走 net.Dial,只是封装了 deadline;Go 1.19+ 推荐改用 net.Dialer + Timeout 字段,更灵活且可复用。
- 超时设太短(如 50ms):内网可能漏报开放端口;设太长(如 2s):并发数稍高就拖慢整体速度
- 别在循环里反复 new
net.Dialer,复用一个实例能减少内存分配 - Windows 上对已关闭端口的 RST 响应较慢,建议超时不低于 300ms
如何安全控制并发数避免被系统 kill 或丢包
开 10000 个 goroutine 同时拨号?大概率触发 Linux 的 net.ipv4.ip_local_port_range 耗尽、或被目标防火墙限速/拉黑。真实可用的并发数取决于你的网络栈和目标响应能力,不是越多越好。
推荐用带缓冲的 channel 做计数器,比 sync.WaitGroup + 全局变量更稳妥:
立即学习“go语言免费学习笔记(深入)”;
sem := make(chan struct{}, 100) // 并发上限 100
for port := 1; port <= 65535; port++ {
sem <- struct{}{} // 阻塞直到有空位
go func(p int) {
defer func() { <-sem }() // 释放
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", target, p), 500*time.Millisecond)
if err == nil {
conn.Close()
fmt.Printf("open: %d\n", p)
}
}(port)
}- 缓冲大小建议从 50 起调,观察
ss -s输出的“TCP: inuse”是否稳定 - 别用
runtime.GOMAXPROCS调整来“加速”——I/O 并发不靠 CPU 核心数 - 某些云厂商(如 AWS SG)会对短连接风暴主动限流,测试前先扫单个 C 段验证策略
怎么判断端口“真开放”而不是中间设备干扰
收到 connection refused(ECONNREFUSED)说明端口明确关闭;但没回包 ≠ 开放——可能是防火墙 DROP、NAT 设备静默丢弃、或 ICMP 不可达被屏蔽。
仅靠 TCP connect 结果容易误判。稳妥做法是结合三次握手状态:
- 成功建立连接(
err == nil)→ 基本可认定开放(仍需后续协议交互确认服务) - 返回
dial tcp X:Y: i/o timeout→ 大概率被过滤,不是“关闭”,需配合其他探测(如 SYN 扫描,但 Go 标准库不支持 raw socket) - 返回
connection refused→ 真实关闭,内核明确拒绝 - 遇到
too many open files错误?立刻检查ulimit -n,并加runtime.LockOSThread()防止 goroutine 跨线程导致 fd 泄漏
为什么不用 golang.org/x/net/proxy 做代理扫描
标准库 net 包不支持 SOCKS/HTTP 代理透传 TCP connect 请求。golang.org/x/net/proxy 只能包装 dialer,但多数代理(尤其 HTTP CONNECT)本身会拦截或重写 TCP 握手,导致端口状态失真——比如代理返回 200 OK 并不表示后端端口开放。
真要走代理,必须用支持原始 TCP 透传的 SOCKS5 服务器,并确认其未做端口白名单限制:
auth := proxy.Auth{User: "u", Password: "p"}
dialer, _ := proxy.SOCKS5("tcp", "127.0.0.1:1080", auth, proxy.Direct)
conn, err := dialer.Dial("tcp", "target:22") // 此处才真正发起目标连接- 绝大多数企业代理禁用 CONNECT 方法,或只允许可信端口(80/443),扫其他端口直接被拒绝
- 代理链路增加 RTT,超时值需同步放大,否则大量误报“超时”
- 别试图用
http.Transport模拟 CONNECT——它会发 HTTP 请求头,和纯 TCP 扫描语义不符
端口扫描的本质是网络层连通性探测,加代理等于隔了一层不可控的状态转换,复杂度和误报率都会上升。除非明确需要绕过本地出口 IP 限制,否则优先直连。










