diff --git a/packages/integration-tests/.aegir.js b/packages/integration-tests/.aegir.js index 6517c7b1ea..b1e634a5f7 100644 --- a/packages/integration-tests/.aegir.js +++ b/packages/integration-tests/.aegir.js @@ -22,7 +22,7 @@ export default { const peerId = await createEd25519PeerId() const libp2p = await createLibp2p({ connectionManager: { - inboundConnectionThreshold: Infinity, + inboundConnectionThreshold: 10000, minConnections: 0 }, addresses: { @@ -47,7 +47,7 @@ export default { identify: identify(), relay: circuitRelayServer({ reservations: { - maxReservations: Infinity + maxReservations: 10000 } }) } diff --git a/packages/libp2p/.aegir.js b/packages/libp2p/.aegir.js index a89fba7ad6..a379dfd8ee 100644 --- a/packages/libp2p/.aegir.js +++ b/packages/libp2p/.aegir.js @@ -20,7 +20,9 @@ export default { const peerId = await createEd25519PeerId() const libp2p = await createLibp2p({ connectionManager: { - inboundConnectionThreshold: Infinity, + inboundConnectionThreshold: 1000, + maxIncomingPendingConnections: 1000, + maxConnections: 1000, minConnections: 0 }, addresses: { @@ -44,7 +46,7 @@ export default { identify: identify(), relay: circuitRelayServer({ reservations: { - maxReservations: Infinity + maxReservations: 100000 } }), echo: echo() diff --git a/packages/libp2p/package.json b/packages/libp2p/package.json index 040249322f..a4eba1c125 100644 --- a/packages/libp2p/package.json +++ b/packages/libp2p/package.json @@ -103,7 +103,8 @@ "merge-options": "^3.0.4", "multiformats": "^13.0.0", "private-ip": "^3.0.1", - "uint8arrays": "^5.0.0" + "uint8arrays": "^5.0.0", + "yup": "^1.3.2" }, "devDependencies": { "@chainsafe/libp2p-yamux": "^6.0.1", diff --git a/packages/libp2p/src/address-manager/utils.ts b/packages/libp2p/src/address-manager/utils.ts index 7062446a86..02d460bad3 100644 --- a/packages/libp2p/src/address-manager/utils.ts +++ b/packages/libp2p/src/address-manager/utils.ts @@ -1,3 +1,7 @@ +import { type ObjectSchema, object, array, string, mixed } from 'yup' +import { validateMultiaddr } from '../config/helpers.js' +import type { Multiaddr } from '@multiformats/multiaddr' + export function debounce (func: () => void, wait: number): () => void { let timeout: ReturnType | undefined @@ -11,3 +15,12 @@ export function debounce (func: () => void, wait: number): () => void { timeout = setTimeout(later, wait) } } + +export function validateAddressManagerConfig (): ObjectSchema> { + return object({ + listen: array().of(string()).test('is multiaddr', validateMultiaddr).default([]), + announce: array().of(string()).test('is multiaddr', validateMultiaddr).default([]), + noAnnounce: array().of(string()).test('is multiaddr', validateMultiaddr).default([]), + announceFilter: mixed().default(() => (addrs: Multiaddr[]): Multiaddr[] => addrs) + }) +} diff --git a/packages/libp2p/src/config.ts b/packages/libp2p/src/config.ts deleted file mode 100644 index 410d31de5a..0000000000 --- a/packages/libp2p/src/config.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { CodeError, FaultTolerance } from '@libp2p/interface' -import { defaultAddressSort } from '@libp2p/utils/address-sort' -import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers' -import mergeOptions from 'merge-options' -import { codes, messages } from './errors.js' -import type { Libp2pInit } from './index.js' -import type { ServiceMap, RecursivePartial } from '@libp2p/interface' -import type { Multiaddr } from '@multiformats/multiaddr' - -const DefaultConfig: Partial = { - addresses: { - listen: [], - announce: [], - noAnnounce: [], - announceFilter: (multiaddrs: Multiaddr[]) => multiaddrs - }, - connectionManager: { - resolvers: { - dnsaddr: dnsaddrResolver - }, - addressSorter: defaultAddressSort - }, - transportManager: { - faultTolerance: FaultTolerance.FATAL_ALL - } -} - -export function validateConfig > (opts: RecursivePartial>): Libp2pInit { - const resultingOptions: Libp2pInit = mergeOptions(DefaultConfig, opts) - - if (resultingOptions.connectionProtector === null && globalThis.process?.env?.LIBP2P_FORCE_PNET != null) { // eslint-disable-line no-undef - throw new CodeError(messages.ERR_PROTECTOR_REQUIRED, codes.ERR_PROTECTOR_REQUIRED) - } - - return resultingOptions -} diff --git a/packages/libp2p/src/config/config.ts b/packages/libp2p/src/config/config.ts new file mode 100644 index 0000000000..0550e5854f --- /dev/null +++ b/packages/libp2p/src/config/config.ts @@ -0,0 +1,35 @@ +import { FaultTolerance } from '@libp2p/interface' +import { defaultAddressSort } from '@libp2p/utils/address-sort' +import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers' +import mergeOptions from 'merge-options' +import { object } from 'yup' +import { validateAddressManagerConfig } from '../address-manager/utils.js' +import { validateConnectionManagerConfig } from '../connection-manager/utils.js' +import type { ConnectionManagerInit } from '../connection-manager/index.js' +import type { Libp2pInit } from '../index.js' +import type { ServiceMap, RecursivePartial } from '@libp2p/interface' + +const defaultConfig: Partial = { + connectionManager: { + resolvers: { + dnsaddr: dnsaddrResolver + }, + addressSorter: defaultAddressSort + }, + transportManager: { + faultTolerance: FaultTolerance.FATAL_ALL + } +} + +export function validateConfig> (opts: RecursivePartial>): Libp2pInit { + const libp2pConfig = object({ + addresses: validateAddressManagerConfig(), + connectionManager: validateConnectionManagerConfig(opts?.connectionManager as ConnectionManagerInit) + }) + + const parsedOpts = libp2pConfig.validateSync(opts) + + const resultingOptions: Libp2pInit = mergeOptions(defaultConfig, parsedOpts) + + return resultingOptions +} diff --git a/packages/libp2p/src/config/helpers.ts b/packages/libp2p/src/config/helpers.ts new file mode 100644 index 0000000000..6d6d9b310e --- /dev/null +++ b/packages/libp2p/src/config/helpers.ts @@ -0,0 +1,19 @@ +import { CodeError } from '@libp2p/interface' +import { multiaddr } from '@multiformats/multiaddr' +import { codes } from '../errors.js' + +export const validateMultiaddr = (value: Array | undefined): boolean => { + if (value == null || value === undefined) { + return false + } + + value?.forEach((addr) => { + try { + multiaddr(addr) + } catch (err) { + throw new CodeError(`invalid multiaddr: ${addr}`, codes.ERR_INVALID_MULTIADDR) + } + }) + + return true +} diff --git a/packages/libp2p/src/connection-manager/utils.ts b/packages/libp2p/src/connection-manager/utils.ts index 4b21456e8e..e76435edf9 100644 --- a/packages/libp2p/src/connection-manager/utils.ts +++ b/packages/libp2p/src/connection-manager/utils.ts @@ -1,4 +1,9 @@ import { type AbortOptions, multiaddr, type Multiaddr } from '@multiformats/multiaddr' +import { type ObjectSchema, array, number, object, string } from 'yup' +import { validateMultiaddr } from '../config/helpers.js' +import { AUTO_DIAL_INTERVAL, AUTO_DIAL_CONCURRENCY, AUTO_DIAL_PRIORITY, MAX_PEER_ADDRS_TO_DIAL, DIAL_TIMEOUT, INBOUND_UPGRADE_TIMEOUT, INBOUND_CONNECTION_THRESHOLD, MAX_INCOMING_PENDING_CONNECTIONS } from './constants.defaults.js' +import { MIN_CONNECTIONS, MAX_CONNECTIONS, MAX_PARALLEL_DIALS } from './constants.js' +import type { ConnectionManagerInit } from '.' import type { LoggerOptions } from '@libp2p/interface' /** @@ -45,3 +50,21 @@ async function resolveRecord (ma: Multiaddr, options: AbortOptions & LoggerOptio return [] } } + +export const validateConnectionManagerConfig = (opts: ConnectionManagerInit): ObjectSchema> => { + return object({ + maxConnections: number().integer().min(opts?.minConnections ?? MIN_CONNECTIONS, `maxConnections must be greater than or equal to minConnections: ${opts?.minConnections ?? MIN_CONNECTIONS}`).default(MAX_CONNECTIONS), + minConnections: number().integer().min(0).max(opts?.maxConnections ?? MAX_CONNECTIONS, `minConnections must be less than or equal to maxConnections : ${opts?.maxConnections ?? MAX_CONNECTIONS}`).default(MIN_CONNECTIONS), + autoDialInterval: number().integer().min(0).default(AUTO_DIAL_INTERVAL), + autoDialConcurrency: number().integer().min(0).default(AUTO_DIAL_CONCURRENCY), + autoDialPriority: number().integer().min(0).default(AUTO_DIAL_PRIORITY), + maxParallelDials: number().integer().min(0).default(MAX_PARALLEL_DIALS), + maxPeerAddrsToDialed: number().integer().min(0).default(MAX_PEER_ADDRS_TO_DIAL), + dialTimeout: number().integer().min(0).default(DIAL_TIMEOUT), + inboundUpgradeTimeout: number().integer().min(0).default(INBOUND_UPGRADE_TIMEOUT), + allow: array().of(string()).test('is multiaddr', validateMultiaddr).default([]), + deny: array().of(string()).test('is multiaddr', validateMultiaddr).default([]), + inboundConnectionThreshold: number().integer().min(0).default(INBOUND_CONNECTION_THRESHOLD), + maxIncomingPendingConnections: number().integer().min(0).default(MAX_INCOMING_PENDING_CONNECTIONS) + }) +} diff --git a/packages/libp2p/src/libp2p.ts b/packages/libp2p/src/libp2p.ts index f104d67a30..999516fcf4 100644 --- a/packages/libp2p/src/libp2p.ts +++ b/packages/libp2p/src/libp2p.ts @@ -11,8 +11,8 @@ import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { DefaultAddressManager } from './address-manager/index.js' import { defaultComponents } from './components.js' +import { validateConfig } from './config/config.js' import { connectionGater } from './config/connection-gater.js' -import { validateConfig } from './config.js' import { DefaultConnectionManager } from './connection-manager/index.js' import { CompoundContentRouting } from './content-routing.js' import { codes } from './errors.js' diff --git a/packages/libp2p/test/connection-manager/index.node.ts b/packages/libp2p/test/connection-manager/index.node.ts index cbc5f2178f..07657e18bb 100644 --- a/packages/libp2p/test/connection-manager/index.node.ts +++ b/packages/libp2p/test/connection-manager/index.node.ts @@ -236,7 +236,9 @@ describe('libp2p.connections', () => { }, connectionManager: { minConnections, - maxConnections: 1 + maxConnections: 1, + inboundConnectionThreshold: 1, + maxIncomingPendingConnections: 1 } } }) diff --git a/packages/protocol-autonat/package.json b/packages/protocol-autonat/package.json index b890ecfe7d..4405c67d89 100644 --- a/packages/protocol-autonat/package.json +++ b/packages/protocol-autonat/package.json @@ -63,7 +63,8 @@ "it-pipe": "^3.0.1", "private-ip": "^3.0.1", "protons-runtime": "^5.0.0", - "uint8arraylist": "^2.4.7" + "uint8arraylist": "^2.4.7", + "yup": "^1.3.2" }, "devDependencies": { "@libp2p/logger": "^4.0.4", diff --git a/packages/protocol-autonat/src/autonat.ts b/packages/protocol-autonat/src/autonat.ts index 12d4b8a112..69be278636 100644 --- a/packages/protocol-autonat/src/autonat.ts +++ b/packages/protocol-autonat/src/autonat.ts @@ -8,6 +8,7 @@ import map from 'it-map' import parallel from 'it-parallel' import { pipe } from 'it-pipe' import isPrivateIp from 'private-ip' +import { number, object, string } from 'yup' import { MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, @@ -23,6 +24,15 @@ import type { IncomingStreamData } from '@libp2p/interface-internal' // https://github.com/libp2p/specs/blob/master/autonat/README.md#autonat-protocol const REQUIRED_SUCCESSFUL_DIALS = 4 +const configValidator = object({ + protocolPrefix: string().default(PROTOCOL_PREFIX), + timeout: number().integer().default(TIMEOUT), + startupDelay: number().integer().default(STARTUP_DELAY), + refreshInterval: number().integer().default(REFRESH_INTERVAL), + maxInboundStreams: number().integer().default(MAX_INBOUND_STREAMS), + maxOutboundStreams: number().integer().default(MAX_OUTBOUND_STREAMS) +}) + export class AutoNATService implements Startable { private readonly components: AutoNATComponents private readonly startupDelay: number @@ -36,15 +46,17 @@ export class AutoNATService implements Startable { private readonly log: Logger constructor (components: AutoNATComponents, init: AutoNATServiceInit) { + const config = configValidator.validateSync(init) + this.components = components this.log = components.logger.forComponent('libp2p:autonat') this.started = false - this.protocol = `/${init.protocolPrefix ?? PROTOCOL_PREFIX}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` - this.timeout = init.timeout ?? TIMEOUT - this.maxInboundStreams = init.maxInboundStreams ?? MAX_INBOUND_STREAMS - this.maxOutboundStreams = init.maxOutboundStreams ?? MAX_OUTBOUND_STREAMS - this.startupDelay = init.startupDelay ?? STARTUP_DELAY - this.refreshInterval = init.refreshInterval ?? REFRESH_INTERVAL + this.protocol = `/${config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` + this.timeout = config.timeout + this.maxInboundStreams = config.maxInboundStreams + this.maxOutboundStreams = config.maxOutboundStreams + this.startupDelay = config.startupDelay + this.refreshInterval = config.refreshInterval this._verifyExternalAddresses = this._verifyExternalAddresses.bind(this) } @@ -382,7 +394,7 @@ export class AutoNATService implements Startable { const networkSegments: string[] = [] const verifyAddress = async (peer: PeerInfo): Promise => { - let onAbort = (): void => {} + let onAbort = (): void => { } try { this.log('asking %p to verify multiaddr', peer.id) diff --git a/packages/protocol-dcutr/package.json b/packages/protocol-dcutr/package.json index ce2a564d47..bcfd8de9f3 100644 --- a/packages/protocol-dcutr/package.json +++ b/packages/protocol-dcutr/package.json @@ -59,7 +59,8 @@ "it-protobuf-stream": "^1.1.1", "private-ip": "^3.0.1", "protons-runtime": "^5.0.0", - "uint8arraylist": "^2.4.7" + "uint8arraylist": "^2.4.7", + "yup": "^1.3.2" }, "devDependencies": { "aegir": "^42.0.0", diff --git a/packages/protocol-dcutr/src/constants.ts b/packages/protocol-dcutr/src/constants.ts new file mode 100644 index 0000000000..e5d4debba5 --- /dev/null +++ b/packages/protocol-dcutr/src/constants.ts @@ -0,0 +1,12 @@ +// https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#rpc-messages +export const MAX_DCUTR_MESSAGE_SIZE = 1024 * 4 +// ensure the dial has a high priority to jump to the head of the dial queue +export const DCUTR_DIAL_PRIORITY = 100 + +export const DEFAULT_MAX_INBOUND_STREAMS = 1 + +export const DEFAULT_MAX_OUTBOUND_STREAMS = 1 + +export const DEFAULT_TIMEOUT = 5000 + +export const DEFAULT_RETRIES = 3 diff --git a/packages/protocol-dcutr/src/dcutr.ts b/packages/protocol-dcutr/src/dcutr.ts index 414a7e2159..fad6358bb0 100644 --- a/packages/protocol-dcutr/src/dcutr.ts +++ b/packages/protocol-dcutr/src/dcutr.ts @@ -2,6 +2,8 @@ import { CodeError, ERR_INVALID_MESSAGE } from '@libp2p/interface' import { type Multiaddr, multiaddr } from '@multiformats/multiaddr' import delay from 'delay' import { pbStream } from 'it-protobuf-stream' +import { number, object } from 'yup' +import { DCUTR_DIAL_PRIORITY, DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS, DEFAULT_RETRIES, DEFAULT_TIMEOUT, MAX_DCUTR_MESSAGE_SIZE } from './constants.js' import { HolePunch } from './pb/message.js' import { isPublicAndDialable } from './utils.js' import { multicodec } from './index.js' @@ -9,19 +11,14 @@ import type { DCUtRServiceComponents, DCUtRServiceInit } from './index.js' import type { Logger, Connection, Stream, PeerStore, Startable } from '@libp2p/interface' import type { AddressManager, ConnectionManager, Registrar, TransportManager } from '@libp2p/interface-internal' -// https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#rpc-messages -const MAX_DCUTR_MESSAGE_SIZE = 1024 * 4 -// ensure the dial has a high priority to jump to the head of the dial queue -const DCUTR_DIAL_PRIORITY = 100 - -const defaultValues = { +const configValidator = object({ // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/holepunch/holepuncher.go#L27 - timeout: 5000, + timeout: number().integer().min(0).default(DEFAULT_TIMEOUT), // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/holepunch/holepuncher.go#L28 - retries: 3, - maxInboundStreams: 1, - maxOutboundStreams: 1 -} + retries: number().integer().min(0).default(DEFAULT_RETRIES), + maxInboundStreams: number().integer().min(0).default(DEFAULT_MAX_INBOUND_STREAMS), + maxOutboundStreams: number().integer().min(0).default(DEFAULT_MAX_OUTBOUND_STREAMS) +}) export class DefaultDCUtRService implements Startable { private started: boolean @@ -46,10 +43,12 @@ export class DefaultDCUtRService implements Startable { this.connectionManager = components.connectionManager this.transportManager = components.transportManager - this.timeout = init.timeout ?? defaultValues.timeout - this.retries = init.retries ?? defaultValues.retries - this.maxInboundStreams = init.maxInboundStreams ?? defaultValues.maxInboundStreams - this.maxOutboundStreams = init.maxOutboundStreams ?? defaultValues.maxOutboundStreams + const config = configValidator.validateSync(init) + + this.timeout = config.timeout + this.retries = config.retries + this.maxInboundStreams = config.maxInboundStreams + this.maxOutboundStreams = config.maxOutboundStreams } isStarted (): boolean { @@ -363,7 +362,7 @@ export class DefaultDCUtRService implements Startable { } output.push(ma) - } catch {} + } catch { } } return output diff --git a/packages/protocol-fetch/package.json b/packages/protocol-fetch/package.json index b9a750e9c0..46b6abca7a 100644 --- a/packages/protocol-fetch/package.json +++ b/packages/protocol-fetch/package.json @@ -56,7 +56,8 @@ "it-protobuf-stream": "^1.1.1", "protons-runtime": "^5.0.0", "uint8arraylist": "^2.4.7", - "uint8arrays": "^5.0.0" + "uint8arrays": "^5.0.0", + "yup": "^1.3.2" }, "devDependencies": { "@libp2p/logger": "^4.0.4", diff --git a/packages/protocol-fetch/src/constants.ts b/packages/protocol-fetch/src/constants.ts index cbab081bcd..ad6769121b 100644 --- a/packages/protocol-fetch/src/constants.ts +++ b/packages/protocol-fetch/src/constants.ts @@ -1,3 +1,8 @@ // https://github.com/libp2p/specs/tree/master/fetch#wire-protocol export const PROTOCOL_VERSION = '0.0.1' export const PROTOCOL_NAME = 'fetch' + +export const MAX_INBOUND_STREAMS = 1 +export const MAX_OUTBOUND_STREAMS = 1 +export const TIMEOUT = 10000 +export const PROTOCOL_PREFIX = 'libp2p' diff --git a/packages/protocol-fetch/src/fetch.ts b/packages/protocol-fetch/src/fetch.ts index 8808dbcbf2..f23ed8c6dc 100644 --- a/packages/protocol-fetch/src/fetch.ts +++ b/packages/protocol-fetch/src/fetch.ts @@ -2,36 +2,50 @@ import { CodeError, ERR_INVALID_MESSAGE, ERR_INVALID_PARAMETERS, ERR_TIMEOUT, se import { pbStream } from 'it-protobuf-stream' import { fromString as uint8arrayFromString } from 'uint8arrays/from-string' import { toString as uint8arrayToString } from 'uint8arrays/to-string' -import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js' +import { object, number, string } from 'yup' +import { MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, PROTOCOL_NAME, PROTOCOL_PREFIX, PROTOCOL_VERSION, TIMEOUT } from './constants.js' import { FetchRequest, FetchResponse } from './pb/proto.js' import type { Fetch as FetchInterface, FetchComponents, FetchInit, LookupFunction } from './index.js' import type { AbortOptions, Logger, Stream, PeerId, Startable } from '@libp2p/interface' import type { IncomingStreamData } from '@libp2p/interface-internal' -const DEFAULT_TIMEOUT = 10000 - /** * A simple libp2p protocol for requesting a value corresponding to a key from a peer. * Developers can register one or more lookup function for retrieving the value corresponding to * a given key. Each lookup function must act on a distinct part of the overall key space, defined * by a fixed prefix that all keys that should be routed to that lookup function will start with. */ + +const configValidator = object({ + timeout: number().integer().default(TIMEOUT), + maxInboundStreams: number().integer().min(0).default(MAX_INBOUND_STREAMS), + maxOutboundStreams: number().integer().min(0).default(MAX_OUTBOUND_STREAMS), + protocolPrefix: string().default(PROTOCOL_PREFIX) +}) + export class Fetch implements Startable, FetchInterface { public readonly protocol: string private readonly components: FetchComponents private readonly lookupFunctions: Map + private readonly timeout: number + private readonly maxInboundStreams: number + private readonly maxOutboundStreams: number private started: boolean - private readonly init: FetchInit private readonly log: Logger constructor (components: FetchComponents, init: FetchInit = {}) { this.log = components.logger.forComponent('libp2p:fetch') this.started = false this.components = components - this.protocol = `/${init.protocolPrefix ?? 'libp2p'}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` this.lookupFunctions = new Map() // Maps key prefix to value lookup function this.handleMessage = this.handleMessage.bind(this) - this.init = init + + const config = configValidator.validateSync(init) + + this.protocol = `/${config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` + this.timeout = config.timeout + this.maxInboundStreams = config.maxInboundStreams + this.maxOutboundStreams = config.maxOutboundStreams } async start (): Promise { @@ -44,8 +58,8 @@ export class Fetch implements Startable, FetchInterface { this.log.error(err) }) }, { - maxInboundStreams: this.init.maxInboundStreams, - maxOutboundStreams: this.init.maxOutboundStreams + maxInboundStreams: this.maxInboundStreams, + maxOutboundStreams: this.maxOutboundStreams }) this.started = true } @@ -72,7 +86,7 @@ export class Fetch implements Startable, FetchInterface { // create a timeout if no abort signal passed if (signal == null) { - const timeout = this.init.timeout ?? DEFAULT_TIMEOUT + const timeout = this.timeout this.log('using default timeout of %d ms', timeout) signal = AbortSignal.timeout(timeout) @@ -138,7 +152,7 @@ export class Fetch implements Startable, FetchInterface { */ async handleMessage (data: IncomingStreamData): Promise { const { stream } = data - const signal = AbortSignal.timeout(this.init.timeout ?? DEFAULT_TIMEOUT) + const signal = AbortSignal.timeout(this.timeout) try { const pb = pbStream(stream) diff --git a/packages/protocol-identify/package.json b/packages/protocol-identify/package.json index 4030cda859..0d8888efde 100644 --- a/packages/protocol-identify/package.json +++ b/packages/protocol-identify/package.json @@ -61,7 +61,8 @@ "protons-runtime": "^5.0.0", "uint8arraylist": "^2.4.7", "uint8arrays": "^5.0.0", - "wherearewe": "^2.0.1" + "wherearewe": "^2.0.1", + "yup": "^1.3.2" }, "devDependencies": { "@libp2p/logger": "^4.0.4", diff --git a/packages/protocol-identify/src/consts.ts b/packages/protocol-identify/src/consts.ts index 60dc5af071..0afb45bdf4 100644 --- a/packages/protocol-identify/src/consts.ts +++ b/packages/protocol-identify/src/consts.ts @@ -2,8 +2,21 @@ export const PROTOCOL_VERSION = 'ipfs/0.1.0' // deprecated export const MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0' // deprecated export const MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0' // deprecated +export const PROTOCOL_PREFIX = 'ipfs' +export const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8 // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52 +export const MAX_INBOUND_STREAMS = 1 +export const MAX_OUTBOUND_STREAMS = 1 +export const MAX_PUSH_INCOMING_STREAMS = 1 +export const MAX_PUSH_OUTGOING_STREAMS = 1 +export const MAX_OBSERVED_ADDRESSES = 10 + +export const RUN_ON_TRANSIENT_CONNECTION = true +export const RUN_ON_CONNECTION_OPEN = true + export const IDENTIFY_PROTOCOL_VERSION = '0.1.0' export const MULTICODEC_IDENTIFY_PROTOCOL_NAME = 'id' export const MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME = 'id/push' export const MULTICODEC_IDENTIFY_PROTOCOL_VERSION = '1.0.0' export const MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION = '1.0.0' + +export const TIMEOUT = 60000 diff --git a/packages/protocol-identify/src/identify.ts b/packages/protocol-identify/src/identify.ts index 836ee7eb1e..6370031011 100644 --- a/packages/protocol-identify/src/identify.ts +++ b/packages/protocol-identify/src/identify.ts @@ -9,34 +9,41 @@ import { pbStream } from 'it-protobuf-stream' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { isNode, isBrowser, isWebWorker, isElectronMain, isElectronRenderer, isReactNative } from 'wherearewe' +import { boolean, number, object, string } from 'yup' import { IDENTIFY_PROTOCOL_VERSION, MULTICODEC_IDENTIFY_PROTOCOL_NAME, MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME, MULTICODEC_IDENTIFY_PROTOCOL_VERSION, - MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION + MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION, + MAX_INBOUND_STREAMS, + MAX_OUTBOUND_STREAMS, + MAX_IDENTIFY_MESSAGE_SIZE, + TIMEOUT, + RUN_ON_CONNECTION_OPEN as DEFAULT_RUN_ON_CONNECTION_OPEN, + PROTOCOL_PREFIX, + RUN_ON_TRANSIENT_CONNECTION as DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS, + MAX_PUSH_INCOMING_STREAMS, + MAX_PUSH_OUTGOING_STREAMS, + MAX_OBSERVED_ADDRESSES } from './consts.js' import { Identify as IdentifyMessage } from './pb/message.js' import type { Identify as IdentifyInterface, IdentifyComponents, IdentifyInit } from './index.js' import type { Libp2pEvents, IdentifyResult, SignedPeerRecord, AbortOptions, Logger, Connection, Stream, TypedEventTarget, PeerId, Peer, PeerData, PeerStore, Startable } from '@libp2p/interface' import type { AddressManager, ConnectionManager, IncomingStreamData, Registrar } from '@libp2p/interface-internal' -// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52 -const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8 - -const defaultValues = { - protocolPrefix: 'ipfs', - // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L48 - timeout: 60000, - maxInboundStreams: 1, - maxOutboundStreams: 1, - maxPushIncomingStreams: 1, - maxPushOutgoingStreams: 1, - maxObservedAddresses: 10, - maxIdentifyMessageSize: 8192, - runOnConnectionOpen: true, - runOnTransientConnection: true -} +const configValidator = object({ + protocolPrefix: string().default(PROTOCOL_PREFIX), + timeout: number().integer().min(0).default(TIMEOUT), + maxIdentifyMessageSize: number().integer().min(0).default(MAX_IDENTIFY_MESSAGE_SIZE), + maxInboundStreams: number().integer().min(0).default(MAX_INBOUND_STREAMS), + maxPushIncomingStreams: number().integer().min(0).default(MAX_PUSH_INCOMING_STREAMS), + maxPushOutgoingStreams: number().integer().min(0).default(MAX_PUSH_OUTGOING_STREAMS), + maxOutboundStreams: number().integer().min(0).default(MAX_OUTBOUND_STREAMS), + maxObservedAddresses: number().integer().min(0).default(MAX_OBSERVED_ADDRESSES), + runOnConnectionOpen: boolean().default(DEFAULT_RUN_ON_CONNECTION_OPEN), + runOnTransientConnection: boolean().default(DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS) +}) export class Identify implements Startable, IdentifyInterface { private readonly identifyProtocolStr: string @@ -61,9 +68,12 @@ export class Identify implements Startable, IdentifyInterface { private readonly maxObservedAddresses: number private readonly events: TypedEventTarget private readonly runOnTransientConnection: boolean + private readonly runOnConnectionOpen: boolean private readonly log: Logger constructor (components: IdentifyComponents, init: IdentifyInit = {}) { + const config = configValidator.validateSync(init) + this.started = false this.peerId = components.peerId this.peerStore = components.peerStore @@ -73,24 +83,25 @@ export class Identify implements Startable, IdentifyInterface { this.events = components.events this.log = components.logger.forComponent('libp2p:identify') - this.identifyProtocolStr = `/${init.protocolPrefix ?? defaultValues.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}` - this.identifyPushProtocolStr = `/${init.protocolPrefix ?? defaultValues.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}` - this.timeout = init.timeout ?? defaultValues.timeout - this.maxInboundStreams = init.maxInboundStreams ?? defaultValues.maxInboundStreams - this.maxOutboundStreams = init.maxOutboundStreams ?? defaultValues.maxOutboundStreams - this.maxPushIncomingStreams = init.maxPushIncomingStreams ?? defaultValues.maxPushIncomingStreams - this.maxPushOutgoingStreams = init.maxPushOutgoingStreams ?? defaultValues.maxPushOutgoingStreams - this.maxIdentifyMessageSize = init.maxIdentifyMessageSize ?? defaultValues.maxIdentifyMessageSize - this.maxObservedAddresses = init.maxObservedAddresses ?? defaultValues.maxObservedAddresses - this.runOnTransientConnection = init.runOnTransientConnection ?? defaultValues.runOnTransientConnection + this.identifyProtocolStr = `/${config.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}` + this.identifyPushProtocolStr = `/${config.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}` + this.timeout = config.timeout + this.maxInboundStreams = config.maxInboundStreams + this.maxOutboundStreams = config.maxOutboundStreams + this.maxPushIncomingStreams = config.maxPushIncomingStreams + this.maxPushOutgoingStreams = config.maxPushOutgoingStreams + this.maxIdentifyMessageSize = config.maxIdentifyMessageSize + this.maxObservedAddresses = config.maxObservedAddresses + this.runOnTransientConnection = config.runOnTransientConnection + this.runOnConnectionOpen = config.runOnConnectionOpen // Store self host metadata this.host = { - protocolVersion: `${init.protocolPrefix ?? defaultValues.protocolPrefix}/${IDENTIFY_PROTOCOL_VERSION}`, + protocolVersion: `${config.protocolPrefix}/${IDENTIFY_PROTOCOL_VERSION}`, agentVersion: init.agentVersion ?? `${components.nodeInfo.name}/${components.nodeInfo.version}` } - if (init.runOnConnectionOpen ?? defaultValues.runOnConnectionOpen) { + if (this.runOnConnectionOpen) { // When a new connection happens, trigger identify components.events.addEventListener('connection:open', (evt) => { const connection = evt.detail @@ -308,7 +319,7 @@ export class Identify implements Startable, IdentifyInterface { this.log('our observed address is %a', cleanObservedAddr) if (cleanObservedAddr != null && - this.addressManager.getObservedAddrs().length < (this.maxObservedAddresses ?? Infinity)) { + this.addressManager.getObservedAddrs().length < (this.maxObservedAddresses)) { this.log('storing our observed address %a', cleanObservedAddr) this.addressManager.addObservedAddr(cleanObservedAddr) } diff --git a/packages/protocol-perf/package.json b/packages/protocol-perf/package.json index 07e4846f0d..9185228575 100644 --- a/packages/protocol-perf/package.json +++ b/packages/protocol-perf/package.json @@ -55,7 +55,8 @@ "@libp2p/interface": "^1.1.1", "@libp2p/interface-internal": "^1.0.6", "@multiformats/multiaddr": "^12.1.10", - "it-pushable": "^3.2.3" + "it-pushable": "^3.2.3", + "yup": "^1.3.2" }, "devDependencies": { "@libp2p/interface-compliance-tests": "^5.1.2", diff --git a/packages/protocol-perf/src/perf-service.ts b/packages/protocol-perf/src/perf-service.ts index f3475eb506..14fc3458fc 100644 --- a/packages/protocol-perf/src/perf-service.ts +++ b/packages/protocol-perf/src/perf-service.ts @@ -1,10 +1,19 @@ import { pushable } from 'it-pushable' +import { boolean, number, object, string } from 'yup' import { MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, PROTOCOL_NAME, RUN_ON_TRANSIENT_CONNECTION, WRITE_BLOCK_SIZE } from './constants.js' import type { PerfOptions, PerfOutput, PerfComponents, PerfInit, Perf as PerfInterface } from './index.js' import type { Logger, Startable } from '@libp2p/interface' import type { IncomingStreamData } from '@libp2p/interface-internal' import type { Multiaddr } from '@multiformats/multiaddr' +const configValidator = object({ + maxInboundStreams: number().integer().min(0).default(MAX_INBOUND_STREAMS), + maxOutboundStreams: number().integer().min(0).default(MAX_OUTBOUND_STREAMS), + protocolName: string().default(PROTOCOL_NAME), + writeBlockSize: number().integer().min(1).default(WRITE_BLOCK_SIZE), + runOnTransientConnection: boolean().default(RUN_ON_TRANSIENT_CONNECTION) +}) + export class Perf implements Startable, PerfInterface { private readonly log: Logger public readonly protocol: string @@ -20,12 +29,15 @@ export class Perf implements Startable, PerfInterface { this.components = components this.log = components.logger.forComponent('libp2p:perf') this.started = false - this.protocol = init.protocolName ?? PROTOCOL_NAME - this.writeBlockSize = init.writeBlockSize ?? WRITE_BLOCK_SIZE + + const config = configValidator.validateSync(init) + + this.protocol = config.protocolName + this.writeBlockSize = config.writeBlockSize + this.maxInboundStreams = config.maxInboundStreams + this.maxOutboundStreams = config.maxOutboundStreams + this.runOnTransientConnection = config.runOnTransientConnection this.databuf = new ArrayBuffer(this.writeBlockSize) - this.maxInboundStreams = init.maxInboundStreams ?? MAX_INBOUND_STREAMS - this.maxOutboundStreams = init.maxOutboundStreams ?? MAX_OUTBOUND_STREAMS - this.runOnTransientConnection = init.runOnTransientConnection ?? RUN_ON_TRANSIENT_CONNECTION } async start (): Promise { diff --git a/packages/protocol-ping/package.json b/packages/protocol-ping/package.json index 04c8aa0e78..ed382c06fb 100644 --- a/packages/protocol-ping/package.json +++ b/packages/protocol-ping/package.json @@ -55,7 +55,8 @@ "@multiformats/multiaddr": "^12.1.10", "it-first": "^3.0.3", "it-pipe": "^3.0.1", - "uint8arrays": "^5.0.0" + "uint8arrays": "^5.0.0", + "yup": "^1.3.2" }, "devDependencies": { "@libp2p/logger": "^4.0.4", diff --git a/packages/protocol-ping/src/constants.ts b/packages/protocol-ping/src/constants.ts index 0e16b6b72b..6808799939 100644 --- a/packages/protocol-ping/src/constants.ts +++ b/packages/protocol-ping/src/constants.ts @@ -13,5 +13,6 @@ export const TIMEOUT = 10000 // opening stream A even though the dialing peer is opening stream B and closing stream A). export const MAX_INBOUND_STREAMS = 2 export const MAX_OUTBOUND_STREAMS = 1 +export const DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS = true export const ERR_WRONG_PING_ACK = 'ERR_WRONG_PING_ACK' diff --git a/packages/protocol-ping/src/ping.ts b/packages/protocol-ping/src/ping.ts index a7af5d16ff..1f7a79b2fa 100644 --- a/packages/protocol-ping/src/ping.ts +++ b/packages/protocol-ping/src/ping.ts @@ -3,12 +3,21 @@ import { CodeError, ERR_TIMEOUT } from '@libp2p/interface' import first from 'it-first' import { pipe } from 'it-pipe' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { PROTOCOL_PREFIX, PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION, TIMEOUT, MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, ERR_WRONG_PING_ACK } from './constants.js' +import { boolean, number, object, string } from 'yup' +import { PROTOCOL_PREFIX, PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION, TIMEOUT, MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, ERR_WRONG_PING_ACK, DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS } from './constants.js' import type { PingServiceComponents, PingServiceInit, PingService as PingServiceInterface } from './index.js' import type { AbortOptions, Logger, Stream, PeerId, Startable } from '@libp2p/interface' import type { IncomingStreamData } from '@libp2p/interface-internal' import type { Multiaddr } from '@multiformats/multiaddr' +const configValidator = object({ + protocolPrefix: string().default(PROTOCOL_PREFIX), + timeout: number().integer().min(0).default(TIMEOUT), + maxInboundStreams: number().integer().min(0).default(MAX_INBOUND_STREAMS), + maxOutboundStreams: number().integer().min(0).default(MAX_OUTBOUND_STREAMS), + runOnTransientConnection: boolean().default(DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS) +}) + export class PingService implements Startable, PingServiceInterface { public readonly protocol: string private readonly components: PingServiceComponents @@ -23,11 +32,14 @@ export class PingService implements Startable, PingServiceInterface { this.components = components this.log = components.logger.forComponent('libp2p:ping') this.started = false - this.protocol = `/${init.protocolPrefix ?? PROTOCOL_PREFIX}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` - this.timeout = init.timeout ?? TIMEOUT - this.maxInboundStreams = init.maxInboundStreams ?? MAX_INBOUND_STREAMS - this.maxOutboundStreams = init.maxOutboundStreams ?? MAX_OUTBOUND_STREAMS - this.runOnTransientConnection = init.runOnTransientConnection ?? true + + const config = configValidator.validateSync(init) + + this.protocol = `/${config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` + this.timeout = config.timeout + this.maxInboundStreams = config.maxInboundStreams + this.maxOutboundStreams = config.maxOutboundStreams + this.runOnTransientConnection = config.runOnTransientConnection this.handleMessage = this.handleMessage.bind(this) } @@ -80,7 +92,7 @@ export class PingService implements Startable, PingServiceInterface { const data = randomBytes(PING_LENGTH) const connection = await this.components.connectionManager.openConnection(peer, options) let stream: Stream | undefined - let onAbort = (): void => {} + let onAbort = (): void => { } if (options.signal == null) { const signal = AbortSignal.timeout(this.timeout) diff --git a/packages/transport-circuit-relay-v2/package.json b/packages/transport-circuit-relay-v2/package.json index 757e8dfa7f..5a4a50bae8 100644 --- a/packages/transport-circuit-relay-v2/package.json +++ b/packages/transport-circuit-relay-v2/package.json @@ -67,7 +67,8 @@ "p-retry": "^6.1.0", "protons-runtime": "^5.0.0", "uint8arraylist": "^2.4.7", - "uint8arrays": "^5.0.0" + "uint8arrays": "^5.0.0", + "yup": "^1.3.2" }, "devDependencies": { "@libp2p/interface-compliance-tests": "^5.1.2", diff --git a/packages/transport-circuit-relay-v2/src/constants.ts b/packages/transport-circuit-relay-v2/src/constants.ts index 50ed7a1134..7d35a7d707 100644 --- a/packages/transport-circuit-relay-v2/src/constants.ts +++ b/packages/transport-circuit-relay-v2/src/constants.ts @@ -42,6 +42,14 @@ export const RELAY_SOURCE_TAG = 'circuit-relay-source' export const RELAY_TAG = 'circuit-relay-relay' +export const DEFAULT_DEFAULT_APPLY_LIMIT = true + +export const DEFAULT_RESERVATION_COMPLETION_TIMEOUT = 10000 + +export const DEFAULT_MAX_RESERVATION_QUEUE_LENGTH = 100 + +export const DEFAULT_DISCOVER_RELAYS = 0 + // circuit v2 connection limits // https://github.com/libp2p/go-libp2p/blob/master/p2p/protocol/circuitv2/relay/resources.go#L61-L66 @@ -73,6 +81,12 @@ export const DEFAULT_ADVERT_BOOT_DELAY = 30 * second export const MAX_CONNECTIONS = 300 +export const DEFAULT_MAX_INBOUND_STREAMS = 300 + +export const DEFAULT_MAX_OUTBOUND_STREAMS = 300 + +export const DEFAULT_STOP_TIMEOUT = 30 * second + export const ERR_NO_ROUTERS_AVAILABLE = 'ERR_NO_ROUTERS_AVAILABLE' export const ERR_RELAYED_DIAL = 'ERR_RELAYED_DIAL' export const ERR_HOP_REQUEST_FAILED = 'ERR_HOP_REQUEST_FAILED' diff --git a/packages/transport-circuit-relay-v2/src/server/advert-service.ts b/packages/transport-circuit-relay-v2/src/server/advert-service.ts index 4b8f55dddb..aba7be9af1 100644 --- a/packages/transport-circuit-relay-v2/src/server/advert-service.ts +++ b/packages/transport-circuit-relay-v2/src/server/advert-service.ts @@ -1,5 +1,6 @@ import { TypedEventEmitter } from '@libp2p/interface' import pRetry from 'p-retry' +import { number, object } from 'yup' import { DEFAULT_ADVERT_BOOT_DELAY, ERR_NO_ROUTERS_AVAILABLE, @@ -28,6 +29,10 @@ export interface AdvertServiceEvents { 'advert:error': CustomEvent } +const configValidator = object({ + bootDelay: number().integer().min(0).default(DEFAULT_ADVERT_BOOT_DELAY) +}) + export class AdvertService extends TypedEventEmitter implements Startable { private readonly contentRouting: ContentRouting private timeout?: any @@ -43,8 +48,11 @@ export class AdvertService extends TypedEventEmitter implem this.log = components.logger.forComponent('libp2p:circuit-relay:advert-service') this.contentRouting = components.contentRouting - this.bootDelay = init?.bootDelay ?? DEFAULT_ADVERT_BOOT_DELAY this.started = false + + const config = configValidator.validateSync(init) + + this.bootDelay = config.bootDelay } isStarted (): boolean { diff --git a/packages/transport-circuit-relay-v2/src/server/index.ts b/packages/transport-circuit-relay-v2/src/server/index.ts index 36eb9fdfa8..109a5cd71c 100644 --- a/packages/transport-circuit-relay-v2/src/server/index.ts +++ b/packages/transport-circuit-relay-v2/src/server/index.ts @@ -4,9 +4,12 @@ import { RecordEnvelope } from '@libp2p/peer-record' import { type Multiaddr, multiaddr } from '@multiformats/multiaddr' import { pbStream, type ProtobufStream } from 'it-protobuf-stream' import pDefer from 'p-defer' +import { object, number } from 'yup' import { CIRCUIT_PROTO_CODE, DEFAULT_HOP_TIMEOUT, + DEFAULT_MAX_INBOUND_STREAMS, + DEFAULT_MAX_OUTBOUND_STREAMS, MAX_CONNECTIONS, RELAY_SOURCE_TAG, RELAY_V2_HOP_CODEC, @@ -15,7 +18,7 @@ import { import { HopMessage, type Reservation, Status, StopMessage } from '../pb/index.js' import { createLimitedRelay } from '../utils.js' import { AdvertService, type AdvertServiceComponents, type AdvertServiceInit } from './advert-service.js' -import { ReservationStore, type ReservationStoreInit } from './reservation-store.js' +import { ReservationStore, reservationStoreConfigValidator, type ReservationStoreInit } from './reservation-store.js' import { ReservationVoucherRecord } from './reservation-voucher.js' import type { CircuitRelayService, RelayReservation } from '../index.js' import type { ComponentLogger, Logger, Connection, Stream, ConnectionGater, PeerId, PeerStore, Startable } from '@libp2p/interface' @@ -86,9 +89,13 @@ export interface RelayServerEvents { 'relay:advert:error': CustomEvent } -const defaults = { - maxOutboundStopStreams: MAX_CONNECTIONS -} +const configValidator = object({ + hopTimeout: number().integer().min(0).default(DEFAULT_HOP_TIMEOUT), + reservations: reservationStoreConfigValidator, + maxInboundHopStreams: number().integer().min(0).default(DEFAULT_MAX_INBOUND_STREAMS), + maxOutboundHopStreams: number().integer().min(0).default(DEFAULT_MAX_OUTBOUND_STREAMS), + maxOutboundStopStreams: number().integer().min(0).default(MAX_CONNECTIONS) +}) class CircuitRelayServer extends TypedEventEmitter implements Startable, CircuitRelayService { private readonly registrar: Registrar @@ -113,6 +120,8 @@ class CircuitRelayServer extends TypedEventEmitter implements constructor (components: CircuitRelayServerComponents, init: CircuitRelayServerInit = {}) { super() + const config = configValidator.validateSync(init) + this.log = components.logger.forComponent('libp2p:circuit-relay:server') this.registrar = components.registrar this.peerStore = components.peerStore @@ -121,11 +130,11 @@ class CircuitRelayServer extends TypedEventEmitter implements this.connectionManager = components.connectionManager this.connectionGater = components.connectionGater this.started = false - this.hopTimeout = init?.hopTimeout ?? DEFAULT_HOP_TIMEOUT + this.hopTimeout = config.hopTimeout this.shutdownController = new AbortController() - this.maxInboundHopStreams = init.maxInboundHopStreams - this.maxOutboundHopStreams = init.maxOutboundHopStreams - this.maxOutboundStopStreams = init.maxOutboundStopStreams ?? defaults.maxOutboundStopStreams + this.maxInboundHopStreams = config.maxInboundHopStreams + this.maxOutboundHopStreams = config.maxOutboundHopStreams + this.maxOutboundStopStreams = config.maxOutboundStopStreams setMaxListeners(Infinity, this.shutdownController.signal) diff --git a/packages/transport-circuit-relay-v2/src/server/reservation-store.ts b/packages/transport-circuit-relay-v2/src/server/reservation-store.ts index f5873d838b..293aa21bf8 100644 --- a/packages/transport-circuit-relay-v2/src/server/reservation-store.ts +++ b/packages/transport-circuit-relay-v2/src/server/reservation-store.ts @@ -1,5 +1,6 @@ import { PeerMap } from '@libp2p/peer-collections' -import { DEFAULT_DATA_LIMIT, DEFAULT_DURATION_LIMIT, DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL, DEFAULT_MAX_RESERVATION_STORE_SIZE, DEFAULT_MAX_RESERVATION_TTL } from '../constants.js' +import { object, mixed, number, boolean } from 'yup' +import { DEFAULT_DATA_LIMIT, DEFAULT_DEFAULT_APPLY_LIMIT, DEFAULT_DURATION_LIMIT, DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL, DEFAULT_MAX_RESERVATION_STORE_SIZE, DEFAULT_MAX_RESERVATION_TTL } from '../constants.js' import { type Limit, Status } from '../pb/index.js' import type { RelayReservation } from '../index.js' import type { RecursivePartial, PeerId, Startable } from '@libp2p/interface' @@ -36,6 +37,14 @@ export interface ReservationStoreInit { export type ReservationStoreOptions = RecursivePartial +export const reservationStoreConfigValidator = object({ + maxReservations: number().min(0).integer().default(DEFAULT_MAX_RESERVATION_STORE_SIZE), + reservationClearInterval: number().integer().min(0).default(DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL), + applyDefaultLimit: boolean().default(DEFAULT_DEFAULT_APPLY_LIMIT), + reservationTtl: number().integer().min(0).default(DEFAULT_MAX_RESERVATION_TTL), + defaultDurationLimit: number().integer().min(0).default(DEFAULT_DURATION_LIMIT), + defaultDataLimit: mixed().test('is-bigint', 'Invalid bigint', value => typeof value === 'bigint').default(DEFAULT_DATA_LIMIT) +}) export class ReservationStore implements Startable { public readonly reservations = new PeerMap() private _started = false @@ -48,12 +57,14 @@ export class ReservationStore implements Startable { private readonly defaultDataLimit: bigint constructor (options: ReservationStoreOptions = {}) { - this.maxReservations = options.maxReservations ?? DEFAULT_MAX_RESERVATION_STORE_SIZE - this.reservationClearInterval = options.reservationClearInterval ?? DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL - this.applyDefaultLimit = options.applyDefaultLimit !== false - this.reservationTtl = options.reservationTtl ?? DEFAULT_MAX_RESERVATION_TTL - this.defaultDurationLimit = options.defaultDurationLimit ?? DEFAULT_DURATION_LIMIT - this.defaultDataLimit = options.defaultDataLimit ?? DEFAULT_DATA_LIMIT + const config = reservationStoreConfigValidator.validateSync(options) + + this.maxReservations = config.maxReservations + this.reservationClearInterval = config.reservationClearInterval + this.applyDefaultLimit = config.applyDefaultLimit + this.reservationTtl = config.reservationTtl + this.defaultDurationLimit = config.defaultDurationLimit + this.defaultDataLimit = config.defaultDataLimit } isStarted (): boolean { diff --git a/packages/transport-circuit-relay-v2/src/transport/reservation-store.ts b/packages/transport-circuit-relay-v2/src/transport/reservation-store.ts index 4557c85301..414c56370b 100644 --- a/packages/transport-circuit-relay-v2/src/transport/reservation-store.ts +++ b/packages/transport-circuit-relay-v2/src/transport/reservation-store.ts @@ -4,7 +4,8 @@ import { PeerQueue } from '@libp2p/utils/peer-queue' import { multiaddr } from '@multiformats/multiaddr' import { pbStream } from 'it-protobuf-stream' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { DEFAULT_RESERVATION_CONCURRENCY, RELAY_TAG, RELAY_V2_HOP_CODEC } from '../constants.js' +import { number, object } from 'yup' +import { DEFAULT_DISCOVER_RELAYS, DEFAULT_MAX_RESERVATION_QUEUE_LENGTH, DEFAULT_RESERVATION_COMPLETION_TIMEOUT, DEFAULT_RESERVATION_CONCURRENCY, RELAY_TAG, RELAY_V2_HOP_CODEC } from '../constants.js' import { HopMessage, Status } from '../pb/index.js' import { getExpirationMilliseconds } from '../utils.js' import type { Reservation } from '../pb/index.js' @@ -71,6 +72,13 @@ export interface ReservationStoreEvents { 'relay:removed': CustomEvent } +const configValidator = object({ + discoverRelays: number().integer().min(0).default(DEFAULT_DISCOVER_RELAYS), + maxReservationQueueLength: number().integer().min(0).default(DEFAULT_MAX_RESERVATION_QUEUE_LENGTH), + reservationCompletionTimeout: number().integer().min(0).default(DEFAULT_RESERVATION_COMPLETION_TIMEOUT), + reservationConcurrency: number().integer().min(0).default(DEFAULT_RESERVATION_CONCURRENCY) +}) + export class ReservationStore extends TypedEventEmitter implements Startable { private readonly peerId: PeerId private readonly connectionManager: ConnectionManager @@ -95,9 +103,12 @@ export class ReservationStore extends TypedEventEmitter this.peerStore = components.peerStore this.events = components.events this.reservations = new PeerMap() - this.maxDiscoveredRelays = init?.discoverRelays ?? 0 - this.maxReservationQueueLength = init?.maxReservationQueueLength ?? 100 - this.reservationCompletionTimeout = init?.reservationCompletionTimeout ?? 10000 + + const config = configValidator.validateSync(init) + + this.maxDiscoveredRelays = config.discoverRelays + this.maxReservationQueueLength = config.maxReservationQueueLength + this.reservationCompletionTimeout = config.reservationCompletionTimeout this.started = false // ensure we don't listen on multiple relays simultaneously diff --git a/packages/transport-circuit-relay-v2/src/transport/transport.ts b/packages/transport-circuit-relay-v2/src/transport/transport.ts index b45d92ca84..2956ce2aa7 100644 --- a/packages/transport-circuit-relay-v2/src/transport/transport.ts +++ b/packages/transport-circuit-relay-v2/src/transport/transport.ts @@ -5,7 +5,8 @@ import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn' import * as mafmt from '@multiformats/mafmt' import { multiaddr } from '@multiformats/multiaddr' import { pbStream } from 'it-protobuf-stream' -import { CIRCUIT_PROTO_CODE, ERR_HOP_REQUEST_FAILED, ERR_RELAYED_DIAL, MAX_CONNECTIONS, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js' +import { object, number } from 'yup' +import { CIRCUIT_PROTO_CODE, DEFAULT_DISCOVER_RELAYS, DEFAULT_STOP_TIMEOUT, ERR_HOP_REQUEST_FAILED, ERR_RELAYED_DIAL, MAX_CONNECTIONS, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js' import { StopMessage, HopMessage, Status } from '../pb/index.js' import { RelayDiscovery } from './discovery.js' import { createListener } from './listener.js' @@ -38,11 +39,12 @@ interface ConnectOptions { disconnectOnFailure: boolean } -const defaults = { - maxInboundStopStreams: MAX_CONNECTIONS, - maxOutboundStopStreams: MAX_CONNECTIONS, - stopTimeout: 30000 -} +const configValidator = object({ + discoverRelays: number().min(0).integer().default(DEFAULT_DISCOVER_RELAYS), + maxInboundStopStreams: number().min(0).integer().default(MAX_CONNECTIONS), + maxOutboundStopStreams: number().min(0).integer().default(MAX_CONNECTIONS), + stopTimeout: number().min(0).integer().default(DEFAULT_STOP_TIMEOUT) +}) export class CircuitRelayTransport implements Transport { private readonly discovery?: RelayDiscovery @@ -63,6 +65,8 @@ export class CircuitRelayTransport implements Transport { private readonly log: Logger constructor (components: CircuitRelayTransportComponents, init: CircuitRelayTransportInit) { + const config = configValidator.validateSync(init) + this.log = components.logger.forComponent('libp2p:circuit-relay:transport') this.registrar = components.registrar this.peerStore = components.peerStore @@ -73,11 +77,11 @@ export class CircuitRelayTransport implements Transport { this.upgrader = components.upgrader this.addressManager = components.addressManager this.connectionGater = components.connectionGater - this.maxInboundStopStreams = init.maxInboundStopStreams ?? defaults.maxInboundStopStreams - this.maxOutboundStopStreams = init.maxOutboundStopStreams ?? defaults.maxOutboundStopStreams - this.stopTimeout = init.stopTimeout ?? defaults.stopTimeout + this.maxInboundStopStreams = config.maxInboundStopStreams + this.maxOutboundStopStreams = config.maxOutboundStopStreams + this.stopTimeout = config.stopTimeout - if (init.discoverRelays != null && init.discoverRelays > 0) { + if (config.discoverRelays > 0) { this.discovery = new RelayDiscovery(components) this.discovery.addEventListener('relay:discover', (evt) => { this.reservationStore.addRelay(evt.detail, 'discovered') diff --git a/packages/transport-webrtc/.aegir.js b/packages/transport-webrtc/.aegir.js index 02e55efc8a..976fbbb7f1 100644 --- a/packages/transport-webrtc/.aegir.js +++ b/packages/transport-webrtc/.aegir.js @@ -1,4 +1,3 @@ - /** @type {import('aegir').PartialOptions} */ export default { build: { @@ -31,13 +30,13 @@ export default { services: { relay: circuitRelayServer({ reservations: { - maxReservations: Infinity + maxReservations: 10000 } }) }, connectionManager: { minConnections: 0, - inboundConnectionThreshold: Infinity + inboundConnectionThreshold: 1000 } }) diff --git a/packages/upnp-nat/package.json b/packages/upnp-nat/package.json index 407e65b48d..ea573d38ab 100644 --- a/packages/upnp-nat/package.json +++ b/packages/upnp-nat/package.json @@ -55,7 +55,8 @@ "@libp2p/utils": "^5.2.1", "@multiformats/multiaddr": "^12.1.10", "private-ip": "^3.0.1", - "wherearewe": "^2.0.1" + "wherearewe": "^2.0.1", + "yup": "^1.3.2" }, "devDependencies": { "@libp2p/logger": "^4.0.4", diff --git a/packages/upnp-nat/src/upnp-nat.ts b/packages/upnp-nat/src/upnp-nat.ts index 9c40fbb531..93ba2b96b5 100644 --- a/packages/upnp-nat/src/upnp-nat.ts +++ b/packages/upnp-nat/src/upnp-nat.ts @@ -4,15 +4,29 @@ import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' import { fromNodeAddress } from '@multiformats/multiaddr' import isPrivateIp from 'private-ip' import { isBrowser } from 'wherearewe' +import { boolean, number, object, string } from 'yup' import type { UPnPNATComponents, UPnPNATInit } from './index.js' import type { Logger, Startable } from '@libp2p/interface' const DEFAULT_TTL = 7200 +const DEFAULT_KEEP_ALIVE = true + function highPort (min = 1024, max = 65535): number { return Math.floor(Math.random() * (max - min + 1) + min) } +const validIPRegex = /^(?:(?:^|\.)(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])){4}$/ + +const configValidator = object({ + externalAddress: string().matches(validIPRegex, 'Invalid IP address'), + localAddress: string().matches(validIPRegex, 'Invalid IP address'), + description: string(), + ttl: number().integer().min(0).default(DEFAULT_TTL), + keepAlive: boolean().default(DEFAULT_KEEP_ALIVE), + gateway: string().optional() +}) + export class UPnPNAT implements Startable { private readonly components: UPnPNATComponents private readonly externalAddress?: string @@ -27,15 +41,17 @@ export class UPnPNAT implements Startable { constructor (components: UPnPNATComponents, init: UPnPNATInit) { this.components = components + this.started = false + const config = configValidator.validateSync(init) this.log = components.logger.forComponent('libp2p:upnp-nat') - this.started = false - this.externalAddress = init.externalAddress - this.localAddress = init.localAddress - this.description = init.description ?? `${components.nodeInfo.name}@${components.nodeInfo.version} ${this.components.peerId.toString()}` - this.ttl = init.ttl ?? DEFAULT_TTL - this.keepAlive = init.keepAlive ?? true - this.gateway = init.gateway + + this.externalAddress = config.externalAddress + this.localAddress = config.localAddress + this.description = config.description ?? `${components.nodeInfo.name}@${components.nodeInfo.version} ${this.components.peerId.toString()}` + this.keepAlive = config.keepAlive + this.gateway = config.gateway + this.ttl = config.ttl if (this.ttl < DEFAULT_TTL) { throw new CodeError(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`, ERR_INVALID_PARAMETERS)