Mullgate
Architecture

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:

  1. the client hits a route-specific local bind IP
  2. Mullgate selects the route by destination bind IP
  3. the route is chained through its exact Mullvad SOCKS5 hostname inside the shared entry tunnel
  4. 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-tunnel
    • route-proxy
    • routing-layer
  • live sweeps through https://am.i.mullvad.net/json confirmed 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:s06 contract still proves the first two saved routes end to end so the default proof stays fast.
  • Fresh pnpm verify:s06 runs 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.

See also

On this page