Two failures look identical from the tray icon: traffic that enters the tunnel and traffic that quietly goes around it. The client reports Connected in both cases, because Connected only means the tunnel interface exists and the client installed the routes it wanted. Whether one packet from one application used those routes is a separate question, answered at a different place in the stack. Four measurements settle most of it — the route lookup, the first hop, a capture on the physical adapter and an outside observation — and the server's own session view covers the rest.

Start with the route the packet will take

The routing table is the decision, so ask it about one destination instead of reading the whole thing and guessing. On Linux, ip route get names the interface and source address the kernel picked:

ip route get 203.0.113.10
ip route get 10.20.5.7

On Windows, Find-NetRoute -RemoteIPAddress 203.0.113.10 is more useful than route print -4 for the same reason: it reports the route that won, not every route that exists. Read the table once anyway for its 0.0.0.0 row. A /1 pair there means a full-tunnel client rewrote the default: OpenVPN's redirect-gateway def1 installs 0.0.0.0/1 and 128.0.0.0/1 through the tunnel, which beats the existing 0.0.0.0/0 on longest-prefix match without deleting the physical default. That is why two defaults can coexist while everything still goes into the tunnel.

Two details make the table lie. First, check the prefix that matched rather than the number of default routes, because an interface metric or a /1 pair decides the winner. Second, a connection that already exists keeps the path it was established with; after the tunnel comes up, an open session may keep using the old route until it is reset, which is why reconnecting appears to fix a routing problem. A running client — ps ax | grep openvpn — proves only that the process started.

Trace one internal name and one public name

A route lookup shows intent; the path shows delivery. Trace a name that must be internal and a name that must not be, while connected, and compare the first hop:

traceroute intranet.corp.example
traceroute 1.1.1.1

The same first hop for both means the internal name never entered the tunnel and the fault is above it, usually a name that resolves to a public address or a route that never got installed. Internal traffic leaving through 10.8.0.1 while public traffic leaves by 192.168.1.1 means split tunneling is doing exactly what it was configured to do. A trace that reaches the VPN gateway and then returns nothing but timeouts is usually a return-path or server-firewall problem, not a missing forward route.

Filtered ICMP makes this check lie: all-stars on both traces looks like a dead tunnel and is usually a firewall. Probe a TCP port where you can — traceroute -T -p 443 on Linux — and remember the trace says nothing about which resolver answered: a name can resolve locally and still be reached through the tunnel.

Capture on the physical adapter, not the tunnel

The physical link is the only place that shows what the network can actually see. With the tunnel up, a session to the office should appear as encrypted payload addressed to the VPN server, not as a connection to 10.20.5.7:

sudo tcpdump -ni eth0 udp port 53
sudo tcpdump -ni eth0 host 10.20.0.5

The first filter catches name resolution leaving locally, which is the most common leak and the quietest — the page still loads, the resolver just saw the query. The second catches plain traffic to an internal address that should only exist inside the tunnel. On Windows, pktmon does the same job without a driver: pktmon filter add -p 53, then pktmon start --capture --pkt-size 128, then pktmon stop and pktmon format pktmon.etl -o capture.txt. Those flags differ a little between Windows builds, so check pktmon filter add /? before trusting the syntax.

Capture the physical interface to find leaks; a capture on the tunnel contains the packets you already believe are there, and on a server it contains unreadable outer packets whose source is the gateway. Byte counters on either interface describe packets, not programs; attributing traffic to a process needs a monitor that maps sockets to processes.

Ask an outside service, then ask it again per application

The only check that proves the exit is an answer from outside: a service that echoes the source address it saw. Run it from a shell, where you can choose the interface, and run it twice:

sudo curl --interface tun0 https://example.net/ip
sudo curl --interface eth0 https://example.net/ip

Different answers give you proof of both paths and a way to test a specific application: bind it, or run it in a network namespace with one route, and see which address the far end records. Identical answers are the interesting failure: either the bind did not take effect, or something between the host and the far end rewrites the source address before it arrives. On Windows, curl --interface wants the adapter address rather than its name.

A browser reporting the VPN address proves the browser. A mail client, a package manager, a backup agent or a second browser profile can each take a different path, especially when a proxy setting or a split-tunnel route covers one of them. An address change is not proof of a leak by itself: a load balancer can answer differently depending on the egress it saw, so compare the recorded source address, not the page.

Use the server view to confirm the peer

The server answers a different question: whether a peer is connected, how much it has carried and whether the session restarted. A peer that is up on the server while the client counters barely move means the application traffic is not entering the tunnel, whatever the client table said. A peer that is moving a lot of data for a user who reports nothing working is a different investigation.

Two traps live here. A masqueraded tunnel makes every flow appear to come from the gateway, so logs and per-client accounting collapse onto one address. And a client is treated as a local host, so a question about someone copying files through the VPN is answered by file-share traffic at the server or by host auditing on the server — watching the tunnel gives endpoints and volume, not file names.

Tell a design decision from a leak

Not everything outside the tunnel is a leak. Link-local and broadcast traffic stays local by design, a printer found over multicast keeps announcing on the LAN, and DHCP has to leave locally. An application bound to a specific interface bypasses the route table on purpose, and only a firewall rule, a namespace or a policy route changes that.

IPv6 deserves its own line in the checklist. A tunnel that carries IPv4 only leaves a dual-stack host with a second usable path, and a dual-stack destination will prefer it — including DNS, if the resolver is reachable over IPv6. Disable IPv6 on the adapter or confirm that the tunnel carries it before treating an IPv4 result as complete.

When the checks disagree, the disagreement usually names the fault:

What you seeWhat it meansNext step
The route picks tun0 but the capture shows cleartext to 10.20.5.7Something bypassed the table: a bound app, a policy route or a session older than the tunnelReset the flow, check the app bind, check the ip rule list
The route picks eth0 for an internal nameThe name resolves to a public address, or the route was never installedCompare the resolved address with the routes the server pushes
The capture shows DNS to the LAN resolverResolver leak: data is tunneled, lookups are notPoint the resolver inside, or push it from the server
All four checks agree and one app still failsPath selection is fine; suspect MTU, a per-app proxy or the far endTest the same destination from a known-good client

An order that works: save the routing table before and after connecting, trace one internal and one public name, capture DNS on the physical adapter, echo the exit address from the shell for the specific application, then read the server for volume. If the first four agree, the tunnel is carrying what you think it is and the remaining problem sits above it. If two of them disagree, the pair that disagrees is the fault, not the icon.

ተዛማጅ ጽሑፎች