Documentation Index
Fetch the complete documentation index at: https://mintlify.com/mullvad/mullvadvpn-app/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The management interface is the primary communication channel between the Mullvad daemon (system service) and the various frontends (GUI, CLI, Android app, iOS app). It uses gRPC with Protocol Buffers for efficient, type-safe, bidirectional communication.
Reference: mullvad-management-interface/proto/management_interface.proto
Architecture
Transport Layer
The management interface uses different transport mechanisms depending on the platform:
- Desktop (Windows, Linux, macOS): Unix domain socket or named pipe
- Android: JNI (Java Native Interface) bridge
- iOS: Direct integration (standalone implementation)
Protocol
The interface uses Protocol Buffers v3 with gRPC for:
- Type safety and schema validation
- Efficient binary serialization
- Language-agnostic interface definitions
- Automatic client/server code generation
- Built-in streaming support
Service Definition
service ManagementService {
// Control and get tunnel state
rpc ConnectTunnel(google.protobuf.Empty) returns (google.protobuf.BoolValue) {}
rpc DisconnectTunnel(google.protobuf.StringValue) returns (google.protobuf.BoolValue) {}
rpc ReconnectTunnel(google.protobuf.Empty) returns (google.protobuf.BoolValue) {}
rpc GetTunnelState(google.protobuf.Empty) returns (TunnelState) {}
// Control the daemon and receive events
rpc EventsListen(google.protobuf.Empty) returns (stream DaemonEvent) {}
// ... (80+ additional RPC methods)
}
Reference: mullvad-management-interface/proto/management_interface.proto:10-153
Core RPC Categories
1. Tunnel Control
Manage VPN tunnel state and configuration:
rpc ConnectTunnel(google.protobuf.Empty) returns (google.protobuf.BoolValue) {}
rpc DisconnectTunnel(google.protobuf.StringValue) returns (google.protobuf.BoolValue) {}
rpc ReconnectTunnel(google.protobuf.Empty) returns (google.protobuf.BoolValue) {}
rpc GetTunnelState(google.protobuf.Empty) returns (TunnelState) {}
TunnelState message includes:
- Current state (Disconnected, Connecting, Connected, Disconnecting, Error)
- Relay information (endpoint, location, protocol)
- Feature indicators (quantum resistance, multihop, obfuscation, etc.)
- Error details if applicable
Reference: management_interface.proto:290-313
2. Event Streaming
Frontends subscribe to real-time daemon events:
rpc EventsListen(google.protobuf.Empty) returns (stream DaemonEvent) {}
DaemonEvent streams:
- Tunnel state changes
- Settings updates
- Relay list updates
- Version information
- Device events (login, logout, revoked)
- Access method changes
- Leak detection alerts
Reference: management_interface.proto:729-740
3. Settings Management
Comprehensive settings configuration:
rpc GetSettings(google.protobuf.Empty) returns (Settings) {}
rpc ResetSettings(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc SetAllowLan(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc SetLockdownMode(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc SetAutoConnect(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc SetWireguardMtu(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {}
rpc SetEnableIpv6(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc SetQuantumResistantTunnel(QuantumResistantState) returns (google.protobuf.Empty) {}
rpc SetEnableDaita(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc SetDnsOptions(DnsOptions) returns (google.protobuf.Empty) {}
Reference: management_interface.proto:40-56
4. Relay Configuration
Relay and tunnel constraint management:
rpc UpdateRelayLocations(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc GetRelayLocations(google.protobuf.Empty) returns (RelayList) {}
rpc SetRelaySettings(RelaySettings) returns (google.protobuf.Empty) {}
rpc SetObfuscationSettings(ObfuscationSettings) returns (google.protobuf.Empty) {}
RelaySettings supports:
- Location constraints (country, city, hostname)
- Provider filtering
- Ownership constraints (Mullvad-owned vs rented)
- WireGuard-specific constraints (IP version, multihop, entry location)
- Custom relay configurations
Reference: management_interface.proto:551-583
5. Account Management
Account and authentication operations:
rpc CreateNewAccount(google.protobuf.Empty) returns (google.protobuf.StringValue) {}
rpc LoginAccount(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc LogoutAccount(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc GetAccountData(google.protobuf.StringValue) returns (AccountData) {}
rpc GetAccountHistory(google.protobuf.Empty) returns (AccountHistory) {}
rpc SubmitVoucher(google.protobuf.StringValue) returns (VoucherSubmission) {}
rpc GetWwwAuthToken(google.protobuf.Empty) returns (google.protobuf.StringValue) {}
Reference: management_interface.proto:58-66
6. Device Management
Multi-device account management:
rpc GetDevice(google.protobuf.Empty) returns (DeviceState) {}
rpc UpdateDevice(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc ListDevices(google.protobuf.StringValue) returns (DeviceList) {}
rpc RemoveDevice(DeviceRemoval) returns (google.protobuf.Empty) {}
DeviceState tracks:
- Current state (LoggedIn, LoggedOut, Revoked)
- Device ID and name
- WireGuard public key
- Creation timestamp
Reference: management_interface.proto:68-72, 807-815
7. WireGuard Key Management
Automatic key rotation and management:
rpc SetWireguardRotationInterval(google.protobuf.Duration) returns (google.protobuf.Empty) {}
rpc ResetWireguardRotationInterval(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc RotateWireguardKey(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc GetWireguardKey(google.protobuf.Empty) returns (PublicKey) {}
Reference: management_interface.proto:75-78
8. Custom Lists
User-defined relay groupings:
rpc CreateCustomList(NewCustomList) returns (google.protobuf.StringValue) {}
rpc DeleteCustomList(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc UpdateCustomList(CustomList) returns (google.protobuf.Empty) {}
rpc ClearCustomLists(google.protobuf.Empty) returns (google.protobuf.Empty) {}
CustomList allows:
- Named relay collections
- Geographic location specifications
- Quick access to favorite relay combinations
Reference: management_interface.proto:80-84, 435-446
9. API Access Methods
Censorship circumvention configuration:
rpc AddApiAccessMethod(NewAccessMethodSetting) returns (UUID) {}
rpc RemoveApiAccessMethod(UUID) returns (google.protobuf.Empty) {}
rpc SetApiAccessMethod(UUID) returns (google.protobuf.Empty) {}
rpc UpdateApiAccessMethod(AccessMethodSetting) returns (google.protobuf.Empty) {}
rpc GetCurrentApiAccessMethod(google.protobuf.Empty) returns (AccessMethodSetting) {}
rpc TestCustomApiAccessMethod(CustomProxy) returns (google.protobuf.BoolValue) {}
Supports:
- Direct TLS connections
- Mullvad bridges (Shadowsocks)
- Encrypted DNS proxy
- Custom SOCKS5/Shadowsocks proxies
See API Communication for details.
Reference: management_interface.proto:86-94
10. Split Tunneling
Platform-specific split tunneling control:
Linux (process-based):
rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {}
rpc AddSplitTunnelProcess(google.protobuf.Int32Value) returns (google.protobuf.Empty) {}
rpc RemoveSplitTunnelProcess(google.protobuf.Int32Value) returns (google.protobuf.Empty) {}
Windows, macOS, Android (app-based):
rpc AddSplitTunnelApp(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc RemoveSplitTunnelApp(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc SetSplitTunnelState(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
Reference: management_interface.proto:99-115
11. App Upgrade
In-app update management:
rpc AppUpgrade(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc AppUpgradeAbort(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc AppUpgradeEventsListen(google.protobuf.Empty) returns (stream AppUpgradeEvent) {}
rpc GetVersionInfo(google.protobuf.Empty) returns (AppVersionInfo) {}
Upgrade events include:
- Download starting/progress
- Verifying installer
- Completion or errors
Reference: management_interface.proto:145-149, 155-182
Communication Patterns
Request-Response
Most operations use simple request-response:
- Frontend sends RPC request
- Daemon processes asynchronously
- Daemon returns response when complete
Example: Connecting to tunnel
Frontend -> ConnectTunnel() -> Daemon
Daemon processes relay selection, tunnel setup
Daemon -> BoolValue(true) -> Frontend
Server Streaming
Long-lived connections for real-time updates:
EventsListen provides continuous state updates:
Frontend -> EventsListen() -> Daemon
Daemon -> TunnelState(Connecting) -> Frontend
Daemon -> TunnelState(Connected) -> Frontend
Daemon -> Settings(updated) -> Frontend
... (stream remains open) ...
Multiple clients can subscribe simultaneously, allowing GUI, CLI, and other tools to monitor daemon state concurrently.
Error Handling
Tunnel Errors
The ErrorState message provides detailed error information:
message ErrorState {
enum Cause {
AUTH_FAILED = 0;
IPV6_UNAVAILABLE = 1;
SET_FIREWALL_POLICY_ERROR = 2;
SET_DNS_ERROR = 3;
START_TUNNEL_ERROR = 4;
CREATE_TUNNEL_DEVICE = 5;
TUNNEL_PARAMETER_ERROR = 6;
IS_OFFLINE = 7;
NOT_PREPARED = 8; // Android only
OTHER_ALWAYS_ON_APP = 9; // Android only
INVALID_DNS_SERVERS = 11; // Android only
SPLIT_TUNNEL_ERROR = 12;
NEED_FULL_DISK_PERMISSIONS = 13; // macOS only
}
// ... detailed error fields ...
}
Reference: management_interface.proto:207-288
Authentication Errors
enum AuthFailedError {
UNKNOWN = 0;
INVALID_ACCOUNT = 1;
EXPIRED_ACCOUNT = 2;
TOO_MANY_CONNECTIONS = 3;
}
Reference: management_interface.proto:231-236
Relay Selection Errors
enum GenerationError {
NO_MATCHING_RELAY_ENTRY = 0;
NO_MATCHING_RELAY_EXIT = 1;
NO_MATCHING_RELAY = 2;
NO_MATCHING_BRIDGE_RELAY = 3;
CUSTOM_TUNNEL_HOST_RESOLUTION_ERROR = 4;
}
Reference: management_interface.proto:238-245
Feature Indicators
The management interface tracks active features:
enum FeatureIndicator {
QUANTUM_RESISTANCE = 0;
MULTIHOP = 1;
SPLIT_TUNNELING = 2;
LOCKDOWN_MODE = 3;
WIREGUARD_PORT = 4;
UDP_2_TCP = 5;
SHADOWSOCKS = 6;
QUIC = 7;
LWO = 8;
LAN_SHARING = 9;
DNS_CONTENT_BLOCKERS = 10;
CUSTOM_DNS = 11;
SERVER_IP_OVERRIDE = 12;
CUSTOM_MTU = 13;
DAITA = 14;
DAITA_MULTIHOP = 15;
}
These indicators are included in tunnel state messages, allowing frontends to display active features to users.
Reference: management_interface.proto:332-349
Android
// Google Play payment integration
rpc InitPlayPurchase(google.protobuf.Empty) returns (PlayPurchasePaymentToken) {}
rpc VerifyPlayPurchase(PlayPurchase) returns (google.protobuf.Empty) {}
Reference: management_interface.proto:117-119
macOS
// Check TCC (Transparency, Consent, and Control) permissions
rpc NeedFullDiskPermissions(google.protobuf.Empty) returns (google.protobuf.BoolValue) {}
Reference: management_interface.proto:121-122
Windows
// Volume monitoring for split tunneling
rpc CheckVolumes(google.protobuf.Empty) returns (google.protobuf.Empty) {}
Reference: management_interface.proto:124-126
Settings Persistence
Settings can be managed individually or via JSON patches:
// Apply a JSON blob to the settings
// See docs/settings-patch-format.md for format description
rpc ApplyJsonSettings(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc ExportJsonSettings(google.protobuf.Empty) returns (google.protobuf.StringValue) {}
This allows bulk settings updates and configuration import/export.
Reference: management_interface.proto:128-132
Implementation
Server Side
The daemon implements the ManagementService in Rust:
// mullvad-daemon/src/management_interface.rs
impl ManagementService for ManagementInterfaceServer {
async fn connect_tunnel(&self, _: Request<()>) -> Result<Response<bool>, Status> {
// Forward to daemon command handler
self.send_command_to_daemon(DaemonCommand::Connect).await
}
async fn events_listen(&self, _: Request<()>) -> Result<Response<Self::EventsListenStream>, Status> {
// Create event stream for this client
let event_stream = self.subscribe_daemon_events();
Ok(Response::new(event_stream))
}
// ... other RPC implementations ...
}
Reference: mullvad-daemon/src/management_interface.rs
Client Side
Desktop GUI (Electron/TypeScript):
import { connectTunnel } from './grpc-client';
await connectTunnel();
CLI (Rust):
use mullvad_management_interface::ManagementServiceClient;
let mut client = ManagementServiceClient::connect(socket_path).await?;
client.connect_tunnel(()).await?;
Reference: mullvad-cli/src/cmds/
Asynchronous Design
Non-Blocking Operations
The management interface is designed to never block:
- All RPC handlers run asynchronously
- Commands are queued to the daemon’s actor system
- Responses are sent when operations complete
- No RPC can block another RPC from being processed
This prevents deadlocks and ensures responsive frontend behavior even during long-running operations.
Reference: From architecture.md:21-33
Actor System
The daemon uses an actor-based architecture:
- Each component runs independently
- Communication via message passing
- No shared mutable state between actors
- Management interface acts as entry point for external commands
Security Considerations
Access Control
Desktop: Socket permissions restrict access to appropriate user/group
- Unix domain socket with restrictive file permissions
- Only local processes can connect
Android: JNI bridge provides isolation
- No network exposure
- App sandbox prevents unauthorized access
All RPC inputs are validated:
- Protocol Buffers ensures type safety
- Additional validation in RPC handlers
- Malformed requests rejected before processing
Debugging and Monitoring
Logging
rpc SetLogFilter(LogFilter) returns (google.protobuf.Empty) {}
rpc LogListen(google.protobuf.Empty) returns (stream LogMessage) {}
Allows runtime log level adjustment and log streaming.
Reference: management_interface.proto:151-152
Relay Override
For testing and debugging:
rpc SetRelayOverride(RelayOverride) returns (google.protobuf.Empty) {}
rpc ClearAllRelayOverrides(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc DisableRelay(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc EnableRelay(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
Reference: management_interface.proto:54-55, 138-139