wg

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Apr 5, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package wgnet implements a WireGuard point-to-point endpoint library.

Unlike the standard WireGuard implementation which is designed for multipoint networks, wgnet focuses on simple client/server endpoint connections.

Index

Constants

View Source
const (

	// CookieRefreshTime is the maximum lifetime of a cookie secret.
	CookieRefreshTime = 120 * time.Second

	// RejectAfterTime is how long sessions and pending handshakes are kept
	// before being cleaned up by Maintenance.
	RejectAfterTime = 180 * time.Second

	// Rekey thresholds (WireGuard spec §5.1)
	RekeyAfterMessages  = uint64(1) << 60
	RejectAfterMessages = ^uint64(0) - (uint64(1) << 13)
	RekeyAfterTime      = 120 * time.Second

	// Key sizes
	NoisePublicKeySize    = 32
	NoisePrivateKeySize   = 32
	NoisePresharedKeySize = 32

	// WindowSize is the size of the replay protection sliding window.
	WindowSize = 8192
)

WireGuard protocol constants

Variables

View Source
var ErrRekeyRequired = errors.New("rekey required")

ErrRekeyRequired is returned alongside a valid encrypted packet when the keypair has exceeded RekeyAfterTime or RekeyAfterMessages. The caller should send the packet and then initiate a new handshake.

View Source
var NowFunc func() time.Time = time.Now

NowFunc is the function used by the WireGuard package to obtain the current time. The default is time.Now. Override this to provide a cached or monotonic clock in environments where syscalls are expensive (e.g. SGX).

Functions

func EncryptedSize added in v0.1.1

func EncryptedSize(plaintextLen int) int

EncryptedSize returns the total wire size of an encrypted WireGuard packet for the given plaintext length.

func UseCachedTime

func UseCachedTime(d time.Duration)

UseCachedTime replaces NowFunc with a goroutine-cached clock that updates every d. This avoids per-call syscall overhead at the cost of d resolution. Call with 0 to restore time.Now.

Types

type Adapter

type Adapter struct {
	// contains filtered or unexported fields
}

Adapter bridges WireGuard peers to pktkit networking. Each peer that completes a handshake gets a per-peer L3Device connected to the configured connector.

With an pktkit.L3Connector (e.g. [slirp.Stack]): each peer gets a namespace-isolated NAT connection. Decrypted IP packets flow directly between the WireGuard tunnel and the NAT engine — no L2 framing overhead.

With an pktkit.L2Connector (e.g. *pktkit.L2Hub): each peer's L3Device is wrapped in an L2Adapter for Ethernet framing on a shared broadcast domain.

func NewAdapter

func NewAdapter(cfg AdapterConfig) (*Adapter, error)

NewAdapter creates a WireGuard adapter. Call Adapter.Serve to start.

func (*Adapter) AcceptUnknownPeer

func (a *Adapter) AcceptUnknownPeer(key NoisePublicKey, packet []byte, addr *net.UDPAddr) error

AcceptUnknownPeer authorizes a previously unknown peer and completes its handshake. In multi-key mode, the correct handler is determined automatically from the packet's MAC1.

func (*Adapter) AddPeer

func (a *Adapter) AddPeer(key NoisePublicKey)

AddPeer authorizes a peer by public key. In multi-key mode, the peer is authorized on all handlers. Use [AddPeerTo] to target a specific identity.

func (*Adapter) AddPeerTo added in v0.1.2

func (a *Adapter) AddPeerTo(key NoisePublicKey, handler *Handler)

AddPeerTo authorizes a peer on a specific handler (multi-key mode).

func (*Adapter) AddPeerWithPSK

func (a *Adapter) AddPeerWithPSK(key NoisePublicKey, psk NoisePresharedKey)

AddPeerWithPSK authorizes a peer with a preshared key. In single-key mode only; use [AddPeerTo] with handler.AddPeerWithPSK in multi-key mode.

func (*Adapter) Close

func (a *Adapter) Close() error

Close stops the adapter and tears down all peer connections.

func (*Adapter) Connect

func (a *Adapter) Connect(key NoisePublicKey, addr *net.UDPAddr) error

Connect initiates a handshake to a peer at the given address. The peer must be authorized first via [AddPeer]. In single-key mode only; use [ConnectWith] in multi-key mode.

func (*Adapter) ConnectWith added in v0.1.2

func (a *Adapter) ConnectWith(key NoisePublicKey, addr *net.UDPAddr, handler *Handler) error

ConnectWith initiates a handshake to a peer using a specific handler.

func (*Adapter) Handler

