Multi-exit Architecture
Why Mullgate moved to a shared-entry multi-exit runtime and what the live proof showed.
Status
Implemented in the current Linux runtime.
This page used to describe the proposed redesign. It now records the shipped architecture and the evidence behind it.
The old problem
The original Mullgate direction tied each route to its own Mullvad WireGuard device.
That scaled badly:
- 1 route meant 1 Mullvad device
- 2 routes meant 2 Mullvad devices
- 10 routes meant 10 Mullvad devices
Because Mullvad limits WireGuard devices per account, that model could not support many simultaneous country-specific exits on one account.
The shipped solution
Mullgate now uses:
- one shared Mullvad WireGuard entry device
- one shared entry tunnel inside the runtime
- many route-specific local listeners
- many logical Mullvad SOCKS5 exit selections, one per route
Traffic still stays explicit:
- the client hits a route-specific local bind IP
- Mullgate selects the route by destination bind IP
- the route is chained through its exact Mullvad SOCKS5 hostname inside the shared entry tunnel
- the observed exit matches that route's configured relay choice
What this does and does not mean
This architecture does not fake or bypass Mullvad device limits.
It still provisions one real Mullvad device. The difference is that the device is shared across many routes instead of being consumed once per route.
Validation
The current runtime has been validated with live traffic:
- the integrated S06 verifier passed with one shared Mullvad device and multiple routes
- the shared-entry topology starts with the normal three-container runtime:
entry-tunnelroute-proxyrouting-layer
- live sweeps through
https://am.i.mullvad.net/jsonconfirmed that route-specific listeners still exit through their configured Mullvad path
What still matters
- Linux is still the only fully supported live runtime environment.
- Non-loopback multi-route setups still need one distinct bind IP per route.
- Hostname-selected routing is still only truthful when each hostname resolves to its assigned bind IP.
- The built-in
pnpm verify:s06contract still proves the first two saved routes end to end so the default proof stays fast. - Fresh
pnpm verify:s06runs need one free Mullvad slot total.--reuse-temp-home <path>lets later reruns reuse that same saved device.
Why the architecture remains explicit
Mullgate did not solve the slot problem by hiding route selection.
It still keeps:
- route-specific config in
routing.locations[] - route-specific bind IPs and hostnames
- exact exit metadata per route
- truthful
status,doctor, and runtime-manifest reporting
The runtime changed. The operator contract stayed explicit.