Hole puncher seems never to kick off

We are using go-libp2p2 version 0.33.2 to create a private cluster. By “private”, I mean we don’t join any public nodes, we use our own bootstrap nodes, we don’t use PSK. We test the cluster with the following setup:

  • 3 public nodes seving as bootstrap nodes and providing relay service
  • 2 nodes behind a home router, 2 different locations. Let’s call them NodeA and NodeB. They are in 2 different home networks. These home networks use the popular 192.168 IP range

NodeA is able to talk to NodeB. However, it always goes through one of our 3 relay nodes.

I turn on debug log and observe that:

  • AutoNAT triggered and concluded that both NodeA and NodeB are behind NATs. Correct
  • NATPortMap is enabled but failed. Correct. Because UPnP is disabled on both home routers
  • I know that identity.ActivationThresh, by default, is set to 4 (the consequence is that the node must way for at least 4 nodes seeing its public IP to conclude its public IP). In our setup, we only have 3 public nodes (the bootstrap nodes), thus, I changed it to 3

With all of these, I am be able to see the following debug log message: Host now has a public address. Starting holepunch protocol. (comes from file protocol/holepunch/svc.go). But I can never see the holepuncher kick off after that. I mean, I don’t see the method directConnect in file protocol/holepunch/holepuncher.go get called. Thus, no hole-punching attempt.

I see the the directConnect method is all invoke when receiving a notification from the host’s network, see file holepuncher.go

func newHolePuncher(h host.Host, ids identify.IDService, tracer *tracer, filter AddrFilter) *holePuncher {
	return hp

But it seems doesn’t notify anything after the message Host now has a public address. Starting holepunch protocol.

Anything could go wrong with our setup?

Hey man, I’m doing a project that has a similar setup as yours but I’m having some difficulties with the hole punching… Have you managed to get your problem fixed?
Do you have a Discord or something so that we could talk and maybe help each other?

My Discord: 0xrage

I haven’t been able to have the hole punching work for me, or at least seeing it trigger. Putting a break point on its code, never hit.

You can try asking your question on the github Issues on the go-libp2p repository.

In fact, three independent IPs are required.

func (oas *ObservedAddrManager) recordObservationUnlocked(conn network.Conn, observed ma.Multiaddr) {
	now := time.Now()
	observerString := observerGroup(conn.RemoteMultiaddr())
	localString := string(conn.LocalMultiaddr().Bytes())
	ob := observation{
		seenTime: now,
		inbound:  conn.Stat().Direction == network.DirInbound,

	// check if observed address seen yet, if so, update it
	for _, observedAddr := range oas.addrs[localString] {
		if observedAddr.addr.Equal(observed) {
			// Don't trump an outbound observation with an inbound
			// one.
			wasInbound := observedAddr.seenBy[observerString].inbound
			isInbound := ob.inbound
			ob.inbound = isInbound || wasInbound

			if !wasInbound && isInbound {

			observedAddr.seenBy[observerString] = ob
			observedAddr.lastSeen = now

	// observed address not seen yet, append it
	oa := &observedAddr{
		addr: observed,
		seenBy: map[string]observation{
			observerString: ob,
		lastSeen: now,
	if ob.inbound {
	oas.addrs[localString] = append(oas.addrs[localString], oa)