Transports
How nodes reach each other. A node is its node ID, from its node key; a transport
only says where it is (peers.md, node IDs and addresses). Continuum has three
transports, each a cargo feature (--features mycelium,libp2p,tor), any of them at
once. Which a node uses is --<transport>-enable, and in which order
--transport-order (configuration.md, Transports); the options below are each
transport's.
| Mycelium | libp2p | Tor | |
|---|---|---|---|
| What it is | An encrypted overlay network, addressed by keys; a daemon per node (supervised, or one running elsewhere) | Direct connections, TCP (Noise, Yamux) and QUIC | An onion service per node (arti, embedded) |
| Through NAT and firewalls | Yes: the overlay routes around them, through its peers | With help: a forwarded or UPnP-mapped port, a relay, hole punching | Yes: onion services need no open port |
| Addresses | mycelium:<public key> |
libp2p:<multiaddr>/p2p/<peer ID> |
tor:<onion>.onion:<port> |
| Encryption | End to end (the overlay) | Per connection, between the two nodes, through a relay too (Noise, QUIC's TLS) | End to end (Tor) |
| Round trips | Those of the overlay's routes | The network's: the fastest on a LAN or VPN | A second or more |
| Needs | Overlay peers to connect through (--mycelium-peers), or a daemon elsewhere |
A listen address on at least one side of each pair, or a relay | Time to bootstrap; arti's onion services are still experimental |
Several transports
A node's addresses, one or more per transport, are addresses in GET /admin/stats.
A node on several transports reaches a peer on the first one in its order that both
have, until the paths are measured, then on the fastest; a send that fails goes over
the next at once (peers.md, node IDs and addresses). A node on several also carries
the traffic between nodes that share none, through the document: it syncs with its
neighbours on each side, and the changes travel through its copy. No node forwards
another's messages: a node that bridges two transports holds the document.
Within one transport, networks are bridged by that transport: a Mycelium daemon (one
without continuum will do) routes for the overlay, and a libp2p relay (a node with
--libp2p-relay, or continuum with --relay-only, which holds no document) carries
connections between nodes that can't dial each other.
Mycelium
Each node runs its own Mycelium daemon, supervised: started with the node, and
stopped with it. --mycelium-peers names the overlay peers it connects through
(tls://host:9651, not continuum peers; public Mycelium nodes are used only if listed
there), and --mycelium-tcp-listen-port the port it listens on for overlay peers (a
free one without). Its key is its identity on the overlay, and so its address:
--mycelium-key-file keeps it across restarts (a new one each start without), and
--mycelium-key-create makes it on the first start. --mycelium-network-key-file and
--mycelium-network-name join a private Mycelium network instead of the public one.
--mycelium-rest-url uses a daemon running elsewhere, through its REST API, instead
of supervising one; then the options above don't apply: those of a supervised daemon
can't be combined with it (configuration.md, unsetting and conflicting options), and
--mycelium-peers is left as it is but unused (that daemon has peers of its own). Seeds are a node's address,
its Mycelium public key or IPv6 address, or a host name with an AAAA record for one.
When Mycelium doesn't work
The supervised daemon's own output goes where the node's does, so its complaints are in the node's log, between the node's lines.
Starting the supervised Mycelium daemonwithRunning `mycelium`: themyceliumprogram isn't on the node'sPATH. Supervising needs Linux (the keys are handed over in memory, and the daemon dies with the node); elsewhere, run the daemon yourself and give--mycelium-rest-url.Querying local Mycelium identity (no answer within 30s): the daemon didn't answer its API: it failed to start (its output says why: a port taken, a bad key file), or--mycelium-rest-urlpoints elsewhere.- Nobody reaches the node, nor it anyone (seeds stay unanswered,
refusedgrows in the traffic,Mycelium rejected outbound message): the daemons aren't connected on the overlay. A supervised daemon connects only to--mycelium-peers(by defaulttls://127.0.0.1:9651, a daemon on the same machine) and to who connects to it (--mycelium-tcp-listen-port, which must then be reachable); it doesn't discover peers on the LAN. Nodes of a private network need the same name and key, or they don't see each other. - A peer's address changes at every restart (
addressChangesin the transports' figures,measurements.md): it has no--mycelium-key-file, so its daemon gets a new key each start. The node ID stays, so this costs a WHO round, but seeds given as its Mycelium address go stale. Discarded messages Mycelium queued for us before we started(info), and a fewstaleHelloReplies: normal after a restart; Mycelium keeps messages for a while.- The daemon exits: the node stops (
A transport stopped, error), to be restarted by its supervisor.
libp2p
libp2p connects directly, over TCP (Noise, Yamux) and QUIC: --libp2p-listen says
where to listen (e.g. /ip4/0.0.0.0/tcp/9661), and the addresses it listens on go
into the member list as libp2p:<multiaddr>/p2p/<peer ID>, loopback and link-local
ones left out (unless there are no others). --libp2p-announce publishes other
addresses instead, e.g. a public address forwarded to the listen port. A port forwarded
on the router needs no announce: once AutoNAT finds the node dialable at the router's
address (peers tell it where they see it, and dial it back there), that address is
published next to the listen addresses. A node's entry in the member list takes its
transports' addresses in turns (each one's best first), 16 at most, so many libp2p
addresses don't crowd out its other transports'. Connected peers
tell each other where they listen (libp2p's identify), so a node also learns where
peers that dialled it can be reached. A node with no reachable listen address can still
take part: it connects out, and its peers answer over that connection (each pair of
peers needs one side that can be dialled). Two peers connected only through a relay
(below) dial each other directly where they know an address for the other; that fails
behind firewalls, so it's tried again after 30 seconds, then less often, up to every 10
minutes. A directly connected peer's other addresses (from identify) are dialled too,
for a better path: of several connections to a peer, one is kept 20 s after the last
opened, a direct one before a relayed one, then the one with the shortest round trip
measured since then (so a new ethernet path can win over a WLAN connection hours old).
Relays
A node nobody can dial (behind NAT, or with its port closed) can be reached through a
relay instead: a node of the swarm that others can dial, started with --libp2p-relay.
A relay says so in the member list (each of its addresses again, ending in
/p2p-circuit), and a node that needs a relay picks up to two of those by itself. It
needs one when it has no address others could dial, or when AutoNAT (connected peers
dialling it back) says it can't be dialled; once AutoNAT says it can, it lets them go.
A slot lost is reserved again at once. A relay that refuses a slot (it has none left,
doesn't allow us, isn't a relay, or another peer answers at its address) isn't picked
again for 10 minutes; one that doesn't grant it otherwise (it doesn't answer, or the
connection closed) isn't for 30 seconds. Give relays a --libp2p-key-file (with
--libp2p-key-create, made on the first start): without
one, a relay's peer ID changes with every start, and the addresses others have for it,
and for the nodes behind it, are wrong until the member list catches up.
--libp2p-relays /ip4/203.0.113.7/tcp/9661 (with or without the relay's
/p2p/<peer ID>) names relays to pick first, before members: used the same way, only
while the node needs a relay. Any libp2p node that serves circuit relay v2 will do,
but most keep circuits short (2 minutes and 128 KiB by default): enough to punch holes,
not to carry traffic where that fails. A relay configured without its peer ID is
connected to, to find it out, and again when it doesn't grant a slot.
Relays for the whole swarm can be listed in the document instead, so no node needs
them configured: a user in --protocol-writers writes their addresses (as libp2p:
addresses, with or without /p2p/<peer ID>, at most 32) to _continuum/relays:
curl -u admin:secret -X PUT localhost:3000/store/_continuum/relays \
-H 'Content-Type: application/json' \
-d '["libp2p:/ip6/fd00::10/tcp/9661", "libp2p:/ip6/fd00::11/tcp/9661"]'
Every member picks among them as among configured relays, after those, and before
members that relay; the list goes into new epochs with the rest of _continuum.
A relay need not be a node: --relay-only runs continuum as a libp2p relay alone, for
machines that should relay but hold no document (stable hosts on a VPN, say). It has
no node key, joins no swarm and keeps no files but its libp2p key; nodes don't take it
for one of them (it doesn't speak continuum's protocol), nor for a seed: a node given it
as one says so (Initial HELLO failed, "a relay, not a node"); its seed is a node behind
the relay (<relay>/p2p-circuit/p2p/<peer ID>, or /p2p/<peer ID> with the relay in
--libp2p-relays). It needs transports = ["libp2p"], libp2p-listen and libp2p-key-file (a relay's peer ID must stay the
same); options about a swarm or a document are errors with it. With htpasswd, its
HTTP API has /health, /admin/stats (its addresses, ready to list, and its
transport figures) and /admin/log; without, it has none.
relay-only = true
[libp2p]
enable = true
listen = ["/ip6/::/tcp/9661", "/ip6/::/udp/9661/quic-v1"]
key-file = "/var/lib/continuum/libp2p.key"
Then list it for the swarm (_continuum/relays, above), or configure it on the nodes
that need it (--libp2p-relays). A node
publishes its address through a relay (…/p2p/<relay>/p2p-circuit/p2p/<peer ID>) with
the others. Peers that reach
it through the relay then try to connect directly (hole punching, libp2p's DCUtR); where
that fails, the relay carries the traffic. Hole punching needs both to listen, though
the firewall may stay closed (/ip6/::/udp/0/quic-v1: QUIC sends from where it
listens). Each offers where the relay saw it and where it listens, so nodes on one LAN
meet there, rather than at their router's public address. A punch that fails is tried
again after a minute, then after 5, then every 30 minutes, while the peers stay
connected through the relay: what failed may have been passing. A relay takes at most 1024 circuits (64 per
peer), each for up to an hour and 1 GiB, and doesn't limit how often they're asked for; a circuit that ends is made again on the next
message. Only relays of the swarm are used, unless the node is told to use public
infrastructure (below). How it goes (AutoNAT's verdict, connections direct or relayed,
relay slots, hole punches, failed dials) is in /admin/stats under
transports.libp2p, and each peer's connection in /peers (measurements.md,
Transports).
Address ranges
With --libp2p-ignore, libp2p leaves address ranges out, e.g. 10.244.0.0/16 and
42::/16 for VPNs it should neither depend on nor take a detour through: addresses
there are neither published (announced ones are, whatever they are) nor told peers
that connect nor offered for hole punching nor dialled, and connections there are
refused, whoever dials. Peers connected to the node forget its addresses there; others
when they next connect. Addresses of peers there aren't kept either (nor shown, nor
passed on), nor those through a relay reached there, as they're never dialled. libp2p still listens there, if told to listen
on every interface: the log (at debug level) says which addresses are left out. A node
listening on every interface (0.0.0.0, ::) has many addresses, and a node
publishes 4 direct ones at most (those the router or AutoNAT found first, then those in
--libp2p-prefer),
and 2 through relays: leaving VPN ranges out keeps the ones that matter. A relay reached
only through a left-out range (or one without a route) can't be used then, and isn't
tried.
Ranges this node has no route to are left out alike, without being named: the operating system is asked whether it has a route to a peer's address (per /24 or /64, again every 30 s), and where it has none, such addresses aren't kept or dialled. A node without IPv6 to the internet thus leaves peers' public IPv6 addresses out; once it has a route again, they're taken again as they're heard of next. Each range that loses or gets its route is logged (at debug level).
With --libp2p-internet-hosts, e.g. one.one.one.one (names or addresses; names are
resolved every 30 s, so both families are covered), the node also asks whether it has a
route to the internet at all, per IPv4 and IPv6: where it has none, it doesn't publish
its own public addresses of that family, which nobody could get answers from; nor any
public address of its own before the first answer (a few seconds after starting).
Nothing is sent to those hosts; only the routing table is asked. Without it, or for a family
none of them resolves to, this isn't asked, and every address is published.
With --libp2p-prefer, the ranges that matter most come first, best first, e.g. the
LAN's 10.23.0.0/16: of the addresses a node listens on, those there are published
first, and of the addresses a connected peer tells (identify), they are kept first
(a node keeps 8 per peer). Without it, and after the preferred ones, public addresses
come first (IPv4 outside the private, shared and reserved ranges; IPv6 in global
unicast, 2000::/3), then those through relays, then all others (container bridges,
other LANs, overlays such as Mycelium's 400::/7). Nodes on one LAN that are connected only through a relay find each
other's LAN address this way, and dial it. A preferred range all inside an ignored one
is refused (nothing there would be used); one with an ignored range inside it is
taken, with a warning: the ignored part stays left out.
Through the router: UPnP
With --libp2p-upnp, a node asks its router to forward its listen ports (UPnP, which
most home routers offer, IPv4), and publishes where: the router's public address, next
to its listen addresses. Nodes elsewhere then dial it directly, with neither a relay
nor hole punching, which matters where the other side's NAT defeats punching (mobile
networks' carrier-grade NAT). It needs a fixed port to be useful across restarts
(/ip4/0.0.0.0/udp/9661/quic-v1), and different ones for nodes behind one router. The
log says what the router did (The router forwards to us), or that no router answered;
a router behind another NAT maps nothing useful, and the log says so too. The ports are
open on the router for as long as the node runs; --libp2p-announce takes precedence.
Public infrastructure
With --libp2p-public, a node uses public libp2p infrastructure: it joins the IPFS DHT
(Kademlia, through the IPFS bootstrap nodes at /dnsaddr/bootstrap.libp2p.io), where it
finds addresses of peers the member list doesn't have, and public peers that relay;
those are picked after members that relay. Public relays keep circuits short (2 minutes
and 128 KiB by default): enough to punch holes through NATs, not to carry a swarm's
traffic for long. It also makes the node's peer ID and addresses known to the DHT, so
to anyone. Without it, nothing public is used. (For Mycelium, public peers are in
--mycelium-peers: none unless given.)
On the LAN
With --libp2p-mdns, a node finds libp2p peers on its LAN (multicast DNS, IPv4): it
notes their LAN addresses, and those that speak continuum are seeds while it joins, so
nodes on one LAN find each other without --seeds. On a LAN with several swarms, a new
node without a state file joins whichever answers first, unless a swarm key
(--swarm-key-file) keeps it out of the others. Seeds for it are such
multiaddrs, with or without the peer ID and with or without the libp2p: scheme
(/ip6/fd00::1/tcp/9661, libp2p:/ip6/fd00::1/tcp/9661/p2p/12D3…); without the peer ID,
the node connects to find out who is there. A peer ID alone (libp2p:/p2p/12D3…) is
reached where the node knows it, or else through the relays it knows (--libp2p-relays,
above): a seed behind a relay, without spelling out the circuit. It doesn't get through NAT yet, and never uses
public libp2p infrastructure (bootstrap nodes, relays).
Tor
Tor (--tor-enable, in builds with the tor cargo feature) is for reachability:
every node runs an onion service (with arti, embedded), which other nodes reach from
anywhere, without a forwarded port or a relay. It isn't for anonymity: a node's other
addresses are in the member list next to its onion address. --tor-state-dir is where
arti keeps its state and cache, the onion service's key among them, which makes its
address: keep it with the node, like the node key. --tor-port is the onion service's
port (9662). A node's address is its onion service, tor:<onion>.onion:<port>, which is
also what a seed gives (one ending in /<node key>, as nodes published them before,
still works). Arti bootstraps in the background, which takes a while: the node starts
without waiting, sends over Tor wait for it, and a new onion service may take a minute
or two to be reachable.
Onion services prove themselves to who connects, but not the other way: at the start of
every connection, both sides prove their node key in a challenge handshake bound to the
onion address dialed (transport/challenge.rs), and the dialer says which onion service
is its own. The node dialed believes that once a dial there found the same node key: it
dials back to check if it hasn't dialed there yet, once, and closes that connection
again; a claim that failed that check is refused without dialing for 10 minutes. Nodes
keep one connection per pair. Round trips over Tor take a second or more, so a peer on a
faster transport too is reached there while it works (the paths layer picks by
round-trip time). Arti's onion services are still experimental: expect them to be less
reliable than the other transports.
Keys
Each key file is read when the node starts; a missing one is an error, unless the option creating it is given (a new key, in a new file only its owner may read):
| Key | File | Created with | A new one means |
|---|---|---|---|
| Node | --node-key-file |
--node-key-create |
A new node |
| Swarm | --swarm-key-file |
--swarm-key-create |
Only on the swarm's first node: the others need a copy |
| Mycelium | --mycelium-key-file |
--mycelium-key-create |
A new Mycelium address |
| libp2p | --libp2p-key-file |
--libp2p-key-create |
A new peer ID |
Tor's onion service key is in --tor-state-dir, made by arti.
Figures
How the transports find and reach peers: measurements.md, Transports.
