Documentation
¶
Overview ¶
Package proxyudp implements VORTEX's UDP tunnel (build plan M2.5): a session-tracking forwarder for connectionless UDP traffic with a per-source-IP token-bucket rate limiter. UDP has no connection concept, so client→backend "sessions" are tracked manually by client address and reaped after an idle TTL. Standard library only (Non-Negotiable Rule #10).
Index ¶
- type RateLimiter
- type Session
- type SessionStats
- type SessionTable
- func (t *SessionTable) ActiveCount() int
- func (t *SessionTable) CloseAll()
- func (t *SessionTable) Delete(clientAddr string)
- func (t *SessionTable) GetOrCreate(clientAddr *net.UDPAddr, backendAddr string) (*Session, bool, error)
- func (t *SessionTable) StartCleanup(ctx context.Context)
- func (t *SessionTable) Stats() SessionStats
- func (t *SessionTable) Touch(clientAddr string)
- type UDPListener
- type UDPListenerConfig
- type UDPStats
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter applies a per-source-IP token bucket: each IP may send `rate` packets per second sustained, bursting up to `burst` packets. Packets over the limit are dropped (Allow returns false).
func NewRateLimiter ¶
func NewRateLimiter(rate, burst int) (*RateLimiter, error)
NewRateLimiter builds a limiter allowing `rate` packets/sec per IP with a burst capacity of `burst`. Both must be positive.
func (*RateLimiter) Allow ¶
func (r *RateLimiter) Allow(ip string) bool
Allow reports whether a packet from ip may be forwarded, consuming a token if so. It refills the bucket based on elapsed time, caps it at burst, and returns false (drop) when fewer than one token is available.
func (*RateLimiter) StartCleanup ¶
func (r *RateLimiter) StartCleanup(ctx context.Context)
StartCleanup runs a goroutine that removes buckets unused for 10 minutes, sweeping every 5 minutes, until ctx is cancelled. This bounds memory under traffic from many distinct source IPs.
type Session ¶
type Session struct {
ClientAddr *net.UDPAddr
BackendConn *net.UDPConn
BytesIn atomic.Int64 // client → backend
BytesOut atomic.Int64 // backend → client
// contains filtered or unexported fields
}
Session is one client's UDP flow: a backend connection plus liveness and byte counters. LastSeen is stored as a Unix-nano atomic so concurrent reader (sweeper) and writers (read loop, reply pump) need no lock.
type SessionStats ¶
SessionStats is a snapshot of session-table counters.
type SessionTable ¶
type SessionTable struct {
// contains filtered or unexported fields
}
SessionTable tracks active UDP sessions keyed by client address string.
func NewSessionTable ¶
func NewSessionTable(ttl time.Duration) *SessionTable
NewSessionTable returns a table with the given idle TTL (default 30s).
func (*SessionTable) ActiveCount ¶
func (t *SessionTable) ActiveCount() int
ActiveCount returns the number of live sessions.
func (*SessionTable) CloseAll ¶
func (t *SessionTable) CloseAll()
CloseAll deletes and closes every session. It is used on listener shutdown to unblock reply pumps (which are reading on the per-session backend conns).
func (*SessionTable) Delete ¶
func (t *SessionTable) Delete(clientAddr string)
Delete removes and closes the session for the given client-address key.
func (*SessionTable) GetOrCreate ¶
func (t *SessionTable) GetOrCreate(clientAddr *net.UDPAddr, backendAddr string) (*Session, bool, error)
GetOrCreate returns the session for clientAddr, creating one (dialing backendAddr) if none exists. The bool is true when a new session was created.
func (*SessionTable) StartCleanup ¶
func (t *SessionTable) StartCleanup(ctx context.Context)
StartCleanup runs a goroutine that reaps idle sessions every ttl/2 until ctx is cancelled.
func (*SessionTable) Stats ¶
func (t *SessionTable) Stats() SessionStats
Stats returns a snapshot of table counters.
func (*SessionTable) Touch ¶
func (t *SessionTable) Touch(clientAddr string)
Touch updates LastSeen for the session with the given client-address key.
type UDPListener ¶
type UDPListener struct {
// contains filtered or unexported fields
}
UDPListener forwards UDP datagrams to a backend, tracking per-client sessions and rate-limiting by source IP.
func NewListener ¶
func NewListener(cfg UDPListenerConfig) (*UDPListener, error)
NewListener validates cfg and constructs a UDPListener.
func (*UDPListener) Listen ¶
func (l *UDPListener) Listen(ctx context.Context) error
Listen binds the UDP socket and forwards datagrams until ctx is cancelled. It starts the session and rate-limiter cleanup goroutines, runs the read loop, and on cancellation closes the socket and all sessions. It returns nil on clean shutdown.
func (*UDPListener) LocalAddr ¶
func (l *UDPListener) LocalAddr() string
LocalAddr returns the bound UDP address once Listen has bound the socket, or "" before then. Safe for concurrent use.
func (*UDPListener) Stats ¶
func (l *UDPListener) Stats() UDPStats
Stats returns a snapshot of the listener's counters.
type UDPListenerConfig ¶
type UDPListenerConfig struct {
// ListenAddr is the local UDP bind address, e.g. ":53".
ListenAddr string
// BackendAddr is the upstream UDP target, "host:port".
BackendAddr string
// MaxSessions caps concurrent client sessions. Default 10000.
MaxSessions int
// RateLimit is sustained packets/sec per source IP; 0 disables limiting.
RateLimit int
// RateBurst is the burst size; defaults to RateLimit*2 when unset.
RateBurst int
// SessionTTL is the idle timeout before a session is reaped. Default 30s.
SessionTTL time.Duration
// BufferSize is the per-read datagram buffer. Default 64KiB.
BufferSize int
// Logger receives diagnostics; defaults to slog.Default.
Logger *slog.Logger
}
UDPListenerConfig configures a UDPListener.
type UDPStats ¶
type UDPStats struct {
Sessions SessionStats
Dropped int64 // rate-limited + max-sessions drops
}
UDPStats is a snapshot of UDP listener counters.