How Amnezia-Client Handles Network Connections: Modular VPN Protocol Architecture

Amnezia-Client manages network connections through a modular protocol architecture where each VPN implementation inherits from an abstract VpnProtocol class, handling configuration parsing, privileged process management, and kill-switch enforcement while exposing a uniform API to the UI layer.

The amnezia-vpn/amnezia-client repository implements a sophisticated networking stack that abstracts platform-specific VPN implementations behind a common interface. Understanding how Amnezia-Client handles network connections reveals a design pattern that isolates protocol-specific logic—such as OpenVPN's management interface versus WireGuard's in-process tunnel controller—from the core connection management code. This architecture enables the addition of new protocols without modifying the core connection management logic.

The Modular Protocol Architecture

At the heart of Amnezia-Client's networking layer is the abstract VpnProtocol class, which serves as the base for all supported protocols including OpenVPN, WireGuard, and Xray. Each protocol implementation is responsible for its own configuration parsing, environment preparation, and connection lifecycle management while exposing standardized methods like prepare(), start(), and stop() to the UI layer.

Protocol selection and configuration retrieval utilize utility functions defined in client/core/protocols/protocolUtils.cpp, which handles JSON-encoded container configuration through ProtocolUtils::key_proto_config_data().

Configuration Parsing and Environment Preparation

Before establishing any connection, the client reads protocol-specific configuration data and verifies system requirements. In client/core/protocols/openVpnProtocol.cpp, the readOpenVpnConfiguration() method (lines 11-23) extracts the VPN configuration from the container settings and writes the raw config to a temporary file.

Environment preparation occurs in the prepare() method (lines 84-100), which validates that required kernel modules and TAP/TUN drivers are present on the system. This step ensures Windows TAP adapters, Linux TUN interfaces, or macOS Network Extensions are ready before attempting connection establishment.

Process Management: OpenVPN vs. WireGuard

OpenVPN Privileged Process Spawning

OpenVPN connections rely on launching the external openvpn binary as a privileged process. The OpenVpnProtocol::start() method (lines 80-62 in client/core/protocols/openVpnProtocol.cpp) utilizes IpcClient::CreatePrivilegedProcess() from client/core/ipc.h to spawn the process with elevated rights.

The implementation passes the --management <host> <port> argument to enable a local TCP management interface, allowing the client to monitor connection state and statistics:

// Assume `containerConfig` holds a DockerContainer::OpenVpn entry
auto *protocol = new OpenVpnProtocol(containerConfig.getOpenVpnProtocolConfig()->toJson(), nullptr);
if (protocol->prepare() == ErrorCode::NoError) {
    protocol->start();          // launches privileged openvpn process
}

WireGuard In-Process LocalSocketController

Contrasting with OpenVPN's external process model, WireGuard operates entirely in-process. The WireguardProtocol class defined in client/core/protocols/wireGuardProtocol.cpp (lines 12-68) instantiates a LocalSocketController—a lightweight wrapper around the Mozilla WireGuard implementation.

This approach directly creates the TUN interface without spawning separate processes, reducing context-switching overhead:

auto *protocol = new WireguardProtocol(containerConfig.getWireGuardProtocolConfig()->toJson(), nullptr);
protocol->start();               // activates the LocalSocketController

Management Interface and State Monitoring

For OpenVPN, the client implements a ManagementServer that listens on the TCP port specified during process startup. The OpenVpnProtocol::onReadyReadDataFromManagementServer() method (lines 80-42 in client/core/protocols/openVpnProtocol.cpp) parses status messages, byte-count updates via the >BYTECOUNT: directive, and routing information.

The client automatically requests byte-count updates when reaching the CONNECTED state:

protocol->sendByteCount();   // sent automatically when CONNECTED
// onReadyReadDataFromManagementServer() parses the ">BYTECOUNT:" line

Each protocol updates the high-level Vpn::ConnectionState enum—tracking states from Preparing and Connecting to Connected, Reconnecting, and Disconnected—and emits protocolError signals when fatal conditions occur.

Kill-Switch and Routing Management

Kill-Switch Implementation

After the VPN tunnel is established, Amnezia-Client optionally enables a kill-switch that blocks all traffic not routed through the tunnel. The implementation in OpenVpnProtocol::updateVpnGateway() (lines 44-89) checks the configKey::killSwitchOption configuration value before invoking platform-specific logic:

// Inside OpenVpnProtocol::updateVpnGateway()
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
    iface->enableKillSwitch(m_configData, netInterfaces.at(i).index());
}

The kill-switch behavior differs by operating system:

  • Windows: Uses the specific network interface index from netInterfaces.at(i).index()
  • Linux/macOS: Passes 0 as the interface parameter to iface->enableKillSwitch(m_configData, 0)

Gateway and Route Selection

The client parses OpenVPN management messages including ROUTE_GATEWAY and net_route_v4_best_gw to discover the VPN gateway IP, storing it in the m_routeGateway member variable. For macOS systems, the implementation additionally invokes the system route command to determine the default gateway.

This logic resides in OpenVpnProtocol::updateRouteGateway() at lines 63-78 of client/core/protocols/openVpnProtocol.cpp.

Summary

  • Modular Architecture: All VPN protocols inherit from the abstract VpnProtocol class, providing a consistent API while isolating implementation details.
  • Process Models: OpenVPN spawns external privileged processes via IpcClient::CreatePrivilegedProcess(), while WireGuard uses an in-process LocalSocketController.
  • State Management: OpenVPN utilizes a local ManagementServer TCP socket to parse status messages and byte-count updates from the >BYTECOUNT: directive.
  • Kill-Switch: Automatically enabled after connection establishment using OS-specific logic in updateVpnGateway(), with Windows requiring interface indices while Linux and macOS use generic parameters.
  • Gateway Detection: Routing information is extracted from management interface messages and system commands, stored in m_routeGateway for traffic routing decisions.

Frequently Asked Questions

How does Amnezia-Client support multiple VPN protocols without code duplication?

The client implements an abstract VpnProtocol base class that defines common interfaces for prepare(), start(), and connection state management. Each protocol-specific class (such as OpenVpnProtocol or WireguardProtocol) inherits from this base and implements only the protocol-specific logic. This design allows the UI layer to interact with any protocol through the same API, regardless of whether the underlying implementation spawns external processes or uses in-process controllers.

What is the difference between OpenVPN and WireGuard connection handling in Amnezia-Client?

OpenVPN connections rely on launching the external openvpn binary as a privileged process via IpcClient::CreatePrivilegedProcess(), communicating through a TCP management interface (--management flag) to receive status updates and routing information. WireGuard connections use an in-process LocalSocketController that directly creates the TUN interface without spawning separate processes, resulting in lower overhead and simpler state management without requiring inter-process communication for status monitoring.

How does the kill-switch feature work across different operating systems?

The kill-switch is implemented in OpenVpnProtocol::updateVpnGateway() and activates after the VPN tunnel is fully established. On Windows, the client passes the specific network interface index to enableKillSwitch(), ensuring only traffic through that interface is allowed. On Linux and macOS, the implementation passes 0 as the interface parameter, relying on platform-specific firewall rules to block non-tunnel traffic. The feature is controlled by the killSwitchOption configuration key.

Where does Amnezia-Client store routing and gateway information?

Gateway information is parsed from OpenVPN management interface messages (such as ROUTE_GATEWAY and net_route_v4_best_gw) and stored in the m_routeGateway member variable of the OpenVpnProtocol class. For macOS clients, the default gateway is additionally determined by executing the system route command. This information is used to configure routing tables and enable the kill-switch after the connection is established.

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 →