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


1 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