Policy routing answers a different question than the one most troubleshooting notes ask. The main table says where a destination goes; policy rules say which table gets consulted at all, and they can decide that from the source address, a packet mark, or the interface a packet arrived on. When a tunnel behaves inconsistently, the rule set is frequently the reason and the routing table frequently is not.

Rules pick the table, then the table picks the route

Each rule carries a priority, a match condition and a target table. The kernel walks the rules in priority order and stops at the first match, then performs an ordinary longest-prefix lookup in that table. A rule pointing at an empty table falls through to whatever the next rule selects, which is how a carefully written rule can appear to do nothing.

This is why dumping the main table rarely explains a working VPN client. Clients commonly insert rules at low priority numbers, well before the main table's default rule, so the packet never reaches the route you were staring at. Lowering a metric in the main table changes nothing for traffic that was already diverted.

A rule can also blackhole or unreachable a prefix on purpose. Some clients add such a rule as a kill switch, and the drop then looks like a broken tunnel rather than a configured one.

Read the rule set as a sequence, not a list

Print the rules with their priorities and the tables they reference, then print each table that carries a default or a tunnel route. The order is the part that matters, and it is the part a screenshot usually omits:

ip rule show
ip route show table all | sed -n '1,60p'
ip route get 198.51.100.20 from 10.8.0.2
ip route get 198.51.100.20 from 10.8.0.2 mark 0x1

The last two commands are the ones that turn a theory into an observation, because they ask what the kernel would do for that source and that mark. If the answer changes when the mark changes, a mark-based rule is in play and any test that omits the mark is testing a different packet. The outer connection deserves a rule of its own. A tunnel that routes all traffic through itself must exclude the endpoint address it connects to, or the encrypted packets get sent back into the tunnel they just left. Most clients insert that exclusion automatically, and a hand-edited rule set is where it goes missing.

Marks and sockets: two ways clients split traffic

Some clients mark packets in the firewall and add a rule that matches the mark. Others use socket-level routing, setting a route on the socket before it connects. The first is visible in ip rule; the second is not, and it will not appear in a generic lookup.

If a test program behaves differently from the application it is meant to represent, check whether the application sets a mark or binds to a device. That difference is not a mystery once the mechanism is named, and it explains why a curl from a shell and a browser tab can take different paths on the same machine. Both techniques are policy routing in the broad sense: they select a path using something other than the destination. Neither one authorizes the traffic, and neither substitutes for the firewall rules or the credentials the peer expects.

Trace ownership before you add another rule

Rules are installed by something. A VPN client, a network manager and a hand-written script can each add rules at startup, and reconnect logic can leave the previous set behind. Two stale rules from an earlier profile will keep steering traffic long after the profile was deleted.

Find which process owns the rule by its priority and its shape, and check whether that owner rewrites the set on every connection. If it does, a manual rule added today disappears tomorrow, and the fix belongs in the client's configuration rather than in a shell history file. Clearing the whole rule set to test is a tempting shortcut with a predictable cost: it also removes the rules the client needs to keep its own tunnel working. Delete one rule at a time, or test on a machine you can reboot.

Prove the scope with two flows, not one

A correction is only verified by a flow that should be affected and a flow that should not. Pick a destination that the policy routes into the tunnel, and one that it must leave outside, then compare the lookups and the captures for both:

ip route get 198.51.100.20 from 192.0.2.44
ip route get 198.51.100.21 from 192.0.2.44
tcpdump -ni any host 198.51.100.21 and port 443

When the two lookups return different interfaces, the rule is doing something. When they return the same interface, the rule is matching more broadly than intended, which is the failure that quietly sends a whole subnet through a tunnel meant for one application.

Record the rule priorities and the owning service in the ticket, together with the two test addresses. The next person then has a reproducible pair instead of a description of a symptom, and can tell within a minute whether policy routing is still in play.

Ũhoro ũhĩtĩte