Documentation
¶
Overview ¶
Package netlink implements DokiLink-Lite: a pure-Go network proxy and mesh layer that replaces external tools (socat, slirp4netns, pasta) for container port forwarding in environments where they are not available (notably Termux on Android, which lacks network namespaces and iptables).
DokiLink-Lite is intentionally minimal:
- TCPProxy / UDPProxy: bidirectional byte-stream proxies using only the Go standard library. No gVisor, no WireGuard, no netstack.
- Optional TLS 1.3 wrapping (see crypto.go).
- Optional NaCl secretbox payload encryption (see crypto.go).
- Optional LAN-only mesh via mDNS or static peer JSON (see mesh.go).
The package is designed to be safe to import on every platform: when the caller does not invoke any of the proxy/mesh entry points, no network resources are opened.
Index ¶
- Constants
- Variables
- func DeriveSecretKey(localPub, remotePub []byte) [32]byte
- func TLSConfigFor(leaf LoadedCert, rootCAs *x509.CertPool, serverName string) (*tls.Config, error)
- type DHT
- func (d *DHT) AddContact(contact *DHTContact)
- func (d *DHT) AllContacts() []*DHTContact
- func (d *DHT) Bootstrap(ctx context.Context, contacts []*DHTContact)
- func (d *DHT) ClosestContacts(target DHTID, count int) []*DHTContact
- func (d *DHT) HandleFindNodeResponse(snaps []PeerSnapshot)
- func (d *DHT) RemoveContact(id DHTID)
- func (d *DHT) Snapshot() ([]byte, error)
- func (d *DHT) Start(ctx context.Context)
- func (d *DHT) Stop()
- type DHTBucket
- type DHTContact
- type DHTID
- type GossipMessage
- type Identity
- func (id *Identity) CACert() *x509.Certificate
- func (id *Identity) CACertPEM() []byte
- func (id *Identity) CAKeyPEM() []byte
- func (id *Identity) InstallID() string
- func (id *Identity) IssueLinkCert(commonName string) (LoadedCert, error)
- func (id *Identity) IssueLinkCertForContainer(containerID string) (LoadedCert, error)
- func (id *Identity) PeerKeyFile(peerID string) string
- func (id *Identity) PrivateKey() ed25519.PrivateKey
- func (id *Identity) PublicKey() ed25519.PublicKey
- func (id *Identity) PublicKeyPEM() string
- func (id *Identity) ShortID() string
- func (id *Identity) Sign(msg []byte) []byte
- func (id *Identity) Verify(msg, sig []byte) bool
- type LoadedCert
- type MDNSEntry
- type MDNSService
- type Mesh
- type MeshConfig
- type NATConfig
- type NATTraversal
- type Peer
- type PeerSnapshot
- type PublicAddr
- type RelayServer
- type RemoteContainer
- type SecretboxWrapper
- func (s *SecretboxWrapper) UnwrapInbound(payload []byte) ([]byte, error)
- func (s *SecretboxWrapper) WrapClient(_ context.Context, c net.Conn) (net.Conn, error)
- func (s *SecretboxWrapper) WrapOutbound(payload []byte) ([]byte, error)
- func (s *SecretboxWrapper) WrapServer(_ context.Context, c net.Conn) (net.Conn, error)
- type StaticPeers
- type StaticPeersConfig
- type TCPProxy
- type TCPProxyConfig
- type TLSWrapper
- type TransportWrapper
- type TrustStore
- func (ts *TrustStore) CACert(peerID string) *x509.Certificate
- func (ts *TrustStore) ListIDs() []string
- func (ts *TrustStore) Load() error
- func (ts *TrustStore) PubKey(peerID string) (ed25519.PublicKey, error)
- func (ts *TrustStore) Trust(peerID string, pub ed25519.PublicKey, ca *x509.Certificate) error
- func (ts *TrustStore) Trusted(peerID string) bool
- type UDPProxy
- type UDPProxyConfig
Constants ¶
const MaxGossipMessageBytes = 4 * 1024
MaxGossipMessageBytes is the on-wire size cap.
Variables ¶
var ErrIdentityNotLoaded = errors.New("netlink: identity not loaded")
ErrIdentityNotLoaded is returned by NewIdentity when generation failed.
Functions ¶
func DeriveSecretKey ¶
DeriveSecretKey derives a 32-byte symmetric key from two Ed25519 public keys. The derivation is ORDER-INDEPENDENT: both peers derive the SAME key regardless of which peer's pubkey is passed first, by sorting the two pubkeys lexicographically before hashing. This is critical for symmetric encryption (NaCl secretbox) where both sides must share the key.
func TLSConfigFor ¶
TLSConfigFor builds a *tls.Config ready to drop into tls.NewListener (server) or tls.Dial (client). It converts a LoadedCert into the cert slice tls.Config expects.
Types ¶
type DHT ¶ added in v0.11.0
type DHT struct {
// contains filtered or unexported fields
}
DHT is a Kademlia distributed hash table for peer discovery.
func NewDHT ¶ added in v0.11.0
NewDHT creates a DHT rooted at the given identity. The mesh reference is used to send FIND_NODE RPCs to peers.
func (*DHT) AddContact ¶ added in v0.11.0
func (d *DHT) AddContact(contact *DHTContact)
AddContact adds a contact to the appropriate k-bucket. If the bucket is full, the contact is not added (Kademlia eviction logic would ping the oldest contact, but we keep it simple).
func (*DHT) AllContacts ¶ added in v0.11.0
func (d *DHT) AllContacts() []*DHTContact
AllContacts returns all contacts in the routing table.
func (*DHT) Bootstrap ¶ added in v0.11.0
func (d *DHT) Bootstrap(ctx context.Context, contacts []*DHTContact)
Bootstrap seeds the DHT with initial contacts (from static config or mDNS). These contacts are added to the routing table, and a FIND_NODE for our own ID is sent to each to expand the table.
func (*DHT) ClosestContacts ¶ added in v0.11.0
func (d *DHT) ClosestContacts(target DHTID, count int) []*DHTContact
ClosestContacts returns the dhtK contacts closest to the target ID, searched across all buckets.
func (*DHT) HandleFindNodeResponse ¶ added in v0.11.0
func (d *DHT) HandleFindNodeResponse(snaps []PeerSnapshot)
HandleFindNodeResponse processes a FIND_NODE response received via the gossip protocol. It adds the returned contacts to the routing table.
func (*DHT) RemoveContact ¶ added in v0.11.0
RemoveContact removes a contact from the routing table.
func (*DHT) Snapshot ¶ added in v0.11.0
Snapshot returns a JSON-serializable snapshot of the DHT routing table for debugging.
type DHTBucket ¶ added in v0.11.0
type DHTBucket struct {
// contains filtered or unexported fields
}
DHTBucket is a k-bucket holding up to dhtK contacts.
type DHTContact ¶ added in v0.11.0
type DHTContact struct {
ID DHTID `json:"id"`
PeerID string `json:"peer_id"` // short install ID
Addr string `json:"addr"` // host:port
PubKey string `json:"pubkey"` // base64 Ed25519 public key
LastSeen time.Time `json:"last_seen"`
}
DHTContact is a peer entry in the routing table.
type DHTID ¶ added in v0.11.0
type DHTID [20]byte
DHTID is a 160-bit node identifier.
type GossipMessage ¶
type GossipMessage struct {
Type string `json:"type"` // "hello", "peer", "container"
From string `json:"from"` // sender short id
Time int64 `json:"time"` // unix nanos
Nonce string `json:"nonce,omitempty"`
Peers []PeerSnapshot `json:"peers,omitempty"`
Container *RemoteContainer `json:"container,omitempty"`
Signature string `json:"sig"` // base64 ed25519 sig over canonical JSON
}
GossipMessage is the wire format for inter-peer gossip. Every message is JSON, ed25519-signed, and capped at 4 KiB. Larger messages are truncated by the sender.
func (*GossipMessage) Sign ¶
func (m *GossipMessage) Sign(priv []byte) error
Sign signs the message in-place using priv. The signature is base64. A random nonce is generated and included in the signed body so that receivers can detect and reject replayed messages.
func (*GossipMessage) Verify ¶
func (m *GossipMessage) Verify(pub []byte) error
Verify checks that the message was signed by the holder of pub.
type Identity ¶
type Identity struct {
// contains filtered or unexported fields
}
Identity holds the per-Doki-install cryptographic identity used by the mesh layer. It consists of:
- An Ed25519 keypair (identity), used to sign mesh messages (HELLO, ADVERTISE, REVOKE, BYE). The private key never leaves the host. The public key is broadcast as the peer "fingerprint".
- A self-signed ECDSA P-256 certificate authority (CA), used to mint per-link TLS certificates on demand. The CA private key never leaves the host.
- Zero or more per-link ECDSA certificates, lazily generated and cached on disk at keys/<id>.crt / keys/<id>.key.
The Identity is loaded (or generated) at daemon startup. Key files are stored with 0600 permissions. Loading is safe to call multiple times; generation is gated by sync.Once.
func NewIdentity ¶
NewIdentity returns the Doki install identity, loading from disk or generating fresh material if absent. root is the keys directory (typically $DOKI_ROOT/keys).
func (*Identity) CACert ¶
func (id *Identity) CACert() *x509.Certificate
CACert returns the self-signed CA certificate.
func (*Identity) CACertPEM ¶
CACertPEM returns the CA certificate in PEM form, suitable for use as tls.Config.RootCAs or for inclusion in a tls.Config.Certificates pair.
func (*Identity) CAKeyPEM ¶
CAKeyPEM returns the CA private key in PEM form. NEVER ship this over the wire; it is exposed here only for the local TLS wrapper to load into crypto/tls.
func (*Identity) InstallID ¶
InstallID returns a short, stable, URL-safe identifier derived from the Ed25519 public key (first 16 chars of base32). It is safe to log.
func (*Identity) IssueLinkCert ¶
func (id *Identity) IssueLinkCert(commonName string) (LoadedCert, error)
IssueLinkCert generates a fresh ECDSA P-256 leaf certificate signed by the CA. commonName is the link/peer identifier (e.g. the remote peer's install ID or a container name).
The returned LoadedCert is the minimal subset of tls.Certificate we need: a parsed leaf plus DER-encoded key. The TLS wrapper (crypto.go) turns it into a real crypto/tls.Certificate.
func (*Identity) IssueLinkCertForContainer ¶
func (id *Identity) IssueLinkCertForContainer(containerID string) (LoadedCert, error)
IssueLinkCertForContainer is a convenience wrapper for the typical "generate a cert for a container's exposed port" use case.
func (*Identity) PeerKeyFile ¶
PeerKeyFile returns the on-disk path for a peer's pinned public key. Used by the mesh layer for trust-on-first-use storage.
func (*Identity) PrivateKey ¶
func (id *Identity) PrivateKey() ed25519.PrivateKey
PrivateKey returns the Ed25519 private key bytes (64). Callers must not mutate the returned slice. Only used for signing in this package's mesh code.
func (*Identity) PublicKeyPEM ¶
PublicKeyPEM returns the Ed25519 public key in PEM format for export.
func (*Identity) ShortID ¶
ShortID returns the short install identifier derived from the Ed25519 public key. Stable for the lifetime of the keys on disk.
type LoadedCert ¶
type LoadedCert struct {
Leaf *x509.Certificate
KeyDER []byte
}
LoadedCert is the minimal subset of tls.Certificate we need: a parsed leaf certificate plus the matching private key in DER. We avoid importing crypto/tls here to keep the keys file portable; the TLS wrapper (crypto.go) does the conversion.
type MDNSEntry ¶
type MDNSEntry struct {
ID string `json:"id"`
Addr string `json:"addr"`
Port int `json:"port"`
PubKey string `json:"pubkey"`
Source string `json:"source"`
Seen time.Time `json:"seen"`
}
MDNSEntry describes a peer found via mDNS. The struct lives in a build-tag-neutral file so that the stub and full implementations share the same return type.
type MDNSService ¶
type MDNSService struct{}
MDNSService is the build-tag stub for installations built without the netlink_mdns tag. All methods are no-ops.
func NewMDNSService ¶
func NewMDNSService(_ *Identity, _ int, _ *slog.Logger) *MDNSService
NewMDNSService returns a disabled service. The full implementation is gated behind the `netlink_mdns` build tag and depends on github.com/hashicorp/mdns.
func (*MDNSService) Entries ¶
func (m *MDNSService) Entries() []MDNSEntry
Entries always returns an empty slice.
func (*MDNSService) Start ¶
func (m *MDNSService) Start(_ context.Context) error
Start is a no-op when mDNS is disabled at build time.
func (*MDNSService) Stop ¶
func (m *MDNSService) Stop() error
Stop is a no-op when mDNS is disabled at build time.
type Mesh ¶
type Mesh struct {
// contains filtered or unexported fields
}
Mesh wires together identity, trust, discovery, and a TCP listener that exchanges gossip with peers. It is the single entry point for the daemon to start participating in a DokiLink-Lite mesh.
func NewMesh ¶
func NewMesh(cfg MeshConfig) (*Mesh, error)
NewMesh constructs a Mesh. The identity is required; everything else is optional (defaulted). The mesh does not listen until Start().
func (*Mesh) AnnounceContainer ¶
func (m *Mesh) AnnounceContainer(c RemoteContainer) error
AnnounceContainer publishes a container descriptor to all peers.
type MeshConfig ¶
type MeshConfig struct {
Identity *Identity
Trust *TrustStore
Static *StaticPeers
ListenAddr string // host:port to listen for gossip, e.g. ":7432"
Logger *slog.Logger
// EnableMDNS requires the netlink_mdns build tag. With the default
// build the mDNS service is a no-op stub.
EnableMDNS bool
}
MeshConfig configures a Mesh.
type NATConfig ¶ added in v0.11.0
type NATConfig struct {
// STUNServers is the list of STUN server addresses (host:port).
// If empty, defaults to a public STUN server.
STUNServers []string
// RelayPeers is a list of peer addresses that can act as relays
// when direct connection and hole punching fail. Each entry is
// host:port of a peer known to have a routable IP.
RelayPeers []string
// EnableHolePunch enables TCP simultaneous open hole punching.
EnableHolePunch bool
// DialTimeout is the timeout for each dial attempt.
DialTimeout time.Duration
// Logger is the structured logger. Defaults to slog.Default().
Logger *slog.Logger
}
NATConfig configures NAT traversal behavior.
func DefaultNATConfig ¶ added in v0.11.0
func DefaultNATConfig() NATConfig
DefaultNATConfig returns a sensible default NAT configuration.
type NATTraversal ¶ added in v0.11.0
type NATTraversal struct {
// contains filtered or unexported fields
}
NATTraversal coordinates NAT traversal for a single peer connection. It is created per-connection and is safe for concurrent use.
func NewNATTraversal ¶ added in v0.11.0
func NewNATTraversal(cfg NATConfig) *NATTraversal
NewNATTraversal creates a NATTraversal with the given config.
func (*NATTraversal) Dial ¶ added in v0.11.0
func (n *NATTraversal) Dial(ctx context.Context, peerAddr string, peerPublicAddr *PublicAddr) (net.Conn, error)
Dial attempts to connect to a peer, trying direct dial, hole punching, and relay in order. Returns the established connection or an error if all methods fail.
func (*NATTraversal) DiscoverPublicAddr ¶ added in v0.11.0
func (n *NATTraversal) DiscoverPublicAddr(ctx context.Context, localPort int) (*PublicAddr, error)
DiscoverPublicAddr queries a STUN server to discover this host's public IP:port as seen by the STUN server. This is used to share the public address with peers for hole punching.
type Peer ¶
type Peer struct {
ID string `json:"id"` // short install id
Name string `json:"name"` // human-friendly (defaults to ID)
Addr string `json:"addr"` // host:port the peer listens on
PubKey string `json:"pubkey"` // base64 of Ed25519 public key
CACert string `json:"ca_cert"` // PEM-encoded CA cert
LastSeen time.Time `json:"last_seen"`
Containers []RemoteContainer `json:"containers,omitempty"`
}
Peer represents a remote Doki instance participating in the mesh. Each peer is uniquely identified by its short install ID (the first 12 chars of base32 of its Ed25519 public key).
type PeerSnapshot ¶
type PeerSnapshot struct {
ID string `json:"id"`
Addr string `json:"addr"`
PubKey string `json:"pubkey"`
}
PeerSnapshot is a compact, wire-friendly peer record. It is what gets gossiped; full Peer objects (with .Containers) live only in local memory.
type PublicAddr ¶ added in v0.11.0
PublicAddr is the public endpoint discovered via STUN.
func (PublicAddr) String ¶ added in v0.11.0
func (a PublicAddr) String() string
String returns "ip:port" format.
type RelayServer ¶ added in v0.11.0
type RelayServer struct {
// contains filtered or unexported fields
}
RelayServer is a TURN-like relay that accepts connections from peers behind NAT and forwards their traffic to the target. A peer with a routable IP runs this to help other peers connect.
func NewRelayServer ¶ added in v0.11.0
func NewRelayServer(addr string, logger *slog.Logger) *RelayServer
NewRelayServer creates a relay server listening on addr.
func (*RelayServer) Addr ¶ added in v0.11.0
func (r *RelayServer) Addr() string
Addr returns the bound address.
func (*RelayServer) Close ¶ added in v0.11.0
func (r *RelayServer) Close() error
Close stops the relay server.
type RemoteContainer ¶
type RemoteContainer struct {
Name string `json:"name"`
ID string `json:"id"`
IP string `json:"ip"`
Port uint16 `json:"port"`
Proto string `json:"proto"`
HostPort uint16 `json:"host_port,omitempty"`
}
RemoteContainer describes a container running on a peer that we can reach through the mesh.
type SecretboxWrapper ¶
type SecretboxWrapper struct {
// contains filtered or unexported fields
}
SecretboxWrapper adds a NaCl secretbox layer on top of whatever transport (TCP or UDP) is being wrapped. It uses a single 32-byte key shared by both peers; that key is derived from the Ed25519 identities of the two endpoints (see DeriveSecretKey).
On TCP, the wrapper applies a per-connection nonce (24 bytes secretbox.NonceSize + 16 bytes secretbox.Overhead) and uses a 4-byte big-endian length prefix per frame. On UDP, it uses the standard nacl/secretbox.Seal/Open API with a per-datagram nonce derived from the message counter.
SecretboxWrapper implements both TransportWrapper (for TCP) and udpWrapper (for UDP).
func NewSecretboxWrapper ¶
func NewSecretboxWrapper(key [32]byte) *SecretboxWrapper
NewSecretboxWrapper builds a wrapper from a 32-byte shared key. Use DeriveSecretKey to compute it from peer identities.
func (*SecretboxWrapper) UnwrapInbound ¶
func (s *SecretboxWrapper) UnwrapInbound(payload []byte) ([]byte, error)
UnwrapInbound reverses WrapOutbound.
func (*SecretboxWrapper) WrapClient ¶
WrapClient mirrors WrapServer; the stream conn is symmetric.
func (*SecretboxWrapper) WrapOutbound ¶
func (s *SecretboxWrapper) WrapOutbound(payload []byte) ([]byte, error)
WrapOutbound / UnwrapInbound for UDP: each datagram is one secretbox.Seal'd payload with its own nonce.
func (*SecretboxWrapper) WrapServer ¶
WrapServer / WrapClient for TCP: wrap the conn in a framed secretbox stream that prepends a 4-byte big-endian length to each encrypted frame. The frame payload itself is:
nonce (24 bytes) || secretbox(plaintext)
Each connection gets its own random nonce base to guarantee nonce uniqueness across connections that share the same derived key.
type StaticPeers ¶
type StaticPeers struct {
// contains filtered or unexported fields
}
StaticPeers is an in-memory view of $DOKI_ROOT/mesh/peers.json with safe concurrent access and atomic file writes.
func NewStaticPeers ¶
func NewStaticPeers(path string) (*StaticPeers, error)
NewStaticPeers loads (or creates) the static peers file at path.
func (*StaticPeers) Add ¶
func (sp *StaticPeers) Add(p *Peer) error
Add inserts or updates a peer, normalising fields, and persists the file.
func (*StaticPeers) Get ¶
func (sp *StaticPeers) Get(id string) *Peer
Get returns a single peer by ID, or nil.
func (*StaticPeers) List ¶
func (sp *StaticPeers) List() []*Peer
List returns a copy of the static peer list.
func (*StaticPeers) Remove ¶
func (sp *StaticPeers) Remove(id string) error
Remove deletes a peer by ID and persists the file.
func (*StaticPeers) Touch ¶
func (sp *StaticPeers) Touch(id string)
Touch updates LastSeen to time.Now() for the given peer ID.
type StaticPeersConfig ¶
type StaticPeersConfig struct {
Peers []*Peer `json:"peers"`
}
StaticPeersConfig is the on-disk representation of manually configured peers. The file is $DOKI_ROOT/mesh/peers.json and can be edited by hand or by `doki link add <name> <addr> --pub <b64>`.
type TCPProxy ¶
type TCPProxy struct {
// contains filtered or unexported fields
}
TCPProxy is a TCP byte-stream proxy. It listens on a host address and, for each accepted client connection, dials the upstream (container) address and pumps bytes in both directions until either side closes.
func NewTCPProxy ¶
func NewTCPProxy(cfg TCPProxyConfig) *TCPProxy
NewTCPProxy creates a TCPProxy in the "configured but not listening" state. Call Start to bind the listener and begin accepting connections.
func (*TCPProxy) Addr ¶
Addr returns the bound listen address. Useful when ListenAddr contained port 0 (OS-assigned). Returns empty string if Start has not succeeded.
type TCPProxyConfig ¶
type TCPProxyConfig struct {
ListenAddr string // e.g. "0.0.0.0:8888" or "127.0.0.1:0"
Upstream string // e.g. "127.0.0.1:80"
Wrap TransportWrapper // optional
IdleTimeout time.Duration // 0 = no timeout
Logger *slog.Logger // nil = slog.Default()
}
TCPProxyConfig configures a TCPProxy.
type TLSWrapper ¶
type TLSWrapper struct {
// contains filtered or unexported fields
}
TLSWrapper wraps a net.Conn with TLS 1.3. It is configured once at construction and then applied to every wrapped connection.
func NewTLSWrapper ¶
func NewTLSWrapper(cfg *tls.Config) (*TLSWrapper, error)
NewTLSWrapper builds a wrapper from a *tls.Config. The same config is used for both WrapServer and WrapClient; whether the resulting conn behaves as server or client is decided by the handshake. The caller's config is cloned so NewTLSWrapper has no side effects on it.
func (*TLSWrapper) WrapClient ¶
WrapClient wraps the client-side (container/upstream) of a connection. The returned net.Conn has completed a TLS handshake by the time the function returns.
func (*TLSWrapper) WrapServer ¶
WrapServer wraps the server-side (host) of a connection. The returned net.Conn has completed a TLS handshake by the time the function returns (or has returned an error).
type TransportWrapper ¶
type TransportWrapper interface {
// WrapServer wraps the server-side (host) of a connection. If the
// implementation performs a handshake (e.g. TLS), it MUST respect
// ctx.Done().
WrapServer(ctx context.Context, c net.Conn) (net.Conn, error)
// WrapClient wraps the client-side (container/upstream) of a connection.
WrapClient(ctx context.Context, c net.Conn) (net.Conn, error)
}
TransportWrapper optionally wraps a net.Conn to add encryption or authentication. It must be safe to call from multiple goroutines.
type TrustStore ¶
type TrustStore struct {
// contains filtered or unexported fields
}
TrustStore persists known peer public keys and CA certs. Trust is established on first contact (TOFU) and then verified on every subsequent message.
func NewTrustStore ¶
func NewTrustStore(dir string) (*TrustStore, error)
NewTrustStore creates a TrustStore rooted at dir. The directory is created if it does not exist.
func (*TrustStore) CACert ¶
func (ts *TrustStore) CACert(peerID string) *x509.Certificate
CACert returns the trusted CA cert for peerID, or nil if none was stored.
func (*TrustStore) ListIDs ¶
func (ts *TrustStore) ListIDs() []string
ListIDs returns all trusted peer IDs.
func (*TrustStore) Load ¶
func (ts *TrustStore) Load() error
Load reads all .pub.pem and .ca.pem files from the trust store directory. Called at startup.
func (*TrustStore) PubKey ¶
func (ts *TrustStore) PubKey(peerID string) (ed25519.PublicKey, error)
PubKey returns the trusted public key for peerID, or an error.
func (*TrustStore) Trust ¶
func (ts *TrustStore) Trust(peerID string, pub ed25519.PublicKey, ca *x509.Certificate) error
Trust records a peer's public key and (optionally) CA cert. If the peer is already trusted, the existing record is returned unless the new pubkey differs (in which case an error is returned to surface the potential MITM or key-rotation mismatch).
func (*TrustStore) Trusted ¶
func (ts *TrustStore) Trusted(peerID string) bool
Trusted returns true if peerID has been recorded in the store.
type UDPProxy ¶
type UDPProxy struct {
// contains filtered or unexported fields
}
UDPProxy is a stateless UDP packet forwarder plus a small session tracker for return traffic. Each incoming datagram from the host side is forwarded to the upstream address, and each datagram from the upstream is forwarded back to the most-recent sender for the matching session.
Unlike TCP, UDP is connectionless: we do not maintain a 1:1 proxy connection. Instead, we track sessions by (hostAddr, upstreamAddr) pair and time them out after sessionTimeout of inactivity.
func NewUDPProxy ¶
func NewUDPProxy(cfg UDPProxyConfig) *UDPProxy
NewUDPProxy creates a UDPProxy in the "configured but not listening" state. Call Start to bind the socket and begin forwarding.