Reaching a peer through a node both ends can already reach. More...
#include "librats/util/rats_export.h"#include "librats/core/service_registry.h"#include "librats/node/peer_network.h"#include "librats/peer/peer_id.h"#include "librats/subsystems/relay_service.h"#include "librats/transport/relay_link.h"#include <atomic>#include <chrono>#include <condition_variable>#include <cstdint>#include <memory>#include <mutex>#include <thread>Go to the source code of this file.
Classes | |
| class | librats::Relay |
| struct | librats::Relay::Config |
Namespaces | |
| namespace | librats |
Reaching a peer through a node both ends can already reach.
The last rung of the connectivity ladder. A direct dial handles the easy cases; PortMappingService and HolePunch handle most of the rest. What is left is the pair of nodes for which no endpoint either one can advertise will ever work — a symmetric NAT on one side, a network that drops UDP and blocks inbound TCP, a punch that simply never lands. For them the only way through is to borrow a path: a node connected to both carries the bytes.
── What is relayed, and what is not ──────────────────────────────────────── The relayed thing is a byte stream, not a message. It becomes a Circuit and a RelayLink (transport/relay_link.h), the reactor adopts it as an ordinary Connection, and everything above the Link runs unchanged — which is why:
── Finding a relay ───────────────────────────────────────────────────────── This stage covers the case where the two ends share a peer — the same topology HolePunch already relies on for its rendezvous, and the one a bootstrap or DHT mesh produces on its own. The initiator asks a few of its peers "do you hold this id?" (Probe) and opens a circuit with the first that says yes. Probing first, rather than opening with everybody at once, is what keeps a successful search from producing three redundant connections to the same peer — each with its own handshake — for the peer table to then tear two of down.
Not covered here, deliberately: reaching a peer with whom we share NO peer. That needs reservations (a node asking a relay to hold a slot for it) and a way to advertise "reachable via R" as an address, which touches identify and PEX. The version byte and the free op-codes in the wire format leave room for it.
── Getting off the relay again ───────────────────────────────────────────── A circuit is a fallback, not a destination: it costs a third node bandwidth and a round trip. So when one comes up, this module asks HolePunch to try the target again — now that the two ends are peers, they can exchange what a punch needs over the very circuit that is carrying them. If the punch lands, PeerTable::add prefers the direct link at BOTH ends (any direct transport outranks Relay) and swaps the route with no disconnect event: the application never sees the seam.
── Carrying other peers' traffic ─────────────────────────────────────────── Serving as a relay is off by default. Forwarding a rendezvous, as HolePunch does, is a few dozen bytes; forwarding a connection is somebody else's file transfer on your uplink, and that is a decision to be made rather than assumed. A node that turns it on is protected by, in order of how much they matter:
── Threading ─────────────────────────────────────────────────────────────── Message handlers run on reactor threads; one worker thread drives attempt timeouts and the relay's own expiry. The tables are behind one mutex, which is never held across a call into a circuit or the reactor — a circuit is touched only by the reactor thread that owns its connection, which is by construction the thread that owns its carrier's (see node/circuit_service.h).
Definition in file relay.h.