mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-16 01:07:01 +02:00
crazy multiport stuff
This commit is contained in:
@@ -23,7 +23,19 @@ message NebulaHandshakeDetails {
|
||||
// hand-written parser silently skips it on read.
|
||||
uint64 Cookie = 4 [deprecated = true];
|
||||
uint64 Time = 5;
|
||||
// Multiport lane negotiation. Absent on hosts without multiport enabled;
|
||||
// vanilla nebula treats 6 and 7 as unknown fields and skips them.
|
||||
LaneDetails InitiatorLanes = 6;
|
||||
LaneDetails ResponderLanes = 7;
|
||||
uint32 CertVersion = 8;
|
||||
// reserved for WIP multiport
|
||||
reserved 6, 7;
|
||||
}
|
||||
|
||||
// LaneDetails advertises a host's multiport lane capability. On a base
|
||||
// handshake LaneIndex is 0 and PortCount/BasePort describe the sender's
|
||||
// consecutively bound UDP ports. On a lane handshake the initiator sets
|
||||
// LaneIndex to its (nonzero) lane number.
|
||||
message LaneDetails {
|
||||
uint32 PortCount = 1;
|
||||
uint32 BasePort = 2;
|
||||
uint32 LaneIndex = 3;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ func newTestMachine(
|
||||
cs.version, cs.getCredential,
|
||||
verifier, func() (uint32, error) { return localIndex, nil },
|
||||
initiator, header.HandshakeIXPSK0,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
return m
|
||||
|
||||
+33
-1
@@ -39,6 +39,13 @@ type Result struct {
|
||||
HandshakeTime uint64
|
||||
MessageIndex uint64 // number of messages exchanged during the handshake
|
||||
Initiator bool
|
||||
|
||||
// Multiport lane negotiation, from the peer's LaneDetails. All zero when
|
||||
// the peer did not advertise (vanilla peer or multiport disabled).
|
||||
// PeerLaneIndex is nonzero only on the responder side of a lane handshake.
|
||||
PeerPortCount uint32
|
||||
PeerBasePort uint32
|
||||
PeerLaneIndex uint32
|
||||
}
|
||||
|
||||
// Machine drives a Noise handshake through N messages. It handles Noise
|
||||
@@ -61,6 +68,7 @@ type Machine struct {
|
||||
verifier CertVerifier
|
||||
result *Result
|
||||
msgs []msgFlags
|
||||
lanes *LaneDetails // our multiport advert; nil emits a vanilla payload
|
||||
myVersion cert.Version
|
||||
subtype header.MessageSubType
|
||||
indexAllocated bool
|
||||
@@ -73,6 +81,8 @@ type Machine struct {
|
||||
// the noise pattern and the per-message content layout. The credential for
|
||||
// `version` is fetched via getCred and used to seed the noise.HandshakeState.
|
||||
// IndexAllocator is called lazily when the first outgoing payload is built.
|
||||
// lanes, when non-nil, is emitted as this side's multiport advert on every
|
||||
// payload-bearing message; nil produces byte-identical vanilla payloads.
|
||||
func NewMachine(
|
||||
version cert.Version,
|
||||
getCred GetCredentialFunc,
|
||||
@@ -80,6 +90,7 @@ func NewMachine(
|
||||
allocIndex IndexAllocator,
|
||||
initiator bool,
|
||||
subtype header.MessageSubType,
|
||||
lanes *LaneDetails,
|
||||
) (*Machine, error) {
|
||||
info, err := subtypeInfoFor(subtype)
|
||||
if err != nil {
|
||||
@@ -103,6 +114,7 @@ func NewMachine(
|
||||
getCred: getCred,
|
||||
allocIndex: allocIndex,
|
||||
verifier: verifier,
|
||||
lanes: lanes,
|
||||
myVersion: version,
|
||||
result: &Result{
|
||||
Initiator: initiator,
|
||||
@@ -298,7 +310,8 @@ func (m *Machine) processPayload(msg []byte, flags msgFlags) error {
|
||||
}
|
||||
|
||||
// Assert the payload contains exactly what we expect
|
||||
hasPayloadData := payload.InitiatorIndex != 0 || payload.ResponderIndex != 0 || payload.Time != 0
|
||||
hasPayloadData := payload.InitiatorIndex != 0 || payload.ResponderIndex != 0 || payload.Time != 0 ||
|
||||
payload.InitiatorLanes != nil || payload.ResponderLanes != nil
|
||||
if hasPayloadData != flags.expectsPayload {
|
||||
m.failed = true
|
||||
return ErrUnexpectedContent
|
||||
@@ -327,6 +340,23 @@ func (m *Machine) processPayload(msg []byte, flags msgFlags) error {
|
||||
m.result.RemoteIndex = remoteIndex
|
||||
m.result.HandshakeTime = payload.Time
|
||||
m.payloadSet = true
|
||||
|
||||
// Multiport advert from the peer's side of the exchange. Out-of-range
|
||||
// values mean a peer we can't pair lanes with; ignore the advert
|
||||
// rather than failing the handshake — the tunnel itself is fine, it
|
||||
// just won't get lanes. Semantic policing (index bounds vs advert,
|
||||
// port-count caps) belongs to the handshake manager.
|
||||
var peerLanes *LaneDetails
|
||||
if m.result.Initiator {
|
||||
peerLanes = payload.ResponderLanes
|
||||
} else {
|
||||
peerLanes = payload.InitiatorLanes
|
||||
}
|
||||
if peerLanes != nil && peerLanes.BasePort <= 0xffff && peerLanes.PortCount <= 0xffff {
|
||||
m.result.PeerPortCount = peerLanes.PortCount
|
||||
m.result.PeerBasePort = peerLanes.BasePort
|
||||
m.result.PeerLaneIndex = peerLanes.LaneIndex
|
||||
}
|
||||
}
|
||||
|
||||
// Process certificate
|
||||
@@ -397,9 +427,11 @@ func (m *Machine) marshalOutgoing(flags msgFlags) ([]byte, error) {
|
||||
|
||||
if m.result.Initiator {
|
||||
p.InitiatorIndex = m.result.LocalIndex
|
||||
p.InitiatorLanes = m.lanes
|
||||
} else {
|
||||
p.ResponderIndex = m.result.LocalIndex
|
||||
p.InitiatorIndex = m.result.RemoteIndex
|
||||
p.ResponderLanes = m.lanes
|
||||
}
|
||||
p.Time = uint64(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package handshake
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert"
|
||||
ct "github.com/slackhq/nebula/cert_test"
|
||||
"github.com/slackhq/nebula/header"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// newTestLaneMachine is newTestMachine with a lane advert attached.
|
||||
func newTestLaneMachine(
|
||||
t *testing.T,
|
||||
cs *testCertState,
|
||||
verifier CertVerifier,
|
||||
initiator bool,
|
||||
localIndex uint32,
|
||||
lanes *LaneDetails,
|
||||
) *Machine {
|
||||
t.Helper()
|
||||
m, err := NewMachine(
|
||||
cs.version, cs.getCredential,
|
||||
verifier, func() (uint32, error) { return localIndex, nil },
|
||||
initiator, header.HandshakeIXPSK0,
|
||||
lanes,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
return m
|
||||
}
|
||||
|
||||
func doFullLaneHandshake(t *testing.T, initLanes, respLanes *LaneDetails) (initR, respR *Result) {
|
||||
t.Helper()
|
||||
ca, _, caKey, _ := ct.NewTestCaCert(
|
||||
cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
|
||||
)
|
||||
caPool := ct.NewTestCAPool(ca)
|
||||
v := testVerifier(caPool)
|
||||
|
||||
initCS := newTestCertState(t, ca, caKey, "initiator", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
|
||||
respCS := newTestCertState(t, ca, caKey, "responder", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
|
||||
|
||||
initM := newTestLaneMachine(t, initCS, v, true, 1000, initLanes)
|
||||
respM := newTestLaneMachine(t, respCS, v, false, 2000, respLanes)
|
||||
|
||||
msg1, err := initM.Initiate(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, respR, err := respM.ProcessPacket(nil, msg1)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, respR)
|
||||
|
||||
_, initR, err = initM.ProcessPacket(nil, resp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, initR)
|
||||
return initR, respR
|
||||
}
|
||||
|
||||
func TestMachineLaneAdvertBothSides(t *testing.T) {
|
||||
initR, respR := doFullLaneHandshake(t,
|
||||
&LaneDetails{PortCount: 8, BasePort: 4242},
|
||||
&LaneDetails{PortCount: 4, BasePort: 5353},
|
||||
)
|
||||
|
||||
// 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(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)
|
||||
}
|
||||
|
||||
func TestMachineLaneAdvertAsymmetric(t *testing.T) {
|
||||
// Vanilla initiator, multiport responder and vice versa: the nil side
|
||||
// yields all-zero peer fields on the other end.
|
||||
initR, respR := doFullLaneHandshake(t, nil, &LaneDetails{PortCount: 4, BasePort: 5353})
|
||||
assert.Equal(t, uint32(4), initR.PeerPortCount)
|
||||
assert.Equal(t, uint32(0), respR.PeerPortCount)
|
||||
assert.Equal(t, uint32(0), respR.PeerBasePort)
|
||||
|
||||
initR, respR = doFullLaneHandshake(t, &LaneDetails{PortCount: 8, BasePort: 4242}, nil)
|
||||
assert.Equal(t, uint32(0), initR.PeerPortCount)
|
||||
assert.Equal(t, uint32(8), respR.PeerPortCount)
|
||||
}
|
||||
|
||||
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},
|
||||
)
|
||||
assert.Equal(t, uint32(0), respR.PeerPortCount)
|
||||
assert.Equal(t, uint32(0), respR.PeerBasePort)
|
||||
// The sane side still negotiates.
|
||||
assert.Equal(t, uint32(4), initR.PeerPortCount)
|
||||
}
|
||||
@@ -444,6 +444,7 @@ func TestMachineThreeMessagePattern(t *testing.T) {
|
||||
initCS.getCredential, v,
|
||||
func() (uint32, error) { return 1000, nil },
|
||||
true, header.HandshakeXXPSK0,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -452,6 +453,7 @@ func TestMachineThreeMessagePattern(t *testing.T) {
|
||||
respCS.getCredential, v,
|
||||
func() (uint32, error) { return 2000, nil },
|
||||
false, header.HandshakeXXPSK0,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -20,6 +20,19 @@ type Payload struct {
|
||||
ResponderIndex uint32
|
||||
Time uint64
|
||||
CertVersion uint32
|
||||
|
||||
// Multiport lane negotiation; nil when the sender has multiport disabled
|
||||
// (which keeps the encoded payload byte-identical to a vanilla one).
|
||||
InitiatorLanes *LaneDetails
|
||||
ResponderLanes *LaneDetails
|
||||
}
|
||||
|
||||
// LaneDetails advertises multiport lane capability. LaneIndex is zero on base
|
||||
// handshakes and the initiator's lane number (>= 1) on lane handshakes.
|
||||
type LaneDetails struct {
|
||||
PortCount uint32
|
||||
BasePort uint32
|
||||
LaneIndex uint32
|
||||
}
|
||||
|
||||
// Proto field numbers for NebulaHandshakeDetails
|
||||
@@ -28,9 +41,18 @@ const (
|
||||
fieldInitiatorIndex = 2 // uint32
|
||||
fieldResponderIndex = 3 // uint32
|
||||
fieldTime = 5 // uint64
|
||||
fieldInitiatorLanes = 6 // LaneDetails
|
||||
fieldResponderLanes = 7 // LaneDetails
|
||||
fieldCertVersion = 8 // uint32
|
||||
)
|
||||
|
||||
// Proto field numbers for LaneDetails
|
||||
const (
|
||||
fieldLanePortCount = 1 // uint32
|
||||
fieldLaneBasePort = 2 // uint32
|
||||
fieldLaneLaneIndex = 3 // uint32
|
||||
)
|
||||
|
||||
// MarshalPayload encodes a handshake payload in protobuf wire format compatible
|
||||
// with NebulaHandshake{Details: NebulaHandshakeDetails{...}}.
|
||||
// Returns out (which may be nil), with the marshalled Payload appended to it.
|
||||
@@ -53,6 +75,14 @@ func MarshalPayload(out []byte, p Payload) []byte {
|
||||
details = protowire.AppendTag(details, fieldTime, protowire.VarintType)
|
||||
details = protowire.AppendVarint(details, p.Time)
|
||||
}
|
||||
if p.InitiatorLanes != nil {
|
||||
details = protowire.AppendTag(details, fieldInitiatorLanes, protowire.BytesType)
|
||||
details = protowire.AppendBytes(details, p.InitiatorLanes.marshal(nil))
|
||||
}
|
||||
if p.ResponderLanes != nil {
|
||||
details = protowire.AppendTag(details, fieldResponderLanes, protowire.BytesType)
|
||||
details = protowire.AppendBytes(details, p.ResponderLanes.marshal(nil))
|
||||
}
|
||||
if p.CertVersion != 0 {
|
||||
details = protowire.AppendTag(details, fieldCertVersion, protowire.VarintType)
|
||||
details = protowire.AppendVarint(details, uint64(p.CertVersion))
|
||||
@@ -64,6 +94,20 @@ func MarshalPayload(out []byte, p Payload) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
// marshal appends the LaneDetails submessage fields to out. All fields are
|
||||
// emitted unconditionally: a LaneDetails is only present at all when multiport
|
||||
// is negotiating, and explicit zeros keep the parser's presence semantics
|
||||
// trivial.
|
||||
func (d *LaneDetails) marshal(out []byte) []byte {
|
||||
out = protowire.AppendTag(out, fieldLanePortCount, protowire.VarintType)
|
||||
out = protowire.AppendVarint(out, uint64(d.PortCount))
|
||||
out = protowire.AppendTag(out, fieldLaneBasePort, protowire.VarintType)
|
||||
out = protowire.AppendVarint(out, uint64(d.BasePort))
|
||||
out = protowire.AppendTag(out, fieldLaneLaneIndex, protowire.VarintType)
|
||||
out = protowire.AppendVarint(out, uint64(d.LaneIndex))
|
||||
return out
|
||||
}
|
||||
|
||||
// UnmarshalPayload decodes a protobuf-encoded NebulaHandshake message.
|
||||
func UnmarshalPayload(b []byte) (Payload, error) {
|
||||
var p Payload
|
||||
@@ -161,6 +205,72 @@ func unmarshalPayloadDetails(p *Payload, b []byte) error {
|
||||
}
|
||||
p.CertVersion = uint32(v)
|
||||
b = b[n:]
|
||||
case fieldInitiatorLanes:
|
||||
if typ != protowire.BytesType {
|
||||
return errInvalidHandshakeDetails
|
||||
}
|
||||
v, n := protowire.ConsumeBytes(b)
|
||||
if n < 0 {
|
||||
return errInvalidHandshakeDetails
|
||||
}
|
||||
p.InitiatorLanes = new(LaneDetails)
|
||||
if err := unmarshalLaneDetails(p.InitiatorLanes, v); err != nil {
|
||||
return err
|
||||
}
|
||||
b = b[n:]
|
||||
case fieldResponderLanes:
|
||||
if typ != protowire.BytesType {
|
||||
return errInvalidHandshakeDetails
|
||||
}
|
||||
v, n := protowire.ConsumeBytes(b)
|
||||
if n < 0 {
|
||||
return errInvalidHandshakeDetails
|
||||
}
|
||||
p.ResponderLanes = new(LaneDetails)
|
||||
if err := unmarshalLaneDetails(p.ResponderLanes, v); err != nil {
|
||||
return err
|
||||
}
|
||||
b = b[n:]
|
||||
default:
|
||||
n := protowire.ConsumeFieldValue(num, typ, b)
|
||||
if n < 0 {
|
||||
return errInvalidHandshakeDetails
|
||||
}
|
||||
b = b[n:]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalLaneDetails(d *LaneDetails, b []byte) error {
|
||||
for len(b) > 0 {
|
||||
num, typ, n := protowire.ConsumeTag(b)
|
||||
if n < 0 {
|
||||
return errInvalidHandshakeDetails
|
||||
}
|
||||
b = b[n:]
|
||||
|
||||
// Same contract as the details parser: known fields hard-fail on a
|
||||
// wire-type mismatch, unknown fields are skipped, repeated singular
|
||||
// fields follow proto3 last-wins.
|
||||
switch num {
|
||||
case fieldLanePortCount, fieldLaneBasePort, fieldLaneLaneIndex:
|
||||
if typ != protowire.VarintType {
|
||||
return errInvalidHandshakeDetails
|
||||
}
|
||||
v, n := protowire.ConsumeVarint(b)
|
||||
if n < 0 || v > math.MaxUint32 {
|
||||
return errInvalidHandshakeDetails
|
||||
}
|
||||
switch num {
|
||||
case fieldLanePortCount:
|
||||
d.PortCount = uint32(v)
|
||||
case fieldLaneBasePort:
|
||||
d.BasePort = uint32(v)
|
||||
case fieldLaneLaneIndex:
|
||||
d.LaneIndex = uint32(v)
|
||||
}
|
||||
b = b[n:]
|
||||
default:
|
||||
n := protowire.ConsumeFieldValue(num, typ, b)
|
||||
if n < 0 {
|
||||
|
||||
+137
-11
@@ -117,23 +117,134 @@ func TestPayloadUnknownFields(t *testing.T) {
|
||||
assert.Equal(t, uint32(88), got.ResponderIndex)
|
||||
})
|
||||
|
||||
t.Run("reserved fields 6 and 7 are skipped", func(t *testing.T) {
|
||||
// Fields 6 and 7 are reserved in the proto definition
|
||||
t.Run("unknown field inside LaneDetails is skipped", func(t *testing.T) {
|
||||
var lane []byte
|
||||
lane = protowire.AppendTag(lane, fieldLanePortCount, protowire.VarintType)
|
||||
lane = protowire.AppendVarint(lane, 4)
|
||||
lane = protowire.AppendTag(lane, 50, protowire.VarintType) // unknown subfield
|
||||
lane = protowire.AppendVarint(lane, 9999)
|
||||
lane = protowire.AppendTag(lane, fieldLaneBasePort, protowire.VarintType)
|
||||
lane = protowire.AppendVarint(lane, 4242)
|
||||
|
||||
var details []byte
|
||||
details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
|
||||
details = protowire.AppendVarint(details, 100)
|
||||
details = protowire.AppendTag(details, 6, protowire.VarintType)
|
||||
details = protowire.AppendVarint(details, 1)
|
||||
details = protowire.AppendTag(details, 7, protowire.VarintType)
|
||||
details = protowire.AppendVarint(details, 2)
|
||||
details = protowire.AppendTag(details, fieldInitiatorLanes, protowire.BytesType)
|
||||
details = protowire.AppendBytes(details, lane)
|
||||
|
||||
var data []byte
|
||||
data = protowire.AppendTag(data, 1, protowire.BytesType)
|
||||
data = protowire.AppendBytes(data, details)
|
||||
got, err := UnmarshalPayload(wrapDetails(details))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint32(100), got.InitiatorIndex)
|
||||
require.NotNil(t, got.InitiatorLanes)
|
||||
assert.Equal(t, uint32(4), got.InitiatorLanes.PortCount)
|
||||
assert.Equal(t, uint32(4242), got.InitiatorLanes.BasePort)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPayloadLaneDetails(t *testing.T) {
|
||||
t.Run("round trip both sides", func(t *testing.T) {
|
||||
data := MarshalPayload(nil, Payload{
|
||||
InitiatorIndex: 12345,
|
||||
Time: 999,
|
||||
InitiatorLanes: &LaneDetails{PortCount: 8, BasePort: 4242, LaneIndex: 3},
|
||||
ResponderLanes: &LaneDetails{PortCount: 4, BasePort: 5353},
|
||||
})
|
||||
|
||||
got, err := UnmarshalPayload(data)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint32(100), got.InitiatorIndex)
|
||||
require.NotNil(t, got.InitiatorLanes)
|
||||
assert.Equal(t, LaneDetails{PortCount: 8, BasePort: 4242, LaneIndex: 3}, *got.InitiatorLanes)
|
||||
require.NotNil(t, got.ResponderLanes)
|
||||
assert.Equal(t, LaneDetails{PortCount: 4, BasePort: 5353}, *got.ResponderLanes)
|
||||
})
|
||||
|
||||
t.Run("zero-valued LaneDetails survives the round trip", func(t *testing.T) {
|
||||
// Presence is what negotiation keys on; an all-zero advert must not
|
||||
// decay to nil.
|
||||
data := MarshalPayload(nil, Payload{
|
||||
InitiatorIndex: 1,
|
||||
InitiatorLanes: &LaneDetails{},
|
||||
})
|
||||
got, err := UnmarshalPayload(data)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got.InitiatorLanes)
|
||||
assert.Equal(t, LaneDetails{}, *got.InitiatorLanes)
|
||||
assert.Nil(t, got.ResponderLanes)
|
||||
})
|
||||
|
||||
t.Run("nil lanes marshal byte-identical to a vanilla payload", func(t *testing.T) {
|
||||
p := Payload{
|
||||
Cert: []byte("cert"),
|
||||
CertVersion: 2,
|
||||
InitiatorIndex: 100,
|
||||
Time: 999,
|
||||
}
|
||||
// The vanilla encoding of the same fields, built by hand in field order.
|
||||
var details []byte
|
||||
details = protowire.AppendTag(details, fieldCert, protowire.BytesType)
|
||||
details = protowire.AppendBytes(details, p.Cert)
|
||||
details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
|
||||
details = protowire.AppendVarint(details, uint64(p.InitiatorIndex))
|
||||
details = protowire.AppendTag(details, fieldTime, protowire.VarintType)
|
||||
details = protowire.AppendVarint(details, p.Time)
|
||||
details = protowire.AppendTag(details, fieldCertVersion, protowire.VarintType)
|
||||
details = protowire.AppendVarint(details, uint64(p.CertVersion))
|
||||
|
||||
assert.Equal(t, wrapDetails(details), MarshalPayload(nil, p))
|
||||
})
|
||||
|
||||
t.Run("lane field with wrong wire type rejected", func(t *testing.T) {
|
||||
for _, field := range []protowire.Number{fieldInitiatorLanes, fieldResponderLanes} {
|
||||
var details []byte
|
||||
details = protowire.AppendTag(details, field, protowire.VarintType)
|
||||
details = protowire.AppendVarint(details, 1)
|
||||
_, err := UnmarshalPayload(wrapDetails(details))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("lane subfield with wrong wire type rejected", func(t *testing.T) {
|
||||
var lane []byte
|
||||
lane = protowire.AppendTag(lane, fieldLanePortCount, protowire.BytesType)
|
||||
lane = protowire.AppendBytes(lane, []byte{1, 2, 3})
|
||||
|
||||
var details []byte
|
||||
details = protowire.AppendTag(details, fieldInitiatorLanes, protowire.BytesType)
|
||||
details = protowire.AppendBytes(details, lane)
|
||||
_, err := UnmarshalPayload(wrapDetails(details))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("truncated LaneDetails submessage rejected", func(t *testing.T) {
|
||||
var details []byte
|
||||
details = protowire.AppendTag(details, fieldInitiatorLanes, protowire.BytesType)
|
||||
details = append(details, 0x0a, 0x01, 0x02) // length 10, only 2 bytes
|
||||
_, err := UnmarshalPayload(wrapDetails(details))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("truncated varint inside LaneDetails rejected", func(t *testing.T) {
|
||||
var lane []byte
|
||||
lane = protowire.AppendTag(lane, fieldLaneBasePort, protowire.VarintType)
|
||||
lane = append(lane, 0x80) // incomplete varint
|
||||
|
||||
var details []byte
|
||||
details = protowire.AppendTag(details, fieldResponderLanes, protowire.BytesType)
|
||||
details = protowire.AppendBytes(details, lane)
|
||||
_, err := UnmarshalPayload(wrapDetails(details))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("lane subfield varint overflow rejected", func(t *testing.T) {
|
||||
var lane []byte
|
||||
lane = protowire.AppendTag(lane, fieldLaneLaneIndex, protowire.VarintType)
|
||||
lane = protowire.AppendVarint(lane, math.MaxUint32+1)
|
||||
|
||||
var details []byte
|
||||
details = protowire.AppendTag(details, fieldInitiatorLanes, protowire.BytesType)
|
||||
details = protowire.AppendBytes(details, lane)
|
||||
_, err := UnmarshalPayload(wrapDetails(details))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -328,6 +439,12 @@ func FuzzPayload(f *testing.F) {
|
||||
Time: 3,
|
||||
CertVersion: 2,
|
||||
}))
|
||||
f.Add(MarshalPayload(nil, Payload{
|
||||
InitiatorIndex: 1,
|
||||
Time: 3,
|
||||
InitiatorLanes: &LaneDetails{PortCount: 8, BasePort: 4242, LaneIndex: 2},
|
||||
ResponderLanes: &LaneDetails{PortCount: 4, BasePort: 5353},
|
||||
}))
|
||||
f.Add([]byte{})
|
||||
f.Add([]byte{0xff})
|
||||
|
||||
@@ -357,5 +474,14 @@ func payloadsEqual(a, b Payload) bool {
|
||||
a.InitiatorIndex == b.InitiatorIndex &&
|
||||
a.ResponderIndex == b.ResponderIndex &&
|
||||
a.Time == b.Time &&
|
||||
a.CertVersion == b.CertVersion
|
||||
a.CertVersion == b.CertVersion &&
|
||||
laneDetailsEqual(a.InitiatorLanes, b.InitiatorLanes) &&
|
||||
laneDetailsEqual(a.ResponderLanes, b.ResponderLanes)
|
||||
}
|
||||
|
||||
func laneDetailsEqual(a, b *LaneDetails) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == b
|
||||
}
|
||||
return *a == *b
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user