@@ -21,6 +21,7 @@ import (
2121 "github.com/libp2p/go-libp2p/core/peerstore"
2222 "github.com/libp2p/go-libp2p/core/routing"
2323 "github.com/libp2p/go-libp2p/p2p/net/swarm"
24+ "github.com/libp2p/go-libp2p/p2p/protocol/holepunch"
2425 quic "github.com/libp2p/go-libp2p/p2p/transport/quic" //nolint:revive // Must be imported with alias
2526 "github.com/libp2p/go-libp2p/p2p/transport/tcp"
2627 ma "github.com/multiformats/go-multiaddr"
@@ -106,7 +107,7 @@ func NewNode(ctx context.Context, cfg Config, key *k1.PrivateKey, connGater Conn
106107 libp2p .ConnectionGater (connGater ),
107108 // Enable Autonat (required for hole punching)
108109 libp2p .EnableNATService (),
109- libp2p .EnableHolePunching (),
110+ libp2p .EnableHolePunching (holepunch . WithTracer ( newHolePunchTracer ( ctx )) ),
110111 libp2p .AddrsFactory (func (internalAddrs []ma.Multiaddr ) []ma.Multiaddr {
111112 return filterAdvertisedAddrs (externalAddrs , internalAddrs , filterPrivateAddrs )
112113 }),
@@ -263,6 +264,8 @@ func NewEventCollector(p2pNode host.Host) lifecycle.HookFuncCtx {
263264 new (event.EvtLocalReachabilityChanged ),
264265 new (event.EvtLocalAddressesUpdated ),
265266 new (event.EvtNATDeviceTypeChanged ),
267+ new (event.EvtPeerIdentificationCompleted ),
268+ new (event.EvtPeerIdentificationFailed ),
266269 })
267270 if err != nil {
268271 log .Error (ctx , "Failed to subscribe to libp2p events" , err )
@@ -302,6 +305,21 @@ func NewEventCollector(p2pNode host.Host) lifecycle.HookFuncCtx {
302305 z .Any ("transport" , evt .TransportProtocol ),
303306 z .Any ("nat_type" , evt .NatDeviceType ),
304307 )
308+ case event.EvtPeerIdentificationCompleted :
309+ isPublic := evt .ObservedAddr != nil && manet .IsPublicAddr (evt .ObservedAddr )
310+ supportsDCUtR := slices .Contains (evt .Protocols , holepunch .Protocol )
311+ log .Debug (ctx , "Peer identification completed" ,
312+ z .Str ("peer" , PeerName (evt .Peer )),
313+ z .Any ("observed_addr" , evt .ObservedAddr ),
314+ z .Any ("conn_local" , evt .Conn .LocalMultiaddr ()),
315+ z .Any ("conn_remote" , evt .Conn .RemoteMultiaddr ()),
316+ z .Bool ("observed_is_public" , isPublic ),
317+ z .Bool ("peer_supports_dcutr" , supportsDCUtR ),
318+ )
319+ case event.EvtPeerIdentificationFailed :
320+ log .Warn (ctx , "Peer identification failed" , evt .Reason ,
321+ z .Str ("peer" , PeerName (evt .Peer )),
322+ )
305323 default :
306324 log .Warn (ctx , "Unknown libp2p event" , nil , z .Str ("type" , fmt .Sprintf ("%T" , e )))
307325 }
@@ -310,6 +328,123 @@ func NewEventCollector(p2pNode host.Host) lifecycle.HookFuncCtx {
310328 }
311329}
312330
331+ // holePunchTracer implements holepunch.EventTracer to log all DCUtR lifecycle events.
332+ type holePunchTracer struct {
333+ ctx context.Context
334+ }
335+
336+ func newHolePunchTracer (ctx context.Context ) * holePunchTracer {
337+ return & holePunchTracer {ctx : log .WithTopic (ctx , "p2p" )}
338+ }
339+
340+ func (t * holePunchTracer ) Trace (evt * holepunch.Event ) {
341+ name := PeerName (evt .Remote )
342+ switch e := evt .Evt .(type ) {
343+ case * holepunch.StartHolePunchEvt :
344+ log .Debug (t .ctx , "Hole punch started" ,
345+ z .Str ("peer" , name ),
346+ z .Any ("remote_addrs" , e .RemoteAddrs ),
347+ z .Any ("rtt" , e .RTT ),
348+ )
349+ case * holepunch.EndHolePunchEvt :
350+ if e .Success {
351+ log .Debug (t .ctx , "Hole punch succeeded" ,
352+ z .Str ("peer" , name ),
353+ z .Any ("elapsed" , e .EllapsedTime ),
354+ )
355+ } else {
356+ log .Warn (t .ctx , "Hole punch failed" , errors .New (e .Error ),
357+ z .Str ("peer" , name ),
358+ z .Any ("elapsed" , e .EllapsedTime ),
359+ )
360+ }
361+ case * holepunch.HolePunchAttemptEvt :
362+ log .Debug (t .ctx , "Hole punch attempt" ,
363+ z .Str ("peer" , name ),
364+ z .Int ("attempt" , e .Attempt ),
365+ )
366+ case * holepunch.DirectDialEvt :
367+ if e .Success {
368+ log .Debug (t .ctx , "Direct dial succeeded during hole punch" ,
369+ z .Str ("peer" , name ),
370+ z .Any ("elapsed" , e .EllapsedTime ),
371+ )
372+ } else {
373+ log .Debug (t .ctx , "Direct dial failed during hole punch" ,
374+ z .Str ("peer" , name ),
375+ z .Any ("elapsed" , e .EllapsedTime ),
376+ z .Str ("error" , e .Error ),
377+ )
378+ }
379+ case * holepunch.ProtocolErrorEvt :
380+ log .Warn (t .ctx , "Hole punch protocol error" , errors .New (e .Error ),
381+ z .Str ("peer" , name ),
382+ )
383+ default :
384+ log .Warn (t .ctx , "Unknown hole punch event" , nil , z .Str ("type" , fmt .Sprintf ("%T" , evt .Evt )))
385+ }
386+ }
387+
388+ // NewPeerStateDiagnostic returns a lifecycle hook that periodically logs the connection
389+ // and peerstore address state for each cluster peer to help diagnose hole punching issues.
390+ func NewPeerStateDiagnostic (p2pNode host.Host , peers []peer.ID ) lifecycle.HookFuncCtx {
391+ return func (ctx context.Context ) {
392+ ctx = log .WithTopic (ctx , "p2p" )
393+
394+ ticker := time .NewTicker (30 * time .Second )
395+ defer ticker .Stop ()
396+
397+ for {
398+ select {
399+ case <- ctx .Done ():
400+ return
401+ case <- ticker .C :
402+ }
403+
404+ selfID := p2pNode .ID ()
405+ for _ , pID := range peers {
406+ if pID == selfID {
407+ continue
408+ }
409+
410+ name := PeerName (pID )
411+
412+ var relayConns , directConns int
413+
414+ for _ , conn := range p2pNode .Network ().ConnsToPeer (pID ) {
415+ if isRelayAddr (conn .RemoteMultiaddr ()) {
416+ relayConns ++
417+ } else {
418+ directConns ++
419+ }
420+ }
421+
422+ var publicAddrs , privateAddrs , relayAddrs int
423+
424+ for _ , addr := range p2pNode .Peerstore ().Addrs (pID ) {
425+ switch {
426+ case isRelayAddr (addr ):
427+ relayAddrs ++
428+ case manet .IsPublicAddr (addr ):
429+ publicAddrs ++
430+ default :
431+ privateAddrs ++
432+ }
433+ }
434+
435+ log .Debug (ctx , "Peer connection state" ,
436+ z .Str ("peer" , name ),
437+ z .Int ("relay_conns" , relayConns ),
438+ z .Int ("direct_conns" , directConns ),
439+ z .Int ("peerstore_public_addrs" , publicAddrs ),
440+ z .Int ("peerstore_private_addrs" , privateAddrs ),
441+ z .Int ("peerstore_relay_addrs" , relayAddrs ),
442+ )
443+ }
444+ }
445+ }
446+ }
447+
313448// peerRoutingFunc wraps a function to implement routing.PeerRouting.
314449type peerRoutingFunc func (context.Context , peer.ID ) (peer.AddrInfo , error )
315450
0 commit comments