Parse DNS Packet requests and responses

Are there any libraries I can use to parse DNS packet requests and responses? Otherwise, I'll have to implement my own.

I would check https://swiftpackageindex.com when looking for existing libraries. A quick search reveals there are at least a few DNS related packages listed there, whether they will suit your needs though I can't say.

1 Like

If you’re working on Apple platforms there’s always <dns_util.h> and specifically the dns_parse_packet function.

This is a low-level C API, so let us know if you need help calling it from Swift.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

That's perfect! How can I add the header file and use it within a Swift Xcode project? Also pardon if I'm misunderstanding, but is there a way to convert a dns_reply_t object into a character buffer to send out through the wire?

What I want to be able to do is:

  1. Parse a DNS request
  2. Construct a DNS Response and convert it into Swift's Foundation Data object so that I can send it through the network

How can I add the header file and use it within a Swift Xcode project?

If you’re building non-library code, you can simply include <dns_util.h> in your bridging header. If you’re building library code, you’ll need to build a wrapper module.

Oh, and the functions are exported by the libresolv.tbd stub library.

Also pardon if I'm misunderstanding, but is there a way to convert a
dns_reply_t object into a character buffer to send out through the
wire?

No. The API is for parsing, not rendering, DNS packets.

As an example, this code:

let packet: [UInt8] = [
    0x21, 0xD9, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x01, 0x07, 0x65, 0x78, 0x61,
    0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D,
    0x00, 0x00, 0x01, 0x00, 0x01, 0xC0, 0x0C, 0x00,
    0x01, 0x00, 0x01, 0x00, 0x00, 0xB2, 0x3E, 0x00,
    0x04, 0x5D, 0xB8, 0xD8, 0x22, 0x00, 0x00, 0x29,
    0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
let replyQ = packet.withUnsafeBytes { buf -> UnsafeMutablePointer<dns_reply_t>? in
    // Many C APIs, including this one, use `char` values for raw bytes.  On
    // Apple platforms `char` is equivalent to `SInt8`.  However, on the
    // Swift side we typically use `UInt8` for raw bytes and thus so we have
    // to do an ugly cast.
    let base = buf.baseAddress!.assumingMemoryBound(to: Int8.self)
    return dns_parse_packet(base, UInt32(buf.count))
}
guard let reply = replyQ else { fatalError() }
// The 0xffff should be a combinary of `DNS_PRINT_` flags but there’s no
// ‘all’ flag so I’m taking a shortcut.
dns_print_reply(reply, stdout, 0xffff)

prints this:

Xid: 8665
QR: Reply
Server: -nil-
Opcode: Standard
AA: Non-Authoritative
TC: Non-Truncated
RD: Recursion desired
RA: Recursion available
Rcode: No error
Question (1):
example.com IN A    
Answer (1):
example.com IN A     45630 93.184.216.34
Authority (0):
Additional records (1):
 ?? OPT   0 0  ()

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

Hello Quinn, can this library be used in the case where I have multiple answers in my reply. If so how could I obtain them?

dns_parse_packet returns a dns_reply_t structure which has this definition:

typedef struct
{
    …
	dns_header_t *header;
    …
    dns_resource_record_t **answer;
    …
} dns_reply_t;

The header contains an ancount field that tells you how many entries there are in the answer array. So, to expand on my example above:

let packet: [UInt8] = [
    0x47, 0x49, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
    0x00, 0x00, 0x00, 0x01, 0x03, 0x77, 0x77, 0x77,
    0x05, 0x61, 0x70, 0x70, 0x6C, 0x65, 0x03, 0x63,
    0x6F, 0x6D, 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0,
    0x0C, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x06,
    0x30, 0x00, 0x1B, 0x03, 0x77, 0x77, 0x77, 0x05,
    0x61, 0x70, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F,
    0x6D, 0x07, 0x65, 0x64, 0x67, 0x65, 0x6B, 0x65,
    0x79, 0x03, 0x6E, 0x65, 0x74, 0x00, 0xC0, 0x2B,
    0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x38, 0x58,
    0x00, 0x32, 0x03, 0x77, 0x77, 0x77, 0x05, 0x61,
    0x70, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D,
    0x07, 0x65, 0x64, 0x67, 0x65, 0x6B, 0x65, 0x79,
    0x03, 0x6E, 0x65, 0x74, 0x0B, 0x67, 0x6C, 0x6F,
    0x62, 0x61, 0x6C, 0x72, 0x65, 0x64, 0x69, 0x72,
    0x06, 0x61, 0x6B, 0x61, 0x64, 0x6E, 0x73, 0x03,
    0x6E, 0x65, 0x74, 0x00, 0xC0, 0x52, 0x00, 0x05,
    0x00, 0x01, 0x00, 0x00, 0x0D, 0xBA, 0x00, 0x1B,
    0x05, 0x65, 0x36, 0x38, 0x35, 0x38, 0x04, 0x64,
    0x73, 0x63, 0x78, 0x0A, 0x61, 0x6B, 0x61, 0x6D,
    0x61, 0x69, 0x65, 0x64, 0x67, 0x65, 0x03, 0x6E,
    0x65, 0x74, 0x00, 0xC0, 0x90, 0x00, 0x01, 0x00,
    0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x02,
    0x14, 0x5E, 0xB1, 0x00, 0x00, 0x29, 0x04, 0xD0,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
… packet parsing code as above …
let answers = UnsafeBufferPointer(start: reply.pointee.answer, count: Int(reply.pointee.header.pointee.ancount))
for a in answers {
    print(a!.pointee)
}

which prints:

dns_resource_record_t(name: …, dnstype: 5, dnsclass: 1, ttl: 1584, data: …)
dns_resource_record_t(name: …, dnstype: 5, dnsclass: 1, ttl: 14424, data: …)
dns_resource_record_t(name: …, dnstype: 5, dnsclass: 1, ttl: 3514, data: …)
dns_resource_record_t(name: …, dnstype: 1, dnsclass: 1, ttl: 5, data: …)

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

Something seems to be wrong with this example (Parse DNS Packet requests and responses - #5 by eskimo). The call to dns_parse_packet() returns nil. I've reimplemented the equivalent code in C as a personal sanity check and I get the same result. I've tried variously on iOS 13, iOS 15, and MacOS 12.4.

I must clearly be overlooking something obvious, but I'm at a loss as to what.

Substituting the packet payload with this one (sniffed from an actual connection) works:

1, 147, 129, 128, 0, 1, 0, 0, 0, 0, 0, 0, 6, 117, 112, 108, 111, 97, 100, 9, 119, 105, 107, 105, 109, 101, 100, 105, 97, 3, 111, 114, 103, 0, 0, 65, 0, 1

but with many other payloads, it fails.

Any hints?

  • not sure if it is the actual source, if not then how far from actual source is it, and how easy it is to compile it but, you may try compiling it in from the source form.
  • same but debug that source manually on the paper (if it's too hard to compile it).
  • other than that you may diff the two packets, one that works another that doesn't, and minimise the differences going either from working packet to not working packet or vice a versa until it stops working (starts working) - then you'll figure out why it doesn't work (and as an added bonus become DNS packet format expert) :wink:

Thanks for the ideas Tera. Immediately after posting I actually did as you suggested—I built libresolv from source (the most recent version available form there, v68; dns_util.c has a few dependencies so that file itself doesn't stand alone). Then, linking my test code with the built library, it works! All packets are parsed.

However, as soon as I clean project, and instead link again with the system libresolv.tbd (or libresolv.9.tbd), 4 out of 5 of my test payloads fail to parse.

oops...

When you built it yourself was it release build? Just checking.

Other than that, somehow figure out the version of sources used in libresolv.tbd (how?) find the exact sources (where?) and compile that from source. Or follow the trial and error procedure in the bullet point above - I know, that can be time consuming. There's always gdb/lldb - assuming the source used and the sources you see are not too much different it won't be too hard to debug it (again, can take a day or three).

That could be a profitable exercise for someone with the wherewithal—alas, for now I've given up on dns_utils (presuming the shipped implementations to be broken), and have implemented a third-party Swift parser that seems to work nicely.

dns_parse_packet was broken by a recent security fix. See this thread on DevForums for the details. We hope to fix this sooner rather than later but the only concrete thing I can say is that the fix is not in the iOS 15.6b3 (19G5046d) that we’re currently seeding.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like