From ff32eba6a0fa222af1a7a46775d5e0346ad6ebdf Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 25 Jan 2022 16:27:01 +0000 Subject: [PATCH] feat: connection gater (#1142) Port of https://github.com/libp2p/go-libp2p-core/blob/master/connmgr/gater.go Adds a new configuration key `connectionGater` which allows denying the dialing of certain peers, individual multiaddrs and the creation of connections at certain points in the connection flow. Fixes: https://github.com/libp2p/js-libp2p/issues/175 Refs: https://github.com/libp2p/js-libp2p/issues/744 Refs: https://github.com/libp2p/js-libp2p/issues/769 Co-authored-by: mzdws <8580712+mzdws@user.noreply.gitee.com> --- doc/CONFIGURATION.md | 124 ++++++++++++ package.json | 1 + src/config.js | 2 + src/dialer/index.js | 21 +- src/errors.js | 2 + src/index.js | 21 +- src/peer-store/address-book.js | 69 ++++--- src/peer-store/index.js | 6 +- src/peer-store/store.js | 25 ++- src/types.ts | 98 +++++++++ src/upgrader.js | 29 +++ test/connection-manager/index.node.js | 233 +++++++++++++++++++++- test/dialing/direct.node.js | 51 +++-- test/dialing/direct.spec.js | 68 +++++-- test/identify/index.spec.js | 23 ++- test/peer-store/address-book.spec.js | 24 ++- test/peer-store/peer-store.spec.js | 11 +- test/registrar/registrar.spec.js | 6 +- test/transports/transport-manager.node.js | 5 +- test/upgrading/upgrader.spec.js | 30 ++- test/utils/mock-connection-gater.js | 19 ++ 21 files changed, 770 insertions(+), 98 deletions(-) create mode 100644 src/types.ts create mode 100644 test/utils/mock-connection-gater.js diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 73ef5158..0276c014 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -23,6 +23,9 @@ - [Setup with Keychain](#setup-with-keychain) - [Configuring Dialing](#configuring-dialing) - [Configuring Connection Manager](#configuring-connection-manager) + - [Configuring Connection Gater](#configuring-connection-gater) + - [Outgoing connections](#outgoing-connections) + - [Incoming connections](#incoming-connections) - [Configuring Transport Manager](#configuring-transport-manager) - [Configuring Metrics](#configuring-metrics) - [Configuring PeerStore](#configuring-peerstore) @@ -590,6 +593,127 @@ const node = await Libp2p.create({ }) ``` +#### Configuring Connection Gater + +The Connection Gater allows us to prevent making incoming and outgoing connections to peers and storing +multiaddrs in the address book. + +The order in which methods are called is as follows: + +##### Outgoing connections + +1. `connectionGater.denyDialPeer(...)` +2. `connectionGater.denyDialMultiaddr(...)` +3. `connectionGater.denyOutboundConnection(...)` +4. `connectionGater.denyOutboundEncryptedConnection(...)` +5. `connectionGater.denyOutboundUpgradedConnection(...)` + +##### Incoming connections + +1. `connectionGater.denyInboundConnection(...)` +2. `connectionGater.denyInboundEncryptedConnection(...)` +3. `connectionGater.denyInboundUpgradedConnection(...)` + +```js +const node = await Libp2p.create({ + // .. other config + connectionGater: { + /** + * denyDialMultiaddr tests whether we're permitted to Dial the + * specified peer. + * + * This is called by the dialer.connectToPeer implementation before + * dialling a peer. + * + * Return true to prevent dialing the passed peer. + */ + denyDialPeer: (peerId: PeerId) => Promise + + /** + * denyDialMultiaddr tests whether we're permitted to dial the specified + * multiaddr for the given peer. + * + * This is called by the dialer.connectToPeer implementation after it has + * resolved the peer's addrs, and prior to dialling each. + * + * Return true to prevent dialing the passed peer on the passed multiaddr. + */ + denyDialMultiaddr: (peerId: PeerId, multiaddr: Multiaddr) => Promise + + /** + * denyInboundConnection tests whether an incipient inbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has accepted a connection from its socket. + * + * Return true to deny the incoming passed connection. + */ + denyInboundConnection: (maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundConnection tests whether an incipient outbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has created a connection with its socket. + * + * Return true to deny the incoming passed connection. + */ + denyOutboundConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyInboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyOutboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyInboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyOutboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * Used by the address book to filter passed addresses. + * + * Return true to allow storing the passed multiaddr for the passed peer. + */ + filterMultiaddrForPeer: (peer: PeerId, multiaddr: Multiaddr) => Promise + } +}) +``` + #### Configuring Transport Manager The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listening on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows: diff --git a/package.json b/package.json index 5165c8cf..7fc234eb 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "it-drain": "^1.0.3", "it-filter": "^1.0.1", "it-first": "^1.0.4", + "it-foreach": "^0.1.1", "it-handshake": "^2.0.0", "it-length-prefixed": "^5.0.2", "it-map": "^1.0.4", diff --git a/src/config.js b/src/config.js index 0d9bbc4a..b2d3f604 100644 --- a/src/config.js +++ b/src/config.js @@ -13,6 +13,7 @@ const { FaultTolerance } = require('./transport-manager') /** * @typedef {import('multiaddr').Multiaddr} Multiaddr + * @typedef {import('./types').ConnectionGater} ConnectionGater * @typedef {import('.').Libp2pOptions} Libp2pOptions * @typedef {import('.').constructorOptions} constructorOptions */ @@ -27,6 +28,7 @@ const DefaultConfig = { connectionManager: { minConnections: 25 }, + connectionGater: /** @type {ConnectionGater} */ {}, transportManager: { faultTolerance: FaultTolerance.FATAL_ALL }, diff --git a/src/dialer/index.js b/src/dialer/index.js index 06fd4d0c..f2e82cd3 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -1,6 +1,9 @@ 'use strict' const debug = require('debug') +const all = require('it-all') +const filter = require('it-filter') +const { pipe } = require('it-pipe') const log = Object.assign(debug('libp2p:dialer'), { error: debug('libp2p:dialer:err') }) @@ -33,12 +36,14 @@ const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets' * @typedef {import('../peer-store/types').PeerStore} PeerStore * @typedef {import('../peer-store/types').Address} Address * @typedef {import('../transport-manager')} TransportManager + * @typedef {import('../types').ConnectionGater} ConnectionGater */ /** * @typedef {Object} DialerProperties * @property {PeerStore} peerStore * @property {TransportManager} transportManager + * @property {ConnectionGater} connectionGater * * @typedef {(addr:Multiaddr) => Promise} Resolver * @@ -70,6 +75,7 @@ class Dialer { constructor ({ transportManager, peerStore, + connectionGater, addressSorter = publicAddressesFirst, maxParallelDials = MAX_PARALLEL_DIALS, maxAddrsToDial = MAX_ADDRS_TO_DIAL, @@ -78,6 +84,7 @@ class Dialer { resolvers = {}, metrics }) { + this.connectionGater = connectionGater this.transportManager = transportManager this.peerStore = peerStore this.addressSorter = addressSorter @@ -136,6 +143,12 @@ class Dialer { * @returns {Promise} */ async connectToPeer (peer, options = {}) { + const { id } = getPeer(peer) + + if (await this.connectionGater.denyDialPeer(id)) { + throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED) + } + const dialTarget = await this._createCancellableDialTarget(peer) if (!dialTarget.addrs.length) { @@ -203,7 +216,13 @@ class Dialer { await this.peerStore.addressBook.add(id, multiaddrs) } - let knownAddrs = await this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || [] + let knownAddrs = await pipe( + await this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter), + (source) => filter(source, async (multiaddr) => { + return !(await this.connectionGater.denyDialMultiaddr(id, multiaddr)) + }), + (source) => all(source) + ) // If received a multiaddr to dial, it should be the first to use // But, if we know other multiaddrs for the peer, we should try them too. diff --git a/src/errors.js b/src/errors.js index 61f30866..4c34ae51 100644 --- a/src/errors.js +++ b/src/errors.js @@ -12,6 +12,8 @@ exports.codes = { PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED', DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED', CONN_ENCRYPTION_REQUIRED: 'ERR_CONN_ENCRYPTION_REQUIRED', + ERR_PEER_DIAL_INTERCEPTED: 'ERR_PEER_DIAL_INTERCEPTED', + ERR_CONNECTION_INTERCEPTED: 'ERR_CONNECTION_INTERCEPTED', ERR_INVALID_PROTOCOLS_FOR_STREAM: 'ERR_INVALID_PROTOCOLS_FOR_STREAM', ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED', ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED', diff --git a/src/index.js b/src/index.js index 966eb77b..1f1b745f 100644 --- a/src/index.js +++ b/src/index.js @@ -48,6 +48,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @typedef {import('libp2p-interfaces/src/pubsub').PubsubOptions} PubsubOptions * @typedef {import('interface-datastore').Datastore} Datastore * @typedef {import('./pnet')} Protector + * @typedef {import('./types').ConnectionGater} ConnectionGater * @typedef {Object} PersistentPeerStoreOptions * @property {number} [threshold] */ @@ -106,6 +107,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {Libp2pModules} modules libp2p modules to use * @property {import('./address-manager').AddressManagerOptions} [addresses] * @property {import('./connection-manager').ConnectionManagerOptions} [connectionManager] + * @property {Partial} [connectionGater] * @property {Datastore} [datastore] * @property {import('./dialer').DialerOptions} [dialer] * @property {import('./identify/index').HostProperties} [host] libp2p host @@ -172,10 +174,25 @@ class Libp2p extends EventEmitter { this.metrics = metrics } + /** @type {ConnectionGater} */ + this.connectionGater = { + denyDialPeer: async () => Promise.resolve(false), + denyDialMultiaddr: async () => Promise.resolve(false), + denyInboundConnection: async () => Promise.resolve(false), + denyOutboundConnection: async () => Promise.resolve(false), + denyInboundEncryptedConnection: async () => Promise.resolve(false), + denyOutboundEncryptedConnection: async () => Promise.resolve(false), + denyInboundUpgradedConnection: async () => Promise.resolve(false), + denyOutboundUpgradedConnection: async () => Promise.resolve(false), + filterMultiaddrForPeer: async () => Promise.resolve(true), + ...this._options.connectionGater + } + /** @type {import('./peer-store/types').PeerStore} */ this.peerStore = new PeerStore({ peerId: this.peerId, - datastore: (this.datastore && this._options.peerStore.persistence) ? this.datastore : new MemoryDatastore() + datastore: (this.datastore && this._options.peerStore.persistence) ? this.datastore : new MemoryDatastore(), + addressFilter: this.connectionGater.filterMultiaddrForPeer }) // Addresses {listen, announce, noAnnounce} @@ -220,6 +237,7 @@ class Libp2p extends EventEmitter { // Setup the Upgrader this.upgrader = new Upgrader({ + connectionGater: this.connectionGater, localPeer: this.peerId, metrics: this.metrics, onConnection: (connection) => this.connectionManager.onConnect(connection), @@ -262,6 +280,7 @@ class Libp2p extends EventEmitter { this.dialer = new Dialer({ transportManager: this.transportManager, + connectionGater: this.connectionGater, peerStore: this.peerStore, metrics: this.metrics, ...this._options.dialer diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 05581a2e..b54ce981 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -7,6 +7,11 @@ const PeerId = require('peer-id') const { codes } = require('../errors') const PeerRecord = require('../record/peer-record') const Envelope = require('../record/envelope') +const { pipe } = require('it-pipe') +const all = require('it-all') +const filter = require('it-filter') +const map = require('it-map') +const each = require('it-foreach') /** * @typedef {import('./types').PeerStore} PeerStore @@ -27,10 +32,12 @@ class PeerStoreAddressBook { /** * @param {PeerStore["emit"]} emit * @param {import('./types').Store} store + * @param {(peerId: PeerId, multiaddr: Multiaddr) => Promise} addressFilter */ - constructor (emit, store) { + constructor (emit, store, addressFilter) { this._emit = emit this._store = store + this._addressFilter = addressFilter } /** @@ -88,7 +95,7 @@ class PeerStoreAddressBook { // Replace unsigned addresses by the new ones from the record // TODO: Once we have ttls for the addresses, we should merge these in updatedPeer = await this._store.patchOrCreate(peerId, { - addresses: convertMultiaddrsToAddresses(multiaddrs, true), + addresses: await filterMultiaddrs(peerId, multiaddrs, this._addressFilter, true), peerRecordEnvelope: envelope.marshal() }) @@ -180,6 +187,11 @@ class PeerStoreAddressBook { throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } + if (!Array.isArray(multiaddrs)) { + log.error('multiaddrs must be an array of Multiaddrs') + throw errcode(new Error('multiaddrs must be an array of Multiaddrs'), codes.ERR_INVALID_PARAMETERS) + } + log('set await write lock') const release = await this._store.lock.writeLock() log('set got write lock') @@ -188,7 +200,7 @@ class PeerStoreAddressBook { let updatedPeer try { - const addresses = convertMultiaddrsToAddresses(multiaddrs) + const addresses = await filterMultiaddrs(peerId, multiaddrs, this._addressFilter) // No valid addresses found if (!addresses.length) { @@ -238,6 +250,11 @@ class PeerStoreAddressBook { throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } + if (!Array.isArray(multiaddrs)) { + log.error('multiaddrs must be an array of Multiaddrs') + throw errcode(new Error('multiaddrs must be an array of Multiaddrs'), codes.ERR_INVALID_PARAMETERS) + } + log('add await write lock') const release = await this._store.lock.writeLock() log('add got write lock') @@ -246,7 +263,7 @@ class PeerStoreAddressBook { let updatedPeer try { - const addresses = convertMultiaddrsToAddresses(multiaddrs) + const addresses = await filterMultiaddrs(peerId, multiaddrs, this._addressFilter) // No valid addresses found if (!addresses.length) { @@ -337,33 +354,29 @@ class PeerStoreAddressBook { } /** - * Transforms received multiaddrs into Address. - * - * @private + * @param {PeerId} peerId * @param {Multiaddr[]} multiaddrs - * @param {boolean} [isCertified] - * @returns {Address[]} + * @param {(peerId: PeerId, multiaddr: Multiaddr) => Promise} addressFilter + * @param {boolean} isCertified */ -function convertMultiaddrsToAddresses (multiaddrs, isCertified = false) { - if (!multiaddrs) { - log.error('multiaddrs must be provided to store data') - throw errcode(new Error('multiaddrs must be provided'), codes.ERR_INVALID_PARAMETERS) - } - - // create Address for each address with no duplicates - return Array.from( - new Set(multiaddrs.map(ma => ma.toString())) - ) - .map(addr => { - try { - return { - multiaddr: new Multiaddr(addr), - isCertified - } - } catch (err) { - throw errcode(err, codes.ERR_INVALID_PARAMETERS) +function filterMultiaddrs (peerId, multiaddrs, addressFilter, isCertified = false) { + return pipe( + multiaddrs, + (source) => each(source, (multiaddr) => { + if (!Multiaddr.isMultiaddr(multiaddr)) { + log.error('multiaddr must be an instance of Multiaddr') + throw errcode(new Error('multiaddr must be an instance of Multiaddr'), codes.ERR_INVALID_PARAMETERS) } - }) + }), + (source) => filter(source, (multiaddr) => addressFilter(peerId, multiaddr)), + (source) => map(source, (multiaddr) => { + return { + multiaddr: new Multiaddr(multiaddr.toString()), + isCertified + } + }), + (source) => all(source) + ) } module.exports = PeerStoreAddressBook diff --git a/src/peer-store/index.js b/src/peer-store/index.js index d17a9f2d..2dceac37 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -12,6 +12,7 @@ const Store = require('./store') * @typedef {import('./types').PeerStore} PeerStore * @typedef {import('./types').Peer} Peer * @typedef {import('peer-id')} PeerId + * @typedef {import('multiaddr').Multiaddr} Multiaddr */ const log = Object.assign(debug('libp2p:peer-store'), { @@ -28,14 +29,15 @@ class DefaultPeerStore extends EventEmitter { * @param {object} properties * @param {PeerId} properties.peerId * @param {import('interface-datastore').Datastore} properties.datastore + * @param {(peerId: PeerId, multiaddr: Multiaddr) => Promise} properties.addressFilter */ - constructor ({ peerId, datastore }) { + constructor ({ peerId, datastore, addressFilter }) { super() this._peerId = peerId this._store = new Store(datastore) - this.addressBook = new AddressBook(this.emit.bind(this), this._store) + this.addressBook = new AddressBook(this.emit.bind(this), this._store, addressFilter) this.keyBook = new KeyBook(this.emit.bind(this), this._store) this.metadataBook = new MetadataBook(this.emit.bind(this), this._store) this.protoBook = new ProtoBook(this.emit.bind(this), this._store) diff --git a/src/peer-store/store.js b/src/peer-store/store.js index 8b3d29f4..34dcce36 100644 --- a/src/peer-store/store.js +++ b/src/peer-store/store.js @@ -100,13 +100,26 @@ class PersistentStore { throw errcode(new Error('publicKey bytes do not match peer id publicKey bytes'), codes.ERR_INVALID_PARAMETERS) } + // dedupe addresses + const addressSet = new Set() + const buf = PeerPB.encode({ - addresses: peer.addresses.sort((a, b) => { - return a.multiaddr.toString().localeCompare(b.multiaddr.toString()) - }).map(({ multiaddr, isCertified }) => ({ - multiaddr: multiaddr.bytes, - isCertified - })), + addresses: peer.addresses + .filter(address => { + if (addressSet.has(address.multiaddr.toString())) { + return false + } + + addressSet.add(address.multiaddr.toString()) + return true + }) + .sort((a, b) => { + return a.multiaddr.toString().localeCompare(b.multiaddr.toString()) + }) + .map(({ multiaddr, isCertified }) => ({ + multiaddr: multiaddr.bytes, + isCertified + })), protocols: peer.protocols.sort(), pubKey: peer.pubKey ? marshalPublicKey(peer.pubKey) : undefined, metadata: [...peer.metadata.keys()].sort().map(key => ({ key, value: peer.metadata.get(key) })), diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..b8af9a6f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,98 @@ +import type PeerId from 'peer-id' +import type { Multiaddr } from 'multiaddr' +import type { MultiaddrConnection } from 'libp2p-interfaces/src/transport/types' + +export interface ConnectionGater { + /** + * denyDialMultiaddr tests whether we're permitted to Dial the + * specified peer. + * + * This is called by the dialer.connectToPeer implementation before + * dialling a peer. + * + * Return true to prevent dialing the passed peer. + */ + denyDialPeer: (peerId: PeerId) => Promise + + /** + * denyDialMultiaddr tests whether we're permitted to dial the specified + * multiaddr for the given peer. + * + * This is called by the dialer.connectToPeer implementation after it has + * resolved the peer's addrs, and prior to dialling each. + * + * Return true to prevent dialing the passed peer on the passed multiaddr. + */ + denyDialMultiaddr: (peerId: PeerId, multiaddr: Multiaddr) => Promise + + /** + * denyInboundConnection tests whether an incipient inbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has accepted a connection from its socket. + * + * Return true to deny the incoming passed connection. + */ + denyInboundConnection: (maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundConnection tests whether an incipient outbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has created a connection with its socket. + * + * Return true to deny the incoming passed connection. + */ + denyOutboundConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyInboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyOutboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyInboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyOutboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * Used by the address book to filter passed addresses. + * + * Return true to allow storing the passed multiaddr for the passed peer. + */ + filterMultiaddrForPeer: (peer: PeerId, multiaddr: Multiaddr) => Promise +} diff --git a/src/upgrader.js b/src/upgrader.js index 58a6418f..cbfc77c3 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -22,6 +22,7 @@ const { codes } = require('./errors') * @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('multiaddr').Multiaddr} Multiaddr + * @typedef {import('./types').ConnectionGater} ConnectionGater */ /** @@ -35,6 +36,8 @@ class Upgrader { /** * @param {object} options * @param {PeerId} options.localPeer + * @param {ConnectionGater} options.connectionGater + * * @param {import('./metrics')} [options.metrics] * @param {Map} [options.cryptos] * @param {Map} [options.muxers] @@ -44,11 +47,13 @@ class Upgrader { constructor ({ localPeer, metrics, + connectionGater, cryptos = new Map(), muxers = new Map(), onConnectionEnd = () => {}, onConnection = () => {} }) { + this.connectionGater = connectionGater this.localPeer = localPeer this.metrics = metrics this.cryptos = cryptos @@ -76,6 +81,10 @@ class Upgrader { let setPeer let proxyPeer + if (await this.connectionGater.denyInboundConnection(maConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + if (this.metrics) { ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) const idString = (Math.random() * 1e9).toString(36) + Date.now() @@ -99,6 +108,10 @@ class Upgrader { protocol: cryptoProtocol } = await this._encryptInbound(this.localPeer, protectedConn, this.cryptos)) + if (await this.connectionGater.denyInboundEncryptedConnection(remotePeer, encryptedConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + // Multiplex the connection if (this.muxers.size) { ({ stream: upgradedConn, Muxer } = await this._multiplexInbound(encryptedConn, this.muxers)) @@ -111,6 +124,10 @@ class Upgrader { throw err } + if (await this.connectionGater.denyInboundUpgradedConnection(remotePeer, encryptedConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + if (this.metrics) { this.metrics.updatePlaceholder(proxyPeer, remotePeer) setPeer(remotePeer) @@ -143,6 +160,10 @@ class Upgrader { const remotePeerId = PeerId.createFromB58String(idStr) + if (await this.connectionGater.denyOutboundConnection(remotePeerId, maConn)) { + throw errCode(new Error('The multiaddr connection is blocked by connectionGater.denyOutboundConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + let encryptedConn let remotePeer let upgradedConn @@ -174,6 +195,10 @@ class Upgrader { protocol: cryptoProtocol } = await this._encryptOutbound(this.localPeer, protectedConn, remotePeerId, this.cryptos)) + if (await this.connectionGater.denyOutboundEncryptedConnection(remotePeer, encryptedConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + // Multiplex the connection if (this.muxers.size) { ({ stream: upgradedConn, Muxer } = await this._multiplexOutbound(encryptedConn, this.muxers)) @@ -186,6 +211,10 @@ class Upgrader { throw err } + if (await this.connectionGater.denyOutboundUpgradedConnection(remotePeer, encryptedConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + if (this.metrics) { this.metrics.updatePlaceholder(proxyPeer, remotePeer) setPeer(remotePeer) diff --git a/test/connection-manager/index.node.js b/test/connection-manager/index.node.js index d187895c..19f59bff 100644 --- a/test/connection-manager/index.node.js +++ b/test/connection-manager/index.node.js @@ -7,12 +7,11 @@ const { CLOSED } = require('libp2p-interfaces/src/connection/status') const delay = require('delay') const pWaitFor = require('p-wait-for') - const peerUtils = require('../utils/creators/peer') const mockConnection = require('../utils/mockConnection') const baseOptions = require('../utils/base-options.browser') - -const listenMultiaddr = '/ip4/127.0.0.1/tcp/15002/ws' +const { codes } = require('../../src/errors') +const { Multiaddr } = require('multiaddr') describe('Connection Manager', () => { let libp2p @@ -27,7 +26,7 @@ describe('Connection Manager', () => { config: { peerId: peerIds[0], addresses: { - listen: [listenMultiaddr] + listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, modules: baseOptions.modules } @@ -305,4 +304,230 @@ describe('libp2p.connections', () => { await remoteLibp2p.stop() }) }) + + describe('connection gater', () => { + let libp2p + let remoteLibp2p + + beforeEach(async () => { + [remoteLibp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[1], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules + } + }) + }) + + afterEach(async () => { + remoteLibp2p && await remoteLibp2p.stop() + libp2p && await libp2p.stop() + }) + + it('intercept peer dial', async () => { + const denyDialPeer = sinon.stub().returns(true) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyDialPeer + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + + await expect(libp2p.dial(remoteLibp2p.peerId)) + .to.eventually.be.rejected().with.property('code', codes.ERR_PEER_DIAL_INTERCEPTED) + }) + + it('intercept addr dial', async () => { + const denyDialMultiaddr = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyDialMultiaddr + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.dialer.connectToPeer(remoteLibp2p.peerId) + + const peerIdMultiaddr = new Multiaddr(`/p2p/${remoteLibp2p.peerId}`) + + for (const multiaddr of remoteLibp2p.multiaddrs) { + expect(denyDialMultiaddr.calledWith(remoteLibp2p.peerId, multiaddr.encapsulate(peerIdMultiaddr))).to.be.true() + } + }) + + it('intercept multiaddr store during multiaddr dial', async () => { + const filterMultiaddrForPeer = sinon.stub().returns(true) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + filterMultiaddrForPeer + } + } + }) + + const peerIdMultiaddr = new Multiaddr(`/p2p/${remoteLibp2p.peerId}`) + const fullMultiaddr = remoteLibp2p.multiaddrs[0].encapsulate(peerIdMultiaddr) + + await libp2p.dialer.connectToPeer(fullMultiaddr) + + expect(filterMultiaddrForPeer.callCount).to.equal(2) + + const args = filterMultiaddrForPeer.getCall(1).args + expect(args[0].toString()).to.equal(remoteLibp2p.peerId.toString()) + expect(args[1].toString()).to.equal(fullMultiaddr.toString()) + }) + + it('intercept accept inbound connection', async () => { + const denyInboundConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyInboundConnection + } + } + }) + await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.multiaddrs) + await remoteLibp2p.dial(libp2p.peerId) + + expect(denyInboundConnection.called).to.be.true() + }) + + it('intercept accept outbound connection', async () => { + const denyOutboundConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyOutboundConnection + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.dial(remoteLibp2p.peerId) + + expect(denyOutboundConnection.called).to.be.true() + }) + + it('intercept inbound encrypted', async () => { + const denyInboundEncryptedConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyInboundEncryptedConnection + } + } + }) + await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.multiaddrs) + await remoteLibp2p.dial(libp2p.peerId) + + expect(denyInboundEncryptedConnection.called).to.be.true() + expect(denyInboundEncryptedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + }) + + it('intercept outbound encrypted', async () => { + const denyOutboundEncryptedConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyOutboundEncryptedConnection + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.dial(remoteLibp2p.peerId) + + expect(denyOutboundEncryptedConnection.called).to.be.true() + expect(denyOutboundEncryptedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + }) + + it('intercept inbound upgraded', async () => { + const denyInboundUpgradedConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyInboundUpgradedConnection + } + } + }) + await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.multiaddrs) + await remoteLibp2p.dial(libp2p.peerId) + + expect(denyInboundUpgradedConnection.called).to.be.true() + expect(denyInboundUpgradedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + }) + + it('intercept outbound upgraded', async () => { + const denyOutboundUpgradedConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyOutboundUpgradedConnection + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.dial(remoteLibp2p.peerId) + + expect(denyOutboundUpgradedConnection.called).to.be.true() + expect(denyOutboundUpgradedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + }) + }) }) diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index b321fc2d..6842811a 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -27,7 +27,7 @@ const TransportManager = require('../../src/transport-manager') const { codes: ErrorCodes } = require('../../src/errors') const Protector = require('../../src/pnet') const swarmKeyBuffer = uint8ArrayFromString(require('../fixtures/swarm.key')) - +const { mockConnectionGater } = require('../utils/mock-connection-gater') const mockUpgrader = require('../utils/mockUpgrader') const createMockConnection = require('../utils/mockConnection') const Peers = require('../fixtures/peers') @@ -37,6 +37,7 @@ const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') describe('Dialing (direct, TCP)', () => { + const connectionGater = mockConnectionGater() let remoteTM let localTM let peerStore @@ -50,7 +51,8 @@ describe('Dialing (direct, TCP)', () => { peerStore = new PeerStore({ peerId: remotePeerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) remoteTM = new TransportManager({ libp2p: { @@ -67,7 +69,8 @@ describe('Dialing (direct, TCP)', () => { peerId: localPeerId, peerStore: new PeerStore({ peerId: localPeerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) }, upgrader: mockUpgrader @@ -86,7 +89,11 @@ describe('Dialing (direct, TCP)', () => { }) it('should be able to connect to a remote node via its multiaddr', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const connection = await dialer.connectToPeer(remoteAddr) expect(connection).to.exist() @@ -94,14 +101,22 @@ describe('Dialing (direct, TCP)', () => { }) it('should be able to connect to a remote node via its stringified multiaddr', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const connection = await dialer.connectToPeer(remoteAddr.toString()) expect(connection).to.exist() await connection.close() }) it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) await expect(dialer.connectToPeer(unsupportedAddr)) .to.eventually.be.rejectedWith(Error) @@ -109,7 +124,11 @@ describe('Dialing (direct, TCP)', () => { }) it('should fail to connect if peer has no known addresses', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const peerId = await PeerId.createFromJSON(Peers[1]) await expect(dialer.connectToPeer(peerId)) @@ -121,11 +140,13 @@ describe('Dialing (direct, TCP)', () => { const peerId = await PeerId.createFromJSON(Peers[0]) const peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) const dialer = new Dialer({ transportManager: localTM, - peerStore + peerStore, + connectionGater }) await peerStore.addressBook.set(peerId, remoteTM.getAddrs()) @@ -143,7 +164,8 @@ describe('Dialing (direct, TCP)', () => { add: () => {}, getMultiaddrsForPeer: () => [unsupportedAddr] } - } + }, + connectionGater }) const peerId = await PeerId.createFromJSON(Peers[0]) @@ -161,7 +183,8 @@ describe('Dialing (direct, TCP)', () => { add: () => { }, getMultiaddrsForPeer: () => [...remoteAddrs, unsupportedAddr] } - } + }, + connectionGater }) const peerId = await PeerId.createFromJSON(Peers[0]) @@ -176,7 +199,8 @@ describe('Dialing (direct, TCP)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore, - dialTimeout: 50 + dialTimeout: 50, + connectionGater }) sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { expect(options.signal).to.exist() @@ -206,7 +230,8 @@ describe('Dialing (direct, TCP)', () => { add: () => {}, getMultiaddrsForPeer: () => addrs } - } + }, + connectionGater }) expect(dialer.tokens).to.have.length(2) diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index ba973526..40193a90 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -21,6 +21,7 @@ const addressSort = require('libp2p-utils/src/address-sort') const PeerStore = require('../../src/peer-store') const TransportManager = require('../../src/transport-manager') const Libp2p = require('../../src') +const { mockConnectionGater } = require('../utils/mock-connection-gater') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') const mockUpgrader = require('../utils/mockUpgrader') @@ -30,6 +31,7 @@ const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1 const remoteAddr = MULTIADDRS_WEBSOCKETS[0] describe('Dialing (direct, WebSockets)', () => { + const connectionGater = mockConnectionGater() let localTM let peerStore let peerId @@ -38,7 +40,8 @@ describe('Dialing (direct, WebSockets)', () => { [peerId] = await createPeerId() peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) localTM = new TransportManager({ libp2p: {}, @@ -54,13 +57,21 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should have appropriate defaults', () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) expect(dialer.maxParallelDials).to.equal(Constants.MAX_PARALLEL_DIALS) expect(dialer.timeout).to.equal(Constants.DIAL_TIMEOUT) }) it('should limit the number of tokens it provides', () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const maxPerPeer = Constants.MAX_PER_PEER_DIALS expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS) const tokens = dialer.getTokens(maxPerPeer + 1) @@ -69,14 +80,22 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should not return tokens if non are left', () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) sinon.stub(dialer, 'tokens').value([]) const tokens = dialer.getTokens(1) expect(tokens.length).to.equal(0) }) it('should NOT be able to return a token twice', () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const tokens = dialer.getTokens(1) expect(tokens).to.have.length(1) expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS - 1) @@ -93,7 +112,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } - } + }, + connectionGater }) const connection = await dialer.connectToPeer(remoteAddr) @@ -109,7 +129,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } - } + }, + connectionGater }) const connection = await dialer.connectToPeer(remoteAddr.toString()) @@ -118,7 +139,11 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) await expect(dialer.connectToPeer(unsupportedAddr)) .to.eventually.be.rejectedWith(AggregateError) @@ -132,7 +157,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } - } + }, + connectionGater }) const connection = await dialer.connectToPeer(peerId) @@ -148,7 +174,8 @@ describe('Dialing (direct, WebSockets)', () => { set: () => {}, getMultiaddrsForPeer: () => [unsupportedAddr] } - } + }, + connectionGater }) await expect(dialer.connectToPeer(peerId)) @@ -164,7 +191,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } - } + }, + connectionGater }) sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { expect(options.signal).to.exist() @@ -191,7 +219,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => { }, getMultiaddrsForPeer: () => Array.from({ length: 11 }, (_, i) => new Multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`)) } - } + }, + connectionGater }) await expect(dialer.connectToPeer(remoteAddr)) @@ -214,7 +243,8 @@ describe('Dialing (direct, WebSockets)', () => { transportManager: localTM, addressSorter: addressSort.publicAddressesFirst, maxParallelDials: 3, - peerStore + peerStore, + connectionGater }) // Inject data in the AddressBook @@ -240,7 +270,8 @@ describe('Dialing (direct, WebSockets)', () => { set: () => {}, getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] } - } + }, + connectionGater }) expect(dialer.tokens).to.have.length(2) @@ -278,7 +309,8 @@ describe('Dialing (direct, WebSockets)', () => { set: () => {}, getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] } - } + }, + connectionGater }) expect(dialer.tokens).to.have.length(2) @@ -320,7 +352,8 @@ describe('Dialing (direct, WebSockets)', () => { addressBook: { set: () => { } } - } + }, + connectionGater }) sinon.stub(dialer, '_createDialTarget').callsFake(() => { @@ -365,7 +398,8 @@ describe('Dialing (direct, WebSockets)', () => { filter: filters.all } } - } + }, + connectionGater }) expect(libp2p.dialer).to.exist() diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 7e960dfe..8b8269d5 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -23,10 +23,12 @@ const pkg = require('../../package.json') const AddressManager = require('../../src/address-manager') const { MemoryDatastore } = require('datastore-core/memory') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') +const { mockConnectionGater } = require('../utils/mock-connection-gater') const remoteAddr = MULTIADDRS_WEBSOCKETS[0] const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] describe('Identify', () => { + const connectionGater = mockConnectionGater() let localPeer, localPeerStore, localAddressManager let remotePeer, remotePeerStore, remoteAddressManager const protocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH] @@ -39,13 +41,15 @@ describe('Identify', () => { localPeerStore = new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await localPeerStore.protoBook.set(localPeer, protocols) remotePeerStore = new PeerStore({ peerId: remotePeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await remotePeerStore.protoBook.set(remotePeer, protocols) @@ -230,7 +234,8 @@ describe('Identify', () => { const agentVersion = 'js-project/1.0.0' const peerStore = new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) sinon.spy(peerStore.metadataBook, 'setValue') @@ -272,7 +277,8 @@ describe('Identify', () => { const localPeerStore = new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await localPeerStore.protoBook.set(localPeer, storedProtocols) @@ -290,7 +296,8 @@ describe('Identify', () => { const remotePeerStore = new PeerStore({ peerId: remotePeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await remotePeerStore.protoBook.set(remotePeer, storedProtocols) @@ -352,7 +359,8 @@ describe('Identify', () => { const localPeerStore = new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await localPeerStore.protoBook.set(localPeer, storedProtocols) @@ -370,7 +378,8 @@ describe('Identify', () => { const remotePeerStore = new PeerStore({ peerId: remotePeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await remotePeerStore.protoBook.set(remotePeer, storedProtocols) diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 671e94f9..e67a899c 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -13,7 +13,7 @@ const { MemoryDatastore } = require('datastore-core/memory') const PeerStore = require('../../src/peer-store') const Envelope = require('../../src/record/envelope') const PeerRecord = require('../../src/record/peer-record') - +const { mockConnectionGater } = require('../utils/mock-connection-gater') const peerUtils = require('../utils/creators/peer') const { codes: { ERR_INVALID_PARAMETERS } @@ -29,6 +29,7 @@ const addr2 = new Multiaddr('/ip4/20.0.0.1/tcp/8001') const addr3 = new Multiaddr('/ip4/127.0.0.1/tcp/8002') describe('addressBook', () => { + const connectionGater = mockConnectionGater() let peerId before(async () => { @@ -44,7 +45,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -164,7 +166,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -323,7 +326,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -364,7 +368,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -418,7 +423,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -478,7 +484,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -670,7 +677,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index 3456417a..d0380025 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -8,6 +8,7 @@ const { Multiaddr } = require('multiaddr') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { MemoryDatastore } = require('datastore-core/memory') const peerUtils = require('../utils/creators/peer') +const { mockConnectionGater } = require('../utils/mock-connection-gater') const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') const addr2 = new Multiaddr('/ip4/127.0.0.1/tcp/8001') @@ -23,6 +24,7 @@ const proto3 = '/protocol3' */ describe('peer-store', () => { + const connectionGater = mockConnectionGater() let peerIds before(async () => { peerIds = await peerUtils.createPeerId({ @@ -37,7 +39,8 @@ describe('peer-store', () => { beforeEach(() => { peerStore = new PeerStore({ peerId: peerIds[4], - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) }) @@ -66,7 +69,8 @@ describe('peer-store', () => { beforeEach(async () => { peerStore = new PeerStore({ peerId: peerIds[4], - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) // Add peer0 with { addr1, addr2 } and { proto1 } @@ -170,7 +174,8 @@ describe('peer-store', () => { beforeEach(() => { peerStore = new PeerStore({ peerId: peerIds[4], - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) }) diff --git a/test/registrar/registrar.spec.js b/test/registrar/registrar.spec.js index b86f52a8..83f87fd6 100644 --- a/test/registrar/registrar.spec.js +++ b/test/registrar/registrar.spec.js @@ -9,7 +9,7 @@ const { MemoryDatastore } = require('datastore-core/memory') const Topology = require('libp2p-interfaces/src/topology/multicodec-topology') const PeerStore = require('../../src/peer-store') const Registrar = require('../../src/registrar') - +const { mockConnectionGater } = require('../utils/mock-connection-gater') const createMockConnection = require('../utils/mockConnection') const peerUtils = require('../utils/creators/peer') const baseOptions = require('../utils/base-options.browser') @@ -17,6 +17,7 @@ const baseOptions = require('../utils/base-options.browser') const multicodec = '/test/1.0.0' describe('registrar', () => { + const connectionGater = mockConnectionGater() let peerStore let registrar let peerId @@ -29,7 +30,8 @@ describe('registrar', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) registrar = new Registrar({ peerStore, connectionManager: new EventEmitter() }) }) diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index f762bbe4..d22d091d 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -14,12 +14,14 @@ const mockUpgrader = require('../utils/mockUpgrader') const sinon = require('sinon') const Peers = require('../fixtures/peers') const pWaitFor = require('p-wait-for') +const { mockConnectionGater } = require('../utils/mock-connection-gater') const addrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/0'), new Multiaddr('/ip4/127.0.0.1/tcp/0') ] describe('Transport Manager (TCP)', () => { + const connectionGater = mockConnectionGater() let tm let localPeer @@ -35,7 +37,8 @@ describe('Transport Manager (TCP)', () => { addressManager: new AddressManager({ listen: addrs }), peerStore: new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) }, upgrader: mockUpgrader, diff --git a/test/upgrading/upgrader.spec.js b/test/upgrading/upgrader.spec.js index e78f7007..176c8c0a 100644 --- a/test/upgrading/upgrader.spec.js +++ b/test/upgrading/upgrader.spec.js @@ -18,7 +18,7 @@ const swarmKeyBuffer = uint8ArrayFromString(require('../fixtures/swarm.key')) const Libp2p = require('../../src') const Upgrader = require('../../src/upgrader') const { codes } = require('../../src/errors') - +const { mockConnectionGater } = require('../utils/mock-connection-gater') const mockMultiaddrConnPair = require('../utils/mockMultiaddrConn') const Peers = require('../fixtures/peers') const addrs = [ @@ -31,6 +31,17 @@ describe('Upgrader', () => { let remoteUpgrader let localPeer let remotePeer + const connectionGater = mockConnectionGater() + + const mockConnectionManager = { + gater: { + allowDialPeer: async () => true, + allowDialMultiaddr: async () => true, + acceptConnection: async () => true, + acceptEncryptedConnection: async () => true, + acceptUpgradedConnection: async () => true + } + } before(async () => { ([ @@ -42,10 +53,14 @@ describe('Upgrader', () => { ])) localUpgrader = new Upgrader({ - localPeer + connectionManager: mockConnectionManager, + localPeer, + connectionGater }) remoteUpgrader = new Upgrader({ - localPeer: remotePeer + connectionManager: mockConnectionManager, + localPeer: remotePeer, + connectionGater }) localUpgrader.protocols.set('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) @@ -321,6 +336,7 @@ describe('Upgrader', () => { describe('libp2p.upgrader', () => { let peers let libp2p + const connectionGater = mockConnectionGater() before(async () => { peers = await Promise.all([ @@ -392,8 +408,10 @@ describe('libp2p.upgrader', () => { const remoteUpgrader = new Upgrader({ localPeer: remotePeer, + connectionManager: libp2p.connectionManager, muxers: new Map([[Muxer.multicodec, Muxer]]), - cryptos: new Map([[Crypto.protocol, Crypto]]) + cryptos: new Map([[Crypto.protocol, Crypto]]), + connectionGater }) remoteUpgrader.protocols.set('/echo/1.0.0', echoHandler) @@ -424,8 +442,10 @@ describe('libp2p.upgrader', () => { const remoteUpgrader = new Upgrader({ localPeer: remotePeer, + connectionManager: libp2p.connectionManager, muxers: new Map([[Muxer.multicodec, Muxer]]), - cryptos: new Map([[Crypto.protocol, Crypto]]) + cryptos: new Map([[Crypto.protocol, Crypto]]), + connectionGater }) const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) diff --git a/test/utils/mock-connection-gater.js b/test/utils/mock-connection-gater.js new file mode 100644 index 00000000..b1081f49 --- /dev/null +++ b/test/utils/mock-connection-gater.js @@ -0,0 +1,19 @@ +'use strict' + +function mockConnectionGater () { + return { + denyDialPeer: async () => Promise.resolve(false), + denyDialMultiaddr: async () => Promise.resolve(false), + denyInboundConnection: async () => Promise.resolve(false), + denyOutboundConnection: async () => Promise.resolve(false), + denyInboundEncryptedConnection: async () => Promise.resolve(false), + denyOutboundEncryptedConnection: async () => Promise.resolve(false), + denyInboundUpgradedConnection: async () => Promise.resolve(false), + denyOutboundUpgradedConnection: async () => Promise.resolve(false), + filterMultiaddrForPeer: async () => Promise.resolve(true) + } +} + +module.exports = { + mockConnectionGater +}