Australian cloud for business software — built and run in Sydney since 2003

NetLeverage
NetLeverage

Connections Half-Work Through Two Gateways: Proving Asymmetric Routing

Technical article
Troubleshooting

Direct answer: when a server or network has more than one way out, a request can arrive by one path and the reply leave by another. The usual cases are:

  • two firewalls or internet links;
  • a second NIC on a VM for backup or management traffic;
  • a VPN appliance beside the main gateway.

Stateful devices track connections, so the firewall that saw only the reply has no connection for it and drops it. The firewall that saw only the request never sees the handshake complete and times it out. A Linux host doing strict reverse-path filtering can drop packets that arrive on an interface it would not use to reach their source.

The symptoms are connections that hang at the handshake, reset after a few seconds, or work from some networks and not others. Prove it with captures on both paths at the same time, and with the connection state on each stateful device. Then fix the routing so each flow returns by the path it arrived on. Policy routing, source NAT on the second path, or removing the second default gateway are all options. Disabling the firewall's state checks is not the fix.

Why it matters

Asymmetric routing faults are easy to misdiagnose:

  • ping often works, because ICMP echo is handled differently from TCP state on many devices, or because the ping happens to take a symmetric path;
  • small requests sometimes succeed, when they fit in packets that do not depend on established state;
  • the failure is often limited to one source network, so it looks like an application or ACL problem.

Common triggers are adding a second NIC to a VM for backups, adding a second internet link or a site-to-site VPN, moving a VM between port groups during a restore, and bringing up a standby firewall that also routes.

The static IP article under Related articles covers addresses lost after a NIC change. This article assumes the addressing is correct, but the return path is wrong.

How it works

State tracking on a firewall

A stateful firewall builds a connection entry from the first packet it sees, typically the TCP SYN. It then accepts later packets that match the entry and the expected TCP sequence window. Asymmetric routing breaks this in two ways:

  1. Firewall A sees the SYN, but the SYN-ACK goes back through firewall B. Firewall A waits for a handshake that never completes through it. Firewall B sees a SYN-ACK with no matching connection and, under a typical "drop invalid" policy, discards it.
  2. Both firewalls see part of an established flow. Each sees only half the sequence numbers and may classify packets as out of window.

On Linux netfilter, the kernel documentation describes the relevant switches:

  • nf_conntrack_tcp_loose (enabled by default) allows already established connections to be picked up mid-stream.
  • nf_conntrack_tcp_be_liberal (disabled by default), when enabled, marks only out-of-window RST segments as INVALID.

These settings show why mid-stream pickup sometimes masks the fault. They are not a fix for a routing design.

Reverse-path filtering on Linux hosts

The Linux rp_filter setting validates the source of incoming packets:

  • 0: no source validation.
  • 1: strict mode, as RFC 3704 Strict Reverse Path. A packet fails if the interface it arrived on is not the best reverse path to its source.
  • 2: loose mode. A packet fails only if its source is not reachable through any interface.

The kernel documentation states that "if using asymmetric routing or other complicated routing, then loose mode is recommended". It also notes that the maximum of conf/all/rp_filter and the per-interface value is the one used, and that the kernel default is 0 but some distributions enable it at startup.

So a Linux VM with two NICs, receiving traffic on the "wrong" one, may discard it silently before any application or firewall sees it.

RFC 3704 itself notes that strict reverse-path forwarding "is only applicable in places where routing is symmetrical".

Windows host model

Windows sets the host model per interface. The Set-NetIPInterface reference describes WeakHostSend and WeakHostReceive: "By default, interface properties set this parameter to disabled."

