用miekg/dns可快速启动响应a记录的dns服务器,因go标准库无服务端实现,该库是轻量纯go事实标准;需注册handler、监听udp/tcp、正确构造fqdn和rr记录,并注意权限与超时等部署细节。

用 miekg/dns 库启动一个可响应 A 记录查询的 DNS 服务器
Go 本身标准库不带 DNS 服务实现,但 miekg/dns 是最常用、最轻量的第三方 DNS 库,稳定且文档清晰。它不依赖 Cgo,纯 Go 实现,适合嵌入或调试用途。
安装命令:
go get github.com/miekg/dns
核心逻辑就是注册一个 dns.Handler,监听 UDP(和可选 TCP)端口,处理 dns.Msg 请求并返回应答。最小可行代码如下:
package main
import (
"log"
"net"
"github.com/miekg/dns"
)
func main() {
server := &dns.Server{Addr: ":53", Net: "udp"}
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
m := new(dns.Msg)
m.SetReply(r)
m.Compress = true
if r.Question[0].Qtype == dns.TypeA {
rr, _ := dns.NewRR("example.com. IN A 192.0.2.1")
m.Answer = append(m.Answer, rr)
}
w.WriteMsg(m)
})
log.Println("DNS server listening on :53")
log.Fatal(server.ListenAndServe())
}
注意:需要 root 权限才能绑定 53 端口(Linux/macOS),开发时可用 :8053 替代测试。
如何正确构造响应记录(dns.RR)避免解析失败
DNS 响应对格式敏感,常见错误是域名没加尾部点、TTL 缺失、类型大小写不匹配,导致客户端收到 malformed response 或直接超时。
- 域名必须以
.结尾(如"example.com."),否则会被当作相对域名拼接搜索域 -
dns.NewRR()字符串格式为"name. TTL class type data",其中TTL推荐显式写(如300),不写可能被设为 0 导致缓存失效 -
class一般用IN(大写),小写in在某些客户端会拒收 - 如果要返回多个 A 记录,需多次调用
dns.NewRR()并append到m.Answer
更安全的构造方式(推荐):
免费 DNS 与邮件安全分析(IntoDNS.ai):包括 DNSSEC、SPF、DKIM、DMARC、MTA-STS、BIMI、SMTP STARTTLS、FCrDNS、黑名单、发件人要求及报告。
rr := &dns.A{
Name: "example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 300,
A: net.ParseIP("192.0.2.1"),
}
m.Answer = append(m.Answer, rr)
为什么只监听 UDP 不够?必须补上 TCP 处理
大多数简单查询走 UDP,但以下情况强制降级到 TCP:
- 响应数据 > 512 字节(例如带多条记录或 DNSSEC 签名)
- 客户端在请求中设置了
TC(Truncated)位(表示期望 TCP 重试) - 某些递归解析器(如 systemd-resolved)默认对非本地权威服务器优先用 TCP
不支持 TCP 的服务器在真实环境中会被部分客户端静默跳过。补上 TCP 只需加一段类似逻辑:
tcpServer := &dns.Server{Addr: ":53", Net: "tcp"}
go func() {
log.Fatal(tcpServer.ListenAndServe())
}()
注意:UDP 和 TCP 可共用同一个 dns.HandleFunc,无需重复注册。
本地测试时 dig 返回 SERVFAIL 或超时的排查点
运行后用 dig @127.0.0.1 -p 53 example.com A 测试,常见问题:
-
connection refused:端口被占或没权限(macOS 上systemd-resolved或dnsmasq可能已占 53) -
SERVFAIL:Handler 中 panic 了(miekg/dns遇到 panic 会自动返回 SERVFAIL,加recover日志可定位) -
no answer:响应里没填m.Answer,或Qtype判断写错(比如用了dns.TypeAAAA但查询的是 A) - Wireshark 抓包看到响应有数据但
dig不显示:检查m.Compress = true是否设置(不压缩可能导致长度计算异常)
真正上线前还得考虑超时控制、并发连接限制、日志采样——但对“简单 DNS 服务器”这个目标,上面四点覆盖了 95% 的卡点。










