ceremonyclient/go-libp2p/examples/relay/main.go
2024-06-07 06:25:43 +00:00

161 lines
5.3 KiB
Go

package main
import (
"context"
"log"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/p2p/net/swarm"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
ma "github.com/multiformats/go-multiaddr"
)
func main() {
run()
}
func run() {
// Create two "unreachable" libp2p hosts that want to communicate.
// We are configuring them with no listen addresses to mimic hosts
// that cannot be directly dialed due to problematic firewall/NAT
// configurations.
unreachable1, err := libp2p.New(
libp2p.NoListenAddrs,
// Usually EnableRelay() is not required as it is enabled by default
// but NoListenAddrs overrides this, so we're adding it in explicitly again.
libp2p.EnableRelay(),
)
if err != nil {
log.Printf("Failed to create unreachable1: %v", err)
return
}
unreachable2, err := libp2p.New(
libp2p.NoListenAddrs,
libp2p.EnableRelay(),
)
if err != nil {
log.Printf("Failed to create unreachable2: %v", err)
return
}
log.Println("First let's attempt to directly connect")
// Attempt to connect the unreachable hosts directly
unreachable2info := peer.AddrInfo{
ID: unreachable2.ID(),
Addrs: unreachable2.Addrs(),
}
err = unreachable1.Connect(context.Background(), unreachable2info)
if err == nil {
log.Printf("This actually should have failed.")
return
}
log.Println("As suspected we cannot directly dial between the unreachable hosts")
// Create a host to act as a middleman to relay messages on our behalf
relay1, err := libp2p.New()
if err != nil {
log.Printf("Failed to create relay1: %v", err)
return
}
// Configure the host to offer the circuit relay service.
// Any host that is directly dialable in the network (or on the internet)
// can offer a circuit relay service, this isn't just the job of
// "dedicated" relay services.
// In circuit relay v2 (which we're using here!) it is rate limited so that
// any node can offer this service safely
_, err = relay.New(relay1)
if err != nil {
log.Printf("Failed to instantiate the relay: %v", err)
return
}
relay1info := peer.AddrInfo{
ID: relay1.ID(),
Addrs: relay1.Addrs(),
}
// Connect both unreachable1 and unreachable2 to relay1
if err := unreachable1.Connect(context.Background(), relay1info); err != nil {
log.Printf("Failed to connect unreachable1 and relay1: %v", err)
return
}
if err := unreachable2.Connect(context.Background(), relay1info); err != nil {
log.Printf("Failed to connect unreachable2 and relay1: %v", err)
return
}
// Now, to test the communication, let's set up a protocol handler on unreachable2
unreachable2.SetStreamHandler("/customprotocol", func(s network.Stream) {
log.Println("Awesome! We're now communicating via the relay!")
// End the example
s.Close()
})
// Hosts that want to have messages relayed on their behalf need to reserve a slot
// with the circuit relay service host
// As we will open a stream to unreachable2, unreachable2 needs to make the
// reservation
_, err = client.Reserve(context.Background(), unreachable2, relay1info)
if err != nil {
log.Printf("unreachable2 failed to receive a relay reservation from relay1. %v", err)
return
}
// Now create a new address for unreachable2 that specifies to communicate via
// relay1 using a circuit relay
relayaddr, err := ma.NewMultiaddr("/p2p/" + relay1info.ID.String() + "/p2p-circuit/p2p/" + unreachable2.ID().String())
if err != nil {
log.Println(err)
return
}
// Since we just tried and failed to dial, the dialer system will, by default
// prevent us from redialing again so quickly. Since we know what we're doing, we
// can use this ugly hack (it's on our TODO list to make it a little cleaner)
// to tell the dialer "no, its okay, let's try this again"
unreachable1.Network().(*swarm.Swarm).Backoff().Clear(unreachable2.ID())
log.Println("Now let's attempt to connect the hosts via the relay node")
// Open a connection to the previously unreachable host via the relay address
unreachable2relayinfo := peer.AddrInfo{
ID: unreachable2.ID(),
Addrs: []ma.Multiaddr{relayaddr},
}
if err := unreachable1.Connect(context.Background(), unreachable2relayinfo); err != nil {
log.Printf("Unexpected error here. Failed to connect unreachable1 and unreachable2: %v", err)
return
}
log.Println("Yep, that worked!")
// Woohoo! we're connected!
// Let's start talking!
// Because we don't have a direct connection to the destination node - we have a relayed connection -
// the connection is marked as transient. Since the relay limits the amount of data that can be
// exchanged over the relayed connection, the application needs to explicitly opt-in into using a
// relayed connection. In general, we should only do this if we have low bandwidth requirements,
// and we're happy for the connection to be killed when the relayed connection is replaced with a
// direct (holepunched) connection.
s, err := unreachable1.NewStream(network.WithUseTransient(context.Background(), "customprotocol"), unreachable2.ID(), "/customprotocol")
if err != nil {
log.Println("Whoops, this should have worked...: ", err)
return
}
s.Read(make([]byte, 1)) // block until the handler closes the stream
}