mirror of
https://github.com/libp2p/go-libp2p.git
synced 2026-04-22 16:17:19 +08:00
feat: NonPublicAddrPublishing option (#3489)
Co-authored-by: Marco Munizaga <git@marcopolo.io>
This commit is contained in:
+18
-15
@@ -125,6 +125,8 @@ type Config struct {
|
||||
|
||||
DisablePing bool
|
||||
|
||||
DisableNonPublicAddrPublishing bool
|
||||
|
||||
Routing RoutingC
|
||||
|
||||
EnableAutoRelay bool
|
||||
@@ -442,21 +444,22 @@ func (cfg *Config) addTransports() ([]fx.Option, error) {
|
||||
|
||||
func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus, an *autonatv2.AutoNAT, o bhost.ObservedAddrsManager) (*bhost.BasicHost, error) {
|
||||
h, err := bhost.NewHost(swrm, &bhost.HostOpts{
|
||||
EventBus: eventBus,
|
||||
ConnManager: cfg.ConnManager,
|
||||
AddrsFactory: cfg.AddrsFactory,
|
||||
NATManager: cfg.NATManager,
|
||||
EnablePing: !cfg.DisablePing,
|
||||
UserAgent: cfg.UserAgent,
|
||||
ProtocolVersion: cfg.ProtocolVersion,
|
||||
EnableHolePunching: cfg.EnableHolePunching,
|
||||
HolePunchingOptions: cfg.HolePunchingOptions,
|
||||
EnableRelayService: cfg.EnableRelayService,
|
||||
RelayServiceOpts: cfg.RelayServiceOpts,
|
||||
EnableMetrics: !cfg.DisableMetrics,
|
||||
PrometheusRegisterer: cfg.PrometheusRegisterer,
|
||||
AutoNATv2: an,
|
||||
ObservedAddrsManager: o,
|
||||
EventBus: eventBus,
|
||||
ConnManager: cfg.ConnManager,
|
||||
AddrsFactory: cfg.AddrsFactory,
|
||||
NATManager: cfg.NATManager,
|
||||
EnablePing: !cfg.DisablePing,
|
||||
UserAgent: cfg.UserAgent,
|
||||
ProtocolVersion: cfg.ProtocolVersion,
|
||||
EnableHolePunching: cfg.EnableHolePunching,
|
||||
HolePunchingOptions: cfg.HolePunchingOptions,
|
||||
EnableRelayService: cfg.EnableRelayService,
|
||||
RelayServiceOpts: cfg.RelayServiceOpts,
|
||||
EnableMetrics: !cfg.DisableMetrics,
|
||||
PrometheusRegisterer: cfg.PrometheusRegisterer,
|
||||
DisableNonPublicAddrPublishing: cfg.DisableNonPublicAddrPublishing,
|
||||
AutoNATv2: an,
|
||||
ObservedAddrsManager: o,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+15
@@ -441,6 +441,21 @@ func Ping(enable bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// NonPublicAddrPublishing controls whether the host advertises addresses that
|
||||
// are not in a globally-routable range (RFC 1918 private, RFC 6598 CGNAT,
|
||||
// link-local, loopback, ULA, IPv6 documentation/multicast/reserved space)
|
||||
// through the peerstore and signed peer records. Multiaddrs without an IP
|
||||
// component such as /p2p-circuit are not affected.
|
||||
//
|
||||
// Defaults to true for backward compatibility. Set to false on public-facing
|
||||
// nodes to avoid leaking internal topology through identify and DHT records.
|
||||
func NonPublicAddrPublishing(enable bool) Option {
|
||||
return func(cfg *Config) error {
|
||||
cfg.DisableNonPublicAddrPublishing = !enable
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Routing will configure libp2p to use routing.
|
||||
func Routing(rt config.RoutingC) Option {
|
||||
return func(cfg *Config) error {
|
||||
|
||||
@@ -71,10 +71,11 @@ type addrsManager struct {
|
||||
addrsMx sync.RWMutex
|
||||
currentAddrs hostAddrs
|
||||
|
||||
signKey crypto.PrivKey
|
||||
addrStore addrStore
|
||||
signedRecordStore peerstore.CertifiedAddrBook
|
||||
hostID peer.ID
|
||||
signKey crypto.PrivKey
|
||||
addrStore addrStore
|
||||
signedRecordStore peerstore.CertifiedAddrBook
|
||||
hostID peer.ID
|
||||
disableNonPublicAddrPublishing bool
|
||||
|
||||
wg sync.WaitGroup
|
||||
ctx context.Context
|
||||
@@ -92,26 +93,28 @@ func newAddrsManager(
|
||||
enableMetrics bool,
|
||||
registerer prometheus.Registerer,
|
||||
disableSignedPeerRecord bool,
|
||||
disableNonPublicAddrPublishing bool,
|
||||
signKey crypto.PrivKey,
|
||||
addrStore addrStore,
|
||||
hostID peer.ID,
|
||||
) (*addrsManager, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
as := &addrsManager{
|
||||
bus: bus,
|
||||
listenAddrs: listenAddrs,
|
||||
addCertHashes: addCertHashes,
|
||||
observedAddrsManager: observedAddrsManager,
|
||||
natManager: natmgr,
|
||||
addrsFactory: addrsFactory,
|
||||
triggerAddrsUpdateChan: make(chan chan struct{}, 1),
|
||||
triggerReachabilityUpdate: make(chan struct{}, 1),
|
||||
interfaceAddrs: &interfaceAddrsCache{},
|
||||
signKey: signKey,
|
||||
addrStore: addrStore,
|
||||
hostID: hostID,
|
||||
ctx: ctx,
|
||||
ctxCancel: cancel,
|
||||
bus: bus,
|
||||
listenAddrs: listenAddrs,
|
||||
addCertHashes: addCertHashes,
|
||||
observedAddrsManager: observedAddrsManager,
|
||||
natManager: natmgr,
|
||||
addrsFactory: addrsFactory,
|
||||
triggerAddrsUpdateChan: make(chan chan struct{}, 1),
|
||||
triggerReachabilityUpdate: make(chan struct{}, 1),
|
||||
interfaceAddrs: &interfaceAddrsCache{},
|
||||
signKey: signKey,
|
||||
addrStore: addrStore,
|
||||
hostID: hostID,
|
||||
disableNonPublicAddrPublishing: disableNonPublicAddrPublishing,
|
||||
ctx: ctx,
|
||||
ctxCancel: cancel,
|
||||
}
|
||||
unknownReachability := network.ReachabilityUnknown
|
||||
as.hostReachability.Store(&unknownReachability)
|
||||
@@ -343,8 +346,11 @@ func (a *addrsManager) updateAddrs(prevHostAddrs hostAddrs, relayAddrs []ma.Mult
|
||||
|
||||
// updatePeerStore updates the peer store for the host
|
||||
func (a *addrsManager) updatePeerStore(currentAddrs []ma.Multiaddr, removedAddrs []ma.Multiaddr) {
|
||||
// update host addresses in the peer store
|
||||
a.addrStore.SetAddrs(a.hostID, currentAddrs, peerstore.PermanentAddrTTL)
|
||||
publishedAddrs := currentAddrs
|
||||
if a.disableNonPublicAddrPublishing {
|
||||
publishedAddrs = filterPublicAddrs(currentAddrs)
|
||||
}
|
||||
a.addrStore.SetAddrs(a.hostID, publishedAddrs, peerstore.PermanentAddrTTL)
|
||||
a.addrStore.SetAddrs(a.hostID, removedAddrs, 0)
|
||||
|
||||
var sr *record.Envelope
|
||||
@@ -354,7 +360,7 @@ func (a *addrsManager) updatePeerStore(currentAddrs []ma.Multiaddr, removedAddrs
|
||||
var err error
|
||||
// add signed peer record to the event
|
||||
// in case of an error drop this event.
|
||||
sr, err = a.makeSignedPeerRecord(currentAddrs)
|
||||
sr, err = a.makeSignedPeerRecord(publishedAddrs)
|
||||
if err != nil {
|
||||
log.Error("error creating a signed peer record from the set of current addresses", "err", err)
|
||||
return
|
||||
@@ -366,6 +372,39 @@ func (a *addrsManager) updatePeerStore(currentAddrs []ma.Multiaddr, removedAddrs
|
||||
}
|
||||
}
|
||||
|
||||
// filterPublicAddrs drops IP-based multiaddrs that are not in a globally
|
||||
// routable range. Addrs without an IP or DNS component (e.g. /p2p-circuit)
|
||||
// are kept as-is because manet.IsPublicAddr returns false for them.
|
||||
// DNS components are evaluated by manet.IsPublicAddr (special-use names
|
||||
// like .local, .invalid, .localhost are non-public).
|
||||
func filterPublicAddrs(addrs []ma.Multiaddr) []ma.Multiaddr {
|
||||
filtered := make([]ma.Multiaddr, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
if hasIPOrDNSComponent(addr) && !manet.IsPublicAddr(addr) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, addr)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// hasIPOrDNSComponent reports whether addr's leading component is an IP,
|
||||
// DNS, or IP6ZONE wrapper. Transport multiaddrs encode their network layer
|
||||
// at the front, so the leading component is sufficient to tell whether
|
||||
// manet.IsPublicAddr can meaningfully evaluate the addr. Without this
|
||||
// guard, filterPublicAddrs would also drop multiaddrs that have no
|
||||
// network-layer address, such as /p2p-circuit/p2p/<id>.
|
||||
func hasIPOrDNSComponent(addr ma.Multiaddr) bool {
|
||||
if len(addr) == 0 {
|
||||
return false
|
||||
}
|
||||
switch addr[0].Protocol().Code {
|
||||
case ma.P_IP4, ma.P_IP6, ma.P_IP6ZONE, ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *addrsManager) notifyAddrsUpdated(emitter event.Emitter, localAddrsEmitter event.Emitter, previous, current hostAddrs) {
|
||||
if areAddrsDifferent(previous.localAddrs, current.localAddrs) {
|
||||
log.Debug("host local addresses updated", "addrs", current.localAddrs)
|
||||
|
||||
@@ -64,14 +64,15 @@ type addrStoreArgs struct {
|
||||
}
|
||||
|
||||
type addrsManagerArgs struct {
|
||||
NATManager NATManager
|
||||
AddrsFactory AddrsFactory
|
||||
ObservedAddrsManager ObservedAddrsManager
|
||||
ListenAddrs func() []ma.Multiaddr
|
||||
AddCertHashes func([]ma.Multiaddr) []ma.Multiaddr
|
||||
AutoNATClient autonatv2Client
|
||||
Bus event.Bus
|
||||
AddrStoreArgs addrStoreArgs
|
||||
NATManager NATManager
|
||||
AddrsFactory AddrsFactory
|
||||
ObservedAddrsManager ObservedAddrsManager
|
||||
ListenAddrs func() []ma.Multiaddr
|
||||
AddCertHashes func([]ma.Multiaddr) []ma.Multiaddr
|
||||
AutoNATClient autonatv2Client
|
||||
Bus event.Bus
|
||||
AddrStoreArgs addrStoreArgs
|
||||
DisableNonPublicAddrPublishing bool
|
||||
}
|
||||
|
||||
type addrsManagerTestCase struct {
|
||||
@@ -118,6 +119,7 @@ func newAddrsManagerTestCase(tb testing.TB, args addrsManagerArgs) addrsManagerT
|
||||
true,
|
||||
prometheus.DefaultRegisterer,
|
||||
false,
|
||||
args.DisableNonPublicAddrPublishing,
|
||||
signKey,
|
||||
addrStore,
|
||||
pid,
|
||||
@@ -488,6 +490,88 @@ func TestAddrsManagerPeerstoreUpdated(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestAddrsManagerNonPublicAddrPublishing(t *testing.T) {
|
||||
publicV4 := ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1")
|
||||
publicV6 := ma.StringCast("/ip6/2001:41d0:203:2ca6::/udp/4001/quic-v1")
|
||||
loopback4 := ma.StringCast("/ip4/127.0.0.1/udp/1/quic-v1")
|
||||
loopback6 := ma.StringCast("/ip6/::1/udp/1/quic-v1")
|
||||
rfc1918 := ma.StringCast("/ip4/192.168.1.5/tcp/4001")
|
||||
cgnat := ma.StringCast("/ip4/100.64.0.1/tcp/4001")
|
||||
linkLocal4 := ma.StringCast("/ip4/169.254.10.10/tcp/4001")
|
||||
ula := ma.StringCast("/ip6/fc00::1/tcp/4001")
|
||||
linkLocal6 := ma.StringCast("/ip6/fe80::1/tcp/4001")
|
||||
reservedV6 := ma.StringCast("/ip6/1e::109d:0:2:c80b/tcp/4001")
|
||||
docV6 := ma.StringCast("/ip6/2001:db8::1/tcp/4001")
|
||||
circuit := ma.StringCast("/p2p/12D3KooWGyVU3Z7iEFEKnLRWUZSCgZkruxXt9TafKigQv9TUx2N1/p2p-circuit")
|
||||
dnsPublic := ma.StringCast("/dns4/example.com/tcp/443/wss")
|
||||
dnsLocal := ma.StringCast("/dns4/foo.local/tcp/443")
|
||||
zonedLinkLocal6 := ma.StringCast("/ip6zone/eth0/ip6/fe80::1/tcp/4001")
|
||||
|
||||
all := []ma.Multiaddr{
|
||||
publicV4, publicV6,
|
||||
loopback4, loopback6,
|
||||
rfc1918, cgnat, linkLocal4,
|
||||
ula, linkLocal6,
|
||||
reservedV6, docV6,
|
||||
circuit, dnsPublic, dnsLocal,
|
||||
zonedLinkLocal6,
|
||||
}
|
||||
|
||||
t.Run("publishes everything by default", func(t *testing.T) {
|
||||
pstore, err := pstoremem.NewPeerstore()
|
||||
require.NoError(t, err)
|
||||
cab, _ := peerstore.GetCertifiedAddrBook(pstore)
|
||||
signKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
pid, err := peer.IDFromPrivateKey(signKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
ListenAddrs: func() []ma.Multiaddr { return nil },
|
||||
AddrsFactory: func([]ma.Multiaddr) []ma.Multiaddr { return all },
|
||||
AddrStoreArgs: addrStoreArgs{
|
||||
AddrStore: pstore,
|
||||
HostID: pid,
|
||||
SignKey: signKey,
|
||||
},
|
||||
})
|
||||
defer am.Close()
|
||||
|
||||
require.ElementsMatch(t, all, pstore.Addrs(pid))
|
||||
pr := peerRecordFromEnvelope(t, cab.GetPeerRecord(pid))
|
||||
require.ElementsMatch(t, all, pr.Addrs)
|
||||
})
|
||||
|
||||
t.Run("strips non-public IP addrs when publishing is disabled", func(t *testing.T) {
|
||||
pstore, err := pstoremem.NewPeerstore()
|
||||
require.NoError(t, err)
|
||||
cab, _ := peerstore.GetCertifiedAddrBook(pstore)
|
||||
signKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
pid, err := peer.IDFromPrivateKey(signKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
ListenAddrs: func() []ma.Multiaddr { return nil },
|
||||
AddrsFactory: func([]ma.Multiaddr) []ma.Multiaddr { return all },
|
||||
AddrStoreArgs: addrStoreArgs{
|
||||
AddrStore: pstore,
|
||||
HostID: pid,
|
||||
SignKey: signKey,
|
||||
},
|
||||
DisableNonPublicAddrPublishing: true,
|
||||
})
|
||||
defer am.Close()
|
||||
|
||||
// kept: public v4/v6, /p2p-circuit (no IP), public DNS
|
||||
// stripped: loopback, RFC1918, CGNAT, link-local (incl. ip6zone-wrapped), ULA, reserved/doc IPv6, .local DNS
|
||||
expected := []ma.Multiaddr{publicV4, publicV6, circuit, dnsPublic}
|
||||
require.ElementsMatch(t, expected, pstore.Addrs(pid))
|
||||
pr := peerRecordFromEnvelope(t, cab.GetPeerRecord(pid))
|
||||
require.ElementsMatch(t, expected, pr.Addrs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveIfNotInSource(t *testing.T) {
|
||||
addrs := make([]ma.Multiaddr, 0, 10)
|
||||
for i := range 10 {
|
||||
|
||||
@@ -127,6 +127,14 @@ type HostOpts struct {
|
||||
// DisableSignedPeerRecord disables the generation of Signed Peer Records on this host.
|
||||
DisableSignedPeerRecord bool
|
||||
|
||||
// DisableNonPublicAddrPublishing excludes addresses that are not in a
|
||||
// globally-routable range (e.g. RFC 1918 private, RFC 6598 CGNAT, link-local,
|
||||
// loopback, ULA, IPv6 documentation/multicast/reserved space) from the
|
||||
// host's peerstore entry and signed peer record. This prevents leaking
|
||||
// non-public IP addresses to remote peers via identify or the DHT.
|
||||
// Multiaddrs without an IP component such as /p2p-circuit are not affected.
|
||||
DisableNonPublicAddrPublishing bool
|
||||
|
||||
// EnableHolePunching enables the peer to initiate/respond to hole punching attempts for NAT traversal.
|
||||
EnableHolePunching bool
|
||||
// HolePunchingOptions are options for the hole punching service
|
||||
@@ -238,6 +246,7 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
|
||||
opts.EnableMetrics,
|
||||
opts.PrometheusRegisterer,
|
||||
opts.DisableSignedPeerRecord,
|
||||
opts.DisableNonPublicAddrPublishing,
|
||||
h.Peerstore().PrivKey(h.ID()),
|
||||
h.Peerstore(),
|
||||
h.ID(),
|
||||
|
||||
Reference in New Issue
Block a user