DNS Query Protocol Analysis: When Do DNS Queries Use TCP Instead of UDP?


12 views

While DNS primarily uses UDP (port 53) for queries due to its low overhead, there are specific scenarios where TCP becomes necessary:

// Example DNS query using UDP in Python
import socket

def udp_dns_query(domain):
    dns_server = "8.8.8.8"
    query = create_dns_query(domain)  # hypothetical function
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(query, (dns_server, 53))
    response = sock.recv(512)
    return response

RFC 1035 section 4.2.1 specifies TCP must be used when:

  • Response data exceeds 512 bytes (including EDNS0)
  • Zone transfers (AXFR/IXFR) occur
  • DNSSEC validation requires larger payloads
# TCP DNS query example
def tcp_dns_query(domain):
    dns_server = "8.8.8.8"
    query = create_dns_query(domain)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((dns_server, 53))
    # Prepend 2-byte length field for TCP
    sock.send(len(query).to_bytes(2, 'big') + query)
    response_length = int.from_bytes(sock.recv(2), 'big')
    response = sock.recv(response_length)
    return response

For comprehensive monitoring:

  1. Monitor both UDP and TCP port 53
  2. Implement packet inspection for DNS-over-TCP
  3. Consider EDNS0 buffer size advertisement

Using tshark to capture both protocols:

tshark -i eth0 -Y "dns and (tcp.port == 53 or udp.port == 53)" -T fields \
  -e frame.time -e ip.src -e ip.dst -e dns.qry.name

Attackers may leverage TCP for:

  • Data exfiltration through large TXT records
  • TCP's reliable delivery for C2 communications
  • Evading UDP-based detection systems

While DNS primarily uses UDP (port 53) for its lightweight efficiency, RFC 1035 explicitly allows TCP usage in specific scenarios. The 512-byte UDP limitation isn't just about domain name length - it encompasses the entire DNS message including headers and record data.

TCP kicks in for DNS when:

1. Response exceeds 512 bytes (truncated UDP response with TC bit set)
2. Zone transfers (AXFR/IXFR)
3. EDNS0 allows larger UDP payloads, but fallback to TCP when:
   - Server doesn't support EDNS0
   - Intermediate devices block large UDP packets
4. DNSSEC validation requiring larger responses

Even for pure queries (not responses), TCP may be used when:

// Example of forced TCP query in Python using dnspython
import dns.query
import dns.message

query = dns.message.make_query('large-domain.example', 'ANY')
response = dns.query.tcp(query, '8.8.8.8')  # Explicit TCP transport

Malicious actors sometimes use TCP for DNS tunneling to bypass UDP size restrictions.

For comprehensive monitoring:

# tcpdump example capturing both UDP and TCP DNS
tcpdump -i eth0 'port 53 and (udp or tcp)' -w dns_full.pcap

# Alternative BPF filter for queries only
tcpdump -i eth0 '(udp and port 53 and dst port 53) or 
                 (tcp and port 53 and tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn)'

The DNS resolution process follows this flow:

Client                          Server
------                          ------
UDP Query ------------------->
<------------------- UDP Response (or TC=1 if truncated)
(if TC=1)
TCP Query ------------------->
<------------------- TCP Response

In your security monitoring setup:

  • TCP DNS typically represents <2% of total traffic
  • Malicious DNS tunneling often uses TCP for larger exfiltration
  • Modern resolvers (like systemd-resolved) may use TCP more frequently