Back to Site
Loading...
Searching...
No Matches
relay.h File Reference

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>
Include dependency graph for relay.h:

Go to the source code of this file.

Classes

class  librats::Relay
 
struct  librats::Relay::Config
 

Namespaces

namespace  librats
 

Detailed Description

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:

  • the Noise_XX handshake is END TO END. The relay moves ciphertext it cannot read, cannot forge and cannot replay. It is a pipe, not a party, and it learns nothing beyond who is talking to whom and how much;
  • the peers authenticate each other's real keys, so a relay cannot substitute itself for either end;
  • every subsystem — pub/sub, file transfer, PEX — works over a relayed peer with no code of its own. To the application it is simply a peer, and PeerInfo::transport is the only thing that says otherwise.

── 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:

  • the end-to-end credit window (transport/relay_link.h), which bounds what one circuit can make the relay hold no matter what either end does. Without it a slow receiver would grow the relay's send queue until the relay dropped that peer entirely — losing all of its traffic, not just the circuit's;
  • forwarding only between peers it already holds. A relay never dials, never resolves an address, and can only ever deliver to somebody that chose to connect to it, so it cannot be turned into an open reflector;
  • no chaining. A circuit is refused if either end is itself only reachable through a relay, which is what stops loops and multi-hop amplification;
  • per-circuit byte and duration caps, per-peer and total circuit counts, and a rate limit on the requests themselves.

── 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.