relay

package
v1.3.9 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: GPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

Package relay provides decentralized peer-to-peer traffic forwarding through Hysteria 2 tunnels.

Every node is equal — each can run both an hy2 server (accepting peers) and multiple hy2 clients (connecting to peers). Peers can route traffic through each other's network.

Peer discovery is local by default (only directly connected peers). Nested discovery (seeing a peer's peers) is opt-in per peer.

Index

Constants

View Source
const LatencyHistoryCap = 120

LatencyHistoryCap bounds the per-peer ring. With ~5 s probe cadence this gives ~10 minutes of history per peer, ~1.5 KB/peer at most — trivial even with dozens of peers.

View Source
const MinCompatVersion = "1.3.0"

MinCompatVersion is the minimum peer version we can work with. Peers below this version are marked incompatible: relay blocked, nested disabled.

View Source
const ProtocolVersion = 3

ProtocolVersion is an integer identifying the inter-node wire format generation. Bumped (independently of NodeVersion semver) whenever a breaking peer-protocol change ships. Older instances that see a remote peer with a HIGHER ProtocolVersion treat it as opaque: hide nested sub-peers entirely (they may use frames we can't parse) and mark direct peers as unsupported. Newer instances accept any older peer and just downgrade behavior locally to the older feature set. v1.3.x ships protocol v3 — v1 = pre-1.0, v2 = 1.0–1.2.

View Source
const (

	// StreamAPI is the well-known relay address the web UI's remote proxy
	// uses to reach a peer's own API server. It bypasses the normal
	// loopback-rewrite logic (which would redirect 127.0.0.1 to the Docker
	// gateway and break this case).
	StreamAPI = "_relay_api_:0"
)

Variables

View Source
var ErrNotHy2scale = fmt.Errorf("relay: remote is not hy2scale")

ErrNotHy2scale indicates the remote is a plain hy2 server without relay protocol.

View Source
var NodeTunCapable bool

NodeTunCapable is set by the app layer at startup if this node can handle exit-side TUN (has NET_ADMIN + /dev/net/tun).

View Source
var NodeVersion = "1.0.0"

NodeVersion is the version string sent during peer registration. Set by the app package at init time.

Functions

func BuildViaAddr added in v1.3.6

func BuildViaAddr(remaining, addr string) string

BuildViaAddr constructs a via-encoded relay address used as the "target" passed to a first-hop DialUDP / DialTCP. `remaining` is the slash-joined chain of hops AFTER the immediate first hop; `addr` is the final target ("host:port"). The first hop's handler parses with ParseVia and forwards down the chain.

func IsRelayStream

func IsRelayStream(addr string) bool

IsRelayStream returns true if addr is a relay internal stream.

func ParseBridgeAddr added in v1.3.0

func ParseBridgeAddr(addr string) (string, string, bool)

HandleBridgeAddr processes a bridge-tagged address from the exit node. Returns (targetAddr, bridgeID, isBridged).

func ParseVia added in v1.3.6

func ParseVia(reqAddr string) (chain, targetAddr string, ok bool)

ParseVia decodes a via-encoded multi-hop relay target address. Returns (chain, targetAddr, true) when reqAddr has the via-prefix. `chain` is the slash-joined remaining hops (e.g. "aub/cn/2400"); `targetAddr` is the final destination ("host:port"). The receiving peer should split `chain` on "/" and forward via its first hop with the remainder re-encoded. Exported so the application's UDP outbound callback (nodeOutbound.UDP in internal/app/app.go) can implement multi-hop UDP routing, mirroring TCP's handleVia.

Types

type BidirectionalIdleTrigger added in v1.3.1

type BidirectionalIdleTrigger struct {
	Timeout time.Duration
}

BidirectionalIdleTrigger fires when *both* directions have been silent for Timeout. Replaces the old "no Read while Writing" rule, which wrongly fired during legitimate one-way uploads (e.g. HTTP POST while the server hadn't responded yet) and slow downloads. Bidirectional silence remains a strong signal of a truly stuck stream — the same signal the cn-xinchang storm fixes were aimed at.

func (BidirectionalIdleTrigger) ShouldRebind added in v1.3.1

func (t BidirectionalIdleTrigger) ShouldRebind(s ConnState) (bool, string)

ShouldRebind implements RebindTrigger.

type ConnState added in v1.3.1

type ConnState struct {
	LastRead  int64
	LastWrite int64
	ViaPath   []string // current via path; len==0 means direct (single-hop bridge)
}

ConnState captures the metrics a RebindTrigger evaluates. Values are unix nanoseconds; zero means "no activity yet".

type IPStatus added in v1.2.0

type IPStatus struct {
	Addr      string `json:"addr"`
	Status    string `json:"status"` // "online", "offline", "mismatch", "native"
	LatencyMs int    `json:"latency_ms,omitempty"`
}

IPStatus represents per-IP connection info.

type LatencySample added in v1.3.4

type LatencySample struct {
	At time.Time `json:"at"`
	Ms int       `json:"ms"`
}

LatencySample is one entry in the per-peer time series. ms < 0 means the probe failed; the operator can render those as gaps. Capped to LatencyHistoryCap entries — older samples slide off the head.

type Node

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

func NewNode

func NewNode(name string, exitNode bool) *Node

NewNode creates a node.

func (*Node) AddPeerConn added in v1.2.0

func (n *Node) AddPeerConn(name string, client hyclient.Client, addr, status string)

AddPeerConn adds an extra QUIC connection to an existing peer.

func (*Node) AttachNative

func (n *Node) AttachNative(ctx context.Context, peerName string, client hyclient.Client) error

AttachTo connects to a remote node via an hy2 client, registers, and handles dial requests from the remote. Blocks until ctx or connection ends. AttachTo connects to a remote node via an hy2 client, registers, and handles dial requests from the remote. Blocks until ctx or connection ends. Returns the remote's actual node ID via the onID callback (if non-nil). AttachNative registers a plain hy2 server (no relay protocol) as a peer. Only DialTCP works (direct proxy). No nested discovery, no control stream.

func (*Node) AttachTo

func (n *Node) AttachTo(ctx context.Context, peerName string, client hyclient.Client, onID func(string)) error

func (*Node) BlockPeer added in v1.2.2

func (n *Node) BlockPeer(name string)

BlockPeer prevents a peer from registering (inbound connections rejected).

func (*Node) Bridges added in v1.3.0

func (n *Node) Bridges() *bridgeManager

PeerCtx returns the context for a peer, canceled when the peer disconnects. Used by idle timeout to detect dead relay streams immediately. Bridges returns the bridge manager for stream rebinding.

func (*Node) ConnectedPeerNames

func (n *Node) ConnectedPeerNames() []string

ConnectedPeerNames returns names of all connected peers (outbound with active client).

func (*Node) DialIPTun added in v1.2.2

func (n *Node) DialIPTun(ctx context.Context, peerName string) (net.Conn, error)

DialIPTun opens a bidirectional IP packet tunnel stream to a peer. The stream uses 2-byte length-prefixed framing for raw IP packets.

func (*Node) DialTCP

func (n *Node) DialTCP(ctx context.Context, peerName string, addr string) (net.Conn, error)

func (*Node) DialTCPBridged added in v1.3.0

func (n *Node) DialTCPBridged(peerName, addr string) (net.Conn, string, error)

DialTCPBridged is like DialTCP but returns a connection that survives QUIC reconnects by rebinding to new streams transparently.

func (*Node) DialTCPIdx added in v1.3.0

func (n *Node) DialTCPIdx(ctx context.Context, peerName string, addr string, clientIdx int) (net.Conn, error)

DialTCPIdx dials TCP through a specific QUIC client index (for bond path pinning). Index -1 means round-robin (same as DialTCP).

func (*Node) DialUDP added in v1.2.2

func (n *Node) DialUDP(ctx context.Context, peerName string, addr string) (net.Conn, error)

DialTCP dials addr through a directly connected peer's network. DialUDP opens a UDP session through a peer's Hysteria2 QUIC connection. Returns a net.Conn-compatible wrapper for a single destination address.

func (*Node) DialUDPVia added in v1.3.6

func (n *Node) DialUDPVia(ctx context.Context, path []string, addr string) (net.Conn, error)

DialUDPVia opens a multi-hop UDP relay connection. `path` is the chain of peer names [hop1, hop2, ..., exit]; `addr` is the final destination ("host:port"). For single-hop, this is DialUDP. For multi-hop, the target address is via-encoded and each intermediate hop's nodeOutbound.UDP re-encodes and forwards to the next hop — the UDP analog of TCP's DialVia.

func (*Node) DialVia

func (n *Node) DialVia(ctx context.Context, path []string, addr string) (net.Conn, error)

DialVia dials addr through a chain of peers. path = ["au", "jp"] means: this node → au → jp → internet.

func (*Node) DialViaBridged added in v1.3.0

func (n *Node) DialViaBridged(ctx context.Context, path []string, addr string) (net.Conn, string, error)

DialViaBridged dials addr through a chain of peers with bridge support. Returns a bridgedConn that survives first-hop QUIC reconnects.

func (*Node) DisconnectPeer added in v1.2.2

func (n *Node) DisconnectPeer(name string)

DisconnectPeer forcibly disconnects a peer (both inbound and outbound). Closes the QUIC client to kill all active streams through this peer, ensuring no connections hang in a black hole after reconnection.

func (*Node) GetAddrLatencies added in v1.3.0

func (n *Node) GetAddrLatencies(peerName string) map[string]int

GetAddrLatencies returns per-address latency for a multi-addr peer.

func (*Node) GetLatency

func (n *Node) GetLatency(peerName string) int

GetLatency returns stored latency for a peer. For multi-addr peers, returns average of all addr latencies. -1 if unknown.

func (*Node) GetStats

func (n *Node) GetStats() Stats

GetStats returns current traffic statistics.

func (*Node) HandleStream

func (n *Node) HandleStream(ctx context.Context, reqAddr string, stream net.Conn)

HandleStream routes relay streams from the hy2 server's Outbound.TCP.

func (*Node) HasPeer

func (n *Node) HasPeer(name string) bool

HasPeer checks if a peer is connected.

func (*Node) HealthSnapshots added in v1.3.4

func (n *Node) HealthSnapshots() []PeerHealthSnapshot

HealthSnapshots returns the QUIC-health proxy axis for every peer.

func (*Node) IsExit

func (n *Node) IsExit() bool

IsExit returns whether this node is an exit node.

func (*Node) IsInbound added in v1.0.5

func (n *Node) IsInbound(name string) bool

IsInbound returns true if the peer is connected inbound (no client).

func (*Node) IsNestedEnabled

func (n *Node) IsNestedEnabled(peerName string) bool

IsNestedEnabled returns whether nested discovery is enabled for a peer.

func (*Node) IsPeerBlocked added in v1.2.2

func (n *Node) IsPeerBlocked(name string) bool

IsPeerBlocked checks if a peer is blocked.

func (*Node) IsPeerTunCapable added in v1.3.0

func (n *Node) IsPeerTunCapable(peerName string) bool

IsPeerTunCapable checks if a peer supports exit-side TUN. For multi-hop paths ("A/B/C") it walks each segment: first hop must be a direct peer, then each subsequent segment is looked up in the sub-peer cache under the qualified parent path ("A", then "A/B", ...). This disambiguates cases where a nested peer shares a name with a direct peer of the local node (e.g. HUB has a direct "2400" AND another "2400" exists as a child of some intermediate peer).

func (*Node) LatencySnapshots added in v1.3.4

func (n *Node) LatencySnapshots() []PeerLatencySnapshot

LatencySnapshots returns the latency-time-series axis for every peer. Includes the current point value (matching what the topology API surfaces) plus the rolling history ring.

func (*Node) Name

func (n *Node) Name() string

Name returns the node's name.

func (*Node) NativeMap

func (n *Node) NativeMap() map[string]bool

NativeMap returns a map of peer names to their native status.

func (*Node) NodeID added in v1.3.1

func (n *Node) NodeID() string

NodeID returns the local stable identifier configured via SetNodeID.

func (*Node) PathRates added in v1.3.0

func (n *Node) PathRates() map[string]PeerTraffic

PathRates returns per-qualified-path traffic rates, keyed identically to cfg.Peers (e.g. "au", "au/au-r1", "au/au-r1/au-r1-a"). Only paths with more than one hop are populated — single-hop flows are covered by the per-peer counter returned from PeerRates.

func (*Node) PeerConflicts added in v1.3.0

func (n *Node) PeerConflicts() map[string]string

PeerConflicts returns a map of client addr → conflicting name.

func (*Node) PeerConnCount added in v1.2.0

func (n *Node) PeerConnCount(name string) int

PeerConnCount returns the number of QUIC connections to a peer.

func (*Node) PeerCtx added in v1.3.0

func (n *Node) PeerCtx(peerName string) context.Context

func (*Node) PeerIPStatuses added in v1.2.0

func (n *Node) PeerIPStatuses(name string) []IPStatus

PeerIPStatuses returns per-IP status for a peer.

func (*Node) PeerRates

func (n *Node) PeerRates() map[string]PeerTraffic

PeerRates returns per-peer traffic rates.

func (*Node) Peers

func (n *Node) Peers() []PeerInfo

Peers returns directly connected peers.

func (*Node) PeersAlt added in v1.3.1

func (n *Node) PeersAlt() []PeerInfo

PeersAlt returns a snapshot of the secondary-slot peer infos. Pairs with Peers() for the primary side. Topology API merges the two so a mutually-dialed peer surfaces as both top-level outbound and inbound child of self in the rendered tree.

func (*Node) PeersOf

func (n *Node) PeersOf(peerName string) ([]PeerInfo, error)

PeersOf returns a peer's peers (flat list). Used by the prober for cache.

func (*Node) PeersOfCacheKeys added in v1.3.8

func (n *Node) PeersOfCacheKeys() []string

PeersOfCacheKeys snapshots every qualified-path key currently held in peersOfCache (both bare direct-peer names and the slash-joined deep keys the api server's walkAndCache populates). Callers that want to enumerate the full set of topology-visible paths beyond cfg.Peers — e.g. the PathInfoExpand history recorder, which used to miss any qualified path the operator hadn't explicitly toggled even though the topology graph rendered it — read this list and union it with their own roots.

func (*Node) PeersOfCached

func (n *Node) PeersOfCached(peerName string) ([]PeerInfo, bool)

PeersOfCached returns cached peer list from the last prober cycle.

func (*Node) PeersOfRaw added in v1.3.0

func (n *Node) PeersOfRaw(peerName string) ([]byte, error)

PeersOfRaw returns the raw JSON from a peer's streamListPeers response. Used by the API server to get rich tree data (with children).

func (*Node) PeersOfVia

func (n *Node) PeersOfVia(ctx context.Context, path []string) ([]PeerInfo, error)

PeersOfVia returns a peer's peers through a multi-hop path. path = ["vm", "au"] means: query au's peers by routing through vm.

func (*Node) PingPeer

func (n *Node) PingPeer(name string) time.Duration

PingPeer measures round-trip latency to a peer. Returns -1 if unreachable. Non-blocking: uses a 3-second timeout.

func (*Node) RebindSnapshots added in v1.3.4

func (n *Node) RebindSnapshots() []PeerRebindSnapshot

RebindSnapshots returns the rebind-check counter axis for every peer.

func (*Node) SetAPIHandler added in v1.3.0

func (n *Node) SetAPIHandler(handler func(stream net.Conn))

SetAPIHandler registers a handler for incoming _relay_api_ streams (remote web-UI proxy tunneling).

func (*Node) SetAPIStreamWrap added in v1.3.7

func (n *Node) SetAPIStreamWrap(fn func(stream net.Conn, authID string) net.Conn)

SetAPIStreamWrap registers the app-layer wrapper that tags a peer-relay API stream with its auth ID so the api server can apply the RelayAdminPassthrough bypass. Required so streams that arrive via the s2c-ctrl dial-request path (inbound-only peer) carry the same identification the direct nodeOutbound.TCP path already wraps for.

func (*Node) SetExit

func (n *Node) SetExit(exit bool)

SetExit updates the exit node flag.

func (*Node) SetIPTunHandler added in v1.2.2

func (n *Node) SetIPTunHandler(handler func(peerName string, stream net.Conn))

SetIPTunHandler registers a handler for incoming IP tunnel streams from peers.

func (*Node) SetLatency

func (n *Node) SetLatency(peerName string, ms int)

SetLatency stores a latency measurement for a peer (called by prober or inbound report).

func (*Node) SetListPeersFunc added in v1.3.0

func (n *Node) SetListPeersFunc(fn func() []byte)

SetListPeersFunc registers a function that returns rich peer list JSON (with children). Used by handleListPeers so both outbound and inbound nested discovery return identical data.

func (*Node) SetName

func (n *Node) SetName(name string)

SetName updates the node's name.

func (*Node) SetNestedDiscovery

func (n *Node) SetNestedDiscovery(peerName string, enabled bool)

SetNestedDiscovery enables/disables nested peer discovery for a peer.

func (*Node) SetNodeID added in v1.3.1

func (n *Node) SetNodeID(id string)

SetNodeID is called by the app layer at startup (and whenever the NodeID is regenerated/rebound) so register handshakes can transmit our stable identifier. Empty until set — keep the first call early in App init, before any connectLoop fires.

func (*Node) SetOnInboundIdentity added in v1.3.6

func (n *Node) SetOnInboundIdentity(handler func(declaredName, nodeID, remoteAddrIP string))

SetOnInboundIdentity registers a callback that fires when an inbound peer completes its register-stream meta exchange. The app layer uses this to detect when an inbound peer that previously connected from the same source IP now reports a different NodeID, and to propagate the rename through cfg.Peers / cfg.Proxies.ExitVia / cfg.Users.ExitVia (the inbound counterpart of AttachTo's outbound rename). See docs/peer-identity-sync-bugs.md Bug B.

func (*Node) SetPeerConflict added in v1.3.0

func (n *Node) SetPeerConflict(clientAddr string, conflictName string)

SetPeerConflict records a name conflict for a client address. The conflict is tracked separately from the peer entry to avoid marking legitimate peers as conflicting.

func (*Node) SetPeersOfCache added in v1.2.0

func (n *Node) SetPeersOfCache(peerName string, peers []PeerInfo)

SetPeersOfCache sets the cached peer list for a given peer name. Called by the API sub-peers updater to share nested topology data.

func (*Node) StartLatencyProber

func (n *Node) StartLatencyProber(ctx context.Context)

StartLatencyProber periodically pings outbound peers and caches their peer lists.

func (*Node) StartRateTicker

func (n *Node) StartRateTicker(ctx context.Context)

StartRateTicker updates per-second rate counters. Call in a goroutine.

func (*Node) UnblockPeer added in v1.2.2

func (n *Node) UnblockPeer(name string)

UnblockPeer allows a peer to register again.

type PeerHealthSnapshot added in v1.3.4

type PeerHealthSnapshot struct {
	Name      string `json:"name"`
	Direction string `json:"direction"` // "outbound" / "inbound"
	PingOK    uint64 `json:"ping_ok"`
	PingFail  uint64 `json:"ping_fail"`
	FailCount int32  `json:"fail_count"` // consecutive failures (existing field)
	TxBytes   uint64 `json:"tx_bytes"`
	RxBytes   uint64 `json:"rx_bytes"`
}

PeerHealthSnapshot is one row of /api/diag/peer-health (the ping-ok/fail axis — application-level proxy for QUIC-tunnel health since the hysteria `Client` interface doesn't expose raw quic-go retransmit / pacer-bandwidth fields).

type PeerInfo

type PeerInfo struct {
	Name      string `json:"name"`
	ExitNode  bool   `json:"exit_node"`
	Direction string `json:"direction"`
	Native    bool   `json:"native"`
	LatencyMs int    `json:"latency_ms"`
	Version   string `json:"version,omitempty"`
	// NodeID is the remote's reported stable identifier (from peerMeta).
	// Used by the bidirectional-slot logic to prove "this incoming
	// register is from the same logical instance as the existing primary,
	// not an impostor". Empty for old peers — that case falls back to
	// legacy single-slot replace behavior.
	NodeID       string `json:"node_id,omitempty"`
	Incompatible bool   `json:"incompatible,omitempty"`
	Conflict     bool   `json:"conflict,omitempty"`
	TunCapable   bool   `json:"tun_capable,omitempty"`
	// PV is the remote's wire-protocol generation. Higher than ours
	// means the remote may speak frames we can't parse — see
	// ProtocolVersion comment. Forwarded to the topology API so the
	// frontend can hide nested sub-peers from such links and render
	// the direct connection itself as `unsupported`.
	PV          int  `json:"pv,omitempty"`
	Unsupported bool `json:"unsupported,omitempty"`
}

PeerInfo describes a connected peer.

type PeerLatencySnapshot added in v1.3.4

type PeerLatencySnapshot struct {
	Name      string          `json:"name"`
	Direction string          `json:"direction"`
	LatencyMs int             `json:"latency_ms"` // most recent point value
	History   []LatencySample `json:"history"`
}

PeerLatencySnapshot is one row of /api/diag/peer-latency — the rolling time series of recent latency samples per peer. Ring is LatencyHistoryCap entries (~10 min at 5 s probe cadence).

type PeerRebindSnapshot added in v1.3.4

type PeerRebindSnapshot struct {
	Name         string `json:"name"`
	Direction    string `json:"direction"`
	RebindChecks uint64 `json:"rebind_checks"`
}

PeerRebindSnapshot is one row of /api/diag/peer-rebinds — counts of [via] rebind-probe arrivals at this peer's tunnel since session start. Spikes correlate with chronic per-stream disruption on a QUIC connection that hasn't fully dropped.

type PeerTraffic

type PeerTraffic struct {
	Name   string
	TxRate uint64
	RxRate uint64
}

PeerTraffic holds per-peer traffic info.

type RebindOpts added in v1.3.1

type RebindOpts struct {
	ViaPath []string // if non-nil, replace bridgedConn.viaPath for this rebind
}

RebindOpts allows callers to override defaults when explicitly invoking rebind. Currently only the via path can be overridden — left as the sole member so the type is the obvious extension point for future cross-path migration work.

type RebindTrigger added in v1.3.1

type RebindTrigger interface {
	ShouldRebind(s ConnState) (yes bool, reason string)
}

RebindTrigger decides whether a bridged stream should be torn down and rebound. Triggers run on the writer hot path; ShouldRebind must be O(1). Implementations may consider any field of ConnState; new fields will be added without breaking compatibility.

type Stats

type Stats struct {
	TxBytes     uint64 // total uploaded
	RxBytes     uint64 // total downloaded
	TxRate      uint64 // bytes/sec upload (snapshot)
	RxRate      uint64 // bytes/sec download (snapshot)
	Conns       int    // active stream count
	ExitClients int    // peers using this node as exit
}

Node is a unified relay endpoint. It can accept peers (hy2 server side) and connect to peers (hy2 client side) simultaneously. Stats holds traffic counters.

Jump to

Keyboard shortcuts

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