netmux

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 11 Imported by: 0

README

netmux godoc test Coverage Status Release License

netmux multiplexes a single TCP listener and a single UDP packet conn into many virtual ones, based on the payload of each connection/packet.

Two multiplexers, same idea:

ConnMux PacketConnMux
Input one net.Listener one net.PacketConn
Output virtual net.Listener virtual net.PacketConn
Unit byte stream (sniffed + replayed) whole datagrams
Matcher ConnMatcher func(io.Reader, net.Addr) bool PacketConnMatcher func([]byte, net.Addr) bool
Dispatch first registered match wins first registered match wins

ConnMux — stream multiplexing (TCP)

The server uses it to expose several protocols from a single TCP port: user matchers are registered first, then an AnyConn() catch-all picks up everything else (TLS, HTTP, WebSocket, ...).

mux := netmux.NewConnMux(listener)

socks := mux.Match(netmux.ConnSOCKS(5, 1, 0)) // SOCKS5 no-auth greetings
rest := mux.Match(netmux.AnyConn())           // everything else

go mux.Serve()
  • Match(...ConnMatcher) / MatchWithWriters(...ConnMatchWriter) register a virtual listener; matchers are tried strictly in registration order, and each gets the connection's net.Addr (from RemoteAddr) alongside the sniffed reader. ConnMatchWriters additionally receive a writer that can send bytes back to the peer before the connection is delivered — used by the ...SendSettings HTTP/2 header-field matchers in the advanced examples for the h2c upgrade handshake:

    bannerLn := mux.MatchWithWriters(func(w io.Writer, r io.Reader, _ net.Addr) bool {
        if !netmux.ConnPrefixMatcher("BANNER")(r, nil) {
            return false
        }
        _, _ = w.Write([]byte("220 welcome\r\n")) // reply before Accept returns
        return true
    })
    
  • Sniffing and replay: while matching, bytes read by the matchers are accumulated in a bufferedReader; each subsequent matcher sees the cumulative bytes of the ones before it, and the winning matcher's reads are replayed to the application (connSniff) — the protocol handler sees the exact byte stream as if it had read the connection directly.

  • Serve() accepts from the root listener and dispatches each connection to the first matching virtual listener (blocked sends escape via the mux's and the listener's donec when closed). ConnMux.Close() makes every Accept return ErrConnMuxClosed — it stops the mux but does not close the root listener, which is owned by the caller (close it yourself to make Serve return). connListener.Close() closes only that virtual listener (ErrConnListenerClosed), leaving the rest of the mux running.

  • SetReadTimeout bounds the sniffing reads; HandleError configures what happens to unmatched connections (default: close the connection and keep serving):

    mux.SetReadTimeout(200 * time.Millisecond) // matchers must decide within 200ms
    mux.HandleError(func(err error) bool {
        // Log and keep serving; return false to stop the mux entirely.
        log.Printf("connection error: %v", err)
        return true
    })
    
  • Errors implement net.Error: ErrConnNotMatched is transient (the mux closes the connection and keeps serving), while ErrConnListenerClosed/ErrConnMuxClosed are permanent closed states.

