The VPN layer is built around an abstractDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ProtonVPN/android-app/llms.txt
Use this file to discover all available pages before exploring further.
VpnBackend class that each protocol implementation extends. A provider selects and prepares the right backend based on the user’s protocol preference.
The VpnBackend abstract class
VpnBackend (vpn/VpnBackend.kt) is the common contract for all protocol implementations. It:
- Manages a local agent connection (certificate-based mTLS channel to the VPN server)
- Tracks the tunnel’s
VpnStatevia aMutableStateFlow - Handles certificate refresh and key rotation
- Surfaces NetShield statistics and exit IP information
Local agent
Every connected backend opens a local agent channel — a TLS connection to the VPN server’s local agent endpoint. The agent:- Authenticates the session with the client certificate
- Sends feature flags (NetShield level, randomised NAT, VPN Accelerator)
- Delivers NetShield statistics and
connectionDetails(exit IP) - Reports certificate errors and policy violations
VpnAgentClient class implements the NativeClient interface from the gopenpgp/localAgent Go library and translates callbacks into VpnState updates on the main coroutine scope.
VPN state
Protocol state is tracked in two places:| Field | Description |
|---|---|
internalVpnProtocolState | Raw state from the OS-level tunnel (WireGuard tunnel up/down, SDK state) |
selfStateFlow | Combined state that takes the local agent into account (certificate errors, NetShield jailing) |
VpnConnectionManager observes selfStateFlow and forwards it to VpnStateMonitor.
Protocol implementations
- WireGuard
- ProTun
WireguardBackend (vpn/wireguard/WireguardBackend.kt) wraps the WireGuard Android library (com.wireguard.android.backend.GoBackend).Supported transmission protocols: UDP (default), TCP, TLSprepareForConnectionscans available ports on the server.connectbuilds a WireGuard tunnel config fromConnectionParamsWireguard(including the X25519 key fromCertificateRepository) and callsbackend.setState(Tunnel.State.UP, config, transmissionStr, serverNameStrategy).- A monitoring coroutine (
startMonitoringJob) pollsbackend.statein a loop and maps the raw integer state toVpnState:
WireguardWrapperService is the VpnService subclass for WireGuard. References to the service are held weakly by the backend via serviceCreated / serviceDestroyed callbacks.On Android 10+, the backend waits for the tunnel’s
NET_CAPABILITY_VALIDATED network capability before opening the local agent connection. This prevents TLS failures on newer OS versions.ProtonVpnBackendProvider
ProtonVpnBackendProvider (vpn/ProtonVpnBackendProvider.kt) implements VpnBackendProvider and decides which backend to use for a given connection request.
AppModule.kt:
Protocol selection logic
prepareConnection maps a ProtocolSelection to the right backend:
| Protocol | Backend | Port scan? |
|---|---|---|
WireGuard | WireguardBackend | Yes (configurable) |
ProTun | ProTunBackend | No |
Smart | Both (in order) | Yes |
getSmartEnabledBackends checks AppConfig.getSmartProtocolConfig() to determine which protocols are enabled (WireGuard UDP/TCP/TLS, ProTun). Feature flag wireguardTlsEnabled gates TCP and TLS transmission modes.
Fallback pinging (pingAll)
When VpnConnectionErrorHandler needs to find an alternative server after a failure, it calls pingAll. This method concurrently probes a list of candidate servers across all smart-enabled backends and returns the first server that responds:
PING_ALL_MAX_PORTS = 3 ports unless it is designated as the fullScanServer, in which case all ports are scanned.
Certificate management
CertificateRepository (vpn/CertificateRepository.kt) manages the Ed25519 keypair and the short-lived VPN certificate issued by the Proton API.
Key lifecycle
- Generate —
CertificateKeyProvider.generateCertInfo()creates a new Ed25519 keypair using thegopenpgpGo library. The result is stored inCertificateStorage(encrypted with Android Keystore viaCertStorageCrypto). - Fetch —
updateCertificateFromBackendcallsapi.getCertificate(sessionId, publicKeyPem)and stores the returned PEM certificate alongside the keys. - Refresh — Certificates are refreshed proactively via
PeriodicUpdateManager. The next refresh time is derived from the API response (refreshAt) or set to halfway between now and expiry on error. - Revoke — If the local agent reports
errorCodeBadCertSignatureorerrorCodeCertificateRevoked, the backend callsrevokeCertificateAndReconnect, which generates a new keypair and forces an immediate certificate fetch before reconnecting.
currentCertUpdateFlow shared flow notifies active backends when a new certificate is available, triggering an agent reconnection without tearing down the VPN tunnel.
Error types
ErrorType (defined in VpnState.kt) enumerates all terminal and recoverable errors that backends can emit:
| Error type | Meaning |
|---|---|
AUTH_FAILED | Authentication rejected by the server |
PEER_AUTH_FAILED | Server certificate verification failure |
UNREACHABLE | Server is unreachable (final — shown to user) |
UNREACHABLE_INTERNAL | Server unreachable (recoverable — triggers fallback) |
MAX_SESSIONS | User has hit the concurrent session limit |
LOCAL_AGENT_ERROR | Generic local agent protocol error |
KEY_USED_MULTIPLE_TIMES | Private key reuse detected |
TORRENT_NOT_ALLOWED | Policy violation: torrent blocked on this server |
POLICY_VIOLATION_LOW_PLAN | Server requires a higher subscription tier |
VpnConnectionErrorHandler, which attempts to find an alternative server via pingAll before surfacing a failure to the user.