nat

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

View Source
const DefaultMinReportInterval = 10 * time.Second

DefaultMinReportInterval is the default floor on the deadline-driven endpoint report cadence.

View Source
const DefaultRefreshInterval = 60 * time.Second

DefaultRefreshInterval is the default interval between STUN binding refreshes.

View Source
const DefaultTimeout = 5 * time.Second

DefaultTimeout is the default per-server STUN request timeout.

Variables

View Source
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.

func (*Config) Validate

func (c *Config) Validate() error

Validate checks that configuration values are within acceptable ranges.

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

type DiscoveryResult struct {
	Endpoint string // "ip:port" format
	NATType  NATType
}

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

type MappedAddress struct {
	IP   net.IP
	Port int
}

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 NATType

type NATType string

NATType represents the classified NAT behavior.

const (
	NATNone      NATType = "none"
	NATFullCone  NATType = "full_cone"
	NATSymmetric NATType = "symmetric"
	NATUnknown   NATType = "unknown"
)

func (NATType) Wire added in v0.2.0

func (t NATType) Wire() string

Wire returns the control-plane wire representation of the NAT type. The wire enum has no "none": an un-NATed endpoint is directly reachable with no filtering, which is the full_cone traversal posture.

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

type UDPSTUNClient struct {
	Timeout time.Duration
}

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.

Jump to

Keyboard shortcuts

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