iron

package
v0.0.0-...-fe4e43c Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 27 Imported by: 0

README

iron — peer-to-peer networking dialed by public key

iron is a Peer-to-Peer (p2p) networking layer for Go. It lets nodes on different networks find each other and communicate without a public IP address and without configuring port forwarding, using a relay for connectivity and hole punching to upgrade to direct, low-latency connections when possible.

Every node is identified by a NodeID: the public half of an Ed25519 key pair. You dial by public key — you never need to know a peer's IP address in advance. Connections are authenticated peer-to-peer: each side proves it owns the public key it claims, over QUIC (via quic-go), using self-signed X.509 certificates whose key is the node's signing key.

Built on top of QUIC, iron gives you reliable, authenticated, multiplexed streams and unreliable datagrams between two endpoints.


How it works

  • Identity. A node is an Ed25519 key pair (base.NodeSecret). Its public key is the NodeID you share and dial.
  • Relay. Every node keeps a persistent authenticated WebSocket connection to one relay server (its lowest-latency one). The relay forwards opaque QUIC packets between nodes that cannot reach each other directly, so two peers behind NATs can still talk. When two peers live on different relays, the relays forward between themselves over a backbone link, so a node still needs only that single relay connection. Each relay pair keeps exactly one bidirectional backbone connection: the relay with the smaller address dials, the larger invites itself with a periodic Hello, and links are kept alive with a ping/pong keepalive (3s/9s) and re-established if a peer drops.
  • Directory. In the relay handshake each node publishes the UDP addresses it is reachable at (LAN, same-host, and its relay-observed public address) to its connected relay. The directory entry lives only as long as the relay connection, so it is purged the moment the node disconnects.
  • Lookup. When dialing, the endpoint asks its connected relay for the peer with a signed HTTP POST /relay/api. The relay answers from its own clients and, when needed, broadcasts the same lookup over HTTP to its full dynamic peer list (its -relays peers plus any learned via Hello, authenticated with the shared secret), then aggregates the answers. The client never has to contact the other relays itself — useful on restricted networks that can only reach their own relay. The endpoint then races a direct connection (using the peer's announced addresses, and hole punching through NATs when necessary) against a relay connection, and uses whichever establishes first.
  • Path maintenance. A connection upgrades from relay to direct once a direct path becomes reachable, and a dialed connection transparently falls back to the relay if the direct path dies — so the application keeps its streams without noticing.
Data flow
   dialer A                                  relay1                  relay2                          listener B
      |                                          |                      |                               |
      | 1. connect (WebSocket)                   |                      |                               |
      |----------------------------------------->|                      |  1. connect (WebSocket)        |
      |                                          |<---------------------|-------------------------------|
      | 2. announce addrs in handshake           |                      |                               |
      |----------------------------------------->|                      |                               |
      | 3. HTTP lookup B (POST /relay/api)       |                      |                               |
      |----------------------------------------->|                      |                               |
      |                                          | 4. HTTP lookup B     |                               |
      |                                          |  (Bearer secret)     |                               |
      |                                          |--------------------->|                               |
      |                                          |<---------------------|-------------------------------|
      | 5. aggregated answer (addrs + which      |                      |                               |
      |    relay found B)                        |                      |                               |
      |<-----------------------------------------|                      |                               |
      |                                          |                      |                               |
      | 6. QUIC packets over relay1 WS           | 7. forward to relay2  | 8. deliver to B (RelayToClient)|
      |----------------------------------------->| (RelayToRelayBatch) ->|------------------------------>|
      |                                          |                      |                               |
      | 6'. reply packets (B's QUIC)             |<-- relay2 forwards --|-------------------------------|
      |<-----------------------------------------|                      |                               |

