177 lines
11 KiB
Markdown
177 lines
11 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
curl -H "X-WEIRCON-RANDOM-IP: $API_KEY" \
|
|
"https://random-proxy.example.com/?url=https://api.ipify.org"
|
|
```
|
|
|
|
Pin a specific tunnel:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```sh
|
|
cd service && make build # → ./weircon-random-proxy
|
|
```
|
|
|
|
## Deploy
|
|
|
|
Dev machine:
|
|
|
|
```sh
|
|
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.
|