Files
weircon-random-proxy/README.md
T
Asger Weirsøe 8652fcfbba
release / release (push) Successful in 1m15s
Initial commit
2026-05-27 15:02:44 +02:00

11 KiB

weircon-random-proxy

A small HTTP fetch service that forwards client requests through one of N upstream WireGuard tunnels and returns the response. Designed to run as an unprivileged LXC on Proxmox, with each tunnel isolated in its own network namespace.

Use it whenever you need an HTTP-facing API that returns results from rotating egress IPs — testing geo-restricted endpoints, distributing outbound traffic across providers, hiding a backend's real source IP, building rate-limit-resilient clients, and so on. It is intentionally generic and doesn't care what consumer calls it.

Architecture

                            Internet
                               │
                               ▼
              ┌──────────────────────────────────┐
              │  Reverse proxy (e.g. NPM, nginx) │
              │  • TLS termination               │
              │  • Validates X-WEIRCON-RANDOM-IP │
              │    (API key) header              │
              │  • Strips header before forward  │
              └──────────────┬───────────────────┘
                             │  http://<lxc-ip>:8080
                             ▼
   ┌──────────────────────────────────────────────────────┐
   │ LXC: weircon-random-proxy  (unprivileged + TUN)      │
   │                                                      │
   │  ┌────────────────────────────────────────────────┐  │
   │  │ fetch-service (Go, net/http + ReverseProxy)    │  │
   │  │  • Reads target URL from:                      │  │
   │  │     - query: ?url=https://example.com          │  │
   │  │     - header: X-WEIRCON-RANDOM-IP-REDIRECT     │  │
   │  │  • Reads tunnel selection from:                │  │
   │  │     - header: X-WEIRCON-PROXY-ID (0..N-1)      │  │
   │  │     - default: random                          │  │
   │  │  • SOCKS5 to 10.99.0.{10+id}:1080              │  │
   │  │  • Streams response chunked                    │  │
   │  │  • Optional /ui — built-in API tester          │  │
   │  └─────────────────┬──────────────────────────────┘  │
   │                    │                                 │
   │             ┌──────┴──────┐ br-weircon (10.99.0.1/24)│
   │             │   bridge    │                          │
   │             └──┬───┬───┬──┘                          │
   │       veth ────┘   │   └──── veth                    │
   │   ┌─────────┐ ┌─────────┐ ┌─────────┐                │
   │   │ netns:  │ │ netns:  │ │ netns:  │  …× N          │
   │   │ proxy0  │ │ proxy1  │ │ proxyN-1│                │
   │   │ veth:   │ │ veth:   │ │ veth:   │                │
   │   │10.99.0.10│ │10.99.0.11│ │10.99.0.X│                │
   │   │  + wg0  │ │  + wg0  │ │  + wg0  │                │
   │   │ + micro │ │ + micro │ │ + micro │                │
   │   │ socks   │ │ socks   │ │ socks   │                │
   │   │ :1080   │ │ :1080   │ │ :1080   │                │
   │   └────┬────┘ └────┬────┘ └────┬────┘                │
   │        ▼           ▼           ▼                     │
   │        WireGuard egress (any provider)               │
   └──────────────────────────────────────────────────────┘

Tunnel count is configurable. Default deploy uses 10 tunnels; the WireGuard configs themselves can come from any provider that gives you a standard [Interface] / [Peer] config (ProtonVPN, Mullvad, AzireVPN, self-hosted, …). Drop the configs into /etc/weircon-random-proxy/wg/proxy<N>.conf and the rest is identical.

Client API

Fetch a URL through a random tunnel:

curl -H "X-WEIRCON-RANDOM-IP: $API_KEY" \
     -H "X-WEIRCON-RANDOM-IP-REDIRECT: https://api.ipify.org" \
     https://random-proxy.example.com/

Or with the URL as a query parameter:

curl -H "X-WEIRCON-RANDOM-IP: $API_KEY" \
     "https://random-proxy.example.com/?url=https://api.ipify.org"

Pin a specific tunnel:

curl -H "X-WEIRCON-RANDOM-IP: $API_KEY" \
     -H "X-WEIRCON-PROXY-ID: 3" \
     "https://random-proxy.example.com/?url=https://api.ipify.org"

Parameters

Source Name Required Description
Reverse-proxy hdr X-WEIRCON-RANDOM-IP yes* API key. Validated and stripped by the reverse proxy in front.
Header or query X-WEIRCON-RANDOM-IP-REDIRECT / ?url= yes Target URL. Header takes precedence over the query parameter.
Header X-WEIRCON-PROXY-ID no 0..N-1. Omitted: pick at random.
Header X-WEIRCON-FORWARD-METHOD no HTTP method sent to the target. Default: same as incoming.
Body passthrough no Forwarded as-is on POST/PUT/PATCH.