func (a *Adapter) Handler() *Handler

Handler returns the underlying WireGuard protocol handler (single-key mode). Returns nil in multi-key mode.

func (*Adapter) Handlers added in v0.1.2

func (a *Adapter) Handlers() []*Handler

Handlers returns all handlers. In single-key mode, returns a slice with the single handler. In multi-key mode, returns all handlers.

func (*Adapter) MultiHandler added in v0.1.2

func (a *Adapter) MultiHandler() *MultiHandler

MultiHandler returns the multi-key handler, or nil in single-key mode.

func (*Adapter) PublicKey

func (a *Adapter) PublicKey() NoisePublicKey

PublicKey returns the adapter's WireGuard public key (single-key mode only). In multi-key mode, use [Handlers] to access individual public keys.

func (*Adapter) RemovePeer

func (a *Adapter) RemovePeer(key NoisePublicKey)

RemovePeer revokes a peer and tears down its network plumbing. In multi-key mode, the peer is removed from all handlers.

func (*Adapter) RemovePeerFrom added in v0.1.2

func (a *Adapter) RemovePeerFrom(key NoisePublicKey, handler *Handler)

RemovePeerFrom removes a peer from a specific handler and tears down its network plumbing.

func (*Adapter) Serve

func (a *Adapter) Serve(conn net.PacketConn) error

Serve starts the WireGuard read loop on conn. Blocks until [Close] is called.

type AdapterConfig

type AdapterConfig struct {
	// PrivateKey is the local WireGuard identity. If zero, a new key is generated.
	// Ignored if MultiHandler is set.
	PrivateKey NoisePrivateKey

	// MultiHandler provides multiple WireGuard identities on a single port.
	// When set, PrivateKey is ignored and the adapter operates in multi-key
	// mode. Use [Adapter.AddPeerTo] and [Adapter.RemovePeerFrom] to manage
	// peers per-identity, or [Adapter.AddPeer] to authorize on all identities.
	MultiHandler *MultiHandler

	// Connector wires each peer's L3Device to the network. Use a
	// [slirp.Stack] or [nat.NAT] for per-peer namespace-isolated NAT.
	// Exactly one of Connector or L2Connector must be set.
	Connector pktkit.L3Connector

	// L2Connector wires each peer as an L2Device on a shared network.
	// Use an [*pktkit.L2Hub] for a shared broadcast domain. When set,
	// Addr must also be set for ARP resolution. Exactly one of
	// Connector or L2Connector must be set.
	L2Connector pktkit.L2Connector

	// Addr is the IP prefix for the peer's L3Device. Required for
	// L2Connector mode (used by the L2Adapter for ARP). Ignored in
	// L3Connector mode.
	Addr netip.Prefix

	// OnUnknownPeer is called when an unauthorized peer initiates a handshake.
	// Call [Adapter.AddPeer] from this callback (or later) to authorize it,
	// then call [Adapter.AcceptUnknownPeer] to complete the handshake.
	OnUnknownPeer func(key NoisePublicKey, addr *net.UDPAddr, packet []byte)
}

AdapterConfig configures a WireGuard Adapter.

type Config

type Config struct {
	// PrivateKey is the local static private key. If zero, a new key is generated.
	PrivateKey NoisePrivateKey

	// Conn is the UDP connection used for sending handshake responses when
	// AcceptUnknownPeer is called. Required if OnUnknownPeer is set.
	Conn net.PacketConn

	// OnUnknownPeer is called when a handshake arrives from an unauthorized peer.
	// If nil, unknown peers are rejected.
	OnUnknownPeer UnknownPeerFunc

	// LoadThreshold sets the number of concurrent handshakes before MAC2
	// cookie validation is required. 0 uses the default (20).
	LoadThreshold int
}

Config configures a Handler.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler implements the WireGuard protocol, supporting both initiator and responder roles. Use InitiateHandshake to start a connection, or pass incoming packets to ProcessPacket to respond to them.

func NewHandler

func NewHandler(cfg Config) (*Handler, error)

NewHandler creates a new WireGuard protocol handler.

func (*Handler) AcceptUnknownPeer

func (h *Handler) AcceptUnknownPeer(peerKey NoisePublicKey, initiationPacket []byte, remoteAddr *net.UDPAddr) error

AcceptUnknownPeer authorizes a previously unknown peer, re-processes its handshake initiation packet, and sends the handshake response via the handler's connection. Call this from an OnUnknownPeer callback (or later) after verifying the peer.

func (*Handler) AddPeer

