Documentation
¶
Overview ¶
Package tcp implements VORTEX's raw TCP tunnel engine (build plan M2.1): a bidirectional byte pump between a client connection and a backend connection, a per-backend connection pool, a weighted round-robin selector, and an accept loop that wires them together. It is the data plane for `protocol: "tcp"` routes. Standard library only (Non-Negotiable Rule #10).
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrIdleTimeout = errors.New("tcp tunnel: idle timeout")
ErrIdleTimeout is returned when a tunnel is closed because no bytes flowed in either direction within the configured IdleTimeout.
var ErrNoBackends = errors.New("tcp: no backends configured")
ErrNoBackends is returned when a selector is constructed or queried with no backends configured.
var ErrPoolClosed = errors.New("tcp pool: closed")
ErrPoolClosed is returned by Get after the pool has been closed.
Functions ¶
func Tunnel ¶
Tunnel copies bytes bidirectionally between client and backend until either side closes, an error occurs, the idle timeout fires, or ctx is cancelled.
Each direction runs in its own goroutine. When one direction finishes, the peer's write half is closed (CloseWrite on *net.TCPConn) so the other end observes EOF and drains cleanly; the function then waits for the second direction before returning. A non-nil result reports the first unexpected error; a clean close (EOF on both sides) returns nil. Idle timeout returns ErrIdleTimeout.
Types ¶
type BackendAddr ¶
type BackendAddr struct {
// Addr is the dial target, "host:port".
Addr string
// Weight biases selection in weighted round-robin; <=0 is treated as 1.
Weight int
}
BackendAddr is a backend target with a load-balancing weight. It is the shared backend descriptor used by the selector and the listener.
type Listener ¶
type Listener struct {
// contains filtered or unexported fields
}
Listener accepts client connections and tunnels each to a selected backend.
func NewListener ¶
func NewListener(cfg ListenerConfig) (*Listener, error)
NewListener validates cfg and constructs a Listener.
func (*Listener) Listen ¶
Listen binds the listener and serves until ctx is cancelled. On cancel it stops accepting, waits up to drainTimeout for active tunnels, then returns nil. It returns an error only on an unexpected bind/accept failure.
func (*Listener) Stats ¶
func (l *Listener) Stats() ListenerStats
Stats returns a snapshot of the listener's counters.
func (*Listener) UpdateBackends ¶
func (l *Listener) UpdateBackends(backends []BackendAddr) error
UpdateBackends atomically replaces the backend set for zero-downtime config reload. Existing tunnels are unaffected; new connections use the new set.
type ListenerConfig ¶
type ListenerConfig struct {
// ListenAddr is the local bind address, e.g. ":5432".
ListenAddr string
// Backends are the upstream targets (weighted).
Backends []BackendAddr
// Pool supplies backend connections. Required.
Pool *Pool
// Tunnel configures each bidirectional copy.
Tunnel TunnelConfig
// MaxConnections caps concurrent tunnels; 0 means unlimited.
MaxConnections int
// TLSConfig, when non-nil, wraps each accepted connection in a TLS server
// handshake before tunneling — used for mTLS routes. Nil means plain TCP.
TLSConfig *tls.Config
// Logger receives tunnel/accept diagnostics; defaults to slog.Default.
Logger *slog.Logger
}
ListenerConfig configures a TCP tunnel Listener.
type ListenerStats ¶
type ListenerStats struct {
Active int64
Total int64
Rejected int64
BytesIn int64 // stub — wired in M5 observability
BytesOut int64 // stub — wired in M5 observability
}
ListenerStats is a point-in-time snapshot of listener counters.
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool is a per-backend-address connection pool. The zero value is not usable; construct one with NewPool. It is safe for concurrent use.
func NewPool ¶
func NewPool(cfg PoolConfig) *Pool
NewPool constructs a Pool with cfg (zero fields take defaults).
func (*Pool) Close ¶
Close closes all idle connections and marks the pool closed. Subsequent Get calls return ErrPoolClosed. Borrowed (active) connections are the caller's responsibility to close.
func (*Pool) Get ¶
Get returns a connection to addr: a healthy idle one if available, otherwise a freshly dialed one. If MaxOpen connections are already open to addr, Get blocks until a Put frees a slot or ctx is cancelled.
type PoolConfig ¶
type PoolConfig struct {
// MaxIdle is the maximum idle (kept-warm) connections retained per backend
// address. Default 8.
MaxIdle int
// MaxOpen is the maximum total connections (idle + borrowed) per backend
// address. Get blocks once this is reached until a Put or ctx cancel.
// Default 64.
MaxOpen int
// IdleTimeout closes idle connections older than this. Default 90s.
IdleTimeout time.Duration
// DialTimeout bounds dialing a new connection. Default 10s.
DialTimeout time.Duration
}
PoolConfig tunes a connection Pool.
type PoolStats ¶
type PoolStats struct {
Idle int
Active int // connections currently borrowed via Get (not yet Put back)
WaitCount int64 // cumulative times Get blocked waiting on MaxOpen
}
PoolStats is a point-in-time snapshot of pool counters.
type TunnelConfig ¶
type TunnelConfig struct {
// DialTimeout bounds backend dialing (used by the pool/listener, kept here
// so the whole tunnel behavior is configured in one place). Default 10s.
DialTimeout time.Duration
// IdleTimeout closes the tunnel if no bytes move in either direction for
// this long. Default 90s.
IdleTimeout time.Duration
// MaxConnections is the per-route connection ceiling (enforced by the
// listener). Default 1000.
MaxConnections int
// BufferSize is the copy buffer size per direction. Default 32KiB.
BufferSize int
}
TunnelConfig tunes a single tunnel.
type WeightedRR ¶
type WeightedRR struct {
// contains filtered or unexported fields
}
WeightedRR selects backends using smooth weighted round-robin — the algorithm Nginx uses. Compared to naive weighted RR (which bursts: A,A,A,A,A,B,C for weights 5,1,1), the smooth variant interleaves selections (A,A,B,A,A,C,A) so load is spread evenly over time rather than clumped.
It is safe for concurrent use. A single-backend selector takes a lock-free fast path.
func NewWeightedRR ¶
func NewWeightedRR(backends []BackendAddr) (*WeightedRR, error)
NewWeightedRR builds a selector over backends. It returns ErrNoBackends if the list is empty. Any backend with Weight <= 0 is normalized to Weight 1.
func (*WeightedRR) Len ¶
func (w *WeightedRR) Len() int
Len returns the number of configured backends.
func (*WeightedRR) Next ¶
func (w *WeightedRR) Next() (BackendAddr, error)
Next returns the next backend to use. It is thread-safe. With a single backend it returns immediately without taking the lock.
func (*WeightedRR) Update ¶
func (w *WeightedRR) Update(backends []BackendAddr) error
Update atomically replaces the backend list, used for config hot-reload so a route's backends can change without dropping traffic. CurrentWeight state is preserved for backends that remain (matched by Addr); new backends start at CurrentWeight 0. Returns ErrNoBackends if the new list is empty.