dns.ServeMux不支持通配符注册,因仅做精确字符串匹配;需注册"."兜底并手动匹配域名后缀,注意全限定名末尾点、Qtype区分及响应构造规范(SetReply、Name一致、TTL合理),同时需UDP/TCP双栈监听并正确配置绑定地址与网络环境。

为什么 dns.ServeMux 不能直接注册通配符域名
很多人写完 dns.ServeMux 的 handler,发现 *.example.com 根本不生效——不是 bug,是设计如此。dns.ServeMux 只做精确匹配,它拿 question.Name(比如 api.example.com.)去查注册的字符串键,而 "*.example.com" 这个字符串根本不会被命中。
真正要支持泛解析,得自己写匹配逻辑:
- 注册一个兜底 handler,比如
mux.HandleFunc(".", yourHandler),把所有未命中的请求都接住 - 在
yourHandler里手动检查req.Question[0].Name是否以"example.com."结尾(注意末尾的点!DNS 域名全限定名带点) - 别忘了把
req.Question[0].Qtype也判一下,A和AAAA处理方式可能不同
用 dns.Msg 构造响应时,为什么返回空应答或格式错误
常见现象:客户端查不到记录,Wireshark 看到响应 RCODE=0 但 Answer 段为空,或者直接报 FORMERR。问题通常出在三个地方:
-
msg.SetReply(req)必须调用,否则 ID、QR、opcode 等字段没对齐,下游当无效包丢弃 -
msg.Answer里每个*dns.A或*dns.CNAME记录,其Header().Name必须和问题里的Question[0].Name完全一致(包括大小写和末尾点) - TTL 设为 0 不等于“不缓存”,某些客户端会拒绝;建议设为
300这类合理值
示例片段:
立即学习“go语言免费学习笔记(深入)”;
msg := dns.Msg{}
msg.SetReply(req)
msg.Authoritative = true
a := &dns.A{
Hdr: dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 300,
},
A: net.ParseIP("192.0.2.1"),
}
msg.Answer = append(msg.Answer, a)
监听 UDP vs TCP 对自定义 DNS 服务器的影响
miekg/dns 默认只开 UDP 监听,但实际部署中必须考虑 TCP:
- UDP 包超过 512 字节(EDNS0 之前)会被截断,
TC=1标志置位,客户端自动 fallback 到 TCP —— 如果你没监听 TCP 端口,这部分查询就失败 - 区域传输(AXFR)、大响应(比如 DNSSEC RRset)强制走 TCP,不支持等于功能残缺
- 启动时建议同时跑两个 listener:
dns.ListenAndServe(":53", "udp", handler)和dns.ListenAndServe(":53", "tcp", handler),别共用同一个net.Listener
注意:Linux 下绑定 :53 需 root 权限;非 root 启动可先 bind 到高权端口(如 :8053),再用 iptables 转发,但别在代码里硬写 sudo。
调试时收不到请求?检查 dns.Server 的 NotifyStartedFunc 和日志位置
本地测试经常“启动没报错,但 dig 无响应”,大概率是监听地址不对或防火墙拦了:
- 默认
dns.ListenAndServe绑定":53",等价于"0.0.0.0:53",但有些系统(尤其 macOS)会限制INADDR_ANY+ port 53;换成"127.0.0.1:53"明确指定更稳妥 - 加
NotifyStartedFunc打印真实监听地址,避免误以为启动成功:
srv := &dns.Server{
Addr: "127.0.0.1:53",
Net: "udp",
Handler: mux,
NotifyStartedFunc: func() { log.Println("DNS server up on 127.0.0.1:53") },
}
go srv.ListenAndServe()
另外,dig @127.0.0.1 -p 53 example.com 要加 -p,否则默认还是走 53,但如果你绑的是 localhost:8053 就必须显式指定。
真正麻烦的是跨网络访问:Docker 容器里跑 DNS 服务,宿主机 dig 不通,往往是因为容器没加 --network host 或端口没 -p 53:53/udp 映射 —— 这些细节比代码逻辑更容易卡住人。










