Parsing CNAME, NS in DNS answer

Devanshu Misra Source

I have the following code that checks the query type in the DNS Response, and then further print it accordingly. I need a way to parse CNAME and NS using the parameters given but am unable to do so. Here tmp is a variable that is defined as tmp = (u_char *)(dpkt->payload + 12); and dns_label_to_str is a function to convert DNS Name in string format defined as : *dns_label_to_str(u_char **label, u_char *dest,size_t dest_size,const u_char *payload,const u_char *end)

Code:

   switch (qtype) {
            case 1: /* A */
                    data = inet_ntop(AF_INET, tmp, dbuf, BUFSIZ);
            break;
            case 2:  /* NS */
            case 5:  /* CNAME */
            case 12: /* PTR */
                    data = (char *)dns_label_to_str(
                            &tmp, (u_char *)dbuf, BUFSIZ,
                            dpkt->payload, tmp + len
                    );
            break;
            case 10: /* NULL */
                    data = "NULL";
            break;
            case 15: /* MX (16-bit priority / label) */
                    i = snprintf(dbuf, 7, "%u ", ntohs(*(uint16_t *)tmp));
                    tmp += 2;
                    data = (char *)dns_label_to_str(
                            &tmp, (u_char *)(dbuf + i), BUFSIZ - i,
                            dpkt->payload, tmp + len - 2
                    );
                    data = dbuf;
            break;
            case 16: /* TXT (1 byte text length / text) */
                    if (*tmp <= len && tmp + len < end) {
                            memcpy(dbuf, tmp+1, *tmp);
                            dbuf[*tmp+1] = '\0';
                    } else *dbuf = '\0';
                    data = dbuf;
            break;
            case 17: /* AAAA */
                    data = inet_ntop(AF_INET6, tmp, dbuf, BUFSIZ);
            break;
            default:
                    /* Ignore unhandled RR types */
                    *dbuf = '\0';
                    data = dbuf;
    }

    /* Print the output. */
    printf("%ld %-5s %-30s %s\n", hdr->ts.tv_sec,
           dns_types[qtype], label, data);

ret:
    return 0;
}

If somebody could help on how I can get the CNAME when qtype == 5 would be helpful. Thanks in advnce.

cdnsdecompressioncname

Answers

answered 5 days ago Florian Weimer #1

It is not entirely clear to me what the dns_label_to_str function does, but the most likely meaning of the parameters is:

  • label: Pointer to a pointer. *label initially points to the start of the name in the packet, and is updated to point to the next byte after the name in the packet.
  • dest: Address of the destination buffer. (This could be a string (text format) or an uncompressed name (wire format).
  • dest: Length of the destination buffer. Depending on the output format of the function, this should be at least 256 (wire format) or at least 1024 (text format, due to escaping). Of course, the destination buffer must be large enough, t oo.
  • payload: Pointer to the start of the entire DNS packet (not the data of the current resource record!).
  • end: Pointer to one past the last byte in th entire DNS packet.

The reason why functions like dns_label_to_str need to refer to the entire packet is DNS label compression: names can contain a compression reference which points to another, earlier place in the packet, reusing the tail of another name in the packet, at a smaller offset. These references will never point within the resource record data because for CNAME records, it contains only a single name: if compression is used, the name tail must come from somewhere else.

EDIT We have a bit of a debate in the comments about forward compression pointers. BIND 9 rejects them, but a lot of other implementations accept them.

answered 5 days ago Alnitak #2

The data at dpkt->payload + 12 is the Question Section, i.e. a copy of what was in the original request, and will comprise an (uncompressed) label known as QNAME and followed by the 16-bit QTYPE and QCLASS fields.

Only after that will you start to find the response data, each record of which follows this structure from RFC 1035, section 4, which I strongly suggest you read before going any further:

                                1  1  1  1  1  1
  0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                                               |
/                                               /
/                      NAME                     /
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      TYPE                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     CLASS                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      TTL                      |
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                   RDLENGTH                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/                     RDATA                     /
/                                               /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Note that the data structure in the Question Section is identical to the first three fields of the above.

comments powered by Disqus