Files
logos-libp2p-module/tutorial/docs/tutorial_11_circuit_relay.md
T

257 lines
7.5 KiB
Markdown
Raw 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.
-----------
```cpp
#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.
```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");
if (!relay.start().success) {
fprintf(stderr, "Relay failed\n");
return 1;
}
if (!dest.start().success) {
fprintf(stderr, "Destination failed\n");
return 1;
}
if (!client.start().success) {
fprintf(stderr, "Client failed\n");
return 1;
}
printf("All three nodes started\n");
```
## Step 2: Get node addresses
```cpp
auto 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>());
auto 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).
```cpp
printf("\nDestination connecting to relay...\n");
if (!dest.connectPeer(relayPeerId, relayAddrs, 5000).success) {
fprintf(stderr, "Destination failed to connect to relay\n");
return 1;
}
printf("Destination connected to relay\n");
printf("Destination requesting relay reservation...\n");
auto 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.
```cpp
printf("\nClient connecting to relay...\n");
if (!client.connectPeer(relayPeerId, relayAddrs, 5000).success) {
fprintf(stderr, "Client failed to connect to relay\n");
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<std::string>() + "/p2p-circuit";
```
For circuit relay, we use a well-known protocol to test connectivity.
The ping protocol works well for this.
```cpp
auto 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);
if (!client.streamWrite(streamId, payload).success) {
fprintf(stderr, "Write failed\n");
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
```
---
<p align="center"><a href="tutorial_10_peerstore.md">&larr; Peer Store Management</a></p>