libp2p / go-libp2p-circuit

Circuit Switching for libp2p
MIT License
48 stars 16 forks source link

Multi-Relay Hopping #127

Open elgohr opened 3 years ago

elgohr commented 3 years ago

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.

mxinden commented 3 years ago

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.