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
|
DisablePing bool
|
||||||
|
|
||||||
|
DisableNonPublicAddrPublishing bool
|
||||||
|
|
||||||
Routing RoutingC
|
Routing RoutingC
|
||||||
|
|
||||||
EnableAutoRelay bool
|
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) {
|
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{
|
h, err := bhost.NewHost(swrm, &bhost.HostOpts{
|
||||||
EventBus: eventBus,
|
EventBus: eventBus,
|
||||||
ConnManager: cfg.ConnManager,
|
ConnManager: cfg.ConnManager,
|
||||||
AddrsFactory: cfg.AddrsFactory,
|
AddrsFactory: cfg.AddrsFactory,
|
||||||
NATManager: cfg.NATManager,
|
NATManager: cfg.NATManager,
|
||||||
EnablePing: !cfg.DisablePing,
|
EnablePing: !cfg.DisablePing,
|
||||||
UserAgent: cfg.UserAgent,
|
UserAgent: cfg.UserAgent,
|
||||||
ProtocolVersion: cfg.ProtocolVersion,
|
ProtocolVersion: cfg.ProtocolVersion,
|
||||||
EnableHolePunching: cfg.EnableHolePunching,
|
EnableHolePunching: cfg.EnableHolePunching,
|
||||||
HolePunchingOptions: cfg.HolePunchingOptions,
|
HolePunchingOptions: cfg.HolePunchingOptions,
|
||||||
EnableRelayService: cfg.EnableRelayService,
|
EnableRelayService: cfg.EnableRelayService,
|
||||||
RelayServiceOpts: cfg.RelayServiceOpts,
|
RelayServiceOpts: cfg.RelayServiceOpts,
|
||||||
EnableMetrics: !cfg.DisableMetrics,
|
EnableMetrics: !cfg.DisableMetrics,
|
||||||
PrometheusRegisterer: cfg.PrometheusRegisterer,
|
PrometheusRegisterer: cfg.PrometheusRegisterer,
|
||||||
AutoNATv2: an,
|
DisableNonPublicAddrPublishing: cfg.DisableNonPublicAddrPublishing,
|
||||||
ObservedAddrsManager: o,
|
AutoNATv2: an,
|
||||||
|
ObservedAddrsManager: o,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// Routing will configure libp2p to use routing.
|
||||||
func Routing(rt config.RoutingC) Option {
|
func Routing(rt config.RoutingC) Option {
|
||||||
return func(cfg *Config) error {
|
return func(cfg *Config) error {
|
||||||
|
|||||||
@@ -71,10 +71,11 @@ type addrsManager struct {
|
|||||||
addrsMx sync.RWMutex
|
addrsMx sync.RWMutex
|
||||||
currentAddrs hostAddrs
|
currentAddrs hostAddrs
|
||||||
|
|
||||||
signKey crypto.PrivKey
|
signKey crypto.PrivKey
|
||||||
addrStore addrStore
|
addrStore addrStore
|
||||||
signedRecordStore peerstore.CertifiedAddrBook
|
signedRecordStore peerstore.CertifiedAddrBook
|
||||||
hostID peer.ID
|
hostID peer.ID
|
||||||
|
disableNonPublicAddrPublishing bool
|
||||||
|
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -92,26 +93,28 @@ func newAddrsManager(
|
|||||||
enableMetrics bool,
|
enableMetrics bool,
|
||||||
registerer prometheus.Registerer,
|
registerer prometheus.Registerer,
|
||||||
disableSignedPeerRecord bool,
|
disableSignedPeerRecord bool,
|
||||||
|
disableNonPublicAddrPublishing bool,
|
||||||
signKey crypto.PrivKey,
|
signKey crypto.PrivKey,
|
||||||
addrStore addrStore,
|
addrStore addrStore,
|
||||||
hostID peer.ID,
|
hostID peer.ID,
|
||||||
) (*addrsManager, error) {
|
) (*addrsManager, error) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
as := &addrsManager{
|
as := &addrsManager{
|
||||||
bus: bus,
|
bus: bus,
|
||||||
listenAddrs: listenAddrs,
|
listenAddrs: listenAddrs,
|
||||||
addCertHashes: addCertHashes,
|
addCertHashes: addCertHashes,
|
||||||
observedAddrsManager: observedAddrsManager,
|
observedAddrsManager: observedAddrsManager,
|
||||||
natManager: natmgr,
|
natManager: natmgr,
|
||||||
addrsFactory: addrsFactory,
|
addrsFactory: addrsFactory,
|
||||||
triggerAddrsUpdateChan: make(chan chan struct{}, 1),
|
triggerAddrsUpdateChan: make(chan chan struct{}, 1),
|
||||||
triggerReachabilityUpdate: make(chan struct{}, 1),
|
triggerReachabilityUpdate: make(chan struct{}, 1),
|
||||||
interfaceAddrs: &interfaceAddrsCache{},
|
interfaceAddrs: &interfaceAddrsCache{},
|
||||||
signKey: signKey,
|
signKey: signKey,
|
||||||
addrStore: addrStore,
|
addrStore: addrStore,
|
||||||
hostID: hostID,
|
hostID: hostID,
|
||||||
ctx: ctx,
|
disableNonPublicAddrPublishing: disableNonPublicAddrPublishing,
|
||||||
ctxCancel: cancel,
|
ctx: ctx,
|
||||||
|
ctxCancel: cancel,
|
||||||
}
|
}
|
||||||
unknownReachability := network.ReachabilityUnknown
|
unknownReachability := network.ReachabilityUnknown
|
||||||
as.hostReachability.Store(&unknownReachability)
|
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
|
// updatePeerStore updates the peer store for the host
|
||||||
func (a *addrsManager) updatePeerStore(currentAddrs []ma.Multiaddr, removedAddrs []ma.Multiaddr) {
|
func (a *addrsManager) updatePeerStore(currentAddrs []ma.Multiaddr, removedAddrs []ma.Multiaddr) {
|
||||||
// update host addresses in the peer store
|
publishedAddrs := currentAddrs
|
||||||
a.addrStore.SetAddrs(a.hostID, currentAddrs, peerstore.PermanentAddrTTL)
|
if a.disableNonPublicAddrPublishing {
|
||||||
|
publishedAddrs = filterPublicAddrs(currentAddrs)
|
||||||
|
}
|
||||||
|
a.addrStore.SetAddrs(a.hostID, publishedAddrs, peerstore.PermanentAddrTTL)
|
||||||
a.addrStore.SetAddrs(a.hostID, removedAddrs, 0)
|
a.addrStore.SetAddrs(a.hostID, removedAddrs, 0)
|
||||||
|
|
||||||
var sr *record.Envelope
|
var sr *record.Envelope
|
||||||
@@ -354,7 +360,7 @@ func (a *addrsManager) updatePeerStore(currentAddrs []ma.Multiaddr, removedAddrs
|
|||||||
var err error
|
var err error
|
||||||
// add signed peer record to the event
|
// add signed peer record to the event
|
||||||
// in case of an error drop this event.
|
// in case of an error drop this event.
|
||||||
sr, err = a.makeSignedPeerRecord(currentAddrs)
|
sr, err = a.makeSignedPeerRecord(publishedAddrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("error creating a signed peer record from the set of current addresses", "err", err)
|
log.Error("error creating a signed peer record from the set of current addresses", "err", err)
|
||||||
return
|
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) {
|
func (a *addrsManager) notifyAddrsUpdated(emitter event.Emitter, localAddrsEmitter event.Emitter, previous, current hostAddrs) {
|
||||||
if areAddrsDifferent(previous.localAddrs, current.localAddrs) {
|
if areAddrsDifferent(previous.localAddrs, current.localAddrs) {
|
||||||
log.Debug("host local addresses updated", "addrs", current.localAddrs)
|
log.Debug("host local addresses updated", "addrs", current.localAddrs)
|
||||||
|
|||||||
@@ -64,14 +64,15 @@ type addrStoreArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type addrsManagerArgs struct {
|
type addrsManagerArgs struct {
|
||||||
NATManager NATManager
|
NATManager NATManager
|
||||||
AddrsFactory AddrsFactory
|
AddrsFactory AddrsFactory
|
||||||
ObservedAddrsManager ObservedAddrsManager
|
ObservedAddrsManager ObservedAddrsManager
|
||||||
ListenAddrs func() []ma.Multiaddr
|
ListenAddrs func() []ma.Multiaddr
|
||||||
AddCertHashes func([]ma.Multiaddr) []ma.Multiaddr
|
AddCertHashes func([]ma.Multiaddr) []ma.Multiaddr
|
||||||
AutoNATClient autonatv2Client
|
AutoNATClient autonatv2Client
|
||||||
Bus event.Bus
|
Bus event.Bus
|
||||||
AddrStoreArgs addrStoreArgs
|
AddrStoreArgs addrStoreArgs
|
||||||
|
DisableNonPublicAddrPublishing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type addrsManagerTestCase struct {
|
type addrsManagerTestCase struct {
|
||||||
@@ -118,6 +119,7 @@ func newAddrsManagerTestCase(tb testing.TB, args addrsManagerArgs) addrsManagerT
|
|||||||
true,
|
true,
|
||||||
prometheus.DefaultRegisterer,
|
prometheus.DefaultRegisterer,
|
||||||
false,
|
false,
|
||||||
|
args.DisableNonPublicAddrPublishing,
|
||||||
signKey,
|
signKey,
|
||||||
addrStore,
|
addrStore,
|
||||||
pid,
|
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) {
|
func TestRemoveIfNotInSource(t *testing.T) {
|
||||||
addrs := make([]ma.Multiaddr, 0, 10)
|
addrs := make([]ma.Multiaddr, 0, 10)
|
||||||
for i := range 10 {
|
for i := range 10 {
|
||||||
|
|||||||
@@ -127,6 +127,14 @@ type HostOpts struct {
|
|||||||
// DisableSignedPeerRecord disables the generation of Signed Peer Records on this host.
|
// DisableSignedPeerRecord disables the generation of Signed Peer Records on this host.
|
||||||
DisableSignedPeerRecord bool
|
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 enables the peer to initiate/respond to hole punching attempts for NAT traversal.
|
||||||
EnableHolePunching bool
|
EnableHolePunching bool
|
||||||
// HolePunchingOptions are options for the hole punching service
|
// HolePunchingOptions are options for the hole punching service
|
||||||
@@ -238,6 +246,7 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
|
|||||||
opts.EnableMetrics,
|
opts.EnableMetrics,
|
||||||
opts.PrometheusRegisterer,
|
opts.PrometheusRegisterer,
|
||||||
opts.DisableSignedPeerRecord,
|
opts.DisableSignedPeerRecord,
|
||||||
|
opts.DisableNonPublicAddrPublishing,
|
||||||
h.Peerstore().PrivKey(h.ID()),
|
h.Peerstore().PrivKey(h.ID()),
|
||||||
h.Peerstore(),
|
h.Peerstore(),
|
||||||
h.ID(),
|
h.ID(),
|
||||||
|
|||||||
Reference in New Issue
Block a user