Перейти к содержимому

Оверлейные сети

Маршрутизация исходящих HTTP-запросов и порождаемых процессов через оверлеи SOCKS5, Tailscale или I2P.

Wippy поддерживает оверлейные сети, прозрачно передающие трафик, исходящий от функций, процессов и HTTP-клиентов. Каждый оверлей — это запись в реестре; код подключается к нему при каждом вызове, и выбор наследуется во вложенных вызовах, пока потомок явно не переопределит его.

Поддерживаемые оверлеи:

  • network.socks5 — универсальный SOCKS5-прокси (в том числе SOCKS5-слушатель Tor)
  • network.tailscale — оверлейный узел tsnet
  • network.i2p — мост I2P SAM v3
netdemo/
├── wippy.lock
└── src/
├── _index.yaml
└── probe.lua

Создайте 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 proxy entry (Tor exposes one at 127.0.0.1:9050 by default)
- 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 открывал новую цепочку при каждом подключении.

Создайте 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 }

Опция overlay_network в http_client выбирает оверлей только для данного вызова. Без неё соединение идёт через дефолтный оверлей процесса (либо network_service.default_network в .wippy.yaml, либо напрямую).

Окно терминала
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 контролирует явный выбор оверлея. Запретите его для scope, чтобы код не мог выбирать оверлей:

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

Унаследованные оверлеи обходят эту проверку — они были авторизованы на границе вызывающего кода. Под контролем находится только явный повторный выбор на границе Lua.