Network Overlays
Network overlay entries route outbound connections or bind listeners through SOCKS5, Tor, Tailscale, or I2P. A selected overlay propagates across function, process, and HTTP boundaries.
This page is a configuration reference. The YAML fences are entry or application-config fragments and assume the external proxy, tailnet, or I2P SAM service already exists.
Entry Kinds
| Kind | Description |
|---|---|
network.socks5 |
Generic SOCKS5 proxy (also covers Tor's SOCKS5 listener) |
network.tailscale |
Tailscale tsnet overlay node |
network.i2p |
I2P SAM v3 bridge |
SOCKS5
- name: proxy
kind: network.socks5
host: 127.0.0.1
port: 1080
username: "optional"
password: "optional"
isolate_streams: false
| Field | Type | Description |
|---|---|---|
host |
string | Proxy host |
port |
int | Proxy port (1-65535) |
username |
string | Optional SOCKS5 auth |
password |
string | Optional SOCKS5 auth |
isolate_streams |
bool | Per-connection random credentials (Tor stream isolation) |
host and port are required. isolate_streams defaults to false. When
isolation is enabled, the runtime generates a new username and password for
each dial instead of using the configured credentials.
Tailscale
- name: tailnet
kind: network.tailscale
hostname: "wippy-node"
auth_key: ${env:TS_AUTHKEY}
ephemeral: false
control_url: ""
| Field | Type | Description |
|---|---|---|
hostname |
string | tsnet node name (used in per-node state directory) |
auth_key |
string | Tailnet auth key — inline or ${env:NAME} resolved via the env registry |
state_dir |
string | Override for tsnet state directory |
control_url |
string | Alternate coordination server |
ephemeral |
bool | Register as an ephemeral tailnet node |
auth_key is required (supply it directly or via ${env:NAME}). The legacy auth_key_env directive resolves the same way but is deprecated; prefer auth_key: ${env:NAME}.
The tsnet hostname defaults to wippy. When state_dir is omitted, the runtime
uses <network_service.state_dir>/tailscale/<node>, where <node> is the
configured hostname or, if no hostname is configured, the registry entry name.
I2P
- name: i2p_bridge
kind: network.i2p
host: 127.0.0.1
port: 7656
session_name: "wippy"
| Field | Type | Description |
|---|---|---|
host |
string | SAM v3 bridge host |
port |
int | SAM v3 bridge port |
session_name |
string | Optional session identifier |
host and port are required. session_name defaults to wippy and is used
as the prefix for per-dial and per-listener SAM session IDs.
Selecting an Overlay
On http.service
Bind the server listener through an overlay (Tailscale, I2P):
- name: gateway
kind: http.service
addr: ":8080"
network: app.net:tailnet
SOCKS5 does not support inbound listening — use it only for outbound dials.
From Lua
Route a called function or spawned process through an overlay using with_options:
local funcs = require("funcs")
local caller, err = funcs.new():with_options({ network = "app.net:proxy" })
if err then return nil, err end
local result, call_err = caller:call("app.api:fetch_data")
if call_err then return nil, call_err end
local process = require("process")
local pid, err = process.with_options({ network = "app.net:tailnet" })
:spawn_monitored("app.workers:probe", "app:processes")
if err then return nil, err end
Constructing the process spawner with custom options also requires process.context on context. A denial raises a Lua error before the spawner is returned; network.select is then checked separately for the selected network ID.
The http_client module accepts the same overlay selection on per-call options under the key overlay_network.
Inheritance
Overlay selection propagates through the call stack. A function called through
funcs.new():with_options({network=...}) uses the overlay for inner dials,
nested calls, and spawned processes unless a new boundary selects another
overlay. An empty network option means "no override"; it does not clear an
inherited overlay or the application default.
For a function call, runtime options override the function entry's
meta.options before network selection. At a new function or process boundary,
a non-empty options.network is selected first. If it is absent,
network_service.default_network is selected when configured; if neither is
present, the inherited frame selection remains. A selected ID must already be
registered. An unknown ID fails the call or spawn instead of falling back to
the host network.
Ambient inheritance bypasses the descendant's own network.select deny rules. Only explicit selection at a Lua edge is gated.
App Configuration
Overlay drivers read app-wide settings from a network_service: block in .wippy.yaml:
network_service:
state_dir: .wippy/net # base dir for driver state (Tailscale keys, etc.)
default_network: app.net:tailnet # overlay applied when no call sets one
| Field | Default | Description |
|---|---|---|
state_dir |
.wippy/net |
Driver state directory. Relative paths resolve against the boot config dir. |
default_network |
— | Registry ID of an overlay applied to any task or process that does not pin its own network via options. |
Raw Dials
Overlay selection is not limited to Lua edges. Dials made through the runtime network service — the WASM socket host and the wasi:sockets dispatcher — read the overlay off the frame and route through it, whether it was set by with_options, by meta.options.network on the entry, or by network_service.default_network.
The private-IP gate behaves differently on that path. A direct dial resolves the target and checks every resulting address against socket.private_ip. With an overlay selected, only a literal IP address in the target is checked; host names are handed to the overlay to resolve, so the local resolver is never consulted and no check is made on what it would have returned.
When an overlay is selected but the context carries no network registry, the dial fails with network "<id>" selected without a network registry.
Updating Overlays
Overlay entries are replaced on registry update. The driver builds the replacement before switching to it; if creation fails, the existing overlay continues running. A successful swap is atomic for new lookups, then the previous service is closed. Work already using the previous service can therefore observe that closure.
Permissions
| Action | Resource | Description |
|---|---|---|
network.select |
Network registry ID | Explicit overlay selection at funcs.call, process.spawn, http_client |
network.bind |
Network registry ID | Binding an http.service listener through an overlay (the network: field) |
socket.connect |
host:port |
Any outbound dial through the network service |
socket.listen |
host:port |
Binding a TCP listener or a UDP socket through the network service |
socket.resolve |
Host name | DNS resolution through the network service |
socket.private_ip |
IP address | Reaching a loopback, private, link-local or unspecified address |
Deny network.select on a scope to stop code inside it from choosing an overlay explicitly. Inherited overlays are unaffected — they were authorized at the caller. network.bind is checked when a server with a network: overlay starts its listener.
The socket.* permissions are checked by the network service itself. socket.connect, socket.listen and socket.resolve are checked before any overlay routing, so they apply equally to clearnet and overlay traffic; socket.private_ip narrows to literal addresses once an overlay is selected, as described under Raw Dials.
See Also
- Security - Policies and actors
- HTTP Service - Server binding
- HTTP Client - Per-call overlay selection
- Host Functions - WASM socket imports