UNPKG

@chainsafe/libp2p-quic

Version:
182 lines 7.09 kB
import { setMaxListeners, TypedEventEmitter } from '@libp2p/interface'; import { multiaddr } from '@multiformats/multiaddr'; import { raceSignal } from 'race-signal'; import { QuicConnection } from './connection.js'; import * as napi from './napi.js'; import { QuicStreamMuxerFactory } from './stream-muxer.js'; import { getMultiaddrs } from './utils.js'; export class QuicListener extends TypedEventEmitter { #config; init; options; log; metrics; shutdownController; addr; state = { status: 'ready' }; constructor(init) { super(); this.#config = init.config; this.init = init; this.options = init.options; this.log = init.logger.forComponent('libp2p:quic:listener'); this.addr = 'unknown'; this.shutdownController = new AbortController(); setMaxListeners(Infinity, this.shutdownController.signal); init.metrics?.registerMetricGroup('libp2p_quic_inbound_connections_total', { label: 'address', help: 'Current active connections in QUIC listener', calculate: () => { if (this.state.status !== 'listening') { return { [this.addr]: 0 }; } return { [this.addr]: this.state.connections.size }; } }); this.metrics = { events: init.metrics?.registerMetricGroup('libp2p_quic_listener_events_total', { label: 'address', help: 'Total count of QUIC listener events by type' }), errors: init.metrics?.registerMetricGroup('libp2p_quic_listener_errors_total', { label: 'address', help: 'Total count of QUIC listener errors by type' }) }; this.log('new'); } updateAnnounceAddrs(addrs) { } getAddrs() { if (this.state.status === 'listening') { const ma = this.state.listenAddr; const addr = ma.nodeAddress(); if (addr.address === '0.0.0.0') { return getMultiaddrs('ip4', addr.address, addr.port); } else if (addr.address === '::') { return getMultiaddrs('ip6', addr.address, addr.port); } return [this.state.listenAddr]; } return []; } async listen(ma) { const addr = ma.nodeAddress(); const controller = new AbortController(); const listener = new napi.Server(this.#config, addr.address, addr.port); this.addr = `${addr.address}:${addr.port === 0 ? listener.port() : addr.port}`; // replace wildcard port with actual listening port if (addr.port === 0) { const components = ma.getComponents(); for (const component of components) { if (component.name === 'udp') { component.value = `${listener.port()}`; } } ma = multiaddr(components); } this.state = { status: 'listening', listener, listenAddr: ma, controller, connections: new Set() }; void this.awaitInboundConnections(); this.safeDispatchEvent('listening'); this.log('listening', multiaddr.toString()); } async close() { if (this.state.status === 'listening') { this.state.controller.abort(); for (const conn of this.state.connections) { conn.abort(new Error('listener closed')); } this.state.connections.clear(); await this.state.listener.abort(); const listenAddr = this.state.listenAddr; this.state = { status: 'closed' }; // stop any in-progress connection upgrades this.shutdownController.abort(); this.safeDispatchEvent('close'); this.log('closed', listenAddr.toString()); } } async awaitInboundConnections() { if (this.state.status === 'listening') { const signal = this.state.controller.signal; const listenAddr = this.state.listenAddr; while (true) { try { const listenerPromise = this.state.listener.inboundConnection(); listenerPromise .then(() => this.metrics.events?.increment({ [`${this.addr} connect`]: true })) .catch((err) => { this.log.error('%a error awaiting inbound connection - %e', listenAddr, err); this.metrics.events?.increment({ [`${this.addr} error`]: true }); }); const connection = await raceSignal(listenerPromise, signal); this.onInboundConnection(connection).catch((e) => { this.log.error('%a error handling inbound connection - %e', listenAddr, e); }); } catch (e) { this.log.error('%a error accepting connection - %e', listenAddr, e); if (signal.aborted) { break; } } } this.log('%s no longer awaiting inbound connections', listenAddr.toString()); } } async onInboundConnection(connection) { if (this.state.status !== 'listening') { this.log.error('ignoring inbound connection after listener closed'); connection.abort(); return; } let maConn; try { maConn = new QuicConnection({ connection, logger: this.init.logger, direction: 'inbound', metrics: this.metrics?.events, metricsPrefix: `${this.addr} ` }); } catch (err) { this.metrics.errors?.increment({ [`${this.addr} inbound_to_connection`]: true }); throw err; } try { await this.options.upgrader.upgradeInbound(maConn, { skipEncryption: true, skipProtection: true, muxerFactory: new QuicStreamMuxerFactory({ connection, logger: this.init.logger }), signal: this.shutdownController.signal }); this.state.connections.add(maConn); maConn.addEventListener('close', () => { if (this.state.status === 'listening') { this.state.connections.delete(maConn); } }, { once: true }); } catch (err) { this.log.error('%s error handling inbound connection', this.state.listenAddr.toString(), err); this.metrics.errors?.increment({ [`${this.addr} inbound_upgrade`]: true }); maConn.abort(err); } } } //# sourceMappingURL=listener.js.map