# 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. ----------- ```cpp #include #include #include #include #include #include #include "plugin.h" int main() { printf("=== Tutorial 11: Circuit Relay ===\n\n"); setLogLevel("fatal"); ``` ## 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. ```cpp // 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"); StdLogosResult relayStartRes = relay.start(); if (!relayStartRes.success) { fprintf(stderr, "Relay failed: %s\n", relayStartRes.error.c_str()); return 1; } StdLogosResult destStartRes = dest.start(); if (!destStartRes.success) { fprintf(stderr, "Destination failed: %s\n", destStartRes.error.c_str()); return 1; } 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 ```cpp 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::vector relayAddrs; for (const auto& a : infoRelay["addrs"]) relayAddrs.push_back(a.get()); 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(); 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). ```cpp printf("\nDestination connecting to relay...\n"); 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"); 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().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. ```cpp printf("\nClient connecting to relay...\n"); 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. ```cpp printf("Client dialing destination through relay...\n"); std::string relayDialAddr; relayDialAddr = reserveRes.value[0].get() + "/p2p-circuit"; ``` For circuit relay, we use a well-known protocol to test connectivity. The ping protocol works well for this. ```cpp 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(); 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(i); 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 ```cpp 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 ``` ---

← Peer Store Management