@@ -16,6 +16,7 @@ import (
1616 "slices"
1717 "strings"
1818 "sync"
19+ "testing"
1920 "time"
2021
2122 "github.com/libp2p/go-libp2p"
@@ -28,6 +29,7 @@ import (
2829 "github.com/libp2p/go-libp2p/core/peer"
2930 "github.com/libp2p/go-libp2p/p2p/discovery/mdns"
3031 drouting "github.com/libp2p/go-libp2p/p2p/discovery/routing"
32+ "github.com/libp2p/go-libp2p/p2p/net/conngater"
3133 "github.com/multiformats/go-multiaddr"
3234 manet "github.com/multiformats/go-multiaddr/net"
3335
@@ -85,11 +87,7 @@ func NewClient(config Config) (Client, error) {
8587 return nil , err
8688 }
8789
88- // Get bootstrap peers from DHT library for DHT bootstrapping
89- bootstrapPeers := dht .GetDefaultBootstrapPeerAddrInfos ()
90-
91- // Determine which peers to use as relays
92- relayPeers := configureRelayPeers (config .RelayPeers , bootstrapPeers , clientLogger )
90+ bootstrapPeers , relayPeers := getBootstrapAndRelayPeers (config , clientLogger )
9391
9492 // Create and setup libp2p host
9593 h , err := createHost (ctx , hostOpts , config , relayPeers , clientLogger , cancel )
@@ -152,6 +150,34 @@ func NewClient(config Config) (Client, error) {
152150 return c , nil
153151}
154152
153+ func getBootstrapAndRelayPeers (config Config , clientLogger logger ) ([]peer.AddrInfo , []peer.AddrInfo ) {
154+ // Get bootstrap peers based on environment
155+ // Test mode: empty list for fast, isolated tests
156+ // Production: IPFS default bootstrap peers
157+ var bootstrapPeers []peer.AddrInfo
158+ if testing .Testing () {
159+ bootstrapPeers = []peer.AddrInfo {}
160+ clientLogger .Infof ("Test mode detected - using no bootstrap peers (isolated mode)" )
161+ } else {
162+ bootstrapPeers = dht .GetDefaultBootstrapPeerAddrInfos ()
163+ clientLogger .Infof ("Using %d default IPFS bootstrap peers" , len (bootstrapPeers ))
164+ }
165+
166+ // Parse custom relay peers if provided
167+ customRelayPeers := parseRelayPeersFromConfig (config .RelayPeers , clientLogger )
168+
169+ // Add custom relay peers to bootstrap list for better peer discovery
170+ // This helps the DHT routing table include your known-good relay peers
171+ if len (customRelayPeers ) > 0 {
172+ bootstrapPeers = append (bootstrapPeers , customRelayPeers ... )
173+ clientLogger .Infof ("Added %d custom relay peers to bootstrap peer list" , len (customRelayPeers ))
174+ }
175+
176+ // Determine which peers to use as relays (relay peers OR bootstrap peers as fallback)
177+ relayPeers := selectRelayPeers (customRelayPeers , bootstrapPeers , clientLogger )
178+ return bootstrapPeers , relayPeers
179+ }
180+
155181// Helper functions for NewClient
156182
157183func getLogger (configLogger logger ) logger {
@@ -163,9 +189,61 @@ func getLogger(configLogger logger) logger {
163189 return configLogger
164190}
165191
192+ // createPrivateIPConnectionGater creates a ConnectionGater that blocks private IP ranges.
193+ // Returns a configured BasicConnectionGater that prevents connections to/from:
194+ // - RFC1918 private networks (10.x, 172.16-31.x, 192.168.x)
195+ // - Link-local addresses (169.254.x, fe80::)
196+ // - Loopback addresses (127.x, ::1)
197+ // - Shared address space (100.64.x)
198+ // - IPv6 unique local addresses (fc00::)
199+ func createPrivateIPConnectionGater (log logger , cancel context.CancelFunc ) (* conngater.BasicConnectionGater , error ) {
200+ ipFilter , err := conngater .NewBasicConnectionGater (nil )
201+ if err != nil {
202+ cancel ()
203+ return nil , fmt .Errorf ("failed to create connection gater: %w" , err )
204+ }
205+
206+ // Standard private IP ranges to block
207+ privateRanges := []string {
208+ "10.0.0.0/8" , // RFC1918 private network
209+ "172.16.0.0/12" , // RFC1918 private network
210+ "192.168.0.0/16" , // RFC1918 private network
211+ "127.0.0.0/8" , // Loopback
212+ "169.254.0.0/16" , // Link-local
213+ "100.64.0.0/10" , // Shared Address Space (RFC6598)
214+ "fc00::/7" , // IPv6 Unique Local Addresses
215+ "fe80::/10" , // IPv6 Link-Local Addresses
216+ "::1/128" , // IPv6 Loopback
217+ }
218+
219+ for _ , cidr := range privateRanges {
220+ _ , ipnet , err := net .ParseCIDR (cidr )
221+ if err != nil {
222+ cancel ()
223+ return nil , fmt .Errorf ("failed to parse CIDR %s: %w" , cidr , err )
224+ }
225+ if err := ipFilter .BlockSubnet (ipnet ); err != nil {
226+ log .Warnf ("Failed to block subnet %s: %v" , cidr , err )
227+ }
228+ }
229+
230+ return ipFilter , nil
231+ }
232+
166233func buildHostOptions (config Config , log logger , cancel context.CancelFunc ) ([]libp2p.Option , error ) {
167234 hostOpts := []libp2p.Option {libp2p .Identity (config .PrivateKey )}
168235
236+ // Add connection gater to block private IPs if AllowPrivateIPs is false (default)
237+ if ! config .AllowPrivateIPs {
238+ ipFilter , err := createPrivateIPConnectionGater (log , cancel )
239+ if err != nil {
240+ return nil , err
241+ }
242+
243+ hostOpts = append (hostOpts , libp2p .ConnectionGater (ipFilter ))
244+ log .Infof ("Private IP connection gater enabled (blocking RFC1918 and local addresses)" )
245+ }
246+
169247 // Configure announce addresses if provided (useful for K8s)
170248 if len (config .AnnounceAddrs ) > 0 {
171249 announceAddrs := make ([]multiaddr.Multiaddr , 0 , len (config .AnnounceAddrs ))
@@ -195,14 +273,17 @@ func createHost(_ context.Context, hostOpts []libp2p.Option, config Config, rela
195273 ),
196274 )
197275
198- // Only enable NAT traversal features if not disabled
199- // NAT features can cause data races in tests due to libp2p's NAT manager using non-thread-safe global state
200- if ! config .DisableNAT {
276+ // Enable NAT features only if explicitly enabled
277+ // UPnP/ NAT-PMP scans the local gateway which triggers network scanning alerts
278+ if config .EnableNAT {
201279 hostOpts = append (hostOpts ,
202280 libp2p .NATPortMap (),
203281 libp2p .EnableNATService (),
204282 libp2p .EnableHolePunching (),
205283 )
284+ log .Infof ("UPnP/NAT-PMP enabled (will scan local gateway for port mapping)" )
285+ } else {
286+ log .Infof ("UPnP/NAT-PMP disabled (production safe default)" )
206287 }
207288
208289 hostOpts = append (hostOpts ,
@@ -280,33 +361,43 @@ func setupDHT(ctx context.Context, h host.Host, config Config, bootstrapPeers []
280361 return kadDHT , nil
281362}
282363
283- func configureRelayPeers (relayPeersConfig []string , bootstrapPeers []peer.AddrInfo , log logger ) []peer.AddrInfo {
284- if len (relayPeersConfig ) == 0 {
285- log .Infof ("Using bootstrap peers as relays" )
286- return bootstrapPeers
364+ // parseRelayPeersFromConfig parses relay peer multiaddr strings into AddrInfo
365+ func parseRelayPeersFromConfig (relayPeersConfig []string , log logger ) []peer.AddrInfo {
366+ return parsePeerMultiaddrs (relayPeersConfig , "relay" , log )
367+ }
368+
369+ // parsePeerMultiaddrs is a shared helper to parse peer multiaddr strings
370+ func parsePeerMultiaddrs (peerConfigs []string , peerType string , log logger ) []peer.AddrInfo {
371+ if len (peerConfigs ) == 0 {
372+ return nil
287373 }
288374
289- relayPeers := make ([]peer.AddrInfo , 0 , len (relayPeersConfig ))
290- for _ , relayStr := range relayPeersConfig {
291- maddr , err := multiaddr .NewMultiaddr (relayStr )
375+ peers := make ([]peer.AddrInfo , 0 , len (peerConfigs ))
376+ for _ , peerStr := range peerConfigs {
377+ maddr , err := multiaddr .NewMultiaddr (peerStr )
292378 if err != nil {
293- log .Errorf ("Invalid relay address %s: %v (hint: use /dns4/ for hostnames, /ip4/ for IP addresses)" , relayStr , err )
379+ log .Errorf ("Invalid %s address %s: %v (hint: use /dns4/ for hostnames, /ip4/ for IP addresses)" , peerType , peerStr , err )
294380 continue
295381 }
296382 addrInfo , err := peer .AddrInfoFromP2pAddr (maddr )
297383 if err != nil {
298- log .Errorf ("Invalid relay peer info %s: %v" , relayStr , err )
384+ log .Errorf ("Invalid %s peer info %s: %v" , peerType , peerStr , err )
299385 continue
300386 }
301- relayPeers = append (relayPeers , * addrInfo )
387+ peers = append (peers , * addrInfo )
302388 }
303389
304- if len (relayPeers ) > 0 {
305- log .Infof ("Using %d custom relay peer(s)" , len (relayPeers ))
306- return relayPeers
390+ return peers
391+ }
392+
393+ // selectRelayPeers determines which peers to use as relays
394+ func selectRelayPeers (customRelayPeers []peer.AddrInfo , bootstrapPeers []peer.AddrInfo , log logger ) []peer.AddrInfo {
395+ if len (customRelayPeers ) > 0 {
396+ log .Infof ("Using %d custom relay peer(s)" , len (customRelayPeers ))
397+ return customRelayPeers
307398 }
308399
309- log .Warnf ( "No valid custom relay peers found, falling back to bootstrap peers as relays" )
400+ log .Infof ( "Using bootstrap peers as relays" )
310401 return bootstrapPeers
311402}
312403
@@ -630,15 +721,7 @@ func (c *client) connectToDiscoveredPeer(ctx context.Context, peerInfo peer.Addr
630721 return
631722 }
632723
633- // Filter private IPs unless explicitly allowed (default: filter for production safety)
634- if ! c .config .AllowPrivateIPs {
635- peerInfo .Addrs = filterPrivateAddrs (peerInfo .Addrs )
636- if len (peerInfo .Addrs ) == 0 {
637- // All addresses were private, skip this peer
638- return
639- }
640- }
641-
724+ // ConnectionGater handles private IP filtering, so just try to connect
642725 if err := c .host .Connect (ctx , peerInfo ); err != nil {
643726 if c .shouldLogConnectionError (err ) {
644727 c .logger .Debugf ("Failed to connect to discovered peer %s: %v" , peerInfo .ID .String (), err )
0 commit comments