At first glance, the concept of connection state (ESTABLISHED
) in UDP seems contradictory since UDP is fundamentally stateless. However, Linux's connection tracking system (conntrack
) implements a pseudo-state mechanism that's crucial for firewall operations.
# Typical DNS rule example
iptables -A OUTPUT -p udp --sport 1024:65535 --dport 53 \
-m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -p udp --sport 53 --dport 1024:65535 \
-m state --state ESTABLISHED -j ACCEPT
The system maintains an internal table tracking:
- Source IP:port and destination IP:port pairs
- Protocol (UDP in this case)
- Timeout values (typically 30-180 seconds)
While this provides basic stateful filtering, there are important caveats:
# View active conntrack entries
conntrack -L -p udp
The security relies on:
- Randomized source ports (1024-65535 range)
- Short timeout windows (default 30 seconds)
- Two-way packet verification
The kernel's connection tracking occurs before normal port binding:
# Example of potential conflict
nc -l -u 5353 & # Bind to port
dig @8.8.8.8 example.com +short # Might use same port
The system handles this by:
- Giving precedence to existing listeners
- Failing new outbound connections if port in use
- Not preventing new services from binding
For more robust DNS filtering:
# More secure DNS ruleset
iptables -N DNS_FILTER
iptables -A DNS_FILTER -p udp --dport 53 -m recent --set --name dnsquery
iptables -A DNS_FILTER -p udp --sport 53 -m recent --rcheck --seconds 5 --name dnsquery -j ACCEPT
iptables -A OUTPUT -p udp -j DNS_FILTER
Key improvements:
- Stricter time window (5 seconds)
- Explicit query/response tracking
- Better isolation from other UDP traffic
Essential diagnostic commands:
# Watch conntrack events in real-time
conntrack -E
# Check iptables counters
iptables -L -v -n
# Verify kernel parameters
sysctl -a | grep net.netfilter.nf_conntrack_udp_timeout
When examining common iptables configurations for DNS traffic, we encounter this apparent contradiction:
iptables -A OUTPUT -p udp --sport 1024:65535 --dport 53 \
-m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -p udp --sport 53 --dport 1024:65535 \
-m state --state ESTABLISHED -j ACCEPT
Despite UDP being connectionless, Linux's connection tracking system (conntrack) implements a pseudo-state mechanism:
- When a UDP packet leaves the system (NEW state), conntrack creates an entry with:
Source IP:Port → Destination IP:Port
- This entry typically lasts 30 seconds (configurable via
/proc/sys/net/netfilter/nf_conntrack_udp_timeout
) - Any reply packet matching this tuple within the timeout is considered ESTABLISHED
Your intuition about port guessing is correct but incomplete:
# Show active conntrack entries:
conntrack -L -p udp
An attacker would need to:
- Guess both the ephemeral client port AND the transaction ID in DNS payload
- Complete this within the UDP timeout window
- Overcome any rate-limiting on your firewall
The kernel handles port allocation intelligently:
# Check used ports during DNS query:
ss -ulnp | grep 53
netstat -ulnp | grep 53
If you attempt to bind a service to a port during the timeout:
- The bind() call will fail with EADDRINUSE if the port is still tracked
- This prevents accidental conflicts with ongoing DNS transactions
For production systems, consider these enhanced rules:
# Adjust UDP timeout (seconds)
echo 15 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout
# Rate-limited DNS rules
iptables -A OUTPUT -p udp --sport 1024:65535 --dport 53 \
-m state --state NEW,ESTABLISHED \
-m hashlimit --hashlimit-name dnsout \
--hashlimit 5/second --hashlimit-burst 10 \
-j ACCEPT
iptables -A INPUT -p udp --sport 53 --dport 1024:65535 \
-m state --state ESTABLISHED \
-m hashlimit --hashlimit-name dnsin \
--hashlimit 5/second --hashlimit-burst 10 \
-j ACCEPT
To monitor actual behavior:
# Watch conntrack events in real-time:
conntrack -E
# Test DNS resolution while monitoring:
tcpdump -ni eth0 port 53