uplink

package
v0.0.0-...-be78b4e Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package uplink provides a TCP/TLS framed transport for carrying WireGuard packet payloads between a relay-attached peer and its relay/gateway (Phase 1 uplink).

It owns connection setup, TLS, framing, session lifecycle, keepalive, and peer→session registration for reverse traffic. It does not implement routing policy, relay selection, or Netmaker control-plane logic—integrate those in a separate adapter.

For application-layer (HTTP CONNECT) egress, see package l7.

Index

Examples

Constants

View Source
const (
	MsgHello uint8 = iota + 1
	MsgHelloAck
	MsgData
	MsgPing
	MsgPong
	MsgClose
	MsgError
)

Message types (spec §11.2).

View Source
const (

	// DefaultMaxFrameSize caps payload length (single frame) to limit memory use.
	DefaultMaxFrameSize = 65536
)
View Source
const ProtocolVersion uint8 = 1

Protocol version for Phase 1 framing.

Variables

View Source
var (
	// ErrNoSession is returned when SendToPeer cannot find an active session for the peer.
	ErrNoSession = errors.New("proxy: no active session for peer")
	// ErrSessionClosed is returned when writing to a closed session.
	ErrSessionClosed = errors.New("proxy: session closed")
	// ErrInvalidFrame is returned for malformed or oversized frames.
	ErrInvalidFrame = errors.New("proxy: invalid frame")
	// ErrProtocolVersion is returned when the peer uses an unsupported protocol version.
	ErrProtocolVersion = errors.New("proxy: unsupported protocol version")
	// ErrAuthFailed is returned when authentication fails (client-side).
	ErrAuthFailed = errors.New("proxy: authentication failed")
	// ErrServerClosed is returned when the server is not running.
	ErrServerClosed = errors.New("proxy: server closed")
	// ErrClientClosed is returned when the client is not running or connection is gone.
	ErrClientClosed = errors.New("proxy: client not connected")
)

Functions

func ComputeHelloProof

func ComputeHelloProof(ourPriv, peerPub *[32]byte, macInput []byte) (string, error)

ComputeHelloProof builds Proof = base64(HMAC-SHA256(X25519(ourPriv, peerPub), macInput)). ourPriv and peerPub are raw 32-byte WireGuard Curve25519 keys.

func HelloMACInput

func HelloMACInput(h ClientHello) []byte

HelloMACInput returns the stable byte sequence covered by ClientHello.Proof. Proof itself is excluded so the client can compute the MAC before setting it.

Encoding is length-prefixed to avoid delimiter ambiguity:

version (uint32 BE) ||
len||node_id || len||relay_peer_id || len||network_id || len||public_key ||
timestamp (int64 BE)

where each len is a uint32 big-endian byte length of the following field.

func VerifyHelloProof

func VerifyHelloProof(ourPriv, peerPub *[32]byte, h ClientHello) bool

VerifyHelloProof checks Proof against the expected MAC for this hello.

Types

type AuthResult

type AuthResult struct {
	PeerID       string
	RelayPeerID  string
	NetworkID    string
	SessionScope map[string]string
}

AuthResult is produced by Authenticator after validating ClientHello.

type Authenticator

type Authenticator interface {
	ValidateClientHello(ctx context.Context, hello ClientHello) (*AuthResult, error)
}

Authenticator validates ClientHello after MsgHello (spec §10.2).

type BackoffConfig

type BackoffConfig struct {
	Initial time.Duration
	Max     time.Duration
	Factor  float64 // >= 1.0; multiplier applied after each failed attempt
}

BackoffConfig controls reconnect delays on the client.

type Client

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

Client maintains a TCP/TLS session to the relay (spec §9.1).

