Encryption starts at a boundary, and that boundary is a routing decision the endpoint makes before any packet is encapsulated. A network that never sees your keys can still hand your machine a route it likes better than the tunnel's own. Two failures get confused here: reading a packet already inside the tunnel, and steering a packet that was never put there.

The distinction matters because the second failure is cheaper to arrange and much easier to miss. It shows up as a request that left by the wrong interface while the client still reported a healthy connection.

Two Ways a Network Can Affect You

A packet that reaches the tunnel has its payload hidden from every hop it crosses, and that protection holds even when the path surprises you. A packet that never reaches the tunnel is an ordinary request from your address, sent wherever the route table pointed. The first failure needs a broken cipher or a compromised peer, while the second needs only a configuration message your machine chose to trust. That asymmetry is why the routing half deserves its own review.

Equipment on the path does not need your keys to influence the second case. It needs your endpoint to accept a route, a resolver, or a policy rule that produces one.

expected:  app -> tunnel device -> encapsulation -> internet
observed:  app -> wlan0        -> plaintext     -> internet

Where a Route Can Come From Before the Tunnel Exists

DHCP hands out more than an address lease. The classless static route option can carry destination prefixes and next hops, and RFC 3442 discusses the security consequences of a wrong router address: misdirected traffic, a denial of service, or an observer parked on the path. A client that accepts those routes before the tunnel is up can end up with a preferred path that has nothing to do with your provider.

None of that makes route injection universal. The client has to accept the option, the prefix has to cover the traffic you care about, the chosen next hop has to be reachable, and the enforcement policy has to allow the packet out that way. Remove any one of those and the same response is harmless. That is also why two machines on one network can behave differently, since they are not running the same client, the same policy, or even the same address family.

IPv6 deserves its own pass, because router advertisements carry a default route through different code than the DHCPv4 option does. A test that inspects only the v4 table has not covered it.

Check the Path the Kernel Actually Selects

Route lookups are answerable, so ask them instead of inferring from a status light. The commands below show the chosen route and source for one destination, plus the rules that can override the main table. Run them while connected and again after a reconnect, because a lease renewal can change the answer.

ip route get 203.0.113.10
ip route show table all | grep -E '^(default|203\.0\.113)'
ip rule show
resolvectl query example.com

Read the output for two things: the interface named in the route and the source address chosen. A tunnel route names the tunnel device, a leaked route names the physical one, and the difference is visible without decrypting anything. If the answer changes between runs, keep both outputs and compare the rules that produced them.

Do not stop at the default route. An application exception, a split-tunnel prefix, or a second policy rule can carry a specific destination around the tunnel while the default still points into it.

Decide Where Enforcement Should Live

Three conditions decide whether client-side enforcement is enough or you need something outside it. If the client installs and defends routes for every address family you use, a tunnel that owns the default is usually sufficient. If the machine must reach local resources while everything else stays inside the tunnel, those exceptions have to be enumerated and owned rather than left to whatever the access network advertised. And if the platform can bind traffic to an interface per application, that binding is a stronger guarantee than a route anything can override.

Whichever you choose, write the decision where the configuration lives. A rule that exists because someone typed it during an incident will not survive the next reboot, so keep local and bootstrap exceptions narrow and record why each one exists.

Re-run the lookup commands after every client upgrade and every network change, and compare against the last recorded output. The route table is the enforcement boundary, so treat it as one.

Ingingo zihuye