func (h *Handler) AddPeer(peerKey NoisePublicKey)

AddPeer authorizes a peer by public key.

func (*Handler) AddPeerWithPSK

func (h *Handler) AddPeerWithPSK(peerKey NoisePublicKey, psk NoisePresharedKey)

AddPeerWithPSK authorizes a peer with a preshared key.

func (*Handler) Close

func (h *Handler) Close() error

Close clears all pending handshakes, sessions, and keypairs. It does not affect peer authorization — peers remain in the authorized list.

func (*Handler) Encrypt

func (h *Handler) Encrypt(data []byte, peerKey NoisePublicKey) ([]byte, error)

Encrypt encrypts data for transmission to a peer.

func (*Handler) EncryptPooled added in v0.1.1

func (h *Handler) EncryptPooled(data []byte, peerKey NoisePublicKey) ([]byte, *[]byte, error)

EncryptPooled encrypts data for a peer using a buffer from the global packet pool. Returns the encrypted packet and a pool handle. The caller MUST call pktkit.FreeBuffer(handle) after the packet has been sent (e.g. after WriteTo).

This is the zero-allocation encrypt path for callers that can manage buffer lifetimes (such as the WireGuard Server).

func (*Handler) EncryptTo added in v0.1.1

func (h *Handler) EncryptTo(dst []byte, data []byte, peerKey NoisePublicKey) (int, error)

EncryptTo encrypts data for a peer into the provided buffer dst. dst must be at least EncryptedSize(len(data)) bytes. Returns the number of bytes written, or an error.

When err is ErrRekeyRequired the data was still encrypted successfully; the caller should send the packet and then initiate a new handshake.

This is the zero-allocation encrypt path. The caller is responsible for providing (and recycling) the buffer.

func (*Handler) GenerateKeepalive

func (h *Handler) GenerateKeepalive(peerKey NoisePublicKey) ([]byte, error)

GenerateKeepalive generates a keepalive packet for a peer.

func (*Handler) GetPeerInfo

func (h *Handler) GetPeerInfo(peerKey NoisePublicKey) (PeerInfo, bool)

GetPeerInfo returns information about an authorized peer. Returns false if the peer is not found.

func (*Handler) HasSession

func (h *Handler) HasSession(peerKey NoisePublicKey) bool

HasSession returns true if there is an active session with the given peer.

func (*Handler) InitiateHandshake

func (h *Handler) InitiateHandshake(peerKey NoisePublicKey) ([]byte, error)

InitiateHandshake creates a handshake initiation packet for the given peer. The peer must already be authorized via AddPeer or AddPeerWithPSK. Returns a 148-byte packet ready to send.

func (*Handler) IsAuthorizedPeer

func (h *Handler) IsAuthorizedPeer(peerKey NoisePublicKey) bool

IsAuthorizedPeer checks if a peer's public key is authorized. Returns false if the peer is unknown or has passed its ExpiresAt time.

func (*Handler) Maintenance

func (h *Handler) Maintenance()

Maintenance performs periodic cleanup: rotates the cookie secret, removes stale pending handshakes, and expires inactive sessions. Call this periodically (e.g. every 10s). The Server calls it automatically.

func (*Handler) Peers

func (h *Handler) Peers() []NoisePublicKey

Peers returns the list of authorized peer public keys.

func (*Handler) ProcessPacket

func (h *Handler) ProcessPacket(data []byte, remoteAddr *net.UDPAddr) (PacketResult, error)

ProcessPacket processes an incoming WireGuard packet and returns the result. It dispatches by message type: initiation (1), response (2), cookie reply (3), and transport data (4). The caller is responsible for sending any Response bytes back to remoteAddr.

func (*Handler) PublicKey

func (h *Handler) PublicKey() NoisePublicKey

PublicKey returns the handler's public key.

func (*Handler) RemovePeer

func (h *Handler) RemovePeer(peerKey NoisePublicKey)

RemovePeer removes a peer from the authorized list and tears down its session.

func (*Handler) SessionInfo

func (h *Handler) SessionInfo(peerKey NoisePublicKey) (lastReceived, lastSent time.Time, ok bool)

SessionInfo returns timing information about a peer's session. Returns zero times if no session exists.

func (*Handler) SetConn

func (h *Handler) SetConn(conn net.PacketConn)

SetConn sets the handler's packet connection for AcceptUnknownPeer responses.

func (*Handler) SetPeerExpiry

func (h *Handler) SetPeerExpiry(peerKey NoisePublicKey, expiresAt time.Time)

SetPeerExpiry sets an expiration time for a peer. After this time, the peer will no longer be authorized for new handshakes.

