Documentation
¶
Overview ¶
Package link is the nfltr endpoint-to-endpoint link layer: many sessions, each a byte stream with its own flow control, multiplexed over one connection the caller dials and secures (TLS, a proxy, a pipe). A session whose reader is slow stalls only itself; one write to the network carries every frame queued at the time.
It imports only the Go standard library.
Wire format, version 1 ¶
hello dialer -> listener: "NFDP" version(1) token(32 bytes) window(4) reply listener -> dialer: status(1), 0 = bound; window(4) when bound frame both ways: length(4, of the body) type(1) body type 0, data: sid_len(1) sid payload type 1, message: opaque bytes of the caller's (never empty) type 2, window: sid_len(1) sid increment(4)
Integers are big endian. A session id (sid) is at most 255 bytes, chosen by the side that opens the session. A window is how many bytes of one session the side that announces it buffers (see Conn.Acquire). In version 1 the frames that open and close a session are message frames in the caller's encoding (Codec); session ids that begin with '_' name control frames, which Mux leaves to the caller.
testdata/v1_session.golden holds a recorded version-1 session; the tests hold every release to those bytes.
Index ¶
- Constants
- Variables
- func CheckWindow(w uint32) (int, error)
- func Hello(nc net.Conn, token string, window int) (int, error)
- func ReadHello(r io.Reader) (string, int, error)
- func Reply(w io.Writer, status byte, window int) error
- type Codec
- type Conn
- func (c *Conn) Acquire(sid string, n int) bool
- func (c *Conn) Close() error
- func (c *Conn) Consumed(sid string, n int)
- func (c *Conn) Flush() error
- func (c *Conn) Forget(sid string)
- func (c *Conn) Open(sid string)
- func (c *Conn) PeerWindow() int
- func (c *Conn) Received(sid string, n int) error
- func (c *Conn) Recv() (Frame, error)
- func (c *Conn) SendData(sid string, data []byte) error
- func (c *Conn) SendMsg(b []byte) error
- func (c *Conn) Window() int
- type Frame
- type Liveness
- type Message
- type Mux
- type OwnedWriter
- type Session
- func (s *Session) Close() error
- func (s *Session) LocalAddr() net.Addr
- func (s *Session) Read(b []byte) (int, error)
- func (s *Session) RemoteAddr() net.Addr
- func (s *Session) SetDeadline(time.Time) error
- func (s *Session) SetReadDeadline(time.Time) error
- func (s *Session) SetWriteDeadline(time.Time) error
- func (s *Session) Write(b []byte) (int, error)
- func (s *Session) WriteTo(w io.Writer) (int64, error)
Constants ¶
const ( StatusBound = 0 StatusRefused = 1 )
Status bytes of the listener's reply to a hello.
const ( MinWindow = 2 * MaxChunk MaxWindow = 1 << 30 )
Window bounds: a side announces a window in [MinWindow, MaxWindow]. Credit returns in steps of half a window, which must hold a MaxChunk.
const ( MinLivenessInterval = time.Second MaxLivenessInterval = 15 * time.Second )
Bounds Validate enforces. A heartbeat sender keeps an interval in [MinLivenessInterval, MaxLivenessInterval]: below it the frames cost more than they tell, above it a 45 s limit for a silent peer (one that answers each frame) would drop the connection. Timeout is at least one more interval, since a peer skips a frame right after sending data.
const Magic = "NFDP"
Magic opens a link connection, where an HTTP/2 connection starts "PRI ": a listener that shares its port can tell the two apart.
const MaxChunk = 64 << 10
MaxChunk is the most data a sender acquires credit for at once: larger writes are split.
const MaxSID = 255
MaxSID is the longest session id.
const MaxSessions = 1024
MaxSessions is the most sessions one Mux carries at once: Open refuses more (ErrFull), Serve closes a session the peer opens past it.
const TokenLen = 32
TokenLen is the length of a hello's token: the hex of 16 random bytes.
const Version = 1
Version is the hello's protocol version.
Variables ¶
var ( // LivenessDefault: dead within 15 s, a frame each way every 5 s. LivenessDefault = Liveness{Interval: 5 * time.Second, Timeout: 10 * time.Second} // LivenessFast: quick failover and interactive use; dead within 4.5 s, // a frame each way every 1.5 s. LivenessFast = Liveness{Interval: 1500 * time.Millisecond, Timeout: 3 * time.Second} // LivenessNAT: long idle behind NATs, conntrack and load balancers that // drop a mapping idle for 30 s or more; dead within 30 s, a frame each // way every 10 s. LivenessNAT = Liveness{Interval: 10 * time.Second, Timeout: 20 * time.Second} // LivenessDatacenter: stable networks; dead within 45 s, a frame each // way every 15 s. LivenessDatacenter = Liveness{Interval: 15 * time.Second, Timeout: 30 * time.Second} // LivenessMobile: laptops and phones on flaky Wi-Fi or metered links: // the fewest wakeups, and a radio that sleeps or hands over is not // taken for a dead peer; dead within 60 s, a frame each way every 15 s. LivenessMobile = Liveness{Interval: 15 * time.Second, Timeout: 45 * time.Second} )
The presets. Idle traffic is one small frame each way per Interval per connection; the bound is Interval + Timeout from the peer's last frame.
var ErrCredit = errors.New("link: peer exceeded the session window")
ErrCredit is the receive error of a peer that broke the credit rules: it sent a session more data than this side's window, or granted more credit than its own window.
var ErrFrame = errors.New("link: malformed frame")
ErrFrame reports bytes that break the frame format.
var ErrFull = errors.New("link: connection carries its most sessions")
ErrFull is Open's refusal of a session past MaxSessions.
var ErrRefused = errors.New("link: peer refused the connection")
ErrRefused is the listener's refusal of a hello: an unknown token, or one that binds no more connections.
var LivenessPresets = []string{"default", "fast", "nat", "datacenter", "mobile"}
LivenessPresets names the presets, in the order help and docs list them.
Functions ¶
func CheckWindow ¶
CheckWindow returns w as a window, or an error when it is out of range.
func Hello ¶
Hello binds a new connection with token, announcing window, and returns the listener's window from its reply.
Types ¶
type Codec ¶
type Codec interface {
// Close returns the message that closes session sid.
Close(sid string) ([]byte, error)
// Parse reads a received message: the session it names, whether it
// opens or closes it, and data it carries for the session.
Parse(msg []byte) (Message, error)
}
Codec reads and writes the messages that open and close sessions: in wire version 1 their encoding is the caller's.
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn sends and receives frames on a bound connection. One goroutine Receives; the sends, Flush and the credit calls are safe from any.
Reads go straight to the connection: over TLS, tls.Conn's record buffer serves the small header reads, and an idle connection pins no buffer. ponytail: a plaintext connection (loopback, dev) reads the socket for each header; give it a bufio.Reader if one carries real traffic.
func NewConn ¶
NewConn returns a Conn over nc, whose hello is done: window is this side's window, peerWindow the other side's.
func (*Conn) Acquire ¶
Acquire waits until sid may send n more bytes and takes them. n must not exceed the peer's window (MaxChunk does not). It reports false once the session is forgotten or the connection closed or failed.
Before it waits it flushes: the peer returns credit only for bytes it received, so a sender waiting with its own frames still buffered (a Session.Write of several chunks) would wait for good.
func (*Conn) Consumed ¶
Consumed acknowledges n bytes of sid that left the receive buffer; a window frame goes out once half a window has.
func (*Conn) Flush ¶
Flush writes the buffered frames: inline when no write is in flight, else the write in flight takes them next. The buffer goes back to the pool once written, so an idle connection holds none.
func (*Conn) Open ¶
Open starts the credit of a session on both sides: the opener before it sends the open frame, the other side when it receives it.
func (*Conn) PeerWindow ¶
PeerWindow is the other side's window. allow:wrapper
func (*Conn) Received ¶
Received charges n bytes of data for sid against this side's window: Recv does for data frames, the caller for data it carries in messages. Data of a session this side does not know is not buffered by anyone and is not charged.
func (*Conn) Recv ¶
Recv returns the next data or message frame; it applies window frames itself and charges data frames against their session's window. A peer that breaks the credit rules gets ErrCredit (credit.go); the caller closes the connection on any error.
func (*Conn) SendData ¶
SendData buffers sid's data as data frames, as SendMsg does. The caller acquired credit for it (Acquire).
type Frame ¶
Frame is a received frame: a session's data (SID, Data), or a message (Msg, the caller's bytes). Window frames never surface: Recv applies them.
type Liveness ¶
Liveness is how a connection notices its peer is gone without a FIN (it lost power, a NAT dropped the path): the peer sends a frame at least every Interval while idle, and the connection is dead once Interval + Timeout pass without any frame. Where one side sends the frames (its heartbeat), the other asks for Interval, the sender states the interval it keeps, and the asker waits DeadAfter(stated).
func ParseLiveness ¶
ParseLiveness reads a preset name, raw values ("interval=2s,timeout=6s"), or a preset with raw values over it ("mobile,timeout=90s"); "" is LivenessDefault. The result is validated.
func (Liveness) DeadAfter ¶
DeadAfter is how long to wait for any frame once the peer stated the interval it keeps: stated + Timeout, and never under three stated intervals (a peer that keeps another interval than the one asked for, or skips a frame after data). 0 when the peer stated none: liveness is then the transport keepalive's.
type Mux ¶
type Mux struct {
// contains filtered or unexported fields
}
Mux carries sessions over a Conn, each a net.Conn. One side opens sessions (Open), the other takes them (Serve's accept). A session's data goes on credit, so a session whose reader is slow stalls only itself; a Write goes to the network inline, or with the frames of other sessions when a write is in flight (Conn).
func (*Mux) Close ¶
Close closes the connection: Serve returns and every session ends. allow:wrapper
func (*Mux) Open ¶
Open starts a session: open returns the message that opens it, for the session id the Mux chose. Open sends it at once, so a peer that speaks first is reached without the caller's bytes.
type OwnedWriter ¶
OwnedWriter is a writer that may keep the slices it is given: WriteTo hands it the received slices themselves, no copy.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is one session of a Mux. Read and WriteTo take the peer's bytes in order, then io.EOF once the peer closed the session; Write sends on credit. Deadlines are not supported: Close ends a session.
func (*Session) Close ¶
Close ends the session: the peer gets a close unless it closed it; a pending Read returns.
func (*Session) RemoteAddr ¶
RemoteAddr is the connection's. allow:wrapper
func (*Session) SetDeadline ¶
SetDeadline and its siblings are no-ops: Close ends a session.