rotate egress over a larger WG pool, with graceful drain and live status
Keep the N always-on tunnel slots fixed but let each slot's WireGuard config
rotate through a larger pool, so a 10-concurrent provider cap (e.g. Proton) can
still cycle 50-100 profiles.
- lxc/rotate.sh + weircon-rotate.{service,timer}: round-robin one slot at a
time through wg-pool/, repointing a symlink and restarting only that slot.
- service: proxyManager tracks per-slot in-flight + drain/undrain state; a
localhost admin server (WEIRCON_ADMIN_LISTEN) lets rotate.sh drain a slot
before teardown and warm it back in after, so no request is routed to a
tunnel mid-rotation. Slots self-heal if undrain never arrives.
- GET /status: poll-friendly JSON of per-slot egress IP/state plus inferred
next-rotation slot + ETA, fed by a background egress-IP prober.
- docs + env examples for all new knobs.
This commit is contained in:
@@ -7,3 +7,28 @@ WEIRCON_PROXY_ADDRS=10.99.0.10:1080,10.99.0.11:1080,10.99.0.12:1080,10.99.0.13:1
|
||||
|
||||
# Maks tid pr. upstream-fetch (sekunder).
|
||||
WEIRCON_REQUEST_TIMEOUT_SEC=30
|
||||
|
||||
# Admin-endpoint til rotation (drain/undrain). Kun localhost — må ALDRIG
|
||||
# eksponeres via reverse-proxy. Tom streng slår admin-serveren helt fra.
|
||||
WEIRCON_ADMIN_LISTEN=127.0.0.1:8081
|
||||
|
||||
# Rotation-drænings-tuning (sekunder):
|
||||
# TIMEOUT = maks ventetid på at in-flight requests på en slot tømmes
|
||||
# SETTLE = ekstra ro-periode efter in-flight = 0 ("et par sek. før")
|
||||
# WARMUP = slot holdes ude af rotation efter undrain, så WG-handshaket
|
||||
# kan sætte sig ("et par sek. efter")
|
||||
# MAX_HOLD= sikkerhed: slot self-healer og kommer tilbage selv hvis
|
||||
# rotate.sh dør mellem drain og undrain
|
||||
WEIRCON_DRAIN_TIMEOUT_SEC=25
|
||||
WEIRCON_DRAIN_SETTLE_SEC=2
|
||||
WEIRCON_DRAIN_WARMUP_SEC=3
|
||||
WEIRCON_DRAIN_MAX_HOLD_SEC=60
|
||||
|
||||
# Egress-IP prober: henter den aktuelle udgangs-IP gennem hver tunnel og cacher
|
||||
# den, så /status kan vise hvilken IP der sidder på hver slot (og hvilken der
|
||||
# snart roteres ud). INTERVAL=0 slår probing helt fra (så er egress_ip tom i
|
||||
# /status, og klienten må selv probe). URL skal returnere klientens IP som ren
|
||||
# tekst (ipify, ifconfig.me/ip, egen endpoint, …).
|
||||
WEIRCON_PROBE_URL=https://api.ipify.org
|
||||
WEIRCON_PROBE_INTERVAL_SEC=60
|
||||
WEIRCON_PROBE_TIMEOUT_SEC=10
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Kopiér til /etc/weircon-random-proxy/rotate.env (valgfrit).
|
||||
# Læses af weircon-rotate.service.
|
||||
|
||||
# Antal aktive tunnel-slots. SKAL matche det weircon-proxy@N-interval du har
|
||||
# enablet (fx weircon-proxy@{0..9} => SLOTS=10). Default hvis usat: 10.
|
||||
SLOTS=10
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#!/bin/bash
|
||||
# Roterer ÉN tunnel-slot ad gangen til den næste WG-config i puljen.
|
||||
#
|
||||
# - Slots (netns proxy0..proxy<SLOTS-1>) er faste: faste IP'er, faste
|
||||
# SOCKS-porte. Fetch-servicen kender kun de faste endpoints.
|
||||
# - Configs ligger i en pulje (wg-pool/) der kan være større end SLOTS.
|
||||
# - Hver slot's aktive config er et symlink:
|
||||
# wg/proxy<ID>.conf -> wg-pool/<navn>.conf
|
||||
# - Hvert kald her flytter præcis én slot videre til næste ledige config
|
||||
# i puljen og genstarter kun den slot. De øvrige 9 bliver oppe, så der
|
||||
# altid er egress ud — og over tid cykler alle configs igennem.
|
||||
#
|
||||
# Hvorfor én ad gangen: holder os trygt inden for udbyderens samtidigheds-
|
||||
# grænse (fx Proton: max 10 samtidige WG-sessioner) og dropper aldrig al
|
||||
# egress på én gang. weircon-proxy@.service river desuden den gamle tunnel
|
||||
# ned (ExecStopPost) FØR den nye rejses (ExecStartPre), så en slot er aldrig
|
||||
# dobbelt-forbundet midt i en rotation.
|
||||
#
|
||||
# Brug:
|
||||
# rotate.sh # roter én slot (cursor i state-filen rykker)
|
||||
# rotate.sh init # seed alle slots med de første SLOTS distinkte configs
|
||||
set -euo pipefail
|
||||
|
||||
POOL="/etc/weircon-random-proxy/wg-pool"
|
||||
ACTIVE="/etc/weircon-random-proxy/wg"
|
||||
STATE="/var/lib/weircon-random-proxy/rotate.state"
|
||||
SLOTS="${SLOTS:-10}"
|
||||
# Fetch-servicens admin-endpoint (lokal, ikke eksponeret via reverse-proxy).
|
||||
# Tom => spring drain/undrain over (falder tilbage til ren restart).
|
||||
ADMIN_URL="${WEIRCON_ADMIN_URL:-http://127.0.0.1:8081}"
|
||||
|
||||
# Pulje-configs, sorteret og stabilt ordnet (kun basenavne).
|
||||
mapfile -t POOL_CONFIGS < <(find "$POOL" -maxdepth 1 -type f -name '*.conf' -printf '%f\n' 2>/dev/null | sort)
|
||||
N="${#POOL_CONFIGS[@]}"
|
||||
if (( N == 0 )); then
|
||||
echo "ingen configs i $POOL — læg proton-*.conf filer der" >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( N < SLOTS )); then
|
||||
echo "WARN: kun $N configs i puljen til $SLOTS slots — der vil være gengangere" >&2
|
||||
fi
|
||||
|
||||
# Hvilke configs er allerede i brug af en ANDEN slot end $skip_slot?
|
||||
configs_in_use() {
|
||||
local skip="$1" i link
|
||||
for (( i = 0; i < SLOTS; i++ )); do
|
||||
(( i == skip )) && continue
|
||||
link="$ACTIVE/proxy${i}.conf"
|
||||
[[ -L "$link" ]] && basename "$(readlink -f "$link")"
|
||||
done
|
||||
}
|
||||
|
||||
# Find næste config i puljen fra position $1 som ikke er i $2..(resten).
|
||||
# Sætter globalt $CHOSEN og $NEXT_POS.
|
||||
pick_free_config() {
|
||||
local start="$1" skip_slot="$2" k cand
|
||||
local -a busy
|
||||
mapfile -t busy < <(configs_in_use "$skip_slot")
|
||||
for (( k = 0; k < N; k++ )); do
|
||||
cand="${POOL_CONFIGS[(start + k) % N]}"
|
||||
if ! printf '%s\n' "${busy[@]}" | grep -qxF "$cand"; then
|
||||
CHOSEN="$cand"
|
||||
NEXT_POS=$(( (start + k + 1) % N ))
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Bed fetch-servicen om at stoppe ny trafik til en slot og vente til
|
||||
# in-flight er drænet (servicen håndterer selv ventetid + settle). Stille
|
||||
# no-op hvis admin-endpointet ikke svarer (fx UI/service slået fra).
|
||||
drain_slot() {
|
||||
local slot="$1"
|
||||
[[ -z "$ADMIN_URL" ]] && return 0
|
||||
curl -fsS -m 60 -X POST "${ADMIN_URL}/drain?id=${slot}" >/dev/null 2>&1 || \
|
||||
echo "WARN: drain af slot $slot fejlede (fortsætter)" >&2
|
||||
}
|
||||
|
||||
# Giv slot tilbage til servicen. Servicen holder den selv ude af rotation et
|
||||
# par sekunder mere (warmup) så den friske WG-handshake kan nå at sætte sig.
|
||||
undrain_slot() {
|
||||
local slot="$1"
|
||||
[[ -z "$ADMIN_URL" ]] && return 0
|
||||
curl -fsS -m 10 -X POST "${ADMIN_URL}/undrain?id=${slot}" >/dev/null 2>&1 || \
|
||||
echo "WARN: undrain af slot $slot fejlede (slot self-healer)" >&2
|
||||
}
|
||||
|
||||
assign_slot() {
|
||||
local slot="$1" cfg="$2"
|
||||
drain_slot "$slot" # stop trafik FØR teardown
|
||||
ln -sfn "$POOL/$cfg" "$ACTIVE/proxy${slot}.conf"
|
||||
systemctl restart "weircon-proxy@${slot}.service" # river gammel ned, rejser ny
|
||||
undrain_slot "$slot" # genoptag trafik EFTER (m. warmup)
|
||||
}
|
||||
|
||||
mkdir -p "$(dirname "$STATE")"
|
||||
|
||||
# --- init: seed alle slots distinkt -------------------------------------
|
||||
if [[ "${1:-}" == "init" ]]; then
|
||||
pos=0
|
||||
for (( s = 0; s < SLOTS; s++ )); do
|
||||
if ! pick_free_config "$pos" "$s"; then
|
||||
echo "kunne ikke finde ledig config til slot $s" >&2
|
||||
exit 1
|
||||
fi
|
||||
ln -sfn "$POOL/$CHOSEN" "$ACTIVE/proxy${s}.conf"
|
||||
pos="$NEXT_POS"
|
||||
echo "slot $s -> $CHOSEN"
|
||||
done
|
||||
printf 'slot=0\npos=%s\n' "$pos" > "$STATE"
|
||||
echo "seedet $SLOTS slots; start/genstart tunnelerne for at aktivere"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- normal: roter præcis én slot ---------------------------------------
|
||||
slot=0
|
||||
pos=0
|
||||
# shellcheck disable=SC1090
|
||||
[[ -f "$STATE" ]] && source "$STATE"
|
||||
(( slot >= SLOTS )) && slot=0
|
||||
|
||||
if ! pick_free_config "$pos" "$slot"; then
|
||||
echo "kunne ikke finde ledig config til slot $slot" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
assign_slot "$slot" "$CHOSEN"
|
||||
echo "roterede slot $slot -> $CHOSEN ($((slot + 1))/$SLOTS denne runde)"
|
||||
|
||||
# Ryk cursors: næste slot, og pulje-position er allerede sat af pick_free_config.
|
||||
slot=$(( (slot + 1) % SLOTS ))
|
||||
printf 'slot=%s\npos=%s\n' "$slot" "$NEXT_POS" > "$STATE"
|
||||
+20
-2
@@ -54,18 +54,26 @@ chmod +x "$BIN"
|
||||
# 4. Helper-scripts
|
||||
install -Dm755 "$SCRIPT_DIR/netns-up.sh" /usr/local/sbin/weircon-netns-up
|
||||
install -Dm755 "$SCRIPT_DIR/netns-down.sh" /usr/local/sbin/weircon-netns-down
|
||||
install -Dm755 "$SCRIPT_DIR/rotate.sh" /usr/local/sbin/weircon-rotate
|
||||
|
||||
# 5. systemd units
|
||||
install -Dm644 "$SCRIPT_DIR/weircon-proxies.target" /etc/systemd/system/weircon-proxies.target
|
||||
install -Dm644 "$SCRIPT_DIR/weircon-proxy@.service" /etc/systemd/system/weircon-proxy@.service
|
||||
install -Dm644 "$SCRIPT_DIR/weircon-fetch.service" /etc/systemd/system/weircon-fetch.service
|
||||
install -Dm644 "$SCRIPT_DIR/weircon-rotate.service" /etc/systemd/system/weircon-rotate.service
|
||||
install -Dm644 "$SCRIPT_DIR/weircon-rotate.timer" /etc/systemd/system/weircon-rotate.timer
|
||||
|
||||
# 6. Config-dir + default env
|
||||
mkdir -p /etc/weircon-random-proxy/wg
|
||||
chmod 700 /etc/weircon-random-proxy/wg
|
||||
# wg/ = aktive slot-configs (proxy0.conf..proxyN.conf, evt. symlinks)
|
||||
# wg-pool/ = valgfri pulje af ekstra configs som rotationen cykler igennem
|
||||
mkdir -p /etc/weircon-random-proxy/wg /etc/weircon-random-proxy/wg-pool
|
||||
chmod 700 /etc/weircon-random-proxy/wg /etc/weircon-random-proxy/wg-pool
|
||||
if [[ ! -f /etc/weircon-random-proxy/fetch.env ]]; then
|
||||
install -m640 "$SCRIPT_DIR/fetch.env.example" /etc/weircon-random-proxy/fetch.env
|
||||
fi
|
||||
if [[ ! -f /etc/weircon-random-proxy/rotate.env ]]; then
|
||||
install -m640 "$SCRIPT_DIR/rotate.env.example" /etc/weircon-random-proxy/rotate.env
|
||||
fi
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
@@ -89,6 +97,16 @@ Start stakken:
|
||||
systemctl enable --now weircon-proxy@{0..9}.service
|
||||
systemctl enable --now weircon-fetch.service
|
||||
|
||||
(Valgfrit) Roter over en STØRRE pulje af configs end de 10 slots:
|
||||
# læg fx 50-100 configs i puljen
|
||||
cp proton-*.conf /etc/weircon-random-proxy/wg-pool/
|
||||
# seed de 10 aktive slots som symlinks ind i puljen
|
||||
weircon-rotate init
|
||||
systemctl restart weircon-proxy@{0..9}.service
|
||||
# roter automatisk én slot ad gangen (interval i weircon-rotate.timer)
|
||||
systemctl enable --now weircon-rotate.timer
|
||||
# juster intervallet: systemctl edit weircon-rotate.timer (default 5min)
|
||||
|
||||
Verificér:
|
||||
systemctl status 'weircon-proxy@*'
|
||||
curl http://127.0.0.1:8080/health
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=weircon-random-proxy: roter én tunnel-slot til næste WG-config i puljen
|
||||
After=weircon-proxies.target
|
||||
Wants=weircon-proxies.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
EnvironmentFile=-/etc/weircon-random-proxy/rotate.env
|
||||
ExecStart=/usr/local/sbin/weircon-rotate
|
||||
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=weircon-random-proxy: periodisk slot-rotation
|
||||
PartOf=weircon-proxies.target
|
||||
|
||||
[Timer]
|
||||
# Standard: roter én slot hvert 5. minut. Med 10 slots betyder det at hele
|
||||
# slot-sættet er cyklet igennem ca. hvert 50. minut, og hver enkelt config i
|
||||
# en pulje på N filer er brugt mindst én gang efter ca. (N * 5) minutter.
|
||||
#
|
||||
# Skift interval uden at redigere denne fil:
|
||||
# systemctl edit weircon-rotate.timer
|
||||
# og indsæt fx:
|
||||
# [Timer]
|
||||
# OnUnitActiveSec= # (tom linje nulstiller arvet værdi)
|
||||
# OnUnitActiveSec=15min
|
||||
OnBootSec=5min
|
||||
OnUnitActiveSec=5min
|
||||
AccuracySec=15s
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user