How Amnezia VPN Client Implements Kill Switch: Architecture and Code Deep Dive

Amnezia-client implements its kill switch through a cross-platform KillSwitch singleton that coordinates strict traffic blocking across Windows, Linux, and macOS by delegating packet filtering to platform-specific firewall helpers.

The kill switch in the amnezia-vpn/amnezia-client repository ensures no traffic leaks when the VPN disconnects or when users explicitly enable strict block-all mode. The implementation centers on the KillSwitch class found in service/server/killswitch.h and service/server/killswitch.cpp, which provides a unified API while leveraging OS-native firewall technologies for actual packet filtering.

Core Architecture: The KillSwitch Singleton

The implementation follows the singleton pattern to maintain a single point of control for firewall rules across the entire application. The KillSwitch::instance() method lazily creates one KillSwitch object attached to the Qt application (qApp), ensuring thread-safe access to firewall state.

Initialization occurs through KillSwitch::init(), which checks for a persisted strict-kill-switch flag. On Linux and macOS, this uses SecureQSettings to read from the Qt settings file; on Windows, it queries the registry. If strict mode is already enabled at startup, init() immediately invokes disableAllTraffic() to block all packets before any other network operations begin.

Platform-Specific Firewall Integration

While the KillSwitch class provides the high-level logic, actual packet filtering relies on dedicated firewall helpers for each operating system:

Windows Implementation

On Windows, the KillSwitch delegates to WindowsFirewall (defined in client/platforms/windows/daemon/windowsfirewall.h and implemented in the corresponding .cpp file). This wrapper interacts with the Windows Filtering Platform to create allow/deny rules.

For strict blocking, WindowsFirewall::create(this)->enableInterface(-1) disables all network interfaces. When restoring connectivity, allowAllTraffic() reverts to default permissive rules. For split-tunnel configurations, enablePeerTraffic() processes an InterfaceConfig object built from JSON configuration to permit specific traffic flows.

Linux and macOS Implementation

Both Unix-like platforms use PF (Packet Filter) based implementations:

These implementations organize rules into anchors such as 100.blockAll, 110.allowNets, and 120.blockNets. The setAnchorEnabled() method toggles specific rule blocks, while setAnchorTable() (macOS) or direct anchor manipulation (Linux) manages allowed IP ranges.

Strict Mode vs. Standard Kill Switch

The client supports two operational modes controlled through KillSwitch::refresh(bool enabled):

Strict Mode (Block-All) When enabled, the method writes strictKillSwitchEnabled = true to persistent storage (registry or settings file) and calls disableAllTraffic(). This immediately activates the 100.blockAll anchor on Unix systems or disables all interfaces on Windows, guaranteeing zero network connectivity regardless of VPN state.

Standard Mode When disabled, refresh() calls disableKillSwitch(), which restores normal networking by:

  • Re-enabling the loopback anchor and permissive anchors (allowDHCP, allowLAN, allowDNS) on Linux
  • Calling MacOSFirewall::uninstall() on macOS (unless strict mode persists)
  • Executing WindowsFirewall::allowAllTraffic() on Windows
  • Clearing the internal m_allowedRanges list

Managing Allowed Traffic and Split-Tunneling

Even with the kill switch active, certain traffic must flow (such as DHCP, DNS, or user-specified split-tunnel sites). The KillSwitch manages these exceptions through:

  • resetAllowedRange(): Clears the whitelist before rebuilding it
  • addAllowedRange(QStringList ranges): Adds IP subnets (e.g., "10.0.0.0/24", "2001:db8::/32") to the internal m_allowedRanges list and updates platform-specific firewall tables

During VPN activation, enableKillSwitch() evaluates the split-tunnel type to determine whether to blockAll, allowNets, or blockNets, automatically programming the appropriate PF anchors or Windows rules.

Integration Points: Peer Traffic and VPN Activation

The KillSwitch integrates with the VPN connection lifecycle through several key methods:

enablePeerTraffic(QJsonObject config) Called primarily on Windows during VPN activation, this method parses the JSON configuration to construct an InterfaceConfig object. It passes this to WindowsFirewall::enablePeerTraffic() to permit traffic to VPN servers while maintaining blocks on other destinations.

enableKillSwitch(int vpnAdapterIdx, QJsonObject config) This comprehensive activation method:

  1. On Windows: Calls allowAllTraffic() for certain split-tunnel types, then enables the specific VPN interface
  2. On Linux/macOS: Computes the effective rule set based on configuration and activates the corresponding anchors (handling DNS servers, LAN traffic, and user-specified ranges)

Summary

  • Centralized Control: The KillSwitch singleton in service/server/killswitch.h provides a single API for all platforms, handling persistence through SecureQSettings or Windows registry.
  • Platform Abstraction: Actual packet filtering delegates to WindowsFirewall, LinuxFirewall, or MacOSFirewall depending on the operating system.
  • Strict Mode: Can be toggled at runtime via refresh(), immediately blocking all traffic through disableAllTraffic() or restoring connectivity via disableKillSwitch().
  • Flexible Rules: addAllowedRange() and resetAllowedRange() support split-tunnel configurations by whitelisting specific subnets while maintaining the kill switch for all other traffic.
  • Lifecycle Integration: Methods like enablePeerTraffic() and enableKillSwitch() coordinate firewall rules with VPN connection states to prevent leaks during connection establishment or teardown.

Frequently Asked Questions

How does amnezia-client prevent traffic leaks during VPN disconnections?

Amnezia-client prevents leaks through the KillSwitch class which maintains active firewall rules even when the VPN tunnels drops. On disconnection, unless the user disables the kill switch entirely, the disableKillSwitch() method restores a safe state that still blocks non-VPN traffic, or in strict mode, disableAllTraffic() maintains a complete block until explicitly released.

What is the difference between strict mode and regular kill switch in amnezia-client?

Strict mode, activated via KillSwitch::refresh(true), immediately blocks all network traffic regardless of VPN state using disableAllTraffic(). The regular kill switch only blocks traffic when the VPN is disconnected or misconfigured, allowing normal internet use when the VPN is intentionally turned off. Strict mode persists across application restarts by storing the flag in Windows registry or Qt settings.

Where does amnezia-client store kill switch settings?

On Windows, the strict kill switch flag is stored in the Windows Registry. On Linux and macOS, the implementation uses SecureQSettings to persist the strictKillSwitchEnabled boolean in the application's Qt settings file, ensuring the preference survives client restarts.

Can the kill switch allow specific IPs while blocking everything else?

Yes, through the addAllowedRange() method in killswitch.cpp. This method accepts a QStringList of CIDR ranges (e.g., "192.168.1.0/24") and updates the platform-specific firewall rules to permit traffic to those destinations while maintaining the block on all other addresses, supporting split-tunnel configurations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →