Understanding ESTABLISHED State in UDP: How iptables Tracks Stateless Connections


20 views

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:

  1. Randomized source ports (1024-65535 range)
  2. Short timeout windows (default 30 seconds)
  3. 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