Files

242 lines
8.2 KiB
C++
Raw Permalink Normal View History

/// # Tutorial 11: Circuit Relay Connecting Through Firewalls
///
/// Not all peers are directly reachable. Some are behind NAT, firewalls,
/// or have no public IP. Circuit Relay solves this by having a
/// **relay node** forward traffic between peers.
///
/// ## The Three-Node Pattern
///
/// Circuit relay uses three roles:
///
/// ```
/// Client <--> Relay <--> Destination
/// (B) (R) (A)
/// ```
///
/// - **Destination** (A) — The peer behind NAT that wants to be reachable.
/// It connects to the relay and reserves a slot.
/// - **Relay** (R) — A publicly reachable node that forwards traffic.
/// - **Client** (B) — A peer that wants to connect to A through R.
///
/// ## How it works
///
/// 1. The Destination connects to the Relay and calls
/// `circuitRelayReserve()` to request a relay slot.
/// 2. The Relay confirms the reservation and returns relay addresses.
/// 3. The Client dials the Destination via the Relay using
/// `dialCircuitRelay()`.
/// 4. The Relay transparently forwards stream data between them.
///
/// > **Note**: Circuit relay adds latency and bandwidth overhead on the
/// > relay node. Use it only when direct connections are impossible.
///
/// -----------
#include <cstdio>
#include <chrono>
#include <cstdint>
#include <thread>
#include <string>
#include <vector>
#include "plugin.h"
int main()
{
printf("=== Tutorial 11: Circuit Relay ===\n\n");
/// ## Step 1: Create three nodes
///
/// - **Relay** (port 9890): publicly reachable, `circuitRelay: true`
/// - **Destination** (port 9891): behind NAT
/// - **Client** (port 9892): wants to connect to Destination
///
/// The Relay and Destination must be connected before the reservation.
/// The Client connects to the Relay before dialing the Destination.
// Relay node — must have circuitRelay enabled
Libp2pModuleOptions optsRelay;
optsRelay.addrs = {"/ip4/127.0.0.1/tcp/9890"};
optsRelay.circuitRelay = true;
// Destination node (behind NAT)
Libp2pModuleOptions optsDest;
optsDest.addrs = {"/ip4/127.0.0.1/tcp/9891"};
optsDest.circuitRelayClient = true;
// Client node
Libp2pModuleOptions optsClient;
optsClient.addrs = {"/ip4/127.0.0.1/tcp/9892"};
optsClient.circuitRelayClient = true;
Libp2pModuleImpl relay(optsRelay);
Libp2pModuleImpl dest(optsDest);
Libp2pModuleImpl client(optsClient);
printf("Starting nodes...\n");
2026-07-24 16:06:11 +02:00
StdLogosResult relayStartRes = relay.start();
if (!relayStartRes.success) {
fprintf(stderr, "Relay failed: %s\n", relayStartRes.error.c_str());
return 1;
}
2026-07-24 16:06:11 +02:00
StdLogosResult destStartRes = dest.start();
if (!destStartRes.success) {
fprintf(stderr, "Destination failed: %s\n",
destStartRes.error.c_str());
return 1;
}
2026-07-24 16:06:11 +02:00
StdLogosResult clientStartRes = client.start();
if (!clientStartRes.success) {
fprintf(stderr, "Client failed: %s\n",
clientStartRes.error.c_str());
return 1;
}
printf("All three nodes started\n");
/// ## Step 2: Get node addresses
2026-07-24 12:01:21 +02:00
StdLogosResult infoRelayRes = relay.peerInfo();
if (!infoRelayRes.success) {
fprintf(stderr, "Failed to get relay info: %s\n",
infoRelayRes.error.c_str());
return 1;
}
auto infoRelay = infoRelayRes.value;
std::string relayPeerId = infoRelay["peerId"].get<std::string>();
std::vector<std::string> relayAddrs;
for (const auto& a : infoRelay["addrs"])
relayAddrs.push_back(a.get<std::string>());
2026-07-24 12:01:21 +02:00
StdLogosResult infoDestRes = dest.peerInfo();
if (!infoDestRes.success) {
fprintf(stderr, "Failed to get destination info: %s\n",
infoDestRes.error.c_str());
return 1;
}
auto infoDest = infoDestRes.value;
std::string destPeerId = infoDest["peerId"].get<std::string>();
printf("Relay peer ID: %s\n", relayPeerId.c_str());
printf("Destination peer ID: %s\n", destPeerId.c_str());
/// ## Step 3: Destination connects to the Relay and reserves a slot
///
/// The Destination must connect to the Relay first, then call
/// `circuitRelayReserve()` to request a reservation. The relay
/// returns the addresses the Destination can be reached at (via relay).
printf("\nDestination connecting to relay...\n");
2026-07-24 16:06:11 +02:00
StdLogosResult destConnectRes =
dest.connectPeer(relayPeerId, relayAddrs, 5000);
if (!destConnectRes.success) {
fprintf(stderr, "Destination failed to connect to relay: %s\n",
destConnectRes.error.c_str());
return 1;
}
printf("Destination connected to relay\n");
printf("Destination requesting relay reservation...\n");
2026-07-24 12:01:21 +02:00
StdLogosResult reserveRes = dest.circuitRelayReserve(relayPeerId, relayAddrs);
if (!reserveRes.success) {
fprintf(stderr, "Relay reservation failed: %s\n",
reserveRes.error.c_str());
return 1;
}
if (reserveRes.value.empty()) {
fprintf(stderr, "Reservation did not return relay addresses\n");
return 1;
}
printf("Relay reservation successful!\n");
printf("Relay addresses:\n");
for (const auto& addr : reserveRes.value) {
printf(" %s\n", addr.get<std::string>().c_str());
}
/// ## Step 4: Client connects to the Relay, then dials Destination
///
/// The Client connects to the Relay (same as any other peer), then
/// uses `dialCircuitRelay()` to reach the Destination through the Relay.
printf("\nClient connecting to relay...\n");
2026-07-24 16:06:11 +02:00
StdLogosResult clientConnectRes =
client.connectPeer(relayPeerId, relayAddrs, 5000);
if (!clientConnectRes.success) {
fprintf(stderr, "Client failed to connect to relay: %s\n",
clientConnectRes.error.c_str());
return 1;
}
printf("Client connected to relay\n");
/// Now the Client dials the Destination's peer ID through the relay.
/// The multiaddr comes from the reservation response, with `/p2p-circuit`
/// appended so libp2p routes the stream through the Relay.
printf("Client dialing destination through relay...\n");
std::string relayDialAddr;
relayDialAddr = reserveRes.value[0].get<std::string>() + "/p2p-circuit";
/// For circuit relay, we use a well-known protocol to test connectivity.
/// The ping protocol works well for this.
2026-07-24 12:01:21 +02:00
StdLogosResult dialRes = client.dialCircuitRelay(
destPeerId,
relayDialAddr,
"/ipfs/ping/1.0.0");
if (!dialRes.success) {
fprintf(stderr, "Circuit relay dial failed: %s\n",
dialRes.error.c_str());
fprintf(stderr, "Circuit relay may need configuration tuning.\n");
fprintf(stderr, "Ensure the relay node has circuitRelay=true and the\n");
fprintf(stderr, "destination has connected and reserved a slot.\n");
return 1;
}
uint64_t streamId = dialRes.value.get<uint64_t>();
printf("Circuit relay stream opened! id: %llu\n",
(unsigned long long)streamId);
// Send a ping through the relay:
std::string payload(32, '\0');
for (int i = 0; i < 32; ++i) payload[i] = static_cast<char>(i);
2026-07-24 16:06:11 +02:00
StdLogosResult writeRes = client.streamWrite(streamId, payload);
if (!writeRes.success) {
fprintf(stderr, "Write failed: %s\n", writeRes.error.c_str());
return 1;
}
printf("Sent %zu bytes through relay\n", payload.size());
client.streamClose(streamId);
client.streamRelease(streamId);
/// ## Step 5: Clean up
relay.stop();
dest.stop();
client.stop();
printf("\n=== Tutorial 11 Complete ===\n");
return 0;
}
/// ## Key Takeaways
///
/// - Circuit Relay allows connecting peers behind NAT/firewalls
/// - Three roles: Destination (relayed), Relay (forwarder), Client
/// - `circuitRelay: true` enables relay mode on a node
/// - Destination calls `circuitRelayReserve()` to request a slot
/// - Client calls `dialCircuitRelay()` with dest peer ID + address
/// - Relay overhead should be considered; prefer direct connections
/// when possible
/// - The relay address returned by reserve may be needed by the
/// client for some configurations
///
/// This concludes the tutorial series! You now have a solid
/// foundation for building peer-to-peer applications with
/// `logos-libp2p-module`.
/// ## Run tutorial
///
/// ```bash
/// ./build/tutorial/tutorial_11_circuit_relay
/// ```