用 miekg/dns 启动基础 DNS 服务器需同时监听 UDP 和 TCP 的 53 端口,响应必须用 msg.SetReply(req) 初始化并正确构造 A/AAAA 记录,部署时须防运营商劫持、配置防火墙,并手动实现缓存与超时控制。

用 miekg/dns 库启动一个基础 DNS 服务器
Go 本身标准库不提供 DNS 服务端实现,必须依赖第三方库;miekg/dns 是最成熟、被广泛使用的方案(CoreDNS 底层就是它)。直接用它注册处理函数、监听 UDP/TCP 端口即可运行。
常见错误是只监听 UDP 而忽略 TCP——某些响应(如大于 512 字节的响应、AXFR 区传输)必须走 TCP,否则客户端会超时或降级失败。
- UDP 监听用
dns.ListenAndServe,传入"udp"网络类型 - TCP 需单独调用
dns.ListenAndServe并传"tcp",不能复用同一个 listener - 务必设置
dns.Server.TsigSecret(若需 TSIG)或dns.Server.Notify(若做主从同步),否则相关功能静默失效 - 示例片段:
srv := &dns.Server{Addr: ":53", Net: "udp"}<br>dns.HandleFunc(".", handleFunc)<br>log.Fatal(srv.ListenAndServe())
如何正确解析并响应 A/AAAA 查询
DNS 响应不是简单拼字符串,必须严格遵循 RFC 1035 的报文结构:Header + Question + Answer + Authority + Additional。手写二进制包极易出错,miekg/dns 提供了 dns.Msg 类型封装整个流程。
容易踩的坑是直接修改 msg.Answer 切片却忘记调用 msg.SetReply(req) ——这会导致响应 ID、QR 标志、opcode 等字段未正确设置,客户端收不到合法响应。
- 始终用
msg := dns.Msg{}; msg.SetReply(req)初始化响应对象 - A 记录用
&dns.A{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 300}, A: net.ParseIP("192.0.2.1")} - AAAA 记录同理,但 IP 字段类型是
net.IP,且必须为 IPv6 地址(如::1) - 不要手动设置
msg.Authoritative或msg.RecursionAvailable,它们由SetReply推导,覆写反而破坏语义
为什么自定义 DNS 服务器常被运营商劫持或返回空响应
不是代码问题,而是部署环境触发了中间设备干预:国内多数家用路由器、校园网、运营商 DNS 代理会拦截非 53 端口的 DNS 流量,或对非知名上游(如 114.114.114.114)的响应做静默替换。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
真实场景中,即使你的服务器逻辑完全正确,客户端也可能收不到原始响应。
- 测试阶段强制指定 DNS:Linux 用
dig @127.0.0.1 -p 53 example.com,绕过系统 resolv.conf - 避免使用 53 端口外的端口(如 5353)对外提供服务——mDNS 除外,但那是另一套协议
- 若需公网可用,必须配置防火墙放行 UDP/TCP 53,并确认云厂商安全组未拦截
- 部分 Android/iOS 客户端强制使用 HTTPS DNS(DoH)或 TLS DNS(DoT),纯 UDP/TCP 服务器对其不可见
高性能场景下要注意 dns.Server 的并发与超时控制
miekg/dns 默认每个连接起一个 goroutine,无内置限流;QPS 高时可能创建大量 goroutine,或因单请求阻塞导致连接堆积。
它不提供类似 HTTP Server 的 ReadTimeout / WriteTimeout,超时必须靠外部上下文或连接层控制。
- UDP 场景建议用
dns.Server.PacketConn自定义带超时的net.PacketConn(如用net.ListenUDP+SetReadDeadline) - TCP 场景可在 Accept 后立即设置
conn.SetReadDeadline(time.Now().Add(2 * time.Second)) - 避免在
HandleFunc中做同步 IO(如查数据库、调远程 API)——DNS 协议要求毫秒级响应,否则客户端重试放大流量 - 缓存必须自己实现(
miekg/dns不含缓存),推荐用groupcache或freecache,而非sync.Map(缺乏 TTL 和淘汰)
真正难的不是“怎么把 DNS 包发出去”,而是让这个服务在真实网络里稳定、不被干扰、不拖垮机器。很多细节藏在 transport 层和运营环境里,不在代码里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