Conn matchers (matchers.go)
Matcher Semantics
AnyConn() Matches everything — the catch-all fallback.
ConnPrefixMatcher(strs ...string) Prefix match via a Patricia tree.
ConnHTTP1(extMethods ...string) Optimistic: matches the standard HTTP methods (OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT) plus extras.
ConnSOCKS(version, nmethods, methods ...) SOCKS client greetings by prefix, e.g. ConnSOCKS(5, 1, 0) (the handshake reply is the caller's job).

The package is intentionally dependency-free, so the stricter HTTP/1, HTTP/2 and TLS matchers (a strict request-line parser, ConnHTTP2, ConnTLS, ConnHTTP1HeaderField, ConnHTTP2HeaderField, ConnHTTP2MatchHeaderField* SendSettings) are no longer shipped. Self-contained drop-in replacements for all of them live in Advanced matcher examples.

PacketConnMux — datagram multiplexing (UDP)

mux := netmux.NewPacketConnMux(packetConn)

quicPC := mux.Match(netmux.PacketPrefix([]byte{0xc0})) // e.g. QUIC Initials
turnPC := mux.Match(netmux.PacketPrefix([]byte{0x00, 0x01}))
otherPC := mux.Match(netmux.AnyPacket()) // catch-all

go mux.Serve()
  • Match(...PacketConnMatcher) registers a virtual net.PacketConn (defaults to AnyPacket()). Serve() reads datagrams from the base conn, copies them into pooled datagrams and dispatches to the first matching virtual conn; unmatched or over-queue packets are dropped and their datagrams returned to the pool.
  • Backpressure: each virtual conn has a 128-datagram buffer; enqueue is non-blocking, so a slow consumer drops packets instead of stalling the shared demux loop. The [closed-check + send] pair is serialized against Close()'s drain so no datagram is ever stranded after a close.
  • WaitUntilServing(timeout) blocks until the demux loop is running; errors.Is(err, net.ErrClosed) detects close errors; deadlines are supported per virtual conn.
  • Closing: PacketConnMux.Close() stops the mux and also closes the base PacketConn. Closing an individual virtual conn (via its Close()) stops that conn and unregisters it, leaving the mux and the other virtual conns running.
Packet matchers
Matcher Semantics
AnyPacket() Matches every packet — the catch-all fallback.
PacketPrefix(prefix) Matches packets whose payload starts with prefix.
PacketExact(payload) Matches packets whose payload equals payload exactly.
PacketQUIC() Matches QUIC packets: the fixed header bit (0x40) is set in both long-header (Initial/Handshake) and short-header (post-handshake) packets. Requires ≥5 bytes.
PacketSTUN() Matches STUN (RFC 5389) messages: top two bits 00 and the magic cookie 0x2112A442 at bytes 4–7. Requires ≥20 bytes.
PacketTURN() Matches TURN (RFC 5766) packets: STUN-formatted messages (same shape as PacketSTUN) plus ChannelData messages (top two bits 01). Requires ≥4 bytes.

Note: matcher ordering matters when the protocol shapes overlap. PacketTURN is a superset of PacketSTUN (it also accepts STUN-formatted messages), so register PacketSTUN first when both are used. And because PacketQUIC claims every packet with the 0x40 fixed bit, TURN ChannelData messages (first byte 0x40–0x7F) match it too — register PacketQUIC before PacketTURN only if QUIC traffic must win, or give the TURN matcher a narrower prefix.

Network helpers (netmux.go)

The package bundles portable socket helpers that set SO_REUSEADDR and SO_REUSEPORT on the underlying sockets via the platform-specific Control function. (SO_REUSEPORT is Unix-only; the Windows and Solaris builds set SO_REUSEADDR alone, which is the equivalent option there.)

Function Description
Listen(network, address) Creates a listener with SO_REUSEADDR + SO_REUSEPORT set.
ListenPacket(network, address) Creates a packet conn with SO_REUSEADDR + SO_REUSEPORT set.
Dial(network, laddr, raddr) Dials raddr from local address laddr, with the reuse options set.
DialTimeout(network, laddr, raddr, timeout) Same as Dial with a configurable timeout.
ResolveAddr(network, address) Resolves ip, tcp, udp or unix addresses.
ResolveIPAddr / ResolveTCPAddr / ResolveUDPAddr / ResolveUnixAddr Overridable resolver variables (default to the net package functions).
Control(network, address, c) The platform-specific socket control function (per syscall.RawConn), implemented in socket_*.go.
ListenConfig A pre-configured net.ListenConfig with Control wired in.

Advanced matchers

The built-in matchers (AnyConn, ConnPrefixMatcher, ConnHTTP1, ConnSOCKS) keep the package dependency-free. The stricter matchers that used to ship — a strict request-line parser, HTTP/2, TLS, and header-field matching — are still available as single self-contained functions. Each example below shows only the function being explained, with no imports: copy it into your own package, add the imports named in the section's prose, and register it with ConnMux.Match / ConnMux.MatchWithWriters exactly like the built-ins.

ConnTLS — match TLS handshake records

ConnTLS matches TLS connections by their first bytes: the record type 0x16 (a handshake record) followed by the two protocol-version bytes (0x0303 is TLS 1.2). By default it accepts SSLv3 through TLS 1.2; pass specific versions to restrict it, e.g. ConnTLS(tls.VersionTLS12).

Imports: crypto/tls (version constants), net, plus the netmux import.

func ConnTLS(versions ...int) netmux.ConnMatcher {
	if len(versions) == 0 {
		// 0x0300 is SSLv3, matched by prefix only (crypto/tls.VersionSSL30
		// itself is deprecated).
		versions = []int{0x0300, tls.VersionTLS10, tls.VersionTLS11, tls.VersionTLS12}
	}
	prefixes := make([]string, 0, len(versions))
	for _, v := range versions {
		prefixes = append(prefixes, string([]byte{22, byte(v >> 8 & 0xff), byte(v & 0xff)}))
	}
	return netmux.ConnPrefixMatcher(prefixes...)
}
ConnHTTP1Strict — require a valid HTTP/1.x request line

The built-in ConnHTTP1 is optimistic: it matches any request whose first word is a standard HTTP method, without validating the rest. ConnHTTP1Strict is the stricter counterpart — it parses the first request line (METHOD URI PROTO) and requires the protocol to be HTTP/1.x. It reads up to 4096 bytes, so it is slower, but it only matches genuine HTTP/1 requests (and rejects e.g. GET / HTTP/2.0).

Imports: bufio, io, net, strings.

func ConnHTTP1Strict() netmux.ConnMatcher {
	const maxHTTPRead = 4096
	return func(r io.Reader, _ net.Addr) bool {
		br := bufio.NewReader(&io.LimitedReader{R: r, N: maxHTTPRead})
		l, part, err := br.ReadLine()
		if err != nil || part {
			return false
		}

		// Split "METHOD URI PROTO" (grabbed from net/http).
		line := string(l)
		s1 := strings.IndexByte(line, ' ')
		s2 := strings.IndexByte(line[s1+1:], ' ')
		if s1 < 0 || s2 < 0 {
			return false
		}
		proto := line[s1+1+s2+1:]

		// HTTP/1.x only: the version token starts with "HTTP/1.".
		return strings.HasPrefix(strings.ToUpper(proto), "HTTP/1.")
	}
}
ConnHTTP2 — match the HTTP/2 client preface

ConnHTTP2 matches the 24-byte HTTP/2 connection preface PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n. It compares the preface byte by byte, reading in a loop because the underlying net.Conn may fragment it across reads.

Imports: io, net.

func ConnHTTP2() netmux.ConnMatcher {
	const preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
	return func(r io.Reader, _ net.Addr) bool {
		var b [len(preface)]byte
		last := 0
		for {
			n, err := r.Read(b[last:])
			if err != nil {
				return false
			}
			last += n
			if string(b[:last]) != preface[:last] {
				return false
			}
			if last == len(preface) {
				return true
			}
		}
	}
}
Shared helper — matchHTTP1Field

matchHTTP1Field powers the two HTTP/1 header-field matchers: it skips the request line, then walks the header fields and applies the name/value predicate to each until one matches or the header block ends.

Imports: bufio, io, net, strings.

func matchHTTP1Field(name string, matches func(string) bool) netmux.ConnMatcher {
	return func(r io.Reader, _ net.Addr) bool {
		br := bufio.NewReader(r)
		// Skip the request line, then inspect the header fields.
		if _, err := br.ReadString('\n'); err != nil {
			return false
		}
		for {
			line, err := br.ReadString('\n')
			if err != nil {
				return false
			}
			line = strings.TrimRight(line, "\r\n")
			if line == "" {
				return false // end of headers without a match
			}
			i := strings.IndexByte(line, ':')
			if i < 0 {
				return false
			}
			if strings.EqualFold(strings.TrimSpace(line[:i]), name) {
				return matches(strings.TrimSpace(line[i+1:]))
			}
		}
	}
}
ConnHTTP1HeaderField / ConnHTTP1HeaderFieldPrefix — match an HTTP/1 header

ConnHTTP1HeaderField matches the first request when the header named name has the exact value; ConnHTTP1HeaderFieldPrefix matches when the value starts with prefix. The field name is compared case-insensitively, and the Host header is matched like any other field. Both need the matchHTTP1Field helper above.

Imports: io, net (+ strings for the prefix variant), plus the helper.

func ConnHTTP1HeaderField(name, value string) netmux.ConnMatcher {
	return matchHTTP1Field(name, func(got string) bool { return got == value })
}
func ConnHTTP1HeaderFieldPrefix(name, prefix string) netmux.ConnMatcher {
	return matchHTTP1Field(name, func(got string) bool { return strings.HasPrefix(got, prefix) })
}
Shared helper — matchHTTP2Field

matchHTTP2Field powers the four HTTP/2 header-field matchers: it verifies the client preface with ConnHTTP2, then reads frames and HPACK-decodes the HEADERS/CONTINUATION header-block fragments, applying the name/value predicate until the header block ends. When a non-ACK SETTINGS frame arrives it answers with our own SETTINGS frame (that is the SendSettings behavior); once the peer acknowledges, it stops writing them. It returns as soon as the header block is complete.

Imports: io, net, golang.org/x/net/http2, golang.org/x/net/http2/hpack; plus the ConnHTTP2 matcher for the preface check.

func matchHTTP2Field(w io.Writer, r io.Reader, name string, matches func(string) bool) bool {
	if !ConnHTTP2()(r, nil) {
		return false
	}

	done := false
	matched := false
	framer := http2.NewFramer(w, r)
	hdec := hpack.NewDecoder(uint32(4<<10), func(hf hpack.HeaderField) {
		if hf.Name == name {
			done = true
			if matches(hf.Value) {
				matched = true
			}
		}
	})
	for {
		f, err := framer.ReadFrame()
		if err != nil {
			return false
		}

		switch f := f.(type) {
		case *http2.SettingsFrame:
			// The peer acknowledged our SETTINGS frame; do not write it
			// again.
			if f.IsAck() {
				break
			}
			if err := framer.WriteSettings(); err != nil {
				return false
			}
		case *http2.ContinuationFrame:
			if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil {
				return false
			}
			done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0
		case *http2.HeadersFrame:
			if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil {
				return false
			}
			done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0
		}

		if done {
			return matched
		}
	}
}
ConnHTTP2HeaderField / ConnHTTP2HeaderFieldPrefix — match an HTTP/2 header

ConnHTTP2HeaderField matches the first HEADERS frame when the header named name has the exact value; ConnHTTP2HeaderFieldPrefix matches when the value starts with prefix. HPACK-decoding follows CONTINUATION frames until the header block ends. Both need the matchHTTP2Field helper (and through it ConnHTTP2).

Imports: io, net, strings (prefix variant), golang.org/x/net/http2 + hpack, plus the helper and ConnHTTP2.

func ConnHTTP2HeaderField(name, value string) netmux.ConnMatcher {
	return func(r io.Reader, _ net.Addr) bool {
		return matchHTTP2Field(io.Discard, r, name, func(got string) bool { return got == value })
	}
}
func ConnHTTP2HeaderFieldPrefix(name, prefix string) netmux.ConnMatcher {
	return func(r io.Reader, _ net.Addr) bool {
		return matchHTTP2Field(io.Discard, r, name, func(got string) bool { return strings.HasPrefix(got, prefix) })
	}
}
ConnHTTP2MatchHeaderFieldSendSettings / ...PrefixSendSettings — h2c upgrade

The plain ConnHTTP2HeaderField* matchers above read the client's headers but never write anything back. For an h2c (HTTP/2 cleartext) upgrade the client blocks until the server sends its own SETTINGS frame, so the matcher must reply during matching. These two ConnMatchWriters do exactly that — register them with MatchWithWriters instead of Match. Prefer the plain ConnHTTP2HeaderField* variants when the client does not block on receiving SETTINGS.

Imports: io, net, strings (prefix variant), golang.org/x/net/http2 + hpack, plus the helper and ConnHTTP2.

func ConnHTTP2MatchHeaderFieldSendSettings(name, value string) netmux.ConnMatchWriter {
	return func(w io.Writer, r io.Reader, _ net.Addr) bool {
		return matchHTTP2Field(w, r, name, func(got string) bool { return got == value })
	}
}
func ConnHTTP2MatchHeaderFieldPrefixSendSettings(name, prefix string) netmux.ConnMatchWriter {
	return func(w io.Writer, r io.Reader, _ net.Addr) bool {
		return matchHTTP2Field(w, r, name, func(got string) bool { return strings.HasPrefix(got, prefix) })
	}
}

Documentation

Overview

Package netmux multiplexes network connections and packet connections based on their payload.

ConnMux turns a single net.Listener into multiple virtual listeners, dispatching each accepted connection to the first virtual listener whose matchers (ConnMatcher / ConnMatchWriter) accept it. Built-in matchers cover HTTP/1 (optimistic method matching) and SOCKS, plus generic Any/Prefix matchers.

PacketConnMux turns a single net.PacketConn into multiple virtual packet conns, dispatching each datagram to the first virtual conn whose matchers (PacketConnMatcher) accept it. Built-in matchers cover QUIC, STUN and TURN, plus generic Any/Prefix/Exact matchers.

The package also provides portable socket helpers (Listen, ListenPacket, Dial, DialTimeout, ResolveAddr) that set SO_REUSEADDR and SO_REUSEPORT on the underlying sockets via the platform-specific Control function.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ListenConfig is a net.ListenConfig with Control function set to Control,
	// which sets SO_REUSEADDR and SO_REUSEPORT options on the socket.
	ListenConfig = net.ListenConfig{Control: Control}

	// ResolveIPAddr resolves an IP address.
	ResolveIPAddr func(network, address string) (*net.IPAddr, error) = net.ResolveIPAddr

	// ResolveTCPAddr resolves a TCP address.
	ResolveTCPAddr func(network, address string) (*net.TCPAddr, error) = net.ResolveTCPAddr

	// ResolveUDPAddr resolves a UDP address.
	ResolveUDPAddr func(network, address string) (*net.UDPAddr, error) = net.ResolveUDPAddr

	// ResolveUnixAddr resolves a Unix address.
	ResolveUnixAddr func(network, address string) (*net.UnixAddr, error) = net.ResolveUnixAddr
)
View Source
var ErrConnListenerClosed = errListenerClosed("mux: listener closed")

