socket

package
v0.0.0-...-28e5946 Latest Latest
Warning

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

Go to latest
Published: Jun 4, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package socket implements iroh's "magic socket": a single net.PacketConn, driven by quic-go, that multiplexes datagrams across several transports (direct UDP, relay, custom). Because quic-go addresses paths with net.Addr, each non-IP path is represented by a synthetic IPv6 Unique Local Address (RFC 4193) from a private range. These mapped addresses are an internal indirection only — they are never sent on the wire — but the byte scheme matches the Rust implementation (iroh/src/socket/mapped_addrs.rs) for cross-referencing.

Index

Examples

Constants

View Source
const (
	// IPv6RttAdvantage is how much lower an IPv6 path's biased RTT is made,
	// expressing a default preference for IPv6 over IPv4.
	IPv6RttAdvantage = 3 * time.Millisecond

	// RttSwitchingMin is the minimum biased-RTT improvement required to switch
	// to a different path in the same tier. It prevents flapping under jitter.
	RttSwitchingMin = 5 * time.Millisecond

	// GoodEnoughLatency is the RTT at or under which a direct path is considered
	// good enough that the actor does not try to upgrade to a better path.
	GoodEnoughLatency = 10 * time.Millisecond
)

Path-selection bias constants. These match the Rust BiasedRttPathSelector (iroh/src/socket/biased_rtt_path_selector.rs:18,22) and remote_state.rs:55.

View Source
const (
	// MaxNonRelayPaths is the maximum number of non-relay paths kept per remote.
	MaxNonRelayPaths = 30

	// MaxInactiveNonRelayPaths is the maximum number of inactive (previously
	// open, now closed) non-relay paths kept per remote.
	MaxInactiveNonRelayPaths = 10
)

Path-state pruning limits. These bound the number of candidate paths an actor keeps per remote so the set cannot grow without limit. Relay paths are never counted or pruned. Values match the Rust reference (iroh/src/socket/remote_map/remote_state/path_state.rs:18,23).

View Source
const (
	// HeartbeatInterval is how often the actor wakes to keep paths alive and
	// re-evaluate path selection. remote_state.rs HEARTBEAT_INTERVAL / socket.rs.
	HeartbeatInterval = 5 * time.Second

	// UpgradeInterval is how often the actor tries to upgrade to a better path
	// even when a working non-relay route exists. remote_state.rs:66.
	UpgradeInterval = 60 * time.Second

	// HolepunchAttemptsInterval throttles hole-punch attempts when the NAT
	// candidate set has not changed. remote_state.rs:52.
	HolepunchAttemptsInterval = 5 * time.Second

	// PathMaxIdleTimeout is the idle timeout for a non-relay path.
	// iroh/src/socket.rs PATH_MAX_IDLE_TIMEOUT.
	PathMaxIdleTimeout = 15 * time.Second

	// RelayPathMaxIdleTimeout is the idle timeout for a relay path.
	// iroh/src/socket.rs RELAY_PATH_MAX_IDLE_TIMEOUT.
	RelayPathMaxIdleTimeout = 30 * time.Second

	// ActorMaxIdleTimeout is how long an actor with no connections stays alive
	// before it exits and deregisters. remote_state.rs:74.
	ActorMaxIdleTimeout = 60 * time.Second
)

Actor timing constants. These match the Rust reference (iroh/src/socket/remote_map/remote_state.rs:52,66,74 and socket.rs).

View Source
const PathBroadcastCapacity = 8

PathBroadcastCapacity is the per-subscriber buffer capacity for path events. A subscriber that falls more than this many events behind is told how many it missed via a PathEventLagged event rather than silently dropping. It matches the Rust BROADCAST_CAPACITY (iroh/src/socket/remote_map/remote_state/path_watcher.rs:50).

Variables

View Source
var ErrExtensionNotNegotiated = errors.New("socket: QUIC extension not negotiated (qng X1/X2/X3 gate)")

ErrExtensionNotNegotiated is returned by hole-punching and other operations that depend on a QUIC extension that is not negotiated on an active connection. Endpoint defaults advertise qng multipath; QNT/DISCO hole-punching is still gated separately.

Functions

This section is empty.

Types

type Addr

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

Addr is the transport-level address of a network path, internal to the magic socket. It is one of three kinds — IP, relay, or custom — mirroring the Rust transports::Addr enum (iroh/src/socket/transports.rs:795). The zero Addr is an unspecified IPv6 IP address, matching Rust's Default (transports.rs:830).

An Addr is never sent on the wire; it is the magic socket's own routing key.

func CustomAddr

func CustomAddr(c netaddr.CustomAddr) Addr

CustomAddr returns an Addr for a custom-transport path.

func IPAddr

func IPAddr(ap netip.AddrPort) Addr

IPAddr returns an Addr for a direct IP path. The address is canonicalized so an IPv4-mapped IPv6 address becomes a plain IPv4 address, matching Rust's SocketAddr -> Addr conversion (iroh/src/socket/transports.rs:825).

func RelayAddr

func RelayAddr(url netaddr.RelayURL, eid key.EndpointID) Addr

RelayAddr returns an Addr for a relay path reaching eid through url.

func (Addr) Custom

func (a Addr) Custom() (netaddr.CustomAddr, bool)

Custom returns the custom address and true if a is an AddrCustom.

func (Addr) IP

func (a Addr) IP() (netip.AddrPort, bool)

IP returns the IP socket address and true if a is an AddrIP.

func (Addr) Kind

func (a Addr) Kind() AddrKind

Kind reports which variant a is.

func (Addr) Relay

func (a Addr) Relay() (netaddr.RelayURL, key.EndpointID, bool)

Relay returns the relay URL, endpoint id, and true if a is an AddrRelay.

func (Addr) String

func (a Addr) String() string

String renders a in a stable "kind:value" form. It is suitable as a map key (two Addrs are equal iff their String values are equal) and for diagnostics. It mirrors the Rust transports::Addr Display (iroh/src/socket/transports.rs).

type AddrKind

type AddrKind int

AddrKind tags the variant of an Addr.

