Did you succeed in creating a bootstrap across the network?

I just want to know how to create a node like the global bootstrap.
I am having some trouble when using libp2p, I would like to ask for your help.

I have 3 servers A, B, C.
Server A has a public IP address
Server B is a server behind the NAT network, no public IP address.
Server C is a server behind another NAT network, and no public IP.

I want server A as a global bootstrap node.
Servers B and C are connected to server A when they are run.
Server B and C are successfully connected to A.
I am having some trouble now, and B and C failed to communicate by creating a stream.

The following is A code as bootstrap,

A.go
//--------- strat of A.go---------

package main

import (
	"context"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"syscall"
	"time"

	dht "github.com/libp2p/go-libp2p-kad-dht"

	"github.com/libp2p/go-libp2p"
	circuit "github.com/libp2p/go-libp2p-circuit"
	net "github.com/libp2p/go-libp2p-net"
	ma "github.com/multiformats/go-multiaddr"
)

const defaultListenAddr = "/ip4/0.0.0.0/tcp/%d"
const protocl = "/chat/1.0.0"

func main() {

	var port int

	flag.IntVar(&port, "port", 30000, "listen port")

	flag.Parse()

	listenAddr := fmt.Sprintf(defaultListenAddr, port)

	listen, err := ma.NewMultiaddr(listenAddr)
	if err != nil {
		fmt.Println(err)
		return
	}

	opts := []libp2p.Option{
		libp2p.ListenAddrs(listen),
		libp2p.NATPortMap(),
		libp2p.EnableRelay(circuit.OptHop),
	}

	ctx := context.Background()
	host, err := libp2p.New(ctx, opts...)
	if err != nil {
		fmt.Println(err)
		return
	}

	kademliaDHT, err := dht.New(ctx, host)
	if err != nil {
		fmt.Println(err)
		return
	}

	if err = kademliaDHT.BootstrapWithConfig(ctx, dht.BootstrapConfig{1, 10 * time.Second, 10 * time.Second}); err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("current node addr: %s/p2p/%s\n", listenAddr, host.ID().Pretty())

	host.SetStreamHandler(protocl, handleStream)

	for {
		if kademliaDHT.RoutingTable().Size() > 0 {
			rt := kademliaDHT.RoutingTable()
			rt.Print()
		}
		time.Sleep(10 * time.Second)
	}

	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
	<-quit
}

func handleStream(s net.Stream) {

	fmt.Printf("get stream from %s via %s\n", s.Conn().RemotePeer().Pretty(), s.Conn().RemoteMultiaddr().String())

	data, err := ioutil.ReadAll(s)

	if err != nil {
		fmt.Println(err)
		return
	}
	if len(data) > 0 {
		fmt.Printf("Message from %s:%s\n", s.Conn().RemotePeer().Pretty(), string(data))
	}
	defer s.Close()
}

//--------end of A.go--------

B and C use the same code
B.go
//--------- strat of B.go---------

package main

import (
	"bufio"
	"context"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"syscall"
	"time"

	dht "github.com/libp2p/go-libp2p-kad-dht"

	"github.com/libp2p/go-libp2p"
	circuit "github.com/libp2p/go-libp2p-circuit"

	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/peer"
	net "github.com/libp2p/go-libp2p-net"
	"github.com/multiformats/go-multiaddr"
	ma "github.com/multiformats/go-multiaddr"
)

const defaultListenAddr = "/ip4/0.0.0.0/tcp/%d"
const protocl = "/chat/1.0.0"

func main() {

	var bs string
	var port int

	flag.StringVar(&bs, "bs", "", "bs addr")
	flag.IntVar(&port, "port", 30000, "listen port")

	flag.Parse()

	listenAddr := fmt.Sprintf(defaultListenAddr, port)

	listen, err := ma.NewMultiaddr(listenAddr)
	if err != nil {
		fmt.Println(err)
		return
	}

	opts := []libp2p.Option{
		libp2p.ListenAddrs(listen),
		libp2p.NATPortMap(),
		libp2p.EnableRelay(circuit.OptHop),
	}

	ctx := context.Background()
	ho, err := libp2p.New(ctx, opts...)
	if err != nil {
		fmt.Println(err)
		return
	}

	kademliaDHT, err := dht.New(ctx, ho)
	if err != nil {
		fmt.Println(err)
		return
	}

	if err = kademliaDHT.Bootstrap(ctx); err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("current node addr: %s/p2p/%s\n", listenAddr, ho.ID().Pretty())

	if bs != "" {
		bsAddr, err := multiaddr.NewMultiaddr(bs)
		if err != nil {
			fmt.Println(err)
			return
		}

		bsInfo, err := peer.AddrInfoFromP2pAddr(bsAddr)
		if err != nil {
			fmt.Println(err)
			return
		}

		ho.Connect(ctx, *bsInfo)

	}

	ho.SetStreamHandler(protocl, handleStream)

	/*go func(kdht *dht.IpfsDHT) {
		for {
			if kdht.RoutingTable().Size() > 0 {
				rt := kdht.RoutingTable()
				rt.Print()
			}
			time.Sleep(10 * time.Second)
		}
	}(kademliaDHT)*/

	for {
		for _, pid := range ho.Peerstore().Peers() {

			fmt.Printf("本地存储的节点 peer: %v\n", pid.Pretty())

			genStream(ho, pid)
		}
		fmt.Println("-----------")
		time.Sleep(3 * time.Second)
	}

	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
	<-quit
}