type MultiHandler

type MultiHandler struct {
	// contains filtered or unexported fields
}

MultiHandler multiplexes multiple Handler instances on a single UDP port. It routes incoming packets to the correct handler based on MAC1 (for initiation messages), pending handshake index (for responses and cookie replies), or keypair index (for transport data).

func NewMultiHandler

func NewMultiHandler(handlers ...*Handler) (*MultiHandler, error)

NewMultiHandler creates a MultiHandler from one or more handlers. Returns an error if no handlers are provided or if duplicate public keys are detected.

func (*MultiHandler) AddHandler

func (mh *MultiHandler) AddHandler(h *Handler) error

AddHandler adds a handler to the MultiHandler. Returns an error if a handler with the same public key already exists.

func (*MultiHandler) Close

func (mh *MultiHandler) Close() error

Close closes all handlers, returning the first error encountered.

func (*MultiHandler) Handler

func (mh *MultiHandler) Handler(pubKey NoisePublicKey) *Handler

Handler returns the handler with the given public key, or nil if not found.

func (*MultiHandler) Handlers

func (mh *MultiHandler) Handlers() []*Handler

Handlers returns a snapshot of all handlers.

func (*MultiHandler) Maintenance

func (mh *MultiHandler) Maintenance()

Maintenance calls Maintenance on all handlers.

func (*MultiHandler) ProcessPacket

func (mh *MultiHandler) ProcessPacket(data []byte, remoteAddr *net.UDPAddr) (*MultiPacketResult, error)

ProcessPacket routes an incoming packet to the correct handler and processes it.

For handshake initiation (type 1): iterates handlers and checks MAC1 against each. For handshake response (type 2) and cookie reply (type 3): extracts the receiver index and checks pending handshake ownership. For transport data (type 4): extracts the receiver index and checks keypair ownership. Other message types return an error.

func (*MultiHandler) RemoveHandler

func (mh *MultiHandler) RemoveHandler(pubKey NoisePublicKey) *Handler

RemoveHandler removes and returns the handler with the given public key. Returns nil if no handler matches.

type MultiPacketResult

type MultiPacketResult struct {
	PacketResult
	Handler *Handler // which identity processed the packet
}

MultiPacketResult extends PacketResult with the handler that processed the packet.

type NoisePresharedKey

type NoisePresharedKey [32]byte

NoisePresharedKey is a WireGuard preshared key.

func (NoisePresharedKey) MarshalText

func (psk NoisePresharedKey) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (NoisePresharedKey) String

func (psk NoisePresharedKey) String() string

String returns the base64-encoded preshared key.

func (*NoisePresharedKey) UnmarshalText

func (psk *NoisePresharedKey) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

type NoisePrivateKey

type NoisePrivateKey [32]byte

NoisePrivateKey is a Curve25519 private key.

func GeneratePrivateKey

func GeneratePrivateKey() (NoisePrivateKey, error)

GeneratePrivateKey generates a new random Curve25519 private key.

func (NoisePrivateKey) MarshalText

func (sk NoisePrivateKey) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (NoisePrivateKey) PublicKey

func (sk NoisePrivateKey) PublicKey() NoisePublicKey

PublicKey derives the public key from this private key.

func (NoisePrivateKey) String

func (sk NoisePrivateKey) String() string

String returns the base64-encoded private key.

func (*NoisePrivateKey) UnmarshalText

func (sk *NoisePrivateKey) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

type NoisePublicKey

type NoisePublicKey [32]byte

NoisePublicKey is a Curve25519 public key.

func (NoisePublicKey) MarshalText

func (pk NoisePublicKey) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (NoisePublicKey) String

func (pk NoisePublicKey) String() string

String returns the base64-encoded public key (WireGuard standard encoding).

func (*NoisePublicKey) UnmarshalText

func (pk *NoisePublicKey) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

type PacketResult

type PacketResult struct {
	// Type indicates what kind of result this is.
	Type PacketType
	// Response contains bytes to send back to the remote address (handshake or cookie reply).
	Response []byte
	// Data contains decrypted plaintext (transport data).
	Data []byte
	// PeerKey is the public key of the peer.
	PeerKey NoisePublicKey
}

PacketResult is the result of processing an incoming packet.

type PacketType

type PacketType int

PacketType indicates the type of a processed packet result.

