go-libp2p-circuit icon indicating copy to clipboard operation
go-libp2p-circuit copied to clipboard

Multi-Relay Hopping

Open elgohr opened this issue 3 years ago • 1 comments

It would be great to have a way for multi-relay hopping.

Let's say there's the following network: [ Peer A ] - [ Relay 1 ] - [ Relay 2 ] - [ Peer B ]

In this example Peer A could reach Peer B via Relay 1 and Relay 2. Nevertheless it looks like this is not possible at the moment.

The following implementation uses Relay 1 as the relay and tries to connected to Peer B. This results in HOP_NO_CONN_TO_DST. Which is comprehensible, as Relay 1 has no direct connection to Peer B.

package main

import (
	"context"
	"fmt"
	logging "github.com/ipfs/go-log"
	"github.com/libp2p/go-libp2p"
	circuit "github.com/libp2p/go-libp2p-circuit"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	ma "github.com/multiformats/go-multiaddr"
	"log"
)

func main() {
	h1, err := libp2p.New(context.Background(), libp2p.EnableRelay(circuit.OptHop))
	if err != nil {
		log.Fatalln(err)
	}

	h2, err := libp2p.New(context.Background(), libp2p.EnableRelay(circuit.OptHop))
	if err != nil {
		log.Fatalln(err)
	}

	h3, err := libp2p.New(context.Background(), libp2p.EnableRelay(circuit.OptHop))
	if err != nil {
		log.Fatalln(err)
	}

	h4, err := libp2p.New(context.Background(), libp2p.ListenAddrs(), libp2p.EnableRelay())
	if err != nil {
		log.Fatalln(err)
	}

	h2info := peer.AddrInfo{
		ID:    h2.ID(),
		Addrs: h2.Addrs(),
	}

	h3info := peer.AddrInfo{
		ID:    h3.ID(),
		Addrs: h3.Addrs(),
	}

	if err := h1.Connect(context.Background(), h2info); err != nil {
		log.Fatalln(err)
	}
	if err := h2.Connect(context.Background(), h3info); err != nil {
		log.Fatalln(err)
	}
	if err := h4.Connect(context.Background(), h3info); err != nil {
		log.Fatalln(err)
	}

	h4.SetStreamHandler("/cats", func(s network.Stream) {
		fmt.Println("Meow! It worked!")
		s.Close()
	})

	relayaddr, err := ma.NewMultiaddr("/p2p/" + h2.ID().Pretty() + "/p2p-circuit/p2p/" + h4.ID().Pretty())
	if err != nil {
		log.Fatalln(err)
	}

	h4relayInfo := peer.AddrInfo{
		ID:    h4.ID(),
		Addrs: []ma.Multiaddr{relayaddr},
	}
	if err := h1.Connect(context.Background(), h4relayInfo); err != nil {
		log.Fatalln(err)
	}

	s, err := h1.NewStream(context.Background(), h4.ID(), "/cats")
	if err != nil {
		fmt.Println("huh, this should have worked: ", err)
		return
	}

	s.Read(make([]byte, 1))
}

On the other hand, changing the relay address to "/p2p/" + h3.ID().Pretty() + "/p2p-circuit/p2p/" + h4.ID().Pretty() (using Relay 2 instead of Relay 1), leads to a connection error - as Peer A is not able to connect to Relay 2.

To me it would be great to talk to Relay 1 and being able to connect to Peer B.

elgohr avatar Apr 16 '21 08:04 elgohr

I can't comment on the Golang specifics.

Referencing the corresponding specification issue here: https://github.com/libp2p/specs/issues/21

See also Future work section in relay specification.

mxinden avatar Apr 16 '21 08:04 mxinden