Documentation
¶
Overview ¶
Package iron implements "dial by public key" networking on top of QUIC.
Every node keeps a persistent connection to a relay and announces the direct addresses it is reachable at. To reach another node you dial its NodeID: the node tries the peer's direct addresses first and falls back to connecting through the relay. Connections are authenticated by the node's Ed25519 identity carried in self-signed X.509 certificates.
Index ¶
- Constants
- type ConnectOption
- type Connection
- func (conn *Connection) AcceptStream(ctx context.Context) (*quic.Stream, error)
- func (conn *Connection) AcceptUniStream(ctx context.Context) (*quic.ReceiveStream, error)
- func (conn *Connection) Close() error
- func (conn *Connection) CloseWithError(code uint64, desc string) error
- func (conn *Connection) Context() context.Context
- func (conn *Connection) OpenStream() (*quic.Stream, error)
- func (conn *Connection) OpenStreamSync(ctx context.Context) (*quic.Stream, error)
- func (conn *Connection) OpenUniStream() (*quic.SendStream, error)
- func (conn *Connection) OpenUniStreamSync(ctx context.Context) (*quic.SendStream, error)
- func (conn *Connection) Path() string
- func (conn *Connection) PeerID() (base.NodeID, error)
- func (conn *Connection) ReceiveDatagram(ctx context.Context) ([]byte, error)
- func (conn *Connection) SendDatagram(p []byte) error
- func (conn *Connection) State() quic.ConnectionState
- type Endpoint
- func (endpoint *Endpoint) Accept(ctx context.Context) (*Connection, error)
- func (endpoint *Endpoint) Close() error
- func (endpoint *Endpoint) Connect(ctx context.Context, peer base.NodeID, opts ...ConnectOption) (*Connection, error)
- func (endpoint *Endpoint) ConnectAddr(ctx context.Context, addr base.NodeAddr, opts ...ConnectOption) (*Connection, error)
- func (endpoint *Endpoint) NodeAddr() base.NodeAddr
- func (endpoint *Endpoint) NodeID() base.NodeID
- func (endpoint *Endpoint) PeerID(conn *Connection) (base.NodeID, error)
- func (endpoint *Endpoint) PublicAddr() *net.UDPAddr
- func (endpoint *Endpoint) SetAnnouncedAddrs(addrs []*net.UDPAddr) error
- type EndpointOption
- func WithAnnouncers(a ...discovery.Announcer) EndpointOption
- func WithDirectConn(conn net.PacketConn) EndpointOption
- func WithLogger(logger *slog.Logger) EndpointOption
- func WithRelayBatching(batchSize, batchCount int, drainDelay time.Duration) EndpointOption
- func WithRelayOnly() EndpointOption
- func WithRelayURLs(urls ...string) EndpointOption
- func WithRelayWaitTimeout(d time.Duration) EndpointOption
- func WithSkipAnnounce() EndpointOption
- func WithTLSConfig(c TLSConfig) EndpointOption
- type TLSConfig
Constants ¶
const ( // PathDirect means the connection uses the peer's direct address. PathDirect = "direct" // PathRelay means the connection is tunnelled through the relay. PathRelay = "relay" )
Path over which a Connection was established.
const DefaultALPN = "iron-example/echo/0"
DefaultALPN is the application protocol historically advertised by endpoints. It is kept for API compatibility but is no longer placed on the wire: the ALPN used for peer connections is the h3 HTTP/3 identifier by default (see TLSConfig.ALPN), so traffic blends in with ordinary web HTTP/3. Use WithTLSConfig(TLSConfig{ALPN: ...}) to advertise something else.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ConnectOption ¶
type ConnectOption func(*connectOptions)
ConnectOption configures a single connection attempt (see Endpoint.Connect and Endpoint.ConnectAddr).
func ConnectRelayOnly ¶
func ConnectRelayOnly() ConnectOption
ConnectRelayOnly forces this connection through the relay, disabling direct dialing and hole punching for this connection only. Unlike the endpoint-wide WithRelayOnly, it does not change what the endpoint announces or whether it opens a direct socket.
func WithDiscoverers ¶
func WithDiscoverers(d ...discovery.Discoverer) ConnectOption
WithDiscoverers registers channels (other than the relay) from which this connection looks up a peer's direct addresses. These are caller-owned and are never closed by the endpoint. Discovery is independent of announcement.
type Connection ¶
type Connection struct {
// contains filtered or unexported fields
}
Connection wraps a quic.Conn and remembers which path it came up on.
For connections dialed by this endpoint (see Endpoint.Connect), it also provides transparent fallback: if the direct path dies, the endpoint automatically re-dials the peer over the relay and subsequent stream and datagram operations run on the replacement connection. Connections opened *by the peer* (from Endpoint.Accept) are not auto-redialed; that side sees the connection close and the peer re-dialing.
func (*Connection) AcceptStream ¶
AcceptStream accepts the next bidirectional stream opened by the peer.
func (*Connection) AcceptUniStream ¶
func (conn *Connection) AcceptUniStream(ctx context.Context) (*quic.ReceiveStream, error)
AcceptUniStream accepts the next unidirectional stream opened by the peer.
func (*Connection) Close ¶
func (conn *Connection) Close() error
Close closes the underlying connection.
func (*Connection) CloseWithError ¶
func (conn *Connection) CloseWithError(code uint64, desc string) error
CloseWithError closes the connection with an application error.
func (*Connection) Context ¶
func (conn *Connection) Context() context.Context
Context returns the context of the current underlying connection. It is canceled when that connection closes.
func (*Connection) OpenStream ¶
func (conn *Connection) OpenStream() (*quic.Stream, error)
OpenStream opens a new bidirectional stream.
func (*Connection) OpenStreamSync ¶
OpenStreamSync opens a new bidirectional stream, blocking until a stream can be opened.
func (*Connection) OpenUniStream ¶
func (conn *Connection) OpenUniStream() (*quic.SendStream, error)
OpenUniStream opens a new unidirectional stream.
func (*Connection) OpenUniStreamSync ¶
func (conn *Connection) OpenUniStreamSync(ctx context.Context) (*quic.SendStream, error)
OpenUniStreamSync opens a new unidirectional stream, blocking until a stream can be opened.
func (*Connection) Path ¶
func (conn *Connection) Path() string
Path reports whether the connection is direct or relayed.
func (*Connection) PeerID ¶
func (conn *Connection) PeerID() (base.NodeID, error)
PeerID returns the node id of the remote endpoint, taken from the authenticated TLS certificate.
func (*Connection) ReceiveDatagram ¶
func (conn *Connection) ReceiveDatagram(ctx context.Context) ([]byte, error)
ReceiveDatagram receives the next datagram sent by the peer.
func (*Connection) SendDatagram ¶
func (conn *Connection) SendDatagram(p []byte) error
SendDatagram sends an unreliable datagram.
func (*Connection) State ¶
func (conn *Connection) State() quic.ConnectionState
State returns the QUIC connection state of the current underlying connection.
type Endpoint ¶
type Endpoint struct {
// contains filtered or unexported fields
}
Endpoint is a node. It maintains a relay connection, listens for direct UDP connections, and can accept inbound QUIC connections and dial outbound ones, both identified by NodeID.
func NewEndpoint ¶
func NewEndpoint(ctx context.Context, secret *base.NodeSecret, alpn string, opts ...EndpointOption) (*Endpoint, error)
NewEndpoint binds a node to a UDP socket for direct connections, and, when relays are configured via WithRelayURLs, to a relay. With no relays the endpoint is relay-free: it only connects directly (via announced addresses, Discoverers or ConnectAddr) and never dials or accepts through a relay.
The alpn parameter is kept for API compatibility but is no longer placed on the wire: peer connections advertise HTTP/3 ("h3") by default so traffic blends in with ordinary web HTTP/3. Configure a different ALPN, cipher suites, key exchange groups or SNI hostnames via WithTLSConfig.
func (*Endpoint) Accept ¶
func (endpoint *Endpoint) Accept(ctx context.Context) (*Connection, error)
Accept returns the next inbound QUIC connection (direct or through the relay).
func (*Endpoint) Close ¶
Close shuts down the endpoint, its UDP socket, its relay connection and any announcers registered at construction.
func (*Endpoint) Connect ¶
func (endpoint *Endpoint) Connect(ctx context.Context, peer base.NodeID, opts ...ConnectOption) (*Connection, error)
Connect dials another node by its NodeID: it races a direct connection (using the peer's announced addresses and, if necessary, hole punching through its NAT) against a relay connection and returns whichever establishes first. The returned Connection redials over the relay transparently if a direct connection drops. ConnectOptions (e.g. ConnectRelayOnly) tailor this one connection attempt.
func (*Endpoint) ConnectAddr ¶
func (endpoint *Endpoint) ConnectAddr(ctx context.Context, addr base.NodeAddr, opts ...ConnectOption) (*Connection, error)
ConnectAddr dials a node by its complete address (see NodeAddr). The direct addresses embedded in the address are tried first; the endpoint's relays are used as the fallback.
func (*Endpoint) NodeAddr ¶
NodeAddr returns this endpoint's complete address: its NodeID, its direct addresses (including the STUN-discovered public address) and its configured relays. Share it out of band so a peer can dial without a lookup.
func (*Endpoint) PeerID ¶
func (endpoint *Endpoint) PeerID(conn *Connection) (base.NodeID, error)
PeerID returns the NodeID of the peer on an established connection.
func (*Endpoint) PublicAddr ¶
PublicAddr returns this endpoint's public UDP address as observed by the relay, or nil until the first successful discovery.
func (*Endpoint) SetAnnouncedAddrs ¶
SetAnnouncedAddrs overrides the direct addresses this endpoint announces to its announce channels (the relay if opted in, plus any announcers). Mostly useful for tests and unusual deployments.
type EndpointOption ¶
type EndpointOption func(*endpointOptions)
EndpointOption configures Endpoint construction.
func WithAnnouncers ¶
func WithAnnouncers(a ...discovery.Announcer) EndpointOption
WithAnnouncers registers channels (other than the relay) to which this endpoint publishes its direct addresses. The endpoint owns them and closes them on Endpoint.Close. Announcement is independent of discovery: a channel may announce without discovering.
func WithDirectConn ¶
func WithDirectConn(conn net.PacketConn) EndpointOption
WithDirectConn overrides the socket used for direct connections. Mostly for tests that simulate NAT with an address-translating PacketConn; the conn's LocalAddr is what gets announced.
func WithLogger ¶
func WithLogger(logger *slog.Logger) EndpointOption
func WithRelayBatching ¶
func WithRelayBatching(batchSize, batchCount int, drainDelay time.Duration) EndpointOption
WithRelayBatching configures outbound relay batching: max bytes per batch frame, max packets per batch frame, and how long a partial batch is held before flushing. A batchSize <= 0 disables batching. Defaults: 64 KiB, 16 packets, 500 microseconds.
func WithRelayOnly ¶
func WithRelayOnly() EndpointOption
WithRelayOnly disables p2p entirely: the endpoint opens no direct UDP socket and only ever connects through the relay. It will neither attempt direct connections nor respond to hole punching.
func WithRelayURLs ¶
func WithRelayURLs(urls ...string) EndpointOption
WithRelayURLs sets the full list of relay URLs the endpoint may use. The endpoint connects to the fastest reachable one and fails over to the others in order. If not given, only the NewEndpoint relayURL is used.
func WithRelayWaitTimeout ¶
func WithRelayWaitTimeout(d time.Duration) EndpointOption
WithRelayWaitTimeout sets how long Connect waits for the relay to come back after an outage before failing (default 5 seconds).
func WithSkipAnnounce ¶
func WithSkipAnnounce() EndpointOption
WithSkipAnnounce disables announcing this endpoint's direct addresses at startup. Use it when you want to publish a precise address set with SetAnnouncedAddrs instead of the auto-detected interface addresses.
func WithTLSConfig ¶
func WithTLSConfig(c TLSConfig) EndpointOption
WithTLSConfig customizes the TLS settings for all peer connections: the set of enabled KeyExchange groups (Go's CurvePreferences), ordered by preference. iron always negotiates TLS 1.3, whose cipher suites are fixed by the spec, so the curve preferences are the only tunable. A zero value keeps the default (X25519MLKEM768). Both endpoints in a connection must share at least one enabled group or the handshake fails.
type TLSConfig ¶
type TLSConfig struct {
// CurvePreferences lists the KeyExchange groups to offer (client) and
// accept (server), ordered by preference. nil means the default
// [X25519, X25519MLKEM768], which looks like a modern browser.
CurvePreferences []tls.CurveID
// CipherSuites lists the TLS 1.3 cipher suites to offer, ordered by
// preference. nil means the default browser-like order
// [AES-128-GCM, AES-256-GCM, CHACHA20].
CipherSuites []uint16
// ALPN is the NextProtos list advertised in the ClientHello. nil means
// ["h3"]: iron connections are presented as HTTP/3 to blend in with web
// traffic. Both endpoints must advertise a compatible list.
ALPN []string
// SNIHostnames is the pool of hostnames used as the random TLS server
// name (SNI) on each connection, so the dialed node id never appears on
// the wire. nil means a built-in pool of realistic-looking hostnames.
// A random name is picked per connection, including on retries.
SNIHostnames []string
}
TLSConfig customizes the TLS settings used for all peer connections. iron always negotiates TLS 1.3, whose cipher suites are fixed by the spec, so the tunables are the enabled KeyExchange groups, cipher-suite preference order, the advertised ALPN (masquerading as HTTP/3 by default) and the pool of hostnames used as the per-connection TLS server name (SNI). A zero TLSConfig keeps the hardened defaults.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package base holds the core identity types shared across iron.
|
Package base holds the core identity types shared across iron. |
|
Package discovery defines the interfaces through which an iron endpoint finds and advertises the direct UDP addresses of peers, independently of the relay.
|
Package discovery defines the interfaces through which an iron endpoint finds and advertises the direct UDP addresses of peers, independently of the relay. |
|
example
|
|
|
echo
command
Command echo demonstrates two iron endpoints exchanging a message over a QUIC connection established through a relay.
|
Command echo demonstrates two iron endpoints exchanging a message over a QUIC connection established through a relay. |
|
relay
command
Command relay runs a minimal iron relay server.
|
Command relay runs a minimal iron relay server. |
|
Package proto defines the iron relay wire protocol.
|
Package proto defines the iron relay wire protocol. |
|
Package relay implements the client side of the relay protocol.
|
Package relay implements the client side of the relay protocol. |
|
Package relayserver implements a minimal relay server: it authenticates clients by challenge/signature and forwards opaque QUIC datagrams between them.
|
Package relayserver implements a minimal relay server: it authenticates clients by challenge/signature and forwards opaque QUIC datagrams between them. |
|
Package stun implements the subset of RFC 5389 needed for NAT address discovery: a Binding request and the XOR-MAPPED-ADDRESS attribute of the Binding response.
|
Package stun implements the subset of RFC 5389 needed for NAT address discovery: a Binding request and the XOR-MAPPED-ADDRESS attribute of the Binding response. |