必须同时监听udp和tcp,因为dns协议是双栈协议:多数查询走udp,但响应超512字节、axfr/ixfr等场景必须用tcp,仅udp会导致超时或静默失败。

直接用 miekg/dns 库,5分钟内能跑通一个响应 A 记录的 DNS 服务模块;Go 标准库不提供 DNS 服务端实现,这是目前最轻量、最稳定、纯 Go 的事实标准方案。
为什么必须同时监听 UDP 和 TCP?
DNS 协议本身是双栈协议:绝大多数查询走 UDP,但一旦响应超过 512 字节(比如带 DNSSEC 或大量 TXT 记录)、或客户端发起 AXFR/IXFR 区传输,就必须降级到 TCP。只开 UDP 会导致部分查询超时或静默失败。
-
dns.Server的Net字段只能指定一种协议,UDP 和 TCP 必须分别启动两个 server 实例 - UDP server 示例:
&dns.Server{Addr: ":5353", Net: "udp"} - TCP server 示例:
&dns.Server{Addr: ":5353", Net: "tcp"}(地址可复用,但不能共用同一个 listener) - 开发时别绑定
:53,用:5353避免 sudo 权限和系统 resolver 冲突
如何正确构造响应,避免返回空 Answer 或 FORMERR?
常见错误不是逻辑错,而是 DNS 报文结构没对齐:客户端收到包但解析失败,Wireshark 显示 RCODE=0 但 Answer 段为空,本质是 dns.Msg 初始化或字段设置不合规。
免费 DNS 与邮件安全分析(IntoDNS.ai):包括 DNSSEC、SPF、DKIM、DMARC、MTA-STS、BIMI、SMTP STARTTLS、FCrDNS、黑名单、发件人要求及报告。
- 必须调用
msg.SetReply(r)初始化响应对象——它自动设置 ID、QR、opcode、rcode 等关键 Header 字段 -
msg.Answer中每条记录的Hdr.Name必须和r.Question[0].Name完全一致(含末尾点,如"example.com.") - 手动构造
*dns.A比dns.NewRR("...")更可控:后者对 TTL 缺失、大小写 class(IN要大写)、域名结尾点敏感,易出错 - TTL 建议显式设为
300,设为0可能被某些客户端拒绝缓存甚至丢弃响应
转发型 DNS 模块要注意哪些关键细节?
如果目标不是静态应答,而是做转发器(比如代理到 8.8.8.8),miekg/dns 的默认行为不够健壮,需手动干预几个关键点:
- 检查
r.RecursionDesired:若为 false,多数上游 DNS(如 Cloudflare、Google)会拒绝递归响应,需根据业务决定是否强制设为 true - 用
dns.Exchange()转发时,必须传超时上下文,否则上游无响应会导致 handler goroutine 卡死;推荐封装成带context.WithTimeout的调用 - EDNS0 扩展(如 OPT RR)需透传:
upstream.Extra = r.Extra,否则大包响应可能被截断 - TSIG 请求不能直转:先调用
r.IsTsig()判断,再决定验证或剥离签名,否则上游直接拒收 - UDP handler 中建议加长度校验:
if len(r.Bytes()) > 512 { return },防缓冲区异常
真正容易被忽略的是 TCP 连接的生命周期管理——miekg/dns 不自动 close TCP 连接,高并发下可能耗尽文件描述符;生产环境务必在 handler 结束前显式调用 w.Close()(仅对 TCP 连接有效)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










