Skip to content

Commit 6e08854

Browse files
committed
Disable MDNS by default and filter out private IPs, to prevent possible netscanning actions from libp2p
1 parent ad7e2fd commit 6e08854

File tree

2 files changed

+115
-5
lines changed

2 files changed

+115
-5
lines changed

client.go

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"errors"
1212
"fmt"
1313
"io"
14+
"net"
1415
"os"
1516
"slices"
1617
"strings"
@@ -28,6 +29,7 @@ import (
2829
"github.com/libp2p/go-libp2p/p2p/discovery/mdns"
2930
drouting "github.com/libp2p/go-libp2p/p2p/discovery/routing"
3031
"github.com/multiformats/go-multiaddr"
32+
manet "github.com/multiformats/go-multiaddr/net"
3133

3234
ds "github.com/ipfs/go-datastore"
3335
dssync "github.com/ipfs/go-datastore/sync"
@@ -114,12 +116,17 @@ func NewClient(config Config) (Client, error) {
114116
return nil, fmt.Errorf("failed to create pubsub: %w", err)
115117
}
116118

117-
// Set up mDNS discovery
118-
mdnsService := mdns.NewMdnsService(h, "", &discoveryNotifee{h: h, ctx: ctx, logger: clientLogger})
119-
if err := mdnsService.Start(); err != nil {
120-
clientLogger.Errorf("mDNS failed to start: %v", err)
119+
// Set up mDNS discovery (only if explicitly enabled)
120+
var mdnsService mdns.Service
121+
if config.EnableMDNS {
122+
mdnsService = mdns.NewMdnsService(h, "", &discoveryNotifee{h: h, ctx: ctx, logger: clientLogger})
123+
if err := mdnsService.Start(); err != nil {
124+
clientLogger.Errorf("mDNS failed to start: %v", err)
125+
} else {
126+
clientLogger.Infof("mDNS discovery started")
127+
}
121128
} else {
122-
clientLogger.Infof("mDNS discovery started")
129+
clientLogger.Infof("mDNS discovery disabled (production safe default)")
123130
}
124131

125132
c := &client{
@@ -623,6 +630,15 @@ func (c *client) connectToDiscoveredPeer(ctx context.Context, peerInfo peer.Addr
623630
return
624631
}
625632

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+
626642
if err := c.host.Connect(ctx, peerInfo); err != nil {
627643
if c.shouldLogConnectionError(err) {
628644
c.logger.Debugf("Failed to connect to discovered peer %s: %v", peerInfo.ID.String(), err)
@@ -913,3 +929,79 @@ func PrivateKeyFromHex(keyHex string) (crypto.PrivKey, error) {
913929

914930
return priv, nil
915931
}
932+
933+
// filterPrivateAddrs filters out private/local IP addresses from a list of multiaddrs.
934+
// Returns only public routable addresses suitable for cloud/shared hosting environments.
935+
func filterPrivateAddrs(addrs []multiaddr.Multiaddr) []multiaddr.Multiaddr {
936+
filtered := make([]multiaddr.Multiaddr, 0, len(addrs))
937+
938+
for _, addr := range addrs {
939+
// Convert multiaddr to net.Addr to check if it's private
940+
netAddr, err := manet.ToNetAddr(addr)
941+
if err != nil {
942+
// If we can't parse it, skip it
943+
continue
944+
}
945+
946+
// Extract IP address
947+
var ip net.IP
948+
switch v := netAddr.(type) {
949+
case *net.TCPAddr:
950+
ip = v.IP
951+
case *net.UDPAddr:
952+
ip = v.IP
953+
default:
954+
// Unknown address type, skip it
955+
continue
956+
}
957+
958+
// Filter out private/local addresses
959+
if isPrivateIP(ip) {
960+
continue
961+
}
962+
963+
filtered = append(filtered, addr)
964+
}
965+
966+
return filtered
967+
}
968+
969+
// isPrivateIP checks if an IP address is private or local.
970+
// Returns true for:
971+
// - RFC1918 private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
972+
// - Link-local addresses (169.254.0.0/16)
973+
// - Loopback addresses (127.0.0.0/8)
974+
// - IPv6 unique local addresses (fc00::/7)
975+
// - IPv6 link-local addresses (fe80::/10)
976+
func isPrivateIP(ip net.IP) bool {
977+
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
978+
return true
979+
}
980+
981+
// Check for private IPv4 ranges
982+
if ip4 := ip.To4(); ip4 != nil {
983+
// 10.0.0.0/8
984+
if ip4[0] == 10 {
985+
return true
986+
}
987+
// 172.16.0.0/12
988+
if ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31 {
989+
return true
990+
}
991+
// 192.168.0.0/16
992+
if ip4[0] == 192 && ip4[1] == 168 {
993+
return true
994+
}
995+
// 169.254.0.0/16 (link-local)
996+
if ip4[0] == 169 && ip4[1] == 254 {
997+
return true
998+
}
999+
}
1000+
1001+
// Check for IPv6 unique local addresses (fc00::/7)
1002+
if len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc {
1003+
return true
1004+
}
1005+
1006+
return false
1007+
}

config.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,22 @@ type Config struct {
103103
// due to libp2p's NAT manager using non-thread-safe global state.
104104
// Default: false (NAT features enabled)
105105
DisableNAT bool
106+
107+
// EnableMDNS enables multicast DNS peer discovery on the local network.
108+
// When true, the node broadcasts mDNS queries to discover peers on the same LAN.
109+
// IMPORTANT: Only enable on isolated local networks with proper VLANs. On shared hosting
110+
// (e.g., Hetzner, AWS) without VLANs, mDNS broadcasts appear as network scanning and may
111+
// result in abuse reports.
112+
// Default: false (mDNS disabled for production safety)
113+
// Set to true only for local development networks with proper isolation
114+
EnableMDNS bool
115+
116+
// AllowPrivateIPs allows connections to private/local IP addresses during peer discovery.
117+
// When true, the node will attempt to connect to RFC1918 private networks (10.0.0.0/8,
118+
// 172.16.0.0/12, 192.168.0.0/16), link-local addresses (169.254.0.0/16), and localhost.
119+
// IMPORTANT: Only enable on private networks. On shared hosting, this may trigger network
120+
// scanning alerts.
121+
// Default: false (private IPs filtered for production safety)
122+
// Set to true only for local development or private network deployments
123+
AllowPrivateIPs bool
106124
}

0 commit comments

Comments
 (0)