multiport: make a lane a session on the tunnel, not a tunnel

Lanes used to be separate HostInfos, each with its own handshake, its own
half-established states, its own lifetime and its own slot bookkeeping. That
bought nothing: a lane is the same tunnel over a different underlay 5-tuple.

Derive lane sessions instead. Noise leaves us with A.eKey == B.dKey, so both
sides HKDF-expand the same two keys with the same per-lane label and land on a
matched pair without exchanging anything. Lanes now cost no handshake, have no
half-established state, and die exactly when their base tunnel does. Which lane
a packet belongs to rides the low byte of the nebula header's Reserved field,
inside the AEAD's associated data.

Receiving on a lane needs no permission, since the session exists the moment the
base handshake completes. Sending on one needs proof the new 5-tuple works, so a
lane stays down until a probe on it is acked and falls back to the base tunnel
the moment it stops being acked. Probing is demand-driven off the connection
manager's per-tunnel traffic tick: a peer we exchange a trickle with never costs
more than its base tunnel, however many lanes are configured. The ack rides the
base session on purpose, so a broken reverse lane can't fail a working one.

Because the data now rides lane counters, the rehandshake, exhaustion and
swap-primary checks take the max counter across the base session and its lanes;
otherwise the base counter would sit near zero while a lane ran its keys past
the nonce ceiling.

Removes OutboundLaneTimer, EnsureLanes, startLaneHandshake, completeLane,
completeLaneResponder, makeLaneTrafficDecision and the lane fields on HostInfo.
Handshake payload field 3 (the per-lane handshake index) is permanently
reserved; peers advertise a TxLanes count instead.
This commit is contained in:
Wade Simmons committed 2026-09-02 14:48:17 -04:00
1 parent 8b914e67f4
commit fb20de39b2
19 files changed
+1223 -1273

No files matched your search

+16 -18
View File
@@ -61,30 +61,18 @@ func doFullLaneHandshake(t *testing.T, initLanes, respLanes *LaneDetails) (initR
func TestMachineLaneAdvertBothSides(t *testing.T) {
initR, respR := doFullLaneHandshake(t,
&LaneDetails{PortCount: 8, BasePort: 4242},
&LaneDetails{PortCount: 4, BasePort: 5353},
&LaneDetails{PortCount: 8, BasePort: 4242, TxLanes: 8},
&LaneDetails{PortCount: 4, BasePort: 5353, TxLanes: 3},
)
// Each side's Result carries the peer's advert.
assert.Equal(t, uint32(4), initR.PeerPortCount)
assert.Equal(t, uint32(5353), initR.PeerBasePort)
assert.Equal(t, uint32(0), initR.PeerLaneIndex)
assert.Equal(t, uint32(3), initR.PeerTxLanes)
assert.Equal(t, uint32(8), respR.PeerPortCount)
assert.Equal(t, uint32(4242), respR.PeerBasePort)
assert.Equal(t, uint32(0), respR.PeerLaneIndex)
}
func TestMachineLaneHandshakeCarriesLaneIndex(t *testing.T) {
// A lane handshake: initiator tags its lane number; responder still
// adverts (harmlessly).
initR, respR := doFullLaneHandshake(t,
&LaneDetails{PortCount: 8, BasePort: 4242, LaneIndex: 3},
&LaneDetails{PortCount: 4, BasePort: 5353},
)
assert.Equal(t, uint32(3), respR.PeerLaneIndex)
assert.Equal(t, uint32(0), initR.PeerLaneIndex)
assert.Equal(t, uint32(8), respR.PeerTxLanes)
}
func TestMachineLaneAdvertAsymmetric(t *testing.T) {
@@ -103,11 +91,21 @@ func TestMachineLaneAdvertAsymmetric(t *testing.T) {
func TestMachineLaneAdvertOutOfRangeIgnored(t *testing.T) {
// A BasePort that can't be a real UDP port is ignored, not fatal.
initR, respR := doFullLaneHandshake(t,
&LaneDetails{PortCount: 8, BasePort: 70000},
&LaneDetails{PortCount: 4, BasePort: 5353},
&LaneDetails{PortCount: 8, BasePort: 70000, TxLanes: 8},
&LaneDetails{PortCount: 4, BasePort: 5353, TxLanes: 4},
)
assert.Equal(t, uint32(0), respR.PeerPortCount)
assert.Equal(t, uint32(0), respR.PeerBasePort)
assert.Equal(t, uint32(0), respR.PeerTxLanes)
// The sane side still negotiates.
assert.Equal(t, uint32(4), initR.PeerPortCount)
// An out-of-range TxLanes drops the whole advert the same way: a lane index
// that does not fit the header is as unusable as an impossible port.
initR, respR = doFullLaneHandshake(t,
&LaneDetails{PortCount: 8, BasePort: 4242, TxLanes: 0x10000},
&LaneDetails{PortCount: 4, BasePort: 5353, TxLanes: 4},
)
assert.Equal(t, uint32(0), respR.PeerPortCount)
assert.Equal(t, uint32(4), initR.PeerPortCount)
}