DNS (Domain Name System) operates primarily over UDP port 53, though TCP is used for larger responses. A DNS query consists of a header and question section, while responses include additional answer, authority, and additional sections.
Here's the breakdown of a standard DNS query:
+---------------------+ | Header | (12 bytes) +---------------------+ | Question | (variable) +---------------------+
The header contains:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
The question section contains the domain name being queried. For example, when resolving "example.com":
+---------------------+ | 7|e|x|a|m|p|l|e| (length-prefixed labels) +---------------------+ | 3|c|o|m| | +---------------------+ | 0 | (null terminator) +---------------------+ | Query Type | (e.g., A, AAAA, MX) +---------------------+ | Query Class | (typically IN for Internet) +---------------------+
A DNS response includes additional sections:
+---------------------+ | Header | +---------------------+ | Question | +---------------------+ | Answer | (resource records) +---------------------+ | Authority | (name server records) +---------------------+ | Additional | (extra info) +---------------------+
Here's how to examine DNS packets using Python's dnspython
library:
import dns.message import dns.query # Create a DNS query query = dns.message.make_query('example.com', 'A') # Print raw query bytes print("DNS Query Bytes:") print(query.to_wire()) # Send query and get response response = dns.query.udp(query, '8.8.8.8') # Print response structure print("\nDNS Response:") print(response) print("\nAnswer Section:") for answer in response.answer: print(answer)
Examining a DNS query for "google.com" in Wireshark shows:
Domain Name System (query) Transaction ID: 0x2d6c Flags: 0x0100 Standard query Questions: 1 Answer RRs: 0 Authority RRs: 0 Additional RRs: 0 Queries google.com: type A, class IN Name: google.com Type: A (Host Address) Class: IN (0x0001)
- DNS queries use a simple binary format with length-prefixed domain names
- The queried URL appears in the Question section as series of labels
- Responses include TTL values and resource record data
- Packet inspection tools like Wireshark reveal the complete structure
A DNS query is transmitted as a binary UDP packet (typically on port 53) with this basic structure:
+---------------------+
| Header |
+---------------------+
| Question |
+---------------------+
| Answer |
+---------------------+
| Authority |
+---------------------+
| Additional |
+---------------------+
The 12-byte header contains control flags and counters:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ID |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode |AA|TC|RD|RA| Z |AD|CD| RCODE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QDCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ANCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NSCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ARCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
The URL being resolved appears in the QNAME field as a series of labels:
; Example query for www.example.com
$ dig www.example.com +nocomments +nostats
; QUESTION SECTION:
;www.example.com. IN A
; Raw bytes representation (hex dump):
C0 18 00 01 00 01 00 00 00 00 03 77 77 77 07 65
78 61 6D 70 6C 65 03 63 6F 6D 00 00 01 00 01
A typical DNS response contains these additional sections:
;; ANSWER SECTION:
www.example.com. 3600 IN A 93.184.216.34
;; AUTHORITY SECTION:
example.com. 172800 IN NS a.iana-servers.net.
;; ADDITIONAL SECTION:
a.iana-servers.net. 172800 IN A 199.43.135.53
Here's Python code to decode DNS packets:
import socket
import struct
import dnslib
def capture_dns_query():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', 5353)) # Capture port
while True:
data, addr = sock.recvfrom(1024)
try:
dns_data = dnslib.DNSRecord.parse(data)
print("Received query:")
print(dns_data)
# Extract domain from question section
if dns_data.questions:
qname = str(dns_data.questions[0].qname)
print(f"Query for: {qname}")
except dnslib.DNSError as e:
print(f"Invalid DNS packet: {e}")
Field | Bytes | Description |
---|---|---|
ID | 2 | Transaction identifier |
Flags | 2 | Control bits (QR, OPCODE, etc.) |
QDCOUNT | 2 | Number of questions |
ANCOUNT | 2 | Number of answers |
NSCOUNT | 2 | Number of authority records |
ARCOUNT | 2 | Number of additional records |
- A (1): IPv4 address
- AAAA (28): IPv6 address
- CNAME (5): Canonical name
- MX (15): Mail exchange
- TXT (16): Text strings