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<typeof setTimeout> | undefined
 
@@ -11,3 +15,12 @@ export function debounce (func: () => void, wait: number): () => void {
     timeout = setTimeout(later, wait)
   }
 }
+
+export function validateAddressManagerConfig (): ObjectSchema<Record<string, unknown>> {
+  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<Libp2pInit> = {
-  addresses: {
-    listen: [],
-    announce: [],
-    noAnnounce: [],
-    announceFilter: (multiaddrs: Multiaddr[]) => multiaddrs
-  },
-  connectionManager: {
-    resolvers: {
-      dnsaddr: dnsaddrResolver
-    },
-    addressSorter: defaultAddressSort
-  },
-  transportManager: {
-    faultTolerance: FaultTolerance.FATAL_ALL
-  }
-}
-
-export function validateConfig <T extends ServiceMap = Record<string, unknown>> (opts: RecursivePartial<Libp2pInit<T>>): Libp2pInit<T> {
-  const resultingOptions: Libp2pInit<T> = 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<Libp2pInit> = {
+  connectionManager: {
+    resolvers: {
+      dnsaddr: dnsaddrResolver
+    },
+    addressSorter: defaultAddressSort
+  },
+  transportManager: {
+    faultTolerance: FaultTolerance.FATAL_ALL
+  }
+}
+
+export function validateConfig<T extends ServiceMap = Record<string, unknown>> (opts: RecursivePartial<Libp2pInit<T>>): Libp2pInit<T> {
+  const libp2pConfig = object({
+    addresses: validateAddressManagerConfig(),
+    connectionManager: validateConnectionManagerConfig(opts?.connectionManager as ConnectionManagerInit)
+  })
+
+  const parsedOpts = libp2pConfig.validateSync(opts)
+
+  const resultingOptions: Libp2pInit<T> = 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<string | undefined> | 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<Record<string, unknown>> => {
+  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<Message.DialResponse | undefined> => {
-        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<string, LookupFunction>
+  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<void> {
@@ -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<void> {
     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<Libp2pEvents>
   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<void> {
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<Error>
 }
 
+const configValidator = object({
+  bootDelay: number().integer().min(0).default(DEFAULT_ADVERT_BOOT_DELAY)
+})
+
 export class AdvertService extends TypedEventEmitter<AdvertServiceEvents> implements Startable {
   private readonly contentRouting: ContentRouting
   private timeout?: any
@@ -43,8 +48,11 @@ export class AdvertService extends TypedEventEmitter<AdvertServiceEvents> 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<Error>
 }
 
-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<RelayServerEvents> implements Startable, CircuitRelayService {
   private readonly registrar: Registrar
@@ -113,6 +120,8 @@ class CircuitRelayServer extends TypedEventEmitter<RelayServerEvents> 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<RelayServerEvents> 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<ReservationStoreInit>
 
+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<bigint>().test('is-bigint', 'Invalid bigint', value => typeof value === 'bigint').default(DEFAULT_DATA_LIMIT)
+})
 export class ReservationStore implements Startable {
   public readonly reservations = new PeerMap<RelayReservation>()
   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<PeerId>
 }
 
+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<ReservationStoreEvents> implements Startable {
   private readonly peerId: PeerId
   private readonly connectionManager: ConnectionManager
@@ -95,9 +103,12 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
     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)