const (
	// AddrIP is a direct IP address path.
	AddrIP AddrKind = iota
	// AddrRelay is a relay path, identified by a relay URL and endpoint id.
	AddrRelay
	// AddrCustom is a custom-transport path.
	AddrCustom
)

type AddrMap

type AddrMap[K comparable, V comparable] struct {
	// contains filtered or unexported fields
}

AddrMap is a bidirectional map between a key K and a mapped address of value type V, generating a new mapped address on first lookup of a key. It is the Go analog of the Rust AddrMap.

func NewAddrMap

func NewAddrMap[K comparable, V comparable](gen func() V, addrOf func(V) netip.Addr) *AddrMap[K, V]

NewAddrMap returns an AddrMap whose missing keys are filled with gen(), keyed in reverse by addrOf(value).

func (*AddrMap[K, V]) Get

func (m *AddrMap[K, V]) Get(key K) V

Get returns the mapped address for key, generating and recording one if it does not yet exist.

func (*AddrMap[K, V]) Lookup

func (m *AddrMap[K, V]) Lookup(addr netip.Addr) (K, bool)

Lookup returns the key that maps to addr, if any.

type BiasedRttPathSelector

type BiasedRttPathSelector struct{}

BiasedRttPathSelector is the default PathSelector. It sorts paths by (tier, biased RTT): the primary tier always beats the backup tier, and within a tier the lowest biased RTT wins. IPv6 paths receive a IPv6RttAdvantage bias. Switching within a tier requires the candidate's biased RTT to be at least RttSwitchingMin better than the current path (no flapping); switching across tiers is immediate.

It mirrors the Rust BiasedRttPathSelector (iroh/src/socket/biased_rtt_path_selector.rs:135).

The zero value is ready to use.

func (BiasedRttPathSelector) Select

func (BiasedRttPathSelector) Select(current *Addr, candidates []PathCandidate) (Addr, bool)

Select implements PathSelector. It returns the best candidate to use, or ok=false to keep the current selection. The decision follows the Rust single-pass algorithm (biased_rtt_path_selector.rs:136): find the lowest-keyed candidate and the lowest key seen for the current path, then switch across tiers immediately and within a tier only when the improvement meets RttSwitchingMin.

type Connection

type Connection interface {
	// SmoothedRTT returns the smoothed round-trip time of the active path.
	SmoothedRTT() time.Duration
	// Done is closed when the connection is closed.
	Done() <-chan struct{}
	// RemoteAddr returns the transport address the connection is using.
	RemoteAddr() Addr
}

Connection is the minimal view of a QUIC connection the RemoteStateActor needs. The iroh package adapts a qng *quic.Conn to it; tests use a fake. It stays small on purpose: the actor only reads liveness and RTT.

SmoothedRTT returns the connection's active-path smoothed RTT. Done is closed when the connection ends. RemoteAddr reports the path the connection is on, so the actor can register it as a candidate path.

type CustomDatagram

type CustomDatagram struct {
	Remote   netaddr.CustomAddr
	Local    netaddr.CustomAddr
	HasLocal bool
	Data     []byte
}

CustomDatagram is one datagram received by a CustomTransport.

type CustomMappedAddr

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

CustomMappedAddr addresses a remote endpoint via a custom transport path.

func CustomMappedAddrFromAddr

func CustomMappedAddrFromAddr(a netip.Addr) CustomMappedAddr

CustomMappedAddrFromAddr wraps an existing custom mapped IPv6 address. It is used to reverse-look-up the custom address via Socket.LookupCustom.

func NewCustomMappedAddr

func NewCustomMappedAddr() CustomMappedAddr

NewCustomMappedAddr allocates a fresh custom mapped address.

func (CustomMappedAddr) Addr

func (m CustomMappedAddr) Addr() netip.Addr

Addr returns the underlying IPv6 address.

func (CustomMappedAddr) AddrPort

func (m CustomMappedAddr) AddrPort() netip.AddrPort

AddrPort returns the mapped address with the fixed dummy port.

type CustomTransport

type CustomTransport interface {
	// Serve runs the transport until ctx is done. Each received datagram should
	// be passed to recv. recv reports false when the magic socket is shutting
	// down or its receive queue is full.
	Serve(ctx context.Context, recv func(CustomDatagram) bool)

	// Send sends p to remote. local is nil when qng did not select a specific
	// local custom address for the path.
	Send(remote netaddr.CustomAddr, local *netaddr.CustomAddr, p []byte) bool
}

CustomTransport is a pluggable transport backend for custom addresses. It is intentionally small: the transport owns its wire format and reports datagrams as iroh custom addresses for the magic socket to map into qng paths.

type EndpointIDMappedAddr

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

EndpointIDMappedAddr addresses a remote endpoint via any/all of its paths. It is used for the initial connection, before a path is selected: the socket duplicates datagrams sent here onto every candidate path.

func EndpointIDMappedAddrFromAddr

func EndpointIDMappedAddrFromAddr(a netip.Addr) EndpointIDMappedAddr

EndpointIDMappedAddrFromAddr wraps an existing endpoint-id mapped IPv6 address. It is used to reverse-look-up the endpoint id via Socket.LookupEndpointID.

func NewEndpointIDMappedAddr

func NewEndpointIDMappedAddr() EndpointIDMappedAddr

NewEndpointIDMappedAddr allocates a fresh endpoint-id mapped address.

func (EndpointIDMappedAddr) Addr

func (m EndpointIDMappedAddr) Addr() netip.Addr

Addr returns the underlying IPv6 address.

func (EndpointIDMappedAddr) AddrPort

func (m EndpointIDMappedAddr) AddrPort() netip.AddrPort

AddrPort returns the mapped address with the fixed dummy port, suitable for handing to quic-go as a path's net.Addr.

type IpTransport

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

IpTransport is the direct-UDP transport: it reads datagrams from a net.PacketConn and forwards them to the MagicConn's recv channel, and sends datagrams the magic socket routes to it. It is the Go analog of the Rust IpTransport (iroh/src/socket/transports/ip.rs).

Create one with NewIpTransport and start its recv loop with IpTransport.Serve.

func NewIpTransport

