Files
nebula/handshake/fragment/wire.go
T

122 lines
3.7 KiB
Go

// Package fragment carries handshake messages that do not fit in one UDP
// packet. A message is Reed-Solomon coded into equally sized symbols (see
// handshake/fec) and each symbol travels in its own packet:
//
// Nebula header (16) | Fragment header (8) | [cookie (16)] | symbol
//
// The fragment header is FragID u32 | MsgLen u16 | Index u8 | Flags u8, all big
// endian. Both ends derive the symbol layout from MsgLen, so any MsgLen-derived
// value never has to be sent. FlagCookie marks a cookie a responder under load
// asked for; room for it is always reserved, so a fragment never outgrows
// MaxPacketLen.
package fragment
import (
"encoding/binary"
"errors"
"github.com/slackhq/nebula/handshake/fec"
"github.com/slackhq/nebula/header"
)
const (
// HeaderLen is the size of the fragment header that follows the Nebula header.
HeaderLen = 8
// CookieLen is the size of the cookie a fragment carries when FlagCookie is set.
CookieLen = 16
// FlagCookie marks a fragment that carries a cookie after its header.
FlagCookie uint8 = 0x01
// MaxPacketLen bounds every fragment packet, Nebula header included. A relayed
// fragment gains a 16 byte header and a 16 byte tag, which lands exactly on
// the 1232 byte UDP payload available on an IPv6 minimum MTU path.
MaxPacketLen = 1200
// MaxSymbolSize is the largest symbol that fits in MaxPacketLen alongside a cookie.
MaxSymbolSize = MaxPacketLen - header.Len - HeaderLen - CookieLen
)
var (
ErrShortFragment = errors.New("fragment is too short")
ErrFragID = errors.New("fragment has a zero FragID")
ErrMsgLen = errors.New("fragment has a zero MsgLen")
ErrFlags = errors.New("fragment has reserved flags set")
ErrNoCookie = errors.New("fragment is too short for its cookie")
ErrSymbolSize = errors.New("fragment symbol size does not match its MsgLen")
)
// Cookie proves a sender received a responder's reply at its address.
type Cookie [CookieLen]byte
// Header is the fragment header.
type Header struct {
FragID uint32 // the sender's local index for this handshake, never zero
MsgLen uint16 // length of the whole message
Index uint8 // symbol index, data below K and repair from K
Flags uint8 // FlagCookie, other bits reserved and zero
Cookie Cookie // valid when Flags has FlagCookie
}
func (h Header) HasCookie() bool {
return h.Flags&FlagCookie != 0
}
// Encode appends the header, and its cookie if it has one, to b.
func (h Header) Encode(b []byte) []byte {
b = binary.BigEndian.AppendUint32(b, h.FragID)
b = binary.BigEndian.AppendUint16(b, h.MsgLen)
b = append(b, h.Index, h.Flags)
if h.HasCookie() {
b = append(b, h.Cookie[:]...)
}
return b
}
// Layout returns the symbol layout the header's MsgLen implies.
func (h Header) Layout() (fec.Layout, error) {
return fec.NewLayout(int(h.MsgLen), MaxSymbolSize)
}
// Parse validates the bytes following the Nebula header and splits them into
// the fragment header and the symbol. The symbol aliases b.
func Parse(b []byte) (Header, []byte, error) {
if len(b) < HeaderLen {
return Header{}, nil, ErrShortFragment
}
h := Header{
FragID: binary.BigEndian.Uint32(b[0:4]),
MsgLen: binary.BigEndian.Uint16(b[4:6]),
Index: b[6],
Flags: b[7],
}
if h.FragID == 0 {
return Header{}, nil, ErrFragID
}
if h.MsgLen == 0 {
return Header{}, nil, ErrMsgLen
}
if h.Flags&^FlagCookie != 0 {
return Header{}, nil, ErrFlags
}
b = b[HeaderLen:]
if h.HasCookie() {
if len(b) < CookieLen {
return Header{}, nil, ErrNoCookie
}
copy(h.Cookie[:], b)
b = b[CookieLen:]
}
l, err := h.Layout()
if err != nil {
return Header{}, nil, err
}
sym := b
if len(sym) != l.SymbolSize {
return Header{}, nil, ErrSymbolSize
}
return h, sym, nil
}