With both disabled (the strong host model), Windows does not send a packet out of an interface that does not hold the packet's source address, and does not accept packets addressed to another interface's address. A reply carries the address the request arrived on as its source, so under these defaults Windows sends it only through the interface holding that address. If that interface has no route towards the client, the reply is not sent at all rather than leaving through another NIC. Sending through a different interface happens only where WeakHostSend is enabled on it. Linux behaves differently: unless policy routing says otherwise, it picks the reply interface from the main routing table, which is how replies end up on the default-gateway NIC. On Windows, find the interface that holds the address the request arrived on (Get-NetIPConfiguration shows each interface's index and addresses), then read the routes of that interface only and check that one of them covers the client. A destination-only route lookup is not enough here, because it can choose a different local interface and source address from the one the reply must use. Confirm with captures rather than assuming.

Multi-NIC VMs

The most common host-level cause is a VM with two NICs on two networks, each with a default gateway, or a single default gateway on the "wrong" NIC. A client on a third network reaches the VM through NIC 2. On Linux, or on a Windows interface with WeakHostSend enabled, the VM's routing table can send the reply through the NIC 1 default gateway. That reply comes from NIC 2's address, through a firewall that never saw the request. Under the Windows default (strong host), the reply is not sent through NIC 1; if NIC 2 has no route towards the client, it is not sent at all, and the client sees a timeout instead.

Read-only checks

None of these was run for this article. Captures need administrator or root rights and record traffic, so filter them to the test client and port and handle the files under your data-handling rules.

1. Map the paths. For one failing flow, write down the client address, the server address and port, every interface on the server with its address and gateway, and every stateful device that could sit on each path.

2. Read the server's routing view.

On Linux:

ip -br address
ip route show table all
ip rule show
ip route get <client-ip> from <server-ip-the-client-used>

ip route get shows which interface and gateway the kernel would use for the reply.

On Windows:

Get-NetIPConfiguration
Get-NetRoute -AddressFamily IPv4 | Sort-Object RouteMetric
Get-NetIPInterface -AddressFamily IPv4 | Select-Object InterfaceAlias, InterfaceMetric, WeakHostSend, WeakHostReceive, Forwarding
Get-NetRoute -AddressFamily IPv4 | Where-Object ifIndex -eq <arrival-interface-index> | Sort-Object RouteMetric

3. Read the reverse-path setting on Linux.

sysctl net.ipv4.conf.all.rp_filter
sysctl -a 2>/dev/null | grep '\.rp_filter'

4. Capture on both legs at the same time. On Linux, run one capture per interface during a single test connection:

tcpdump -ni <nic1> 'host <client-ip> and tcp port <port>'
tcpdump -ni <nic2> 'host <client-ip> and tcp port <port>'

On Windows, use Packet Monitor where it is present (Microsoft's pages list Windows Server 2016 to 2025 and Windows 10/11); confirm with pktmon help and pktmon filter help on the installed build, because options differ between builds. First check that no other capture or filter set is in use, and record what you find:

pktmon status
pktmon filter list

If a capture is already running, do not stop or change it; arrange the test with whoever owns it. Each filter reports a packet when all its conditions match, and a packet matching any one filter is reported, so existing filters widen what you capture. Then add a filter, start a capture, stop it and convert a copy for Wireshark. The capture writes files to C:\Temp, so check free space there:

pktmon filter add TestFlow -i <client-ip> -p <port>
pktmon start --capture --file-name C:\Temp\flow.etl
pktmon stop
pktmon etl2pcap C:\Temp\flow.etl --out C:\Temp\flow.pcapng

Keep the .etl file: it holds the drop evidence. Afterwards, remove only what you added. Use pktmon filter remove only if pktmon filter list was empty before the test; otherwise check pktmon filter help for what it removes on your build and leave the filter list as you recorded it.

Microsoft notes that the pcapng output loses pktmon's drop reports and component information. Use pktmon etl2pcap <file> --drop-only to keep dropped packets in a separate file.

5. Read connection state on each stateful device. On a Linux firewall with conntrack-tools installed:

conntrack -L -p tcp --dport <port> --src <client-ip>

An entry stuck at SYN_SENT on one firewall, while the reply is captured on the other path, is the signature of the first failure mode. On commercial firewalls, use the vendor's session table and drop log.

Interpreting the results

Observation Meaning Next step
SYN arrives on NIC 2, SYN-ACK leaves on NIC 1 The host replies through a different gateway Fix host routing (policy routing or gateway design)
SYN arrives on NIC 2, nothing leaves, and the effective rp_filter (the higher of all and the NIC 2 value) is 1 Possible reverse-path rejection Run ip route get <client-ip> from <local-ip> iif <nic2>; a failure or a different interface supports it, as do drop counters or logs. Then fix routing so NIC 2 is the best path back, or use loose mode with a reason
Firewall A shows SYN_SENT and firewall B logs an invalid or no-session drop Asymmetric path through two stateful firewalls Route both directions through the same firewall, or share state between the pair
Works from the server's own subnets, fails from routed networks Consistent with replies following the default route rather than the arrival interface (Linux), or with no route from the arrival interface (Windows strong host) Confirm with route checks and captures on both legs, then add a source-based route for that interface
Fails only after a failover Paths changed without state moving Check failover state sync and routing
Captures show symmetric paths, but resets still occur Not asymmetric routing Look at MTU, the application or the firewall policy

Decision guidance

Fix 1: one default gateway, specific routes for the second network

For a VM with a backup or management NIC, the simplest design is a single default gateway on the main NIC. The second NIC gets static routes only for the networks it serves. Replies to everything else leave by the main path, and the clients on the second network are directly connected or explicitly routed.

  • Verification: ip route get on Linux, or the routes of the arrival interface on Windows, show a route towards each client network through the interface the requests arrive on. Captures show replies on the arrival leg.
  • Rollback: restore the saved routing configuration. Record ip route show table all or Get-NetRoute output before the change.

Fix 2: policy routing by source address (Linux)

When both NICs must serve arbitrary clients, add a routing table per interface and a rule that selects it by source address. The ip rule syntax supports from PREFIX selectors with a table TABLE_ID action, so replies sourced from the NIC 2 address use NIC 2's gateway. Make the change in the distribution's persistent network configuration so that it survives reboots.

  • Verification: ip rule show lists the rule, and ip route get <client> from <nic2-address> returns NIC 2 and its gateway. Rerun the two-leg capture.
  • Rollback: remove the rule and table from the persistent configuration and reload.

On Windows, there is no direct equivalent of Linux source-based policy routing in the standard tools covered here. Prefer the single-default-gateway design, or terminate the second path on a device that NATs, as in Fix 3.

Fix 3: source NAT on the second path

If traffic arrives through a second firewall or link, that device can translate the client's source address to its own inside address. The server then replies to the device, and the reply returns by the arrival path. The cost is that the server sees the device's address instead of the real client address.

  • Verification: the server capture shows the device's address as the source, and handshakes complete.
  • Rollback: remove the NAT rule.

Fix 4: firewall pair design

For two firewalls in parallel, route both directions of each flow through the same unit, for example with consistent routing toward the active unit. Or use the vendor's supported state-synchronisation design. Follow the vendor's documentation for the specific product.

Loose reverse-path mode

Setting rp_filter to 2 is the kernel documentation's recommendation where asymmetric routing is intended. Treat it as a deliberate design decision, scoped to the interfaces involved, rather than a quick fix. Strict mode exists to drop spoofed sources.

  • Verification: read the effective value; remember the maximum of all and the per-interface value is used.
  • Rollback: restore the previous value in the persistent sysctl configuration.

What not to do

  • Do not disable stateful inspection, or accept INVALID packets, on a firewall to make an asymmetric path work. The routing is still wrong, and you lose protection.
  • Do not set rp_filter=0 globally on a multi-homed host as the default fix.
  • Do not give both NICs of a VM a default gateway and hope the metric sorts it out.
  • Do not diagnose from ping alone. Test the real protocol and port.
  • Do not leave pktmon filters or long-running captures in place after the test.

Limitations

  • Examples use IPv4 and generic Linux and Windows tools. Commercial firewall session tables, asymmetric-routing options and state-sync designs are vendor-specific and not covered.
  • Windows routing for multi-homed hosts depends on interface metrics and routes. This article does not claim an equivalent of Linux source policy routing in the Windows tools covered.
  • Cloud and overlay networks may enforce their own source and destination checks, which are not covered here.

Acceptance checklist

  • [ ] Failing flow mapped: client, server address and port, interfaces, gateways, stateful devices
  • [ ] Routing view recorded for the client network (ip route get on Linux; the arrival interface's routes on Windows)
  • [ ] Simultaneous captures on both legs show where the SYN and the SYN-ACK travel
  • [ ] Connection state read on each stateful device
  • [ ] Fix chosen (gateway design, policy routing, source NAT or firewall pair design) with the reason
  • [ ] Previous routing and sysctl configuration saved for rollback
  • [ ] Post-change capture shows replies on the arrival path, and the real application test passes

References

All accessed 2 November 2026.

  • RFC 3704, Ingress Filtering for Multihomed Networks (strict and loose reverse path forwarding): https://www.rfc-editor.org/rfc/rfc3704.txt
  • Linux kernel documentation, IP Sysctl (rp_filter, arp_filter, src_valid_mark): https://docs.kernel.org/networking/ip-sysctl.html
  • Linux kernel documentation, Netfilter Conntrack Sysfs variables (nf_conntrack_tcp_loose, nf_conntrack_tcp_be_liberal): https://docs.kernel.org/networking/nf_conntrack-sysctl.html
  • conntrack-tools user manual (conntrack -L examples): https://conntrack-tools.netfilter.org/manual.html
  • ip-rule(8) manual page: https://man7.org/linux/man-pages/man8/ip-rule.8.html
  • ip-route(8) manual page (ip route get): https://man7.org/linux/man-pages/man8/ip-route.8.html
  • Microsoft Learn, Set-NetIPInterface (WeakHostSend, WeakHostReceive defaults): https://learn.microsoft.com/en-us/powershell/module/nettcpip/set-netipinterface
  • Microsoft Learn, Get-NetIPInterface: https://learn.microsoft.com/en-us/powershell/module/nettcpip/get-netipinterface
  • Microsoft Learn, Packet Monitor (Pktmon): https://learn.microsoft.com/en-us/windows-server/networking/technologies/pktmon/pktmon
  • Microsoft Learn, pktmon start: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/pktmon-start
  • Microsoft Learn, pktmon filter add: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/pktmon-filter-add
  • Microsoft Learn, pktmon etl2pcap: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/pktmon-etl2pcap

Related articles

Learning notes

  1. Stateful firewalls and strict reverse-path filtering both assume a flow returns the way it came. Asymmetric routing breaks that assumption.
  2. Prove the paths with captures on both legs at the same time, and read connection state on each stateful device.
  3. On Linux, rp_filter strict mode can drop packets that arrive on an interface that is not the best way back. The effective value is the maximum of all and the per-interface setting; confirm with ip route get ... iif before blaming it.
  4. Windows interfaces default to the strong host model: a reply leaves only by the interface holding its source address, so check that interface's routes.
  5. Fix the routing (one default gateway, source policy routing or source NAT), not the firewall's state checks.