ErrConnListenerClosed is returned from connListener.Accept when the underlying listener is closed.

View Source
var ErrConnMuxClosed = errServerClosed("mux: server closed")

ErrConnMuxClosed is returned from connListener.Accept when mux server is closed.

Functions

func Control

func Control(network, address string, c syscall.RawConn) (err error)

Control sets the SO_REUSEADDR and SO_REUSEPORT options on the socket. It is used as the Control function in net.ListenConfig and net.Dialer to enable address and port reuse for network connections.

func Dial

func Dial(network, laddr, raddr string) (net.Conn, error)

Dial creates a network connection with SO_REUSEADDR and SO_REUSEPORT options set.

func DialTimeout

func DialTimeout(network, laddr, raddr string, timeout time.Duration) (net.Conn, error)

DialTimeout creates a network connection with SO_REUSEADDR and SO_REUSEPORT options set, with a specified timeout.

func Listen

func Listen(network, address string) (net.Listener, error)

Listen creates a network listener with SO_REUSEADDR and SO_REUSEPORT options set.

func ListenPacket

func ListenPacket(network, address string) (net.PacketConn, error)

ListenPacket creates a packet network listener with SO_REUSEADDR and SO_REUSEPORT options set.

func ResolveAddr

func ResolveAddr(network, address string) (net.Addr, error)

