Documentation
¶
Overview ¶
Package host is the Phase 2 SDK: DHT + QUIC, nat-sense, multi-candidate endpoints (2b), optional UPnP, Happy Eyeballs dial, Publish/Resolve/Connect/Accept.
A Host manages the node-level resources (DHT, QUIC transport). One or more agents (each identified by a unique Address) share the same QUIC listener via TLS SNI routing (spec Phase 2a "mux" module).
When Config.QUICListenAddr is empty, DHT and QUIC share one UDP port via UDPMux (spec target). When QUICListenAddr is set, QUIC uses a separate socket — recommended until mux is hardened on all platforms.
Index ¶
- Constants
- Variables
- func FirstQUICAddr(er *protocol.EndpointRecord) (*net.UDPAddr, error)
- func PeerAddressFromConn(tlsPeerCerts []*x509.Certificate) (a2al.Address, error)
- func QUICDialTargets(er *protocol.EndpointRecord) ([]*net.UDPAddr, error)
- type AgentConn
- type Config
- type Host
- func (h *Host) Accept(ctx context.Context) (*AgentConn, error)
- func (h *Host) AcceptICEViaSignal(ctx context.Context, localAgent, expectRemote a2al.Address, signalBase string) (*AgentConn, error)
- func (h *Host) Address() a2al.Address
- func (h *Host) BuildEndpointPayload(ctx context.Context) (protocol.EndpointPayload, error)
- func (h *Host) Close() error
- func (h *Host) Connect(ctx context.Context, expectRemote a2al.Address, udpAddr *net.UDPAddr) (quic.Connection, error)
- func (h *Host) ConnectFromRecord(ctx context.Context, expectRemote a2al.Address, er *protocol.EndpointRecord) (quic.Connection, error)
- func (h *Host) ConnectFromRecordFor(ctx context.Context, localAgent, expectRemote a2al.Address, ...) (quic.Connection, error)
- func (h *Host) DHTLocalAddr() *net.UDPAddr
- func (h *Host) DebugHTTPHandler() http.Handler
- func (h *Host) EffectiveICESignalBase() string
- func (h *Host) FindRecords(ctx context.Context, target a2al.Address, recType uint8) ([]protocol.SignedRecord, error)
- func (h *Host) InvalidateNetworkCaches()
- func (h *Host) LocalUDPAddr() *net.UDPAddr
- func (h *Host) Node() *dht.Node
- func (h *Host) ObserveFromPeers(ctx context.Context, seeds []net.Addr)
- func (h *Host) ObserveFromRouting(ctx context.Context, n int) int
- func (h *Host) PollMailbox(ctx context.Context) ([]protocol.MailboxMessage, error)
- func (h *Host) PollMailboxForAgent(ctx context.Context, agentAddr a2al.Address) ([]protocol.MailboxMessage, error)
- func (h *Host) PublishEndpoint(ctx context.Context, seq uint64, ttl uint32) error
- func (h *Host) PublishEndpointBuilt(ctx context.Context, ep protocol.EndpointPayload, seq uint64, ttl uint32) error
- func (h *Host) PublishEndpointForAgent(ctx context.Context, agentAddr a2al.Address, seq uint64, ttl uint32) error
- func (h *Host) PublishRecord(ctx context.Context, rec protocol.SignedRecord) error
- func (h *Host) QUICLocalAddr() *net.UDPAddr
- func (h *Host) RegisterAgent(addr a2al.Address, priv ed25519.PrivateKey) error
- func (h *Host) RegisterDelegatedAgent(addr a2al.Address, opPriv ed25519.PrivateKey, delegationCBOR []byte) error
- func (h *Host) RegisterTopic(ctx context.Context, topic string, entry protocol.TopicPayload, ttl uint32) error
- func (h *Host) RegisterTopicForAgent(ctx context.Context, agentAddr a2al.Address, topic string, ...) error
- func (h *Host) RegisterTopics(ctx context.Context, topics []string, base protocol.TopicPayload, ttl uint32) error
- func (h *Host) RegisterTopicsForAgent(ctx context.Context, agentAddr a2al.Address, topics []string, ...) error
- func (h *Host) RegisteredAgents() []a2al.Address
- func (h *Host) Resolve(ctx context.Context, target a2al.Address) (*protocol.EndpointRecord, error)
- func (h *Host) RunNATProbe(ctx context.Context)
- func (h *Host) SearchTopic(ctx context.Context, topic string) ([]protocol.TopicEntry, error)
- func (h *Host) SearchTopics(ctx context.Context, topics []string) ([]protocol.TopicEntry, error)
- func (h *Host) SendMailbox(ctx context.Context, recipient a2al.Address, msgType uint8, body []byte) error
- func (h *Host) SendMailboxForAgent(ctx context.Context, agentAddr, recipient a2al.Address, msgType uint8, ...) error
- func (h *Host) Sense() *natsense.Sense
- func (h *Host) SetDerivedICESignalURL(s string)
- func (h *Host) SetSignalStatsProvider(f func() map[string]any)
- func (h *Host) StartDebugHTTP(addr string) (stop func(), err error)
- func (h *Host) SymmetricNATReachabilityHint() string
- func (h *Host) UnregisterAgent(addr a2al.Address)
- type TURNCredentialType
- type TURNServer
Constants ¶
const ALPNQuic = "a2al-quic-1"
const DefaultConnectStagger = 250 * time.Millisecond
DefaultConnectStagger is the delay between starting each QUIC dial (Happy Eyeballs).
Variables ¶
var ErrNoAgent = errors.New("a2al/host: target agent not registered")
ErrNoAgent is returned by runICESession when the signaling hub reports that the target agent is not currently registered ("noagent" frame). Callers may retry after a brief delay to ride out a callee reconnect window.
Functions ¶
func FirstQUICAddr ¶
func FirstQUICAddr(er *protocol.EndpointRecord) (*net.UDPAddr, error)
FirstQUICAddr returns the first quic:// (or legacy udp://) endpoint as a UDP address.
func PeerAddressFromConn ¶
func PeerAddressFromConn(tlsPeerCerts []*x509.Certificate) (a2al.Address, error)
PeerAddressFromConn extracts the remote peer's AID from a QUIC connection's TLS state (works after mutual TLS handshake). For Phase 3 delegated agents, it returns the AID from the delegation extension rather than the op-key-derived address.
func QUICDialTargets ¶
func QUICDialTargets(er *protocol.EndpointRecord) ([]*net.UDPAddr, error)
QUICDialTargets returns ordered, deduplicated UDP addresses from quic:// / udp:// entries.
Types ¶
type AgentConn ¶
type AgentConn struct {
quic.Connection
// Local is the agent Address that was targeted (agent-route frame, or SNI fallback).
Local a2al.Address
// Remote is the connecting peer's Address (from mutual TLS client cert).
Remote a2al.Address
}
AgentConn wraps a QUIC connection with the resolved peer and local agent identities.
type Config ¶
type Config struct {
KeyStore crypto.KeyStore
// ListenAddr is the DHT UDP bind address, e.g. ":5001".
// Currently resolved as udp4; dual-stack (udp / "[::]:port") is planned.
ListenAddr string
// QUICListenAddr, if non-empty, is a separate UDP bind for QUIC.
// Same udp4 constraint as ListenAddr.
QUICListenAddr string
PrivateKey ed25519.PrivateKey
MinObservedPeers int
FallbackHost string
// DisableUPnP skips IGD port mapping for the QUIC UDP port (Phase 2b).
DisableUPnP bool
// ICESignalURL is the primary WebSocket base URL for ICE signaling (single URL, backward compat).
// Superseded by ICESignalURLs when that field is non-empty.
ICESignalURL string
// ICESignalURLs lists WebSocket base URLs for ICE signaling (multi-center support).
// When non-empty, supersedes ICESignalURL. The first URL is also written to
// EndpointPayload.Signal (CBOR key 3) for backward compatibility with old nodes.
ICESignalURLs []string
// ICESTUNURLs lists stun: URIs for ICE gathering; empty means default public STUN when no TURN is configured.
ICESTUNURLs []string
// ICETURNURLs lists turn: URIs with embedded credentials for ICE relay (legacy format).
// Use TURNServers for new deployments; both fields are processed when set.
ICETURNURLs []string
// TURNServers lists TURN relay servers with structured credential configuration.
// Supports Static, HMAC (coturn use-auth-secret), and REST API credential types.
// Credentials are resolved per ICE session and never published to the DHT.
TURNServers []TURNServer
// ICEPublishTurns is retained for decoding old records; new nodes do not publish turns[].
// Deprecated: callee-pays TURN relay addresses are exchanged via trickle ICE, not the DHT.
ICEPublishTurns []string
// Logger is forwarded to the DHT node for diagnostic logging (reply failures, RPC retries).
// If nil, slog.Default() is used.
Logger *slog.Logger
// SeenPeersPath is forwarded to the DHT node for seenPeers persistence (spec §7.3).
// Empty disables persistence.
SeenPeersPath string
}
Config wires DHT + QUIC.
IPv6 note: currently Host binds udp4 sockets only. The Transport interface, protocol wire format (NodeInfo.IP 4/16 bytes, observed_addr 6/18 bytes), and endpoint URL model ("quic://[v6]:port") are all IPv6-ready. Dual-stack requires changing the socket setup in New() — either "udp" dual-stack or separate v4+v6 listeners — and adding v6 candidate collection in candidates.go. No interface or data-model changes are expected.
type Host ¶
type Host struct {
// contains filtered or unexported fields
}
Host is the Phase 2a runtime.
Identity layering strategy:
- DHT and signaling transport use node identity.
- QUIC mutual-TLS uses agent identity certificates (including delegated agent cert extensions when applicable).
- Each QUIC connection represents a (localAgent, remoteAgent) pair.
- The gateway relies on AgentConn.Remote as the authenticated caller AID.
func (*Host) Accept ¶
Accept waits for an incoming QUIC connection and returns an AgentConn.
Agent routing priority:
- Agent-route control stream (first stream: magic + 21-byte Address) — canonical.
- TLS SNI (Address hex) — fast-path hint when not camouflaged.
- Default to the host's own Address.
Remote peer AID is extracted from the mutual TLS client certificate.
func (*Host) AcceptICEViaSignal ¶
func (h *Host) AcceptICEViaSignal(ctx context.Context, localAgent, expectRemote a2al.Address, signalBase string) (*AgentConn, error)
AcceptICEViaSignal is the controlled (callee) side: WebSocket ICE signaling on signalBase, then QUIC-over-ICE. expectRemote is the caller's agent address.
Same lifetime semantics as connectViaICESignal — resources are freed when the AgentConn's underlying QUIC connection closes.
func (*Host) BuildEndpointPayload ¶
BuildEndpointPayload builds ordered, deduplicated quic:// candidates (Phase 2b). UPnP discovery and external IP probing (STUN + HTTP) run concurrently.
func (*Host) Connect ¶
func (h *Host) Connect(ctx context.Context, expectRemote a2al.Address, udpAddr *net.UDPAddr) (quic.Connection, error)
Connect dials the remote agent over QUIC with mutual TLS. After the QUIC handshake, it opens a control stream and sends the agent-route frame (4-byte magic + 21-byte target Address) so the server can route the connection even when TLS SNI is camouflaged.
func (*Host) ConnectFromRecord ¶
func (h *Host) ConnectFromRecord(ctx context.Context, expectRemote a2al.Address, er *protocol.EndpointRecord) (quic.Connection, error)
ConnectFromRecord dials expectRemote using the three-layer strategy: ① Happy Eyeballs over quic:// candidates → ② ICE via signal (if record has Signal). The host's default agent identity is used for mutual TLS.
func (*Host) ConnectFromRecordFor ¶
func (h *Host) ConnectFromRecordFor(ctx context.Context, localAgent, expectRemote a2al.Address, er *protocol.EndpointRecord) (quic.Connection, error)
ConnectFromRecordFor dials as localAgent (must be registered) toward expectRemote. After Happy Eyeballs over quic:// candidates fails, if the record includes Signal, falls back to ICE+QUIC over WebSocket signaling.
func (*Host) DHTLocalAddr ¶
func (*Host) DebugHTTPHandler ¶
DebugHTTPHandler returns an http.Handler serving /debug/host (Phase 2 state) and delegates /debug/identity, /debug/routing, /debug/store, /debug/stats to the underlying DHT node.
func (*Host) EffectiveICESignalBase ¶ added in v0.1.4
EffectiveICESignalBase returns the primary ICE signaling WebSocket base URL published in endpoint records: explicit config overrides bootstrap-derived value.
func (*Host) FindRecords ¶
func (h *Host) FindRecords(ctx context.Context, target a2al.Address, recType uint8) ([]protocol.SignedRecord, error)
FindRecords runs iterative FIND_VALUE for the given RecType filter (0 = all types).
func (*Host) InvalidateNetworkCaches ¶ added in v0.1.4
func (h *Host) InvalidateNetworkCaches()
InvalidateNetworkCaches clears short-lived external-network caches so subsequent endpoint building uses fresh probes after network changes.
func (*Host) LocalUDPAddr ¶
func (*Host) ObserveFromPeers ¶
func (*Host) ObserveFromRouting ¶ added in v0.1.4
ObserveFromRouting samples current routing-table candidates and performs passive observed_addr collection from them.
func (*Host) PollMailbox ¶
PollMailbox aggregates mailbox records for the host default AID (spec §4.4–4.6).
func (*Host) PollMailboxForAgent ¶
func (h *Host) PollMailboxForAgent(ctx context.Context, agentAddr a2al.Address) ([]protocol.MailboxMessage, error)
PollMailboxForAgent aggregates and decrypts mailbox records for agentAddr.
func (*Host) PublishEndpoint ¶
func (*Host) PublishEndpointBuilt ¶ added in v0.1.3
func (h *Host) PublishEndpointBuilt(ctx context.Context, ep protocol.EndpointPayload, seq uint64, ttl uint32) error
PublishEndpointBuilt publishes the node endpoint using a pre-built EndpointPayload. Use this when the payload has already been constructed (e.g. to avoid a redundant probe).
func (*Host) PublishEndpointForAgent ¶
func (h *Host) PublishEndpointForAgent(ctx context.Context, agentAddr a2al.Address, seq uint64, ttl uint32) error
PublishEndpointForAgent publishes an endpoint record signed by the given registered agent. For Phase 3 delegated agents (registered via RegisterDelegatedAgent), the record embeds the DelegationProof so DHT nodes can verify the operational key's authority.
func (*Host) PublishRecord ¶
PublishRecord pushes a signed sovereign record (RecType 0x01–0x0F) to the DHT. Returns an error if rec is not a sovereign-category record; use PublishTopicRecord / host mailbox APIs for other categories.
func (*Host) QUICLocalAddr ¶
func (*Host) RegisterAgent ¶
RegisterAgent adds an additional agent identity to this host's SNI router. Incoming connections with TLS ServerName matching addr will be served with the corresponding certificate. Returns an error if the address is already registered.
func (*Host) RegisterDelegatedAgent ¶
func (h *Host) RegisterDelegatedAgent(addr a2al.Address, opPriv ed25519.PrivateKey, delegationCBOR []byte) error
RegisterDelegatedAgent adds a Phase 3 agent whose operational key is authorized by a DelegationProof (delegationCBOR). The proof is embedded in endpoint records so DHT nodes can verify the authority of the operational key independently.
func (*Host) RegisterTopic ¶
func (h *Host) RegisterTopic(ctx context.Context, topic string, entry protocol.TopicPayload, ttl uint32) error
RegisterTopic publishes one topic registration for the host default identity (spec §5.7).
func (*Host) RegisterTopicForAgent ¶
func (h *Host) RegisterTopicForAgent(ctx context.Context, agentAddr a2al.Address, topic string, entry protocol.TopicPayload, ttl uint32) error
RegisterTopicForAgent signs and stores a topic record for a registered agent (delegation-aware).
func (*Host) RegisterTopics ¶
func (h *Host) RegisterTopics(ctx context.Context, topics []string, base protocol.TopicPayload, ttl uint32) error
RegisterTopics registers under multiple topic strings for the default identity (spec §5.7).
func (*Host) RegisterTopicsForAgent ¶
func (h *Host) RegisterTopicsForAgent(ctx context.Context, agentAddr a2al.Address, topics []string, base protocol.TopicPayload, ttl uint32) error
RegisterTopicsForAgent is RegisterTopics for a registered agent address.
func (*Host) RegisteredAgents ¶
RegisteredAgents returns the addresses of all registered agents.
func (*Host) RunNATProbe ¶ added in v0.1.4
RunNATProbe performs an AutoNAT-style active reachability test to classify NAT type. At most one probe runs at a time (TryLock); concurrent callers return immediately.
Classification logic:
QUIC bind IP is public WAN → sense.RecordBindPublic(true); return (no probe needed) probe echo received from ≥1 candidate → sense.RecordProbeResult(true) [Full Cone / cloud NAT] no echo despite known external address → sense.RecordProbeResult(false) [Restricted]
func (*Host) SearchTopic ¶
SearchTopic runs AggregateRecords on the topic key and returns verified entries (spec §5.5).
func (*Host) SearchTopics ¶
SearchTopics returns agents registered on all given topics (intersection by AID) (spec §5.5). The returned TopicEntry values are taken from the first topic's results; fields from subsequent topics are not merged.
func (*Host) SendMailbox ¶
func (h *Host) SendMailbox(ctx context.Context, recipient a2al.Address, msgType uint8, body []byte) error
SendMailbox encrypts a message for recipient using the host default identity (spec §4.4–4.6).
func (*Host) SendMailboxForAgent ¶
func (h *Host) SendMailboxForAgent(ctx context.Context, agentAddr, recipient a2al.Address, msgType uint8, body []byte) error
SendMailboxForAgent encrypts and stores a mailbox record signed by the given registered agent. Delegated agents use SignRecordDelegated (same authority model as PublishEndpointForAgent).
func (*Host) SetDerivedICESignalURL ¶ added in v0.1.4
SetDerivedICESignalURL sets the bootstrap-derived signal base when Config.ICESignalURL is empty. Explicit config always wins and is not overwritten.
func (*Host) SetSignalStatsProvider ¶ added in v0.1.4
SetSignalStatsProvider merges hub stats into GET /debug/stats under "signal".
func (*Host) StartDebugHTTP ¶
StartDebugHTTP listens on addr and serves /debug/* JSON for both DHT and Phase 2 host state. Returns a stop function.
func (*Host) SymmetricNATReachabilityHint ¶
SymmetricNATReachabilityHint returns a user-facing note when NAT looks symmetric. Phase 2b does not guarantee inbound QUIC from arbitrary peers; TURN is deferred.
func (*Host) UnregisterAgent ¶
UnregisterAgent removes an agent from the SNI router (the default agent created at New() cannot be unregistered).
type TURNCredentialType ¶ added in v0.1.4
type TURNCredentialType int
TURNCredentialType selects how TURN credentials are obtained per session.
const ( // TURNCredentialStatic uses a fixed username and password stored in config. TURNCredentialStatic TURNCredentialType = iota // TURNCredentialHMAC generates time-limited credentials from a shared secret // using the coturn use-auth-secret mechanism: // username = strconv.FormatInt(unix_expiry, 10) + ":" + Username // password = base64(HMAC-SHA1(Credential, username)) TURNCredentialHMAC // TURNCredentialRESTAPI fetches short-lived credentials from an HTTP endpoint // before each ICE session. The response must be JSON with "username" and // "password" (or "credential") fields. Covers Twilio, Metered.ca, etc. TURNCredentialRESTAPI )
type TURNServer ¶ added in v0.1.4
type TURNServer struct {
// URL is the TURN server address without embedded credentials,
// e.g. "turn:turn.example.com:3478?transport=udp".
URL string
// CredentialType selects the credential acquisition method.
CredentialType TURNCredentialType
// Username is the static username (Static) or base username prefix (HMAC).
Username string
// Credential is the static password (Static), the HMAC shared secret (HMAC),
// or the Authorization header value for the REST API request (RESTAPI).
Credential string
// CredentialURL is the HTTP endpoint that returns short-lived credentials (RESTAPI only).
CredentialURL string
}
TURNServer describes a TURN relay server and how to obtain credentials for it. Credentials are resolved fresh per ICE session; they are never published to the DHT.