net.LookupIP仅支持同步查A/AAAA且依赖系统resolver、无超时控制;需超时、自定义DNS或查MX/TXT等记录时,必须用net.Resolver并配PreferGo和Dial,且每次调用须传context.WithTimeout。

直接用 net.LookupIP 就能查 A/AAAA,但只要涉及超时、自定义 DNS 服务器、或查 MX/TXT 等记录,就必须换 net.Resolver —— 后者不是“更高级的选项”,而是唯一可控路径。
为什么 net.LookupIP 会卡住或返回空
net.LookupIP 是同步阻塞调用,不接受 context,也不支持传超时;它完全依赖系统 resolver(/etc/resolv.conf 或 Windows API),超时由 libc 或系统策略控制,Go 层面无法干预。常见现象包括:
- 传了带端口的字符串,比如
"example.com:443",报错*net.DNSError,Err字段是"no such host",不是格式错误 - 容器环境(如 Alpine)缺失
/etc/resolv.conf,直接返回"no such host" - 内网 DNS 被拦截或响应慢,goroutine 卡住 10–30 秒
- 想查
"localhost"却没走/etc/hosts:默认不读 hosts,得靠net.Resolver{PreferGo: true}才生效
net.Resolver 必须配齐 PreferGo 和 Dial
Resolver.PreferGo = true 只是启用 Go 自研 DNS 客户端,**不是**自动指定 DNS 服务器。漏掉 Dial 函数,它仍 fallback 到系统 resolver,等于白设。
-
Dial必须是函数类型,返回net.Conn;不能写成字符串地址 - UDP 查询示例:
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { return (&net.Dialer{Timeout: 2*time.Second}).DialContext(ctx, "udp", "1.1.1.1:53") } - TCP 查询需换网络名
"tcp",但注意 Cloudflare/1.1.1.1 默认不响应 TCP 查询 - 必须用
context.WithTimeout包裹LookupIP调用,Resolver本身不管理上下文
查 MX/TXT/CNAME 不能靠 LookupIP
net.LookupIP 只返回 A/AAAA 地址,查其他记录必须用对应专用函数,且它们底层只认 net.Resolver 实例(net.DefaultResolver 不行):
立即学习“go语言免费学习笔记(深入)”;
-
LookupMX(domain)返回[]*net.MX,注意MX.Target是域名,不是 IP -
LookupTXT(domain)返回[][]string,每条记录可能被拆成多段(EDNS 分片) -
LookupCNAME(domain)只返回最终 canonical name,不返回跳转链;若无 CNAME,则 fallback 到 A 记录 - Kubernetes Headless Service 场景下要拿全量 Pod IP,必须用
r.LookupIP(ctx, "ip", domain),传"ip4"会漏 IPv6
错误判断别只看 err != nil
*net.DNSError 有多种语义,仅靠非空 error 无法区分是“域名不存在”还是“网络临时故障”:
-
err.(*net.DNSError).IsNotFound()→ 权威回答 NXDOMAIN,可认为域名无效 -
err.(*net.DNSError).IsTemporary()→ UDP 丢包、防火墙拦截、DNS 无响应等,应重试 -
err.(*net.DNSError).IsTimeout()→ Dial 层超时,大概率是网络问题,不是配置错 - 别用
strings.Contains(err.Error(), "no such host")做判断,字段语义不稳定
最易忽略的是:即使你写了 PreferGo: true 和 Dial,如果没在 LookupIP 调用时传带超时的 context,整个查询依然可能卡死——Go 的 DNS 解析器不会自己读取 Resolver 上的超时设置,上下文必须显式传入每次调用。


















