wgnet

package module
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Mar 30, 2026 License: MIT Imports: 16 Imported by: 1

README

wgnet

Tests Coverage Status

A Go library that abstracts the WireGuard protocol behind a simple interface: feed it UDP packets, get back decrypted network packets, and vice versa. All the cryptography, handshakes, session management, and replay protection are handled internally.

Install

go get github.com/vpdotnet/wgnet

Quick Start

// Create a handler (generates a new identity if no private key is provided)
handler, _ := wgnet.NewHandler(wgnet.Config{})
defer handler.Close()

// Authorize peers by public key
handler.AddPeer(peerPublicKey)

// Start maintenance (cookie rotation, session cleanup)
go func() {
    for range time.Tick(10 * time.Second) {
        handler.Maintenance()
    }
}()
Receiving: UDP in, network packets out
// Read a UDP datagram from the wire
n, remoteAddr, _ := conn.ReadFromUDP(buf)

// Hand it to the handler
result, err := handler.ProcessPacket(buf[:n], remoteAddr)
if err != nil {
    log.Println(err)
    continue
}

switch result.Type {
case wgnet.PacketHandshakeResponse, wgnet.PacketCookieReply:
    // Protocol response — relay back over UDP
    conn.WriteToUDP(result.Response, remoteAddr)

case wgnet.PacketTransportData:
    // Decrypted network packet (e.g. IPv4/IPv6) ready for processing
    forward(result.Data, result.PeerKey)

case wgnet.PacketKeepalive:
    // Nothing to do
}
Sending: network packets in, UDP out
encrypted, _ := handler.Encrypt(ipPacket, peerPublicKey)
conn.WriteToUDP(encrypted, peerAddr)

Multiple Identities on One Port

MultiHandler lets a single UDP socket serve multiple WireGuard server identities. Incoming packets are automatically routed to the correct handler based on MAC1 (handshakes) or receiver index (transport data).

mh, _ := wgnet.NewMultiHandler(handler1, handler2)
defer mh.Close()

result, _ := mh.ProcessPacket(buf[:n], remoteAddr)
// result.Handler tells you which identity was targeted
fmt.Println("routed to", result.Handler.PublicKey())

Handlers can be added or removed at runtime:

mh.AddHandler(handler3)
mh.RemoveHandler(handler2.PublicKey())

Peer Management

// Authorize with optional preshared key
handler.AddPeer(pubKey)
handler.AddPeerWithPSK(pubKey, psk)

// Async authorize unknown peers via callback
handler, _ := wgnet.NewHandler(wgnet.Config{
    OnUnknownPeer: func(pk wgnet.NoisePublicKey, addr *net.UDPAddr, packet []byte) {
        pkt := make([]byte, len(packet))
        copy(pkt, packet)
        go func() {
            if !isAllowed(pk) {
                return
            }
            if err := handler.AcceptUnknownPeer(pk, pkt, addr); err != nil {
                log.Println("accept peer:", err)
            }
        }()
    },
})

// Set peer expiry
handler.SetPeerExpiry(pubKey, time.Now().Add(24 * time.Hour))

// Remove peer (tears down session immediately)
handler.RemovePeer(pubKey)

Key Generation

privKey, _ := wgnet.GeneratePrivateKey()
pubKey := privKey.PublicKey()

License

MIT - see LICENSE for details.

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

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

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

WireGuard protocol constants

Variables

This section is empty.

Functions

This section is empty.

Types

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
}

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 added in v0.1.1

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) GenerateKeepalive

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

GenerateKeepalive generates a keepalive packet for a peer.

func (*Handler) GetPeerInfo added in v0.1.4

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) 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 added in v0.1.4

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

MarshalText implements encoding.TextMarshaler.

func (NoisePresharedKey) String added in v0.1.4

func (psk NoisePresharedKey) String() string

String returns the base64-encoded preshared key.

func (*NoisePresharedKey) UnmarshalText added in v0.1.4

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 added in v0.1.4

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 added in v0.1.4

func (sk NoisePrivateKey) String() string

String returns the base64-encoded private key.

func (*NoisePrivateKey) UnmarshalText added in v0.1.4

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 added in v0.1.4

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

MarshalText implements encoding.TextMarshaler.

func (NoisePublicKey) String added in v0.1.4

func (pk NoisePublicKey) String() string

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

func (*NoisePublicKey) UnmarshalText added in v0.1.4

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.

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
}

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