【问题标题】:DNS query over TCP基于 TCP 的 DNS 查询
【发布时间】:2017-01-06 19:05:49
【问题描述】:

我现在拥有的是通过 UDP 进行的 DNS 查询,它工作正常,但如果消息被截断,我需要使用相同的查询通过 TCP 重新连接,我不能这样做,主要问题是我确实有字节查询数组和 tcp 发送字符。

我的 UDP 查询组装:

String DNS_SERVER_ADDRESS = args[0];
    String domain = args[1];
    ipAddress = InetAddress.getByName(DNS_SERVER_ADDRESS);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    dos = new DataOutputStream(baos);

    // *** Build a DNS Request Frame ****

    // Identifier: A 16-bit identification field generated by the device that creates the DNS query.
    // It is copied by the server into the response, so it can be used by that device to match that
    // query to the corresponding reply received from a DNS server. This is used in a manner similar
    // to how the Identifier field is used in many of the ICMP message types.
    dos.writeShort(0x1234);

    // Write Query Flags
    dos.writeShort(0x0100);

    // Question Count: Specifies the number of questions in the Question section of the message.
    dos.writeShort(0x0001);

    // Answer Record Count: Specifies the number of resource records in the Answer section of the message.
    dos.writeShort(0x0000);

    // Authority Record Count: Specifies the number of resource records in the Authority section of
    // the message. (“NS” stands for “name server”)
    dos.writeShort(0x0000);

    // Additional Record Count: Specifies the number of resource records in the Additional section of the message.
    dos.writeShort(0x0000);

    // TODO: write query
    String[] domainParts = domain.split("\\.");
    System.out.println(domain + " has " + domainParts.length + " parts");

    for (String domainPart : domainParts) {
        System.out.println("Writing: " + domainPart);
        byte[] domainBytes = domainPart.getBytes("UTF-8");
        dos.writeByte(domainBytes.length);
        dos.write(domainBytes);
    }

    // No more parts
    dos.writeByte(0x00);

    // QType 0x01 = A (Host Request)
    if (args.length>2)
        dos.writeShort(typeEncode(args[2]));
    else
        dos.writeShort(0x00ff); // "ANY" as default

    // QClass 0x01 = IN
    dos.writeShort(0x0001);

    dnsFrame = baos.toByteArray();

    System.out.println("Sending: " + dnsFrame.length + " bytes");
    for (byte aDnsFrame : dnsFrame) {
        System.out.print("0x" + String.format("%x", aDnsFrame) + " ");
    }

    // *** Send DNS Request Frame ***
    DatagramSocket socket = new DatagramSocket();
    DatagramPacket dnsReqPacket = new DatagramPacket(dnsFrame, dnsFrame.length, ipAddress, DNS_SERVER_PORT);
    socket.send(dnsReqPacket);
    return socket;

我如何尝试通过 TCP 发送它:

Socket echoSocket = null;
    // strumień do zapisu do serwera
    Writer out = null;
    // strumień do odczytu z serwera
    BufferedReader in = null;
    // nazwa serwera
    String hostname=args[0];

    try {
        System.out.println("próba utworzenia gniazda");
        echoSocket = new Socket(ipAddress, DNS_SERVER_PORT);
        System.out.println("próba utworzenia strumienia wyjściowego");
        out = new PrintWriter(echoSocket.getOutputStream(), true);
        OutputStream outputStream = echoSocket.getOutputStream();
        System.out.println("próba utworzenia strumienia wejściowego");
        in = new BufferedReader(new InputStreamReader(echoSocket.getInputStream()));


        ObjectOutputStream os = new ObjectOutputStream(echoSocket.getOutputStream());
        os.flush();
        ObjectInputStream is = new ObjectInputStream(echoSocket.getInputStream());
        os.writeObject(dnsFrame);
        byte[] temp = (byte[]) is.readObject();
    } catch (UnknownHostException e) {
        System.err.println("Nieznany host: " + hostname + ".");
        System.exit(1);
    } catch (IOException e) {
        System.err.println("Błąd połączenia z " + hostname + ".");
        System.exit(1);
    }


    // zakończenie pracy - pozamykaj strumienie i gniazda
    out.close();
    in.close();
    echoSocket.close();

我使用wireshark读取我在TCP中实际发送的内容,它与UDP不同,所以我认为问题在于发送字节数组的过程中。