Step by step:

  1. Each endpoint connects (WebSocket, authenticated) to its chosen relay — the first reachable in its WithRelayURLs list.
  2. In the handshake each endpoint announces the UDP addresses it is reachable at (LAN, same-host, relay-observed public). Disconnecting purges the entry immediately.
  3. Dialer A POSTs a signed APILookup for B's NodeID to its own relay (relay1) — the only relay A ever contacts.
  4. Relay1 answers from its own clients and, when B isn't there, broadcasts the same lookup over HTTP to its federated peers (relay2), authenticating with the shared secret (Authorization: Bearer <secret>). Peers answer locally only, so the broadcast never recurses.
  5. Relay1 aggregates: B's direct addresses, its observed public IP, and the relays that reported it (FoundRelays). A uses that to decide where to route.
  6. A's QUIC packets go over A's relay1 WebSocket; because B is on relay2, A's packets are tagged with B's relay and relay1 forwards them over the backbone (RelayToRelayBatch) to relay2, which delivers them to B (step 7–8). While doing so, relay2 learns that A is reachable via relay1, so B's reply packets (which carry no hint) are routed back over the backbone too.
  7. If A and B are directly reachable (LAN, same host, or after a successful hole punch), the connection upgrades to a direct one and the relays are no longer on the data path.

The relay control plane (handshake, keepalives, restart advisories, hole-punch coordination) uses CBOR-tagged messages; the HTTP directory lookup (POST /relay/api) uses the same signed CBOR messages.


Running a relay

A relay is a standalone server you (or others) run on a public IP. Peers connect to it over http:// (plain) or https:// (TLS). Relay URLs are always http(s)://; the WebSocket tunnel is derived from them internally. See iron/example/relay:

# a standalone relay
go run ./iron/example/relay -addr :3333 -url http://203.0.113.5:3333

# a federated relay: broadcast lookups to (and receive them from) other relays.
# -url must be this relay's own reachable URL; -secret must match the peers'.
# The relay with the smaller address dials the other; the larger one invites
# itself (a Hello) so the smaller dials — each pair keeps exactly one persistent
# backbone connection, even if only one side lists the other.
go run ./iron/example/relay -addr :3333 -secret <shared-secret> \
    -url http://203.0.113.5:3333 \
    -relays http://203.0.113.10:3333 -relays http://203.0.113.20:3333

You can pass an http:// URL to this address from any endpoint, e.g. http://203.0.113.5:3333 or http://127.0.0.1:3333 for local testing. Two relays configured with the same -secret, each other's URL in -relays and their own -url federate: they broadcast lookups to each other over HTTP (Bearer-authenticated), keep a single persistent backbone connection, and forward data between their clients over it. The relay logs each peer relay connect/disconnect like it does for clients.

Dynamic federation. The peer list is dynamic: relays connect to each other eagerly at startup, and peers can be added or removed at runtime without restarting (s.SetPeers(...) on relayserver.Server, or by passing -relays on the command line at start). Because the smaller-address relay dials while the larger one invites itself with a periodic Hello, a single backbone connection is formed even when only one side lists the other — so you can bring up relays one at a time. Backbone links are kept alive with a ping/pong keepalive (3s/9s); a silently-dead peer is detected and the link re-established.

Under the hood this is relayserver.Server.ListenAndServe:

logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
s := relayserver.NewServer()
s.Log = logger
s.Secret = "shared-secret"        // shared with the relays you federate with
s.Self = "http://203.0.113.5:3333" // this relay's own URL (backbone election)
s.Peers = []string{"http://203.0.113.10:3333"} // relays to federate with
if err := s.ListenAndServe(":3333"); err != nil {
    log.Fatal(err)
}

The server serves:

  • /relay — the WebSocket datagram tunnel (and the backbone link when the peer presents the shared secret), and
  • /relay/api — the HTTP directory: signed APILookup requests from clients, and Bearer-authenticated broadcasts between federated relays.

Using the library to connect endpoints

The iron package exposes the endpoint API. The simplest end-to-end example (copy of iron/example/echo): two endpoints meet through a relay, and the dialer opens a QUIC stream to the listener's NodeID, sends a message, and gets it echoed back.

1. Give each node an identity
import "github.com/skerkour/stdx-go/iron/base"

secret, err := base.NewNodeSecret() // a fresh Ed25519 identity
// keep it stable across restarts by persisting it:
// secret, err = base.NewNodeSecretFromBytes(privKeyBytes)
2. Create an endpoint
import "github.com/skerkour/stdx-go/iron"

const relayURL = "http://127.0.0.1:3333"

// context cancels the outgoing dials, not the endpoint's lifetime.
ctx := context.Background()

