August 29–31, 2026
Primary-Link Failover, a Real Latency Mystery Finally Closed, and a New Job
Three days that closed out a genuinely months-old mlvpn-rs mystery, shipped a brand-new way to run the tunnel, and turned up a real performance bug live on production hardware that isn't fully fixed yet. Plus a personal update: tomorrow I start a new part-time job.
Closing a mystery that's been open for months
Long-time readers of this blog (or anyone who's heard me complain about it in person) know about
the "real UDP loss, zero counters" mystery — real-world packet loss showing up under load, while
every single counter mlvpn-rs keeps on itself stayed at zero the whole time. It survived several
releases without ever getting root-caused. It's closed now, and it turned out to never have been
loss at all — it was delay, badly misread by everything downstream as loss.
The culprit was the piece of the tunnel responsible for putting packets back in the right order
when they arrive out of sequence — think of it as a bouncer at the door, holding a packet back
briefly if it looks like it arrived ahead of one that should have come first. The bug: it was
comparing each real chunk of data against a counter that's actually shared by every kind
of message the tunnel sends, not just real data — so the specific number it was waiting for almost
never showed up as an actual data packet, and it ended up holding nearly everything back for
almost the full delay window, every time, in both directions. That's around 110 milliseconds of
pure, needless added delay, which is exactly the kind of thing a tool like iperf3 can mistake for
loss when a retransmit timer fires early.
Fixed that in one release, watched it work great right after a fresh restart — then watched it
quietly break itself again a couple minutes later. The fix kept track of the highest sequence
number it had ever seen, but that tracking doesn't survive the tunnel's own routine security key
rotation (every two minutes by default), which restarts its own numbering from near zero. So the
first rotation after a restart permanently stranded the tracking above where real traffic could
ever reach it again, silently undoing the fix — and getting slightly worse with every rotation
after that. Caught this for real: measured a healthy connection right after restart, watched it
degrade back to the old bad numbers a few minutes later, while a plain ping on the same physical
link never budged. Fixed by resetting that tracking whenever a new security session starts, and
confirmed by sitting there and watching it survive a real key rotation with the connection staying
healthy the whole time.
A new way to run the tunnel: one link at a time, with failover
mlvpn-rs has always worked by spreading traffic across every internet connection it's given at
once, to add their speeds together. That's great when every link is roughly similar, but not
always what you want — sometimes one connection is simply better, and you'd rather use it
exclusively and only fall back to the other one if it actually drops. Built exactly that: a new
mode where one link is designated primary and carries everything, with automatic failover to
another link the moment the primary looks unhealthy, plus a variant where one side of the tunnel
can tell the other which link it's actually using, so both ends make the same choice instead of
guessing independently. Deployed it the same night on my own real home-to-cloud router pair — not
a lab test, the actual pair that's been carrying real traffic this whole time.
Small companion feature that landed the same night: both sides of a tunnel now show each other's
real tunnel size (MTU) live, so a mismatch between the two ends is something you can actually see
instead of having to guess at from symptoms.
A real performance bug, caught live, not yet fully fixed
While all that was going out to production, went looking at exactly how efficiently the daemon
was actually using the CPU on the real routers — using a tool that watches, moment by moment,
what a running program is actually doing. What it found: the daemon was spending somewhere around
75 to 80 percent of its time waiting to get exclusive access to shared internal data, rather than
actually moving traffic. That's a real bottleneck, capping real throughput well below what the
underlying links can actually carry, and it was invisible without watching the program run live —
nothing about it showed up as an error or a dropped packet anywhere.
Shipped two same-night follow-up fixes that each cut down on how often that contention happens.
Being straightforward about where this stands: it's better, but it isn't fully resolved yet. This
is exactly the kind of bug where I'd rather say "meaningfully improved, more work still to do"
than claim it's solved before I've actually proven that with more measurement on the real hardware.
Smaller things, and a personal update
Also in this stretch: raised the size of an internal queue the tunnel uses on its way out to the
network, since the old default could start dropping bursts of traffic before the rest of the
pipeline even got a chance to handle them — cheap insurance against a whole class of bursty-traffic
problems. And fixed a smaller but real annoyance in the build pipeline: pushing a release tag was
always treated as a brand-new, never-before-seen branch for caching purposes, so every single
tagged release was forced through a slow, fully-cold rebuild even when the exact same code had
just been built minutes earlier on the main branch. Now a tagged release reuses that work instead
of redoing it.
On the personal side: tomorrow I start a new part-time job as a sales representative at the Ace
Hardware here in Zion. Different kind of work than anything above — good to have something that
isn't a keyboard for a few hours a week.
← Back to Blog