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 ¶
- Constants
- Variables
- func ComputeHelloProof(ourPriv, peerPub *[32]byte, macInput []byte) (string, error)
- func HelloMACInput(h ClientHello) []byte
- func VerifyHelloProof(ourPriv, peerPub *[32]byte, h ClientHello) bool
- type AuthResult
- type Authenticator
- type BackoffConfig
- type Client
- type ClientHello
- type ClientOptions
- type ClientState
- type FrameHeader
- type InMemoryRegistry
- func (r *InMemoryRegistry) Attach(peerID string, sess Session) error
- func (r *InMemoryRegistry) CloseAll()
- func (r *InMemoryRegistry) Detach(peerID string)
- func (r *InMemoryRegistry) DetachSession(peerID string, sess Session)
- func (r *InMemoryRegistry) Get(peerID string) (Session, bool)
- func (r *InMemoryRegistry) Len() int
- func (r *InMemoryRegistry) PeerIDs() []string
- type Logger
- type MetricsSink
- type PacketHandler
- type Server
- type ServerOptions
- type Session
- type SessionRegistry
- type SessionState
Examples ¶
Constants ¶
const ( MsgHello uint8 = iota + 1 MsgHelloAck MsgData MsgPing MsgPong MsgClose MsgError )
Message types (spec §11.2).
const (
// DefaultMaxFrameSize caps payload length (single frame) to limit memory use.
DefaultMaxFrameSize = 65536
)
const ProtocolVersion uint8 = 1
Protocol version for Phase 1 framing.
Variables ¶
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 ¶
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())
}
Output:
func NewClient ¶
func NewClient(opts ClientOptions) (*Client, error)
NewClient validates options and returns a Client.
func (*Client) SendPacket ¶
SendPacket sends a DATA frame with the current session ID (spec §9.1). Safe for concurrent use.
func (*Client) Start ¶
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).
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 ¶
Addr returns the bound listen address (e.g. after Start with ":0"). It is nil before Start or after Stop.
func (*Server) SendToPeer ¶
SendToPeer sends a DATA frame to the attached peer's session.
func (*Server) SessionPeerIDs ¶
SessionPeerIDs returns the peer IDs with an attached session, if the registry supports enumeration.
func (*Server) Start ¶
Start listens with TLS and accepts sessions until ctx is cancelled or Stop is called.
func (*Server) Stop ¶
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" )