Documentation
¶
Overview ¶
Package nat provides NAT traversal via STUN for plexd mesh nodes.
Package nat implements STUN-based NAT traversal and endpoint discovery.
Index ¶
Constants ¶
const DefaultMinReportInterval = 10 * time.Second
DefaultMinReportInterval is the default floor on the deadline-driven endpoint report cadence.
const DefaultRefreshInterval = 60 * time.Second
DefaultRefreshInterval is the default interval between STUN binding refreshes.
const DefaultTimeout = 5 * time.Second
DefaultTimeout is the default per-server STUN request timeout.
Variables ¶
var DefaultSTUNServers = []string{
"stun.l.google.com:19302",
"stun.cloudflare.com:3478",
}
DefaultSTUNServers is the default list of STUN servers used for NAT traversal.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Enabled controls whether NAT traversal is active.
// Default: true (set by ApplyDefaults).
Enabled bool `yaml:"enabled"`
// STUNServers is the list of STUN server addresses (host:port).
STUNServers []string `yaml:"stun_servers"`
// RefreshInterval is the interval between STUN binding refreshes.
// Must be at least 10s.
RefreshInterval time.Duration `yaml:"refresh_interval"`
// Timeout is the per-server STUN request timeout.
// Must be positive.
Timeout time.Duration `yaml:"timeout"`
// MinReportInterval is the floor on the endpoint report cadence derived
// from the control plane's stale_after deadline. A short deadline can
// otherwise drive STUN queries and endpoint reports well above
// RefreshInterval; raising this bounds how far the server may accelerate
// the loop. Must be positive.
// Default: 10s (set by ApplyDefaults).
MinReportInterval time.Duration `yaml:"min_report_interval"`
}
Config holds the configuration for NAT traversal.
func (*Config) ApplyDefaults ¶
func (c *Config) ApplyDefaults()
ApplyDefaults sets default values for zero-valued fields. On a zero-valued Config, Enabled defaults to true. To disable NAT traversal, set Enabled=false before or after calling ApplyDefaults.
type Discoverer ¶
type Discoverer struct {
// contains filtered or unexported fields
}
Discoverer performs STUN-based NAT traversal to discover the node's public endpoint.
STUN runs on its own ephemeral UDP socket, never on the WireGuard listen port: the kernel WireGuard socket owns that port, and UDP port sharing would require SO_REUSEPORT on both sockets, which the kernel side does not set — a bind attempt fails with EADDRINUSE on every node whose WireGuard interface is up. The mapped address therefore describes the STUN socket's mapping, and the published endpoint carries the mapped IP with the advertised WireGuard listen port instead. That endpoint is exact wherever the NAT preserves or forwards the port; where it translates ports per-mapping, nat_type (symmetric) remains the signal that direct dialing is unreliable.
func NewDiscoverer ¶
func NewDiscoverer(client STUNClient, cfg Config, advertisePort int, logger *slog.Logger) *Discoverer
NewDiscoverer creates a new Discoverer. advertisePort is the WireGuard listen port carried in every published endpoint.
func (*Discoverer) Discover ¶
func (d *Discoverer) Discover(ctx context.Context) (*DiscoveryResult, error)
Discover performs STUN binding requests to discover the public endpoint and classify NAT type.
func (*Discoverer) LastResult ¶
func (d *Discoverer) LastResult() *DiscoveryResult
LastResult returns the most recently discovered NAT info, or nil if no discovery has succeeded.
func (*Discoverer) Run ¶
func (d *Discoverer) Run(ctx context.Context, reporter EndpointReporter, nodeID string) error
Run performs initial STUN discovery, reports the endpoint, then enters a refresh loop. It blocks until ctx is cancelled or an unrecoverable error occurs.
type DiscoveryResult ¶
DiscoveryResult holds the outcome of a STUN discovery cycle.
type EndpointReporter ¶
type EndpointReporter interface {
ReportEndpoint(ctx context.Context, nodeID string, req api.EndpointRequest) (*api.EndpointResponse, error)
}
EndpointReporter abstracts the control plane endpoint reporting API.
type MappedAddress ¶
MappedAddress represents a STUN XOR-MAPPED-ADDRESS result.
func (MappedAddress) Routable ¶ added in v0.2.0
func (m MappedAddress) Routable() bool
Routable reports whether the address can serve as this node's public endpoint: a port in 1..65535 and an IP that is neither loopback, link-local, nor unspecified.
A STUN Binding Response is unauthenticated, so the mapped address it carries is attacker-influenceable: an on-path attacker or the operator of a configured STUN server can name any address. The control plane hands that address to every mesh peer, which would then aim WireGuard handshakes at it — traffic redirection and a reflection primitive against a third party. A loopback or unspecified value instead blackholes the node. Neither is ever a legitimate public endpoint, so both are rejected before the address is published.
func (MappedAddress) String ¶
func (m MappedAddress) String() string
String returns the address in canonical "host:port" form; IPv6 hosts are bracketed per RFC 5952.
type STUNClient ¶
type STUNClient interface {
Bind(ctx context.Context, serverAddr string, localPort int) (MappedAddress, int, error)
}
STUNClient abstracts STUN binding operations for testability.
Bind sends a Binding Request to serverAddr from localPort and returns the mapped address together with the local port the request was actually sent from. A localPort of 0 lets the OS pick an ephemeral port; the returned port tells the caller which one, so a discovery cycle can reuse it across servers and compare their mapped addresses meaningfully.
type UDPSTUNClient ¶
UDPSTUNClient performs STUN binding requests over UDP.
func (*UDPSTUNClient) Bind ¶
func (c *UDPSTUNClient) Bind(ctx context.Context, serverAddr string, localPort int) (MappedAddress, int, error)
Bind sends a STUN Binding Request to serverAddr from localPort (0 for an OS-assigned ephemeral port) and returns the mapped address from the response along with the local port the socket was bound to.