Example
package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"time"

	"github.com/gravitl/proxy/uplink"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	c, err := uplink.NewClient(uplink.ClientOptions{
		Addr:       "relay.example.com:443",
		ServerName: "relay.example.com",
		TLSConfig:  &tls.Config{MinVersion: tls.VersionTLS12},
		HelloFactory: func() (uplink.ClientHello, error) {
			return uplink.ClientHello{
				Version: 1, NodeID: "node", RelayPeerID: "relay",
				PublicKey: "wg-pubkey", Proof: "proof",
				Timestamp: time.Now().Unix(),
			}, nil
		},
		PacketHandler: func(pkt []byte) error {
			// Inject pkt into the local WireGuard/TUN receive path.
			_ = pkt
			return nil
		},
	})
	if err != nil {
		panic(err)
	}
	_ = c.Start(ctx)
	out := []byte{0x01} // placeholder WG packet bytes
	_ = c.SendPacket(ctx, out)
	_ = c.Stop(context.Background())
	fmt.Println(c.State())
}

func NewClient

func NewClient(opts ClientOptions) (*Client, error)

NewClient validates options and returns a Client.

func (*Client) SendPacket

func (c *Client) SendPacket(ctx context.Context, pkt []byte) error

SendPacket sends a DATA frame with the current session ID (spec §9.1). Safe for concurrent use.

func (*Client) Start

func (c *Client) Start(ctx context.Context) error

Start begins the connection supervisor until ctx is cancelled or Stop is called.

func (*Client) State

func (c *Client) State() ClientState

State returns the current client state (spec §13.1).

func (*Client) Stop

func (c *Client) Stop(ctx context.Context) error

Stop cancels the client and closes the active connection.

type ClientHello

type ClientHello struct {
	Version     int    `json:"version"`
	NodeID      string `json:"node_id"`
	RelayPeerID string `json:"relay_peer_id"`
	NetworkID   string `json:"network_id,omitempty"`
	PublicKey   string `json:"public_key"` // client WireGuard public key (wgtypes base64)
	Timestamp   int64  `json:"timestamp"`
	Proof       string `json:"proof"` // base64 MAC proving possession of PublicKey's private key
}

ClientHello is serialized as JSON in MsgHello payload (spec §12). Identity is proven with WireGuard Curve25519 keys (PublicKey + Proof), not control-plane JWTs. Proof is base64(HMAC-SHA256(X25519(client_priv, gateway_pub), HelloMACInput(...))).

type ClientOptions

type ClientOptions struct {
	Addr             string
	ServerName       string
	TLSConfig        *tls.Config
	HelloFactory     func() (ClientHello, error)
	PacketHandler    func([]byte) error
	Logger           Logger
	Metrics          MetricsSink
	KeepAlivePeriod  time.Duration
	WriteTimeout     time.Duration
	ReconnectBackoff BackoffConfig
	MaxFrameSize     int
}

ClientOptions configures the TCP/TLS uplink client (spec §9.1).

type ClientState

type ClientState string

ClientState (spec §13.1).

const (
	StateDisconnected   ClientState = "disconnected"
	StateConnecting     ClientState = "connecting"
	StateTLSReady       ClientState = "tls_ready"
	StateAuthenticating ClientState = "authenticating"
	StateActive         ClientState = "active"
	StateClosing        ClientState = "closing"
	StateFailed         ClientState = "failed"
)

type FrameHeader

type FrameHeader struct {
	Version    uint8
	MsgType    uint8
	Flags      uint16
	SessionID  uint32
	PayloadLen uint32
}

FrameHeader is the 12-byte header (spec §11.1), big-endian.

type InMemoryRegistry

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

InMemoryRegistry is a thread-safe SessionRegistry with replace-on-attach semantics. If a new session is attached for an existing peer ID, the previous session's Close() is called when the previous value implements interface{ Close() error }.

func NewInMemoryRegistry

func NewInMemoryRegistry() *InMemoryRegistry

NewInMemoryRegistry returns an empty registry.

func (*InMemoryRegistry) Attach

func (r *InMemoryRegistry) Attach(peerID string, sess Session) error

Attach registers a session for peerID, replacing any existing session.

func (*InMemoryRegistry) CloseAll

func (r *InMemoryRegistry) CloseAll()

CloseAll closes every registered session and clears the registry. Used by Server.Stop so clients drop and re-HELLO after a gateway restart (closing the listener alone leaves accepted TLS conns answering PING forever).

