mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 13:36: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
+32
-5
@@ -12,6 +12,9 @@ import (
|
||||
// |-----------------------------------------------------------------------|
|
||||
// | Version (uint4) | Type (uint4) | Subtype (uint8) | Reserved (uint16) | 32
|
||||
// |-----------------------------------------------------------------------|
|
||||
// The low 8 bits of Reserved carry the multiport lane index (0 is the base
|
||||
// tunnel, which is what every non-multiport sender emits). The high 8 bits
|
||||
// remain reserved and are always sent as zero.
|
||||
// | Remote index (uint32) | 64
|
||||
// |-----------------------------------------------------------------------|
|
||||
// | Message counter | 96
|
||||
@@ -57,8 +60,18 @@ const (
|
||||
const (
|
||||
TestRequest MessageSubType = 0
|
||||
TestReply MessageSubType = 1
|
||||
// LaneProbe is sent on a multiport lane to prove the lane's 5-tuple is
|
||||
// usable; LaneProbeAck answers it on the base tunnel.
|
||||
LaneProbe MessageSubType = 2
|
||||
LaneProbeAck MessageSubType = 3
|
||||
)
|
||||
|
||||
// MaxLane is the largest lane index the header can carry.
|
||||
const MaxLane = 0xff
|
||||
|
||||
// laneMask covers the bits of Reserved that hold the lane index.
|
||||
const laneMask uint16 = 0x00ff
|
||||
|
||||
const (
|
||||
HandshakeIXPSK0 MessageSubType = 0
|
||||
HandshakeXXPSK0 MessageSubType = 1
|
||||
@@ -67,8 +80,10 @@ const (
|
||||
var ErrHeaderTooShort = errors.New("header is too short")
|
||||
|
||||
var subTypeTestMap = map[MessageSubType]string{
|
||||
TestRequest: "testRequest",
|
||||
TestReply: "testReply",
|
||||
TestRequest: "testRequest",
|
||||
TestReply: "testReply",
|
||||
LaneProbe: "laneProbe",
|
||||
LaneProbeAck: "laneProbeAck",
|
||||
}
|
||||
|
||||
var subTypeNoneMap = map[MessageSubType]string{0: "none"}
|
||||
@@ -100,10 +115,16 @@ type H struct {
|
||||
// Encode uses the provided byte array to encode the provided header values into.
|
||||
// Byte array must be capped higher than HeaderLen or this will panic
|
||||
func Encode(b []byte, v uint8, t MessageType, st MessageSubType, ri uint32, c uint64) []byte {
|
||||
return EncodeLane(b, v, t, st, ri, c, 0)
|
||||
}
|
||||
|
||||
// EncodeLane is Encode with an explicit multiport lane index, which is carried
|
||||
// in the low 8 bits of Reserved.
|
||||
func EncodeLane(b []byte, v uint8, t MessageType, st MessageSubType, ri uint32, c uint64, lane uint8) []byte {
|
||||
b = b[:Len]
|
||||
b[0] = v<<4 | byte(t&0x0f)
|
||||
b[1] = byte(st)
|
||||
binary.BigEndian.PutUint16(b[2:4], 0)
|
||||
binary.BigEndian.PutUint16(b[2:4], uint16(lane))
|
||||
binary.BigEndian.PutUint32(b[4:8], ri)
|
||||
binary.BigEndian.PutUint64(b[8:16], c)
|
||||
return b
|
||||
@@ -136,7 +157,13 @@ func (h *H) Encode(b []byte) ([]byte, error) {
|
||||
return nil, errors.New("nil header")
|
||||
}
|
||||
|
||||
return Encode(b, h.Version, h.Type, h.Subtype, h.RemoteIndex, h.MessageCounter), nil
|
||||
return EncodeLane(b, h.Version, h.Type, h.Subtype, h.RemoteIndex, h.MessageCounter, h.Lane()), nil
|
||||
}
|
||||
|
||||
// Lane returns the multiport lane index carried in Reserved. Lane 0 is the base
|
||||
// tunnel, which is what any sender that does not know about lanes will report.
|
||||
func (h *H) Lane() uint8 {
|
||||
return uint8(h.Reserved & laneMask)
|
||||
}
|
||||
|
||||
// Parse is a helper function to parses given bytes into new Header struct
|
||||
@@ -196,7 +223,7 @@ func IsValidSubType(t MessageType, s MessageSubType) bool {
|
||||
case Handshake:
|
||||
return s == HandshakeIXPSK0
|
||||
case Test:
|
||||
return s == TestReply || s == TestRequest
|
||||
return s == TestReply || s == TestRequest || s == LaneProbe || s == LaneProbeAck
|
||||
case Control, CloseTunnel, RecvError, LightHouse:
|
||||
return s == 0
|
||||
default:
|
||||
|
||||
+25
-1
@@ -52,6 +52,28 @@ func TestParse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeLane(t *testing.T) {
|
||||
b := EncodeLane(make([]byte, Len), Version, Message, MessageNone, 10, 9, 3)
|
||||
assert.Equal(t, []byte{0x11, 0x0, 0x0, 0x3}, b[:4])
|
||||
|
||||
h := &H{}
|
||||
require.NoError(t, h.Parse(b))
|
||||
assert.Equal(t, uint16(3), h.Reserved)
|
||||
assert.Equal(t, uint8(3), h.Lane())
|
||||
|
||||
// Encode is EncodeLane on the base tunnel, and the H method round trips the lane.
|
||||
assert.Equal(t,
|
||||
EncodeLane(make([]byte, Len), Version, Message, MessageNone, 10, 9, 0),
|
||||
Encode(make([]byte, Len), Version, Message, MessageNone, 10, 9))
|
||||
|
||||
rt, err := h.Encode(make([]byte, Len))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, b, rt)
|
||||
|
||||
// Only the low 8 bits of Reserved are the lane.
|
||||
assert.Equal(t, uint8(0x2a), (&H{Reserved: 0xff2a}).Lane())
|
||||
}
|
||||
|
||||
func TestTypeName(t *testing.T) {
|
||||
assert.Equal(t, "test", TypeName(Test))
|
||||
assert.Equal(t, "test", (&H{Type: Test}).TypeName())
|
||||
@@ -127,7 +149,9 @@ func TestIsValidSubType(t *testing.T) {
|
||||
|
||||
assert.True(t, IsValidSubType(Test, TestRequest))
|
||||
assert.True(t, IsValidSubType(Test, TestReply))
|
||||
assert.False(t, IsValidSubType(Test, 2))
|
||||
assert.True(t, IsValidSubType(Test, LaneProbe))
|
||||
assert.True(t, IsValidSubType(Test, LaneProbeAck))
|
||||
assert.False(t, IsValidSubType(Test, 4))
|
||||
|
||||
// These types only ever carry subtype 0.
|
||||
for _, mt := range []MessageType{Control, CloseTunnel, RecvError, LightHouse} {
|
||||
|
||||
Reference in new issue
Block a user