Parsing DNS response Answer section doesn't give expected results

Viewed 140

I'm trying to parse a DNS response using java. I'm following RFC-1035 for guidelines on how to send requests and receieve responses, the format that is.

According to said RFC the answer section of a response should look like so:

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                                               |
/                                               /
/                      NAME                     /
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      TYPE                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     CLASS                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      TTL                      |
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                   RDLENGTH                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/                     RDATA                     /
/                                               /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

I'm trying to parse the NAME section.

According to section 4.1.4. Message compression I should be able to get the first 2 bits and determine if the next byte is either a label or a pointer.

Here's my code so far, the request works just fine for a A record of google.com using CouldFlare as a DNS

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Random;

public class Stuff {
    private static final String DNS_SERVER_ADDRESS = "1.1.1.1";
    private static final int DNS_SERVER_PORT = 53;

    public static void main(String[] args) throws IOException {

        String domain = "google.com";
        InetAddress ipAddress = InetAddress.getByName(DNS_SERVER_ADDRESS);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        //Whenever an octet represents a numeric quantity, the left most bit in
        //the diagram is the high order or most significant bit.
        //this goes for requests as well as responses


       short requestFlags = Short.parseShort("0000000100000000", 2);
        ByteBuffer bytes = ByteBuffer.allocate(2).putShort(requestFlags);
        byte[] array = bytes.array();
        dos.writeShort(1234); // randomly chosen id
        dos.write(array);
        dos.writeShort(1); // QDCOUNT
        dos.writeShort(0); // ANCOUNT
        dos.writeShort(0); // NSCOUNT
        dos.writeShort(0); // ARCOUNT

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

        for (int i = 0; i < domainParts.length; i++) {
            System.out.println("Writing: " + domainParts[i]);
            byte[] domainBytes = domainParts[i].getBytes(StandardCharsets.UTF_8);
            dos.writeByte(domainBytes.length);
            dos.write(domainBytes);
        }

        // No more parts
        dos.writeByte(0);
        // Type 0x01 = A (Host Request)
        dos.writeShort(1);
        // Class 0x01 = IN
        dos.writeShort(1);

        byte[] dnsFrame = baos.toByteArray();

        System.out.println("Sending: " + dnsFrame.length + " bytes");
        for (int i = 0; i < dnsFrame.length; i++) {
            System.out.print(String.format("%s", dnsFrame[i]) + " ");
        }

        DatagramSocket socket = new DatagramSocket();
        DatagramPacket dnsReqPacket = new DatagramPacket(dnsFrame, dnsFrame.length, ipAddress, DNS_SERVER_PORT);
        socket.send(dnsReqPacket);

        // Await response from DNS server
        byte[] buf = new byte[512];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        socket.receive(packet);

        System.out.println("\n\nReceived: " + packet.getLength() + " bytes");

        for (int i = 0; i < packet.getLength(); i++) {
            System.out.print(String.format("%s", buf[i]) + " ");
        }
        System.out.println("\n");
        //All communications inside of the domain protocol are carried in a single
        //format called a message.  The top level format of message is divided
        //into 5 sections (some of which are empty in certain cases) shown below:
        //    +---------------------+
        //    |        Header       |
        //    +---------------------+
        //    |       Question      | the question for the name server
        //    +---------------------+
        //    |        Answer       | RRs answering the question
        //    +---------------------+
        //    |      Authority      | RRs pointing toward an authority
        //    +---------------------+
        //    |      Additional     | RRs holding additional information
        //    +---------------------+

        // HEADER
        //                                    1  1  1  1  1  1
        //      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                      ID                       |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                    QDCOUNT                    |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                    ANCOUNT                    |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                    NSCOUNT                    |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                    ARCOUNT                    |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

        DataInputStream din = new DataInputStream(new ByteArrayInputStream(buf));
        System.out.println("\n\nStart response decode");
        System.out.println("Transaction ID: " + din.readShort()); // ID
        short flags = din.readByte();
        int QR = (flags & 0b10000000) >>> 7; //QR
        int opCode = ( flags & 0b01111000) >>> 3; //Opcode
        int AA = ( flags & 0b00000100) >>> 2; //AA
        int TC = ( flags & 0b00000010) >>> 1; //TC
        int RD =  flags & 0b00000001;//RD
        System.out.println("QR "+QR);
        System.out.println("Opcode "+opCode);
        System.out.println("AA "+AA);
        System.out.println("TC "+TC);
        System.out.println("RD "+RD);
        flags = din.readByte();
        int RA = (flags & 0b10000000) >>> 7;//RA
        int Z = ( flags & 0b01110000) >>> 4;//Z
        int RCODE =  flags & 0b00001111;//RCODE
        System.out.println("RA "+RA);
        System.out.println("Z "+ Z);
        System.out.println("RCODE " +RCODE);

        System.out.println("Questions: " + String.format("%s", din.readShort())); //QDCOUNT
        System.out.println("Answers RRs: " + String.format("%s", din.readShort())); //ANCOUNT
        System.out.println("Authority RRs: " + String.format("%s", din.readShort())); //NSCOUNT
        System.out.println("Additional RRs: " + String.format("%s", din.readShort())); //ARCOUNT

        // Question
        //      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                                               |
        //    /                     QNAME                     /
        //    /                                               /
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                     QTYPE                     |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                     QCLASS                    |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        int recLen;
        while ((recLen = din.readByte()) > 0) {
            byte[] record = new byte[recLen];
            for (int i = 0; i < recLen; i++) {
                record[i] = din.readByte();
            }
            System.out.println("Record: " + new String(record, StandardCharsets.UTF_8)); //QNAME
        }
        System.out.println("Record Type: " + String.format("%s", din.readShort()));//QTYPE
        System.out.println("QCLASS - Class: " + String.format("%s", din.readShort())); // QCLASS
        System.out.println("\n\nstart answer, authority, and additional sections\n");

        //The answer, authority, and additional
        //      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                                               |
        //    /                                               /
        //    /                      NAME                     /
        //    |                                               |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                      TYPE                     |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                     CLASS                     |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                      TTL                      |
        //    |                                               |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    |                   RDLENGTH                    |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
        //    /                     RDATA                     /
        //    /                                               /
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        byte firstBytes = din.readByte();
        int firstTwoBits = (firstBytes & 0b11000000) >>> 6;
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        //    | 1  1|                OFFSET                   |
        //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        // 11 for offset
        //(The 10 and 01 combinations
        //are reserved for future use.)
        // 00 for label
        //label must begin with two zero bits because
        //labels are restricted to 63 octets or less
        System.out.println(firstTwoBits);
        if(firstTwoBits == 3) {
            System.out.println("It's a pointer");
        }else if(firstTwoBits == 0){
            System.out.println("It's a label");
        }

    }
}