ResolveAddr resolves the given network and address into a net.Addr. It supports various network types such as "ip", "tcp", "udp", and "unix".

Types

type ConnMatchWriter

type ConnMatchWriter func(io.Writer, io.Reader, net.Addr) bool

ConnMatchWriter is a match that can also write response (say to do handshake).

type ConnMatcher

type ConnMatcher func(io.Reader, net.Addr) bool

ConnMatcher matches a connection based on its content and the remote address of the connection.

func AnyConn

func AnyConn() ConnMatcher

AnyConn is a ConnMatcher that matches any connection.

func ConnHTTP1

func ConnHTTP1(extMethods ...string) ConnMatcher

ConnHTTP1 only matches the methods in the HTTP request.

This matcher is very optimistic: if it returns true, it does not mean that the request is a valid HTTP response. See the "Advanced matcher examples" section in the README for stricter self-contained HTTP/1 and HTTP/2 matchers.

func ConnPrefixMatcher

func ConnPrefixMatcher(strs ...string) ConnMatcher

ConnPrefixMatcher returns a matcher that matches a connection if it starts with any of the strings in strs.

func ConnSOCKS

func ConnSOCKS(version byte, nmethods byte, methods ...byte) ConnMatcher

ConnSOCKS matches SOCKS client greetings by their initial bytes.

