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:
- Monitor both UDP and TCP port 53
- Implement packet inspection for DNS-over-TCP
- 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