technology

How It Works

vpnw, the kernel of VPN Works, runs a program in a sealed sandbox, takes every connection it makes, decides it with a policy, sends it down a path and writes it down. Four commands cover the cycle: trace, learn, guard and run.

Each of the four is the same machinery with a different setting. trace lets everything through and prints it. learn reads a trace and drafts a policy. guard enforces a policy. run just sends the program down a path, quietly.

The cycle is short: watch the program once, turn what it did into rules, read them, enforce them.

The four commands: run chooses the path, trace sees every connection, guard enforces a policy, learn drafts the policy from a trace

The Sealed Sandbox

On Linux, vpnw starts the program in a network namespace of its own, with nothing in it but loopback. The only things listening there are vpnw’s HTTP and SOCKS5 proxy ports, and the program’s environment points at them. curl, git, pip, Python, Go and Node.js all honor that. A program that doesn’t has no route anywhere, so it fails instead of leaking.

A seccomp filter closes the side doors: Unix sockets (so the program can’t ask Docker to connect for it), io_uring (which can make sockets without calling socket()), and system calls through a second table. A program that really needs a local socket, like ssh-agent, can be let out with --allow-unix-sockets.

The Policy

Every connection is decided in the same order:

  1. deny rules on the name
  2. deny rules on every address the name resolved to
  3. deny_private on every address: loopback, private ranges, link-local and cloud metadata
  4. allow rules
  5. the default

A name is looked up only when an address could change the answer. A name that’s denied, or simply not allowed, is never looked up, because a DNS query can carry data out too. If a name resolves to several addresses, all of them must pass. Then any Guard plugins get their say, and they can only refuse.

The Path

Allowed connections leave the way you chose: straight out, through a proxy, through a list of exits that fail over, or through a WireGuard tunnel that vpnw runs in its own process. With WireGuard, names are looked up through the tunnel too, so the policy checks the addresses the tunnel would really use, and no lookup leaks outside it. If the path can’t be used, the program never starts.

The Record

Every step is an event: the attempt, the lookup, the decision and its rule, the open, the close with bytes each way. Events never carry payloads, URL paths, headers or passwords. They print through the Trace plugin, save as JSON Lines, and go to any Observer plugin you add.

The Plugins

Plugins are WebAssembly modules that vpnw runs in a sandbox. Observers watch a run, Advisors read traces and write advice, Guards can refuse connections. A plugin that crashes is stopped and the run carries on; a Guard that crashes refuses everything after it. The platform has the details, and the reference has every option.