不能只用 net 包启动 dns 服务器,因为其仅提供客户端解析能力(如 lookuphost),不支持监听 53 端口、解析查询报文或构造合法响应;手动拼 dns 报文极易出错,必须使用 miekg/dns 库并同时启用 udp/tcp 双栈监听。

为什么不能只用 net 包启动 DNS 服务器
Go 标准库 net 包只提供客户端能力,比如 net.LookupHost 或 net.ResolveIPAddr,它不暴露 DNS 报文结构,也不支持监听 53 端口、解析查询字段或构造合法响应。试图用 net.ListenUDP 手动收发字节并自己拼 dns.Msg 头部——ID 错一位、QR 没设成 1、Name 缺尾部点,客户端直接丢包,Wireshark 里看到 RCODE=1(Format Error)却查不出哪错了。
必须用 miekg/dns 且得同时跑 UDP 和 TCP
CoreDNS 底层也在用的 miekg/dns 是当前最稳的纯 Go DNS 协议库,零 cgo、协议细节全封装。但常见错误是只启 UDP:dns.Server{Net: "udp"} —— 这会导致大响应(如带 DNSSEC 的 TXT)、区域传输(AXFR)或 EDNS0 扩展失败,客户端降级重试后超时。TCP 不是可选,是必须。
- UDP 实例:用
&dns.Server{Addr: ":8053", Net: "udp"}开发调试,避开 root 权限 - TCP 实例:另起一个
&dns.Server{Addr: ":8053", Net: "tcp"},不能复用同一个Server实例 - Handler 注册必须在 ListenAndServe 前,否则
dns.HandleFunc(".", handler)不生效 - 别写
dns.ListenAndServe(":8053", "udp", handler)—— 这个函数已弃用,新版必须用Server.ListenAndServe()
响应构造三步不能少:SetReply + Name 一致 + WriteMsg
最常卡住的地方不是逻辑,而是响应对象没初始化对。直接 new(dns.Msg) 后往 Answer 里塞记录,ID 和 QR 仍是 0,客户端当垃圾包扔掉。Wireshark 里能看到请求发出,但没回包,或者回包 RCODE=0 但 Answer 长度为 0。
免费 DNS 与邮件安全分析(IntoDNS.ai):包括 DNSSEC、SPF、DKIM、DMARC、MTA-STS、BIMI、SMTP STARTTLS、FCrDNS、黑名单、发件人要求及报告。
- 必须调用
m.SetReply(r),它自动设QR=1、同步ID、推导Authoritative等字段 -
rr, _ := dns.NewRR("example.com. 300 IN A 192.0.2.1")中的域名必须带尾部点,写成example.com会被当相对名拼搜索域 - 如果用结构体构造(如
&dns.A),Hdr.Name必须和r.Question[0].Name完全一致(含大小写、末尾点) - 最后一定要显式调用
w.WriteMsg(m),漏掉这句,goroutine 结束但没发包
GoLand 调试时容易忽略的部署细节
在 GoLand 里点运行能起来,但一换环境就失效,问题往往不在代码,在绑定地址和网络策略。
- 开发时用
:8053,上线想切:53?Linux/macOS 下必须加sudo,否则bind: permission denied - 监听
"127.0.0.1:8053"只能本机 curl,想让手机连 Wi-Fi 后访问,得监听"0.0.0.0:8053" - 家用路由器或校园网会劫持非 53 端口 DNS 流量,返回空响应或伪造 IP,这不是你代码错,是中间设备干的
- GoLand 默认 Run Configuration 用的是 local GOPATH,确保
go.mod里github.com/miekg/dns版本是 v1.1.50+,老版本有WriteMsgpanic bug
真正难的不是写完能返回 A 记录,而是让响应被客户端稳定收到——UDP/TCP 双栈、域名全限定、权限与网络环境,缺一不可。










