← All posts

One pipe, many priorities: VPN routing, QoS, and a connection flood that wouldn't quit

Last week was storage; this week was the network. The exit-VPN went from "it works" to "it's configurable, and it survives being abused" — per-destination routing, pluggable transports, router-grade QoS, and a week-long fight with a connection flood that kept killing DNS.

Mixed traffic classified, prioritised and shaped through one VPN pipe

Last week was storage. This week was the network — specifically the exit-VPN, which went from "it works" to "it's configurable, and it survives being abused." Most of the abuse was self-inflicted: I spent a good chunk of the week pointing connection-hungry software at my own tunnel — the kind that fans out into thousands of sockets at once — to watch things break.

Routing got opinions

The exit-VPN used to be all-or-nothing: flip it on, everything goes through one exit. Now it's a policy. Send some destinations through an exit and others direct; keep an ordered chain of exits with priorities, so if your first choice drops offline the tunnel fails over to the next without tearing down your session. Split-tunnel exclusions, per-destination rules, and the whole thing collapsed into a single exit-nodes panel on the Network page instead of the three overlapping toggles it used to be.

Pluggable transports

Underneath, the tunnel no longer speaks one wire format. It now rides interchangeable carriers: a plain elastic TCP pool, native QUIC, and obfuscated variants that present as ordinary HTTP/3 or an SSH session — so a node can still connect from a network where plain tunnels get throttled or blocked. Same circuits, same encryption; different disguise.

The connection flood that wouldn't quit

Here's the bug that ate the week. Run a piece of software that fans out into thousands of connections over the VPN and DNS would die — pages stopped resolving while it happily saturated the pipe. Underneath it was three things stacked: UDP packets head-of-line-blocking each other, bufferbloat from oversized windows, and a goroutine-per-flow explosion once there were thousands of flows. The fix needed real instrumentation — load tests that reproduce that connection flood in-process so I could watch one DNS lookup try to squeeze past it — plus per-flow scheduling, idle reapers that evict the stalest flow instead of the newest, and capped windows. Your name lookups now survive whatever else the pipe is doing.

Router-grade QoS

The proper answer to "this download is starving my call" isn't a hack, it's a scheduler. So the VPN egress now runs a real classifier and a software traffic shaper: prioritise interactive traffic, cap the bulk lane, shape the download direction too, with one-click presets (balanced, gaming, voip) and full control on the CLI, the REST API, and a Traffic-prioritisation panel in the web UI.

It was a deep, unglamorous week — exactly the kind the "going deep" phase is made of. The network is quieter now, and that's the whole point.


Since last week

  • Exit-VPN routing rework: split-tunnel exclusions + per-destination multi-exit policy + a priority-ordered exit chain with automatic failover, all unified into one exit-nodes panel
  • Pluggable tunnel carriers: an elastic plain-TCP pool, native QUIC, and obfuscated obfs-quic / obfs-ssh — plus a long fight with UDP head-of-line blocking, bufferbloat, and flood survival, backed by real in-process load tests
  • Router-grade QoS: a classifier + software shaper with downlink shaping and balanced / gaming / voip presets, across CLI, REST, and web
  • Version history & retention: per-node keep-history with a retention policy, Syncthing-style staggered history, history-safe garbage collection, and .holdignore ignore rules
  • Player & desktop polish: Windows taskbar thumbnail media controls, Now Playing backfill + buffering feedback, Android pause on headset unplug