func NewIpTransport(conn *net.UDPConn, recvCh chan<- recvBatch) *IpTransport

NewIpTransport returns an IpTransport over conn that delivers received datagrams to recvCh. The transport does not take ownership of conn; the caller closes it.

func (*IpTransport) LocalAddr

func (t *IpTransport) LocalAddr() net.Addr

LocalAddr returns the bound local address of the underlying socket.

func (*IpTransport) Serve

func (t *IpTransport) Serve(ctx context.Context)

Serve runs the receive loop until ctx is cancelled or the socket is closed. Each datagram is delivered to the recv channel tagged with its real remote IP address (canonicalized: an IPv4-mapped IPv6 source becomes plain IPv4, to match iroh/src/socket/transports/ip.rs:221 to_canonical). Empty datagrams and transient errors are skipped; a closed socket ends the loop cleanly.

type MagicConn

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

MagicConn is the single net.PacketConn handed to a quic-go Transport. It presents every iroh network path — direct IP, relay, custom — as one UDP socket, mapping non-IP paths to synthetic IPv6 ULAs so quic-go can address them. It is the Go analog of the Rust `impl AsyncUdpSocket for Transport` (iroh/src/socket/transports.rs:1067).

MagicConn satisfies net.PacketConn. It deliberately does not satisfy quic-go's OOBCapablePacketConn: GSO/GRO/ECN are per-platform UDP-socket optimizations that do not generalize across relay and custom transports, so quic-go falls back to its single-packet basicConn path. Correctness does not depend on them.

Create one with NewMagicConn and start it with MagicConn.Serve. The zero value is not usable.

func NewMagicConn

func NewMagicConn(sock *Socket, udp *net.UDPConn) *MagicConn

NewMagicConn returns a MagicConn whose sole transport is an IpTransport bound to udp. sock holds the mapped-address tables shared with the transports. Start the receive loop with MagicConn.Serve before handing the MagicConn to a quic-go Transport.

Example

ExampleNewMagicConn builds a magic socket over a UDP socket and uses it as a net.PacketConn, the way iroh hands it to a quic-go Transport.

package main

import (
	"context"
	"fmt"
	"net"
	"net/netip"

	"github.com/tmc/go-iroh/internal/socket"
)

func main() {
	udp, err := net.ListenUDP("udp", net.UDPAddrFromAddrPort(
		netip.AddrPortFrom(netip.IPv6Loopback(), 0)))
	if err != nil {
		panic(err)
	}

	sock := socket.NewSocket()
	magic := socket.NewMagicConn(sock, udp)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go magic.Serve(ctx)

	var pc net.PacketConn = magic
	defer pc.Close()

	fmt.Println(pc.LocalAddr().Network())
}
Output:
udp

func NewMagicConnWithRelay

func NewMagicConnWithRelay(sock *Socket, udp *net.UDPConn, actor *RelayActor) *MagicConn

NewMagicConnWithRelay returns a MagicConn with an IP transport over udp and, if actor is non-nil, a relay transport driven by it. Datagrams received from relays surface through MagicConn.ReadFrom as a RelayMappedAddr; sends to a relay mapped address route to the actor. Start the receive loops with MagicConn.Serve.

func NewMagicConnWithTransports

func NewMagicConnWithTransports(sock *Socket, udp *net.UDPConn, actor *RelayActor, custom ...CustomTransport) *MagicConn

NewMagicConnWithTransports returns a MagicConn with direct IP, optional relay, and optional custom transports.

func (*MagicConn) Close

func (m *MagicConn) Close() error

Close releases the magic socket. It marks the shared Socket closed and closes the underlying UDP socket, which ends the receive loop. It implements net.PacketConn.

func (*MagicConn) LocalAddr

func (m *MagicConn) LocalAddr() net.Addr

LocalAddr returns the bound local address of the underlying UDP socket. It implements net.PacketConn.

func (*MagicConn) ReadFrom

func (m *MagicConn) ReadFrom(p []byte) (int, net.Addr, error)

ReadFrom delivers the next datagram from any transport into p, returning its length and the net.Addr quic-go should associate with the path it arrived on. For IP paths that addr is the real remote IP; for relay and custom paths it is the synthetic mapped IPv6 ULA (port 12345). It implements net.PacketConn.

func (*MagicConn) Relay

func (m *MagicConn) Relay() *RelayTransport

Relay returns the relay transport, or nil if no relay actor was configured.

func (*MagicConn) SendAddr

func (m *MagicConn) SendAddr(addr Addr, p []byte) bool

SendAddr routes p to one concrete magic-socket transport address. It is used by RemoteStateActor endpoint-id fanout.

func (*MagicConn) Serve

func (m *MagicConn) Serve(ctx context.Context)

Serve runs the magic socket's receive loops until ctx is cancelled or the underlying socket is closed. It blocks; run it in its own goroutine.

func (*MagicConn) SetDeadline

func (m *MagicConn) SetDeadline(t time.Time) error

SetDeadline sets both the read and write deadlines. It implements net.PacketConn.

func (*MagicConn) SetEndpointSender

func (m *MagicConn) SetEndpointSender(send func(key.EndpointID, []byte) bool)

SetEndpointSender sets the callback used for endpoint-id mapped addresses. The callback should route p through the remote endpoint's actor and report whether it accepted the datagram. A nil callback restores blackhole behavior.

func (*MagicConn) SetReadBuffer

func (m *MagicConn) SetReadBuffer(n int) error

SetReadBuffer sets the kernel receive buffer size on the underlying UDP socket. quic-go calls it to raise the buffer to its desired size.

func (*MagicConn) SetReadDeadline

func (m *MagicConn) SetReadDeadline(t time.Time) error

SetReadDeadline sets the deadline for future ReadFrom calls. It implements net.PacketConn.

func (*MagicConn) SetWriteBuffer

func (m *MagicConn) SetWriteBuffer(n int) error

SetWriteBuffer sets the kernel send buffer size on the underlying UDP socket.

func (*MagicConn) SetWriteDeadline

