Understanding ESTABLISHED State in UDP: How iptables Tracks Stateless Connections


1 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