mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 15:16:52 +01:00
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:
1 parent
8b914e67f4
commit
fb20de39b2
19 files changed
+1223
-1273
No files matched your search
@@ -1,9 +1,12 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"crypto/hkdf"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -74,6 +77,53 @@ func newConnectionStateFromResult(r *handshake.Result) (*ConnectionState, error)
|
||||
return ci, nil
|
||||
}
|
||||
|
||||
// newLaneConnectionState derives multiport lane s's session from the completed
|
||||
// base handshake. Each key is an HKDF expansion of the base tunnel's matching
|
||||
// key, labelled with the lane index, so the pair stays matched with no extra
|
||||
// negotiation: Noise leaves our send key equal to the peer's receive key, and
|
||||
// expanding both with the same label preserves that.
|
||||
//
|
||||
// The lane gets its own counter and replay window starting from zero. No
|
||||
// handshake messages were spent on it, so unlike the base session there is
|
||||
// nothing to seed.
|
||||
func newLaneConnectionState(r *handshake.Result, lane uint8) (*ConnectionState, error) {
|
||||
if lane == 0 {
|
||||
return nil, fmt.Errorf("lane 0 is the base session")
|
||||
}
|
||||
|
||||
eKey, err := deriveLaneKey(r.EKey.UnsafeKey(), lane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dKey, err := deriveLaneKey(r.DKey.UnsafeKey(), lane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ConnectionState{
|
||||
myCert: r.MyCert,
|
||||
initiator: r.Initiator,
|
||||
peerCert: r.RemoteCert,
|
||||
eKey: noiseutil.NewCipherStateFromKey(eKey, r.Cipher),
|
||||
dKey: noiseutil.NewCipherStateFromKey(dKey, r.Cipher),
|
||||
window: NewBits(ReplayWindow),
|
||||
epoch: sessionEpoch.Add(1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// deriveLaneKey expands a base tunnel key into the key for one lane.
|
||||
func deriveLaneKey(base [32]byte, lane uint8) ([32]byte, error) {
|
||||
var out [32]byte
|
||||
// The base key is already unique to this tunnel and direction, so the lane
|
||||
// index is the only thing that needs to vary; no salt is required.
|
||||
k, err := hkdf.Key(sha256.New, base[:], nil, laneKeyInfo+" "+strconv.Itoa(int(lane)), len(out))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
copy(out[:], k)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (cs *ConnectionState) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(m{
|
||||
"certificate": cs.peerCert,
|
||||
|
||||
Reference in new issue
Block a user