A policy rule sends marked packets to a dedicated table, and the usual expectation is that losing the VPN route blocks them. The mechanism does something else. When a table contains no matching route, the lookup carries on to the next rule rather than stopping, and the main table still holds an ordinary uplink default. The result is a configuration that looks fail-closed and behaves fail-open.

Nothing is broken; this is how policy routing is specified.

Lookup Continues When a Table Has Nothing

100:   from all fwmark 0x36 lookup 42
110:   from all fwmark 0x36 lookup default
32766: from all lookup main

Take that rule set: a marked lookup goes to table 42, and a later rule sends the same mark to a table named default. While table 42 holds a usable default route, the first lookup supplies the path. When that route disappears and nothing else in table 42 matches, the lookup does not become a discard; processing moves on to the next rule, and when that one finds nothing either, it reaches the main table. Nothing in that sequence reports which rule produced the final answer, so a working connection looks like a working policy.

Table 253, conventionally called default, is an ordinary routing table and is normally empty. Its name is not an instruction to drop packets, and it is not the same thing as a default destination route inside a table.

Terminal Actions End the Search

100:   from all fwmark 0x36 lookup 42
110:   from all fwmark 0x36 prohibit
32766: from all lookup main

A terminal rule can follow the VPN lookup in place of an empty-table lookup. With the same mark, a prohibit action placed after the table-42 rule ends the search when that table yields nothing, returning an administrative rejection instead of offering the main table another chance. Unreachable and blackhole are terminal too, with different error behaviour for the sender.

A route of type throw is different: within that table it behaves as if nothing matched, so later rules still get their turn. That makes throw useful for deliberate fallback and wrong as a final block.

Verify With the Real Mark and Interface

ip -4 rule show
ip -4 route show table 42
ip -4 route get 198.51.100.20 from 10.44.0.2 iif lan0 mark 0x36

Inspect the real rules and table rather than a diagram, then query the flow with the source, mark and ingress context it truly has. A read-only lookup aimed at a destination, told the source address, interface and mark, reports the route the kernel would pick without a packet leaving. Supplying a mark in the query does not prove the firewall marks real traffic; that is a separate check of the marking rules. Confirm the value with a counter or a log before drawing conclusions from the query.

Locally generated requests need their own context, because their marking path often differs from that of forwarded traffic. Check them with the output interface the application would really use.

Two Failure States, Not One

One failure state is the VPN route disappearing and the traffic falling through to the uplink. A dead tunnel whose route remains installed is another: packets stay directed into an unusable path rather than falling through, and the symptom is silent loss rather than a visible direct connection. Test both, and test startup, reconnect and service-stop behaviour as well.

Such a rule helps only while it exists and while packets still carry the mark that selects it. A shutdown script that deletes the rule, or a path that strips the mark, restores the open behaviour. Those transitions are worth rehearsing.

Apply the policy to IPv6 separately, and keep approved endpoint exceptions explicit rather than implied.

ບົດຄວາມທີ່ກ່ຽວຂ້ອງ