Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Hole Punching] - Implementation Example #2761

Closed
AfonsoBatista7 opened this issue Apr 9, 2024 · 24 comments
Closed

[Hole Punching] - Implementation Example #2761

AfonsoBatista7 opened this issue Apr 9, 2024 · 24 comments

Comments

@AfonsoBatista7
Copy link

Is there currently any available NAT Transversal Hole Punching implementation example in Go? I saw it is in Rust but I didn't find it, in Go. If it doesn't exist yet, does someone have some recommendations or links on how I could implement one?

@wlynxg
Copy link
Contributor

wlynxg commented Apr 10, 2024

Enable the holepunch module by passing in Option:

func EnableHolePunching(opts ...holepunch.Option) Option {

@wlynxg
Copy link
Contributor

wlynxg commented Apr 10, 2024

You can enable the holepunch module like this:

libp2p.New(libp2p.EnableHolePunching())

@AfonsoBatista7
Copy link
Author

AfonsoBatista7 commented Apr 10, 2024

You can enable the holepunch module like this:

libp2p.New(libp2p.EnableHolePunching())

Thanks!!

But how could I provide a public relay server address so that my peer could perform the hole punching with another peer?

I found this option to pass on the EnableHolePunching function and on it I can provide peer addrs info... do you know if thats it?

libp2p.New(libp2p.EnableHolePunching(libp2p.EnableAutoRelayWithStaticRelays()))

@wlynxg
Copy link
Contributor

wlynxg commented Apr 11, 2024

If you want to use a fixed Relay node address, you can write like this:

var nodes []peer.AddrInfo
for _, s := range []string{"node1 info","node2 info"} {
    addrInfo, err := peer.AddrInfoFromString(s)
    if err != nil {
        panic(err)
    }
    nodes = append(nodes, *addrInfo)
}
libp2p.New(libp2p.EnableAutoRelayWithStaticRelays(nodes), libp2p.EnableHolePunching())

@AfonsoBatista7
Copy link
Author

@wlynxg What does the EnableHolePunching do? does it only create a Node that can perform the hole punching?
I ask this so I can understand what I still have to do to have it all working (Peer A behind NAT to be able to connect directly to Peer B behind NAT).

Thank you for your responses btw!

@wlynxg
Copy link
Contributor

wlynxg commented Apr 12, 2024

EnableHolePunching causes libp2p to load the holepunch module, which is enabled when a libp2p node finds itself behind a NAT via AutoNAT. When you establish a relay connection, the hole punching module will work and try to upgrade to a direct connection, which is the DCUtR protocol defined by libp2p.

These things are all done automatically inside libp2p, so you only need to enable holepunch through EnableHolePunching when creating the node, and use EnableAutoRelayWithStaticRelays to load the relay node address. Then libp2p will automatically perform hole drilling when establishing a connection with another node.

@AfonsoBatista7
Copy link
Author

AfonsoBatista7 commented Apr 12, 2024

Oh cool! so now, to connect peer A to B, I only need to implement some type of Discovery to find out B address, and when I try to connect to it, the hole drilling will be performed and the peers will connect?

Do you know if I need first to find the public IP of peer B to perform the hole punching or, when I try to connect to it using the address with his private IP the holepunch module will take care of the rest?

@wlynxg
Copy link
Contributor

wlynxg commented Apr 12, 2024

When a node is connected to the DHT network, the node will automatically exchange information with other nodes through the Identify protocol. In this process, the node will obtain its own public network egress address through information exchange.

So you just need to first link nodes A and B to the DHT network, then node B then connects to a relay server, and then node A connects to node B through the relay node. Later, nodes A and B will automatically try the holepunch logic.

@AfonsoBatista7
Copy link
Author

Sorry to bother you again, but I'm trying to make the DHT work right now, and I already saw some examples, so I've been trying to implement it... I use this code to connect to some Default Bootstrap Peers:

	if err = kademliaDht.Bootstrap(ctx); err != nil {
		logCallback(fmt.Sprintf("Failed to bootstrap the DHT: %s\n", err))
	}

	// Let's connect to the bootstrap nodes first. They will tell us about the
	// other nodes in the network.
	var wg sync.WaitGroup
	for _, peerAddr := range dht.DefaultBootstrapPeers {
		logCallback(fmt.Sprintf("Connecting to bootstrap node: %s\n", peerAddr))
		peerinfo, _ := peer.AddrInfoFromP2pAddr(peerAddr)
		wg.Add(1)
		go func() {
			defer wg.Done()
			if err := h.Connect(ctx, *peerinfo); err != nil {
				logCallback(fmt.Sprintf("Connection failed: %s\n", err))
			} else {
				logCallback(fmt.Sprintf("Connected to peer %s", peerinfo.ID))
			}
		}()
	}
	wg.Wait()

after, if I try to open another console to run peer B, it will also connect to the Bootstrap peers and populate the DHT but if I try to find peer A on it by providing his peer ID it says that he can't find it. I don't know if I need to do something else so that peer A and B can find each other on the DHT.

@MarcoPolo
Copy link
Collaborator

I'm not sure why it isn't working for you. From what I see it seems generally correct.

Take a look at the chat with rendezvous example. It should be doing something similar.

Also you could try using kubo to debug things. I find Kubo's ipfs routing findpeer <peerid> helpful to check if a node has inserted itself into the routing table correctly.

@wlynxg
Copy link
Contributor

wlynxg commented Apr 16, 2024

For node A, you are missing the program to broadcast data to the DHT. You need to add the following code:

import (
	"context"
	"io"
	"log"
	"net"

	"github.com/libp2p/go-libp2p"
	dht "github.com/libp2p/go-libp2p-kad-dht"
	"github.com/libp2p/go-libp2p/core"
	"github.com/libp2p/go-libp2p/core/peer"
	"github.com/libp2p/go-libp2p/p2p/discovery/routing"
	"github.com/libp2p/go-libp2p/p2p/discovery/util"
)

func main() {
        // the code you have implemented to connect to the DHT node
        ......

        // you need to broadcast node A’s information to the DHT network here
	discovery := routing.NewRoutingDiscovery(kademliaDht)
	util.Advertise(ctx, discovery, h.ID().String())

	select {}
}

For node B, you can query the information of node A through the following code:

import (
	"context"
	"io"
	"log"
	"net"

	"github.com/libp2p/go-libp2p"
	dht "github.com/libp2p/go-libp2p-kad-dht"
	"github.com/libp2p/go-libp2p/core"
	"github.com/libp2p/go-libp2p/core/peer"
	"github.com/libp2p/go-libp2p/p2p/discovery/routing"
	"github.com/libp2p/go-libp2p/p2p/discovery/util"
)

func main() {
        // the code you have implemented to connect to the DHT node
        ......

        // query information about node A
	discovery := routing.NewRoutingDiscovery(kademliaDht)
        peerChan, err := routingDiscovery.FindPeers(ctx, "node A's ID")
	if err != nil {
		panic(err)
	}

	for peer := range peerChan {
                ......
        }
}

@AfonsoBatista7
Copy link
Author

Still cant find peers... but I have a question about the query you showed.
First I tried to query the DHT by doing:

	peerID, err := peer.Decode(destination)

	if err != nil {
		logCallback(fmt.Sprintf("Failed to decode peer ID: %s\n", err))
	}

	kademliaDht.FindPeer(contextVar, peerID)

(this way also cant find the other peer)
but do you know if your way is better?

@wlynxg
Copy link
Contributor

wlynxg commented Apr 16, 2024

Are you using the code below to expose node information to DHT?

discovery := routing.NewRoutingDiscovery(kademliaDht)
util.Advertise(ctx, discovery, h.ID().String())

@AfonsoBatista7
Copy link
Author

Yes Yes
image

@wlynxg
Copy link
Contributor

wlynxg commented Apr 16, 2024

When querying nodes, do you use peerID.String() or the original peerID? (Because peerID and peerID.String() values are different)

@AfonsoBatista7
Copy link
Author

AfonsoBatista7 commented Apr 17, 2024

After a lot of testing and trial and error, I GOT THE DHT WORKING :D! Thanks a lot, guys!
The error was because of the context var... while I was testing different things I deleted some important parts... so I had to come back to an early version and just start again, and it worked. So what @wlynxg and @MarcoPolo said should work.

Now I need to put the Hole Punching working... I'll come back when I have more questions...

@MarcoPolo
Copy link
Collaborator

Let's continue on discussions. Closing this issue as there's nothing to do.

@iGwkang
Copy link

iGwkang commented Jul 18, 2024

@MarcoPolo @wlynxg @AfonsoBatista7
Hello, excuse me.
I have studied it for a long time, but I can't find a solution. I hope you can help me. Thank you !

I tried modifying the chat-with-rendezvous code. Added NATPortMap, EnableHolePunching, EnableAutoRelayWithStaticRelays
image

Host A run:

chat -listen /ip4/0.0.0.0/tcp/6666 -peer /ip4/public_ip/tcp/9876/p2p/12D3KooWP2soRwZSeaYabBvbRS76DNYzxqcKvimqKbMJRtHhNAra

Host B run:

chat -listen /ip4/0.0.0.0/tcp/6668 -peer /ip4/public_ip/tcp/9876/p2p/12D3KooWP2soRwZSeaYabBvbRS76DNYzxqcKvimqKbMJRtHhNAra

Host A and Host B are in different LANs, that is, behind NAT.

Here is my simple Bootstrap and Relay server code:

relay1, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/9876"))
if err != nil {
	log.Printf("Failed to create relay1: %v", err)
	return
}

_, err = relay.New(relay1, relay.WithInfiniteLimits())
if err != nil {
	log.Printf("Failed to instantiate the relay: %v", err)
	return
}
_, err = dht.New(context.Background(), relay1, dht.Mode(dht.ModeServer))
if err != nil {
	log.Printf("Failed to create DHT: %v", err)
	return
}

_, err = autonat.New(relay1)
if err != nil {
	log.Printf("Failed to create AutoNAT: %v", err)
	return
}

fmt.Printf("[*] Your Bootstrap ID Is: /ip4/%s/tcp/%v/p2p/%s\n", "0.0.0.0", 9876, relay1.ID().String())

select {}

After running, I found that the hole punching did not work. Host A and Host B could find each other, but could not establish a connection.

image

@AfonsoBatista7
Copy link
Author

AfonsoBatista7 commented Jul 18, 2024

@iGwkang
Hey man :D
Idk if this will help, but this is the code I am using for my relay:

        sourceMultiAddrTCP, _ := multiaddr.NewMultiaddr("/ip4/0.0.0.0/tcp/4001")
	sourceMultiAddrUDP, _ := multiaddr.NewMultiaddr("/ip4/0.0.0.0/udp/4001/quic-v1")

	// libp2p.New constructs a new libp2p Host.
	// Other options can be added here.
	return libp2p.New(
		libp2p.ListenAddrs(sourceMultiAddrTCP, sourceMultiAddrUDP),

		// Attempt to open ports using uPNP for NATed hosts.
		libp2p.NATPortMap(),
		libp2p.EnableHolePunching(),
		libp2p.EnableNATService(),

		libp2p.EnableRelayService(),
	)

I think the rest of your code is the same as mine

@iGwkang
Copy link

iGwkang commented Jul 18, 2024

@AfonsoBatista7

Still can't connect successfully, please note that Host A and Host B are behind different firewalls and both are cone NAT.
I feel like HolePunching isn't working.

image

image

This was referenced Jul 18, 2024
@AfonsoBatista7
Copy link
Author

AfonsoBatista7 commented Jul 25, 2024

@iGwkang

Sorry for the late reply...
I think the relay node need to be a public node, both Host A and B (behind NATs) need to be able to dial to the relay node.
Only this way, both node A and B will be able to connect to each other, by performing the hole punch through the relay peer

Can your host A and B dial the relay peer?

@iGwkang
Copy link

iGwkang commented Jul 26, 2024

@AfonsoBatista7 I have specified the relay node.
#2884

Do you have any sample code that can hole punching properly for me to refer to?
Thank you so mach. ❤

@NanoRed
Copy link

NanoRed commented Oct 24, 2024

@iGwkang Hi have you solve this yet? i have the same problem

@iGwkang
Copy link

iGwkang commented Oct 31, 2024

@NanoRed Sorry, I have given up using go-libp2p.
I spent up to a month on this issue, and no matter what I did, I couldn't succeed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants