v0.16.0
Fixes a connection failure that looked intermittent and was reported as "some players on a VPN can't connect".
The bug
The handshake probes the path MTU in one direction only — the connecting peer pads ID_OPEN_CONNECTION_REQUEST_1 down the ladder, the accepting peer echoes back whatever size arrived — and the result is then frozen for the life of the connection and applied to both directions. Nothing re-probes, and nothing detects a path-MTU black hole afterwards, so a datagram too large for the return path is resent at the same size until the connection times out.
Every handshake packet is small enough to survive that, so the failure landed on the first split payload instead: the peer connected and then hung or dropped. Which tunnelled peers it hit depended entirely on their exit node's encapsulation overhead — WireGuard 1420, Tailscale and many providers 1280, IKEv2 ~1400 — against an old top rung of 1492.
Changes
MAXIMUM_MTU_SIZE1492 → 1400. The top rung now clears WireGuard and typical IPSec/IKEv2 on a 1500-byte path without the peer having to discover anything. The ladder gains 1280 (the IPv6 minimum, and where WireGuard-derived tunnels commonly sit) and 1024, so stepping down costs far less payload capacity than the old 1492 → 1200 jump.- Security: a peer-reported MTU is clamped. It arrives unauthenticated in
ID_OPEN_CONNECTION_REQUEST_2/ID_OPEN_CONNECTION_REPLY_2and sizes every datagram the reliability layer builds intoMAXIMUM_MTU_SIZE-byte buffers. The only guard was aRakAssert, compiled out of exactly the builds that ship — a release build would adopt an MTU of 65535 from a single forged datagram. Verified: with the clamp removed, a Release build does exactly that. - A locally-refused MTU no longer burns its rung's attempt budget. The check compared
Send()'s return value against10040, whichsendtonever yields (it yieldsSOCKET_ERROR), so the branch was dead. NewRNS2_GetLastSocketError()/RNS2_IsDatagramTooLargeError()read the real error. - A
sendtoblocking over 100 ms no longer abandons the connection attempt when already on the lowest rung — that turned a transient stall on a virtual adapter intoID_CONNECTION_ATTEMPT_FAILEDwith attempts to spare. - Fixed a divide-by-zero for any
sendConnectionAttemptCountbelow the number of MTU rungs.Connect()enforces no lower bound; it was latent only because the default (12) exceeded the ladder.
Upgrading
Non-breaking. RAKNET_PROTOCOL_VERSION stays at 7 and no message ids move.
Two peers converge on the smaller of their two caps, because the accepting side clamps to its own MAXIMUM_MTU_SIZE before replying and both sides clamp what they are told. A server rebuilt on 0.16.0 therefore caps every connection — including clients still built against 0.15.0 — with no client update required.
Lowering MAXIMUM_MTU_SIZE further is safe if your peers sit behind heavier encapsulation. Raising it past ~1420 reintroduces the failure, and a unit test now fails if it is.
Known limitation
There is still no in-session path-MTU black-hole detection: the MTU is fixed at connection time and never re-probed. This release makes the negotiated value conservative enough that the common tunnel cases work; making the transport self-healing (halve the MTU when the resend queue goes N ms with zero acks) is a follow-up.
What's Changed
- fix(peer): stop tunnelled clients black-holing on the negotiated MTU by @Segfaultd in #55
Full Changelog: v0.15.0...v0.16.0