UNPKG

libp2p

Version:

JavaScript implementation of libp2p, a modular peer to peer network stack

346 lines • 14.9 kB
import { InvalidMultiaddrError, InvalidPeerIdError } from '@libp2p/interface'; import * as mss from '@libp2p/multistream-select'; import { peerIdFromString } from '@libp2p/peer-id'; import { trackedMap } from '@libp2p/utils'; import { CODE_P2P } from '@multiformats/multiaddr'; import { anySignal } from 'any-signal'; import { setMaxListeners } from 'main-event'; import { CustomProgressEvent } from 'progress-events'; import { raceSignal } from 'race-signal'; import { PROTOCOL_NEGOTIATION_TIMEOUT, INBOUND_UPGRADE_TIMEOUT, CONNECTION_CLOSE_TIMEOUT } from './connection-manager/constants.js'; import { createConnection } from './connection.js'; import { ConnectionDeniedError, ConnectionInterceptedError, EncryptionFailedError, MuxerUnavailableError } from './errors.js'; export class Upgrader { components; connectionEncrypters; streamMuxers; inboundUpgradeTimeout; inboundStreamProtocolNegotiationTimeout; outboundStreamProtocolNegotiationTimeout; events; metrics; connectionCloseTimeout; constructor(components, init) { this.components = components; this.connectionEncrypters = trackedMap({ name: 'libp2p_upgrader_connection_encrypters', metrics: this.components.metrics }); init.connectionEncrypters.forEach(encrypter => { this.connectionEncrypters.set(encrypter.protocol, encrypter); }); this.streamMuxers = trackedMap({ name: 'libp2p_upgrader_stream_multiplexers', metrics: this.components.metrics }); init.streamMuxers.forEach(muxer => { this.streamMuxers.set(muxer.protocol, muxer); }); this.inboundUpgradeTimeout = init.inboundUpgradeTimeout ?? INBOUND_UPGRADE_TIMEOUT; this.inboundStreamProtocolNegotiationTimeout = init.inboundStreamProtocolNegotiationTimeout ?? PROTOCOL_NEGOTIATION_TIMEOUT; this.outboundStreamProtocolNegotiationTimeout = init.outboundStreamProtocolNegotiationTimeout ?? PROTOCOL_NEGOTIATION_TIMEOUT; this.connectionCloseTimeout = init.connectionCloseTimeout ?? CONNECTION_CLOSE_TIMEOUT; this.events = components.events; this.metrics = { dials: components.metrics?.registerCounterGroup('libp2p_connection_manager_dials_total'), errors: components.metrics?.registerCounterGroup('libp2p_connection_manager_dial_errors_total'), inboundErrors: components.metrics?.registerCounterGroup('libp2p_connection_manager_dials_inbound_errors_total'), outboundErrors: components.metrics?.registerCounterGroup('libp2p_connection_manager_dials_outbound_errors_total') }; } [Symbol.toStringTag] = '@libp2p/upgrader'; async shouldBlockConnection(method, ...args) { const denyOperation = this.components.connectionGater[method]; if (denyOperation == null) { return; } const result = await denyOperation.apply(this.components.connectionGater, args); if (result === true) { throw new ConnectionInterceptedError(`The multiaddr connection is blocked by gater.${method}`); } } createInboundAbortSignal(signal) { const output = anySignal([ AbortSignal.timeout(this.inboundUpgradeTimeout), signal ]); setMaxListeners(Infinity, output); return output; } async upgradeInbound(maConn, opts) { let accepted = false; // always apply upgrade timeout for incoming upgrades const signal = this.createInboundAbortSignal(opts.signal); try { this.metrics.dials?.increment({ inbound: true }); accepted = this.components.connectionManager.acceptIncomingConnection(maConn); if (!accepted) { throw new ConnectionDeniedError('Connection denied'); } await raceSignal(this.shouldBlockConnection('denyInboundConnection', maConn), signal); await this._performUpgrade(maConn, 'inbound', { ...opts, signal }); } catch (err) { this.metrics.errors?.increment({ inbound: true }); this.metrics.inboundErrors?.increment({ [err.name ?? 'Error']: true }); throw err; } finally { signal.clear(); if (accepted) { this.components.connectionManager.afterUpgradeInbound(); } } } async upgradeOutbound(maConn, opts) { try { this.metrics.dials?.increment({ outbound: true }); const idStr = maConn.remoteAddr.getComponents().findLast(c => c.code === CODE_P2P)?.value; let remotePeerId; if (idStr != null) { remotePeerId = peerIdFromString(idStr); await raceSignal(this.shouldBlockConnection('denyOutboundConnection', remotePeerId, maConn), opts.signal); } let direction = 'outbound'; // act as the multistream-select server if we are not to be the initiator if (opts.initiator === false) { direction = 'inbound'; } return await this._performUpgrade(maConn, direction, opts); } catch (err) { this.metrics.errors?.increment({ outbound: true }); this.metrics.outboundErrors?.increment({ [err.name ?? 'Error']: true }); throw err; } } async _performUpgrade(maConn, direction, opts) { let stream = maConn; let remotePeer; let muxerFactory; let muxer; let cryptoProtocol; const id = `${(parseInt(String(Math.random() * 1e9))).toString(36)}${Date.now()}`; maConn.log = maConn.log.newScope(`${direction}:${id}`); this.components.metrics?.trackMultiaddrConnection(maConn); maConn.log.trace('starting the %s connection upgrade', direction); // Protect if (opts?.skipProtection !== true) { const protector = this.components.connectionProtector; if (protector != null) { maConn.log('protecting the %s connection', direction); stream = await protector.protect(stream, opts); } } try { // Encrypt the connection if (isEncryptionSkipped(opts)) { if (opts.remotePeer == null) { throw new InvalidMultiaddrError(`${direction} connection that skipped encryption must have a peer id`); } cryptoProtocol = 'native'; remotePeer = opts.remotePeer; } else { const peerIdString = maConn.remoteAddr.getComponents().findLast(c => c.code === CODE_P2P)?.value; let remotePeerFromMultiaddr; if (peerIdString != null) { remotePeerFromMultiaddr = peerIdFromString(peerIdString); } opts?.onProgress?.(new CustomProgressEvent(`upgrader:encrypt-${direction}-connection`)); ({ connection: stream, remotePeer, protocol: cryptoProtocol, streamMuxer: muxerFactory } = await (direction === 'inbound' ? this._encryptInbound(stream, { ...opts, remotePeer: remotePeerFromMultiaddr }) : this._encryptOutbound(stream, { ...opts, remotePeer: remotePeerFromMultiaddr }))); } // this can happen if we dial a multiaddr without a peer id, we only find // out the identity of the remote after the connection is encrypted if (remotePeer.equals(this.components.peerId)) { const err = new InvalidPeerIdError('Can not dial self'); maConn.abort(err); throw err; } // stream.pause() await this.shouldBlockConnection(direction === 'inbound' ? 'denyInboundEncryptedConnection' : 'denyOutboundEncryptedConnection', remotePeer, maConn); // stream.resume() if (opts?.muxerFactory != null) { muxerFactory = opts.muxerFactory; } else if (muxerFactory == null && this.streamMuxers.size > 0) { opts?.onProgress?.(new CustomProgressEvent(`upgrader:multiplex-${direction}-connection`)); // Multiplex the connection muxerFactory = await (direction === 'inbound' ? this._multiplexInbound(stream, this.streamMuxers, opts) : this._multiplexOutbound(stream, this.streamMuxers, opts)); } } catch (err) { maConn.log.error('failed to upgrade %s connection %s %a - %e', direction, direction === 'inbound' ? 'from' : 'to', maConn.remoteAddr, err); throw err; } // create the connection muxer if one is configured if (muxerFactory != null) { maConn.log('create muxer %s', muxerFactory.protocol); muxer = muxerFactory.createStreamMuxer(stream); } // stream.pause() await this.shouldBlockConnection(direction === 'inbound' ? 'denyInboundUpgradedConnection' : 'denyOutboundUpgradedConnection', remotePeer, maConn); const conn = this._createConnection({ id, cryptoProtocol, direction, maConn, stream, muxer, remotePeer, limits: opts?.limits, closeTimeout: this.connectionCloseTimeout }); conn.log('successfully upgraded connection'); // stream.resume() return conn; } /** * A convenience method for generating a new `Connection` */ _createConnection(opts) { // Create the connection const connection = createConnection(this.components, { ...opts, outboundStreamProtocolNegotiationTimeout: this.outboundStreamProtocolNegotiationTimeout, inboundStreamProtocolNegotiationTimeout: this.inboundStreamProtocolNegotiationTimeout }); connection.addEventListener('close', () => { this.events.safeDispatchEvent('connection:close', { detail: connection }); }); this.events.safeDispatchEvent('connection:open', { detail: connection }); return connection; } /** * Attempts to encrypt the incoming `connection` with the provided `cryptos` */ async _encryptInbound(connection, options) { const protocols = Array.from(this.connectionEncrypters.keys()); try { const protocol = await mss.handle(connection, protocols, options); const encrypter = this.connectionEncrypters.get(protocol); if (encrypter == null) { throw new EncryptionFailedError(`no crypto module found for ${protocol}`); } connection.log('encrypting inbound connection using %s', protocol); return { ...await encrypter.secureInbound(connection, options), protocol }; } catch (err) { throw new EncryptionFailedError(err.message); } } /** * Attempts to encrypt the given `connection` with the provided connection encrypters. * The first `ConnectionEncrypter` module to succeed will be used */ async _encryptOutbound(connection, options) { const protocols = Array.from(this.connectionEncrypters.keys()); try { connection.log.trace('selecting encrypter from %s', protocols); const protocol = await mss.select(connection, protocols, options); const encrypter = this.connectionEncrypters.get(protocol); if (encrypter == null) { throw new EncryptionFailedError(`no crypto module found for ${protocol}`); } connection.log('encrypting outbound connection using %s', protocol); return { ...await encrypter.secureOutbound(connection, options), protocol }; } catch (err) { throw new EncryptionFailedError(err.message); } } /** * Selects one of the given muxers via multistream-select. That * muxer will be used for all future streams on the connection. */ async _multiplexOutbound(maConn, muxers, options) { const protocols = Array.from(muxers.keys()); maConn.log('outbound selecting muxer %s', protocols); try { maConn.log.trace('selecting stream muxer from %s', protocols); const protocol = await mss.select(maConn, protocols, options); const muxerFactory = muxers.get(protocol); if (muxerFactory == null) { throw new MuxerUnavailableError(`No muxer configured for protocol "${protocol}"`); } maConn.log('selected %s as muxer protocol', protocol); return muxerFactory; } catch (err) { maConn.log.error('error multiplexing outbound connection - %e', err); throw new MuxerUnavailableError(String(err)); } } /** * Registers support for one of the given muxers via multistream-select. The * selected muxer will be used for all future streams on the connection. */ async _multiplexInbound(maConn, muxers, options) { const protocols = Array.from(muxers.keys()); maConn.log('inbound handling muxers %s', protocols); try { maConn.log.trace('selecting stream muxer from %s', protocols); const protocol = await mss.handle(maConn, protocols, options); const muxerFactory = muxers.get(protocol); if (muxerFactory == null) { throw new MuxerUnavailableError(`No muxer configured for protocol "${protocol}"`); } maConn.log('selected %s as muxer protocol', protocol); return muxerFactory; } catch (err) { maConn.log.error('error multiplexing inbound connection - %e', err); throw err; } } getConnectionEncrypters() { return this.connectionEncrypters; } getStreamMuxers() { return this.streamMuxers; } } function isEncryptionSkipped(opts) { return opts.skipEncryption === true; } //# sourceMappingURL=upgrader.js.map