diff --git a/components/p2p/component.go b/components/p2p/component.go index f9fb9c791..54a6cf7a2 100644 --- a/components/p2p/component.go +++ b/components/p2p/component.go @@ -8,11 +8,9 @@ import ( "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" - p2pbhost "github.com/libp2p/go-libp2p/p2p/host/basic" "github.com/libp2p/go-libp2p/p2p/net/connmgr" "github.com/libp2p/go-libp2p/p2p/transport/tcp" "github.com/multiformats/go-multiaddr" - mamask "github.com/whyrusleeping/multiaddr-filter" "go.uber.org/dig" "github.com/iotaledger/hive.go/app" @@ -21,7 +19,6 @@ import ( "github.com/iotaledger/hive.go/db" "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/kvstore" - "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/iota-core/pkg/daemon" "github.com/iotaledger/iota-core/pkg/network" "github.com/iotaledger/iota-core/pkg/network/p2p" @@ -218,7 +215,7 @@ func provide(c *dig.Container) error { libp2p.NATPortMap(), libp2p.DisableRelay(), // Define a custom address factory to inject external addresses to the DHT advertisements. - libp2p.AddrsFactory(publicOnlyAddresses(ParamsP2P.Autopeering.ExternalMultiAddresses)), + libp2p.AddrsFactory(externalAddresses(ParamsP2P.Autopeering.ExternalMultiAddresses, ParamsP2P.Autopeering.AllowLocalIPs)), ) if err != nil { Component.LogFatalf("unable to initialize libp2p host: %s", err) @@ -246,27 +243,11 @@ func provide(c *dig.Container) error { } return c.Provide(func(inDeps p2pManagerDeps) network.Manager { - peersMultiAddresses, err := getMultiAddrsFromString(ParamsPeers.BootstrapPeers) - if err != nil { - Component.LogFatalf("Failed to parse bootstrapPeers param: %s", err) - } - - for _, multiAddr := range peersMultiAddresses { - bootstrapPeer, err := network.NewPeerFromMultiAddr(multiAddr) - if err != nil { - Component.LogFatalf("Failed to parse bootstrap peer multiaddress: %s", err) - } - - if err := inDeps.PeerDB.UpdatePeer(bootstrapPeer); err != nil { - Component.LogErrorf("Failed to update bootstrap peer: %s", err) - } - } - onBlockSentCallback := func() { inDeps.P2PMetrics.OutgoingBlocks.Add(1) } - return p2p.NewManager(Component.Logger, inDeps.Host, inDeps.PeerDB, ParamsP2P.Autopeering.MaxPeers, onBlockSentCallback) + return p2p.NewManager(Component.Logger, inDeps.Host, inDeps.PeerDB, ParamsP2P.Autopeering.MaxPeers, ParamsP2P.Autopeering.AllowLocalIPs, onBlockSentCallback) }) } @@ -307,7 +288,7 @@ func run() error { if err := Component.Daemon().BackgroundWorker(Component.Name, func(ctx context.Context) { defer deps.NetworkManager.Shutdown() - if err := deps.NetworkManager.Start(ctx, deps.Protocol.LatestAPI().ProtocolParameters().NetworkName()); err != nil { + if err := deps.NetworkManager.Start(ctx, deps.Protocol.LatestAPI().ProtocolParameters().NetworkName(), bootstrapPeers()); err != nil { Component.LogFatalf("Failed to start p2p manager: %s", err) } @@ -322,6 +303,24 @@ func run() error { return nil } +func bootstrapPeers() []peer.AddrInfo { + peersMultiAddresses, err := getMultiAddrsFromString(ParamsP2P.Autopeering.BootstrapPeers) + if err != nil { + Component.LogFatalf("Failed to parse bootstrapPeers param: %s", err) + } + + addrInfos := make([]peer.AddrInfo, 0, len(peersMultiAddresses)) + for _, multiAddr := range peersMultiAddresses { + addrInfo, err := peer.AddrInfoFromP2pAddr(multiAddr) + if err != nil { + Component.LogFatalf("Failed to parse bootstrap peer multiaddress: %s", err) + } + addrInfos = append(addrInfos, *addrInfo) + } + + return addrInfos +} + func getMultiAddrsFromString(peers []string) ([]multiaddr.Multiaddr, error) { peersMultiAddresses := make([]multiaddr.Multiaddr, 0, len(peers)) @@ -356,36 +355,7 @@ func connectConfigKnownPeers() { } } -// Based on https://github.com/ipfs/kubo/blob/master/config/profile.go -// defaultServerFilters has is a list of IPv4 and IPv6 prefixes that are private, local only, or unrouteable. -// according to https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml -// and https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml -var reservedFilters = []string{ - "/ip4/0.0.0.0/ipcidr/32", - "/ip4/10.0.0.0/ipcidr/8", - "/ip4/100.64.0.0/ipcidr/10", - "/ip4/127.0.0.0/ipcidr/8", - "/ip4/169.254.0.0/ipcidr/16", - "/ip4/172.16.0.0/ipcidr/12", - "/ip4/192.0.0.0/ipcidr/24", - "/ip4/192.0.2.0/ipcidr/24", - "/ip4/192.168.0.0/ipcidr/16", - "/ip4/192.31.196.0/ipcidr/24", - "/ip4/192.52.193.0/ipcidr/24", - "/ip4/198.18.0.0/ipcidr/15", - "/ip4/198.51.100.0/ipcidr/24", - "/ip4/203.0.113.0/ipcidr/24", - "/ip4/240.0.0.0/ipcidr/4", - - "/ip6/::1/ipcidr/64", - "/ip6/100::/ipcidr/64", - "/ip6/2001:2::/ipcidr/48", - "/ip6/2001:db8::/ipcidr/32", - "/ip6/fc00::/ipcidr/7", - "/ip6/fe80::/ipcidr/10", -} - -func publicOnlyAddresses(additionalMultiaddresses []string) p2pbhost.AddrsFactory { +func externalAddresses(additionalMultiaddresses []string, allowLocalNetworks bool) network.AddressFilter { var externalMultiAddrs []multiaddr.Multiaddr // Add the external multi addresses to the list of addresses to be announced. @@ -400,27 +370,9 @@ func publicOnlyAddresses(additionalMultiaddresses []string) p2pbhost.AddrsFactor } } - // Create a filter that blocks localhost and reserved addresses. - filters := multiaddr.NewFilters() - for _, addr := range reservedFilters { - f, err := mamask.NewMask(addr) - if err != nil { - Component.LogPanicf("unable to parse ip mask filter %s: %s", addr, err) - } - filters.AddFilter(*f, multiaddr.ActionDeny) - } + publicFilter := network.PublicOnlyAddressesFilter(allowLocalNetworks) return func(addresses []multiaddr.Multiaddr) []multiaddr.Multiaddr { - filteredAddresses := lo.Filter(append(addresses, externalMultiAddrs...), func(m multiaddr.Multiaddr) bool { - blocked := filters.AddrBlocked(m) - if blocked { - Component.LogTracef("Filtered out address %s", m) - } - return !blocked - }) - - Component.LogTracef("Announcing addresses: %v", filteredAddresses) - - return filteredAddresses + return publicFilter(append(addresses, externalMultiAddrs...)) } } diff --git a/components/p2p/params.go b/components/p2p/params.go index f34cf3d1f..907df3678 100644 --- a/components/p2p/params.go +++ b/components/p2p/params.go @@ -29,6 +29,12 @@ type ParametersP2P struct { // MaxPeers defines the max number of auto-peer connections. Set to 0 to disable auto-peering. MaxPeers int `default:"5" usage:"the max number of auto-peer connections. Set to 0 to disable auto-peering."` + // Defines the peers to be used as discovery for other peers (CLI). + BootstrapPeers []string `default:"" usage:"peers to be used as discovery for other peers"` + + // AllowLocalIPs defines if local IPs are allowed to be used for autopeering. + AllowLocalIPs bool `default:"false" usage:"allow local IPs to be used for autopeering"` + // ExternalMultiAddress defines additional p2p multiaddresses to be advertised via DHT. ExternalMultiAddresses []string `default:"" usage:"external reacheable multi addresses advertised to the network"` } @@ -45,8 +51,6 @@ type ParametersPeers struct { Peers []string `default:"" usage:"the static peers this node should retain a connection to (CLI)"` // Defines the aliases of the static peers (must be the same length like CfgP2PPeers) (CLI). PeerAliases []string `default:"" usage:"the aliases of the static peers (must be the same amount like \"p2p.peers\""` - // Defines the peers to be used as discovery for other peers (CLI). - BootstrapPeers []string `default:"" usage:"peers to be used as discovery for other peers (CLI)"` } var ( diff --git a/config_defaults.json b/config_defaults.json index 643c36554..f514d7c6f 100644 --- a/config_defaults.json +++ b/config_defaults.json @@ -29,6 +29,8 @@ "identityPrivateKey": "", "autopeering": { "maxPeers": 5, + "bootstrapPeers": [], + "allowLocalIPs": false, "externalMultiAddresses": [] }, "db": { diff --git a/documentation/configuration.md b/documentation/configuration.md index 1b642ae86..b8230dbb2 100644 --- a/documentation/configuration.md +++ b/documentation/configuration.md @@ -110,10 +110,12 @@ Example: ### Autopeering -| Name | Description | Type | Default value | -| ---------------------- | -------------------------------------------------------------------------- | ----- | ------------- | -| maxPeers | The max number of auto-peer connections. Set to 0 to disable auto-peering. | int | 5 | -| externalMultiAddresses | External reacheable multi addresses advertised to the network | array | | +| Name | Description | Type | Default value | +| ---------------------- | -------------------------------------------------------------------------- | ------- | ------------- | +| maxPeers | The max number of auto-peer connections. Set to 0 to disable auto-peering. | int | 5 | +| bootstrapPeers | Peers to be used as discovery for other peers | array | | +| allowLocalIPs | Allow local IPs to be used for autopeering | boolean | false | +| externalMultiAddresses | External reacheable multi addresses advertised to the network | array | | ### Database @@ -137,6 +139,8 @@ Example: "identityPrivateKey": "", "autopeering": { "maxPeers": 5, + "bootstrapPeers": [], + "allowLocalIPs": false, "externalMultiAddresses": [] }, "db": { diff --git a/pkg/network/filter.go b/pkg/network/filter.go new file mode 100644 index 000000000..edb8210bb --- /dev/null +++ b/pkg/network/filter.go @@ -0,0 +1,70 @@ +package network + +import ( + "fmt" + + "github.com/multiformats/go-multiaddr" + mamask "github.com/whyrusleeping/multiaddr-filter" + + "github.com/iotaledger/hive.go/lo" +) + +// Based on https://github.com/ipfs/kubo/blob/master/config/profile.go +// defaultServerFilters has is a list of IPv4 and IPv6 prefixes that are private, local only, or unrouteable. +// according to https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml +// and https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml +var reservedFilters = []string{ + "/ip4/0.0.0.0/ipcidr/32", + "/ip4/100.64.0.0/ipcidr/10", + "/ip4/127.0.0.0/ipcidr/8", + "/ip4/169.254.0.0/ipcidr/16", + "/ip4/192.0.0.0/ipcidr/24", + "/ip4/192.0.2.0/ipcidr/24", + "/ip4/192.31.196.0/ipcidr/24", + "/ip4/192.52.193.0/ipcidr/24", + "/ip4/198.18.0.0/ipcidr/15", + "/ip4/198.51.100.0/ipcidr/24", + "/ip4/203.0.113.0/ipcidr/24", + "/ip4/240.0.0.0/ipcidr/4", + + "/ip6/::/ipcidr/128", + "/ip6/::1/ipcidr/128", + "/ip6/100::/ipcidr/64", + "/ip6/2001:2::/ipcidr/48", + "/ip6/2001:db8::/ipcidr/32", +} + +var localNetworks = []string{ + "/ip4/10.0.0.0/ipcidr/8", + "/ip4/172.16.0.0/ipcidr/12", + "/ip4/192.168.0.0/ipcidr/16", + + "/ip6/fc00::/ipcidr/7", + "/ip6/fe80::/ipcidr/10", +} + +type AddressFilter = func([]multiaddr.Multiaddr) []multiaddr.Multiaddr + +func PublicOnlyAddressesFilter(allowLocalNetworks bool) AddressFilter { + // Create a filter that blocks localhost and reserved addresses. + filters := multiaddr.NewFilters() + + filtersToApply := reservedFilters + if !allowLocalNetworks { + filtersToApply = append(filtersToApply, localNetworks...) + } + + for _, addr := range filtersToApply { + f, err := mamask.NewMask(addr) + if err != nil { + panic(fmt.Sprintf("unable to parse ip mask filter %s: %s", addr, err)) + } + filters.AddFilter(*f, multiaddr.ActionDeny) + } + + return func(addresses []multiaddr.Multiaddr) []multiaddr.Multiaddr { + return lo.Filter(addresses, func(m multiaddr.Multiaddr) bool { + return !filters.AddrBlocked(m) + }) + } +} diff --git a/pkg/network/manager.go b/pkg/network/manager.go index 648cdfd9e..36c99cd29 100644 --- a/pkg/network/manager.go +++ b/pkg/network/manager.go @@ -47,6 +47,6 @@ type Manager interface { P2PHost() host.Host - Start(ctx context.Context, networkID string) error + Start(ctx context.Context, networkID string, bootstrapPeers []peer.AddrInfo) error Shutdown() } diff --git a/pkg/network/p2p/autopeering/autopeering.go b/pkg/network/p2p/autopeering/autopeering.go index 41976e1c2..33af4c9a9 100644 --- a/pkg/network/p2p/autopeering/autopeering.go +++ b/pkg/network/p2p/autopeering/autopeering.go @@ -11,6 +11,7 @@ import ( dht "github.com/libp2p/go-libp2p-kad-dht" "github.com/libp2p/go-libp2p/core/discovery" "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/p2p/discovery/routing" "github.com/libp2p/go-libp2p/p2p/discovery/util" @@ -33,6 +34,7 @@ type Manager struct { ctx context.Context stopFunc context.CancelFunc routingDiscovery *routing.RoutingDiscovery + addrFilter network.AddressFilter advertiseLock sync.Mutex advertiseCtx context.Context @@ -40,13 +42,14 @@ type Manager struct { } // NewManager creates a new autopeering manager. -func NewManager(maxPeers int, networkManager network.Manager, host host.Host, peerDB *network.DB, logger log.Logger) *Manager { +func NewManager(maxPeers int, networkManager network.Manager, host host.Host, peerDB *network.DB, addressFilter network.AddressFilter, logger log.Logger) *Manager { return &Manager{ maxPeers: maxPeers, networkManager: networkManager, host: host, peerDB: peerDB, logger: logger.NewChildLogger("Autopeering"), + addrFilter: addressFilter, } } @@ -55,7 +58,7 @@ func (m *Manager) MaxNeighbors() int { } // Start starts the autopeering manager. -func (m *Manager) Start(ctx context.Context, networkID string) (err error) { +func (m *Manager) Start(ctx context.Context, networkID string, bootstrapPeers []peer.AddrInfo) (err error) { //nolint:contextcheck m.startOnce.Do(func() { // We will use /iota/networkID/kad/1.0.0 for the DHT protocol. @@ -64,7 +67,15 @@ func (m *Manager) Start(ctx context.Context, networkID string) (err error) { extension := protocol.ID(fmt.Sprintf("/%s", networkID)) m.namespace = fmt.Sprintf("%s%s/%s", prefix, extension, network.CoreProtocolID) dhtCtx, dhtCancel := context.WithCancel(ctx) - kademliaDHT, innerErr := dht.New(dhtCtx, m.host, dht.Mode(dht.ModeServer), dht.ProtocolPrefix(prefix), dht.ProtocolExtension(extension)) + kademliaDHT, innerErr := dht.New( + dhtCtx, + m.host, + dht.Mode(dht.ModeServer), + dht.ProtocolPrefix(prefix), + dht.ProtocolExtension(extension), + dht.AddressFilter(m.addrFilter), + dht.BootstrapPeers(bootstrapPeers...), + ) if innerErr != nil { err = innerErr dhtCancel() @@ -105,7 +116,8 @@ func (m *Manager) Start(ctx context.Context, networkID string) (err error) { onGossipNeighborRemovedHook := m.networkManager.OnNeighborRemoved(func(_ network.Neighbor) { m.startAdvertisingIfNeeded() }) - onGossipNeighborAddedHook := m.networkManager.OnNeighborAdded(func(_ network.Neighbor) { + onGossipNeighborAddedHook := m.networkManager.OnNeighborAdded(func(neighbor network.Neighbor) { + m.logger.LogInfof("Gossip layer successfully connected with the peer %s", neighbor.Peer()) m.stopAdvertisingItNotNeeded() }) @@ -253,13 +265,26 @@ func (m *Manager) discoverAndDialPeers() { continue } - m.logger.LogDebugf("Found peer: %s", peerAddrInfo) + peerInfo := m.filteredPeerAddrInfo(&peerAddrInfo) + if len(peerInfo.Addrs) == 0 { + m.logger.LogWarnf("Filtered out peer %s because it has no public reachable addresses", peerAddrInfo) + continue + } + + m.logger.LogInfof("Found peer: %s", peerInfo) - peer := network.NewPeerFromAddrInfo(&peerAddrInfo) - if err := m.networkManager.DialPeer(m.ctx, peer); err != nil { + p := network.NewPeerFromAddrInfo(peerInfo) + if err := m.networkManager.DialPeer(m.ctx, p); err != nil { m.logger.LogWarnf("Failed to dial peer %s: %s", peerAddrInfo, err) } else { peersToFind-- } } } + +func (m *Manager) filteredPeerAddrInfo(peerAddrInfo *peer.AddrInfo) *peer.AddrInfo { + return &peer.AddrInfo{ + ID: peerAddrInfo.ID, + Addrs: m.addrFilter(peerAddrInfo.Addrs), + } +} diff --git a/pkg/network/p2p/manager.go b/pkg/network/p2p/manager.go index 0e0813772..26bc27f46 100644 --- a/pkg/network/p2p/manager.go +++ b/pkg/network/p2p/manager.go @@ -50,6 +50,7 @@ type Manager struct { protocolHandlerMutex syncutils.RWMutex onBlockSentCallback func() + addrFilter network.AddressFilter autoPeering *autopeering.Manager manualPeering *manualpeering.Manager } @@ -57,7 +58,7 @@ type Manager struct { var _ network.Manager = (*Manager)(nil) // NewManager creates a new Manager. -func NewManager(logger log.Logger, libp2pHost host.Host, peerDB *network.DB, maxAutopeeringPeers int, onBlockSentCallback func()) *Manager { +func NewManager(logger log.Logger, libp2pHost host.Host, peerDB *network.DB, maxAutopeeringPeers int, allowLocalAutopeering bool, onBlockSentCallback func()) *Manager { m := &Manager{ logger: logger, libp2pHost: libp2pHost, @@ -66,9 +67,10 @@ func NewManager(logger log.Logger, libp2pHost host.Host, peerDB *network.DB, max neighborRemoved: event.New1[network.Neighbor](), neighbors: shrinkingmap.New[peer.ID, *neighbor](), onBlockSentCallback: onBlockSentCallback, + addrFilter: network.PublicOnlyAddressesFilter(allowLocalAutopeering), } - m.autoPeering = autopeering.NewManager(maxAutopeeringPeers, m, libp2pHost, peerDB, logger) + m.autoPeering = autopeering.NewManager(maxAutopeeringPeers, m, libp2pHost, peerDB, m.addrFilter, logger) m.manualPeering = manualpeering.NewManager(m, logger) return m @@ -123,7 +125,7 @@ func (m *Manager) DialPeer(ctx context.Context, peer *network.Peer) error { } // Adds the peer's multiaddresses to the peerstore, so that they can be used for dialing. - m.libp2pHost.Peerstore().AddAddrs(peer.ID, peer.PeerAddresses, peerstore.ConnectedAddrTTL) + m.libp2pHost.Peerstore().AddAddrs(peer.ID, m.addrFilter(peer.PeerAddresses), peerstore.ConnectedAddrTTL) cancelCtx := ctx stream, err := m.P2PHost().NewStream(cancelCtx, peer.ID, network.CoreProtocolID) @@ -156,13 +158,13 @@ func (m *Manager) DialPeer(ctx context.Context, peer *network.Peer) error { } // Start starts the manager and initiates manual- and autopeering. -func (m *Manager) Start(ctx context.Context, networkID string) error { +func (m *Manager) Start(ctx context.Context, networkID string, bootstrapPeers []peer.AddrInfo) error { m.ctx = ctx m.manualPeering.Start() if m.autoPeering.MaxNeighbors() > 0 { - return m.autoPeering.Start(ctx, networkID) + return m.autoPeering.Start(ctx, networkID, bootstrapPeers) } return nil diff --git a/pkg/network/p2p/manualpeering/manualpeering.go b/pkg/network/p2p/manualpeering/manualpeering.go index 5e6f281a6..6280d8fdb 100644 --- a/pkg/network/p2p/manualpeering/manualpeering.go +++ b/pkg/network/p2p/manualpeering/manualpeering.go @@ -236,7 +236,6 @@ func (m *Manager) onGossipNeighborRemoved(neighbor network.Neighbor) { func (m *Manager) onGossipNeighborAdded(neighbor network.Neighbor) { m.changeNeighborStatus(neighbor) - m.logger.LogInfof("Gossip layer successfully connected with the peer %s", neighbor.Peer()) } func (m *Manager) changeNeighborStatus(neighbor network.Neighbor) { diff --git a/tools/docker-network/.env b/tools/docker-network/.env index 2fb926159..e965fe92e 100644 --- a/tools/docker-network/.env +++ b/tools/docker-network/.env @@ -10,10 +10,11 @@ COMMON_CONFIG=" --protocol.snapshot.path=/app/data/snapshot.bin --restAPI.publicRoutes=/health,/api/routes,/api/core/v3/info,/api/core/v3/network*,/api/core/v3/blocks*,/api/core/v3/transactions*,/api/core/v3/commitments*,/api/core/v3/outputs*,/api/core/v3/accounts*,/api/core/v3/validators*,/api/core/v3/rewards*,/api/core/v3/committee*,/api/debug/v2/*,/api/indexer/v2/*,/api/mqtt/v2,/api/blockissuer/v1/*,/api/management/v1/* --debugAPI.enabled=false +--p2p.autopeering.allowLocalIPs=true " AUTOPEERING_CONFIG=" ---p2p.bootstrapPeers=/dns/node-1-validator/tcp/15600/p2p/12D3KooWRVt4Engu27jHnF2RjfX48EqiAqJbgLfFdHNt3Vn6BtJK +--p2p.autopeering.bootstrapPeers=/dns/node-1-validator/tcp/15600/p2p/12D3KooWRVt4Engu27jHnF2RjfX48EqiAqJbgLfFdHNt3Vn6BtJK --p2p.autopeering.maxPeers=3 "