func (m *MagicConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets the deadline for future WriteTo calls. Writes go straight to the underlying socket, so the deadline is applied there. It implements net.PacketConn.

func (*MagicConn) SyscallConn

func (m *MagicConn) SyscallConn() (syscall.RawConn, error)

SyscallConn returns the underlying UDP socket's raw connection. quic-go uses it to size the kernel receive buffer and to set the Don't Fragment bit on the direct-IP path. Exposing it does not make MagicConn an OOBCapablePacketConn — that interface also needs ReadMsgUDP/WriteMsgUDP, which MagicConn does not provide, so quic-go still uses its single-packet path.

func (*MagicConn) WriteTo

func (m *MagicConn) WriteTo(p []byte, addr net.Addr) (int, error)

WriteTo routes p to the transport addressed by addr and reports success.

addr is classified by Classify: a real IP routes to the IP transport; the EndpointID, relay, and custom mapped ULAs route to their transports. A send to a path with no live transport, an unknown mapped address, or a closed socket is blackholed — WriteTo still returns (len(p), nil). quic-go observes the send as successful and its loss recovery retransmits the lost datagram, matching the Rust Sender::poll_send blackhole invariant (iroh/src/socket/transports.rs:1176).

type MappedKind

type MappedKind int

MappedKind classifies a netip.Addr as one of the mapped kinds or a real IP.

const (
	// KindIP is a real (non-mapped) IP address.
	KindIP MappedKind = iota
	// KindEndpointID is an EndpointIDMappedAddr.
	KindEndpointID
	// KindRelay is a RelayMappedAddr.
	KindRelay
	// KindCustom is a CustomMappedAddr.
	KindCustom
)

func Classify

func Classify(addr netip.Addr) MappedKind

Classify reports which mapped kind addr belongs to, or KindIP if it is a real address. The order matches the Rust MultipathMappedAddr::from conversion.

type PathCandidate

type PathCandidate struct {
	// Addr is the path's transport address.
	Addr Addr
	// RTT is the smoothed round-trip time observed on the path.
	// qng multipath paths use per-path RTT when available; otherwise the socket
	// layer falls back to connection-level active-path RTT.
	RTT time.Duration
}

PathCandidate is one path offered to a PathSelector, pairing its Addr with the most recent RTT observed for it. It is the Go analog of the Rust PathSelectionData (biased_rtt_path_selector.rs).

type PathEvent

type PathEvent struct {
	// Kind is which kind of event this is.
	Kind PathEventKind
	// Addr is the path's transport address (zero for Lagged).
	Addr Addr
	// Missed is the number of dropped events (only for Lagged).
	Missed uint64
}

PathEvent is a lifecycle notification for a network path of a connection. It is the Go analog of the Rust PathEvent enum (path_watcher.rs:55).

For Opened, Closed, and Selected, Addr identifies the path. For Lagged, Missed is the number of events the subscriber missed and Addr is the zero value.

type PathEventKind

type PathEventKind int

PathEventKind tags the variant of a PathEvent.

const (
	// PathEventOpened reports a newly-opened network path.
	PathEventOpened PathEventKind = iota
	// PathEventClosed reports a closed network path.
	PathEventClosed
	// PathEventSelected reports that a path was selected for transmission.
	PathEventSelected
	// PathEventLagged reports that events were dropped before a subscriber read
	// them; Missed carries the count.
	PathEventLagged
)

func (PathEventKind) String

func (k PathEventKind) String() string

type PathInfo

type PathInfo struct {
	// ID is the QUIC multipath PathID.
	ID uint32
	// Validated reports whether the path can carry non-probing application data.
	Validated bool
	// Addr is the path's transport address, when HasAddr is true.
	Addr Addr
	// HasAddr reports whether Addr was observed from qng route metadata.
	HasAddr bool
	// RTT is the path's smoothed round-trip time, when HasRTT is true.
	RTT time.Duration
	// HasRTT reports whether RTT was observed from qng per-path state.
	HasRTT bool
}

PathInfo is qng multipath path state observed through a Connection. Addr and RTT are set only when qng reports them; socket must not fabricate Addr from the connection's original RemoteAddr.

type PathSelector

type PathSelector interface {
	// Select returns the address of the path to use, or ok=false to keep the
	// current selection (including keeping no selection). current is the
	// currently selected path, if any.
	Select(current *Addr, candidates []PathCandidate) (selected Addr, ok bool)
}

PathSelector chooses the preferred path among the candidates for a remote endpoint. It is a pure function of the candidate set and the currently selected path. Implementations must not block.

It is the Go analog of the Rust PathSelector trait (iroh/src/socket/remote_map/remote_state.rs). The default implementation is BiasedRttPathSelector.

type PathState

type PathState struct {
	// Status is the current lifecycle status of the path.
	Status PathStatus
	// contains filtered or unexported fields
}

PathState is the per-path bookkeeping kept by RemotePathState.

type PathStatus

type PathStatus int

PathStatus is the lifecycle status of a candidate path. It mirrors the Rust PathStatus enum (path_state.rs:44).

const (
	// PathStatusUnknown is a path that has never been dialed: it was added by an
	// address-lookup mechanism and is only potentially usable.
	PathStatusUnknown PathStatus = iota
	// PathStatusOpen is a path that is currently open in QUIC.
	PathStatusOpen
	// PathStatusInactive is a path that was open at some point but has since
	// closed. The time records when it closed, used to prune oldest-first.
	PathStatusInactive
	// PathStatusUnusable is a path where hole-punching was attempted and failed.
	PathStatusUnusable
)

func (PathStatus) String

func (s PathStatus) String() string

type PathWatcher

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

PathWatcher is a drop-oldest broadcast of [PathEvent]s to any number of subscribers. Each subscriber has its own ring buffer of PathBroadcastCapacity events; when a subscriber falls behind, the oldest buffered event is dropped and the next event the subscriber receives is a PathEventLagged with the running missed count, mirroring tokio::broadcast's lagged-receiver behavior (path_watcher.rs).

PathWatcher is safe for concurrent use. The writer calls PathWatcher.Send; readers call PathWatcher.Subscribe and consume the returned channel. Each subscriber is served by a dedicated delivery goroutine that stops when the subscriber is cancelled or the watcher is closed.

func NewPathWatcher

func NewPathWatcher() *PathWatcher

NewPathWatcher returns an empty broadcast with no subscribers.

func (*PathWatcher) Close

func (w *PathWatcher) Close()

Close stops every subscriber's delivery goroutine, closing its channel, and rejects future sends and subscriptions. It is idempotent. It is the analog of dropping the Rust broadcast sender, which ends every outstanding receiver.

func (*PathWatcher) Send

func (w *PathWatcher) Send(ev PathEvent)

Send broadcasts ev to every subscriber. A subscriber whose ring buffer is full has its oldest pending event dropped and its missed counter incremented; the next event that subscriber receives is a PathEventLagged carrying the missed count. Send never blocks.

func (*PathWatcher) Subscribe

func (w *PathWatcher) Subscribe() (<-chan PathEvent, func())

Subscribe registers a new subscriber and returns the channel its events are delivered on plus a function to unsubscribe and stop delivery. Events sent before Subscribe are not replayed. The channel is closed when the subscriber is cancelled or the watcher is closed.

type RecvInfo

type RecvInfo struct {
	Remote Addr
	Local  netaddr.CustomAddr
	// HasLocal reports whether Local is set (custom transports only).
	HasLocal bool
}

RecvInfo carries the per-datagram metadata a transport reports alongside the payload: the remote Addr it came from and, for custom transports, the local custom address that received it. It mirrors the Rust RecvInfo (iroh/src/socket/transports.rs:572). For IP and relay paths Local is the zero value.

type RelayActor

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

RelayActor manages connections to relay servers. It starts one [activeRelay] per relay URL on demand, routes outgoing datagrams to the right one, and surfaces received datagrams on a single queue. It tracks the home relay and publishes its status through a watch.Value.

It is the Go analog of the Rust RelayActor (iroh/src/socket/transports/relay/actor.rs:855). Create one with NewRelayActor and start it with RelayActor.Run.

func NewRelayActor

func NewRelayActor(cfg RelayActorConfig) *RelayActor

NewRelayActor returns a RelayActor ready to be started with RelayActor.Run.

func (*RelayActor) HomeRelayStatus

func (a *RelayActor) HomeRelayStatus() watch.Observer[*RelayStatus]

HomeRelayStatus returns a watcher over the home relay's connection status. The value is nil until a home relay is set with RelayActor.SetHomeRelay.

func (*RelayActor) InsertRelay

func (a *RelayActor) InsertRelay(url netaddr.RelayURL, cfg relay.Config) (relay.Config, bool)

InsertRelay adds or replaces url's relay configuration, returning the previous config when one existed. If there is no home relay, url becomes home.

func (*RelayActor) Recv

func (a *RelayActor) Recv() <-chan RelayRecvDatagram

Recv returns the queue of datagrams received from relays. A RelayTransport drains it; the channel is closed when the actor stops.

func (*RelayActor) RemoveRelay

func (a *RelayActor) RemoveRelay(url netaddr.RelayURL) (relay.Config, bool)

RemoveRelay removes url's configuration, returning it when present. Any live non-home connection to url is stopped. If url was the home relay, the next configured relay (if any) becomes home.

func (*RelayActor) Run

func (a *RelayActor) Run(ctx context.Context)

Run drives the actor until ctx is cancelled. It starts active relays on demand from queued send items. It blocks; run it in its own goroutine. When it returns it has closed the recv channel and stopped all active relays.

func (*RelayActor) Send

func (a *RelayActor) Send(item RelaySendItem) bool

Send queues item for delivery to its relay. It never blocks: if the queue is full the item is dropped (treated as datagram loss so QUIC's loss recovery retransmits), matching the Rust non-blocking send invariant (iroh/src/socket/transports.rs:1176). Send reports whether the item was queued; a false result is a dropped (lost) datagram, not an error.

func (*RelayActor) SetHomeRelay

func (a *RelayActor) SetHomeRelay(url netaddr.RelayURL)

SetHomeRelay designates url as the home relay, ensuring an [activeRelay] for it (which then never idles out) and demoting any previous home relay. A zero url clears the home relay. It mirrors the Rust on_network_change / set_home_relay path (iroh/src/socket/transports/relay/actor.rs:1126).

type RelayActorConfig

type RelayActorConfig struct {
	// SecretKey is the local endpoint's secret key, used to authenticate to
	// relays. Required.
	SecretKey key.SecretKey
	// Map is the relay map; consulted for per-relay auth tokens.
	Map *relay.Map
	// contains filtered or unexported fields
}

RelayActorConfig configures a RelayActor.

SecretKey is required. The zero value is otherwise not usable; build a config and pass it to NewRelayActor.

type RelayConnState

type RelayConnState int

RelayConnState is the connection state of a relay, published through the home relay watcher. It is the Go analog of the Rust RelayConnectionState (iroh/src/socket/transports/relay/actor.rs:897).

const (
	// RelayConnecting means the actor is dialing or handshaking.
	RelayConnecting RelayConnState = iota
	// RelayConnected means the connection is established and handshaked.
	RelayConnected
	// RelayDisconnected means there is no connection: an attempt failed or a
	// previously-established connection was lost.
	RelayDisconnected
)

func (RelayConnState) String

func (s RelayConnState) String() string

type RelayKey

type RelayKey struct {
	URL netaddr.RelayURL
	EID key.EndpointID
}

RelayKey identifies a relay path: a relay URL together with the remote endpoint reached through it. It is the key type of the relay mapped-address table.

type RelayMappedAddr

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

RelayMappedAddr addresses a remote endpoint via a specific relay path (an (EndpointID, RelayURL) pair).

func NewRelayMappedAddr

func NewRelayMappedAddr() RelayMappedAddr

NewRelayMappedAddr allocates a fresh relay mapped address.

func RelayMappedAddrFromAddr

func RelayMappedAddrFromAddr(a netip.Addr) RelayMappedAddr

RelayMappedAddrFromAddr wraps an existing relay mapped IPv6 address. It is used to reverse-look-up the (relay, endpoint) pair an address maps to via Socket.LookupRelay; it does not allocate a new mapping.

func (RelayMappedAddr) Addr

func (m RelayMappedAddr) Addr() netip.Addr

Addr returns the underlying IPv6 address.

func (RelayMappedAddr) AddrPort

func (m RelayMappedAddr) AddrPort() netip.AddrPort

AddrPort returns the mapped address with the fixed dummy port.

type RelayRecvDatagram

type RelayRecvDatagram struct {
	// URL is the relay it arrived on.
	URL netaddr.RelayURL
	// Src is the endpoint that sent it.
	Src key.EndpointID
	// Datagrams is the payload.
	Datagrams relayproto.Datagrams
}

RelayRecvDatagram is a datagram received from a relay. It is the Go analog of the Rust RelayRecvDatagram (iroh/src/socket/transports/relay/actor.rs:1383).

type RelaySendItem

type RelaySendItem struct {
	// RemoteEndpoint is the destination endpoint.
	RemoteEndpoint key.EndpointID
	// URL is the relay through which to reach RemoteEndpoint.
	URL netaddr.RelayURL
	// Datagrams is the payload.
	Datagrams relayproto.Datagrams
}

RelaySendItem is one or more datagrams to send to a remote endpoint via a relay. It is the Go analog of the Rust RelaySendItem (iroh/src/socket/transports/relay/actor.rs:846).

type RelayStatus

type RelayStatus struct {
	// URL is the home relay URL.
	URL netaddr.RelayURL
	// State is the current connection state.
	State RelayConnState
	// LastError is the most recent connection error while disconnected, or nil.
	LastError error
}

RelayStatus is the connection status of a single home relay, observed through RelayActor.HomeRelayStatus. It is the Go analog of the Rust RelayStatus (iroh/src/endpoint.rs:1832).

The zero value reports no home relay; use RelayActor.HomeRelayStatus to observe it.

func (RelayStatus) IsConnected

func (s RelayStatus) IsConnected() bool

IsConnected reports whether the relay is connected.

type RelayTransport

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

RelayTransport is the magic socket's relay path: it owns a RelayActor, forwards datagrams received from relays into the MagicConn's recv channel (tagged so they surface as a RelayMappedAddr), and routes outgoing datagrams addressed to a relay mapped address to the right relay connection.

It is the Go analog of the Rust RelayTransport (iroh/src/socket/transports/relay.rs:31). Create one with NewRelayTransport and start it with RelayTransport.Serve. The zero value is not usable.

func NewRelayTransport

func NewRelayTransport(sock *Socket, actor *RelayActor, recvCh chan<- recvBatch) *RelayTransport

NewRelayTransport returns a RelayTransport that drives actor and delivers received relay datagrams to recvCh. sock supplies the relay mapped-address table shared with the MagicConn. The transport does not start the actor; call RelayTransport.Serve.

func (*RelayTransport) HomeRelayStatus

func (t *RelayTransport) HomeRelayStatus() watch.Observer[*RelayStatus]

HomeRelayStatus returns a watcher over the home relay's connection status. See RelayActor.HomeRelayStatus.

func (*RelayTransport) InsertRelay

func (t *RelayTransport) InsertRelay(url netaddr.RelayURL, cfg relay.Config) (relay.Config, bool)

InsertRelay adds or replaces a relay config in the underlying actor.

func (*RelayTransport) RemoveRelay

func (t *RelayTransport) RemoveRelay(url netaddr.RelayURL) (relay.Config, bool)

RemoveRelay removes a relay config from the underlying actor.

func (*RelayTransport) Send

func (t *RelayTransport) Send(m RelayMappedAddr, p []byte) bool

Send routes p to the relay addressed by the relay mapped address m. It looks up the (relay url, endpoint id) pair m maps to and queues a datagram to the relay actor. It reports whether the datagram was routed; a false result means the address is unknown or the send queue was full, in which case the datagram is treated as lost (QUIC's loss recovery retransmits), matching the Rust blackhole-on-failure invariant (iroh/src/socket/transports.rs:1176).

func (*RelayTransport) Serve

func (t *RelayTransport) Serve(ctx context.Context)

Serve runs the relay actor and the recv-forwarding loop until ctx is cancelled. It blocks; run it in its own goroutine.

func (*RelayTransport) SetHomeRelay

func (t *RelayTransport) SetHomeRelay(url netaddr.RelayURL)

SetHomeRelay designates url as the endpoint's home relay. See RelayActor.SetHomeRelay.

type RemoteMap

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

RemoteMap is the registry of per-remote [RemoteStateActor]s, keyed by key.EndpointID. It spawns an actor on first reference to a remote and removes it when the actor idles out. It is the Go analog of the Rust RemoteMap (iroh/src/socket/remote_map.rs).

Actor insertion and idle-teardown deregistration are serialized by a single mutex, and an actor only removes itself if it is still the actor registered under its id. So an AddConnection arriving exactly as the 60s idle timeout fires yields exactly one actor: the teardown either runs first (the next reference spawns a fresh actor) or the AddConnection runs first (which resets the actor's idle timer, so it does not tear down). See RemoteMap.ResolveRemote / RemoteMap.AddConnection and the onExit closure in [RemoteMap.actor].

RemoteMap is safe for concurrent use. Create one with NewRemoteMap.

func NewRemoteMap

func NewRemoteMap(ctx context.Context, selector PathSelector, resolve ResolveFunc) *RemoteMap

NewRemoteMap returns a RemoteMap whose actors live until ctx is cancelled or they idle out. selector is the path selector shared by all actors (nil uses BiasedRttPathSelector); resolve is the address-lookup hook (nil disables lookup-driven resolution).

func (*RemoteMap) Actor

func (m *RemoteMap) Actor(id key.EndpointID) *RemoteStateActor

Actor returns the running actor for id, spawning one if none exists.

func (*RemoteMap) AddConnection

func (m *RemoteMap) AddConnection(remote key.EndpointID, conn Connection) <-chan PathEvent

AddConnection registers conn with the actor for remote, spawning the actor if needed, and returns the connection's path-event channel. Registering a connection resets the actor's idle timer, so this can race the idle teardown safely (O12): if the actor is mid-teardown the send observes its done channel and a fresh actor is spawned on the retry.

func (*RemoteMap) AddNATTraversalAddresses

func (m *RemoteMap) AddNATTraversalAddresses(addrs []netip.AddrPort)

AddNATTraversalAddresses reconciles local QNT candidates on currently-active remote actors. It does not spawn actors and ignores per-actor errors, because candidate updates must not make an established endpoint fail.

func (*RemoteMap) Len

func (m *RemoteMap) Len() int

Len returns the number of registered actors. Intended for tests and metrics.

func (*RemoteMap) ResolveRemote

func (m *RemoteMap) ResolveRemote(addr netaddr.EndpointAddr) error

ResolveRemote asks the actor for addr.ID to resolve and register more candidate paths, spawning the actor if needed. It returns the lookup error if any. It races idle teardown the same way as RemoteMap.AddConnection.

type RemotePathState

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

RemotePathState tracks all candidate paths to a single remote endpoint: direct IP, relay, and custom transport addresses, each with a PathStatus. It is the Go analog of the Rust RemotePathState (path_state.rs).

Paths added by address lookup start PathStatusUnknown; QUIC path events move them through Open and Inactive; failed hole-punches mark them Unusable. The set is bounded by RemotePathState.Prune, which keeps at most MaxNonRelayPaths non-relay paths plus MaxInactiveNonRelayPaths inactive non-relay paths. Relay paths are never pruned.

RemotePathState is not safe for concurrent use; it is owned by a single RemoteStateActor goroutine.

func NewRemotePathState

func NewRemotePathState() *RemotePathState

NewRemotePathState returns an empty path-state tracker.

func (*RemotePathState) Add

func (p *RemotePathState) Add(addr Addr)

Add records a candidate path with PathStatusUnknown if it is not already known. A path already present keeps its current status.

func (*RemotePathState) Addrs

func (p *RemotePathState) Addrs() []Addr

Addrs returns the addresses of all known paths in unspecified order.

func (*RemotePathState) ExpireIdle

func (p *RemotePathState) ExpireIdle(now time.Time) []Addr

ExpireIdle closes open paths that have not been observed within their path idle timeout. Direct and custom paths use PathMaxIdleTimeout; relay paths use RelayPathMaxIdleTimeout.

func (*RemotePathState) IsEmpty

func (p *RemotePathState) IsEmpty() bool

IsEmpty reports whether no paths are known.

func (*RemotePathState) Len

func (p *RemotePathState) Len() int

Len returns the number of known paths, including relay paths.

func (*RemotePathState) Prune

func (p *RemotePathState) Prune()

Prune bounds the non-relay path set. It is a no-op when there are fewer than MaxNonRelayPaths non-relay paths. Otherwise it removes failed (unusable) paths and all but the MaxInactiveNonRelayPaths most-recently-closed inactive paths. Open and unknown paths are always kept; relay paths are never pruned or counted. It mirrors prune_non_relay_paths (path_state.rs:254).

func (*RemotePathState) SetClosed

func (p *RemotePathState) SetClosed(addr Addr, now time.Time)

SetClosed transitions addr toward an inactive/unusable status, recording the close time for inactive pruning. It mirrors the Rust remove_path transition (path_state.rs:106): an open or already-inactive path becomes inactive (still considered usable later); an unusable or unknown path becomes unusable.

func (*RemotePathState) SetOpen

func (p *RemotePathState) SetOpen(addr Addr)

SetOpen marks addr as open, adding it if unknown. It mirrors the Rust add_path / on path-open transition (path_state.rs:90).

func (*RemotePathState) SetOpenAt

func (p *RemotePathState) SetOpenAt(addr Addr, now time.Time)

SetOpenAt marks addr as open with an explicit activity time. It is used by tests and by the actor heartbeat, which already has a shared timestamp for all observed paths.

func (*RemotePathState) SetUnusable

func (p *RemotePathState) SetUnusable(addr Addr)

SetUnusable marks addr unusable: a hole-punch was attempted and failed.

func (*RemotePathState) Status

func (p *RemotePathState) Status(addr Addr) (PathStatus, bool)

Status returns the status of addr and whether it is known.

type RemoteStateActor

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

RemoteStateActor manages all connection and path state for a single remote endpoint. Exactly one goroutine runs per remote, driven by a single select loop over the inbox (which carries add-connection, resolve, and connection-closed messages) and timers (heartbeat, upgrade, idle teardown). It is the Go analog of the Rust RemoteStateActor (iroh/src/socket/remote_map/remote_state.rs).

The actor does not build NAT traversal frames itself. It advertises vetted local candidates and asks qng to start traversal rounds; qng owns probe timers, response matching, and route-bearing path opening. Path selection is driven by qng path observability.

Create an actor with the RemoteMap; do not construct one directly.

func (*RemoteStateActor) AddConnection

func (a *RemoteStateActor) AddConnection(conn Connection) (events <-chan PathEvent, ok bool)

AddConnection registers conn with the actor and returns a channel of path events for it. ok is false if the actor stopped before it could register the connection; the caller should retry with a fresh actor. The returned channel is closed when the actor stops.

func (*RemoteStateActor) AddNATTraversalAddresses

func (a *RemoteStateActor) AddNATTraversalAddresses(addrs []netip.AddrPort) error

AddNATTraversalAddresses reconciles the full local QNT candidate set for active qng connections. Candidate discovery stays outside this method: callers must pass already-vetted local candidates, such as endpoint-bound direct addresses and QAD reflexive addresses. qng owns QNT state, wire frames, probe timers, and eventual path opening.

func (*RemoteStateActor) ID

ID returns the remote endpoint this actor manages.

func (*RemoteStateActor) MultipathPaths

func (a *RemoteStateActor) MultipathPaths() []PathInfo

MultipathPaths returns qng multipath path state observed from active connections. Paths with explicit qng route metadata are also registered with RemotePathState by the actor loop and can produce path events.

func (*RemoteStateActor) NATTraversalAddresses

func (a *RemoteStateActor) NATTraversalAddresses() ([]netip.AddrPort, error)

NATTraversalAddresses returns the remote QNT ADD_ADDRESS set observed on active qng connections. Duplicate addresses are removed in first-seen order.

func (*RemoteStateActor) PathEvents

func (a *RemoteStateActor) PathEvents() (<-chan PathEvent, func())

PathEvents returns a fresh subscription to this actor's path events and a function to cancel it.

func (*RemoteStateActor) ResolveRemote

func (a *RemoteStateActor) ResolveRemote(addr netaddr.EndpointAddr) error

ResolveRemote asks the actor to resolve more addresses for addr via the ResolveFunc and register them as candidate paths. It blocks until resolution completes, returning the lookup error if any. With no resolver and no addrs it returns nil immediately.

func (*RemoteStateActor) SelectedPath

func (a *RemoteStateActor) SelectedPath() (Addr, bool)

SelectedPath returns the actor's currently selected path and whether one is selected. It is safe to call concurrently with the actor loop.

func (*RemoteStateActor) SendDatagram

func (a *RemoteStateActor) SendDatagram(p []byte, send func(Addr, []byte) bool) error

SendDatagram routes a datagram toward the remote via send. If a path is selected it sends there; otherwise it sends to every known path. It NEVER returns an error for an unreachable path: an unroutable datagram is treated as lost so QUIC loss recovery handles it (the socket-core blackhole invariant, iroh/src/socket/remote_map/remote_state.rs:782). send's bool result is advisory only.

qng addresses datagrams to a concrete path directly through the MagicConn, so this method backs the Mixed-EndpointID send path, which is exercised by unit tests rather than the QUIC data plane in this slice.

func (*RemoteStateActor) TriggerHolepunch

func (a *RemoteStateActor) TriggerHolepunch() error

TriggerHolepunch attempts to open a new direct path by NAT traversal. It is gated on an active qng connection with QNT support: socket advertises its already-known local candidates and asks qng to initiate one NAT traversal round. qng owns QNT frames, probe timers, response matching, and path opening.

func (*RemoteStateActor) ValidateDirectPath

func (a *RemoteStateActor) ValidateDirectPath(ctx context.Context) error

ValidateDirectPath asks qng to open and validate one ordinary multipath path over the current direct socket. This is the RFC 9000 path-validation path used before or independent of QNT-discovered NAT candidates.

type ResolveFunc

type ResolveFunc func(ctx context.Context, id key.EndpointID) ([]netaddr.TransportAddr, error)

ResolveFunc resolves additional transport addresses for a remote endpoint. It is supplied by the iroh package (which owns the address-lookup services) so the socket package does not import iroh. It returns the resolved addresses, or an error if lookup failed. A nil ResolveFunc disables lookup-driven resolution.

It is the hook for the Rust RemoteStateActor::resolve_remote path (remote_state.rs:843), wired in slice G's address lookup.

type Socket

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

Socket holds the magic socket's mapped-address tables: the bidirectional maps between transport addresses and the synthetic IPv6 ULAs that quic-go uses to address paths. It is the Go analog of the Rust Socket's mapped_addrs (iroh/src/socket.rs:332).

A Socket is created by NewSocket and shared by a MagicConn and its Transports. It is safe for concurrent use. The zero Socket is not usable; use NewSocket.

func NewSocket

func NewSocket() *Socket

NewSocket returns a ready Socket with empty mapped-address tables.

func (*Socket) Close

func (s *Socket) Close()

Close marks the socket closed. Subsequent sends are dropped (blackholed) so quic-go's loss recovery handles in-flight datagrams rather than seeing a hard error. It is idempotent.

func (*Socket) CustomMappedAddrFor

func (s *Socket) CustomMappedAddrFor(c netaddr.CustomAddr) CustomMappedAddr

CustomMappedAddrFor returns the custom mapped address for c, allocating one on first use and recording the reverse mapping back to c.

func (*Socket) EndpointIDMappedAddrFor

func (s *Socket) EndpointIDMappedAddrFor(id key.EndpointID) EndpointIDMappedAddr

EndpointIDMappedAddrFor returns the endpoint-id mapped address for id, allocating one on first use.

func (*Socket) IsClosed

func (s *Socket) IsClosed() bool

IsClosed reports whether the socket has been closed.

func (*Socket) LookupCustom

func (s *Socket) LookupCustom(m CustomMappedAddr) (netaddr.CustomAddr, bool)

LookupCustom returns the custom address for a custom mapped address, if known.

func (*Socket) LookupEndpointID

func (s *Socket) LookupEndpointID(m EndpointIDMappedAddr) (key.EndpointID, bool)

LookupEndpointID returns the endpoint id for an endpoint-id mapped address, if known.

func (*Socket) LookupRelay

func (s *Socket) LookupRelay(m RelayMappedAddr) (RelayKey, bool)

LookupRelay returns the (url, eid) pair for a relay mapped address, if known.

func (*Socket) PathAddr

func (s *Socket) PathAddr(remoteID key.EndpointID, ra net.Addr) Addr

PathAddr classifies a QUIC connection's remote net.Addr into the magic socket's transport Addr: a real IP becomes an IP path; a relay or custom mapped ULA is reverse-looked-up through the mapped-address tables. An unknown mapped address (or one whose mapping has been forgotten) falls back to an IP path so the per-remote actor still tracks a stable address. remoteID is used for relay paths, which are keyed by (relay url, endpoint id).

func (*Socket) RelayMappedAddrFor

func (s *Socket) RelayMappedAddrFor(url netaddr.RelayURL, eid key.EndpointID) RelayMappedAddr

RelayMappedAddrFor returns the relay mapped address for the (url, eid) pair, allocating one on first use.

type Transports

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

Transports multiplexes the magic socket's network paths: a direct-IP transport plus optional relay and custom transports. It is the Go analog of the Rust Transports struct (iroh/src/socket/transports.rs:47).

The IP transport is always present. The relay transport is present when the endpoint has relays configured; otherwise relay-addressed sends are blackholed (reported as success so quic-go's loss recovery retransmits). Custom transports are present only when callers configure them.

Jump to

Keyboard shortcuts

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