ep, err := iron.NewEndpoint(ctx, secret, "", iron.WithRelayURLs(relayURL))
if err != nil {
    log.Fatal(err)
}
defer ep.Close()

log.Printf("node id: %s", ep.NodeID()) // share this with your peer

NewEndpoint binds a UDP socket for direct connections, connects to the relay (if any are configured), announces this node's direct addresses, and starts accepting inbound connections. Relevant EndpointOptions:

  • iron.WithRelayURLs(urls ...string) — one or more relay ws(s):// URLs to use (dial/announce/lookup). Without any, the endpoint is relay-free: it only connects directly via announced addresses or discovery channels.
  • iron.WithRelayOnly() — never open a direct socket; only ever connect through the relay.
  • iron.WithTLSConfig(...) — customize the TLS used for all peer connections (see below). By default connections are hardened to blend in with web HTTP/3 traffic.
  • iron.WithSkipAnnounce(), iron.WithAnnouncers(...), iron.WithRelayBatching(...), iron.WithRelayWaitTimeout(...), iron.WithLogger(...) — see the package docs.
3. Listener: accept connections and echo
// accept the next inbound connection; it is authenticated (the peer's
// identity comes from the TLS certificate)
conn, err := ep.Accept(ctx)
if err != nil {
    log.Fatal(err)
}
remote, err := ep.PeerID(conn)       // the dialer's NodeID
log.Printf("connection from %s via %s", remote, conn.Path())

// echo every stream the peer opens
for {
    st, err := conn.AcceptStream(ctx)
    if err != nil {
        return
    }
    go func() { defer st.Close(); io.Copy(st, st) }()
}
4. Dialer: connect by NodeID and open a stream
peer, err := base.NodeIDFromString("...the listener's node id...")
if err != nil {
    log.Fatal(err)
}

conn, err := ep.Connect(ctx, peer) // dial by public key
if err != nil {
    log.Fatal(err)
}
defer conn.CloseWithError(0, "")

st, err := conn.OpenStreamSync(ctx)
if err != nil {
    log.Fatal(err)
}
if _, err := st.Write([]byte("hello")); err != nil {
    log.Fatal(err)
}
st.Close() // finish the send half; the read half stays open

reply, err := io.ReadAll(st)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("echo: %s\n", reply)
The Connection API

A *iron.Connection wraps quic-go's connection. It transparently keeps your dialed connection alive across network changes (falling back to the relay if a direct path dies, and upgrading relay → direct when possible), so you operate on streams and datagrams without managing paths:

  • StreamsOpenStream, OpenStreamSync, OpenStreamSync(ctx), OpenUniStream, AcceptStream, AcceptUniStream (bidirectional and unidirectional).
  • DatagramsSendDatagram, ReceiveDatagram (unreliable).
  • IntrospectionPath() ("direct" or "relay"), PeerID(), State(), Context().
  • conn.Path() tells you whether the connection is currently direct or relayed, e.g. for logging or metrics.
Peer discovery without the relay

The relay is not the only way to find peers. The iron/discovery package defines two roles you can plug into an endpoint:

import "github.com/skerkour/stdx-go/iron/discovery"

// lookup a peer's direct addresses (Discoverer), and/or announce ours
// (Announcer). Implement the interfaces for mDNS, a DHT, your own directory,
// etc.
ep, err := iron.NewEndpoint(ctx, secret, "", iron.WithRelayURLs(relayURL))
    iron.WithAnnouncers(myAnnouncer),
)

conn, err := ep.Connect(ctx, peer, iron.WithDiscoverers(myDiscoverer))

Traffic hardening (HTTP/3 masquerade)

