Golang自定义DNS服务器异常:nslookup/dig正常但curl/ping无法解析
自定义Golang DNS服务器:nslookup/dig正常,但curl/wget/ping无响应问题排查
问题背景
用Golang实现自定义DNS服务器,当前在macOS 13 + Go 1.20.1环境下,nslookup和dig工具能正常通过该服务器解析域名,但curl、wget、ping等工具无法获取解析结果。不想依赖dnsmasq(避免增大程序体积),尚未在Windows/Linux系统测试。
核心原因分析
nslookup/dig与其他工具的行为差异,本质是DNS解析路径的不同:
nslookup/dig默认直接向配置的DNS服务器发送UDP请求,跳过系统DNS缓存服务- macOS上的
curl/wget/ping会优先使用系统的mDNSResponder服务,该服务会对DNS响应做校验、缓存,且可能优先选择TCP请求或使用其他配置的DNS服务器
排查与解决步骤
1. 补充TCP请求支持
macOS部分工具在UDP请求超时或响应数据较大时,会自动切换到TCP请求。如果你的DNS服务器仅实现了UDP监听,会导致这类请求失败。
添加TCP服务处理逻辑:
package main import ( "encoding/binary" "io" "log" "net" ) func main() { // 启动UDP服务(保留原有逻辑) udpConn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 53}) if err != nil { log.Fatalf("Failed to start UDP listener: %v", err) } defer udpConn.Close() go handleUDPRequests(udpConn) // 新增TCP服务监听 tcpListener, err := net.Listen("tcp", ":53") if err != nil { log.Fatalf("Failed to start TCP listener: %v", err) } defer tcpListener.Close() for { conn, err := tcpListener.Accept() if err != nil { log.Printf("TCP accept error: %v", err) continue } go handleTCPRequest(conn) } } // 原有UDP请求处理函数 func handleUDPRequests(conn *net.UDPConn) { buf := make([]byte, 512) for { n, addr, err := conn.ReadFromUDP(buf) if err != nil { log.Printf("UDP read error: %v", err) continue } resp := handleDNSQuery(buf[:n]) _, err = conn.WriteToUDP(resp, addr) if err != nil { log.Printf("UDP write error: %v", err) } } } // TCP请求处理函数 func handleTCPRequest(conn net.Conn) { defer conn.Close() // 读取TCP DNS请求的长度前缀(2字节大端序) lenBuf := make([]byte, 2) _, err := io.ReadFull(conn, lenBuf) if err != nil { log.Printf("TCP length read error: %v", err) return } reqLen := binary.BigEndian.Uint16(lenBuf) // 读取请求内容 reqBuf := make([]byte, reqLen) _, err = io.ReadFull(conn, reqBuf) if err != nil { log.Printf("TCP request read error: %v", err) return } // 复用UDP的DNS处理逻辑生成响应 respBuf := handleDNSQuery(reqBuf) // 写入响应长度前缀+响应内容 respLen := uint16(len(respBuf)) binary.BigEndian.PutUint16(lenBuf, respLen) conn.Write(lenBuf) conn.Write(respBuf) } // 你的DNS查询处理核心逻辑 func handleDNSQuery(req []byte) []byte { // 原有解析、生成响应的代码 // 注意确保响应符合RFC 1035规范 return []byte{} }
2. 校验DNS响应的规范性
系统DNS服务对响应格式要求严格,微小的不符合规范会导致响应被丢弃:
- 确保响应的AA(Authoritative Answer)位设置为1:部分工具优先信任权威响应
- 合理设置TTL值:避免设为0,macOS可能不缓存TTL为0的记录
- 检查域名压缩逻辑:如果实现了域名压缩,确保完全符合RFC 1035的压缩规则
- 使用
dig +dnssec +additional example.com @<你的DNSIP>查看响应的完整结构,对比标准DNS响应的字段
3. 清理macOS DNS缓存并调整配置
- 刷新系统DNS缓存:
sudo dscacheutil -flushcache sudo killall -HUP mDNSResponder - 确保系统网络设置中,自定义DNS服务器是唯一配置项,避免系统优先使用路由器或其他默认DNS
4. 强制工具使用指定DNS测试
- 用curl强制指定DNS服务器验证:
curl --dns-servers 127.0.0.1 example.com - 执行ping后,查看系统缓存记录:
确认输出中的IP是否为自定义DNS返回的结果ping -c 1 example.com dscacheutil -q host -a name example.com
代码检查重点
- 确保所有DNS字段的字节序为大端序(Big Endian),这是DNS协议的强制要求
- 处理并发请求时避免阻塞,确保UDP/TCP服务能同时响应多个请求
- 验证响应的长度不超过UDP的512字节限制(如果超过,需支持TCP分片)
内容的提问来源于stack exchange,提问作者XRSec
相关产品推荐
相关产品推荐

