Network Overlays

Route outbound traffic and bind listeners through overlay networks (SOCKS5 proxies, Tor, Tailscale mesh, I2P). Overlay selection is opt-in per call and inherits across function, process, and HTTP boundaries.

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)

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 Inline tailnet auth key
auth_key_env string Env var name holding the auth key (resolved via env registry)
state_dir string Override for tsnet state directory
control_url string Alternate coordination server
ephemeral bool Register as an ephemeral tailnet node

Either auth_key or auth_key_env is required.

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

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 result, err = funcs.new()
    :with_options({ network = "app.net:proxy" })
    :call("app.api:fetch_data")
local pid, err = process.with_options({ network = "app.net:tailnet" })
    :spawn_monitored("app.workers:probe", "app:processes")

The http_client module accepts the same overlay selection on per-call options under the key overlay_network.

Inheritance

Overlay selection flows through the call stack. A function called via funcs.new():with_options({network=...}) sees the overlay on every inner dial, every nested funcs.call, and every process.spawn it performs — until a descendant explicitly selects a different overlay or clears it.

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.

Permissions

Action Resource Description
network.select Network registry ID Explicit overlay selection at funcs.call, process.spawn, http_client

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.

See Also