mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 13:56:52 +01:00
The lane byte was described in prose wedged between the first and second rows of the table, which both broke up the diagram and left the wire layout still claiming a single Reserved (uint16). Make it a field: widen the table to fit Reserved (uint8) and Lane (uint8) with their types, and move the explanation below the whole diagram. The prose now covers what the picture can't: lane 0 is what a lanes-unaware sender and every non-lane packet carry, so the field is compatible in both directions; the remaining 8 bits stay reserved and zero; the H struct still stores the pair as one Reserved field, so callers reach the low byte through H.Lane and EncodeLane; and lane is part of the AEAD's associated data, so it cannot be altered in flight.
251 lines
7.2 KiB
Go
251 lines
7.2 KiB
Go
package header
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
//Version 1 header:
|
|
// 0 31
|
|
// |------------------------------------------------------------------------------------|
|
|
// | Version (uint4) | Type (uint4) | Subtype (uint8) | Reserved (uint8) | Lane (uint8) | 32
|
|
// |------------------------------------------------------------------------------------|
|
|
// | Remote index (uint32) | 64
|
|
// |------------------------------------------------------------------------------------|
|
|
// | Message counter | 96
|
|
// | (uint64) | 128
|
|
// |------------------------------------------------------------------------------------|
|
|
// | payload... |
|
|
//
|
|
// Lane is the multiport lane index, carved out of the low 8 bits of what was a
|
|
// single Reserved (uint16) before multiport. Lane 0 is the base tunnel, which is
|
|
// what every sender that does not know about lanes emits and what every non-lane
|
|
// packet — handshakes, lighthouse, relays, close — carries, so the field is
|
|
// compatible in both directions with a peer that has never heard of it. The
|
|
// remaining 8 bits stay reserved and are always sent as zero. The H struct still
|
|
// holds the pair as one Reserved field; use H.Lane and EncodeLane to reach the
|
|
// low byte.
|
|
//
|
|
// Lane is part of the AEAD's associated data, so a lane index cannot be altered
|
|
// in flight: a packet decrypts on the lane it claims or not at all.
|
|
|
|
type m = map[string]any
|
|
|
|
const (
|
|
Version uint8 = 1
|
|
Len = 16
|
|
)
|
|
|
|
type MessageType uint8
|
|
type MessageSubType uint8
|
|
|
|
const (
|
|
Handshake MessageType = 0
|
|
Message MessageType = 1
|
|
RecvError MessageType = 2
|
|
LightHouse MessageType = 3
|
|
Test MessageType = 4
|
|
CloseTunnel MessageType = 5
|
|
Control MessageType = 6
|
|
)
|
|
|
|
var typeMap = map[MessageType]string{
|
|
Handshake: "handshake",
|
|
Message: "message",
|
|
RecvError: "recvError",
|
|
LightHouse: "lightHouse",
|
|
Test: "test",
|
|
CloseTunnel: "closeTunnel",
|
|
Control: "control",
|
|
}
|
|
|
|
const (
|
|
MessageNone MessageSubType = 0
|
|
MessageRelay MessageSubType = 1
|
|
)
|
|
|
|
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
|
|
)
|
|
|
|
var ErrHeaderTooShort = errors.New("header is too short")
|
|
|
|
var subTypeTestMap = map[MessageSubType]string{
|
|
TestRequest: "testRequest",
|
|
TestReply: "testReply",
|
|
LaneProbe: "laneProbe",
|
|
LaneProbeAck: "laneProbeAck",
|
|
}
|
|
|
|
var subTypeNoneMap = map[MessageSubType]string{0: "none"}
|
|
|
|
var subTypeMap = map[MessageType]*map[MessageSubType]string{
|
|
Message: {
|
|
MessageNone: "none",
|
|
MessageRelay: "relay",
|
|
},
|
|
RecvError: &subTypeNoneMap,
|
|
LightHouse: &subTypeNoneMap,
|
|
Test: &subTypeTestMap,
|
|
CloseTunnel: &subTypeNoneMap,
|
|
Handshake: {
|
|
HandshakeIXPSK0: "ix_psk0",
|
|
},
|
|
Control: &subTypeNoneMap,
|
|
}
|
|
|
|
type H struct {
|
|
Version uint8
|
|
Type MessageType
|
|
Subtype MessageSubType
|
|
Reserved uint16
|
|
RemoteIndex uint32
|
|
MessageCounter uint64
|
|
}
|
|
|
|
// 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], uint16(lane))
|
|
binary.BigEndian.PutUint32(b[4:8], ri)
|
|
binary.BigEndian.PutUint64(b[8:16], c)
|
|
return b
|
|
}
|
|
|
|
// String creates a readable string representation of a header
|
|
func (h *H) String() string {
|
|
if h == nil {
|
|
return "<nil>"
|
|
}
|
|
return fmt.Sprintf("ver=%d type=%s subtype=%s reserved=%#x remoteindex=%v messagecounter=%v",
|
|
h.Version, h.TypeName(), h.SubTypeName(), h.Reserved, h.RemoteIndex, h.MessageCounter)
|
|
}
|
|
|
|
// MarshalJSON creates a json string representation of a header
|
|
func (h *H) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(m{
|
|
"version": h.Version,
|
|
"type": h.TypeName(),
|
|
"subType": h.SubTypeName(),
|
|
"reserved": h.Reserved,
|
|
"remoteIndex": h.RemoteIndex,
|
|
"messageCounter": h.MessageCounter,
|
|
})
|
|
}
|
|
|
|
// Encode turns header into bytes
|
|
func (h *H) Encode(b []byte) ([]byte, error) {
|
|
if h == nil {
|
|
return nil, errors.New("nil header")
|
|
}
|
|
|
|
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
|
|
func (h *H) Parse(b []byte) error {
|
|
if len(b) < Len {
|
|
return ErrHeaderTooShort
|
|
}
|
|
// get upper 4 bytes
|
|
h.Version = uint8((b[0] >> 4) & 0x0f)
|
|
// get lower 4 bytes
|
|
h.Type = MessageType(b[0] & 0x0f)
|
|
h.Subtype = MessageSubType(b[1])
|
|
h.Reserved = binary.BigEndian.Uint16(b[2:4])
|
|
h.RemoteIndex = binary.BigEndian.Uint32(b[4:8])
|
|
h.MessageCounter = binary.BigEndian.Uint64(b[8:16])
|
|
return nil
|
|
}
|
|
|
|
// TypeName will transform the headers message type into a human string
|
|
func (h *H) TypeName() string {
|
|
return TypeName(h.Type)
|
|
}
|
|
|
|
// TypeName will transform a nebula message type into a human string
|
|
func TypeName(t MessageType) string {
|
|
if n, ok := typeMap[t]; ok {
|
|
return n
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
// SubTypeName will transform the headers message sub type into a human string
|
|
func (h *H) SubTypeName() string {
|
|
return SubTypeName(h.Type, h.Subtype)
|
|
}
|
|
|
|
func (h *H) IsValidSubType() bool {
|
|
return IsValidSubType(h.Type, h.Subtype)
|
|
}
|
|
|
|
// SubTypeName will transform a nebula message sub type into a human string
|
|
func SubTypeName(t MessageType, s MessageSubType) string {
|
|
if n, ok := subTypeMap[t]; ok {
|
|
if x, ok := (*n)[s]; ok {
|
|
return x
|
|
}
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
func IsValidSubType(t MessageType, s MessageSubType) bool {
|
|
switch t {
|
|
case Message:
|
|
return s == MessageNone || s == MessageRelay
|
|
case Handshake:
|
|
return s == HandshakeIXPSK0
|
|
case Test:
|
|
return s == TestReply || s == TestRequest || s == LaneProbe || s == LaneProbeAck
|
|
case Control, CloseTunnel, RecvError, LightHouse:
|
|
return s == 0
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// NewHeader turns bytes into a header
|
|
func NewHeader(b []byte) (*H, error) {
|
|
h := new(H)
|
|
if err := h.Parse(b); err != nil {
|
|
return nil, err
|
|
}
|
|
return h, nil
|
|
}
|