dig versions and default EDNS UDP buffer size changes

When using the dig utility, be aware that the default EDNS buffer size advertised by dig has changed in version 9.18 from previous versions. This difference can affect dig's results. If using multiple versions of dig when troubleshooting or monitoring DNS servers, use the information in this article to ensure relevant results across different versions of dig.

Versions of dig

dig comes bundled with all ISC BIND distributions, vendors often provide older versions of ISC software. The version of dig present can vary widely depending on the operating system or other factors. Some Linux distributions may provide relatively recent packages, while other operating systems may provide older versions of dig. For example, macOS currently provides dig version 9.10.

Additionally, users may may install multiple executables on a given system, either via packages with pre-built binaries or binaries built from source code. So when using dig, be aware of the version, which can be found by running dig -v, and whether more than one version of dig exists on your system.

dig version numbers

In systems with both BIND and dig, the version number of dig matches the version number of BIND, as they are distributed together. Using matching versions of dig and BIND on the same system is not required, but it is recommended.

Buffer size and EDNS

The Extended DNS protocol (EDNS) allows clients and servers to advertise their maximum UDP buffer size, which increases the the original DNS specification's 512-byte limit on the size of DNS data in a UDP datagram. For additional information about EDNS0, please read the following documents:

In versions with EDNS support prior to 9.18, dig advertised a default EDNS buffer size of 4096 bytes. In version 9.18, the default was changed to 1232 bytes to comply with the recommendations produced by the 2020 DNS Flag Day. Add the +qr option on the dig command line to display query data in the dig output. The "EDNS" line will contain a udp parameter showing the maximum UDP buffer size that can be sent or received:

A query section from dig 9.16:

;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39594
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096

A query section from dig 9.18:

;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39594
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232

Effects on results

This difference in maximum UDP buffer size can affect dig's results, depending on a number of factors. The same query may produce different results using 9.16 and earlier versions with the larger maximum size versus 9.18 and later versions with their smaller maximum size. Differences can include slower response times or query failures due to DNS response truncation or to IP fragmentation. Let's use an example to illustrate these effects.

dig +qr 2048.size.dns.netmeister.org

In this example, the above query sent to the local recursive resolver receives a response with a large number of "A" resource records. The size of the complete answer is 2025 bytes.

Version 9.16 and earlier

Using dig 9.16 with a default maximum advertised UDP buffer size of 4096 bytes, the server responds with the complete answer over UDP. This response is fragmented into two packets because the MTU on the network segments traversed is 1500 bytes, which is less than 2025 bytes. IP Fragmentation of the UDP datagrams can be seen using packet capture software such as tcpdump or Wireshark, as seen in the image below:

UDP_fragment_wireshark.png

The first packet contains the request. The second packet is the first part of the response, and contains the maximum number of bytes for a network path whose MTU is 1500 bytes (total frame length = 1514 bytes, which includes 14 bytes of Ethernet header). The third packet contains the remaining bytes of the response.

Fragmented DNS packets may fail to pass through firewalls or similar security devices, which disallow them on security grounds, or are just not capable of processing the fragments. Failure to receive all of a packet's fragments typically manifests as a timeout error for the requester.

Current versions

Using dig 9.18 with a default maximum advertised UDP buffer size of 1232 bytes, the server sends back a truncated response (DNS header with the TC flag = 1) because the complete DNS message would contain 2025 bytes--more than the 1232 byte maximum. Automatically, dig then switches to TCP, re-queries and retrieves the complete answer as expected.

; <<>> DiG 9.18.27 <<>> -4 2048.size.dns.netmeister.org +qr
;; global options: +cmd
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25064
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: a5d66f43b1109b6b
;; QUESTION SECTION:
;2048.size.dns.netmeister.org.	IN	A

;; QUERY SIZE: 69

;; Truncated, retrying in TCP mode.
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52149
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: a5d66f43b1109b6b
;; QUESTION SECTION:
;2048.size.dns.netmeister.org.	IN	A

;; QUERY SIZE: 69

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52149
;; flags: qr rd ra; QUERY: 1, ANSWER: 123, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;2048.size.dns.netmeister.org.	IN	A

;; ANSWER SECTION:
2048.size.dns.netmeister.org. 300 IN	A	127.0.0.94
2048.size.dns.netmeister.org. 300 IN	A	127.0.0.6
<records removed for brevity>
2048.size.dns.netmeister.org. 300 IN	A	127.0.0.70

;; Query time: 3621 msec
;; SERVER: 192.168.1.254#53(192.168.1.254) (TCP)
;; WHEN: Wed May 22 23:03:31 PDT 2024
;; MSG SIZE  rcvd: 2025

Note the two "Sending sections". The second contains:
Truncated, retrying in TCP mode.

Also, in version 9.18, the Statistics section at the end includes the transport layer protocol used, in this case TCP:
SERVER: 192.168.1.254#53(192.168.1.254) (TCP).

In Wireshark, the conversation looks like:

UDP_to_TCP.png

The first frame contains the UDP request, and the second the response with the truncation flag set in the DNS header (TC=1). This causes dig to switch to TCP, starting with the TCP three-way handshake in frames 3 to 5. The remaining frames exchange the DNS data.

Use of TCP avoids IP fragmentation, but takes longer to complete the request. The initial UDP request must first complete, then a TCP-based request must be completed, which takes longer than a corresponding UDP request because TCP first performs its "handshake" (exchange of segments with the SYN bit set) to establish the connection before exchanging data.

Additionally, administrators sometimes make the mistake of not allowing their firewalls to permit DNS over TCP, so the TCP traffic may be rejected or silently dropped. This manifests as a connection refused or timeout error when using dig.

So, when using current versions of dig, the likelihood of receiving truncated responses is greater since the default maximum UDP size is now smaller, making TCP re-query more likely.

Legacy behavior and current versions

If users with version 9.18 or greater need to generate queries with the older maximum UDP size of 4096 bytes, add +bufsize=4096 to the dig command line, which overrides the default size of 1232 bytes. Similarly, older versions of dig can behave like newer versions with the default of 1232 bytes by adding +bufsize=1232 to the command line. Shell aliases can be used to simplify typing, such as dig4k="dig +bufsize=4096".

Alternatively, dig can be built from source with a different maximum as the default. In the 9.18 source code for dig, edit the file dighost.h in the ./bin/dig/ directory and replace the value of 1232 with the desired value in the line defining the DEFAULT_EDNS_BUFSIZE constant:

#define DEFAULT_EDNS_BUFSIZE 1232

In the 9.16 and earlier source code for dig, edit the file dig.h in the ./bin/dig/include/dig/ directory and replace the value of 4096 with the desired value for the same constant.