TCP上的DNS查询

时间:2017-01-06 19:05:50

标签: java sockets networking tcp dns

我现在拥有的是通过UDP进行DNS查询并且它正常工作,但是如果消息被截断并且我需要通过TCP重新连接相同的查询我不能这样做,主要的问题是我确实有字节查询array和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不同,所以我假设问题正在发送字节数组。

1 个答案:

答案 0 :(得分:5)

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

Per RFC 1035,"域名 - 实施和规范":

  

4.2。传输

     

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

     

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

     

4.2.1。 UDP使用

     

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

     

UDP承载的消息限制为512字节(不包括IP   或UDP标头)。截断更长的消息并设置TC位   标题。

     

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. 完成后关闭连接。

  6. 另见:

    RFC 7766: DNS Transport over TCP - Implementation Requirements

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