Documentation
¶
Overview ¶
Package wire is loft overlay protocol 1.0 (0x0100): Hello/Status handshake, mux frames, signed roster/lists, and fleet control. Handshake is auth/Hello/Status, not mux. CmdLeave ends the mux session.
Index ¶
- Constants
- Variables
- func DecodeDrain(b []byte) (id string, drain bool)
- func DecodeEnrolNo(b []byte) (uint16, string)
- func DecodeFlt(b []byte) (kind byte, body []byte, err error)
- func DecodeL3(plain []byte) (typ byte, payload []byte, err error)
- func DecodeUplink(s string) ([]byte, error)
- func DefaultFronts() []string
- func EncodeBatch(frames []Frame) ([]byte, error)
- func EncodeDrain(id string, drain bool) []byte
- func EncodeEnrolNo(code uint16, reason string) []byte
- func EncodeFlt(kind byte, body []byte) []byte
- func EncodeFront(name string) []byte
- func EncodeL3(typ byte, payload []byte) []byte
- func EncodeL3Batch(pkts [][]byte) []byte
- func EncodeReport(applied uint64) []byte
- func EncodeTune(event, inflight byte, budget uint16) []byte
- func EncodeUplink(blob []byte, enc byte) string
- func Fail(c Code, msg string) error
- func IntersectFronts(client, server []string) []string
- func Negotiate(peer, node Offer) (selected uint16, err error)
- func ParseFront(s string) (string, error)
- func ParseFrontCmd(p []byte) string
- func ParseFronts(list []string) ([]string, error)
- func ParseReport(p []byte) (uint64, bool)
- func Port(s string) uint16
- func ProtoString(v uint16) string
- func ResetPayload(c Code) []byte
- func Resin(f Frame) bool
- func SplitL3(plain []byte) ([][]byte, error)
- func SplitUplink(fs []Frame) [][]Frame
- func U16(n int) uint16
- func WriteFrame(w io.Writer, f Frame) error
- type Addr
- type AddrList
- type Beat
- type Cap
- type Code
- type EnrolReq
- type Error
- type Frame
- func Decode(buf []byte) (Frame, int, error)
- func DecodeBatch(buf []byte) ([]Frame, error)
- func FrontFrame(name string) Frame
- func PackResin(fs []Frame) []Frame
- func ReadFrame(r io.Reader) (Frame, error)
- func ReportFrame(applied uint64) Frame
- func ResinOnly(fs []Frame) []Frame
- func SplitFront(fs []Frame) (rest []Frame, last string)
- func SplitReport(fs []Frame) (rest []Frame, applied uint64, ok bool)
- func SplitTune(fs []Frame) (rest []Frame, event byte)
- func TuneFrame(event, inflight byte, budget uint16) Frame
- type FrontEP
- type Hello
- type Lists
- type Manifest
- type Offer
- type PeerHint
- type PeerTags
- type Policy
- type PolicyRule
- type RelayInfo
- type Roster
- type Route
- type Slice
- type Status
- type Tune
Constants ¶
const ( // UplinkBase64 is base64url (old clients and the auth GET). UplinkBase64 byte = 0 // UplinkCookie85 is '~' || Z85(u16be length || blob). ~6% more mux // in the same encoded header budget; alphabet is Cookie-safe. UplinkCookie85 byte = 1 )
const ( FltEnrolReq byte = 1 FltEnrolOK byte = 2 FltEnrolNo byte = 3 FltBeat byte = 4 FltSlice byte = 5 FltDrain byte = 6 )
Control messages on mux Open ctl:1 (relay trunk only).
const ( EnrolUnknown uint16 = 1 EnrolForm uint16 = 2 EnrolDup uint16 = 3 EnrolDisabled uint16 = 4 )
EnrolNo reason codes: unknown id, bad form, duplicate, disabled.
const ( CmdOpen byte = 1 CmdData byte = 2 CmdClose byte = 3 CmdPing byte = 4 CmdPong byte = 5 CmdPad byte = 6 CmdReset byte = 7 CmdDgram byte = 8 // UDP datagram: Open-target prefix + payload CmdLeave byte = 9 // end the mux session (stream_id 0, empty payload) CmdTune byte = 10 // front event (client) or budget/inflight set (node) CmdReport byte = 11 // GET uplink applied-through seq; stream_id 0 CmdFront byte = 12 // which attached front carries bulk mux; stream_id 0 )
Multiplex commands. Handshake (auth, Hello, Status) is not mux.
const ( // Header is u16be payload length, u8 cmd, u32be stream id. HeaderSize = 7 MaxPayload = 16 << 10 // 16384; mux payload inside AEAD )
Frame header is 7 bytes; payload is at most 16 KiB.
const ( FrontGET = "get" FrontWS = "ws" FrontQUIC = "quic" FrontH2 = "h2" FrontH3 = "h3" FrontConnect = "connect" FrontL3 = "l3" )
Front names on the wire and in config. get/ws/quic stay; h2 and h3 are extra. connect is HTTP CONNECT splice (mux-off TCP); it is not in FrontOrder. l3 is HTTP/2 CONNECT carrying IP datagrams; it is not in FrontOrder.
const ( // L3IP is one IPv4 or IPv6 datagram. The version nibble of payload[0] selects the family. L3IP byte = 0x00 // L3Batch is repeated u16be length || datagram. Amortizes AEAD on a corked CONNECT. L3Batch byte = 0x01 )
const ( KindExact byte = 1 KindSuffix byte = 2 KindCIDR byte = 3 )
Address-list entry kinds: exact domain, suffix, CIDR.
const ( Proto uint16 = 0x0100 // 1.0 until the first release; no bump while stubs remain ProtoMin uint16 = 0x0100 )
Protocol version: high byte major, low byte minor. 0x0100 is 1.0.
Major bump = incompatible framing or auth. Minor = new caps, old clients still speak. Handshake picks the highest version in both ranges.
const ( HubHost = "hub" HubHTTPPort = "1" HubH2Port = "2" CtlHost = "ctl" CtlPort = "1" )
Reserved mux Open targets on a relay trunk: hub:1 HTTP/1.1, hub:2 HTTP/2, ctl:1 fleet.
const ( TuneSet byte = 0 TuneCalm byte = 1 // H2 GOAWAY ENHANCE_YOUR_CALM Tune413 byte = 2 Tune404 byte = 3 TuneReset byte = 4 Tune429 byte = 5 TuneGoaway byte = 6 // other GOAWAY )
CmdTune payload is 4 bytes. Client event ≠ 0 reports a front break once; node event 0 sets live header_budget / inflight. Zero inflight or budget means leave that field unchanged.
const FrontBits = CapGET | CapWS | CapQUIC | CapH2 | CapH3 | CapConnect | CapL3
FrontBits is the cap mask of transport fronts (not other features).
const MemberCaps = NodeCaps &^ CapRelay
MemberCaps is the default Hello offer for loft-front / Supervise. CapRelay is omitted: a member must not self-assert a trunk (hub:* / ctl:1). loft-node in role relay ORs it back via ClientConfig.ExtraCaps. CapHold stays so a path drop still parks the mux (fresh 0x02, same instance_id).
const NodeCaps = CapMulti | CapResume | CapStickyIP | CapVLAN | CapRules | CapGET | CapWS | CapStatus | CapTimers | CapHelloCaps | CapReset | CapQUIC | CapDgram | CapClockless | CapTicket | CapDecoy | CapH2 | CapH3 | CapMask | CapRelay | CapRoster | CapLists | CapP2P | CapDNS | CapConnect | CapResin | CapHold | CapL3
NodeCaps is every capability this implementation advertises on Status. CapRelay stays here so a controller still shows it accepts trunks. Members MUST NOT copy this bit into Hello (see MemberCaps).
Variables ¶
var ( ErrTooLarge = errors.New("wire: payload too large") ErrShort = errors.New("wire: short frame") )
Frame codec errors. Payload above MaxPayload is ErrTooLarge.
FrontOrder is the try-list when both sides list every transport front.
Functions ¶
func DecodeDrain ¶
DecodeDrain reads id and drain flag.
func DecodeEnrolNo ¶
DecodeEnrolNo reads an EnrolNo. A short blob is EnrolForm with empty reason.
func DecodeUplink ¶
DecodeUplink accepts base64url or a '~' Z85 value. Empty is nil.
func DefaultFronts ¶ added in v1.0.23
func DefaultFronts() []string
DefaultFronts is the public path when config omits fronts: GET only. ws, h2, quic, h3, connect, and l3 stay off until listed.
func EncodeBatch ¶
EncodeBatch concatenates frames. CmdData larger than MaxPayload is split so a GET front does not drop the whole AEAD batch.
func EncodeDrain ¶
EncodeDrain is id || u8 (1 = drain).
func EncodeEnrolNo ¶
EncodeEnrolNo is u16be code || reason (u8 length, at most 64 bytes).
func EncodeFlt ¶
EncodeFlt is u8 type || body (the outer u32be length is the length-prefixed wrapper).
func EncodeFront ¶ added in v1.0.28
EncodeFront is the CmdFront payload: the front name (get, ws, h2, quic, h3).
func EncodeL3 ¶ added in v1.0.25
EncodeL3 is type || payload. writeMsg already carries the outer length.
func EncodeL3Batch ¶ added in v1.0.25
EncodeL3Batch is type 0x01 || {u16be n || datagram}...
func EncodeReport ¶
EncodeReport is u64be applied-through GET seq (server nextSeq-1).
func EncodeTune ¶
EncodeTune is event || inflight || u16be budget.
func EncodeUplink ¶
EncodeUplink is the GET header value for a sealed mux blob.
func IntersectFronts ¶
IntersectFronts keeps client order among fronts the server also lists.
func Negotiate ¶
Negotiate picks a version both speak. Caps in the result are the node's advertised set (what it can do), not the intersection: the client decides what to use. Empty peer (old hello) is treated as 1.0 with unknown caps.
func ParseFront ¶
ParseFront accepts get/ws/h2/quic/h3/connect/l3. Empty is get. Vendor names are rejected.
func ParseFrontCmd ¶ added in v1.0.28
ParseFrontCmd reads a CmdFront payload. Unknown names are empty.
func ParseFronts ¶
ParseFronts deduplicates names, preserving order. Empty list is DefaultFronts.
func ParseReport ¶
ParseReport reads a CmdReport payload. Short payloads are ignored.
func ProtoString ¶
ProtoString formats a protocol version as "major.minor" (0x0100 is "1.0").
func Resin ¶
Resin frames are held until CmdReport, then retransmitted. Draft frames are sent once: inner TCP or UDP will produce a new frame if the application still needs the bytes. Close/Reset are resin but SplitUplink puts them on their own GET seq after Data so they cannot jump queued Data in the same Cookie.
func SplitL3 ¶ added in v1.0.25
SplitL3 returns the IP datagrams in one AEAD plaintext (0x00 or 0x01). Unknown types yield a nil slice and no error (ignore).
func SplitUplink ¶
SplitUplink cuts one mux Pull into GET seqs: control resin, draft Data, then Close/Reset. A 404 retries only that seq.
Types ¶
type Addr ¶
Addr is one list entry: kind plus value (domain, suffix, or CIDR).
type Beat ¶
Beat is a relay heartbeat: load, live connections, program name, uptime.
func DecodeBeat ¶
DecodeBeat reads a Beat. Trailing uptime is optional for older relays.
type Cap ¶
type Cap uint64
Cap is a feature bit advertised on the inner handshake (inside TLS). Only protocol abilities, never build or OS version.
const ( CapMulti Cap = 1 << iota // several instances on one key CapResume // ok_resumed + lease_hold CapStickyIP // reserved overlay IP on the key CapVLAN // VLAN from the key CapRules // switch ACL CapGET // GET + header uplink CapWS // WebSocket CapStatus // Hello/Status with Code CapTimers // keepalive / idle / reconnect in Status CapHelloCaps // this cap block CapReset // CmdReset on streams CapQUIC // QUIC/UDP front (loss recovery, migration) CapDgram // CmdDgram UDP CapClockless // auth without client clock CapTicket // Status ticket is the GET sid for 0x04 attach CapDecoy // Status lists this node's decoy origins CapH2 // HTTP/2 CONNECT front CapH3 // HTTP/3 (ALPN h3) front CapMask // decoy mask: borrowed origin cert on loft path CapRelay // Hello: this session is a relay trunk (ExtraCaps only; not a member default) CapRoster // Status may carry a signed relay roster CapLists // Status may advertise address-list serial/hash CapP2P // peer-to-peer overlay when mesh CapDNS // Status may carry DNS servers; gateway:53 is a resolver CapConnect // HTTP/2 CONNECT splice; TCP without inner mux CapResin // CmdReport + resin/draft GET uplink CapHold // mux streams survive a brief front drop CapQUICMark // quic front on a decoy node: the Initial must carry the loft mark CapH3Mark // h3 front on a decoy node: the Initial must carry the loft mark CapL3 // HTTP/2 CONNECT carrying AEAD IP datagrams (no mux) )
Capability bits on Hello/Status. SOCKS, TUN, CDN vendor, OS, and origin names are not caps.
func CapForFront ¶
CapForFront maps a front name to its capability bit. Unknown names are 0.
func CapsWithFronts ¶
CapsWithFronts replaces FrontBits on base with the named fronts.
func (Cap) Fronts ¶
Fronts lists enabled transport fronts in FrontOrder, then connect and l3 when those bits are set. connect and l3 are not in the mux try-list; they still have to appear here so a reconnect keeps an advertised extra front. A decoy node sends quic and h3 as quic_mark and h3_mark so a client that cannot mark its QUIC Initial does not try them.
type Code ¶
type Code uint16
Code is a protocol status. Values are stable; names may grow, numbers do not move.
const ( OK Code = 0x0000 OKResumed Code = 0x0001 // same instance, same IP, fast path AuthBadKey Code = 0x0101 AuthRevoked Code = 0x0102 AuthExpired Code = 0x0103 AuthNotAuthorized Code = 0x0104 AuthLimit Code = 0x0105 // multi_limit reached AuthAlreadyOnline Code = 0x0106 // multi off, another instance holds the key AuthInstance Code = 0x0107 AuthClock Code = 0x0108 NetDisabled Code = 0x0201 NetUnknown Code = 0x0202 Protocol Code = 0x0203 RateLimit Code = 0x0204 Timeout Code = 0x0205 Internal Code = 0x0206 IPPool Code = 0x0301 IPConflict Code = 0x0302 VLANDenied Code = 0x0303 PolicyDrop Code = 0x0401 StreamRefused Code = 0x0501 // Open: dial failed StreamReset Code = 0x0502 // peer closed with error )
Assigned Status and stream codes. Numbers do not move.
func ParseReset ¶
ParseReset reads a CmdReset payload. A short payload is Internal.
type EnrolReq ¶
EnrolReq is a relay asking the controller to publish it.
func DecodeEnrolReq ¶
DecodeEnrolReq reads an EnrolReq. Empty id or url is an error.
type Frame ¶
Frame is one mux frame: cmd, stream id, payload (at most MaxPayload).
func DecodeBatch ¶
DecodeBatch reads concatenated frames until buf is empty.
func FrontFrame ¶ added in v1.0.28
FrontFrame is CmdFront on stream 0.
func SplitFront ¶ added in v1.0.28
SplitFront pulls CmdFront out of a mux batch. last is the last valid name.
func SplitReport ¶
SplitReport pulls CmdReport out of a mux batch. applied is the highest contiguous GET seq the peer has ingested.
type FrontEP ¶ added in v1.0.28
FrontEP is one Status front: name (get/ws/h2/h3/quic), HTTP path, optional URL. Empty URL is the host:port of the front that did 0x02. quic has an empty path.
type Hello ¶
type Hello struct {
Version byte
MemberID string
InstanceID string
Min uint16
Max uint16
Caps Cap
Program string // client binary name, e.g. loft
Software string // client release, e.g. 1.0.22 — not the protocol
}
Hello is the client's first inner payload after the key proof. Trailing proto/caps are optional: an old blob still decodes.
func DecodeHello ¶
DecodeHello reads a Hello. Trailing proto/caps are optional; unknown bytes are ignored.
type Lists ¶
Lists is a signed set of named address lists. Names have no protocol meaning; the client maps them to direct / via / drop.
func DecodeLists ¶
DecodeLists reads a signed lists blob. Trailing pub/sig may be omitted.
func ParseListsManifest ¶
ParseListsManifest accepts JSON Manifest or a raw Encode blob.
func (Lists) ManifestJSON ¶
ManifestJSON is the public cover document: {"v":1,"p":"<base64url>"}.
type Manifest ¶
ManifestJSON is the public cover document: looks like a frontend bundle pointer, payload is base64url(Roster.Encode()). No member secrets.
type PeerHint ¶
type PeerHint struct {
IP string
Host string
Port uint16
VLAN uint16
Pub []byte // 32-byte X25519; empty if the peer did not announce
}
PeerHint is one Status p2p hint: overlay IP, TLS-observed host, listen port, VLAN, and the peer's 32-byte X25519 pub for LFT.
type Policy ¶ added in v1.0.20
type Policy struct {
Present bool
OwnTags []string
ACLDefault byte // 0 peers/accept, 1 drop
Rules []PolicyRule
Peers []PeerTags
}
Policy is compact ACL pushed so a client can enforce the same Allow on direct P2P. Present is true when the node sent this tail. Old Status blobs omit it; the client then skips P2P ACL (hub splice still checks).
type PolicyRule ¶ added in v1.0.20
type PolicyRule struct {
Action byte // 0 drop, 1 accept
SrcVLAN *uint16
DstVLAN *uint16
Src string
Dst string
SrcTag string
DstTag string
Proto byte
Dport []uint16
Sport []uint16
}
PolicyRule is one compact ACL line. Zero VLAN pointers and empty strings are wildcards. Proto: 0 any, 1 tcp, 2 udp, 3 icmp.
type RelayInfo ¶
type RelayInfo struct {
ID string
URL string
Decoy string
Fronts []string
Drain bool
Weight byte // 1–255; 0 encodes as 100
Path string // get/ws/h2 path; empty = node default
MaskPub []byte // 32-byte mask pub for this front; empty = none
}
RelayInfo is one advertised front. The controller signs the roster; a relay cannot mint entries.
type Roster ¶
type Roster struct {
Serial uint64
Relays []RelayInfo
Pub []byte // Ed25519 public, 32 bytes
Sig []byte
}
Roster is the controller's signed list of live fronts.
func DecodeRoster ¶
DecodeRoster reads a signed roster. Trailing pub/sig and newer fields may be omitted.
func ParseManifest ¶
ParseManifest accepts JSON Manifest or a raw Encode blob.
func (Roster) Body ¶
Body is the signed fields without pub/sig. At most 255 relays; weight 0 encodes as 100.
func (Roster) ManifestJSON ¶
ManifestJSON is the public cover document: {"v":1,"p":"<base64url(Roster.Encode())>"}.
type Route ¶ added in v1.0.20
Route is one Status managed CIDR. Via is the overlay IP of the member that owns the prefix, or empty/gateway when the node itself is next hop.
type Slice ¶
type Slice struct {
Serial uint64
ID string
URL string
Decoy string
Fronts []string
Drain bool
Weight byte
BeatMS uint32
Expires int64
Pub []byte
Sig []byte
}
Slice is the controller's signed config for one relay.
func DecodeSlice ¶
DecodeSlice reads a signed slice. Trailing pub/sig may be omitted.
type Status ¶
type Status struct {
Code Code
IP string
Gateway string
InstanceID string
MTU uint16
VLAN uint16
LeaseSec uint32
KeepaliveMS uint32
IdleMS uint32
ReMinMS uint32
ReMaxMS uint32
ReFastN uint16
ReFastForMS uint32
Proto uint16 // selected protocol version
Min uint16 // node protocol min
Max uint16 // node protocol max
Caps Cap // what this node implements
Program string // node binary name, e.g. loft-node
Software string // node release — not the protocol
Ticket []byte // GET sid; other fronts present it in 0x04 to attach
Decoy []string // this node's splice origins; first is identity (SNI); empty = off
MaskPub []byte // 32-byte X25519 public for decoy mask; empty = mask off
Replicas []string // extra node host:port for failover; empty = this addr only
CanaryFlags byte // bit0 fat hello, bit1 split ClientHello
CanaryPad uint16 // extra ClientHello padding
Roster []byte // signed controller roster; empty = none
ListsSerial uint64 // 0 = no address lists
ListsHash []byte // SHA-256 of signed lists blob; 32 bytes
Peers byte // 0 hub, 1 mesh, 2 vlan
P2P byte // 1 = node allows p2p
PeerHints []PeerHint
DNS []string // resolver IPs for the client TUN; empty = do not change OS DNS
IP6 string // overlay IPv6; empty = IPv4-only (optional tail)
Gateway6 string // overlay IPv6 gateway; empty = none
HeaderBudget uint16 // GET encoded Cookie budget; 0 = client keeps its own
Inflight uint8 // GET inflight; 0 = client keeps its own
H2Conns uint8 // GET HTTP/2 connections (1–4); 0 = client keeps one
UplinkEnc uint8 // 0 base64url, 1 Cookie Z85; 0 = client keeps base64url
GapHoldMS uint32 // mux/stream hold after a front drop; 0 = client default
Routes []Route // managed CIDRs (lab LAN behind a member); empty = none
Policy Policy // compact ACL for P2P; omitted when empty
SessionTTLMS uint32 // live session max; 0 = off. Distinct from LeaseSec (IP hold).
RateBPS uint32 // bulk Data/Dgram cap, both ways; 0 = off
Fronts []FrontEP // mux fronts this node accepts; empty = client keeps URI/defaults
}
Status is the node's reply: code, lease, timers, protocol offer.
func DecodeStatus ¶
DecodeStatus reads a Status. Trailing unknown fields MUST be ignored; old blobs still decode.