The prefix is built from the version, the number of authentication methods advertised (nmethods) and the offered method bytes, so a greeting must start with exactly these bytes to match. Only the greeting is sniffed; the SOCKS handshake reply is the caller's responsibility. For example:

ConnSOCKS(5, 1, 0) // SOCKS5, 1 method, NO AUTHENTICATION REQUIRED
ConnSOCKS(5, 1)    // SOCKS5, any single offered method

type ConnMux

type ConnMux struct {
	// contains filtered or unexported fields
}

ConnMux is a multiplexer for network connections. It turns a single net.Listener into multiple virtual listeners, each serving the connections matched by the matchers registered with Match/MatchWithWriters (first match wins, in registration order).

func NewConnMux

func NewConnMux(l net.Listener) *ConnMux

NewConnMux instantiates a new connection multiplexer.

func (*ConnMux) Close

func (m *ConnMux) Close()

Close stops the multiplexer: every Accept on current and future virtual listeners returns ErrConnMuxClosed. Close does NOT close the root listener, which is owned by the caller; close it yourself to make Serve return.

func (*ConnMux) HandleError

func (m *ConnMux) HandleError(h ErrorHandler)

HandleError replaces the default error handler. The handler returns whether the mux should keep serving (true) or stop (false) after an error. The default keeps serving whenever possible: unmatched connections are closed, transient accept errors are retried, and only a closed root listener (or a handler veto) stops Serve.