And the printed info

google.com has 2 parts
Writing: google
Writing: com
Sending: 28 bytes
4 -46 1 0 0 1 0 0 0 0 0 0 6 103 111 111 103 108 101 3 99 111 109 0 0 1 0 1 

Received: 44 bytes
4 -46 -127 -128 0 1 0 1 0 0 0 0 6 103 111 111 103 108 101 3 99 111 109 0 0 1 0 1 -64 12 0 1 0 1 0 0 1 27 0 4 -114 -5 37 110 



Start response decode
Transaction ID: 1234
QR 1
Opcode 0
AA 0
TC 0
RD 1
RA 1
Z 0
RCODE 0
Questions: 1
Answers RRs: 1
Authority RRs: 0
Additional RRs: 0
Record: google
Record: com
Record Type: 1
QCLASS - Class: 1


start answer, authority, and additional sections

3
It's a pointer

Process finished with exit code 0

Comparing this with a command line DNS lookup tool

id 1329, opcode QUERY, rcode NOERROR, flags QR RD RA
;QUESTION
google.com. IN A
;ANSWER
google.com. 257 IN A 142.250.81.206
;AUTHORITY
;ADDITIONAL

The result I get seems valid.

My problem is that I can't seem to parse the NAME in the answer section. It seems to start with a pointer which makes no sense.

Clearly I'm doing something wrong here but I can't tell what.

I understand that what I'm asking is vague but then again if I knew what the issue was I wouldn't be here.

I've also used Wireshark to incercept a request I did to make sure the response is correct. It seems to be correct indeed(I've obviously changed the url to stackoverflow.com)

enter image description here

1 Answers

My problem is that I can't seem to parse the NAME in the answer section. It seems to start with a pointer which makes no sense.

I probably know at lot less about this than you but am wondering why you say that? firstByte is telling you there's a pointer and the following value (0x0c) shows you the offset of the name for compression purposes (if I've got that right). None of the other bits in the same byte as firstByte is set so that can be ignored from the point of view of the offset value

Related