func (*InMemoryRegistry) Detach

func (r *InMemoryRegistry) Detach(peerID string)

Detach removes a peer from the registry, whichever session is registered.

func (*InMemoryRegistry) DetachSession

func (r *InMemoryRegistry) DetachSession(peerID string, sess Session)

DetachSession removes peerID only if sess is still the registered session. A reconnecting client attaches its new session before the old session's read loop finishes unwinding, so an unconditional Detach from the old session would evict the live one and leave the peer looking session-less.

func (*InMemoryRegistry) Get

func (r *InMemoryRegistry) Get(peerID string) (Session, bool)

Get returns the session for peerID.

func (*InMemoryRegistry) Len

func (r *InMemoryRegistry) Len() int

Len returns the number of registered sessions (for metrics / tests).

func (*InMemoryRegistry) PeerIDs

func (r *InMemoryRegistry) PeerIDs() []string

PeerIDs returns the peer IDs with a registered session.

type Logger

type Logger interface {
	Debug(msg string, kv ...any)
	Info(msg string, kv ...any)
	Warn(msg string, kv ...any)
	Error(msg string, kv ...any)
}

Logger is a structured logging facade (spec §10.4).

type MetricsSink

type MetricsSink interface {
	IncCounter(name string, labels map[string]string)
	ObserveHistogram(name string, value float64, labels map[string]string)
	SetGauge(name string, value float64, labels map[string]string)
}

MetricsSink is optional telemetry (spec §10.5).

type PacketHandler

type PacketHandler interface {
	HandleInboundPacket(ctx context.Context, peerID string, pkt []byte) error
}

PacketHandler receives inbound DATA frames on the server (spec §10.1).

type Server

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

Server terminates TLS and manages framed sessions (spec §9.2).

func NewServer

func NewServer(opts ServerOptions) (*Server, error)

NewServer validates options and constructs a Server.

func (*Server) Addr

func (s *Server) Addr() net.Addr

Addr returns the bound listen address (e.g. after Start with ":0"). It is nil before Start or after Stop.

func (*Server) SendToPeer

func (s *Server) SendToPeer(ctx context.Context, peerID string, pkt []byte) error

SendToPeer sends a DATA frame to the attached peer's session.

func (*Server) SessionPeerIDs

func (s *Server) SessionPeerIDs() []string

SessionPeerIDs returns the peer IDs with an attached session, if the registry supports enumeration.

func (*Server) Start

func (s *Server) Start(ctx context.Context) error

Start listens with TLS and accepts sessions until ctx is cancelled or Stop is called.

func (*Server) Stop

func (s *Server) Stop(ctx context.Context) error

Stop closes the listener, forcibly closes all attached sessions, and waits for handlers. Session close is required: Accept() stopping alone leaves live TLS conns that still answer client PING/PONG, so clients stay StateActive and never re-HELLO the new server after a netclient soft restart (SIGHUP / pull).

type ServerOptions

type ServerOptions struct {
	ListenAddr      string
	TLSConfig       *tls.Config
	Authenticator   Authenticator
	PacketHandler   PacketHandler
	SessionRegistry SessionRegistry
	Logger          Logger
	Metrics         MetricsSink
	KeepAlivePeriod time.Duration
	WriteTimeout    time.Duration
	MaxFrameSize    int
}

ServerOptions configures the TCP/TLS uplink server (spec §9.2).

type Session

type Session interface {
	PeerID() string
	State() SessionState
}

Session is the handle stored per attached peer (spec §14).

type SessionRegistry

type SessionRegistry interface {
	Attach(peerID string, sess Session) error
	Get(peerID string) (Session, bool)
	Detach(peerID string)
}

SessionRegistry tracks peer ID to active session (spec §10.3).

type SessionState

type SessionState string

SessionState (spec §13.2).

const (
	SessionPending       SessionState = "pending"
	SessionAuthenticated SessionState = "authenticated"
	SessionAttached      SessionState = "attached"
	SessionStale         SessionState = "stale"
	SessionClosed        SessionState = "closed"
)

Jump to

Keyboard shortcuts

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