Skip to main content
Mullvad VPN manipulates the system routing table to ensure all traffic flows through the VPN tunnel and to implement split tunneling. The implementation varies by platform.

Overview

Routing management handles:
  • Default route replacement - Routes all traffic through tunnel
  • Specific routes - Allows API access and relay connectivity
  • Split tunneling - Excludes specific applications from tunnel
  • Route monitoring - Detects and handles external changes
Source: talpid-routing/src/lib.rs

Core Concepts

Route Structure

A route consists of:
Source: talpid-routing/src/lib.rs:75-83

Node Types

Gateway Node:
Address-only Node:
Device-only Node:
Source: talpid-routing/src/lib.rs:226-249

Route Destinations

Real Node:
Default Node (macOS/Windows):
Source: talpid-routing/src/lib.rs:199-210

Windows Routing

Implementation

Uses IP Helper API and routing change notifications: Source: talpid-routing/src/windows/mod.rs

Route Management

Add route:
Remove route:

Default Route Handling

Windows monitors default route changes: Detection:
  • Listens for NotifyRouteChange2 callbacks
  • Detects new default gateway
  • Updates symbolic default routes
Source: talpid-routing/src/windows/default_route_monitor.rs Best route selection:
Returns:
  • Interface with lowest metric
  • Gateway IP address
  • Used to route non-tunnel traffic
Source: talpid-routing/src/windows/get_best_default_route.rs

Split Tunneling (Windows)

Windows implements split tunneling via driver: Architecture:
  1. WFP callout driver intercepts packets
  2. Checks process path against exclusion list
  3. Redirects excluded traffic to alternative interface
  4. Preserves in-tunnel for other traffic
Source: talpid-core/src/split_tunnel/windows/mod.rs Driver communication:
  • Opens device handle to driver
  • Sends IOCTLs to configure exclusions
  • Monitors driver status
Path monitoring:
  • Watches for excluded executable changes
  • Updates driver when paths modified
  • Handles volume mount/unmount events
Source: talpid-core/src/split_tunnel/windows/path_monitor.rs

Linux Routing

Implementation

Uses Netlink (rtnetlink) for routing operations: Source: talpid-routing/src/unix/linux.rs

Routing Tables

Linux uses multiple routing tables: Main table (254):
  • Default application routes
  • Tunnel default route
Custom table (e.g. 1984):
  • Split tunnel routes
  • Excluded process routes
  • Selected via fwmark
Source: talpid-core/src/firewall/linux.rs:98-100

Route Operations

Add route:
Route attributes:
  • RTA_DST - Destination prefix
  • RTA_GATEWAY - Gateway IP
  • RTA_OIF - Output interface index
  • RTA_PRIORITY - Metric
  • RTA_TABLE - Routing table ID
  • RTA_METRICS - MTU and other metrics
Source: talpid-routing/src/unix/linux.rs

Default Route Replacement

Tunnel setup replaces default route:

Policy Routing

Linux uses policy routing for split tunneling: IP rules:
Custom table routes:

Split Tunneling (Linux)

cgroup2 Method (Modern)

Primary method using cgroups v2: Source: talpid-core/src/split_tunnel/linux/mod.rs:99-112 Setup:
  1. Create cgroup2 at /sys/fs/cgroup/mullvad-exclusions
  2. Add PIDs to cgroup:
  3. nftables matches cgroup:
Source: talpid-core/src/firewall/linux.rs:355-375 How it works:
  • Processes added to cgroup2
  • All packets from cgroup get fwmark
  • Policy routing sends marked packets to custom table
  • Custom table routes to non-tunnel interface
cgroup2 location: Default: /sys/fs/cgroup Customize: TALPID_CGROUP2_FS=/custom/path Source: docs/README.md:207-210

net_cls Method (Legacy)

Fallback using cgroups v1 net_cls controller: Source: talpid-core/src/split_tunnel/linux/mod.rs:114-124 Setup:
  1. Mount net_cls (if needed):
    Customize: TALPID_NET_CLS_MOUNT_DIR=/custom/path
  2. Create cgroup:
  3. Add PIDs:
  4. nftables matches classid:
Source: talpid-core/src/firewall/linux.rs:376-383 How it works:
  • Processes added to net_cls cgroup
  • Packets inherit classid (0xf41)
  • nftables matches classid
  • Applies fwmark for routing
Source: docs/README.md:212-214

Firewall Integration

Split tunnel rules in nftables: Mangle chain (mark packets):
Output chain (allow marked):
NAT chain (masquerade):
Source: talpid-core/src/firewall/linux.rs:391-495

Connection Tracking

Connection tracking ensures consistency:
  • New connections marked based on cgroup
  • Subsequent packets match conntrack mark
  • Survives process exit or cgroup removal
Source: talpid-core/src/firewall/linux.rs:426-441

macOS Routing

Implementation

Uses routing socket and BSD routing APIs: Source: talpid-routing/src/unix/macos/mod.rs

Routing Socket