const (
	// PacketHandshakeResponse indicates a completed handshake with response bytes
	// that should be sent back. On the responder side this is the handshake response
	// message; on the initiator side it is a keepalive confirming the session.
	PacketHandshakeResponse PacketType = iota
	// PacketCookieReply indicates a cookie reply that should be sent back.
	PacketCookieReply
	// PacketTransportData indicates decrypted transport data.
	PacketTransportData
	// PacketKeepalive indicates a keepalive (empty transport data).
	PacketKeepalive
	// PacketCookieReceived indicates a cookie reply was received and stored
	// internally. No action is required; retry the handshake initiation.
	PacketCookieReceived
)

type PeerInfo

type PeerInfo struct {
	PublicKey     NoisePublicKey
	HasPSK        bool
	CreatedAt     time.Time
	ExpiresAt     time.Time
	LastHandshake time.Time
}

PeerInfo contains information about an authorized peer.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server manages a WireGuard endpoint over a net.PacketConn, handling the read loop, automatic protocol responses, and periodic maintenance.

func NewServer

func NewServer(cfg ServerConfig) (*Server, error)

NewServer creates a Server from the given configuration.

func (*Server) Close

func (s *Server) Close() error

Close stops the server's read loop and maintenance goroutines. It does not close the net.PacketConn or the handler(s) — the caller owns those.

Note: Close sets a read deadline on the connection to unblock any pending ReadFrom call. The caller should reset the deadline if reusing the connection.

func (*Server) Connect

func (s *Server) Connect(peerKey NoisePublicKey, addr *net.UDPAddr) error

Connect initiates a handshake to a peer at the given address. In single-handler mode, uses the handler. In multi-handler mode, returns an error (use ConnectWith to specify the handler).

func (*Server) ConnectWith

func (s *Server) ConnectWith(peerKey NoisePublicKey, addr *net.UDPAddr, handler *Handler) error

ConnectWith initiates a handshake to a peer using the specified handler.

func (*Server) PeerAddr

func (s *Server) PeerAddr(peerKey NoisePublicKey) *net.UDPAddr

PeerAddr returns the last known UDP address for the given peer, or nil if the peer has not been seen.

func (*Server) Send

func (s *Server) Send(data []byte, peerKey NoisePublicKey) error

Send encrypts data and sends it to the given peer. In multi-handler mode the handler is chosen automatically based on which handler completed the peer's handshake. Use SendTo to specify the handler explicitly.

func (*Server) SendTo

func (s *Server) SendTo(data []byte, peerKey NoisePublicKey, handler *Handler) error

SendTo encrypts data and sends it to the given peer using the specified handler. This is useful in multi-handler mode when you want to select the identity explicitly.

func (*Server) Serve

func (s *Server) Serve(conn net.PacketConn) error

Serve starts the read loop and maintenance goroutines, blocking until Close is called or the connection encounters a permanent error. The number of reader goroutines is controlled by [ServerConfig.Concurrency].

type ServerConfig

type ServerConfig struct {
	// Handler provides a single WireGuard identity.
	// Mutually exclusive with MultiHandler.
	Handler *Handler

	// MultiHandler provides multiple WireGuard identities.
	// Mutually exclusive with Handler.
	MultiHandler *MultiHandler

	// OnPacket is called when decrypted transport data arrives. The handler
	// argument identifies which identity processed the packet (always the
	// same pointer in single-handler mode).
	OnPacket func(data []byte, peerKey NoisePublicKey, handler *Handler)

	// OnPeerConnected is called when a new handshake completes. Optional.
	OnPeerConnected func(peerKey NoisePublicKey, handler *Handler)

	// MaintenanceInterval controls how often handler maintenance runs.
	// Default: 10s.
	MaintenanceInterval time.Duration

	// ReadBufferSize is the size of the UDP read buffer. Default: 2048.
	ReadBufferSize int

	// Concurrency is the number of goroutines reading from the PacketConn
	// in parallel. Each goroutine has its own buffer and calls ReadFrom
	// independently; the kernel distributes packets across readers.
	// Default: 0 (uses runtime.NumCPU()).
	Concurrency int
}

ServerConfig configures a Server.

type UnknownPeerFunc

type UnknownPeerFunc func(publicKey NoisePublicKey, remoteAddr *net.UDPAddr, packet []byte)

UnknownPeerFunc is called when a handshake is received from an unknown peer. The packet slice is only valid for the duration of the call — the callback must copy it before returning if it needs to keep the data (e.g. to pass to AcceptUnknownPeer asynchronously). To accept the peer, call Handler.AcceptUnknownPeer with the peer key, copied packet, and remote address; it re-processes the handshake and sends the response via the handler's Conn.

Jump to

Keyboard shortcuts

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