Peer-to-peer connections are QUIC with TLS 1.3; everything after the handshake is encrypted. To keep iron traffic from standing out at the TLS layer, the defaults make each connection look like ordinary web HTTP/3:

  • ALPN h3 — the TLS ClientHello advertises h3, the HTTP/3 protocol identifier, exactly like a browser. The node id or an iron-specific application protocol never appears on the wire.
  • Browser-like ClientHello — the three TLS 1.3 cipher suites are offered in browser order (AES-128-GCM, AES-256-GCM, CHACHA20) and the KeyExchange groups default to [X25519, X25519MLKEM768], matching modern browsers.
  • Random SNI — instead of a synthetic <nodeid>.iron.invalid name (which leaked the node id and the iron TLD), each connection uses a random realistic-looking hostname from a built-in pool, so the dialed identity never appears in the cleartext Server Name Indication.
  • Certificate matches the SNI — the server mints its self-signed certificate on the fly with the requested SNI as its DNS subject alternative name, so the handshake is internally consistent. Authentication is unchanged: the peer's certificate still carries exactly its Ed25519 public key (VerifyConnection), so a middlebox cannot impersonate anyone.

These are tunable via iron.WithTLSConfig(...):

iron.WithTLSConfig(iron.TLSConfig{
    ALPN:             []string{"h3"},                 // advertise something else
    CipherSuites:     []uint16{tls.TLS_AES_128_GCM_SHA256},
    CurvePreferences: []tls.CurveID{tls.X25519},
    SNIHostnames:     []string{"cdn.example.net"},    // SNI pool
})

Both endpoints must advertise a compatible ALPN list or the handshake fails (see TestMismatchedALPNFails).

Known limitations: the QUIC transport parameters and the exact ClientHello extension set still fingerprint quic-go/Go's TLS stack (there is no GREASE or Encrypted ClientHello in Go), and the relay path itself is plaintext HTTP/1.1 + WebSocket. The hardened defaults only mask the peer-to-peer QUIC layer.


Try it end-to-end

# terminal 1 — a relay
go run ./iron/example/relay

# terminal 2 — the listener (note the printed node id)
go run ./iron/example/echo -relay http://127.0.0.1:3333 -mode listen

# terminal 3 — the dialer
go run ./iron/example/echo -relay http://127.0.0.1:3333 \
    -mode connect -peer <the-listener-node-id>

You should see echo round-trip OK on the dialer, and the listener will report that the connection started relayed and upgraded to direct (both endpoints are on the same host).

Backbone test across two relays

Run two federated relays and put each endpoint on a different one; the dialer finds the listener through the relay-to-relay lookup broadcast and the data travels over the backbone:

# terminal 1 — relay 1
go run ./iron/example/relay -addr :3333 -secret s3cret -url http://127.0.0.1:3333 \
    -relays http://127.0.0.1:4444

# terminal 2 — relay 2 (federated with relay 1)
go run ./iron/example/relay -addr :4444 -secret s3cret -url http://127.0.0.1:4444 \
    -relays http://127.0.0.1:3333

# terminal 3 — the listener, connected to relay 2 only
go run ./iron/example/echo -relay http://127.0.0.1:4444 -mode listen

# terminal 4 — the dialer, connected to relay 1 only (note the node id)
go run ./iron/example/echo -relay http://127.0.0.1:3333 \
    -mode connect -peer <the-listener-node-id>

Add -relay-only to both echo endpoints to force all traffic through the relays, so you can observe the pure relayed path end to end.

Running all on one machine works because same-host peers get a fast loopback connection; across the internet they connect through the relay (and directly when hole punching succeeds).

TODO

  • make relay a binary, not a library?
  • ensure that client batching is really usefull
  • quic / or http3 connection from endpoints to relay (will need to handle when clients migrate IP), fallback to websocket.

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

View Source
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.

View Source
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

func (conn *Connection) AcceptStream(ctx context.Context) (*quic.Stream, error)

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

func (conn *Connection) OpenStreamSync(ctx context.Context) (*quic.Stream, error)

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

func (endpoint *Endpoint) Close() error

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

func (endpoint *Endpoint) NodeAddr() base.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) NodeID

func (endpoint *Endpoint) NodeID() base.NodeID

NodeID returns this endpoint's public identity.

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

func (endpoint *Endpoint) PublicAddr() *net.UDPAddr

PublicAddr returns this endpoint's public UDP address as observed by the relay, or nil until the first successful discovery.

func (*Endpoint) SetAnnouncedAddrs

func (endpoint *Endpoint) SetAnnouncedAddrs(addrs []*net.UDPAddr) error

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.

Jump to

Keyboard shortcuts

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