MacOS communicates via PF_ROUTE socket: Operations:
  • RTM_ADD - Add route
  • RTM_DELETE - Delete route
  • RTM_GET - Query route
  • RTM_CHANGE - Modify route
Source: talpid-routing/src/unix/macos/routing_socket.rs

Default Route Monitoring

Monitors default route changes: Detection:
Events:
  • DefaultRouteAdded - New default gateway
  • DefaultRouteRemoved - Default gateway removed
  • DefaultRouteChanged - Gateway IP changed
Source: talpid-routing/src/unix/macos/watch.rs Route parsing:
  • Reads routing messages from socket
  • Parses route attributes (dst, gateway, interface)
  • Identifies default routes (0.0.0.0/0, ::/0)
  • Resolves MAC address via ARP
Source: talpid-routing/src/unix/macos/default_routes.rs

Split Tunneling (macOS)

MacOS implements split tunneling via PF routing: Mechanism:
  1. Process monitoring tracks excluded apps
  2. PF rules use BPF device filters
  3. Route-to directs traffic to physical interface
  4. Preserves tunnel traffic for other apps
Source: talpid-core/src/split_tunnel/macos/mod.rs PF route-to rule:
  • Matches tunnel interface
  • Routes to physical interface instead
  • Bypasses tunnel for excluded processes
Source: talpid-core/src/firewall/macos.rs:755-775 Process identification:
  • Uses Endpoint Security framework
  • Monitors process launch/exit
  • Tracks process tree (parent-child)
  • Updates exclusions dynamically
Source: talpid-core/src/split_tunnel/macos/process.rs BPF integration:
  • Attaches BPF program to interface
  • Filters packets by process
  • Works with PF rules for routing
Source: talpid-core/src/split_tunnel/macos/bpf.rs

Interface and Gateway Resolution

Gateway includes MAC address:
Required for proper route-to operation. Source: talpid-routing/src/lib.rs:63-70

Android Routing

Implementation

Uses VPN Service API - no direct routing control: Source: talpid-routing/src/unix/android.rs

VPN Service Routing

Configuration:
Behavior:
  • System handles all routing automatically
  • Cannot add specific routes
  • Split tunneling via allowed/disallowed apps

Split Tunneling (Android)

Per-app exclusion:
  • Excluded apps bypass VPN entirely
  • Use system default routes
  • Get system DNS
Source: docs/split-tunneling.md:63-73

Route Manager Interface

Initialization

Adding Routes

Simple route:
With options:
Source: talpid-routing/src/lib.rs:170-196

Removing Routes

Default Route Handling

Routes using DefaultNode update automatically:
When system default route changes:
  • Route manager detects change
  • Updates all DefaultNode routes
  • Uses new default gateway
Source: talpid-routing/src/lib.rs:199-210

Common Routing Patterns

Tunnel All Traffic

Relay Endpoint Access

API Access

LAN Access

MTU Considerations

Routes can specify MTU:
Why lower MTU:
  • WireGuard adds ~60 bytes overhead
  • Prevents fragmentation
  • Typical: 1420 for tunnel, 1380 for multihop
Source: talpid-routing/src/lib.rs:192-196

Monitoring and Resilience

Route Change Detection

All platforms monitor routing changes: Windows:
  • NotifyRouteChange2 callbacks
  • Detects route additions/deletions
  • Updates symbolic routes
Source: talpid-routing/src/windows/default_route_monitor.rs Linux:
  • Netlink route notifications
  • Monitors RTM_NEWROUTE/RTM_DELROUTE
  • Validates expected routes
Source: talpid-routing/src/unix/linux.rs macOS:
  • Routing socket messages
  • Parses RTM_* events
  • Updates gateway information
Source: talpid-routing/src/unix/macos/watch.rs

Route Restoration

On disconnect:
  1. Remove tunnel default routes
  2. Restore original default gateway
  3. Delete custom routes
  4. Reset routing tables (Linux)

Split Tunneling Summary

Windows

Method: WFP callout driver Granularity: Per executable path How: Driver intercepts and redirects packets

Linux

Method: cgroup2 + nftables (or net_cls fallback) Granularity: Per process (PID) How: Mark packets, policy routing to alternate table

macOS

Method: PF route-to + process monitoring Granularity: Per process tree How: BPF + PF rules route to physical interface

Android

Method: VPN Service disallowed apps Granularity: Per app package How: System bypasses VPN for excluded apps Source: docs/split-tunneling.md

Environment Variables

Source: docs/README.md:207-214

Troubleshooting

Routes Not Applied

Check route manager status:

Split Tunneling Not Working

Linux - Verify cgroup:
macOS - Check PF:
Windows - Check driver:

Default Route Issues

Symptoms: No connectivity after tunnel up Diagnosis:
  1. Verify tunnel interface exists
  2. Check default routes point to tunnel
  3. Verify relay route via physical interface
  4. Check firewall allows relay endpoint
Fix: Route manager should auto-correct, or restart daemon