Asymmetric Routing with Stateful Firewall Dropping Packets
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://ipfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://ipfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://ipfyi.com/entity//)
Use the native HTML custom element.
Traffic flows correctly in one direction but responses are silently dropped, causing connections to appear established but never transferring data. A stateful firewall sees the return traffic arriving on a different interface than the one the original flow entered, so it has no session record for the packet and drops it as unsolicited. This occurs in multi-homed servers, load-balanced environments, and networks with redundant uplinks.
Symptoms
- ⚠ TCP connections appear established (SYN/SYN-ACK seen) but no data transfers
- ⚠ One-directional ping succeeds (request) but replies never arrive
- ⚠ traceroute shows different outbound and inbound paths
- ⚠ tcpdump on the server shows incoming packets and outgoing replies, but the client receives nothing
- ⚠ Intermittent connectivity that depends on which load balancer or gateway is selected
- ⚠ Connection works when source IP is from one subnet but not another
Possible Root Causes
- • Multi-homed server receives traffic on one NIC (eth0/ISP-A) but routes replies via the default gateway on another NIC (eth1/ISP-B)
- • Stateful firewall or Linux conntrack tracks connections per-interface and marks return packets from unexpected interface as INVALID
- • Missing Linux policy routing rules — `ip rule` entries needed to route each source address back through its respective gateway
- • Load balancer sends new connections through one upstream path but ECMP or failover redirects return traffic through a second path
- • NAT device on one path rewrites source IPs inconsistently, causing the server to use a different gateway for responses
Diagnosis Steps
Step 1: Confirm Asymmetric Paths with Traceroute
# From client to server
traceroute 203.0.113.50
# From server to client (using mtr for continuous view)
mtr --report --report-cycles 10 192.0.2.100
# Compare the paths — they should be symmetric; different paths = asymmetric routing
Step 2: Capture Traffic on Both Interfaces
# On the multi-homed server, capture on all interfaces simultaneously
sudo tcpdump -n -i eth0 host 192.0.2.100 &
sudo tcpdump -n -i eth1 host 192.0.2.100 &
# Look for:
# - Inbound traffic on eth0 (ISP A)
# - Outbound replies on eth1 (ISP B)
# This is asymmetric — the firewall tracks state per-interface and drops the reply
Step 3: Check Stateful Firewall Logs
# iptables: check for INVALID state drops
sudo iptables -L FORWARD -n -v | grep DROP
sudo iptables -L INPUT -n -v | grep DROP
# Enable conntrack logging to see why packets are dropped:
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j LOG --log-prefix "INVALID: "
sudo tail -f /var/log/kern.log | grep INVALID
# nftables
sudo nft list ruleset | grep drop
Step 4: Check Routing Policy (Linux)
# Linux policy routing — show all routing tables
ip rule show
# Expected for dual-NIC: separate rules routing per-interface traffic back out the same interface
# Absence of rules = default table used for all traffic = asymmetric
ip route show table 100 # Custom routing table for eth1
ip route show table 200 # Custom routing table for eth2
Step 5: Verify Connection Tracking State
# Show active conntrack entries
sudo conntrack -L | grep 192.0.2.100
# Packets marked INVALID have no matching conntrack entry — they are not part of a known flow
sudo conntrack -L --output extended | grep INVALID
Solution
Fix: Implement Linux Policy Routing (Source-Based Routing)
Create per-interface routing tables that force replies out the same interface the request arrived on:
# /etc/iproute2/rt_tables: add table IDs
echo "100 isp_a" | sudo tee -a /etc/iproute2/rt_tables
echo "200 isp_b" | sudo tee -a /etc/iproute2/rt_tables
# eth0 = ISP-A (203.0.113.50/24, gateway 203.0.113.1)
# eth1 = ISP-B (198.51.100.50/24, gateway 198.51.100.1)
# Table isp_a: all traffic from 203.0.113.50 exits via eth0
sudo ip route add default via 203.0.113.1 dev eth0 table isp_a
sudo ip rule add from 203.0.113.50 lookup isp_a priority 100
# Table isp_b: all traffic from 198.51.100.50 exits via eth1
sudo ip route add default via 198.51.100.1 dev eth1 table isp_b
sudo ip rule add from 198.51.100.50 lookup isp_b priority 200
# Verify:
ip rule show
ip route show table isp_a
Make permanent using /etc/network/interfaces post-up directives or Netplan.
Fix: Disable conntrack INVALID Drop (Temporary Workaround)
# If policy routing cannot be implemented immediately, allow INVALID packets through:
sudo iptables -D INPUT -m conntrack --ctstate INVALID -j DROP
# WARNING: This reduces security — only use as a short-term measure while implementing source routing
Fix: Configure ECMP to Use Per-Flow Hashing
If the asymmetry is caused by ECMP, ensure the hash function is flow-based (5-tuple: src IP, dst IP, src port, dst port, protocol) so forward and return paths use the same hash:
# Linux kernel: ECMP hash policy
sudo sysctl net.ipv4.fib_multipath_hash_policy=1 # 0=L3, 1=L4 (5-tuple)
Prevention
- Design multi-homed servers with source-based policy routing from day one; document the routing tables and rules in the infrastructure repo
- Test asymmetric routing explicitly during network changes by running
mtrin both directions and comparing hop counts - Use
--ctstate ESTABLISHED,RELATEDin firewall accept rules and log all INVALID drops during initial deployment - In cloud environments, rely on the cloud provider's routing guarantees rather than bare-metal multi-NIC policy routing when possible
- Implement network monitoring that alerts when inbound and outbound path MTU or hop counts diverge significantly