Reference
Every command, option, file, rule and event in vpnw 0.4.0. The same reference ships with the code as docs/reference.md; plugin authors want docs/plugins.md.
Commands
| Command | What it does |
|---|---|
vpnw run [options] -- CMD [ARGS] |
runs a program through a chosen path |
vpnw trace [options] -- CMD [ARGS] |
the same, printing every connection |
vpnw guard [options] -- CMD [ARGS] |
the same, under a policy, enforced |
vpnw learn [--from last|FILE] [--name NAME] [--ports] [--wildcards] [-o FILE] |
drafts a policy from traces, with the built-in Learn plugin |
vpnw advise PLUGIN [--from FILE] [--set KEY=VALUE] [-o FILE] |
runs an Advisor plugin on traces |
vpnw plugin list|info|add|remove|keygen|sign|trust|keys |
manages plugins |
vpnw doctor |
checks what this machine supports |
vpnw version |
Options for run, trace and guard
| Option | Meaning |
|---|---|
--policy FILE |
a file with a [policy] table, and optionally paths |
--config FILE |
a file with [network] and [paths.NAME] tables |
--via NAME |
a named path from --config or --policy |
--direct |
connect from this machine (the default) |
--wireguard FILE |
through a WireGuard tunnel, from a wg-quick file; vpnw runs the tunnel itself |
--proxy URL |
socks5://, socks5h://, http://, https:// or socks5+tls://; given more than once, a list of exits with failover |
--ca FILE, --token-file FILE |
for proxies over TLS |
--dns MODE |
local or remote for --proxy; tunnel or local for --wireguard |
--allow RULE, --deny RULE |
rules on the command line |
--deny-private |
deny loopback, private and link-local addresses, cloud metadata included |
--default allow|deny |
when no rule matches |
--plugin NAME |
an Observer or Guard plugin alongside the run |
--set NAME.KEY=VALUE |
a setting for a plugin |
--out FILE, --json |
events to a file, or JSON on the console |
-v, -q, --no-save |
more, less, and don’t keep this trace as last |
--backend sealed|env |
sealed (Linux, enforced) or env (proxy settings only) |
--allow-unix-sockets |
let a sealed program use Unix sockets |
--no-deny-exit |
keep the program’s exit code when something was denied |
A policy or a Guard plugin needs the sealed backend: the env backend only sets proxy variables, so it can’t stop a program that ignores them.
Exit Codes
| Code | Meaning |
|---|---|
| the program’s own | it ran and nothing was denied |
| 120 | something was denied and the program exited with 0 |
| 121 | usage or configuration error |
| 122 | no sealed backend on this machine |
| 123 | the path can’t be used, so the program never started |
| 124 | vpnw itself failed |
| 126, 127 | the program can’t be run, or wasn’t found |
Files
version = 1
name = "agent"
[network] # the path used unless --via names another
type = "wireguard"
config = "office.conf" # a wg-quick file, next to this one
[paths.home]
type = "proxy"
url = "socks5h://127.0.0.1:1080"
[policy]
default = "deny"
deny_private = true
allow = ["api.github.com", "*.githubusercontent.com", "pypi.org:443"]
deny = ["telemetry.example"]
A strict subset of TOML. Anything vpnw doesn’t know is an error with its line number, never skipped.
WireGuard Config Files
The usual wg-quick format. [Interface] takes PrivateKey, Address, DNS, MTU and ListenPort; Table, SaveConfig and FwMark are accepted and do nothing, since the tunnel has no routing table or interface. Each [Peer] takes PublicKey, Endpoint, AllowedIPs, PresharedKey and PersistentKeepalive. IPv6 endpoints are written [address]:port.
- Shell hooks are refused.
PreUp,PostUp,PreDownandPostDownstop the run: vpnw runs no commands from a config file. - DNS goes through the tunnel, to the config’s DNS servers. A config with no DNS server needs
--dns localsaid out loud, and one whose DNS server is outside every peer’s AllowedIPs is refused. - The tunnel must be up before the program starts. vpnw waits up to 12 seconds for a handshake with every peer (WireGuard retries after 5) and stops with exit code 123 if one doesn’t answer.
- AllowedIPs hold. A connection to an address outside every peer’s AllowedIPs is refused with that reason.
Policy Rules
A rule is a host (api.github.com), every subdomain of one (*.github.com, not github.com itself), an address (10.0.0.7) or a range (10.0.0.0/8), each with an optional port. Rules never match more than they say.
Every connection is decided in the same order: deny rules on the name, deny rules on every address it resolved to, deny_private on every address, allow rules, then the default. With an allow list and no default, the default is deny. A name is looked up only when an address could change the answer. If a name resolves to several addresses, all must pass. Guard plugins come after the policy, and can only refuse.
Events
One JSON object per line. Every event has v, ts, type and run; most have pid, path and conn.
| Type | Fields |
|---|---|
run.start |
mode, backend, path_kind, path, policy, plugins |
connection.attempt |
host or ip, port, proto |
dns.query, dns.result |
host, then ips and ms, or error |
policy.allow, policy.deny |
rule, reason; a Guard plugin’s rule is plugin:NAME |
path.switch |
from, to, error, ms |
connection.open, connection.close |
ip, exit, ms; then bytes_up, bytes_down |
connection.error |
error, exit |
plugin.error |
plugin, type, error |
process.start, process.exit, run.end |
the program, its exit code, the totals |
plugin.NAME.TYPE |
whatever plugin NAME emitted |
Where Things Are Kept
| What | Where |
|---|---|
| the last trace | ~/.local/state/vpnw/last.jsonl |
| installed plugins | ~/.local/share/vpnw/plugins/ |
| trusted signing keys | ~/.config/vpnw/trusted-keys |
| compiled plugins | ~/.cache/vpnw/wasm/, safe to delete |
Each follows $XDG_STATE_HOME, $XDG_DATA_HOME, $XDG_CONFIG_HOME and $XDG_CACHE_HOME when set.