Type
Guide
Published
Reading time
4 min read
Author
rootzero
Lire en français
Lire en français →

VPN kill switches: fail-closed design and IP leak verification

A VPN route does not guarantee that your source IP stays hidden. Define a fail-closed policy and test DNS, IPv6 and reconnects against your own endpoint.

  • Red Team
  • VPN
  • Network
  • OpSec

The route to a target goes through the VPN. Then Wi-Fi drops, the client reconnects, and that route disappears for a few seconds. Where does the next packet go? That is the question a kill switch has to answer. A “connected” indicator and an IP-check website showing the VPN address at one instant say nothing about the transition. On an offensive engagement, the consequence is concrete: a request to an authorised target could leave through the physical interface and expose the team’s public source address instead of the agreed egress address.

We did not audit a rootzero VPN configuration here or observe an actual leak. This article sets out a testable property and a method for a controlled environment. It does not offer a firewall rule to paste into a production host: a misplaced exception can cut off remote access or open the very path it was meant to close.

Name the protected traffic before writing rules

“Everything must use the VPN” is sometimes the right policy for a full tunnel. It may be wrong on a split-tunnel engagement: only the mission network should use the tunnel, while other traffic keeps its normal route. First specify the mission destinations and domains, expected egress address, possible interfaces and permitted exceptions. The VPN server must remain reachable through the physical interface to establish the tunnel; the environment may require a few local services too. An exception for “all DNS” or “the whole LAN” is rarely precise enough to deserve the name kill switch.

The property is fail-closed for protected destinations: with the tunnel absent at boot, connecting or failed, no packet to those destinations leaves in cleartext through the physical interface. An application connection error is expected in that state. On its own, it does not prove that no packet left the host. A persistent egress firewall policy installed before the VPN client, or a separate network namespace, can enforce the property. WireGuard’s network namespace guide shows a process that can see only wg0, while the tunnel’s encrypted UDP socket stays in its original namespace.

A default route through the VPN is not that barrier. WireGuard explains that a DHCP manager can alter routes and that an interface change can remove an explicit route to the VPN endpoint (WireGuard routing and namespaces). A route says where the kernel plans to send a packet now; the egress policy says which paths are allowed when the state changes.

A disconnect hook may run too late

The wg-quick manual includes a kill-switch example based on PostUp and PreDown rules. It explains the mechanism, but PreDown removes the reject rule on a normal shutdown. That example alone therefore cannot demonstrate blocking before the first start or after a clean stop. The manual also calls for a separate IPv6 rule when IPv6 is in use and notes that some DHCP packets bypass Netfilter through a different path. Read what a rule covers, and when the rule exists, before adopting its label.

Other clients have similar lifecycle traps. OpenVPN documents up and down scripts and restarts where persist-tun may keep the interface open without replaying those scripts. A GUI disconnect, process failure, reconnect and Wi-Fi handoff do not necessarily follow the same hook sequence. An egress policy independent of that sequence is easier to reason about: it need not wait for the tunnel to announce its own failure before blocking a packet.

DNS and IPv6 may choose another path

Even when IPv4 mission addresses are routed correctly, a target name might be resolved through the DNS server supplied by Wi-Fi. That query exposes the name and physical source address to the resolver; depending on the application, an answer obtained outside the tunnel can also influence the next connection. Inspect DNS policy per interface and domain. NetworkManager documents DNS priorities, including negative values that can exclude other profiles’ servers. systemd-resolved exposes servers, routing domains and per-link settings through resolvectl status. Reading /etc/resolv.conf alone may not tell you how a query will be routed.

Test both IP families. A destination may have an AAAA record, and the physical interface may have a usable IPv6 route while the tunnel covers only 0.0.0.0/0. Conversely, disabling IPv6 without checking the engagement’s requirements is not a proof method. For named targets, include lookups during reconnects, both with and without cached answers, and identify applications that use their own resolver. Egress rules should cover what is actually emitted. The routing table we expected to configure is insufficient.

Observe every tunnel transition

For a lab endpoint you control, choose an IPv4 address, an IPv6 address if relevant, and a name with observable resolution. First inspect interfaces and routes with ip route get <ipv4-address> and ip -6 route get <ipv6-address>; the ip-route(8) manual describes this lookup. Inspect DNS decisions with resolvectl status. These commands show a state, but do not yet prove that a packet can never leave by another route.

Next, make repeated requests to your own test endpoint while observing the physical and tunnel interfaces together, for example with tcpdump -ni <interface> on each. The physical capture should show the encrypted traffic needed to reach the VPN server, but no cleartext packet to the protected target; watch for related DNS queries too. Compare four states: before the VPN starts, established tunnel, reconnect or link loss, and client shutdown. Repeat after a network handoff if the workstation must survive one. The test service should see the expected source address while the tunnel is established, and receive no protected connection when it is gone.

A “what is my IP?” website can complement this test for a full tunnel. Under split tunnelling, the site may report the physical IP because it is outside the protected destinations. To verify the kill switch, correlate the protected destination, observed network path and source address recorded by your test server during each tunnel state change.

#rootzero

A question about your exposure?

Write to us. Someone from the technical team will answer, with sources.