relay

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package relay implements a DERP-style relay fallback for peers that cannot establish a direct UDP path via NAT hole punching (e.g. symmetric NATs).

The relay is a volunteer mesh peer — never the coord server. Clients connect to it over TCP and open a Noise IK-encrypted channel (X25519 + SHA-256 + ChaCha20-Poly1305) using the relay's pinned static key. The handshake's encrypted payload carries the *remote peer's* X25519 public key; the relay derives the rendezvous channel from that key plus the initiator key it just authenticated (ChannelID), and splices the two connections that arrive on the same channel from opposite directions.

Because the channel is derived from the authenticated initiator key rather than from a client-supplied channel ID, a third party who merely knows both peers' public keys cannot join or hijack a pair's channel — only connections authenticated as one of the two peers map onto it. (A malicious relay can still refuse service or splice wrongly, but it learns nothing and no honest relay will pair an impostor.)

The relay is blind: payloads flowing over a channel are the peers' own data-plane datagrams, which are already end-to-end encrypted by the session layer (peer-to-peer Noise IK + ChaCha20-Poly1305). The relay learns only endpoints, volumes, and timing.

Wire protocol (client → relay is the Noise IK initiator):

frame 1: Noise IK message_1, payload = 32-byte remote peer X25519 key
frame 2: Noise IK message_2 from the relay, empty payload
thereafter: length-prefixed frames carrying encrypted messages

Every frame on the wire is [uint16 big-endian length][ciphertext].

This package holds the MIT-licensed relay client (Dial/Conn/Proxy) and the wire protocol. The relay server lives in the top-level relay package.

Index

Constants

View Source
const ChannelIDSize = 16

ChannelIDSize is the size of a derived relay channel identifier in bytes.

Variables

NoiseSuite is the Noise cipher suite used by the relay channel protocol (X25519 + ChaCha20-Poly1305 + SHA-256). It is exported so the relay server (top-level relay package) speaks exactly the same protocol.

Functions

func ChannelID

func ChannelID(a, b [32]byte) [ChannelIDSize]byte

ChannelID derives the 16-byte rendezvous channel ID for a key pair. It is symmetric: ChannelID(a, b) == ChannelID(b, a), so both peers of a failing NAT pair arrive at the same channel without any extra signalling.

func ReadFrame

func ReadFrame(r io.Reader) ([]byte, error)

ReadFrame reads one length-prefixed frame.

func WriteFrame

func WriteFrame(w io.Writer, msg []byte) error

WriteFrame writes msg with a 2-byte big-endian length prefix.

Types

type Conn

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

Conn is a Noise IK-encrypted message stream to a relay, paired with the remote peer's connection on the same channel.

func Dial

func Dial(ctx context.Context, addr string, relayX25519 [32]byte, id *crypto.Identity, remotePeerKey [32]byte) (*Conn, error)

Dial connects to the relay service at addr ("host:port", TCP), completes a Noise IK handshake against the relay's pinned X25519 static key, and requests a channel to remotePeerKey — the X25519 public key of the peer this node is trying to reach. The relay derives the rendezvous channel from the caller's authenticated static key and remotePeerKey; the returned Conn starts delivering messages once that peer has also connected.

func (*Conn) Close

func (c *Conn) Close() error

Close terminates the underlying TCP connection.

func (*Conn) ReadMessage

func (c *Conn) ReadMessage() ([]byte, error)

ReadMessage reads and decrypts the next frame from the relay.

func (*Conn) WriteMessage

func (c *Conn) WriteMessage(b []byte) error

WriteMessage encrypts b and writes it as one length-prefixed frame. Concurrent calls are serialised: both encryption and the write happen under the write mutex so the cipher state is never raced.

type Proxy

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

Proxy bridges a relay channel to the local data plane. It listens on a loopback UDP socket; datagrams the daemon sends to the proxy address are framed and forwarded over the relay channel, and datagrams arriving from the relay channel are injected into the local data-plane socket.

To the dispatcher, the relay path looks like an ordinary peer endpoint: the peer table entry's endpoint is simply set to the proxy's local address.

func NewProxy

func NewProxy(stream *Conn, target *net.UDPAddr) (*Proxy, error)

NewProxy creates a Proxy that forwards datagrams between stream (a paired relay channel from Dial) and the local data-plane socket at target (typically "127.0.0.1:<daemon UDP port>").

The proxy starts forwarding immediately. Set the peer's endpoint to LocalAddr() and initiate the Noise handshake through it.

func (*Proxy) Close

func (p *Proxy) Close() error

Close tears down the proxy and the relay channel.

func (*Proxy) LocalAddr

func (p *Proxy) LocalAddr() netip.AddrPort

LocalAddr returns the loopback address the daemon should use as the peer's endpoint while the relay path is active.

Jump to

Keyboard shortcuts

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