*Only when a reverse proxy is in front. Direct LAN access doesn't require it.

Response

Status, headers, and body of the upstream response are returned directly. The service adds X-WEIRCON-EGRESS-PROXY: <id> so the caller can see which tunnel was used.

Built-in API tester (/ui)

The fetch service ships with an embedded HTML page at /ui that documents the API and lets you exercise every endpoint interactively:

  • Enter API key (stored in browser localStorage).
  • "Try it out" form for / with all parameters as inputs.
  • Live cURL equivalent.
  • Tunnel health panel — sends a probe to each tunnel and shows egress IP + latency. A healthy stack returns N distinct IPs.

The UI is enabled by default. Set WEIRCON_UI_ENABLED=false in fetch.env to disable it (the rest of the service is unaffected). The UI is also gated by your reverse proxy's auth check by default — see npm/advanced.conf for an optional exception that allows the UI page itself to load without an API key.

Components

Path Purpose
service/main.go Go fetch service (static binary, runs in the LXC's main netns).
service/ui.html Embedded API tester page served at /ui.
service/Makefile make build → ~6MB static binary; make install to /usr/local/bin.
lxc/setup-host.md Proxmox host: pct create, TUN passthrough, push instructions.
lxc/setup-container.sh Runs inside the LXC: apt deps, microsocks, helper scripts, systemd units.
lxc/netns-up.sh Brings one tunnel namespace up (proxy<N> with wg0 + veth + bridge).
lxc/netns-down.sh Tears it back down (invoked by systemd ExecStopPost).
lxc/systemd/weircon-proxies.target Grouping target for all tunnels.
lxc/systemd/weircon-proxy@.service Templated unit. Enable with weircon-proxy@{0..N-1}.service.
lxc/systemd/weircon-fetch.service The fetch service itself. Hardened (DynamicUser, no capabilities).
lxc/fetch.env.example Default env. Copy to /etc/weircon-random-proxy/fetch.env.
npm/advanced.conf Reverse-proxy snippet: header check, strip, forward.
wg-configs/ Local placeholder. Actual .conf files live on the LXC, not in git.

Service env vars

Var Default Description
WEIRCON_LISTEN :8080 host:port to bind.
WEIRCON_PROXY_ADDRS (computed) Comma-separated host:port list of upstream SOCKS5 endpoints.
WEIRCON_PROXY_HOST 127.0.0.1 Fallback host when WEIRCON_PROXY_ADDRS is unset.
WEIRCON_PROXY_BASE_PORT 25400 First port in the fallback range.
WEIRCON_PROXY_COUNT 10 Number of fallback endpoints.
WEIRCON_REQUEST_TIMEOUT_SEC 30 Per-upstream-fetch ceiling.
WEIRCON_UI_ENABLED true Toggle the embedded /ui page.

If WEIRCON_PROXY_ADDRS is set it wins; otherwise the service computes WEIRCON_PROXY_HOST:WEIRCON_PROXY_BASE_PORT + i for i in [0, WEIRCON_PROXY_COUNT).

Service internals

  • net/http/httputil.ReverseProxy streams response bodies chunked, without buffering.
  • One *http.Transport per tunnel — connection pools stay separate per egress.
  • golang.org/x/net/proxy.SOCKS5 ContextDialer on each Transport.
  • Static CGO_ENABLED=0 binary, ~6 MB, no runtime dependencies in the LXC.

Build:

cd service && make build      # → ./weircon-random-proxy

Deploy

Dev machine:

cd service && make build      # → ./weircon-random-proxy

Proxmox host (see lxc/setup-host.md for full detail):

  1. pct create with --unprivileged 1 --features nesting=1,keyctl=1 (Debian 12).
  2. Add TUN passthrough lines to /etc/pve/lxc/<ctid>.conf.
  3. pct push binary, scripts, units, and your WireGuard configs into the container.
  4. pct enter <ctid> && bash /root/setup-container.sh.
  5. systemctl enable --now weircon-proxies.target weircon-proxy@{0..9}.service weircon-fetch.service.
  6. Point your reverse proxy at the LXC on port 8080; paste npm/advanced.conf into its advanced config with your API key.
  7. Open https://random-proxy.example.com/ui to verify.

Releases

Tagged builds (v*) trigger .gitea/workflows/release.yml which produces a tarball with the binary + LXC scripts + reverse-proxy config bundled for one-shot deployment.