Does the VPN app keep traffic in the tunnel when things go wrong?
Lab runs a VPN app in a private test network, keeps a probe sending traffic every 50 ms, and breaks things on a timetable. Observers on the far side write down everything that arrives and where it came from. Below are two real runs of stand-in clients through the same fault: the VPN server falls silent for five seconds. Lab's verdict code runs in this page on the recorded packets.
Loading Lab's engine
Step 1 of 6
A laptop, a home router, the internet and a VPN server
The test network is four Linux network namespaces on one machine, joined by virtual cables. The VPN app under test runs on the laptop next to a probe. Every 50 ms the probe asks for a DNS name nobody asked for before, opens a short TCP connection and sends a UDP datagram, each tagged with a sequence number.
device
- the VPN app under test, on a TUN device
- the probe: DNS, TCP and UDP every 50 ms
- 192.168.1.10 on the home network
home
- translates addresses to 203.0.113.2
- runs a DNS resolver at 192.168.1.1
- can push a route or hand out a new DNS server
internet
- TCP and UDP observers at 198.51.100.10
- the name server for the probe's names
- each writes down every arrival and its source address
vpn
- the tunnel's far end, at 192.0.2.10
- sends everything on from 192.0.2.20
- a DNS resolver inside the tunnel
192.0.2.20, or a query at the VPN's own resolver.203.0.113.2, or anything that reached the home router's resolver.Lab watches from the network side. It doesn't ask the app what it did; it sees what arrived, where from, and when.
Step 2 of 6
The VPN server falls silent for five seconds
Each run follows a timetable. The tunnel is up and the probe running at the start; then the server's address stops answering, and everything sent to it is dropped. Five seconds later it answers again. Both clients notice after a second without an answer, drop their routes and try to reconnect. What they do with DNS in between is where they differ.
The timetable as the bench carried it out, seed 7
The correct client's own log
The dns-leak client's own log
The dns-leak client points DNS at the home router while it reconnects, so that names keep working. Its kill switch lets local traffic through. Steps 3 and 4 show what that does.
Step 3 of 6
The correct client keeps everything in
Every mark is one probe, placed at the moment it was sent. Teal ones came through the tunnel. Gray ones arrived nowhere: lost in the tunnel while the server was silent and the client hadn't noticed yet, or held by the kill switch while the tunnel was down. The shaded stretch is the silent server.
Step 4 of 6
The dns-leak client sends its DNS outside
The same fault, the same timetable, a different client. Its TCP and UDP probes are held back as the correct client's are. Its DNS queries are not.
Step 5 of 6
Around the reconnect, one probe at a time
The two runs again, from just before the fault to just after the tunnel is back. Pick any probe to see where it arrived and how Lab judged it.
The correct client
The dns-leak client
Pick a probe.
Lab tells the paths apart by address alone: it needs no access to the app, the device or the tunnel.
Step 6 of 6
Other faults, and every app
Two more recorded runs, decided in this page: a client with no kill switch, killed for two seconds, and a client that follows the routing table while the home network pushes a route, the flaw published as TunnelVision (CVE-2024-3661).
Every app through every scenario from the final run's kernel tests, not computed in this page
| App | Steady | Server silent | Server gone | App killed | Link drops | Route pushed | DNS changed |
|---|---|---|---|---|---|---|---|
| Correct client | pass | pass | pass | pass | pass | pass | pass |
| DNS leak | pass | DNS | DNS | pass | DNS | pass | DNS |
| No kill switch | pass | TCP, UDP | TCP, UDP | TCP, UDP | TCP, UDP | pass | pass |
| Follows routes | pass | pass | pass | pass | pass | TCP, UDP | pass |
| Agent, sealed | pass | pass | pass | pass | pass | pass | pass |
| Agent, proxy settings only | DNS, TCP, UDP | DNS, TCP, UDP | DNS, TCP, UDP | DNS, TCP, UDP | DNS, TCP, UDP | DNS, TCP, UDP | DNS, TCP, UDP |
Each cell held over 5 runs with the same seed. In the Agent's proxy-settings mode only the probes that ignore proxy settings leaked, as the Agent's documents say they will. None of the probes sent through the proxy leaked.
Each stand-in's planted fault was caught, and the correct client passed every scenario in every run.
What is real here
Real, and recorded
- The runs: the stand-in clients ran as real Linux processes on real TUN devices, with real routing tables and nftables firewalls, in network namespaces on one machine. The fault was a real firewall rule at the server.
- The packets: every probe and every arrival in the timelines is a line of a recording made during those runs by the vpnw-lab command. The page replays them; nothing runs live.
Real, and running here
- The verdict: Lab's own Go code, compiled to WebAssembly, reads the recordings in this page and decides them. The command line gives the same counts for the same files.
- The numbers: every count, time and window on the steps above comes from that code.
Stand-ins
- The VPN clients and the server were built for the bench. The tunnel carries packets in plain UDP: no encryption, no keys. No vendor's VPN app took part.
- The addresses come from ranges set aside for documentation and private networks.
- The table in step 6 is copied from the final run's results.
VPN Works is open source under the Apache License 2.0. Copyright VPNW.com 2026. The code is at https://github.com/VPNWorks/vpnw.