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

如何通过Socket向DNS服务器发起请求?解决请求无响应问题

正确向DNS服务器发起Socket请求的方法

你的代码存在几个关键错误,导致无法和DNS服务器正常通信:

  • 协议类型错误:DNS默认使用UDP协议(53端口),你使用的是TCP Socket,虽然DNS也支持TCP,但普通查询用UDP更合适,且你的代码逻辑完全不符合TCP DNS的交互流程。
  • 连接逻辑混乱:你创建Socket时指定了Google DNS的地址和53端口,但随后又调用connect连接到www.youtube.com:80,这会覆盖之前的目标,完全偏离了DNS查询的方向。
  • 请求格式错误:DNS查询需要发送符合DNS协议规范的二进制报文,而不是直接发送"www.youtube.com"字符串,服务器无法识别这种文本请求。
  • 响应读取方式错误:DNS响应是二进制数据,用InputStreamReader和BufferedReader按文本行读取会破坏数据结构,无法解析有效内容。

正确实现步骤

1. 使用UDP DatagramSocket

DNS默认使用UDP协议,因此需要用DatagramSocket来发送和接收数据包。

2. 构造符合DNS协议的请求报文

DNS请求报文核心结构(简化版):

  • 2字节随机ID:用于匹配请求与响应
  • 2字节标志位:设置为标准查询标志(0x0100)
  • 2字节问题数:设为1
  • 问题部分:域名采用长度+标签的编码方式(比如www.youtube.com编码为0x03 0x77 0x77 0x77 0x07 0x79 0x6F 0x75 0x74 0x75 0x62 0x65 0x03 0x63 0x6F 0x6D 0x00),加上查询类型(A记录,0x0001)和查询类(IN类,0x0001)。

3. 发送请求并解析响应

将构造好的二进制报文封装为DatagramPacket发送到DNS服务器,接收响应后解析二进制数据提取IP。

示例代码

Network network = ...;
String dnsServer = "8.8.8.8";
int dnsPort = 53;
String domain = "www.youtube.com";

try {
    // 创建UDP Socket并设置超时
    DatagramSocket socket = network.getSocketFactory().createDatagramSocket();
    socket.setSoTimeout(3000);

    // 构造DNS请求报文
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 生成随机ID
    Random random = new Random();
    int id = random.nextInt(65536);
    baos.write(id >> 8);
    baos.write(id & 0xFF);
    // 标志位:标准查询
    baos.write(0x01);
    baos.write(0x00);
    // 问题数:1,其余计数为0
    baos.write(0x00);
    baos.write(0x01);
    baos.write(0x00);
    baos.write(0x00);
    baos.write(0x00);
    baos.write(0x00);
    baos.write(0x00);
    baos.write(0x00);

    // 编码域名
    String[] domainParts = domain.split("\\.");
    for (String part : domainParts) {
        baos.write(part.length());
        baos.write(part.getBytes(StandardCharsets.UTF_8));
    }
    baos.write(0x00); // 域名结束标记

    // 查询类型(A记录)和查询类(IN类)
    baos.write(0x00);
    baos.write(0x01);
    baos.write(0x00);
    baos.write(0x01);

    byte[] requestData = baos.toByteArray();

    // 发送请求数据包
    InetAddress dnsAddr = InetAddress.getByName(dnsServer);
    DatagramPacket requestPacket = new DatagramPacket(
            requestData, requestData.length, dnsAddr, dnsPort);
    socket.send(requestPacket);

    // 接收响应数据包
    byte[] responseBuffer = new byte[512];
    DatagramPacket responsePacket = new DatagramPacket(responseBuffer, responseBuffer.length);
    socket.receive(responsePacket);

    // 解析响应,提取A记录IP
    byte[] responseData = responsePacket.getData();
    int offset = 12; // 跳过ID、标志位、计数字段
    // 跳过域名部分
    while (responseData[offset] != 0x00) {
        offset += responseData[offset] + 1;
    }
    offset += 5; // 跳过0x00、查询类型、查询类
    // 验证是否为A记录
    int type = ((responseData[offset] & 0xFF) << 8) | (responseData[offset+1] & 0xFF);
    int dataLen = ((responseData[offset+8] & 0xFF) << 8) | (responseData[offset+9] & 0xFF);
    offset += 10;
    if (type == 0x0001 && dataLen == 4) {
        String ip = String.format("%d.%d.%d.%d",
                responseData[offset] & 0xFF,
                responseData[offset+1] & 0xFF,
                responseData[offset+2] & 0xFF,
                responseData[offset+3] & 0xFF);
        Log.e("DNS_RESULT", "www.youtube.com的IP:" + ip);
    }

    socket.close();
} catch (IOException e) {
    String message = context.getText(R.string.nointernet) + ": " + e.getLocalizedMessage();
    Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    context.getVariables().setNetwork(null);
}

补充说明

  • 若需使用TCP查询DNS(比如响应超过512字节),需先发送2字节的请求长度,再发送请求报文,读取时先读取响应长度再读取完整数据,逻辑比UDP复杂。
  • 上述示例仅实现基础A记录查询,如需支持更多记录类型,可参考DNS协议规范(RFC 1035)扩展解析逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 07:10:27