ネットワークオーバーレイ

アウトバウンドHTTPコールとスポーンされたプロセスをSOCKS5、Tailscale、またはI2Pオーバーレイ経由でルーティングします。

概要

Wippyは、関数、プロセス、HTTPクライアントからのトラフィックを透過的に運ぶオーバーレイネットワークをサポートします。各オーバーレイはレジストリエントリです。コードは呼び出しごとにオプトインし、子孫が明示的にオーバーライドするまでその選択が内部呼び出しに継承されます。

サポートされるオーバーレイ:

  • network.socks5 — 汎用SOCKS5プロキシ(TorのSOCKS5リスナーにも使用可)
  • network.tailscale — tsnetオーバーレイノード
  • network.i2p — I2P SAM v3ブリッジ

プロジェクト構造

netdemo/
├── wippy.lock
└── src/
    ├── _index.yaml
    └── probe.lua

ステップ1: オーバーレイを定義する

src/_index.yamlを作成:

version: "1.0"
namespace: app

entries:
  - name: processes
    kind: process.host
    lifecycle:
      auto_start: true

  - name: terminal
    kind: terminal.host
    lifecycle:
      auto_start: true

  # SOCKS5プロキシエントリ(Torはデフォルトで127.0.0.1:9050でリッスン)
  - name: tor
    kind: network.socks5
    host: 127.0.0.1
    port: 9050
    isolate_streams: true

  - name: probe
    kind: process.lua
    meta:
      command:
        name: probe
        short: Check outbound IP through overlays
    source: file://probe.lua
    method: main
    modules:
      - io
      - http_client
      - json

isolate_streams: trueを指定すると、SOCKS5ドライバーが接続ごとにランダムなクレデンシャルを生成し、Torが各ダイアルで新しいサーキットを開きます。

ステップ2: アウトバウンドコールをルーティングする

src/probe.luaを作成:

local io = require("io")
local http_client = require("http_client")
local json = require("json")

local function fetch_ip(overlay)
    local options = { timeout = "15s" }
    if overlay then
        options.overlay_network = overlay
    end

    local resp, err = http_client.get("https://api.ipify.org?format=json", options)
    if err then
        return nil, tostring(err)
    end
    if resp.status_code ~= 200 then
        return nil, "HTTP " .. resp.status_code
    end

    local body = json.decode(resp.body or "")
    return body and body.ip, nil
end

local function main()
    local direct, d_err = fetch_ip(nil)
    if d_err then
        io.print("direct failed: " .. d_err)
    else
        io.print("direct IP: " .. direct)
    end

    local routed, r_err = fetch_ip("app:tor")
    if r_err then
        io.print("tor failed: " .. r_err)
    else
        io.print("tor IP:    " .. routed)
    end

    return 0
end

return { main = main }

http_clientoverlay_networkオプションは、その呼び出しのみにオーバーレイを適用します。指定しない場合、ダイアルはプロセスデフォルト(.wippy.yamlnetwork_service.default_networkまたはダイレクト)を使用します。

ステップ3: 実行する

wippy init
wippy run probe

Torがローカルで動作している場合:

direct IP: 203.0.113.42
tor IP:    185.220.101.61

Torが動作していない場合、tor IP行にダイアルエラーが報告されます — SOCKS5オーバーレイはダイレクト接続に静かにフォールバックしません。

継承

オーバーレイの選択はネストされた呼び出しを通じて伝播します。funcs.callまたはprocess.spawnの境界で一度オーバーレイを指定すれば、その下のすべての内部HTTPコール、ネストされたfuncs.call、およびprocess.spawnは明示的なオーバーライドがあるまでそれを使用します:

local funcs = require("funcs")

local result, err = funcs.new()
    :with_options({ network = "app:tor" })
    :call("app:scrape_site", url)
local pid, err = process.with_options({ network = "app:tor" })
    :spawn_monitored("app.workers:probe", "app:processes")

ネストされた関数またはスポーンされたプロセスは、明示的に渡さなくても、すべてのアウトバウンドダイアルでオーバーレイを使用します。

リスナーのバインド

インバウンドトラフィックをサポートするオーバーレイ(Tailscale、I2P)はHTTPリスナーも受け付けられます。クライアントの代わりにhttp.serviceにオーバーレイを付与します:

  - name: tailnet
    kind: network.tailscale
    hostname: wippy-node
    auth_key_env: TS_AUTHKEY
    ephemeral: true

  - name: gateway
    kind: http.service
    addr: ":8080"
    network: app:tailnet
    lifecycle:
      auto_start: true

サーバーはtailnetインターフェースにバインドし、クライアントはTailscaleアドレス経由でアクセスします。SOCKS5はアウトバウンド専用です — http.serviceに割り当てると拒否されます。

アプリ全体のデフォルト

.wippy.yamlにデフォルトオーバーレイを設定すると、オーバーライドされない限りすべての呼び出しで使用されます:

network_service:
  state_dir: .wippy/net
  default_network: app:tor

network = nilによる明示的な選択で、その呼び出しのデフォルトをクリアできます。

パーミッション

network.selectアクションが明示的なオーバーレイ選択を制御します。スコープで拒否するとコードがオーバーレイを選択できなくなります:

  - name: deny_network
    kind: security.policy
    policy:
      actions: "network.select"
      resources: "*"
      effect: deny
    groups:
      - untrusted

継承されたオーバーレイはこのチェックをバイパスします — 呼び出し元の境界で認可済みです。Lua境界での明示的な再選択のみが制御されます。

次のステップ