You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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后,查看系统缓存记录:
    ping -c 1 example.com
    dscacheutil -q host -a name example.com
    
    确认输出中的IP是否为自定义DNS返回的结果

代码检查重点

  • 确保所有DNS字段的字节序为大端序(Big Endian),这是DNS协议的强制要求
  • 处理并发请求时避免阻塞,确保UDP/TCP服务能同时响应多个请求
  • 验证响应的长度不超过UDP的512字节限制(如果超过,需支持TCP分片)

内容的提问来源于stack exchange,提问作者XRSec

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 20:42:47