【问题讨论】:

  • "tcp sent chars" -- 是什么让你认为这是真的? ObjectOutputStream 用于 Java 对象,与 DataOutputStream 非常不同。您的 TCP 和 UDP 版本非常不同。
  • TCP 发送 八位字节。您不应该使用ReadersWriters,您应该使用与YDP 相同的DataInput/OutputStream,确实非常相同的代码,但使用Socket.getInput/OutputStream() 作为底层流而不是ByteArrayInput/OutputStreams.

标签: java sockets networking tcp dns


【解决方案1】:

DNS 的 TCP 协议与其 UDP 协议相同,但有一个区别 - 通过 TCP 发送的消息以网络字节顺序的 16 位整数为前缀,以指定消息字节长度。这在 UDP 上不是必需的,因为消息长度由数据报的大小决定。

根据RFC 1035,“域名 - 实施和规范”:

4.2。交通

DNS 假定消息将作为数据报或以 由虚电路承载的字节流。虽然虚拟电路可以 用于任何 DNS 活动,数据报优先用于查询,因为 它们的开销更低,性能更好。区域刷新活动 由于需要可靠传输,必须使用虚拟电路。

互联网支持在服务器上使用 TCP [RFC-793] 访问名称服务器 端口 53(十进制)以及在 UDP 上使用 UDP [RFC-768] 的数据报访问 端口 53(十进制)。

4.2.1。 UDP 使用情况

使用 UDP 用户服务器端口 53(十进制)发送的消息。

UDP 携带的消息限制为 512 字节(不包括 IP 或 UDP 标头)。较长的消息被截断并且 T​​C 位被设置 标题。

UDP 不能用于区域传输,但它是推荐的方法 用于 Internet 中的标准查询。使用 UDP 发送的查询可能是 丢失,因此需要重传策略。查询或其 响应可以由网络重新排序,或者通过名称处理 服务器,因此解析器不应依赖于它们按顺序返回。

最优的 UDP 重传策略会随着 互联网和客户的需求,但建议如下:

  • 在重复查询服务器的特定地址之前,客户端应该尝试其他服务器和服务器地址。

  • 如果可能,重传间隔应基于先前的统计信息。过于激进的重新传输很容易减慢整个社区的响应速度。根据客户端与其预期服务器的连接情况,最小重传间隔应为 2-5 秒。

更多关于服务器选择和重传策略的建议可以 可在本备忘录的解析器部分找到。

4.2.2。 TCP 使用情况

通过 TCP 连接发送的消息使用服务器端口 53(十进制)。 该 消息以两个字节长度字段为前缀,该字段给出消息长度,不包括两个字节长度字段。这个长度字段允许 在开始之前组装完整消息的低级处理 解析它

推荐几种连接管理策略:

  • 服务器不应阻塞等待 TCP 数据的其他活动。

  • 服务器应该支持多个连接。

  • 服务器应假定客户端将启动连接关闭,并应延迟关闭其结束的连接,直到所有未完成的客户端请求都得到满足。

  • 如果服务器需要关闭一个休眠连接来回收资源,它应该等到连接空闲大约两分钟。特别是,服务器应允许在单个连接上进行 SOA 和 AXFR 请求序列(开始刷新操作)。由于服务器无论如何都无法回答查询,因此可以使用单方面关闭或重置来代替正常关闭。

所以,你在 TCP 中所要做的就是:

  1. 将您的 TCP 套接字连接到 DNS 服务器

  2. 发送 DNS 查询时,按照与 UDP 中的完全相同的方式创建消息字节数组,然后在发送字节数组本身之前将字节数组的长度作为 16 位整数发送。

  3. 读取 DNS 响应时,先读取 16 位长度,然后读取它指定的字节数。

  4. 根据需要重复步骤 2-3,无论您需要发送多少查询。

  5. 完成后关闭连接。

另见:

RFC 7766: DNS Transport over TCP - Implementation Requirements

简而言之,不要使用基于字符串字符、对象等的 I/O 类,就像您目前正在尝试做的那样。 DNS 消息是二进制数据,在 UDP 和 TCP 中的格式完全相同。无论您使用哪种传输方式,您生成和解析 DNS 消息的代码都应该完全相同。唯一不同的是您如何传输/接收原始消息字节:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 1970-01-01
    • 2018-11-14
    • 2017-10-29
    • 1970-01-01
    相关资源
    最近更新 更多