Call it before Serve: the handler is read from the hot path without synchronization.

func (*ConnMux) Match

func (m *ConnMux) Match(matchers ...ConnMatcher) net.Listener

Match registers a virtual listener for connections accepted by any of the given matchers. Matchers are evaluated in registration order and the first match wins, so register specific matchers before catch-alls. The returned net.Listener delivers matched connections with the bytes sniffed during matching replayed, so the protocol handler sees the exact byte stream.

func (*ConnMux) MatchWithWriters

func (m *ConnMux) MatchWithWriters(matchers ...ConnMatchWriter) net.Listener

MatchWithWriters is like Match, but each matcher also receives a writer that can send bytes to the peer before the connection is delivered to the application (e.g. a server-initiated protocol handshake). The writer is the raw connection and is only valid during the matcher call; do not retain it.

func (*ConnMux) Serve

func (m *ConnMux) Serve() error

Serve accepts connections from the root listener and dispatches each one to the first virtual listener whose matcher accepts it. It returns when the root listener fails with an error the error handler does not veto — normally because the root listener was closed. Run Serve in its own goroutine once all virtual listeners are registered.

func (*ConnMux) SetReadTimeout

func (m *ConnMux) SetReadTimeout(t time.Duration)

SetReadTimeout bounds how long a matcher may spend sniffing a connection's initial bytes: a matcher that needs more than t of reads fails to match.

