Peer discovery via the Kademlia DHT — a thin adapter, not a rewrite. More...
#include "librats/util/rats_export.h"#include "librats/node/peer_network.h"#include "librats/subsystems/dht_service.h"#include "librats/dht/dht.h"#include <atomic>#include <chrono>#include <condition_variable>#include <cstdint>#include <memory>#include <mutex>#include <optional>#include <string>#include <thread>#include <unordered_map>#include <vector>Go to the source code of this file.
Classes | |
| class | librats::DhtDiscovery |
| struct | librats::DhtDiscovery::Config |
| class | librats::DhtDiscovery::Redials |
| Which addresses found to dial, and how: a policy of times alone. More... | |
| struct | librats::DhtDiscovery::Key |
Namespaces | |
| namespace | librats |
Peer discovery via the Kademlia DHT — a thin adapter, not a rewrite.
Wraps the existing, well-tested DhtClient (src/dht.h) as a Subsystem WITHOUT modifying it. On start it brings up DhtClients (their own UDP sockets + threads), then periodically announces our TCP listen port under a discovery hash and searches that hash, dialing any peers it finds through the node. The discovery hash namespaces peers of the same application (same key → same hash).
Dual-stack: IPv4 and IPv6 are separate Kademlia networks (BEP 32), so each runs its own DhtClient. Both announce/search the same hash and feed discovered peers to the same dial path, so a peer reachable over either family is found. Startup is best-effort per family: if one family can't bind (e.g. a host with no usable IPv6) the subsystem still runs on the other.
Two nodes behind NATs find each other here but cannot dial each other: each NAT drops what comes in unless something went out to that peer first. Both dial what they find, though, so each one's dial opens the way for the other's — if they overlap. A peer found and not connected is therefore dialed again, as a punch (DialProfile::punch(): dense probes over the shared datagram socket), every few seconds for a while after it was first found, found again or not (Redials) — or after it was last found connected, a link that went since dialed for anew — and after that each time it is found again, once a round, for as long as the DHT hands it out: a peer that comes online later at an address found before (the DHT keeps an announce half an hour, and a NAT often keeps a port) is met as its own punches, every few seconds, land while ours keep our NAT open to it.
The port announced is the one peers are to dial. Over IPv6, which has no NAT, the listen port; over IPv4 the one the router forwards to it (PortMapped, from PortMappingService), or else the one the NAT maps the shared datagram socket to — asked of STUN from that socket (ReflexiveAddressService), as most carrier-grade NATs pick another — or the listen port. STUN is asked again every so often: that keeps the mapping alive while no peer does, and finds it anew if it moved.
Several keys: one DHT, each key's hash announced and searched in its turn — what a node meets several sets of peers under (an application's peers that share a secret each: a hash only they work out). They change as the node runs (set_keys()): a key new is announced, and so searched, at once; one gone is no longer announced, and the DHT forgets it within half an hour. A key may be announced alone (who asks for it finds the node; the node seeks nobody, and dials none of what its announces' lookups hand back), or searched alone (the node finds who is there, and is not found there). A DHT that knows no node yet (bootstrapping) is asked nothing: its lookup would end at once with none, and an announce so lost leave the node unfound until the next — what is held back goes half a second on, then twice as long each time up to a round (a family that never knows a node, IPv6 with no route out, is not asked of twice a second for ever).
Everything DHT-specific stays in DhtClient; this class only bridges its callbacks to PeerNetwork::connect().
Definition in file dht_discovery.h.