func genStream(h host.Host, pid peer.ID) {

	if h.ID() == pid {
		return
	}

	s, err := h.NewStream(context.Background(), pid, protocl)
	fmt.Println("create stream,", s, err)
	if err != nil {
		fmt.Println(err)
		return
	}

	w := bufio.NewWriter(s)

	msg := "HI\n"
	msg = fmt.Sprintf("%v %s", time.Now(), msg)

	num, err := w.WriteString(msg)
	if num != len(msg) {
		fmt.Printf("expected to write %d bytes, wrote %d", len(msg), num)
		return
	}
	if err != nil {
		fmt.Println(err)
		return
	}
	if err = w.Flush(); err != nil {
		fmt.Println(err)
		return
	}

	s.Close()
	//return nil

	time.Sleep(3 * time.Second)

}

func handleStream(s net.Stream) {

	fmt.Printf("get stream from %s via %s\n", s.Conn().RemotePeer().Pretty(), s.Conn().RemoteMultiaddr().String())

	data, err := ioutil.ReadAll(s)

	if err != nil {
		fmt.Println(err)
		return
	}
	if len(data) > 0 {
		fmt.Printf("Message from %s:%s\n", s.Conn().RemotePeer().Pretty(), string(data))
	}
	defer s.Close()
}

//--------end of B.go--------

You should call the dht.Bootstrap() after you connect to the bootstrap peer, and also make sure to use a RoutedHost so that libp2p will use the DHT to lookup the addresses of the other peer.

I have seen many examples and have not succeeded. Now I am confused.
I believe many people have the same needs as me.
Now there are three nodes in different networks, A, B, and C. A has a public network IP as the Global Bootstrap node, Node B and C are behind two different NAT networks, and now it is desirable to establish a Stream between B and C for communication.

Can you give me a simple demo? Thank you very much!!!

Note: nodes don’t just try to connect to all peers in the network. The DHT will try to find some peers but, in general, you’ll have to specify which peers you want to connect to (by peer ID).

Beyond that, it looks like NAT port mapping isn’t working. In this situation, the only way forward (currently) is relays. Unfortunately, B has no way to know that A is a valid relay for node C.

One solution is to specify an “address factory”. You can pass the following option to nodes B and C to make them advertise node A as a relay (in addition to advertising their private addresses.

libp2p.AddressFactory(func(addrs []ma.Multiaddr) []ma.Multiaddr {
  return append(addrs, ma.StringCast(bsaddr.Encapsulate(ma.StringCast("/p2p-circuit"))))
})

thank you very much!

In opts I added the following option

opts := []libp2p.Option{
                ... ...
		libp2p.AddrsFactory(newAddrsFactory(bootstrapAddrs)),
	}

The function used in the options

func newAddrsFactory(advertiseAddrs []multiaddr.Multiaddr) func([]multiaddr.Multiaddr) []multiaddr.Multiaddr {
	return func([]multiaddr.Multiaddr) []multiaddr.Multiaddr {
		return advertiseAddrs
	}
}

In this way, B and C can communicate by creating a stream.
On this B or C node, when I use

stream.Conn().RemotePeer().Pretty()

The value I got:

Ip:/ipfs/QmZTLvt6nP4aqQfqn6Prkv5NcmFEvYKqPZwsbd4SDyRcMw/p2p-circuit

So I am not sure if my usage is correct or is a best practice, so I sent you an email for help.

If you meant to say stream.Conn().RemoteMultiaddr() that looks correct (mostly). Is that exactly what you got?

In the same network environment, the same method of the old version of libp2p obtains the public network address of the other node accessing the public network (the node is behind the NAT)

like this:

/ip4/40.102.184.155/tcp/40001