Call it before Serve: the value is read from the hot path without synchronization and must not change while connections are being dispatched. A zero duration disables the timeout, which is the default.

type ErrConnNotMatched

type ErrConnNotMatched struct {
	// contains filtered or unexported fields
}

ErrConnNotMatched is returned whenever a connection is not matched by any of the matchers registered in the multiplexer.

func (ErrConnNotMatched) Error

func (e ErrConnNotMatched) Error() string

Error returns a message identifying the remote address of the unmatched connection.

func (ErrConnNotMatched) Temporary

func (e ErrConnNotMatched) Temporary() bool

Temporary implements the net.Error interface.

func (ErrConnNotMatched) Timeout

func (e ErrConnNotMatched) Timeout() bool

Timeout implements the net.Error interface.

type ErrorHandler

type ErrorHandler func(error) bool

ErrorHandler decides what happens after an error: it returns whether the mux should continue serving (true) or stop (false).

type PacketConnMatcher

type PacketConnMatcher func(packet []byte, src net.Addr) bool

PacketConnMatcher returns true when a packet belongs to a virtual PacketConn.

Matchers are invoked synchronously from the demux loop (under the PacketConnMux read lock), so they must be fast and non-blocking.

func AnyPacket

func AnyPacket() PacketConnMatcher

Any matches all packets.

func PacketExact

func PacketExact(payload []byte) PacketConnMatcher

Exact matches packets equal to payload.

func PacketPrefix

func PacketPrefix(prefix []byte) PacketConnMatcher

Prefix matches packets starting with prefix.

func PacketQUIC

func PacketQUIC() PacketConnMatcher

PacketQUIC matches QUIC packets. QUIC's fixed header bit (0x40) is set in both long-header (0xC0-0xFF, incl. Initial/Handshake) and short-header (0x40-0x7F, post-handshake) packets, so this matcher claims every QUIC packet regardless of handshake state. (Version Negotiation packets may clear the fixed bit, but they only occur during version mismatch.) Note that TURN ChannelData messages (RFC 5766 §11.4) share the 0x40-0x7F first byte range and therefore also match — see the README for ordering guidance when combining QUIC and TURN on one mux.

func PacketSTUN

func PacketSTUN() PacketConnMatcher

PacketSTUN matches STUN (RFC 5389) messages: the first two bits of the first byte are 00, and the magic cookie 0x2112A442 sits at bytes 4-7 followed by a 12-byte transaction id. Requires at least 20 bytes.

func PacketTURN

func PacketTURN() PacketConnMatcher

PacketTURN matches TURN (RFC 5766) packets: STUN-formatted messages (same shape as PacketSTUN) plus ChannelData messages, whose first two bits are 01 (RFC 5766 §11.4). Note that PacketTURN is a superset of PacketSTUN — when both matchers are registered on the same mux, register PacketSTUN first so STUN messages reach the STUN conn.

type PacketConnMux

type PacketConnMux struct {
	// contains filtered or unexported fields
}

PacketConnMux multiplexes one PacketConn into multiple virtual PacketConns.

func NewPacketConnMux

func NewPacketConnMux(conn net.PacketConn) *PacketConnMux

NewPacketConnMux creates a packet multiplexer around conn.

func (*PacketConnMux) Close

func (m *PacketConnMux) Close() error

Close closes the multiplexer and the base PacketConn.

func (*PacketConnMux) Match

func (m *PacketConnMux) Match(matchers ...PacketConnMatcher) net.PacketConn

Match registers a virtual PacketConn for packets that match one of matchers.

Match order matters: PacketConnMux dispatches the packet to the first registered virtual PacketConn whose matchers accept the packet.

func (*PacketConnMux) Serve

func (m *PacketConnMux) Serve() error

Serve starts reading packets from the base PacketConn and dispatches them.

It returns when the base PacketConn returns an error or Close is called.

func (*PacketConnMux) WaitUntilServing

func (m *PacketConnMux) WaitUntilServing(timeout time.Duration) error

WaitUntilServing blocks until Serve has started reading packets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL