diff --git a/p2p/discovery/discovery.go b/p2p/discovery/discovery.go index 5271ec9d91..91990ee375 100644 --- a/p2p/discovery/discovery.go +++ b/p2p/discovery/discovery.go @@ -11,6 +11,7 @@ import ( libp2p_host "github.com/libp2p/go-libp2p/core/host" libp2p_peer "github.com/libp2p/go-libp2p/core/peer" libp2p_dis "github.com/libp2p/go-libp2p/p2p/discovery/routing" + manet "github.com/multiformats/go-multiaddr/net" "github.com/rs/zerolog" ) @@ -72,7 +73,59 @@ func (d *dhtDiscovery) Advertise(ctx context.Context, ns string) (time.Duration, // FindPeers discovers peers providing a service func (d *dhtDiscovery) FindPeers(ctx context.Context, ns string, peerLimit int) (<-chan libp2p_peer.AddrInfo, error) { opt := discovery.Limit(peerLimit) - return d.disc.FindPeers(ctx, ns, opt) + in, err := d.disc.FindPeers(ctx, ns, opt) + if err != nil { + return nil, err + } + + out := make(chan libp2p_peer.AddrInfo) + go func() { + defer close(out) + for { + select { + case <-ctx.Done(): + return + case info, ok := <-in: + if !ok { + return + } + if d.host != nil && info.ID == d.host.ID() { + continue + } + if hasOnlyLoopbackAddrs(info) { + d.logger.Debug(). + Interface("peerID", info.ID). + Int("numAddrs", len(info.Addrs)). + Msg("skip discovered peer with loopback-only addresses") + continue + } + select { + case <-ctx.Done(): + return + case out <- info: + } + } + } + }() + + return out, nil +} + +func hasOnlyLoopbackAddrs(info libp2p_peer.AddrInfo) bool { + if len(info.Addrs) == 0 { + return false + } + for _, addr := range info.Addrs { + ip, err := manet.ToIP(addr) + if err != nil { + // Treat non-IP addresses (e.g. DNS) as potentially routable. + return false + } + if !ip.IsLoopback() { + return false + } + } + return true } // GetRawDiscovery get the raw discovery to be used for libp2p pubsub options diff --git a/p2p/host.go b/p2p/host.go index c89fbc9189..47a750fad7 100644 --- a/p2p/host.go +++ b/p2p/host.go @@ -51,6 +51,7 @@ import ( "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr" madns "github.com/multiformats/go-multiaddr-dns" + manet "github.com/multiformats/go-multiaddr/net" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/rs/zerolog" @@ -749,6 +750,32 @@ func normalizeDNSAddress(addr string) string { return "/dnsaddr/" + domain } +// filterRemoteDialAddrs removes loopback addresses that can trigger dial-to-self +// attempts when remote peers advertise localhost endpoints. +func filterRemoteDialAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { + filtered := make([]ma.Multiaddr, 0, len(addrs)) + loopback := make([]ma.Multiaddr, 0, len(addrs)) + for _, addr := range addrs { + ip, err := manet.ToIP(addr) + if err != nil { + // Keep non-IP multiaddrs such as DNS addresses. + filtered = append(filtered, addr) + continue + } + if ip.IsLoopback() { + loopback = append(loopback, addr) + continue + } + filtered = append(filtered, addr) + } + // Keep loopback addresses only when they are the only available candidates. + // This preserves local/test connectivity while still preferring non-loopback routes. + if len(filtered) == 0 && len(loopback) > 0 { + return loopback + } + return filtered +} + // hasTrustedSources checks if there are any trusted sources configured. // Returns true if at least one of trustedNodes or dnsStaticNodes is non-empty. func (host *HostV2) hasTrustedSources() bool { @@ -892,9 +919,15 @@ func (host *HostV2) AddTrustedNodes(ctx context.Context) { host.logger.Error().Err(err).Str("addr", addr).Msg("[AddTrustedNodes] failed to parse concrete trusted node") continue } - host.logger.Info().Interface("peerID", info.ID).Int("numAddrs", len(info.Addrs)).Str("src", addr).Msg("[AddTrustedNodes] adding concrete trusted peer") - host.Peerstore().AddAddrs(info.ID, info.Addrs, libp2p_peerstore.PermanentAddrTTL) - if err := host.h.Connect(ctx, *info); err != nil { + dialInfo := *info + dialInfo.Addrs = filterRemoteDialAddrs(info.Addrs) + if len(dialInfo.Addrs) == 0 { + host.logger.Warn().Interface("peerID", info.ID).Str("src", addr).Msg("[AddTrustedNodes] skipping trusted peer: only loopback addresses") + continue + } + host.logger.Info().Interface("peerID", info.ID).Int("numAddrs", len(dialInfo.Addrs)).Str("src", addr).Msg("[AddTrustedNodes] adding concrete trusted peer") + host.Peerstore().AddAddrs(dialInfo.ID, dialInfo.Addrs, libp2p_peerstore.PermanentAddrTTL) + if err := host.h.Connect(ctx, dialInfo); err != nil { host.logger.Error().Err(err).Interface("peerID", info.ID).Msg("[AddTrustedNodes] failed to connect trusted peer") trustedPeersConnectFailuresCounter.Inc() continue @@ -1089,13 +1122,19 @@ func (host *HostV2) AddDNSNodestoTrustedPeers(ctx context.Context, sources []str continue } - host.Peerstore().AddAddrs(info.ID, info.Addrs, libp2p_peerstore.PermanentAddrTTL) - addrsStr := make([]string, len(info.Addrs)) - for i, addr := range info.Addrs { + dialInfo := info + dialInfo.Addrs = filterRemoteDialAddrs(info.Addrs) + if len(dialInfo.Addrs) == 0 { + host.logger.Warn().Interface("peerID", info.ID).Msg("[AddDNSNodestoTrustedPeers] skipping peer: only loopback addresses") + continue + } + host.Peerstore().AddAddrs(dialInfo.ID, dialInfo.Addrs, libp2p_peerstore.PermanentAddrTTL) + addrsStr := make([]string, len(dialInfo.Addrs)) + for i, addr := range dialInfo.Addrs { addrsStr[i] = addr.String() } - host.logger.Info().Interface("peerID", info.ID).Int("numAddrs", len(info.Addrs)).Strs("addrs", addrsStr).Msg("[AddDNSNodestoTrustedPeers] attempting to connect") - if err := host.h.Connect(ctx, info); err != nil { + host.logger.Info().Interface("peerID", info.ID).Int("numAddrs", len(dialInfo.Addrs)).Strs("addrs", addrsStr).Msg("[AddDNSNodestoTrustedPeers] attempting to connect") + if err := host.h.Connect(ctx, dialInfo); err != nil { host.logger.Error().Err(err).Interface("peerID", info.ID).Strs("addrs", addrsStr).Msg("[AddDNSNodestoTrustedPeers] connect failed") trustedPeersConnectFailuresCounter.Inc() continue