Skip to main content

VPN connection flow

When the user taps Connect, the following sequence runs synchronously through the layered architecture before the tunnel is active and the UI reflects the new state.
1

User triggers a ConnectIntent

The UI creates an AnyConnectIntent value and passes it to VpnConnectionManager.connect. ConnectIntent is a sealed interface that captures the user’s intent without yet resolving a specific server:
The composable delegates through a VpnConnect function interface, which VpnConnectionManager implements.
2

VpnConnectionManager processes the intent

VpnConnectionManager (vpn/VpnConnectionManager.kt) is the central coordinator. It holds the internal connection state machine:
On receiving a connect request, the manager checks VPN permission, acquires a wake lock to prevent the CPU from sleeping during setup, and starts a connection coroutine.
3

Server selection

ServerManager2.getBestServerForConnectIntent resolves the intent to a concrete Server object. It delegates to ServerManager.getServerForProfile which walks the in-memory server list applying filters for:
  • User’s subscription tier (free vs. paid servers)
  • Excluded locations (split-tunneling exclusions from ExcludedLocations)
  • Server load and maintenance status
  • Required ServerFeature flags (Secure Core, P2P, streaming, etc.)
4

Protocol negotiation and port scanning

ProtonVpnBackendProvider.prepareConnection is called with the resolved server and the user’s ProtocolSelection. For WireGuard it probes available ports on the server to find an open one:
The result is a PrepareResult holding the chosen VpnBackend and the fully-populated ConnectionParams.
5

Certificate acquisition

Before opening the tunnel, each backend needs a valid client certificate. CertificateRepository.getCertificate returns the locally cached certificate if it has not expired; otherwise it fetches a fresh one from the API:
The certificate contains the session’s public key signed by Proton. It is passed to the local agent alongside the private key PEM so the agent can open an mTLS connection to the VPN server.
6

Backend starts the VPN tunnel

VpnBackend.connect(connectionParams) is called on the selected backend.WireGuard path:
ProTun path:
7

VPN service starts

For WireGuard, the OS VpnService (WireguardWrapperService) is started as a foreground service when the GoBackend sets the tunnel to UP. The service holds the VPN interface file descriptor. WireguardBackend keeps a weak reference to it via serviceCreated / serviceDestroyed callbacks.
Android requires a foreground service notification while the VPN tunnel is active. The notification is managed by the VPN service, not the backend.
8

VpnStateMonitor emits state updates

VpnConnectionManager observes each backend’s selfStateFlow and forwards changes to VpnStateMonitor:
9

UI observes state via StateFlow

ViewModels collect VpnStateMonitor.status (or the UI-filtered VpnStatusProviderUI.uiStatus) and map it to composable-friendly UI state. Compose recomposes only the affected parts of the tree when the state changes.
VpnStatusProviderUI wraps the monitor and filters out internal GuestHole connections so they are never surfaced in the main UI.

Server list data flow

The server list follows a separate path from API through storage to the UI.
  1. ServerListUpdater periodically calls the Proton API and saves the raw server list via ServersStore to a file (servers.store) in the app’s files directory.
  2. ServerManager loads the file on demand (ensureLoaded), parses it into VpnCountry / Server objects, and keeps them in memory. It exposes a serverListVersion StateFlow that increments on each reload.
  3. ServerManager2 is a thin suspending wrapper that calls serverManager.ensureLoaded() before delegating. New code should use ServerManager2 rather than ServerManager directly.
  4. ViewModels collect serverListVersion-derived flows (e.g., allServersFlow, allServersByScoreFlow) and map them to their own UI state.

Settings propagation

User settings flow from storage to the active VPN backend through SettingsForConnection.
SettingsForConnection merges base settings with per-connection overrides (SettingsOverrides) from the active ConnectIntent. The backend listens for changes:
Settings are pushed to the local agent immediately whenever they change, with no reconnection required. NetShield level changes, for example, take effect within seconds without interrupting the tunnel.
SettingsOverrides in a ConnectIntent let profiles override the global settings (e.g., a profile that always uses WireGuard UDP regardless of the global Smart protocol setting) without changing the user’s default preferences.

Error and fallback flow

When a backend emits an error state, VpnConnectionManager decides whether to recover or surface the failure.
Recoverable errors trigger VpnConnectionErrorHandler, which pings candidate servers and returns either a Switch result (connect to the new server) or an Error result. The manager then either initiates a seamless server switch or disconnects and notifies the user.