diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 44b92c7181..a5a6e3f645 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{"packages/auto-tls":"0.0.0","packages/connection-encrypter-plaintext":"2.0.11","packages/connection-encrypter-tls":"2.0.11","packages/crypto":"5.0.7","packages/interface":"2.2.1","packages/interface-compliance-tests":"6.1.11","packages/interface-internal":"2.1.1","packages/kad-dht":"14.1.3","packages/keychain":"5.0.10","packages/libp2p":"2.3.1","packages/logger":"5.1.4","packages/metrics-devtools":"1.1.10","packages/metrics-prometheus":"4.2.7","packages/metrics-simple":"1.2.7","packages/multistream-select":"6.0.9","packages/peer-collections":"6.0.12","packages/peer-discovery-bootstrap":"11.0.13","packages/peer-discovery-mdns":"11.0.13","packages/peer-id":"5.0.8","packages/peer-record":"8.0.12","packages/peer-store":"11.0.12","packages/pnet":"2.0.13","packages/protocol-autonat":"2.0.12","packages/protocol-dcutr":"2.0.12","packages/protocol-echo":"2.1.3","packages/protocol-fetch":"2.0.12","packages/protocol-identify":"3.0.12","packages/protocol-perf":"4.0.13","packages/protocol-ping":"2.0.12","packages/pubsub":"10.0.12","packages/pubsub-floodsub":"10.1.11","packages/record":"4.0.4","packages/stream-multiplexer-mplex":"11.0.13","packages/transport-circuit-relay-v2":"3.1.3","packages/transport-memory":"1.0.1","packages/transport-tcp":"10.0.13","packages/transport-webrtc":"5.0.19","packages/transport-websockets":"9.0.13","packages/transport-webtransport":"5.0.18","packages/upnp-nat":"2.0.12","packages/utils":"6.2.1"} +{"packages/auto-tls":"1.0.1","packages/connection-encrypter-plaintext":"2.0.12","packages/connection-encrypter-tls":"2.0.12","packages/crypto":"5.0.8","packages/interface":"2.3.0","packages/interface-compliance-tests":"6.2.1","packages/interface-internal":"2.2.1","packages/kad-dht":"14.1.5","packages/keychain":"5.0.11","packages/libp2p":"2.4.1","packages/logger":"5.1.5","packages/metrics-devtools":"1.1.12","packages/metrics-prometheus":"4.2.9","packages/metrics-simple":"1.2.8","packages/multistream-select":"6.0.10","packages/peer-collections":"6.0.13","packages/peer-discovery-bootstrap":"11.0.15","packages/peer-discovery-mdns":"11.0.15","packages/peer-id":"5.0.9","packages/peer-record":"8.0.13","packages/peer-store":"11.0.13","packages/pnet":"2.0.15","packages/protocol-autonat":"2.0.14","packages/protocol-dcutr":"2.0.14","packages/protocol-echo":"2.1.5","packages/protocol-fetch":"2.0.14","packages/protocol-identify":"3.0.14","packages/protocol-perf":"4.0.15","packages/protocol-ping":"2.0.14","packages/pubsub":"10.0.14","packages/pubsub-floodsub":"10.1.13","packages/record":"4.0.4","packages/stream-multiplexer-mplex":"11.0.15","packages/transport-circuit-relay-v2":"3.1.5","packages/transport-memory":"1.0.2","packages/transport-tcp":"10.0.14","packages/transport-webrtc":"5.0.21","packages/transport-websockets":"9.1.0","packages/transport-webtransport":"5.0.20","packages/upnp-nat":"3.0.2","packages/utils":"6.3.0"} diff --git a/packages/auto-tls/CHANGELOG.md b/packages/auto-tls/CHANGELOG.md new file mode 100644 index 0000000000..12463e11d6 --- /dev/null +++ b/packages/auto-tls/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +## [1.0.1](https://github.com/libp2p/js-libp2p/compare/auto-tls-v1.0.0...auto-tls-v1.0.1) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## 1.0.0 (2024-12-09) + + +### Features + +* add auto-confirm option to auto-tls ([#2875](https://github.com/libp2p/js-libp2p/issues/2875)) ([2625cc3](https://github.com/libp2p/js-libp2p/commit/2625cc323b77ed4843d200a3b7022f80eba2e8f8)) +* add auto-tls service ([#2798](https://github.com/libp2p/js-libp2p/issues/2798)) ([d866eb5](https://github.com/libp2p/js-libp2p/commit/d866eb5bb8269485364c233119331ca073ff1343)) + + +### Bug Fixes + +* add retries to certificate provisioning ([#2841](https://github.com/libp2p/js-libp2p/issues/2841)) ([98b4304](https://github.com/libp2p/js-libp2p/commit/98b43045cb4786defc74e21c637489109377ea35)) +* require external confirmation of public addresses ([#2867](https://github.com/libp2p/js-libp2p/issues/2867)) ([d19974d](https://github.com/libp2p/js-libp2p/commit/d19974d93a1015acfca95c2155dbcffc5fd6a6c0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/keychain bumped from ^5.0.10 to ^5.0.11 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 diff --git a/packages/auto-tls/package.json b/packages/auto-tls/package.json index 1884e0a7e4..deb8cb1480 100644 --- a/packages/auto-tls/package.json +++ b/packages/auto-tls/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/auto-tls", - "version": "0.0.0", + "version": "1.0.1", "description": "Automatically acquire a .libp2p.direct TLS certificate", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/auto-tls#readme", @@ -47,12 +47,12 @@ }, "dependencies": { "@chainsafe/is-ip": "^2.0.2", - "@libp2p/crypto": "^5.0.7", + "@libp2p/crypto": "^5.0.8", "@libp2p/http-fetch": "^2.1.0", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/keychain": "^5.0.10", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/keychain": "^5.0.11", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "@peculiar/x509": "^1.12.3", @@ -64,8 +64,8 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-id": "^5.0.9", "aegir": "^45.0.5", "datastore-core": "^10.0.2", "p-event": "^6.0.1", diff --git a/packages/auto-tls/src/auto-tls.ts b/packages/auto-tls/src/auto-tls.ts index c15f539c8a..357aadfe19 100644 --- a/packages/auto-tls/src/auto-tls.ts +++ b/packages/auto-tls/src/auto-tls.ts @@ -10,7 +10,7 @@ import { base36 } from 'multiformats/bases/base36' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { DEFAULT_ACCOUNT_PRIVATE_KEY_BITS, DEFAULT_ACCOUNT_PRIVATE_KEY_NAME, DEFAULT_ACME_DIRECTORY, DEFAULT_CERTIFICATE_DATASTORE_KEY, DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS, DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME, DEFAULT_FORGE_DOMAIN, DEFAULT_FORGE_ENDPOINT, DEFAULT_PROVISION_DELAY, DEFAULT_PROVISION_REQUEST_TIMEOUT, DEFAULT_PROVISION_TIMEOUT, DEFAULT_RENEWAL_THRESHOLD } from './constants.js' +import { DEFAULT_ACCOUNT_PRIVATE_KEY_BITS, DEFAULT_ACCOUNT_PRIVATE_KEY_NAME, DEFAULT_ACME_DIRECTORY, DEFAULT_AUTO_CONFIRM_ADDRESS, DEFAULT_CERTIFICATE_DATASTORE_KEY, DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS, DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME, DEFAULT_FORGE_DOMAIN, DEFAULT_FORGE_ENDPOINT, DEFAULT_PROVISION_DELAY, DEFAULT_PROVISION_REQUEST_TIMEOUT, DEFAULT_PROVISION_TIMEOUT, DEFAULT_RENEWAL_THRESHOLD } from './constants.js' import { DomainMapper } from './domain-mapper.js' import { createCsr, importFromPem, loadOrCreateKey, supportedAddressesFilter } from './utils.js' import type { AutoTLSComponents, AutoTLSInit, AutoTLS as AutoTLSInterface } from './index.js' @@ -60,9 +60,10 @@ export class AutoTLS implements AutoTLSInterface { private readonly email private readonly domain private readonly domainMapper: DomainMapper + private readonly autoConfirmAddress: boolean constructor (components: AutoTLSComponents, init: AutoTLSInit = {}) { - this.log = components.logger.forComponent('libp2p:certificate-manager') + this.log = components.logger.forComponent('libp2p:auto-tls') this.addressManager = components.addressManager this.privateKey = components.privateKey this.peerId = components.peerId @@ -80,6 +81,7 @@ export class AutoTLS implements AutoTLSInterface { this.certificatePrivateKeyName = init.certificatePrivateKeyName ?? DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME this.certificatePrivateKeyBits = init.certificatePrivateKeyBits ?? DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS this.certificateDatastoreKey = init.certificateDatastoreKey ?? DEFAULT_CERTIFICATE_DATASTORE_KEY + this.autoConfirmAddress = init.autoConfirmAddress ?? DEFAULT_AUTO_CONFIRM_ADDRESS this.clientAuth = new ClientAuth(this.privateKey) this.started = false this.fetching = false @@ -100,10 +102,16 @@ export class AutoTLS implements AutoTLSInterface { ] get [serviceDependencies] (): string[] { - return [ + const dependencies = [ '@libp2p/identify', '@libp2p/keychain' ] + + if (!this.autoConfirmAddress) { + dependencies.push('@libp2p/autonat') + } + + return dependencies } async start (): Promise { @@ -346,8 +354,8 @@ export class AutoTLS implements AutoTLSInterface { 'Content-Type': 'application/json' }, body: JSON.stringify({ - value: keyAuthorization, - addresses + Value: keyAuthorization, + Addresses: addresses }), ...options }) diff --git a/packages/auto-tls/src/constants.ts b/packages/auto-tls/src/constants.ts index 8ef00fe699..33ffbddc43 100644 --- a/packages/auto-tls/src/constants.ts +++ b/packages/auto-tls/src/constants.ts @@ -2,7 +2,7 @@ export const DEFAULT_FORGE_ENDPOINT = 'https://registration.libp2p.direct' export const DEFAULT_FORGE_DOMAIN = 'libp2p.direct' export const DEFAULT_ACME_DIRECTORY = 'https://acme-v02.api.letsencrypt.org/directory' export const DEFAULT_PROVISION_TIMEOUT = 120_000 -export const DEFAULT_PROVISION_REQUEST_TIMEOUT = 10_000 +export const DEFAULT_PROVISION_REQUEST_TIMEOUT = 60_000 export const DEFAULT_PROVISION_DELAY = 5_000 export const DEFAULT_RENEWAL_THRESHOLD = 86_400_000 export const DEFAULT_ACCOUNT_PRIVATE_KEY_NAME = 'auto-tls-acme-account-private-key' @@ -10,3 +10,4 @@ export const DEFAULT_ACCOUNT_PRIVATE_KEY_BITS = 2048 export const DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME = 'auto-tls-certificate-private-key' export const DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS = 2048 export const DEFAULT_CERTIFICATE_DATASTORE_KEY = '/libp2p/auto-tls/certificate' +export const DEFAULT_AUTO_CONFIRM_ADDRESS = false diff --git a/packages/auto-tls/src/domain-mapper.ts b/packages/auto-tls/src/domain-mapper.ts index 1c87747528..b7b92246db 100644 --- a/packages/auto-tls/src/domain-mapper.ts +++ b/packages/auto-tls/src/domain-mapper.ts @@ -1,8 +1,11 @@ import { isIPv4, isIPv6 } from '@chainsafe/is-ip' +import { multiaddr } from '@multiformats/multiaddr' import { getPublicIps } from './utils.js' import type { ComponentLogger, Libp2pEvents, Logger, TypedEventTarget } from '@libp2p/interface' import type { AddressManager } from '@libp2p/interface-internal' +const MAX_DATE = 8_640_000_000_000_000 + export interface DomainMapperComponents { logger: ComponentLogger events: TypedEventTarget @@ -11,6 +14,7 @@ export interface DomainMapperComponents { export interface DomainMapperInit { domain: string + autoConfirmAddress?: boolean } export class DomainMapper { @@ -19,13 +23,15 @@ export class DomainMapper { private readonly events: TypedEventTarget private readonly mappedAddresses: Set private readonly domain: string + private readonly autoConfirmAddress: boolean private hasCertificate: boolean constructor (components: DomainMapperComponents, init: DomainMapperInit) { - this.log = components.logger.forComponent('libp2p:certificate-manager:domain-mapper') + this.log = components.logger.forComponent('libp2p:auto-tls:domain-mapper') this.addressManager = components.addressManager this.events = components.events this.domain = init.domain + this.autoConfirmAddress = init.autoConfirmAddress ?? false this.mappedAddresses = new Set() this.hasCertificate = false @@ -58,7 +64,10 @@ export class DomainMapper { } updateMappings (): void { - const publicIps = getPublicIps(this.addressManager.getAddresses()) + const publicIps = getPublicIps( + this.addressManager.getAddressesWithMetadata() + .map(({ multiaddr }) => multiaddr) + ) // did our public IPs change? const addedIp4 = [] @@ -113,6 +122,14 @@ export class DomainMapper { this.log.trace('mapping IP %s to domain %s', ip, domain) this.addressManager.addDNSMapping(domain, [ip]) this.mappedAddresses.add(ip) + + if (this.autoConfirmAddress) { + const ma = multiaddr(`/dns4/${domain}`) + this.log('auto-confirming IP address %a', ma) + this.addressManager.confirmObservedAddr(ma, { + ttl: MAX_DATE - Date.now() + }) + } }) addedIp6.forEach(ip => { @@ -120,6 +137,14 @@ export class DomainMapper { this.log.trace('mapping IP %s to domain %s', ip, domain) this.addressManager.addDNSMapping(domain, [ip]) this.mappedAddresses.add(ip) + + if (this.autoConfirmAddress) { + const ma = multiaddr(`/dns6/${domain}`) + this.log('auto-confirming IP address %a', ma) + this.addressManager.confirmObservedAddr(ma, { + ttl: MAX_DATE - Date.now() + }) + } }) } diff --git a/packages/auto-tls/src/index.ts b/packages/auto-tls/src/index.ts index 0bf440c27d..3d07ce27ae 100644 --- a/packages/auto-tls/src/index.ts +++ b/packages/auto-tls/src/index.ts @@ -112,7 +112,7 @@ export interface AutoTLSInit { * How long asking the forge endpoint to answer a DNS challenge can take * before we retry * - * @default 10_000 + * @default 60_000 */ provisionRequestTimeout?: number @@ -168,6 +168,17 @@ export interface AutoTLSInit { * @default 2048 */ certificatePrivateKeyBits?: number + + /** + * Any mapped addresses are added to the observed address list. These + * addresses require additional verification by the `@libp2p/autonat` protocol + * or similar before they are trusted. + * + * To skip this verification and trust them immediately pass `true` here + * + * @default false + */ + autoConfirmAddress?: boolean } export interface AutoTLS { diff --git a/packages/auto-tls/test/domain-mapper.spec.ts b/packages/auto-tls/test/domain-mapper.spec.ts index 0e8c246046..29335d9060 100644 --- a/packages/auto-tls/test/domain-mapper.spec.ts +++ b/packages/auto-tls/test/domain-mapper.spec.ts @@ -42,13 +42,32 @@ describe('domain-mapper', () => { const ip4 = '81.12.12.9' const ip6 = '2001:4860:4860::8889' - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr('/dns4/example.com/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr(`/ip4/${ip4}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), - multiaddr(`/ip6/${ip6}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`) - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/dns4/example.com/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr(`/ip4/${ip4}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }, { + multiaddr: multiaddr(`/ip6/${ip6}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }]) components.events.safeDispatchEvent('certificate:provision', { detail: { @@ -65,17 +84,77 @@ describe('domain-mapper', () => { ])).to.be.true() }) + it('should auto-confirm DNS mapping', async () => { + await stop(mapper) + mapper = new DomainMapper(components, { + domain: 'example.com', + autoConfirmAddress: true + }) + await start(mapper) + + const ip4 = '81.12.12.9' + const domain = '81-12-12-9.example.com' + + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr(`/ip4/${ip4}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }]) + + components.events.safeDispatchEvent('certificate:provision', { + detail: { + key: importFromPem(PRIVATE_KEY_PEM), + cert: CERT + } + }) + + expect(components.addressManager.addDNSMapping.calledWith(domain, [ + ip4 + ])).to.be.true() + expect(components.addressManager.confirmObservedAddr.calledWith(multiaddr(`/dns4/${domain}`))).to.be.true() + }) + it('should update domain mapping on self peer update', () => { const ip4v1 = '81.12.12.9' const ip6v1 = '2001:4860:4860::8889' - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr('/dns4/example.com/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr(`/ip4/${ip4v1}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), - multiaddr(`/ip6/${ip6v1}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`) - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/dns4/example.com/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr(`/ip4/${ip4v1}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }, { + multiaddr: multiaddr(`/ip6/${ip6v1}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }]) components.events.safeDispatchEvent('certificate:provision', { detail: { @@ -94,13 +173,32 @@ describe('domain-mapper', () => { const ip4v2 = '81.12.12.10' const ip6v2 = '2001:4860:4860::8890' - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr('/dns4/example.com/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr(`/ip4/${ip4v2}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), - multiaddr(`/ip6/${ip6v2}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`) - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/dns4/example.com/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr(`/ip4/${ip4v2}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }, { + multiaddr: multiaddr(`/ip6/${ip6v2}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }]) components.events.safeDispatchEvent('self:peer:update', { detail: stubInterface() @@ -121,13 +219,32 @@ describe('domain-mapper', () => { const ip4 = '81.12.12.9' const ip6 = '2001:4860:4860::8889' - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr('/dns4/example.com/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), - multiaddr(`/ip4/${ip4}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), - multiaddr(`/ip6/${ip6}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`) - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/dns4/example.com/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr(`/ip4/${ip4}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }, { + multiaddr: multiaddr(`/ip6/${ip6}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }]) components.events.safeDispatchEvent('self:peer:update', { detail: stubInterface() diff --git a/packages/auto-tls/test/index.spec.ts b/packages/auto-tls/test/index.spec.ts index 4c3eb57b22..40c5429ef5 100644 --- a/packages/auto-tls/test/index.spec.ts +++ b/packages/auto-tls/test/index.spec.ts @@ -16,7 +16,7 @@ import { DEFAULT_CERTIFICATE_DATASTORE_KEY, DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME import { importFromPem } from '../src/utils.js' import { CERT, CERT_FOR_OTHER_KEY, EXPIRED_CERT, INVALID_CERT, PRIVATE_KEY_PEM } from './fixtures/cert.js' import type { ComponentLogger, Libp2pEvents, Peer, PeerId, PrivateKey, RSAPrivateKey, TypedEventTarget } from '@libp2p/interface' -import type { AddressManager } from '@libp2p/interface-internal' +import type { AddressManager, NodeAddress } from '@libp2p/interface-internal' import type { Keychain } from '@libp2p/keychain' import type { StubbedInstance } from 'sinon-ts' @@ -49,12 +49,26 @@ describe('auto-tls', () => { datastore: new MemoryDatastore() } - // mixture of LAN and public addresses - components.addressManager.getAddresses.returns([ - multiaddr(`/ip4/127.0.0.1/tcp/1235/p2p/${components.peerId}`), - multiaddr(`/ip4/192.168.0.100/tcp/1235/p2p/${components.peerId}`), - multiaddr(`/ip4/82.32.57.46/tcp/2345/p2p/${components.peerId}`) - ]) + // a mixture of LAN and public addresses + const addresses: NodeAddress[] = [{ + multiaddr: multiaddr(`/ip4/127.0.0.1/tcp/1235/p2p/${components.peerId}`), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr(`/ip4/192.168.0.100/tcp/1235/p2p/${components.peerId}`), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr(`/ip4/82.32.57.46/tcp/2345/p2p/${components.peerId}`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }] + + components.addressManager.getAddressesWithMetadata.returns(addresses) + components.addressManager.getAddresses.returns(addresses.map(({ multiaddr }) => multiaddr)) }) afterEach(async () => { diff --git a/packages/connection-encrypter-plaintext/CHANGELOG.md b/packages/connection-encrypter-plaintext/CHANGELOG.md index 2b96b22151..ca510c005f 100644 --- a/packages/connection-encrypter-plaintext/CHANGELOG.md +++ b/packages/connection-encrypter-plaintext/CHANGELOG.md @@ -72,6 +72,20 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [2.0.12](https://github.com/libp2p/js-libp2p/compare/plaintext-v2.0.11...plaintext-v2.0.12) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [2.0.11](https://github.com/libp2p/js-libp2p/compare/plaintext-v2.0.10...plaintext-v2.0.11) (2024-11-18) diff --git a/packages/connection-encrypter-plaintext/package.json b/packages/connection-encrypter-plaintext/package.json index 3092784b00..1130ff6ff0 100644 --- a/packages/connection-encrypter-plaintext/package.json +++ b/packages/connection-encrypter-plaintext/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/plaintext", - "version": "2.0.11", + "version": "2.0.12", "description": "An insecure connection encrypter", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/connection-encrypter-plaintext#readme", @@ -52,9 +52,9 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/peer-id": "^5.0.9", "it-protobuf-stream": "^1.1.5", "it-stream-types": "^2.0.2", "protons-runtime": "^5.5.0", @@ -62,8 +62,8 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/logger": "^5.1.4", + "@libp2p/crypto": "^5.0.8", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "it-pair": "^2.0.6", "protons": "^7.6.0", diff --git a/packages/connection-encrypter-tls/CHANGELOG.md b/packages/connection-encrypter-tls/CHANGELOG.md index f2a078899b..ad7cdcebd8 100644 --- a/packages/connection-encrypter-tls/CHANGELOG.md +++ b/packages/connection-encrypter-tls/CHANGELOG.md @@ -6,6 +6,19 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [2.0.12](https://github.com/libp2p/js-libp2p/compare/tls-v2.0.11...tls-v2.0.12) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [2.0.11](https://github.com/libp2p/js-libp2p/compare/tls-v2.0.10...tls-v2.0.11) (2024-11-18) diff --git a/packages/connection-encrypter-tls/package.json b/packages/connection-encrypter-tls/package.json index de58da6567..9de25c8432 100644 --- a/packages/connection-encrypter-tls/package.json +++ b/packages/connection-encrypter-tls/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/tls", - "version": "2.0.11", + "version": "2.0.12", "description": "A connection encrypter that uses TLS 1.3", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/connection-encrypter-tls#readme", @@ -48,9 +48,9 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/peer-id": "^5.0.9", "@peculiar/asn1-schema": "^2.3.13", "@peculiar/asn1-x509": "^2.3.13", "@peculiar/webcrypto": "^1.5.0", @@ -63,7 +63,7 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "it-pair": "^2.0.6", "protons": "^7.6.0", diff --git a/packages/crypto/CHANGELOG.md b/packages/crypto/CHANGELOG.md index f82942db81..be35ff96dd 100644 --- a/packages/crypto/CHANGELOG.md +++ b/packages/crypto/CHANGELOG.md @@ -17,6 +17,16 @@ * dependencies * @libp2p/interface bumped from ^1.0.2 to ^1.1.0 +## [5.0.8](https://github.com/libp2p/js-libp2p/compare/crypto-v5.0.7...crypto-v5.0.8) (2024-12-09) + + +### Dependencies + +* bump @stablelib/ed25519 in /packages/crypto/benchmark/ed25519 ([#2855](https://github.com/libp2p/js-libp2p/issues/2855)) ([86a646b](https://github.com/libp2p/js-libp2p/commit/86a646b7fb0efb1c09fdbd068dc9a78653553fcf)) +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + ## [5.0.7](https://github.com/libp2p/js-libp2p/compare/crypto-v5.0.6...crypto-v5.0.7) (2024-11-18) diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 2e96a3b9fe..2cdfa35bb5 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/crypto", - "version": "5.0.7", + "version": "5.0.8", "description": "Crypto primitives for libp2p", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/crypto#readme", @@ -92,7 +92,7 @@ "generate": "protons ./src/keys/keys.proto" }, "dependencies": { - "@libp2p/interface": "^2.2.1", + "@libp2p/interface": "^2.3.0", "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "asn1js": "^3.0.5", diff --git a/packages/integration-tests/test/addresses.spec.ts b/packages/integration-tests/test/addresses.spec.ts deleted file mode 100644 index 26123516f1..0000000000 --- a/packages/integration-tests/test/addresses.spec.ts +++ /dev/null @@ -1,159 +0,0 @@ -/* eslint-env mocha */ - -import { memory } from '@libp2p/memory' -import { multiaddr } from '@multiformats/multiaddr' -import { expect } from 'aegir/chai' -import { createLibp2p } from 'libp2p' -import { pEvent } from 'p-event' -import type { Libp2p, PeerUpdate } from '@libp2p/interface' -import type { AddressManager } from '@libp2p/interface-internal' -import type { Multiaddr } from '@multiformats/multiaddr' - -const listenAddresses = ['/memory/address-1', '/memory/address-2'] -const announceAddresses = ['/dns4/peer.io/tcp/433/p2p/12D3KooWNvSZnPi3RrhrTwEY4LuuBeB6K6facKUCJcyWG1aoDd2p'] - -describe('addresses', () => { - let libp2p: Libp2p - - afterEach(async () => { - await libp2p?.stop() - }) - - it('should return transport listen addresses if announce addresses are not provided', async () => { - libp2p = await createLibp2p({ - addresses: { - listen: listenAddresses - }, - transports: [ - memory() - ] - }) - - expect(libp2p.getMultiaddrs().map(ma => ma.decapsulate('/p2p').toString())).to.deep.equal(listenAddresses) - }) - - it('should override listen addresses with announce addresses when provided', async () => { - libp2p = await createLibp2p({ - addresses: { - listen: listenAddresses, - announce: announceAddresses - }, - transports: [ - memory() - ] - }) - - expect(libp2p.getMultiaddrs().map(ma => ma.decapsulate('/p2p').toString())).to.deep.equal(announceAddresses) - }) - - it('should filter listen addresses filtered by the announce filter', async () => { - libp2p = await createLibp2p({ - addresses: { - listen: listenAddresses, - announceFilter: (multiaddrs: Multiaddr[]) => multiaddrs.slice(1) - }, - transports: [ - memory() - ] - }) - - expect(libp2p.getMultiaddrs().map(ma => ma.decapsulate('/p2p').toString())).to.deep.equal([listenAddresses[1]]) - }) - - it('should filter announce addresses filtered by the announce filter', async () => { - libp2p = await createLibp2p({ - addresses: { - listen: listenAddresses, - announce: announceAddresses, - announceFilter: () => [] - }, - transports: [ - memory() - ] - }) - - expect(libp2p.getMultiaddrs().map(ma => ma.decapsulate('/p2p').toString())).to.have.lengthOf(0) - }) - - it('should include observed addresses in returned multiaddrs', async () => { - const ma = '/ip4/83.32.123.53/tcp/43928' - - libp2p = await createLibp2p({ - start: false, - addresses: { - listen: listenAddresses - }, - transports: [ - memory() - ], - services: { - observer: (components: { addressManager: AddressManager }) => { - components.addressManager.confirmObservedAddr(multiaddr(ma)) - } - } - }) - - expect(libp2p.getMultiaddrs().map(ma => ma.decapsulate('/p2p').toString())).to.include(ma) - }) - - it('should update our peer record with announce addresses on startup', async () => { - libp2p = await createLibp2p({ - start: false, - addresses: { - listen: listenAddresses, - announce: announceAddresses - }, - transports: [ - memory() - ] - }) - - const eventPromise = pEvent<'self:peer:update', CustomEvent>(libp2p, 'self:peer:update', { - filter: (event) => { - return event.detail.peer.addresses.map(({ multiaddr }) => multiaddr.toString()) - .includes(announceAddresses[0]) - } - }) - - await libp2p.start() - - const event = await eventPromise - - expect(event.detail.peer.addresses.map(({ multiaddr }) => multiaddr.toString())) - .to.include.members(announceAddresses, 'peer info did not include announce addresses') - }) - - it('should only include confirmed observed addresses in peer record', async () => { - const unconfirmedAddress = '/ip4/127.0.0.1/tcp/4010/ws' - const confirmedAddress = '/ip4/127.0.0.1/tcp/4011/ws' - - libp2p = await createLibp2p({ - start: false, - addresses: { - listen: listenAddresses, - announce: announceAddresses - }, - transports: [ - memory() - ], - services: { - observer: (components: { addressManager: AddressManager }) => { - components.addressManager.confirmObservedAddr(multiaddr(confirmedAddress)) - components.addressManager.addObservedAddr(multiaddr(unconfirmedAddress)) - } - } - }) - - await libp2p.start() - - const eventPromise = pEvent<'self:peer:update', CustomEvent>(libp2p, 'self:peer:update') - - const event = await eventPromise - - expect(event.detail.peer.addresses.map(({ multiaddr }) => multiaddr.toString())) - .to.not.include(unconfirmedAddress, 'peer info included unconfirmed observed address') - - expect(event.detail.peer.addresses.map(({ multiaddr }) => multiaddr.toString())) - .to.include(confirmedAddress, 'peer info did not include confirmed observed address') - }) -}) diff --git a/packages/interface-compliance-tests/CHANGELOG.md b/packages/interface-compliance-tests/CHANGELOG.md index 84c496085c..5b5c80d3e9 100644 --- a/packages/interface-compliance-tests/CHANGELOG.md +++ b/packages/interface-compliance-tests/CHANGELOG.md @@ -75,6 +75,42 @@ * dependencies * @libp2p/utils bumped from ^5.2.4 to ^5.2.5 +## [6.2.1](https://github.com/libp2p/js-libp2p/compare/interface-compliance-tests-v6.2.0...interface-compliance-tests-v6.2.1) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/echo bumped from ^2.1.4 to ^2.1.5 + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + * libp2p bumped from ^2.4.0 to ^2.4.1 + +## [6.2.0](https://github.com/libp2p/js-libp2p/compare/interface-compliance-tests-v6.1.11...interface-compliance-tests-v6.2.0) (2024-12-09) + + +### Features + +* add getMaxConnections method to connection manager ([#2877](https://github.com/libp2p/js-libp2p/issues/2877)) ([1729fca](https://github.com/libp2p/js-libp2p/commit/1729fcaebc78307ff06783d5a2201ad83f92c109)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/echo bumped from ^2.1.3 to ^2.1.4 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/memory bumped from ^1.0.1 to ^1.0.2 + * @libp2p/multistream-select bumped from ^6.0.9 to ^6.0.10 + * @libp2p/peer-collections bumped from ^6.0.12 to ^6.0.13 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/plaintext bumped from ^2.0.11 to ^2.0.12 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * libp2p bumped from ^2.3.1 to ^2.4.0 + ## [6.1.11](https://github.com/libp2p/js-libp2p/compare/interface-compliance-tests-v6.1.10...interface-compliance-tests-v6.1.11) (2024-11-19) diff --git a/packages/interface-compliance-tests/package.json b/packages/interface-compliance-tests/package.json index 9a9944a714..e9b87ed586 100644 --- a/packages/interface-compliance-tests/package.json +++ b/packages/interface-compliance-tests/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/interface-compliance-tests", - "version": "6.1.11", + "version": "6.2.1", "description": "Compliance tests for JS libp2p interfaces", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/interface-compliance-tests#readme", @@ -104,17 +104,17 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/echo": "^2.1.3", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/logger": "^5.1.4", - "@libp2p/memory": "^1.0.1", - "@libp2p/multistream-select": "^6.0.9", - "@libp2p/peer-collections": "^6.0.12", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/plaintext": "^2.0.11", - "@libp2p/utils": "^6.2.1", + "@libp2p/crypto": "^5.0.8", + "@libp2p/echo": "^2.1.5", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/logger": "^5.1.5", + "@libp2p/memory": "^1.0.2", + "@libp2p/multistream-select": "^6.0.10", + "@libp2p/peer-collections": "^6.0.13", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/plaintext": "^2.0.12", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "abortable-iterator": "^5.1.0", @@ -131,7 +131,7 @@ "it-pushable": "^3.2.3", "it-stream-types": "^2.0.2", "it-to-buffer": "^4.0.7", - "libp2p": "^2.3.1", + "libp2p": "^2.4.1", "merge-options": "^3.0.4", "p-defer": "^4.0.1", "p-event": "^6.0.1", diff --git a/packages/interface-compliance-tests/src/mocks/connection-manager.ts b/packages/interface-compliance-tests/src/mocks/connection-manager.ts index 6e820543e4..2bea431676 100644 --- a/packages/interface-compliance-tests/src/mocks/connection-manager.ts +++ b/packages/interface-compliance-tests/src/mocks/connection-manager.ts @@ -97,6 +97,10 @@ class MockConnectionManager implements ConnectionManager, Startable { return map } + getMaxConnections (): number { + return 10_000 + } + async openConnection (peerId: PeerId | Multiaddr | Multiaddr[]): Promise { if (isMultiaddr(peerId)) { throw new UnsupportedOperationError('Dialing multiaddrs not supported') diff --git a/packages/interface-internal/CHANGELOG.md b/packages/interface-internal/CHANGELOG.md index b8ec8bb9d7..fd81d4a1ce 100644 --- a/packages/interface-internal/CHANGELOG.md +++ b/packages/interface-internal/CHANGELOG.md @@ -32,6 +32,35 @@ * dependencies * @libp2p/peer-collections bumped from ^5.1.3 to ^5.1.4 +## [2.2.1](https://github.com/libp2p/js-libp2p/compare/interface-internal-v2.2.0...interface-internal-v2.2.1) (2024-12-10) + + +### Bug Fixes + +* auto-confirm relay addresses ([#2886](https://github.com/libp2p/js-libp2p/issues/2886)) ([5c4a79e](https://github.com/libp2p/js-libp2p/commit/5c4a79e5a6e8d0db1ef6464075841a0b9de507ef)), closes [#2883](https://github.com/libp2p/js-libp2p/issues/2883) + +## [2.2.0](https://github.com/libp2p/js-libp2p/compare/interface-internal-v2.1.1...interface-internal-v2.2.0) (2024-12-09) + + +### Features + +* add getMaxConnections method to connection manager ([#2877](https://github.com/libp2p/js-libp2p/issues/2877)) ([1729fca](https://github.com/libp2p/js-libp2p/commit/1729fcaebc78307ff06783d5a2201ad83f92c109)) +* allow adding external ip/port mapping ([#2836](https://github.com/libp2p/js-libp2p/issues/2836)) ([6ddc1b8](https://github.com/libp2p/js-libp2p/commit/6ddc1b80ebe396afee58082865ae6cae2bb39fb1)) + + +### Bug Fixes + +* require confirmation of global unicast addresses ([#2876](https://github.com/libp2p/js-libp2p/issues/2876)) ([92cc740](https://github.com/libp2p/js-libp2p/commit/92cc740828963a4786ea83befe606dac4ba25e45)) +* require external confirmation of public addresses ([#2867](https://github.com/libp2p/js-libp2p/issues/2867)) ([d19974d](https://github.com/libp2p/js-libp2p/commit/d19974d93a1015acfca95c2155dbcffc5fd6a6c0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/peer-collections bumped from ^6.0.12 to ^6.0.13 + ## [2.1.1](https://github.com/libp2p/js-libp2p/compare/interface-internal-v2.1.0...interface-internal-v2.1.1) (2024-11-18) diff --git a/packages/interface-internal/package.json b/packages/interface-internal/package.json index 64506af893..cba5920b8f 100644 --- a/packages/interface-internal/package.json +++ b/packages/interface-internal/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/interface-internal", - "version": "2.1.1", + "version": "2.2.1", "description": "Interfaces implemented by internal libp2p components", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/interface-internal#readme", @@ -48,8 +48,8 @@ "build": "aegir build" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/peer-collections": "^6.0.12", + "@libp2p/interface": "^2.3.0", + "@libp2p/peer-collections": "^6.0.13", "@multiformats/multiaddr": "^12.3.3", "progress-events": "^1.0.1", "uint8arraylist": "^2.4.8" diff --git a/packages/interface-internal/src/address-manager/index.ts b/packages/interface-internal/src/address-manager/index.ts index 34cb48825e..f645c87dbe 100644 --- a/packages/interface-internal/src/address-manager/index.ts +++ b/packages/interface-internal/src/address-manager/index.ts @@ -1,5 +1,62 @@ import type { Multiaddr } from '@multiformats/multiaddr' +/** + * The type of address: + * + * - 'transport' a listen address supplied by a transport + * - 'announce' a pre-configured announce address + * - 'observed' a peer reported this as a public address + * - 'dns-mapping' a DNS address dynamically mapped to one or more public addresses + * - 'ip-mapping' an external IP address dynamically mapped to a LAN address + */ +export type AddressType = 'transport' | 'announce' | 'observed' | 'dns-mapping' | 'ip-mapping' + +/** + * An address that has been configured or detected + */ +export interface NodeAddress { + /** + * The multiaddr that represents the address + */ + multiaddr: Multiaddr + + /** + * Dynamically configured addresses such as observed or IP/DNS mapped ones + * must be verified as valid by AutoNAT or some other means before the current + * node will add them to it's peer record and share them with peers. + * + * When this value is true, it's safe to share the address. + */ + verified: boolean + + /** + * The timestamp at which the address was last verified + */ + lastVerified?: number + + /** + * A millisecond timestamp after which this address should be reverified + */ + expires: number + + /** + * The source of this address + */ + type: AddressType +} + +export interface ConfirmAddressOptions { + /** + * Override the TTL of the observed address verification + */ + ttl?: number + + /** + * Allows hinting which type of address this is + */ + type?: AddressType +} + export interface AddressManager { /** * Get peer listen multiaddrs @@ -21,7 +78,7 @@ export interface AddressManager { * Signal that we have confidence an observed multiaddr is publicly dialable - * this will make it appear in the output of getAddresses() */ - confirmObservedAddr(addr: Multiaddr): void + confirmObservedAddr(addr: Multiaddr, options?: ConfirmAddressOptions): void /** * Signal that we do not have confidence an observed multiaddr is publicly dialable - @@ -41,6 +98,11 @@ export interface AddressManager { */ getAddresses(): Multiaddr[] + /** + * Return all known addresses with metadata + */ + getAddressesWithMetadata(): NodeAddress[] + /** * Adds a mapping between one or more IP addresses and a domain name - when * `getAddresses` is invoked, where the IP addresses are present in a diff --git a/packages/interface-internal/src/connection-manager/index.ts b/packages/interface-internal/src/connection-manager/index.ts index de9d036e08..adb7ff04b6 100644 --- a/packages/interface-internal/src/connection-manager/index.ts +++ b/packages/interface-internal/src/connection-manager/index.ts @@ -46,6 +46,12 @@ export interface ConnectionManager { */ getConnectionsMap(): PeerMap + /** + * Returns the configured maximum number of connections this connection + * manager will accept + */ + getMaxConnections(): number + /** * Open a connection to a remote peer * diff --git a/packages/interface/CHANGELOG.md b/packages/interface/CHANGELOG.md index 0b7f34399b..11351ca064 100644 --- a/packages/interface/CHANGELOG.md +++ b/packages/interface/CHANGELOG.md @@ -5,6 +5,13 @@ * add start/stop events to libp2p interface ([#407](https://github.com/libp2p/js-libp2p-interfaces/issues/407)) ([016c1e8](https://github.com/libp2p/js-libp2p-interfaces/commit/016c1e82b060c93c80546cd8c493ec6e6c97cbec)) +## [2.3.0](https://github.com/libp2p/js-libp2p/compare/interface-v2.2.1...interface-v2.3.0) (2024-12-09) + + +### Features + +* add auto-tls service ([#2798](https://github.com/libp2p/js-libp2p/issues/2798)) ([d866eb5](https://github.com/libp2p/js-libp2p/commit/d866eb5bb8269485364c233119331ca073ff1343)) + ## [2.2.1](https://github.com/libp2p/js-libp2p/compare/interface-v2.2.0...interface-v2.2.1) (2024-11-18) diff --git a/packages/interface/package.json b/packages/interface/package.json index 5c531d4964..19d1924235 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/interface", - "version": "2.2.1", + "version": "2.3.0", "description": "The interface implemented by a libp2p node", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/interface#readme", diff --git a/packages/kad-dht/CHANGELOG.md b/packages/kad-dht/CHANGELOG.md index 6956230d8d..d7c77003fd 100644 --- a/packages/kad-dht/CHANGELOG.md +++ b/packages/kad-dht/CHANGELOG.md @@ -106,6 +106,41 @@ * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 * @libp2p/peer-store bumped from ^10.0.9 to ^10.0.10 +## [14.1.5](https://github.com/libp2p/js-libp2p/compare/kad-dht-v14.1.4...kad-dht-v14.1.5) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [14.1.4](https://github.com/libp2p/js-libp2p/compare/kad-dht-v14.1.3...kad-dht-v14.1.4) (2024-12-09) + + +### Bug Fixes + +* do not add peers to routing table during RPC handling ([#2866](https://github.com/libp2p/js-libp2p/issues/2866)) ([99f5f27](https://github.com/libp2p/js-libp2p/commit/99f5f270b9e7b69e4ef543c1ff1c019815af58cb)) +* include DHT client in FIND_NODE response if exact match ([#2835](https://github.com/libp2p/js-libp2p/issues/2835)) ([98f3c77](https://github.com/libp2p/js-libp2p/commit/98f3c773dce0deea7abf15c77fad5d2bb83b507e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/peer-collections bumped from ^6.0.12 to ^6.0.13 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-store bumped from ^11.0.12 to ^11.0.13 + ## [14.1.3](https://github.com/libp2p/js-libp2p/compare/kad-dht-v14.1.2...kad-dht-v14.1.3) (2024-11-19) diff --git a/packages/kad-dht/package.json b/packages/kad-dht/package.json index 89f13e9e0e..00dcba7a28 100644 --- a/packages/kad-dht/package.json +++ b/packages/kad-dht/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/kad-dht", - "version": "14.1.3", + "version": "14.1.5", "description": "JavaScript implementation of the Kad-DHT for libp2p", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/kad-dht#readme", @@ -57,13 +57,13 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/peer-collections": "^6.0.12", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/peer-collections": "^6.0.13", + "@libp2p/peer-id": "^5.0.9", "@libp2p/record": "^4.0.4", - "@libp2p/utils": "^6.2.1", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "any-signal": "^4.1.1", "interface-datastore": "^8.3.1", @@ -89,9 +89,9 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-store": "^11.0.12", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-store": "^11.0.13", "@types/lodash.random": "^3.2.9", "@types/lodash.range": "^3.2.9", "@types/sinon": "^17.0.3", diff --git a/packages/keychain/CHANGELOG.md b/packages/keychain/CHANGELOG.md index c19d68bc15..c15e21aaee 100644 --- a/packages/keychain/CHANGELOG.md +++ b/packages/keychain/CHANGELOG.md @@ -47,6 +47,18 @@ * devDependencies * @libp2p/peer-id-factory bumped from ^4.0.3 to ^4.0.4 +## [5.0.11](https://github.com/libp2p/js-libp2p/compare/keychain-v5.0.10...keychain-v5.0.11) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [5.0.10](https://github.com/libp2p/js-libp2p/compare/keychain-v5.0.9...keychain-v5.0.10) (2024-11-18) diff --git a/packages/keychain/package.json b/packages/keychain/package.json index de06a04dd0..973ab313ce 100644 --- a/packages/keychain/package.json +++ b/packages/keychain/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/keychain", - "version": "5.0.10", + "version": "5.0.11", "description": "Key management and cryptographically protected messages", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/keychain#readme", @@ -59,8 +59,8 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", "@noble/hashes": "^1.6.1", "asn1js": "^3.0.5", "interface-datastore": "^8.3.1", @@ -70,7 +70,7 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "datastore-core": "^10.0.2" }, diff --git a/packages/libp2p/CHANGELOG.md b/packages/libp2p/CHANGELOG.md index 47d523739c..21b9707bc4 100644 --- a/packages/libp2p/CHANGELOG.md +++ b/packages/libp2p/CHANGELOG.md @@ -110,6 +110,57 @@ * @libp2p/tcp bumped from ^9.0.14 to ^9.0.15 * @libp2p/websockets bumped from ^8.0.14 to ^8.0.15 +## [2.4.1](https://github.com/libp2p/js-libp2p/compare/libp2p-v2.4.0...libp2p-v2.4.1) (2024-12-10) + + +### Bug Fixes + +* auto-confirm relay addresses ([#2886](https://github.com/libp2p/js-libp2p/issues/2886)) ([5c4a79e](https://github.com/libp2p/js-libp2p/commit/5c4a79e5a6e8d0db1ef6464075841a0b9de507ef)), closes [#2883](https://github.com/libp2p/js-libp2p/issues/2883) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [2.4.0](https://github.com/libp2p/js-libp2p/compare/libp2p-v2.3.1...libp2p-v2.4.0) (2024-12-09) + + +### Features + +* add append announce addresses ([#2834](https://github.com/libp2p/js-libp2p/issues/2834)) ([b248eef](https://github.com/libp2p/js-libp2p/commit/b248eefc01e6034c211b0d458d0ce7a74e99c24f)) +* allow adding external ip/port mapping ([#2836](https://github.com/libp2p/js-libp2p/issues/2836)) ([6ddc1b8](https://github.com/libp2p/js-libp2p/commit/6ddc1b80ebe396afee58082865ae6cae2bb39fb1)) +* Use CIDR format for connection-manager allow/deny lists ([#2783](https://github.com/libp2p/js-libp2p/issues/2783)) ([48e9cfa](https://github.com/libp2p/js-libp2p/commit/48e9cfa56fdf9d2dcdc0efc758cf7f055106cbb5)) + + +### Bug Fixes + +* confirm dns mappings with ip mappings ([#2861](https://github.com/libp2p/js-libp2p/issues/2861)) ([0f87479](https://github.com/libp2p/js-libp2p/commit/0f8747950c26a47828c826b7f0a257bf95276b0f)) +* ensure user dial signals are respected ([#2842](https://github.com/libp2p/js-libp2p/issues/2842)) ([bc90b4f](https://github.com/libp2p/js-libp2p/commit/bc90b4fd58aee1ccd94d4fd61cc48d336e77d772)) +* handle router mappings of mixed IP version ([#2858](https://github.com/libp2p/js-libp2p/issues/2858)) ([f28c31d](https://github.com/libp2p/js-libp2p/commit/f28c31d803f13872ec151f8b5fe073aedc5dbcbf)) +* limit observed addresses in address manager ([#2869](https://github.com/libp2p/js-libp2p/issues/2869)) ([06f79b6](https://github.com/libp2p/js-libp2p/commit/06f79b6466fa8f6656676a71a5b90e6071825303)) +* remove browser dial filter ([#2838](https://github.com/libp2p/js-libp2p/issues/2838)) ([d6cd25d](https://github.com/libp2p/js-libp2p/commit/d6cd25d0deca292420093d894edbfbc47b347e5d)) +* require confirmation of global unicast addresses ([#2876](https://github.com/libp2p/js-libp2p/issues/2876)) ([92cc740](https://github.com/libp2p/js-libp2p/commit/92cc740828963a4786ea83befe606dac4ba25e45)) +* require external confirmation of public addresses ([#2867](https://github.com/libp2p/js-libp2p/issues/2867)) ([d19974d](https://github.com/libp2p/js-libp2p/commit/d19974d93a1015acfca95c2155dbcffc5fd6a6c0)) +* trigger self:peer:update when ip/dns mappings change ([#2839](https://github.com/libp2p/js-libp2p/issues/2839)) ([4a85eb0](https://github.com/libp2p/js-libp2p/commit/4a85eb033f7ea8461a10bc8b38bbc76d1383d1cc)) +* update agent version ([#2845](https://github.com/libp2p/js-libp2p/issues/2845)) ([4761dd7](https://github.com/libp2p/js-libp2p/commit/4761dd701aec6620ee504cb9908fa2319971b79b)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/multistream-select bumped from ^6.0.9 to ^6.0.10 + * @libp2p/peer-collections bumped from ^6.0.12 to ^6.0.13 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/peer-store bumped from ^11.0.12 to ^11.0.13 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + ## [2.3.1](https://github.com/libp2p/js-libp2p/compare/libp2p-v2.3.0...libp2p-v2.3.1) (2024-11-18) diff --git a/packages/libp2p/package.json b/packages/libp2p/package.json index 17b6f4f5b0..d542ad31f3 100644 --- a/packages/libp2p/package.json +++ b/packages/libp2p/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "2.3.1", + "version": "2.4.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/libp2p#readme", @@ -86,15 +86,16 @@ }, "dependencies": { "@chainsafe/is-ip": "^2.0.2", - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/logger": "^5.1.4", - "@libp2p/multistream-select": "^6.0.9", - "@libp2p/peer-collections": "^6.0.12", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/peer-store": "^11.0.12", - "@libp2p/utils": "^6.2.1", + "@chainsafe/netmask": "^2.0.0", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/logger": "^5.1.5", + "@libp2p/multistream-select": "^6.0.10", + "@libp2p/peer-collections": "^6.0.13", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/peer-store": "^11.0.13", + "@libp2p/utils": "^6.3.0", "@multiformats/dns": "^1.0.6", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", diff --git a/packages/libp2p/src/address-manager.ts b/packages/libp2p/src/address-manager.ts deleted file mode 100644 index b14e2b19d2..0000000000 --- a/packages/libp2p/src/address-manager.ts +++ /dev/null @@ -1,454 +0,0 @@ -import { isIPv4 } from '@chainsafe/is-ip' -import { peerIdFromString } from '@libp2p/peer-id' -import { debounce } from '@libp2p/utils/debounce' -import { multiaddr, protocols } from '@multiformats/multiaddr' -import type { ComponentLogger, Libp2pEvents, Logger, TypedEventTarget, PeerId, PeerStore } from '@libp2p/interface' -import type { AddressManager as AddressManagerInterface, TransportManager } from '@libp2p/interface-internal' -import type { Multiaddr } from '@multiformats/multiaddr' - -export const defaultValues = { - maxObservedAddresses: 10 -} - -export interface AddressManagerInit { - /** - * Pass an function in this field to override the list of addresses - * that are announced to the network - */ - announceFilter?: AddressFilter - - /** - * A list of string multiaddrs to listen on - */ - listen?: string[] - - /** - * A list of string multiaddrs to use instead of those reported by transports - */ - announce?: string[] - - /** - * A list of string multiaddrs string to never announce - */ - noAnnounce?: string[] - - /** - * A list of string multiaddrs to add to the list of announced addresses - */ - appendAnnounce?: string[] - - /** - * Limits the number of observed addresses we will store - */ - maxObservedAddresses?: number -} - -export interface AddressManagerComponents { - peerId: PeerId - transportManager: TransportManager - peerStore: PeerStore - events: TypedEventTarget - logger: ComponentLogger -} - -/** - * A function that takes a list of multiaddrs and returns a list - * to announce - */ -export interface AddressFilter { - (addrs: Multiaddr[]): Multiaddr[] -} - -const defaultAddressFilter = (addrs: Multiaddr[]): Multiaddr[] => addrs - -interface ObservedAddressMetadata { - confident: boolean -} - -/** - * If the passed multiaddr contains the passed peer id, remove it - */ -function stripPeerId (ma: Multiaddr, peerId: PeerId): Multiaddr { - const observedPeerIdStr = ma.getPeerId() - - // strip our peer id if it has been passed - if (observedPeerIdStr != null) { - const observedPeerId = peerIdFromString(observedPeerIdStr) - - // use same encoding for comparison - if (observedPeerId.equals(peerId)) { - ma = ma.decapsulate(multiaddr(`/p2p/${peerId.toString()}`)) - } - } - - return ma -} - -const CODEC_IP4 = 0x04 -const CODEC_IP6 = 0x29 -const CODEC_DNS4 = 0x36 -const CODEC_DNS6 = 0x37 -const CODEC_TCP = 0x06 -const CODEC_UDP = 0x0111 - -interface PublicAddressMapping { - externalIp: string - externalPort: number -} - -interface DNSMapping { - domain: string - confident: boolean -} - -export class AddressManager implements AddressManagerInterface { - private readonly log: Logger - private readonly components: AddressManagerComponents - // this is an array to allow for duplicates, e.g. multiples of `/ip4/0.0.0.0/tcp/0` - private readonly listen: string[] - private readonly announce: Set - private readonly appendAnnounce: Set - private readonly observed: Map - private readonly announceFilter: AddressFilter - private readonly ipDomainMappings: Map - private readonly publicAddressMappings: Map - private readonly maxObservedAddresses: number - - /** - * Responsible for managing the peer addresses. - * Peers can specify their listen and announce addresses. - * The listen addresses will be used by the libp2p transports to listen for new connections, - * while the announce addresses will be used for the peer addresses' to other peers in the network. - */ - constructor (components: AddressManagerComponents, init: AddressManagerInit = {}) { - const { listen = [], announce = [], appendAnnounce = [] } = init - - this.components = components - this.log = components.logger.forComponent('libp2p:address-manager') - this.listen = listen.map(ma => ma.toString()) - this.announce = new Set(announce.map(ma => ma.toString())) - this.appendAnnounce = new Set(appendAnnounce.map(ma => ma.toString())) - this.observed = new Map() - this.ipDomainMappings = new Map() - this.publicAddressMappings = new Map() - this.announceFilter = init.announceFilter ?? defaultAddressFilter - this.maxObservedAddresses = init.maxObservedAddresses ?? defaultValues.maxObservedAddresses - - // this method gets called repeatedly on startup when transports start listening so - // debounce it so we don't cause multiple self:peer:update events to be emitted - this._updatePeerStoreAddresses = debounce(this._updatePeerStoreAddresses.bind(this), 1000) - - // update our stored addresses when new transports listen - components.events.addEventListener('transport:listening', () => { - this._updatePeerStoreAddresses() - }) - // update our stored addresses when existing transports stop listening - components.events.addEventListener('transport:close', () => { - this._updatePeerStoreAddresses() - }) - } - - readonly [Symbol.toStringTag] = '@libp2p/address-manager' - - _updatePeerStoreAddresses (): void { - // if announce addresses have been configured, ensure they make it into our peer - // record for things like identify - const addrs = this.getAddresses() - .map(ma => { - // strip our peer id if it is present - if (ma.getPeerId() === this.components.peerId.toString()) { - return ma.decapsulate(`/p2p/${this.components.peerId.toString()}`) - } - - return ma - }) - - this.components.peerStore.patch(this.components.peerId, { - multiaddrs: addrs - }) - .catch(err => { - this.log.error('error updating addresses', err) - }) - } - - /** - * Get peer listen multiaddrs - */ - getListenAddrs (): Multiaddr[] { - return Array.from(this.listen).map((a) => multiaddr(a)) - } - - /** - * Get peer announcing multiaddrs - */ - getAnnounceAddrs (): Multiaddr[] { - return Array.from(this.announce).map((a) => multiaddr(a)) - } - - /** - * Get peer announcing multiaddrs - */ - getAppendAnnounceAddrs (): Multiaddr[] { - return Array.from(this.appendAnnounce).map((a) => multiaddr(a)) - } - - /** - * Get observed multiaddrs - */ - getObservedAddrs (): Multiaddr[] { - return Array.from(this.observed).map(([a]) => multiaddr(a)) - } - - /** - * Add peer observed addresses - */ - addObservedAddr (addr: Multiaddr): void { - if (this.observed.size === this.maxObservedAddresses) { - return - } - - addr = stripPeerId(addr, this.components.peerId) - const addrString = addr.toString() - - // do not trigger the change:addresses event if we already know about this address - if (this.observed.has(addrString)) { - return - } - - this.observed.set(addrString, { - confident: false - }) - } - - confirmObservedAddr (addr: Multiaddr): void { - addr = stripPeerId(addr, this.components.peerId) - const addrString = addr.toString() - - const metadata = this.observed.get(addrString) ?? { - confident: false - } - - const startingConfidence = metadata.confident - - this.observed.set(addrString, { - confident: true - }) - - // only trigger the 'self:peer:update' event if our confidence in an address has changed - if (!startingConfidence) { - this._updatePeerStoreAddresses() - } - } - - removeObservedAddr (addr: Multiaddr): void { - addr = stripPeerId(addr, this.components.peerId) - const addrString = addr.toString() - - this.observed.delete(addrString) - } - - getAddresses (): Multiaddr[] { - let multiaddrs = this.getAnnounceAddrs() - - if (multiaddrs.length === 0) { - // no configured announce addrs, add configured listen addresses - multiaddrs = this.components.transportManager.getAddrs() - } - - multiaddrs = multiaddrs - .concat( - // add additional announce addresses - ...this.getAppendAnnounceAddrs(), - - // add observed addresses we are confident in - Array.from(this.observed) - .filter(([ma, metadata]) => metadata.confident) - .map(([ma]) => multiaddr(ma)) - ) - - // add public addresses - const ipMappedMultiaddrs: Multiaddr[] = [] - multiaddrs.forEach(ma => { - const tuples = ma.stringTuples() - let tuple: string | undefined - - // see if the internal host/port/protocol tuple has been mapped externally - if ((tuples[0][0] === CODEC_IP4 || tuples[0][0] === CODEC_IP6) && tuples[1][0] === CODEC_TCP) { - tuple = `${tuples[0][1]}-${tuples[1][1]}-tcp` - } else if ((tuples[0][0] === CODEC_IP4 || tuples[0][0] === CODEC_IP6) && tuples[1][0] === CODEC_UDP) { - tuple = `${tuples[0][1]}-${tuples[1][1]}-udp` - } - - if (tuple == null) { - return - } - - const mappings = this.publicAddressMappings.get(tuple) - - if (mappings == null) { - return - } - - for (const mapping of mappings) { - tuples[0][0] = isIPv4(mapping.externalIp) ? CODEC_IP4 : CODEC_IP6 - tuples[0][1] = mapping.externalIp - tuples[1][1] = `${mapping.externalPort}` - - ipMappedMultiaddrs.push( - multiaddr(`/${ - tuples.map(tuple => { - return [ - protocols(tuple[0]).name, - tuple[1] - ].join('/') - }).join('/') - }`) - ) - } - }) - multiaddrs = multiaddrs.concat(ipMappedMultiaddrs) - - // add ip->domain mappings - const dnsMappedMultiaddrs: Multiaddr[] = [] - for (const ma of multiaddrs) { - const tuples = ma.stringTuples() - let mappedIp = false - - for (const [ip, mapping] of this.ipDomainMappings.entries()) { - if (!mapping.confident) { - continue - } - - for (let i = 0; i < tuples.length; i++) { - if (tuples[i][1] !== ip) { - continue - } - - if (tuples[i][0] === CODEC_IP4) { - tuples[i][0] = CODEC_DNS4 - tuples[i][1] = mapping.domain - mappedIp = true - } - - if (tuples[i][0] === CODEC_IP6) { - tuples[i][0] = CODEC_DNS6 - tuples[i][1] = mapping.domain - mappedIp = true - } - } - } - - if (mappedIp) { - dnsMappedMultiaddrs.push( - multiaddr(`/${ - tuples.map(tuple => { - return [ - protocols(tuple[0]).name, - tuple[1] - ].join('/') - }).join('/') - }`) - ) - } - } - multiaddrs = multiaddrs.concat(dnsMappedMultiaddrs) - - // dedupe multiaddrs - const addrSet = new Set() - multiaddrs = multiaddrs.filter(ma => { - const maStr = ma.toString() - - if (addrSet.has(maStr)) { - return false - } - - addrSet.add(maStr) - - return true - }) - - // Create advertising list - return this.announceFilter( - Array.from(addrSet) - .map(str => { - const ma = multiaddr(str) - - // do not append our peer id to a path multiaddr as it will become invalid - if (ma.protos().pop()?.path === true) { - return ma - } - - if (ma.getPeerId() === this.components.peerId.toString()) { - return ma - } - - return ma.encapsulate(`/p2p/${this.components.peerId.toString()}`) - }) - ) - } - - addDNSMapping (domain: string, addresses: string[]): void { - addresses.forEach(ip => { - this.log('add DNS mapping %s to %s', ip, domain) - - // check ip/public ip mappings to see if we think we are contactable - const confident = [...this.publicAddressMappings.entries()].some(([key, mappings]) => { - return mappings.some(mapping => mapping.externalIp === ip) - }) - - this.ipDomainMappings.set(ip, { - domain, - confident - }) - }) - this._updatePeerStoreAddresses() - } - - removeDNSMapping (domain: string): void { - for (const [key, mapping] of this.ipDomainMappings.entries()) { - if (mapping.domain === domain) { - this.log('remove DNS mapping for %s', domain) - this.ipDomainMappings.delete(key) - } - } - this._updatePeerStoreAddresses() - } - - addPublicAddressMapping (internalIp: string, internalPort: number, externalIp: string, externalPort: number = internalPort, protocol: 'tcp' | 'udp' = 'tcp'): void { - const key = `${internalIp}-${internalPort}-${protocol}` - const mappings = this.publicAddressMappings.get(key) ?? [] - mappings.push({ - externalIp, - externalPort - }) - - this.publicAddressMappings.set(key, mappings) - - // update domain mappings to indicate we are now confident that any matching - // ip/domain combination can now be resolved externally - for (const [key, mapping] of this.ipDomainMappings.entries()) { - if (key === externalIp) { - mapping.confident = true - - this.ipDomainMappings.set(key, mapping) - } - } - - this._updatePeerStoreAddresses() - } - - removePublicAddressMapping (internalIp: string, internalPort: number, externalIp: string, externalPort: number = internalPort, protocol: 'tcp' | 'udp' = 'tcp'): void { - const key = `${internalIp}-${internalPort}-${protocol}` - const mappings = (this.publicAddressMappings.get(key) ?? []).filter(mapping => { - return mapping.externalIp !== externalIp && mapping.externalPort !== externalPort - }) - - if (mappings.length === 0) { - this.publicAddressMappings.delete(key) - } else { - this.publicAddressMappings.set(key, mappings) - } - - this._updatePeerStoreAddresses() - } -} diff --git a/packages/libp2p/src/address-manager/dns-mappings.ts b/packages/libp2p/src/address-manager/dns-mappings.ts new file mode 100644 index 0000000000..f414d2f6c8 --- /dev/null +++ b/packages/libp2p/src/address-manager/dns-mappings.ts @@ -0,0 +1,182 @@ +import { isPrivateIp } from '@libp2p/utils/private-ip' +import { multiaddr, protocols } from '@multiformats/multiaddr' +import type { AddressManagerComponents, AddressManagerInit } from './index.js' +import type { Logger } from '@libp2p/interface' +import type { NodeAddress } from '@libp2p/interface-internal' +import type { Multiaddr, StringTuple } from '@multiformats/multiaddr' + +const MAX_DATE = 8_640_000_000_000_000 + +export const defaultValues = { + maxObservedAddresses: 10 +} + +interface DNSMapping { + domain: string + verified: boolean + expires: number + lastVerified?: number +} + +const CODEC_TLS = 0x01c0 +const CODEC_SNI = 0x01c1 +const CODEC_DNS = 0x35 +const CODEC_DNS4 = 0x36 +const CODEC_DNS6 = 0x37 +const CODEC_DNSADDR = 0x38 + +export class DNSMappings { + private readonly log: Logger + private readonly mappings: Map + + constructor (components: AddressManagerComponents, init: AddressManagerInit = {}) { + this.log = components.logger.forComponent('libp2p:address-manager:dns-mappings') + this.mappings = new Map() + } + + has (ma: Multiaddr): boolean { + const host = this.findHost(ma) + + for (const mapping of this.mappings.values()) { + if (mapping.domain === host) { + return true + } + } + + return false + } + + add (domain: string, addresses: string[]): void { + addresses.forEach(ip => { + this.log('add DNS mapping %s to %s', ip, domain) + // we are only confident if this is an local domain mapping, otherwise + // we will require external validation + const verified = isPrivateIp(ip) === true + + this.mappings.set(ip, { + domain, + verified, + expires: verified ? MAX_DATE - Date.now() : 0, + lastVerified: verified ? MAX_DATE - Date.now() : undefined + }) + }) + } + + remove (ma: Multiaddr): boolean { + const host = this.findHost(ma) + let wasConfident = false + + for (const [ip, mapping] of this.mappings.entries()) { + if (mapping.domain === host) { + this.log('removing %s to %s DNS mapping %e', ip, mapping.domain, new Error('where')) + this.mappings.delete(ip) + wasConfident = wasConfident || mapping.verified + } + } + + return wasConfident + } + + getAll (addresses: NodeAddress[]): NodeAddress[] { + const dnsMappedAddresses: NodeAddress[] = [] + + for (let i = 0; i < addresses.length; i++) { + const address = addresses[i] + const tuples = address.multiaddr.stringTuples() + const host = tuples[0][1] + + if (host == null) { + continue + } + + for (const [ip, mapping] of this.mappings.entries()) { + if (host !== ip) { + continue + } + + // insert SNI tuple after TLS tuple, if one is present + const mappedIp = this.maybeAddSNITuple(tuples, mapping.domain) + + if (mappedIp) { + // remove the address and replace it with the version that includes + // the SNI tuple + addresses.splice(i, 1) + i-- + + dnsMappedAddresses.push({ + multiaddr: multiaddr(`/${ + tuples.map(tuple => { + return [ + protocols(tuple[0]).name, + tuple[1] + ].join('/') + }).join('/') + }`), + verified: mapping.verified, + type: 'dns-mapping', + expires: mapping.expires, + lastVerified: mapping.lastVerified + }) + } + } + } + + return dnsMappedAddresses + } + + private maybeAddSNITuple (tuples: StringTuple[], domain: string): boolean { + for (let j = 0; j < tuples.length; j++) { + if (tuples[j][0] === CODEC_TLS && tuples[j + 1]?.[0] !== CODEC_SNI) { + tuples.splice(j + 1, 0, [CODEC_SNI, domain]) + return true + } + } + + return false + } + + confirm (ma: Multiaddr, ttl: number): boolean { + const host = this.findHost(ma) + let startingConfidence = false + + for (const [ip, mapping] of this.mappings.entries()) { + if (mapping.domain === host) { + this.log('marking %s to %s DNS mapping as verified', ip, mapping.domain) + startingConfidence = mapping.verified + mapping.verified = true + mapping.expires = Date.now() + ttl + mapping.lastVerified = Date.now() + } + } + + return startingConfidence + } + + unconfirm (ma: Multiaddr, ttl: number): boolean { + const host = this.findHost(ma) + let wasConfident = false + + for (const [ip, mapping] of this.mappings.entries()) { + if (mapping.domain === host) { + this.log('removing verification of %s to %s DNS mapping', ip, mapping.domain) + wasConfident = wasConfident || mapping.verified + mapping.verified = false + mapping.expires = Date.now() + ttl + } + } + + return wasConfident + } + + private findHost (ma: Multiaddr): string | undefined { + for (const tuple of ma.stringTuples()) { + if (tuple[0] === CODEC_SNI) { + return tuple[1] + } + + if (tuple[0] === CODEC_DNS || tuple[0] === CODEC_DNS4 || tuple[0] === CODEC_DNS6 || tuple[0] === CODEC_DNSADDR) { + return tuple[1] + } + } + } +} diff --git a/packages/libp2p/src/address-manager/index.ts b/packages/libp2p/src/address-manager/index.ts new file mode 100644 index 0000000000..60cc8f84ef --- /dev/null +++ b/packages/libp2p/src/address-manager/index.ts @@ -0,0 +1,413 @@ +/* eslint-disable complexity */ +import { isIPv4 } from '@chainsafe/is-ip' +import { peerIdFromString } from '@libp2p/peer-id' +import { debounce } from '@libp2p/utils/debounce' +import { createScalableCuckooFilter } from '@libp2p/utils/filters' +import { multiaddr } from '@multiformats/multiaddr' +import { DNSMappings } from './dns-mappings.js' +import { IPMappings } from './ip-mappings.js' +import { ObservedAddresses } from './observed-addresses.js' +import { TransportAddresses } from './transport-addresses.js' +import type { ComponentLogger, Libp2pEvents, Logger, TypedEventTarget, PeerId, PeerStore } from '@libp2p/interface' +import type { AddressManager as AddressManagerInterface, TransportManager, NodeAddress, ConfirmAddressOptions } from '@libp2p/interface-internal' +import type { Filter } from '@libp2p/utils/filters' +import type { Multiaddr } from '@multiformats/multiaddr' + +const ONE_MINUTE = 60_000 + +export const defaultValues = { + maxObservedAddresses: 10, + addressVerificationTTL: ONE_MINUTE * 10, + addressVerificationRetry: ONE_MINUTE * 5 +} + +export interface AddressManagerInit { + /** + * Pass an function in this field to override the list of addresses + * that are announced to the network + */ + announceFilter?: AddressFilter + + /** + * A list of string multiaddrs to listen on + */ + listen?: string[] + + /** + * A list of string multiaddrs to use instead of those reported by transports + */ + announce?: string[] + + /** + * A list of string multiaddrs string to never announce + */ + noAnnounce?: string[] + + /** + * A list of string multiaddrs to add to the list of announced addresses + */ + appendAnnounce?: string[] + + /** + * Limits the number of observed addresses we will store + */ + maxObservedAddresses?: number + + /** + * How long before each public address should be reverified in ms. + * + * Requires `@libp2p/autonat` or some other verification method to be + * configured. + * + * @default 600_000 + */ + addressVerificationTTL?: number + + /** + * After a transport or mapped address has failed to verify, how long to wait + * before retrying it in ms + * + * Requires `@libp2p/autonat` or some other verification method to be + * configured. + * + * @default 300_000 + */ + addressVerificationRetry?: number +} + +export interface AddressManagerComponents { + peerId: PeerId + transportManager: TransportManager + peerStore: PeerStore + events: TypedEventTarget + logger: ComponentLogger +} + +/** + * A function that takes a list of multiaddrs and returns a list + * to announce + */ +export interface AddressFilter { + (addrs: Multiaddr[]): Multiaddr[] +} + +const defaultAddressFilter = (addrs: Multiaddr[]): Multiaddr[] => addrs + +/** + * If the passed multiaddr contains the passed peer id, remove it + */ +function stripPeerId (ma: Multiaddr, peerId: PeerId): Multiaddr { + const observedPeerIdStr = ma.getPeerId() + + // strip our peer id if it has been passed + if (observedPeerIdStr != null) { + const observedPeerId = peerIdFromString(observedPeerIdStr) + + // use same encoding for comparison + if (observedPeerId.equals(peerId)) { + ma = ma.decapsulate(multiaddr(`/p2p/${peerId.toString()}`)) + } + } + + return ma +} + +export class AddressManager implements AddressManagerInterface { + private readonly log: Logger + private readonly components: AddressManagerComponents + // this is an array to allow for duplicates, e.g. multiples of `/ip4/0.0.0.0/tcp/0` + private readonly listen: string[] + private readonly announce: Set + private readonly appendAnnounce: Set + private readonly announceFilter: AddressFilter + private readonly observed: ObservedAddresses + private readonly dnsMappings: DNSMappings + private readonly ipMappings: IPMappings + private readonly transportAddresses: TransportAddresses + private readonly observedAddressFilter: Filter + private readonly addressVerificationTTL: number + private readonly addressVerificationRetry: number + + /** + * Responsible for managing the peer addresses. + * Peers can specify their listen and announce addresses. + * The listen addresses will be used by the libp2p transports to listen for new connections, + * while the announce addresses will be used for the peer addresses' to other peers in the network. + */ + constructor (components: AddressManagerComponents, init: AddressManagerInit = {}) { + const { listen = [], announce = [], appendAnnounce = [] } = init + + this.components = components + this.log = components.logger.forComponent('libp2p:address-manager') + this.listen = listen.map(ma => ma.toString()) + this.announce = new Set(announce.map(ma => ma.toString())) + this.appendAnnounce = new Set(appendAnnounce.map(ma => ma.toString())) + this.observed = new ObservedAddresses(components, init) + this.dnsMappings = new DNSMappings(components, init) + this.ipMappings = new IPMappings(components, init) + this.transportAddresses = new TransportAddresses(components, init) + this.announceFilter = init.announceFilter ?? defaultAddressFilter + this.observedAddressFilter = createScalableCuckooFilter(1024) + this.addressVerificationTTL = init.addressVerificationTTL ?? defaultValues.addressVerificationTTL + this.addressVerificationRetry = init.addressVerificationRetry ?? defaultValues.addressVerificationRetry + + // this method gets called repeatedly on startup when transports start listening so + // debounce it so we don't cause multiple self:peer:update events to be emitted + this._updatePeerStoreAddresses = debounce(this._updatePeerStoreAddresses.bind(this), 1000) + + // update our stored addresses when new transports listen + components.events.addEventListener('transport:listening', () => { + this._updatePeerStoreAddresses() + }) + // update our stored addresses when existing transports stop listening + components.events.addEventListener('transport:close', () => { + this._updatePeerStoreAddresses() + }) + } + + readonly [Symbol.toStringTag] = '@libp2p/address-manager' + + _updatePeerStoreAddresses (): void { + // if announce addresses have been configured, ensure they make it into our peer + // record for things like identify + const addrs = this.getAddresses() + .map(ma => { + // strip our peer id if it is present + if (ma.getPeerId() === this.components.peerId.toString()) { + return ma.decapsulate(`/p2p/${this.components.peerId.toString()}`) + } + + return ma + }) + + this.components.peerStore.patch(this.components.peerId, { + multiaddrs: addrs + }) + .catch(err => { + this.log.error('error updating addresses', err) + }) + } + + /** + * Get peer listen multiaddrs + */ + getListenAddrs (): Multiaddr[] { + return Array.from(this.listen).map((a) => multiaddr(a)) + } + + /** + * Get peer announcing multiaddrs + */ + getAnnounceAddrs (): Multiaddr[] { + return Array.from(this.announce).map((a) => multiaddr(a)) + } + + /** + * Get peer announcing multiaddrs + */ + getAppendAnnounceAddrs (): Multiaddr[] { + return Array.from(this.appendAnnounce).map((a) => multiaddr(a)) + } + + /** + * Get observed multiaddrs + */ + getObservedAddrs (): Multiaddr[] { + return this.observed.getAll().map(addr => addr.multiaddr) + } + + /** + * Add peer observed addresses + */ + addObservedAddr (addr: Multiaddr): void { + const tuples = addr.stringTuples() + const socketAddress = `${tuples[0][1]}:${tuples[1][1]}` + + // ignore if this address if it's been observed before + if (this.observedAddressFilter.has(socketAddress)) { + return + } + + this.observedAddressFilter.add(socketAddress) + + addr = stripPeerId(addr, this.components.peerId) + + // ignore observed address if it is an IP mapping + if (this.ipMappings.has(addr)) { + return + } + + // ignore observed address if it is a DNS mapping + if (this.dnsMappings.has(addr)) { + return + } + + this.observed.add(addr) + } + + confirmObservedAddr (addr: Multiaddr, options?: ConfirmAddressOptions): void { + addr = stripPeerId(addr, this.components.peerId) + let startingConfidence = true + + if (options?.type === 'observed' || this.observed.has(addr)) { + startingConfidence = this.observed.confirm(addr, options?.ttl ?? this.addressVerificationTTL) + } + + if (options?.type === 'transport' || this.transportAddresses.has(addr)) { + startingConfidence = this.transportAddresses.confirm(addr, options?.ttl ?? this.addressVerificationTTL) + } + + if (options?.type === 'dns-mapping' || this.dnsMappings.has(addr)) { + startingConfidence = this.dnsMappings.confirm(addr, options?.ttl ?? this.addressVerificationTTL) + } + + if (options?.type === 'ip-mapping' || this.ipMappings.has(addr)) { + startingConfidence = this.ipMappings.confirm(addr, options?.ttl ?? this.addressVerificationTTL) + } + + // only trigger the 'self:peer:update' event if our confidence in an address has changed + if (!startingConfidence) { + this._updatePeerStoreAddresses() + } + } + + removeObservedAddr (addr: Multiaddr, options?: ConfirmAddressOptions): void { + addr = stripPeerId(addr, this.components.peerId) + + let startingConfidence = false + + if (this.observed.has(addr)) { + startingConfidence = this.observed.remove(addr) + } + + if (this.transportAddresses.has(addr)) { + startingConfidence = this.transportAddresses.unconfirm(addr, options?.ttl ?? this.addressVerificationRetry) + } + + if (this.dnsMappings.has(addr)) { + startingConfidence = this.dnsMappings.unconfirm(addr, options?.ttl ?? this.addressVerificationRetry) + } + + if (this.ipMappings.has(addr)) { + startingConfidence = this.ipMappings.unconfirm(addr, options?.ttl ?? this.addressVerificationRetry) + } + + // only trigger the 'self:peer:update' event if our confidence in an address has changed + if (startingConfidence) { + this._updatePeerStoreAddresses() + } + } + + getAddresses (): Multiaddr[] { + const addresses = new Set() + + const multiaddrs = this.getAddressesWithMetadata() + .filter(addr => { + if (!addr.verified) { + return false + } + + const maStr = addr.multiaddr.toString() + + if (addresses.has(maStr)) { + return false + } + + addresses.add(maStr) + + return true + }) + .map(address => address.multiaddr) + + // filter addressees before returning + return this.announceFilter( + multiaddrs.map(str => { + const ma = multiaddr(str) + + // do not append our peer id to a path multiaddr as it will become invalid + if (ma.protos().pop()?.path === true) { + return ma + } + + if (ma.getPeerId() === this.components.peerId.toString()) { + return ma + } + + return ma.encapsulate(`/p2p/${this.components.peerId.toString()}`) + }) + ) + } + + getAddressesWithMetadata (): NodeAddress[] { + const announceMultiaddrs = this.getAnnounceAddrs() + + if (announceMultiaddrs.length > 0) { + return announceMultiaddrs.map(multiaddr => ({ + multiaddr, + verified: true, + type: 'announce', + expires: Date.now() + this.addressVerificationTTL, + lastVerified: Date.now() + })) + } + + let addresses: NodeAddress[] = [] + + // add transport addresses + addresses = addresses.concat( + this.components.transportManager.getAddrs() + .map(multiaddr => this.transportAddresses.get(multiaddr, this.addressVerificationTTL)) + ) + + // add append announce addresses + addresses = addresses.concat( + this.getAppendAnnounceAddrs().map(multiaddr => ({ + multiaddr, + verified: true, + type: 'announce', + expires: Date.now() + this.addressVerificationTTL, + lastVerified: Date.now() + })) + ) + + // add observed addresses + addresses = addresses.concat( + this.observed.getAll() + ) + + // add ip mapped addresses + addresses = addresses.concat( + this.ipMappings.getAll(addresses) + ) + + // add ip->domain mappings, must be done after IP mappings + addresses = addresses.concat( + this.dnsMappings.getAll(addresses) + ) + + return addresses + } + + addDNSMapping (domain: string, addresses: string[]): void { + this.dnsMappings.add(domain, addresses) + } + + removeDNSMapping (domain: string): void { + if (this.dnsMappings.remove(multiaddr(`/dns/${domain}`))) { + this._updatePeerStoreAddresses() + } + } + + addPublicAddressMapping (internalIp: string, internalPort: number, externalIp: string, externalPort: number = internalPort, protocol: 'tcp' | 'udp' = 'tcp'): void { + this.ipMappings.add(internalIp, internalPort, externalIp, externalPort, protocol) + + // remove duplicate observed addresses + this.observed.removePrefixed(`/ip${isIPv4(externalIp) ? 4 : 6}/${externalIp}/${protocol}/${externalPort}`) + } + + removePublicAddressMapping (internalIp: string, internalPort: number, externalIp: string, externalPort: number = internalPort, protocol: 'tcp' | 'udp' = 'tcp'): void { + if (this.ipMappings.remove(multiaddr(`/ip${isIPv4(externalIp) ? 4 : 6}/${externalIp}/${protocol}/${externalPort}`))) { + this._updatePeerStoreAddresses() + } + } +} diff --git a/packages/libp2p/src/address-manager/ip-mappings.ts b/packages/libp2p/src/address-manager/ip-mappings.ts new file mode 100644 index 0000000000..76ea109888 --- /dev/null +++ b/packages/libp2p/src/address-manager/ip-mappings.ts @@ -0,0 +1,191 @@ +import { isIPv4 } from '@chainsafe/is-ip' +import { multiaddr, protocols } from '@multiformats/multiaddr' +import type { AddressManagerComponents, AddressManagerInit } from './index.js' +import type { Logger } from '@libp2p/interface' +import type { NodeAddress } from '@libp2p/interface-internal' +import type { Multiaddr } from '@multiformats/multiaddr' + +export const defaultValues = { + maxObservedAddresses: 10 +} + +interface PublicAddressMapping { + internalIp: string + internalPort: number + externalIp: string + externalPort: number + externalFamily: 4 | 6 + protocol: 'tcp' | 'udp' + verified: boolean + expires: number + lastVerified?: number +} + +const CODEC_IP4 = 0x04 +const CODEC_IP6 = 0x29 +const CODEC_TCP = 0x06 +const CODEC_UDP = 0x0111 + +export class IPMappings { + private readonly log: Logger + private readonly mappings: Map + + constructor (components: AddressManagerComponents, init: AddressManagerInit = {}) { + this.log = components.logger.forComponent('libp2p:address-manager:ip-mappings') + this.mappings = new Map() + } + + has (ma: Multiaddr): boolean { + const tuples = ma.stringTuples() + + for (const mappings of this.mappings.values()) { + for (const mapping of mappings) { + if (mapping.externalIp === tuples[0][1]) { + return true + } + } + } + + return false + } + + add (internalIp: string, internalPort: number, externalIp: string, externalPort: number = internalPort, protocol: 'tcp' | 'udp' = 'tcp'): void { + const key = `${internalIp}-${internalPort}-${protocol}` + const mappings = this.mappings.get(key) ?? [] + const mapping: PublicAddressMapping = { + internalIp, + internalPort, + externalIp, + externalPort, + externalFamily: isIPv4(externalIp) ? 4 : 6, + protocol, + verified: false, + expires: 0 + } + mappings.push(mapping) + + this.mappings.set(key, mappings) + } + + remove (ma: Multiaddr): boolean { + const tuples = ma.stringTuples() + const host = tuples[0][1] ?? '' + const protocol = tuples[1][0] === CODEC_TCP ? 'tcp' : 'udp' + const port = parseInt(tuples[1][1] ?? '0') + let wasConfident = false + + for (const [key, mappings] of this.mappings.entries()) { + for (let i = 0; i < mappings.length; i++) { + const mapping = mappings[i] + + if (mapping.externalIp === host && mapping.externalPort === port && mapping.protocol === protocol) { + this.log('removing %s:%s to %s:%s %s IP mapping', mapping.externalIp, mapping.externalPort, host, port, protocol) + + wasConfident = wasConfident || mapping.verified + mappings.splice(i, 1) + i-- + } + } + + if (mappings.length === 0) { + this.mappings.delete(key) + } + } + + return wasConfident + } + + getAll (addresses: NodeAddress[]): NodeAddress[] { + const ipMappedAddresses: NodeAddress[] = [] + + for (const { multiaddr: ma } of addresses) { + const tuples = ma.stringTuples() + let tuple: string | undefined + + // see if the internal host/port/protocol tuple has been mapped externally + if ((tuples[0][0] === CODEC_IP4 || tuples[0][0] === CODEC_IP6) && tuples[1][0] === CODEC_TCP) { + tuple = `${tuples[0][1]}-${tuples[1][1]}-tcp` + } else if ((tuples[0][0] === CODEC_IP4 || tuples[0][0] === CODEC_IP6) && tuples[1][0] === CODEC_UDP) { + tuple = `${tuples[0][1]}-${tuples[1][1]}-udp` + } + + if (tuple == null) { + continue + } + + const mappings = this.mappings.get(tuple) + + if (mappings == null) { + continue + } + + for (const mapping of mappings) { + tuples[0][0] = mapping.externalFamily === 4 ? CODEC_IP4 : CODEC_IP6 + tuples[0][1] = mapping.externalIp + tuples[1][1] = `${mapping.externalPort}` + + ipMappedAddresses.push({ + multiaddr: multiaddr(`/${ + tuples.map(tuple => { + return [ + protocols(tuple[0]).name, + tuple[1] + ].join('/') + }).join('/') + }`), + verified: mapping.verified, + type: 'ip-mapping', + expires: mapping.expires, + lastVerified: mapping.lastVerified + }) + } + } + + return ipMappedAddresses + } + + confirm (ma: Multiaddr, ttl: number): boolean { + const tuples = ma.stringTuples() + const host = tuples[0][1] + let startingConfidence = false + + for (const mappings of this.mappings.values()) { + for (const mapping of mappings) { + // eslint-disable-next-line max-depth + if (mapping.externalIp === host) { + this.log('marking %s to %s IP mapping as verified', mapping.internalIp, mapping.externalIp) + startingConfidence = mapping.verified + mapping.verified = true + mapping.expires = Date.now() + ttl + mapping.lastVerified = Date.now() + } + } + } + + return startingConfidence + } + + unconfirm (ma: Multiaddr, ttl: number): boolean { + const tuples = ma.stringTuples() + const host = tuples[0][1] ?? '' + const protocol = tuples[1][0] === CODEC_TCP ? 'tcp' : 'udp' + const port = parseInt(tuples[1][1] ?? '0') + let wasConfident = false + + for (const mappings of this.mappings.values()) { + for (let i = 0; i < mappings.length; i++) { + const mapping = mappings[i] + + if (mapping.externalIp === host && mapping.externalPort === port && mapping.protocol === protocol) { + this.log('removing verification of %s:%s to %s:%s %s IP mapping', mapping.externalIp, mapping.externalPort, host, port, protocol) + + wasConfident = wasConfident || mapping.verified + mapping.verified = false + mapping.expires = Date.now() + ttl + } + } + } + + return wasConfident + } +} diff --git a/packages/libp2p/src/address-manager/observed-addresses.ts b/packages/libp2p/src/address-manager/observed-addresses.ts new file mode 100644 index 0000000000..379ad88a72 --- /dev/null +++ b/packages/libp2p/src/address-manager/observed-addresses.ts @@ -0,0 +1,94 @@ +import { isLinkLocal } from '@libp2p/utils/multiaddr/is-link-local' +import { isPrivate } from '@libp2p/utils/multiaddr/is-private' +import { multiaddr } from '@multiformats/multiaddr' +import type { AddressManagerComponents, AddressManagerInit } from './index.js' +import type { Logger } from '@libp2p/interface' +import type { NodeAddress } from '@libp2p/interface-internal' +import type { Multiaddr } from '@multiformats/multiaddr' + +export const defaultValues = { + maxObservedAddresses: 10 +} + +interface ObservedAddressMetadata { + verified: boolean + expires: number + lastVerified?: number +} + +export class ObservedAddresses { + private readonly log: Logger + private readonly addresses: Map + private readonly maxObservedAddresses: number + + constructor (components: AddressManagerComponents, init: AddressManagerInit = {}) { + this.log = components.logger.forComponent('libp2p:address-manager:observed-addresses') + this.addresses = new Map() + this.maxObservedAddresses = init.maxObservedAddresses ?? defaultValues.maxObservedAddresses + } + + has (ma: Multiaddr): boolean { + return this.addresses.has(ma.toString()) + } + + removePrefixed (prefix: string): void { + for (const key of this.addresses.keys()) { + if (key.toString().startsWith(prefix)) { + this.addresses.delete(key) + } + } + } + + add (ma: Multiaddr): void { + if (this.addresses.size === this.maxObservedAddresses) { + return + } + + if (isPrivate(ma) || isLinkLocal(ma)) { + return + } + + this.log('adding observed address %a', ma) + this.addresses.set(ma.toString(), { + verified: false, + expires: 0 + }) + } + + getAll (): NodeAddress[] { + return Array.from(this.addresses) + .map(([ma, metadata]) => ({ + multiaddr: multiaddr(ma), + verified: metadata.verified, + type: 'observed', + expires: metadata.expires, + lastVerified: metadata.lastVerified + })) + } + + remove (ma: Multiaddr): boolean { + const startingConfidence = this.addresses.get(ma.toString())?.verified ?? false + + this.log('removing observed address %a', ma) + this.addresses.delete(ma.toString()) + + return startingConfidence + } + + confirm (ma: Multiaddr, ttl: number): boolean { + const addrString = ma.toString() + const metadata = this.addresses.get(addrString) ?? { + verified: false, + expires: Date.now() + ttl, + lastVerified: Date.now() + } + const startingConfidence = metadata.verified + metadata.verified = true + metadata.lastVerified = Date.now() + + this.log('marking observed address %a as verified', addrString) + this.addresses.set(addrString, metadata) + + return startingConfidence + } +} diff --git a/packages/libp2p/src/address-manager/transport-addresses.ts b/packages/libp2p/src/address-manager/transport-addresses.ts new file mode 100644 index 0000000000..edb483a0b8 --- /dev/null +++ b/packages/libp2p/src/address-manager/transport-addresses.ts @@ -0,0 +1,116 @@ +import { isPrivate } from '@libp2p/utils/multiaddr/is-private' +import type { AddressManagerComponents, AddressManagerInit } from './index.js' +import type { Logger } from '@libp2p/interface' +import type { NodeAddress } from '@libp2p/interface-internal' +import type { Multiaddr } from '@multiformats/multiaddr' + +export const defaultValues = { + maxObservedAddresses: 10 +} + +interface TransportAddressMetadata { + verified: boolean + expires: number + lastVerified?: number +} + +export class TransportAddresses { + private readonly log: Logger + private readonly addresses: Map + private readonly maxObservedAddresses: number + + constructor (components: AddressManagerComponents, init: AddressManagerInit = {}) { + this.log = components.logger.forComponent('libp2p:address-manager:observed-addresses') + this.addresses = new Map() + this.maxObservedAddresses = init.maxObservedAddresses ?? defaultValues.maxObservedAddresses + } + + get (multiaddr: Multiaddr, ttl: number): NodeAddress { + if (isPrivate(multiaddr)) { + return { + multiaddr, + verified: true, + type: 'transport', + expires: Date.now() + ttl, + lastVerified: Date.now() + } + } + + const key = this.toKey(multiaddr) + let metadata = this.addresses.get(key) + + if (metadata == null) { + metadata = { + verified: false, + expires: 0 + } + + this.addresses.set(key, metadata) + } + + return { + multiaddr, + verified: metadata.verified, + type: 'transport', + expires: metadata.expires, + lastVerified: metadata.lastVerified + } + } + + has (ma: Multiaddr): boolean { + const key = this.toKey(ma) + return this.addresses.has(key) + } + + remove (ma: Multiaddr): boolean { + const key = this.toKey(ma) + const startingConfidence = this.addresses.get(key)?.verified ?? false + + this.log('removing observed address %a', ma) + this.addresses.delete(key) + + return startingConfidence + } + + confirm (ma: Multiaddr, ttl: number): boolean { + const key = this.toKey(ma) + const metadata = this.addresses.get(key) ?? { + verified: false, + expires: 0, + lastVerified: 0 + } + + const startingConfidence = metadata.verified + + metadata.verified = true + metadata.expires = Date.now() + ttl + metadata.lastVerified = Date.now() + + this.addresses.set(key, metadata) + + return startingConfidence + } + + unconfirm (ma: Multiaddr, ttl: number): boolean { + const key = this.toKey(ma) + const metadata = this.addresses.get(key) ?? { + verified: false, + expires: 0 + } + + const startingConfidence = metadata.verified + + metadata.verified = false + metadata.expires = Date.now() + ttl + + this.addresses.set(key, metadata) + + return startingConfidence + } + + private toKey (ma: Multiaddr): string { + const options = ma.toOptions() + + return `${options.host}-${options.port}-${options.transport}` + } +} diff --git a/packages/libp2p/src/connection-manager/connection-pruner.ts b/packages/libp2p/src/connection-manager/connection-pruner.ts index c780e6a03d..7550bb4140 100644 --- a/packages/libp2p/src/connection-manager/connection-pruner.ts +++ b/packages/libp2p/src/connection-manager/connection-pruner.ts @@ -1,6 +1,8 @@ import { PeerMap } from '@libp2p/peer-collections' import { safelyCloseConnectionIfUnused } from '@libp2p/utils/close' import { MAX_CONNECTIONS } from './constants.js' +import { multiaddrToIpNet } from './utils.js' +import type { IpNet } from '@chainsafe/netmask' import type { Libp2pEvents, Logger, ComponentLogger, TypedEventTarget, PeerStore, Connection } from '@libp2p/interface' import type { ConnectionManager } from '@libp2p/interface-internal' import type { Multiaddr } from '@multiformats/multiaddr' @@ -29,13 +31,13 @@ export class ConnectionPruner { private readonly maxConnections: number private readonly connectionManager: ConnectionManager private readonly peerStore: PeerStore - private readonly allow: Multiaddr[] + private readonly allow: IpNet[] private readonly events: TypedEventTarget private readonly log: Logger constructor (components: ConnectionPrunerComponents, init: ConnectionPrunerInit = {}) { this.maxConnections = init.maxConnections ?? defaultOptions.maxConnections - this.allow = init.allow ?? defaultOptions.allow + this.allow = (init.allow ?? []).map(ma => multiaddrToIpNet(ma)) this.connectionManager = components.connectionManager this.peerStore = components.peerStore this.events = components.events @@ -107,8 +109,8 @@ export class ConnectionPruner { for (const connection of sortedConnections) { this.log('too many connections open - closing a connection to %p', connection.remotePeer) // check allow list - const connectionInAllowList = this.allow.some((ma) => { - return connection.remoteAddr.toString().startsWith(ma.toString()) + const connectionInAllowList = this.allow.some((ipNet) => { + return ipNet.contains(connection.remoteAddr.nodeAddress().address) }) // Connections in the allow list should be excluded from pruning diff --git a/packages/libp2p/src/connection-manager/dial-queue.ts b/packages/libp2p/src/connection-manager/dial-queue.ts index 8300905d19..22d092cce0 100644 --- a/packages/libp2p/src/connection-manager/dial-queue.ts +++ b/packages/libp2p/src/connection-manager/dial-queue.ts @@ -349,7 +349,7 @@ export class DialQueue { this.log('looking up multiaddrs for %p in the peer routing', peerId) try { - const peerInfo = await this.components.peerRouting.findPeer(peerId) + const peerInfo = await this.components.peerRouting.findPeer(peerId, options) this.log('found multiaddrs for %p in the peer routing', peerId, addrs.map(({ multiaddr }) => multiaddr.toString())) diff --git a/packages/libp2p/src/connection-manager/index.ts b/packages/libp2p/src/connection-manager/index.ts index 3d12445578..43ec0ffba6 100644 --- a/packages/libp2p/src/connection-manager/index.ts +++ b/packages/libp2p/src/connection-manager/index.ts @@ -9,6 +9,8 @@ import { ConnectionPruner } from './connection-pruner.js' import { DIAL_TIMEOUT, INBOUND_CONNECTION_THRESHOLD, MAX_CONNECTIONS, MAX_DIAL_QUEUE_LENGTH, MAX_INCOMING_PENDING_CONNECTIONS, MAX_PARALLEL_DIALS, MAX_PEER_ADDRS_TO_DIAL } from './constants.js' import { DialQueue } from './dial-queue.js' import { ReconnectQueue } from './reconnect-queue.js' +import { multiaddrToIpNet } from './utils.js' +import type { IpNet } from '@chainsafe/netmask' import type { PendingDial, AddressSorter, Libp2pEvents, AbortOptions, ComponentLogger, Logger, Connection, MultiaddrConnection, ConnectionGater, TypedEventTarget, Metrics, PeerId, PeerStore, Startable, PendingDialStatus, PeerRouting, IsDialableOptions } from '@libp2p/interface' import type { ConnectionManager, OpenConnectionOptions, TransportManager } from '@libp2p/interface-internal' import type { JobStatus } from '@libp2p/utils/queue' @@ -176,8 +178,8 @@ export interface DefaultConnectionManagerComponents { export class DefaultConnectionManager implements ConnectionManager, Startable { private started: boolean private readonly connections: PeerMap - private readonly allow: Multiaddr[] - private readonly deny: Multiaddr[] + private readonly allow: IpNet[] + private readonly deny: IpNet[] private readonly maxIncomingPendingConnections: number private incomingPendingConnections: number private outboundPendingConnections: number @@ -216,8 +218,8 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { this.onDisconnect = this.onDisconnect.bind(this) // allow/deny lists - this.allow = (init.allow ?? []).map(ma => multiaddr(ma)) - this.deny = (init.deny ?? []).map(ma => multiaddr(ma)) + this.allow = (init.allow ?? []).map(str => multiaddrToIpNet(str)) + this.deny = (init.deny ?? []).map(str => multiaddrToIpNet(str)) this.incomingPendingConnections = 0 this.maxIncomingPendingConnections = init.maxIncomingPendingConnections ?? defaultOptions.maxIncomingPendingConnections @@ -237,7 +239,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { logger: components.logger }, { maxConnections: this.maxConnections, - allow: this.allow + allow: init.allow?.map(a => multiaddr(a)) }) this.dialQueue = new DialQueue(components, { @@ -395,6 +397,10 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { this.log('stopped') } + getMaxConnections (): number { + return this.maxConnections + } + onConnect (evt: CustomEvent): void { void this._onConnect(evt).catch(err => { this.log.error(err) @@ -571,7 +577,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { async acceptIncomingConnection (maConn: MultiaddrConnection): Promise { // check deny list const denyConnection = this.deny.some(ma => { - return maConn.remoteAddr.toString().startsWith(ma.toString()) + return ma.contains(maConn.remoteAddr.nodeAddress().address) }) if (denyConnection) { @@ -580,8 +586,8 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { } // check allow list - const allowConnection = this.allow.some(ma => { - return maConn.remoteAddr.toString().startsWith(ma.toString()) + const allowConnection = this.allow.some(ipNet => { + return ipNet.contains(maConn.remoteAddr.nodeAddress().address) }) if (allowConnection) { diff --git a/packages/libp2p/src/connection-manager/utils.ts b/packages/libp2p/src/connection-manager/utils.ts index b2f657d355..b45bb0fbb3 100644 --- a/packages/libp2p/src/connection-manager/utils.ts +++ b/packages/libp2p/src/connection-manager/utils.ts @@ -1,6 +1,7 @@ -import { resolvers } from '@multiformats/multiaddr' +import { multiaddr, resolvers, type Multiaddr, type ResolveOptions } from '@multiformats/multiaddr' +import { convertToIpNet } from '@multiformats/multiaddr/convert' +import type { IpNet } from '@chainsafe/netmask' import type { LoggerOptions } from '@libp2p/interface' -import type { Multiaddr, ResolveOptions } from '@multiformats/multiaddr' /** * Recursively resolve DNSADDR multiaddrs @@ -28,3 +29,35 @@ export async function resolveMultiaddrs (ma: Multiaddr, options: ResolveOptions return output } + +/** + * Converts a multiaddr string or object to an IpNet object. + * If the multiaddr doesn't include /ipcidr, it will encapsulate with the appropriate CIDR: + * - /ipcidr/32 for IPv4 + * - /ipcidr/128 for IPv6 + * + * @param {string | Multiaddr} ma - The multiaddr string or object to convert. + * @returns {IpNet} The converted IpNet object. + * @throws {Error} Throws an error if the multiaddr is not valid. + */ +export function multiaddrToIpNet (ma: string | Multiaddr): IpNet { + try { + let parsedMa: Multiaddr + if (typeof ma === 'string') { + parsedMa = multiaddr(ma) + } else { + parsedMa = ma + } + + // Check if /ipcidr is already present + if (!parsedMa.protoNames().includes('ipcidr')) { + const isIPv6 = parsedMa.protoNames().includes('ip6') + const cidr = isIPv6 ? '/ipcidr/128' : '/ipcidr/32' + parsedMa = parsedMa.encapsulate(cidr) + } + + return convertToIpNet(parsedMa) + } catch (error) { + throw new Error(`Can't convert to IpNet, Invalid multiaddr format: ${ma}`) + } +} diff --git a/packages/libp2p/src/index.ts b/packages/libp2p/src/index.ts index d05b2883c6..fc68bc0bdf 100644 --- a/packages/libp2p/src/index.ts +++ b/packages/libp2p/src/index.ts @@ -18,7 +18,7 @@ import { generateKeyPair } from '@libp2p/crypto/keys' import { peerIdFromPrivateKey } from '@libp2p/peer-id' import { validateConfig } from './config.js' import { Libp2p as Libp2pClass } from './libp2p.js' -import type { AddressManagerInit, AddressFilter } from './address-manager.js' +import type { AddressManagerInit, AddressFilter } from './address-manager/index.js' import type { Components } from './components.js' import type { ConnectionManagerInit } from './connection-manager/index.js' import type { ConnectionMonitorInit } from './connection-monitor.js' diff --git a/packages/libp2p/src/libp2p.ts b/packages/libp2p/src/libp2p.ts index b88e29dab9..46470e1c27 100644 --- a/packages/libp2p/src/libp2p.ts +++ b/packages/libp2p/src/libp2p.ts @@ -8,7 +8,7 @@ import { isMultiaddr, type Multiaddr } from '@multiformats/multiaddr' import { MemoryDatastore } from 'datastore-core/memory' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { AddressManager } from './address-manager.js' +import { AddressManager } from './address-manager/index.js' import { checkServiceDependencies, defaultComponents } from './components.js' import { connectionGater } from './config/connection-gater.js' import { DefaultConnectionManager } from './connection-manager/index.js' diff --git a/packages/libp2p/test/addresses/address-manager.spec.ts b/packages/libp2p/test/addresses/address-manager.spec.ts index 2cbe8a0427..fc1480073b 100644 --- a/packages/libp2p/test/addresses/address-manager.spec.ts +++ b/packages/libp2p/test/addresses/address-manager.spec.ts @@ -9,7 +9,7 @@ import { expect } from 'aegir/chai' import delay from 'delay' import Sinon from 'sinon' import { type StubbedInstance, stubInterface } from 'sinon-ts' -import { type AddressFilter, AddressManager } from '../../src/address-manager.js' +import { type AddressFilter, AddressManager } from '../../src/address-manager/index.js' import type { TransportManager } from '@libp2p/interface-internal' const listenAddresses = ['/ip4/127.0.0.1/tcp/15006/ws', '/ip4/127.0.0.1/tcp/15008/ws'] @@ -196,6 +196,7 @@ describe('Address Manager', () => { it('should only set addresses once', async () => { const ma = '/ip4/123.123.123.123/tcp/39201' + const ma2 = `${ma.toString()}/p2p/${peerId.toString()}` const am = new AddressManager({ peerId, transportManager: stubInterface({ @@ -206,10 +207,13 @@ describe('Address Manager', () => { logger: defaultLogger() }) + am.addObservedAddr(multiaddr(ma)) + am.addObservedAddr(multiaddr(ma2)) + am.confirmObservedAddr(multiaddr(ma)) am.confirmObservedAddr(multiaddr(ma)) am.confirmObservedAddr(multiaddr(ma)) - am.confirmObservedAddr(multiaddr(`${ma.toString()}/p2p/${peerId.toString()}`)) + am.confirmObservedAddr(multiaddr(ma2)) // wait for address manager _updatePeerStoreAddresses debounce await delay(1500) @@ -291,15 +295,17 @@ describe('Address Manager', () => { const internalPort = 1234 const protocol = 'tcp' - // one loopback, one LAN address + // one loopback, one LAN, one TLS address transportManager.getAddrs.returns([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}`), - multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`) + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws`) ]) expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`), - multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`) + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws/p2p/${peerId}`) ]) const domain = 'example.com' @@ -307,21 +313,25 @@ describe('Address Manager', () => { const externalPort = 4566 am.addDNSMapping(domain, [externalIp]) + am.addPublicAddressMapping(internalIp, internalPort, externalIp, externalPort, 'tcp') // have not verified DNS mapping so it is not included expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`), - multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`) + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws/p2p/${peerId}`) ]) - // public address mapping confirms DNS mapping - am.addPublicAddressMapping(internalIp, internalPort, externalIp, externalPort, protocol) + // confirm public IP and DNS mapping + am.confirmObservedAddr(multiaddr(`/ip4/${externalIp}/tcp/${externalPort}`)) + am.confirmObservedAddr(multiaddr(`/dns4/${domain}/tcp/${externalPort}`)) expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`), multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws/p2p/${peerId}`), multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/p2p/${peerId}`), - multiaddr(`/dns4/${domain}/tcp/${externalPort}/p2p/${peerId}`) + multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/tls/sni/${domain}/ws/p2p/${peerId}`) ]) }) @@ -340,15 +350,17 @@ describe('Address Manager', () => { const internalPort = 1234 const protocol = 'tcp' - // one loopback, one LAN address + // one loopback, one LAN, one TLS address transportManager.getAddrs.returns([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}`), - multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`) + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws`) ]) expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`), - multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`) + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws/p2p/${peerId}`) ]) const domain = 'example.com' @@ -356,21 +368,25 @@ describe('Address Manager', () => { const externalPort = 4566 am.addDNSMapping(domain, [externalIp]) + am.addPublicAddressMapping(internalIp, internalPort, externalIp, externalPort, 'tcp') // have not verified DNS mapping so it is not included expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`), - multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`) + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws/p2p/${peerId}`) ]) - // public address mapping confirms DNS mapping - am.addPublicAddressMapping(internalIp, internalPort, externalIp, externalPort, protocol) + // confirm public IP and DNS mapping + am.confirmObservedAddr(multiaddr(`/ip6/${externalIp}/tcp/${externalPort}`)) + am.confirmObservedAddr(multiaddr(`/dns6/${domain}/tcp/${externalPort}`)) expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`), multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws/p2p/${peerId}`), multiaddr(`/ip6/${externalIp}/tcp/${externalPort}/p2p/${peerId}`), - multiaddr(`/dns6/${domain}/tcp/${externalPort}/p2p/${peerId}`) + multiaddr(`/ip6/${externalIp}/tcp/${externalPort}/tls/sni/${domain}/ws/p2p/${peerId}`) ]) }) @@ -389,10 +405,11 @@ describe('Address Manager', () => { const internalPort = 1234 const protocol = 'tcp' - // one loopback, one LAN address + // one loopback, one LAN, one TLS address transportManager.getAddrs.returns([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}`), - multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`) + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws`) ]) const domain = 'example.com' @@ -402,20 +419,27 @@ describe('Address Manager', () => { am.addDNSMapping(domain, [externalIp]) am.addPublicAddressMapping(internalIp, internalPort, externalIp, externalPort, protocol) + // confirm public IP and DNS mapping + am.confirmObservedAddr(multiaddr(`/ip4/${externalIp}/tcp/${externalPort}`)) + am.confirmObservedAddr(multiaddr(`/dns4/${domain}/tcp/${externalPort}`)) + expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`), multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws/p2p/${peerId}`), multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/p2p/${peerId}`), - multiaddr(`/dns4/${domain}/tcp/${externalPort}/p2p/${peerId}`) + multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/tls/sni/${domain}/ws/p2p/${peerId}`) ]) - // public address mapping confirms DNS mapping + // remove DNS mapping am.removeDNSMapping(domain) expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`), multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`), - multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/p2p/${peerId}`) + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/tls/ws/p2p/${peerId}`), + multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/p2p/${peerId}`), + multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/tls/ws/p2p/${peerId}`) ]) }) @@ -443,6 +467,15 @@ describe('Address Manager', () => { multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`) ]) + // not confirmed the mapping yet + expect(am.getAddresses()).to.deep.equal([ + multiaddr(`/ip4/127.0.0.1/tcp/1234/p2p/${peerId.toString()}`), + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`) + ]) + + // confirm IP mapping + am.confirmObservedAddr(multiaddr(`/ip4/${externalIp}/${protocol}/${externalPort}`)) + // should have mapped the LAN address to the external IP expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/127.0.0.1/tcp/1234/p2p/${peerId.toString()}`), @@ -482,6 +515,15 @@ describe('Address Manager', () => { multiaddr(`/ip6/${internalIp}/${protocol}/${internalPort}`) ]) + // not confirmed the mapping yet + expect(am.getAddresses()).to.deep.equal([ + multiaddr(`/ip6/::1/tcp/1234/p2p/${peerId.toString()}`), + multiaddr(`/ip6/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`) + ]) + + // confirm IP mapping + am.confirmObservedAddr(multiaddr(`/ip6/${externalIp}/${protocol}/${externalPort}`)) + // should have mapped the LAN address to the external IP expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip6/::1/tcp/1234/p2p/${peerId.toString()}`), @@ -507,7 +549,7 @@ describe('Address Manager', () => { logger: defaultLogger() }) - const internalIp = '2a00:23c6:14b1:7e00:28b8:30d:944e:27f3' + const internalIp = 'fdad:23c6:14b1:7e00:28b8:30d:944e:27f3' const internalPort = 4567 const externalIp = '81.12.12.1' const externalPort = 8910 @@ -520,6 +562,9 @@ describe('Address Manager', () => { multiaddr(`/ip6/${internalIp}/${protocol}/${internalPort}`) ]) + // confirm IP mapping + am.confirmObservedAddr(multiaddr(`/ip4/${externalIp}/${protocol}/${externalPort}`)) + // should have mapped the LAN address to the external IP expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip6/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`), @@ -533,6 +578,40 @@ describe('Address Manager', () => { ]) }) + it('should require confirmation of global unicast IPv6 addresses', () => { + const transportManager = stubInterface() + const am = new AddressManager({ + peerId, + transportManager, + peerStore, + events, + logger: defaultLogger() + }) + + const internalIp = '2a01:23c6:14b1:7e00:28b8:30d:944e:27f3' + const internalPort = 4567 + const externalIp = '81.12.12.1' + const externalPort = 8910 + const protocol = 'tcp' + + am.addPublicAddressMapping(internalIp, internalPort, externalIp, externalPort, protocol) + + // one loopback, one LAN address + transportManager.getAddrs.returns([ + multiaddr(`/ip6/${internalIp}/${protocol}/${internalPort}`) + ]) + + expect(am.getAddresses()).to.be.empty() + + // confirm global IP + am.confirmObservedAddr(multiaddr(`/ip6/${internalIp}/${protocol}/${internalPort}`)) + + // should include IP now + expect(am.getAddresses()).to.deep.equal([ + multiaddr(`/ip6/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`) + ]) + }) + it('should add a public IPv6 address mapping when only local IPv4 addresses are present', () => { const transportManager = stubInterface() const am = new AddressManager({ @@ -556,6 +635,9 @@ describe('Address Manager', () => { multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`) ]) + // confirm IP mapping + am.confirmObservedAddr(multiaddr(`/ip6/${externalIp}/${protocol}/${externalPort}`)) + // should have mapped the LAN address to the external IP expect(am.getAddresses()).to.deep.equal([ multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`), @@ -568,4 +650,68 @@ describe('Address Manager', () => { multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`) ]) }) + + it('should not confirm unknown observed addresses', () => { + const transportManager = stubInterface() + const am = new AddressManager({ + peerId, + transportManager, + peerStore, + events, + logger: defaultLogger() + }) + + const internalIp = '192.168.1.123' + const internalPort = 4567 + const externalIp = '2a00:23c6:14b1:7e00:28b8:30d:944e:27f3' + const externalPort = 8910 + const protocol = 'tcp' + + // one loopback, one LAN address + transportManager.getAddrs.returns([ + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`) + ]) + + // confirm address we have not observed + am.confirmObservedAddr(multiaddr(`/ip6/${externalIp}/${protocol}/${externalPort}`)) + + // should not have changed the address list + expect(am.getAddresses()).to.deep.equal([ + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`) + ]) + }) + + it('should confirm unknown observed addresses with hints', () => { + const transportManager = stubInterface() + const am = new AddressManager({ + peerId, + transportManager, + peerStore, + events, + logger: defaultLogger() + }) + + const internalIp = '192.168.1.123' + const internalPort = 4567 + const externalIp = '2a00:23c6:14b1:7e00:28b8:30d:944e:27f3' + const externalPort = 8910 + const protocol = 'tcp' + + // confirm address before fetching addresses + am.confirmObservedAddr(multiaddr(`/ip6/${externalIp}/${protocol}/${externalPort}`), { + type: 'transport' + }) + + // one loopback, one LAN address + transportManager.getAddrs.returns([ + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`), + multiaddr(`/ip6/${externalIp}/${protocol}/${externalPort}`) + ]) + + // should have changed the address list + expect(am.getAddresses()).to.deep.equal([ + multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`), + multiaddr(`/ip6/${externalIp}/${protocol}/${externalPort}/p2p/${peerId.toString()}`) + ]) + }) }) diff --git a/packages/libp2p/test/connection-manager/connection-pruner.spec.ts b/packages/libp2p/test/connection-manager/connection-pruner.spec.ts index 8f93fae1f4..8d41fe4ac3 100644 --- a/packages/libp2p/test/connection-manager/connection-pruner.spec.ts +++ b/packages/libp2p/test/connection-manager/connection-pruner.spec.ts @@ -219,6 +219,44 @@ describe('connection-pruner', () => { expect(shortestLivedWithLowestTagSpy).to.have.property('callCount', 1) }) + it('should correctly parse and store allow list as IpNet objects in ConnectionPruner', () => { + const mockInit = { + allow: [ + multiaddr('/ip4/83.13.55.32/ipcidr/32'), + multiaddr('/ip4/83.13.55.32'), + multiaddr('/ip4/192.168.1.1/ipcidr/24'), + multiaddr('/ip6/2001::0/ipcidr/64') + ] + } + + // Create a ConnectionPruner instance + const pruner = new ConnectionPruner(components, mockInit) + + // Expected IpNet objects for comparison + const expectedAllowList = [ + { + mask: new Uint8Array([255, 255, 255, 255]), + network: new Uint8Array([83, 13, 55, 32]) + }, + { + mask: new Uint8Array([255, 255, 255, 255]), + network: new Uint8Array([83, 13, 55, 32]) + }, + { + mask: new Uint8Array([255, 255, 255, 0]), + network: new Uint8Array([192, 168, 1, 0]) + }, + { + mask: new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0]), + network: new Uint8Array([32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + } + ] + + // Verify that the allow list in the pruner matches the expected IpNet objects + // eslint-disable-next-line @typescript-eslint/dot-notation + expect(pruner['allow']).to.deep.equal(expectedAllowList) + }) + it('should not close connection that is on the allowlist when pruning', async () => { const max = 2 const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') @@ -241,6 +279,7 @@ describe('connection-pruner', () => { for (let i = 0; i < max; i++) { const connection = stubInterface({ remotePeer: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), + remoteAddr: multiaddr('/ip4/127.0.0.1/tcp/12345'), streams: [] }) const spy = connection.close @@ -269,7 +308,6 @@ describe('connection-pruner', () => { const value = 0 const spy = connection.close spies.set(value, spy) - // Tag that allowed peer with lowest value components.peerStore.get.withArgs(connection.remotePeer).resolves(stubInterface({ tags: new Map([['test-tag', { value }]]) diff --git a/packages/libp2p/test/connection-manager/index.spec.ts b/packages/libp2p/test/connection-manager/index.spec.ts index 32556dc624..d7de4ef0e9 100644 --- a/packages/libp2p/test/connection-manager/index.spec.ts +++ b/packages/libp2p/test/connection-manager/index.spec.ts @@ -33,6 +33,48 @@ describe('Connection Manager', () => { await stop(connectionManager, libp2p) }) + it('should correctly parse and store allow and deny lists as IpNet objects in ConnectionManager', () => { + // Define common IPs and CIDRs for reuse + const ipAllowDeny = [ + '/ip4/83.13.55.32', // Single IP address + '/ip4/83.13.55.32/ipcidr/32', // CIDR notation for a single IP + '/ip4/192.168.1.1/ipcidr/24' // CIDR notation for a network + ] + + // Initialize mock input for the allow and deny lists + const mockInit = { + allow: [...ipAllowDeny], + deny: [...ipAllowDeny] + } + + // Create an instance of the DefaultConnectionManager with the mock initialization + const connectionManager = new DefaultConnectionManager(components, mockInit) + + // Define the expected IpNet objects that should result from parsing the allow and deny lists + const expectedIpNets = [ + { + mask: new Uint8Array([255, 255, 255, 255]), // Netmask for a single IP address + network: new Uint8Array([83, 13, 55, 32]) // Network address for '83.13.55.32' + }, + { + mask: new Uint8Array([255, 255, 255, 255]), // Netmask for a single IP address + network: new Uint8Array([83, 13, 55, 32]) // Network address for '83.13.55.32' + }, + { + mask: new Uint8Array([255, 255, 255, 0]), // Netmask for a /24 CIDR block + network: new Uint8Array([192, 168, 1, 0]) // Network address for '192.168.1.0' + } + ] + + // Test that the 'allow' list is correctly parsed and stored as IpNet objects + // eslint-disable-next-line @typescript-eslint/dot-notation + expect(connectionManager['allow']).to.deep.equal(expectedIpNets) + + // Test that the 'deny' list is correctly parsed and stored as IpNet objects + // eslint-disable-next-line @typescript-eslint/dot-notation + expect(connectionManager['deny']).to.deep.equal(expectedIpNets) + }) + it('should fail if the connection manager has mismatched connection limit options', async () => { await expect( createLibp2p({ @@ -43,6 +85,17 @@ describe('Connection Manager', () => { ).to.eventually.rejected('maxConnections must be greater') }) + it('should return the max allowed connections', async () => { + const maxConnections = 10 + + connectionManager = new DefaultConnectionManager(components, { + ...defaultOptions, + maxConnections + }) + + expect(connectionManager.getMaxConnections()).to.equal(maxConnections) + }) + it('should reconnect to important peers on startup', async () => { const peerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) @@ -72,7 +125,7 @@ describe('Connection Manager', () => { expect(connectionManagerOpenConnectionSpy.getCall(0).args[0].toString()).to.equal(peerId.toString(), 'Attempted to connect to the wrong peer') }) - it('should deny connections from denylist multiaddrs', async () => { + it('should deny connections from denylist multiaddrs (IPv4)', async () => { const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') connectionManager = new DefaultConnectionManager(components, { ...defaultOptions, @@ -90,6 +143,53 @@ describe('Connection Manager', () => { .to.eventually.be.false() }) + it('should allow connections from allowlist multiaddrs (IPv6)', async () => { + const remoteAddr = multiaddr('/ip6/2001:db8::1/tcp/59283') + const connectionManager = new DefaultConnectionManager(components, { + ...defaultOptions, + maxConnections: 1, + allow: [ + '/ip6/2001:db8::1' + ] + }) + await connectionManager.start() + + sinon.stub(connectionManager.dialQueue, 'dial').resolves(stubInterface({ + remotePeer: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), + status: 'open' + })) + + // max out the connection limit + await connectionManager.openConnection(peerIdFromPrivateKey(await generateKeyPair('Ed25519'))) + expect(connectionManager.getConnections()).to.have.lengthOf(1) + + // an inbound connection is opened from an address in the allow list + const maConn = stubInterface({ + remoteAddr + }) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.true() + }) + + it('should deny connections from denylist multiaddrs (IPv6)', async () => { + const remoteAddr = multiaddr('/ip6/2001:db8::1/tcp/59283') + const connectionManager = new DefaultConnectionManager(components, { + ...defaultOptions, + deny: [ + '/ip6/2001:db8::1' + ] + }) + await connectionManager.start() + + const maConn = stubInterface({ + remoteAddr + }) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.false() + }) + it('should deny connections when maxConnections is exceeded', async () => { connectionManager = new DefaultConnectionManager(components, { ...defaultOptions, @@ -141,7 +241,7 @@ describe('Connection Manager', () => { .to.eventually.be.false() }) - it('should allow connections from allowlist multiaddrs', async () => { + it('should allow connections from allowlist multiaddrs (IPv4)', async () => { const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') connectionManager = new DefaultConnectionManager(components, { ...defaultOptions, @@ -170,6 +270,80 @@ describe('Connection Manager', () => { .to.eventually.be.true() }) + it('should allow connections from allowlist subnet (IPv4)', async () => { + const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') + const connectionManager = new DefaultConnectionManager(components, { + ...defaultOptions, + maxConnections: 1, + allow: [ + '/ip4/83.13.55.0/ipcidr/24' // Allow IPv4 subnet /24 + ] + }) + await connectionManager.start() + + const maConn = stubInterface({ + remoteAddr + }) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.true() + }) + + it('should deny connections from denylist subnet (IPv4)', async () => { + const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') + const connectionManager = new DefaultConnectionManager(components, { + ...defaultOptions, + deny: [ + '/ip4/83.13.55.0/ipcidr/24' // Deny IPv4 subnet /24 + ] + }) + await connectionManager.start() + + const maConn = stubInterface({ + remoteAddr + }) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.false() + }) + + it('should allow connections from allowlist subnet (IPv6)', async () => { + const remoteAddr = multiaddr('/ip6/2001:db8::1/tcp/59283') + const connectionManager = new DefaultConnectionManager(components, { + ...defaultOptions, + maxConnections: 1, + allow: [ + '/ip6/2001:db8::/ipcidr/64' // Allow an IPv6 subnet + ] + }) + await connectionManager.start() + + const maConn = stubInterface({ + remoteAddr + }) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.true() + }) + + it('should deny connections from denylist subnet (IPv6)', async () => { + const remoteAddr = multiaddr('/ip6/2001:db8::1/tcp/59283') + const connectionManager = new DefaultConnectionManager(components, { + ...defaultOptions, + deny: [ + '/ip6/2001:db8::/ipcidr/64' // Deny an IPv6 subnet + ] + }) + await connectionManager.start() + + const maConn = stubInterface({ + remoteAddr + }) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.false() + }) + it('should limit the number of inbound pending connections', async () => { connectionManager = new DefaultConnectionManager(components, { ...defaultOptions, diff --git a/packages/libp2p/test/connection-manager/multiaddr-to-ipnet.spec.ts b/packages/libp2p/test/connection-manager/multiaddr-to-ipnet.spec.ts new file mode 100644 index 0000000000..1590d0d7b6 --- /dev/null +++ b/packages/libp2p/test/connection-manager/multiaddr-to-ipnet.spec.ts @@ -0,0 +1,62 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import { multiaddrToIpNet } from '../../src/connection-manager/utils.js' + +describe('multiaddrToIpNet', () => { + it('should convert a simple IPv4 multiaddr to an IpNet', () => { + const ma = '/ip4/127.0.0.1' + const ipNet = multiaddrToIpNet(ma) + expect(ipNet.toString()).to.equal('127.0.0.1/32') + }) + + it('should convert a multiaddr with an IPv4 ipcidr to an IpNet', () => { + const ma = '/ip4/127.0.0.1/ipcidr/32' + const ipNet = multiaddrToIpNet(ma) + expect(ipNet.toString()).to.equal('127.0.0.1/32') + }) + + it('should convert a simple IPv6 multiaddr to an IpNet', () => { + const ma = '/ip6/::1/ipcidr/128' + const ipNet = multiaddrToIpNet(ma) + expect(ipNet.toString()).to.equal('0000:0000:0000:0000:0000:0000:0000:0001/128') + }) + + it('should convert a multiaddr with an IPv6 ipcidr to an IpNet', () => { + const ma = '/ip6/2001:db8::/ipcidr/64' + const ipNet = multiaddrToIpNet(ma) + expect(ipNet.toString()).to.equal('2001:0db8:0000:0000:0000:0000:0000:0000/64') + }) + + it('should throw an error for invalid multiaddr', () => { + const ma = '/ip6/invalid::address' + expect(() => multiaddrToIpNet(ma)).to.throw(Error, 'Invalid multiaddr') + }) + + it('should expand shorthand IPv6 addresses to full form', () => { + const ma = '/ip6/2001:db8::1/ipcidr/128' + const ipNet = multiaddrToIpNet(ma) + expect(ipNet.toString()).to.equal('2001:0db8:0000:0000:0000:0000:0000:0001/128') + }) + + it('should throw an error for invalid CIDR value in IPv4 and IPv6 multiaddr', () => { + const invalidIPv6 = '/ip6/2001:db8::1/ipcidr/256' + const invalidIPv4 = '/ip4/192.168.1.1/ipcidr/33' + + expect(() => multiaddrToIpNet(invalidIPv6)).to.throw(Error, 'Invalid multiaddr format') + expect(() => multiaddrToIpNet(invalidIPv4)).to.throw(Error, 'Invalid multiaddr format') + }) + + it('should handle IPv6 address with different prefix lengths (e.g., /0, /48, /128)', () => { + const testCases = [ + { ma: '/ip6/2001:db8::/ipcidr/0', expected: '0000:0000:0000:0000:0000:0000:0000:0000/0' }, + { ma: '/ip6/2001:db8:abcd:0000::1/ipcidr/48', expected: '2001:0db8:abcd:0000:0000:0000:0000:0000/48' }, + { ma: '/ip6/2001:db8:abcd:1234::1/ipcidr/128', expected: '2001:0db8:abcd:1234:0000:0000:0000:0001/128' } + ] + + testCases.forEach(({ ma, expected }) => { + const ipNet = multiaddrToIpNet(ma) + expect(ipNet.toString()).to.equal(expected) + }) + }) +}) diff --git a/packages/libp2p/test/core/consume-peer-record.spec.ts b/packages/libp2p/test/core/consume-peer-record.spec.ts deleted file mode 100644 index f3da3952bd..0000000000 --- a/packages/libp2p/test/core/consume-peer-record.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-env mocha */ - -import { multiaddr } from '@multiformats/multiaddr' -import { createLibp2p } from '../../src/index.js' -import type { Libp2p } from '@libp2p/interface' - -describe('Consume peer record', () => { - let libp2p: Libp2p - - beforeEach(async () => { - libp2p = await createLibp2p() - }) - - afterEach(async () => { - await libp2p.stop() - }) - - it('should update addresses when observed addrs are confirmed', async () => { - let done: () => void - - libp2p.peerStore.patch = async () => { - done() - return {} as any - } - - const p = new Promise(resolve => { - done = resolve - }) - - await libp2p.start() - - // @ts-expect-error components field is private - libp2p.components.addressManager.confirmObservedAddr(multiaddr('/ip4/123.123.123.123/tcp/3983')) - - await p - - await libp2p.stop() - }) -}) diff --git a/packages/libp2p/test/transports/transport-manager.spec.ts b/packages/libp2p/test/transports/transport-manager.spec.ts index 2ee34f1b69..010aec0366 100644 --- a/packages/libp2p/test/transports/transport-manager.spec.ts +++ b/packages/libp2p/test/transports/transport-manager.spec.ts @@ -12,7 +12,7 @@ import { pEvent } from 'p-event' import pWaitFor from 'p-wait-for' import Sinon from 'sinon' import { stubInterface } from 'sinon-ts' -import { AddressManager } from '../../src/address-manager.js' +import { AddressManager } from '../../src/address-manager/index.js' import { DefaultTransportManager } from '../../src/transport-manager.js' import type { Components } from '../../src/components.js' import type { Connection, Transport, Upgrader, Listener } from '@libp2p/interface' diff --git a/packages/logger/CHANGELOG.md b/packages/logger/CHANGELOG.md index d40af4d54f..5bcdf5153e 100644 --- a/packages/logger/CHANGELOG.md +++ b/packages/logger/CHANGELOG.md @@ -21,6 +21,17 @@ * devDependencies * @libp2p/peer-id bumped from ^4.0.2 to ^4.0.3 +## [5.1.5](https://github.com/libp2p/js-libp2p/compare/logger-v5.1.4...logger-v5.1.5) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * devDependencies + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + ## [5.1.4](https://github.com/libp2p/js-libp2p/compare/logger-v5.1.3...logger-v5.1.4) (2024-11-18) diff --git a/packages/logger/package.json b/packages/logger/package.json index fba9c5d547..886de25665 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/logger", - "version": "5.1.4", + "version": "5.1.5", "description": "A logging component for use in js-libp2p modules", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/logger#readme", @@ -54,14 +54,14 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "^2.2.1", + "@libp2p/interface": "^2.3.0", "@multiformats/multiaddr": "^12.3.3", "interface-datastore": "^8.3.1", "multiformats": "^13.3.1", "weald": "^1.0.4" }, "devDependencies": { - "@libp2p/peer-id": "^5.0.8", + "@libp2p/peer-id": "^5.0.9", "aegir": "^45.0.5", "sinon": "^19.0.2", "uint8arrays": "^5.1.0" diff --git a/packages/metrics-devtools/CHANGELOG.md b/packages/metrics-devtools/CHANGELOG.md index 319a60e391..cf8f0b7985 100644 --- a/packages/metrics-devtools/CHANGELOG.md +++ b/packages/metrics-devtools/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [1.1.12](https://github.com/libp2p/js-libp2p/compare/devtools-metrics-v1.1.11...devtools-metrics-v1.1.12) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [1.1.11](https://github.com/libp2p/js-libp2p/compare/devtools-metrics-v1.1.10...devtools-metrics-v1.1.11) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/simple-metrics bumped from ^1.2.7 to ^1.2.8 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + ## [1.1.10](https://github.com/libp2p/js-libp2p/compare/devtools-metrics-v1.1.9...devtools-metrics-v1.1.10) (2024-11-18) diff --git a/packages/metrics-devtools/package.json b/packages/metrics-devtools/package.json index 80aa0e36b1..b9788d7c0a 100644 --- a/packages/metrics-devtools/package.json +++ b/packages/metrics-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/devtools-metrics", - "version": "1.1.10", + "version": "1.1.12", "description": "Collect libp2p metrics and send them to browser DevTools", "author": "", "license": "Apache-2.0 OR MIT", @@ -68,11 +68,11 @@ "test:firefox": "aegir test -t browser --browser firefox" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/simple-metrics": "^1.2.7", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/simple-metrics": "^1.2.8", "@multiformats/multiaddr": "^12.3.3", "cborg": "^4.2.6", "it-pipe": "^3.0.1", @@ -82,7 +82,7 @@ "progress-events": "^1.0.1" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", + "@libp2p/crypto": "^5.0.8", "aegir": "^45.0.5", "sinon-ts": "^2.0.0" }, diff --git a/packages/metrics-prometheus/CHANGELOG.md b/packages/metrics-prometheus/CHANGELOG.md index 19efdf7705..bd6473c18c 100644 --- a/packages/metrics-prometheus/CHANGELOG.md +++ b/packages/metrics-prometheus/CHANGELOG.md @@ -81,6 +81,29 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [4.2.9](https://github.com/libp2p/js-libp2p/compare/prometheus-metrics-v4.2.8...prometheus-metrics-v4.2.9) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [4.2.8](https://github.com/libp2p/js-libp2p/compare/prometheus-metrics-v4.2.7...prometheus-metrics-v4.2.8) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + ## [4.2.7](https://github.com/libp2p/js-libp2p/compare/prometheus-metrics-v4.2.6...prometheus-metrics-v4.2.7) (2024-11-19) diff --git a/packages/metrics-prometheus/package.json b/packages/metrics-prometheus/package.json index 1927572396..3d93525114 100644 --- a/packages/metrics-prometheus/package.json +++ b/packages/metrics-prometheus/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/prometheus-metrics", - "version": "4.2.7", + "version": "4.2.9", "description": "Collect libp2p metrics for scraping by Prometheus or Graphana", "author": "", "license": "Apache-2.0 OR MIT", @@ -48,17 +48,17 @@ "test:electron-main": "aegir test -t electron-main --cov" }, "dependencies": { - "@libp2p/interface": "^2.2.1", + "@libp2p/interface": "^2.3.0", "it-foreach": "^2.1.1", "it-stream-types": "^2.0.2", "prom-client": "^15.1.3", "uint8arraylist": "^2.4.8" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-id": "^5.0.9", "@multiformats/multiaddr": "^12.3.3", "aegir": "^45.0.5", "it-drain": "^3.0.7", diff --git a/packages/metrics-simple/CHANGELOG.md b/packages/metrics-simple/CHANGELOG.md index ae7618bbee..b43b87ad16 100644 --- a/packages/metrics-simple/CHANGELOG.md +++ b/packages/metrics-simple/CHANGELOG.md @@ -5,6 +5,16 @@ * track stream metrics ([#2](https://github.com/libp2p/js-libp2p-simple-metrics/issues/2)) ([caafb3d](https://github.com/libp2p/js-libp2p-simple-metrics/commit/caafb3d103fd7df0a2a4e6b3e800f4bc9c35c58f)) +## [1.2.8](https://github.com/libp2p/js-libp2p/compare/simple-metrics-v1.2.7...simple-metrics-v1.2.8) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [1.2.7](https://github.com/libp2p/js-libp2p/compare/simple-metrics-v1.2.6...simple-metrics-v1.2.7) (2024-11-18) diff --git a/packages/metrics-simple/package.json b/packages/metrics-simple/package.json index fad82746cb..16c290a44c 100644 --- a/packages/metrics-simple/package.json +++ b/packages/metrics-simple/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/simple-metrics", - "version": "1.2.7", + "version": "1.2.8", "description": "Simple in-memory metrics gathering for libp2p", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/metrics-simple#readme", @@ -49,8 +49,8 @@ "dep-check": "aegir dep-check -i events" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/logger": "^5.1.4", + "@libp2p/interface": "^2.3.0", + "@libp2p/logger": "^5.1.5", "it-foreach": "^2.1.1", "it-stream-types": "^2.0.2", "tdigest": "^0.1.2" diff --git a/packages/multistream-select/CHANGELOG.md b/packages/multistream-select/CHANGELOG.md index 882977bd3a..4b8d13c8a7 100644 --- a/packages/multistream-select/CHANGELOG.md +++ b/packages/multistream-select/CHANGELOG.md @@ -19,6 +19,17 @@ * devDependencies * @libp2p/logger bumped from ^4.0.1 to ^4.0.2 +## [6.0.10](https://github.com/libp2p/js-libp2p/compare/multistream-select-v6.0.9...multistream-select-v6.0.10) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [6.0.9](https://github.com/libp2p/js-libp2p/compare/multistream-select-v6.0.8...multistream-select-v6.0.9) (2024-11-18) diff --git a/packages/multistream-select/package.json b/packages/multistream-select/package.json index 80557af2ef..b1aab8ba4c 100644 --- a/packages/multistream-select/package.json +++ b/packages/multistream-select/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/multistream-select", - "version": "6.0.9", + "version": "6.0.10", "description": "JavaScript implementation of multistream-select", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/multistream-select#readme", @@ -58,7 +58,7 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "^2.2.1", + "@libp2p/interface": "^2.3.0", "it-length-prefixed": "^9.1.0", "it-length-prefixed-stream": "^1.2.0", "it-stream-types": "^2.0.2", @@ -69,7 +69,7 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "iso-random-stream": "^2.0.2", "it-all": "^3.0.6", diff --git a/packages/peer-collections/CHANGELOG.md b/packages/peer-collections/CHANGELOG.md index e95a5b7f24..f5bac5def5 100644 --- a/packages/peer-collections/CHANGELOG.md +++ b/packages/peer-collections/CHANGELOG.md @@ -35,6 +35,19 @@ * devDependencies * @libp2p/peer-id-factory bumped from ^4.0.3 to ^4.0.4 +## [6.0.13](https://github.com/libp2p/js-libp2p/compare/peer-collections-v6.0.12...peer-collections-v6.0.13) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + ## [6.0.12](https://github.com/libp2p/js-libp2p/compare/peer-collections-v6.0.11...peer-collections-v6.0.12) (2024-11-18) diff --git a/packages/peer-collections/package.json b/packages/peer-collections/package.json index 569b8b14ea..bf27656c1c 100644 --- a/packages/peer-collections/package.json +++ b/packages/peer-collections/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/peer-collections", - "version": "6.0.12", + "version": "6.0.13", "description": "Stores values against a peer id", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/peer-collections#readme", @@ -54,13 +54,13 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/utils": "^6.3.0", "multiformats": "^13.3.1" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", + "@libp2p/crypto": "^5.0.8", "@types/sinon": "^17.0.3", "aegir": "^45.0.5", "sinon": "^19.0.2", diff --git a/packages/peer-discovery-bootstrap/CHANGELOG.md b/packages/peer-discovery-bootstrap/CHANGELOG.md index e0a69befa6..94d1b35583 100644 --- a/packages/peer-discovery-bootstrap/CHANGELOG.md +++ b/packages/peer-discovery-bootstrap/CHANGELOG.md @@ -83,6 +83,31 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [11.0.15](https://github.com/libp2p/js-libp2p/compare/bootstrap-v11.0.14...bootstrap-v11.0.15) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [11.0.14](https://github.com/libp2p/js-libp2p/compare/bootstrap-v11.0.13...bootstrap-v11.0.14) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [11.0.13](https://github.com/libp2p/js-libp2p/compare/bootstrap-v11.0.12...bootstrap-v11.0.13) (2024-11-19) diff --git a/packages/peer-discovery-bootstrap/package.json b/packages/peer-discovery-bootstrap/package.json index f5d7b38f19..99f33b348b 100644 --- a/packages/peer-discovery-bootstrap/package.json +++ b/packages/peer-discovery-bootstrap/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/bootstrap", - "version": "11.0.13", + "version": "11.0.15", "description": "Peer discovery via a list of bootstrap peers", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap#readme", @@ -54,15 +54,15 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/peer-id": "^5.0.9", "@multiformats/mafmt": "^12.1.6", "@multiformats/multiaddr": "^12.3.3" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "sinon-ts": "^2.0.0" }, diff --git a/packages/peer-discovery-mdns/CHANGELOG.md b/packages/peer-discovery-mdns/CHANGELOG.md index 56920d9315..29f677593a 100644 --- a/packages/peer-discovery-mdns/CHANGELOG.md +++ b/packages/peer-discovery-mdns/CHANGELOG.md @@ -105,6 +105,33 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [11.0.15](https://github.com/libp2p/js-libp2p/compare/mdns-v11.0.14...mdns-v11.0.15) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [11.0.14](https://github.com/libp2p/js-libp2p/compare/mdns-v11.0.13...mdns-v11.0.14) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [11.0.13](https://github.com/libp2p/js-libp2p/compare/mdns-v11.0.12...mdns-v11.0.13) (2024-11-19) diff --git a/packages/peer-discovery-mdns/package.json b/packages/peer-discovery-mdns/package.json index 12c6ffe54f..961eb9a960 100644 --- a/packages/peer-discovery-mdns/package.json +++ b/packages/peer-discovery-mdns/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/mdns", - "version": "11.0.13", + "version": "11.0.15", "description": "Node.js libp2p mDNS discovery implementation for peer discovery", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/peer-discovery-mdns#readme", @@ -50,19 +50,19 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@types/multicast-dns": "^7.2.4", "dns-packet": "^5.6.1", "multicast-dns": "^7.2.5" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "p-wait-for": "^5.0.2", "sinon-ts": "^2.0.0" diff --git a/packages/peer-id/CHANGELOG.md b/packages/peer-id/CHANGELOG.md index 415c5411bc..7f9f75d836 100644 --- a/packages/peer-id/CHANGELOG.md +++ b/packages/peer-id/CHANGELOG.md @@ -11,6 +11,16 @@ * dependencies * @libp2p/interface bumped from ^1.0.1 to ^1.0.2 +## [5.0.9](https://github.com/libp2p/js-libp2p/compare/peer-id-v5.0.8...peer-id-v5.0.9) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + ## [5.0.8](https://github.com/libp2p/js-libp2p/compare/peer-id-v5.0.7...peer-id-v5.0.8) (2024-11-18) diff --git a/packages/peer-id/package.json b/packages/peer-id/package.json index 8f3f493834..bd14b0e3ec 100644 --- a/packages/peer-id/package.json +++ b/packages/peer-id/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/peer-id", - "version": "5.0.8", + "version": "5.0.9", "description": "Implementation of @libp2p/interface-peer-id", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/peer-id#readme", @@ -54,8 +54,8 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", "multiformats": "^13.3.1", "uint8arrays": "^5.1.0" }, diff --git a/packages/peer-record/CHANGELOG.md b/packages/peer-record/CHANGELOG.md index 7950e592df..86605d5d21 100644 --- a/packages/peer-record/CHANGELOG.md +++ b/packages/peer-record/CHANGELOG.md @@ -60,6 +60,18 @@ * dependencies * @libp2p/utils bumped from ^5.2.4 to ^5.2.5 +## [8.0.13](https://github.com/libp2p/js-libp2p/compare/peer-record-v8.0.12...peer-record-v8.0.13) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + ## [8.0.12](https://github.com/libp2p/js-libp2p/compare/peer-record-v8.0.11...peer-record-v8.0.12) (2024-11-18) diff --git a/packages/peer-record/package.json b/packages/peer-record/package.json index 82a449d2b2..1b9aae0521 100644 --- a/packages/peer-record/package.json +++ b/packages/peer-record/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/peer-record", - "version": "8.0.12", + "version": "8.0.13", "description": "Used to transfer signed peer data across the network", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/peer-record#readme", @@ -61,10 +61,10 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/utils": "^6.2.1", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "multiformats": "^13.3.1", "protons-runtime": "^5.5.0", diff --git a/packages/peer-store/CHANGELOG.md b/packages/peer-store/CHANGELOG.md index 1e39e6d8e9..3dd6ee3ec1 100644 --- a/packages/peer-store/CHANGELOG.md +++ b/packages/peer-store/CHANGELOG.md @@ -68,6 +68,26 @@ * dependencies * @libp2p/peer-record bumped from ^7.0.8 to ^7.0.9 +## [11.0.13](https://github.com/libp2p/js-libp2p/compare/peer-store-v11.0.12...peer-store-v11.0.13) (2024-12-09) + + +### Bug Fixes + +* ignore corrupt peerstore data ([#2859](https://github.com/libp2p/js-libp2p/issues/2859)) ([f2f9008](https://github.com/libp2p/js-libp2p/commit/f2f9008b8e7c634a3855fea746af0762af920beb)) +* only log when data is invalid ([#2862](https://github.com/libp2p/js-libp2p/issues/2862)) ([a0c8ceb](https://github.com/libp2p/js-libp2p/commit/a0c8ceb9917518e82587dab1be71f02aa7a6a52c)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/peer-record bumped from ^8.0.12 to ^8.0.13 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [11.0.12](https://github.com/libp2p/js-libp2p/compare/peer-store-v11.0.11...peer-store-v11.0.12) (2024-11-18) diff --git a/packages/peer-store/package.json b/packages/peer-store/package.json index 3475cb59cb..fb5e2e8439 100644 --- a/packages/peer-store/package.json +++ b/packages/peer-store/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/peer-store", - "version": "11.0.12", + "version": "11.0.13", "description": "Stores information about peers libp2p knows on the network", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/peer-store#readme", @@ -59,10 +59,10 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/peer-record": "^8.0.12", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/peer-record": "^8.0.13", "@multiformats/multiaddr": "^12.3.3", "interface-datastore": "^8.3.1", "it-all": "^3.0.6", @@ -73,7 +73,7 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", + "@libp2p/logger": "^5.1.5", "@types/sinon": "^17.0.3", "aegir": "^45.0.5", "datastore-core": "^10.0.2", diff --git a/packages/pnet/CHANGELOG.md b/packages/pnet/CHANGELOG.md index 0d05744999..f5074cb698 100644 --- a/packages/pnet/CHANGELOG.md +++ b/packages/pnet/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## [2.0.15](https://github.com/libp2p/js-libp2p/compare/pnet-v2.0.14...pnet-v2.0.15) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [2.0.14](https://github.com/libp2p/js-libp2p/compare/pnet-v2.0.13...pnet-v2.0.14) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + ## [2.0.13](https://github.com/libp2p/js-libp2p/compare/pnet-v2.0.12...pnet-v2.0.13) (2024-11-19) diff --git a/packages/pnet/package.json b/packages/pnet/package.json index e862591272..b179fc940b 100644 --- a/packages/pnet/package.json +++ b/packages/pnet/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/pnet", - "version": "2.0.13", + "version": "2.0.15", "description": "Implementation of Connection protection management via a shared secret", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/pnet#readme", @@ -50,8 +50,8 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", "it-byte-stream": "^1.1.0", "it-map": "^3.1.1", "it-pair": "^2.0.6", @@ -62,9 +62,9 @@ "xsalsa20": "^1.2.0" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-id": "^5.0.9", "@multiformats/multiaddr": "^12.3.3", "@types/xsalsa20": "^1.1.3", "aegir": "^45.0.5", diff --git a/packages/protocol-autonat/CHANGELOG.md b/packages/protocol-autonat/CHANGELOG.md index 643d435733..5a69424ea3 100644 --- a/packages/protocol-autonat/CHANGELOG.md +++ b/packages/protocol-autonat/CHANGELOG.md @@ -55,6 +55,37 @@ * dependencies * @libp2p/utils bumped from ^5.2.4 to ^5.2.5 +## [2.0.14](https://github.com/libp2p/js-libp2p/compare/autonat-v2.0.13...autonat-v2.0.14) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [2.0.13](https://github.com/libp2p/js-libp2p/compare/autonat-v2.0.12...autonat-v2.0.13) (2024-12-09) + + +### Bug Fixes + +* check max connections before reverifying addresses ([#2879](https://github.com/libp2p/js-libp2p/issues/2879)) ([9614de7](https://github.com/libp2p/js-libp2p/commit/9614de7c63d5dfad71fdad533b9be650d885205d)) +* require external confirmation of public addresses ([#2867](https://github.com/libp2p/js-libp2p/issues/2867)) ([d19974d](https://github.com/libp2p/js-libp2p/commit/d19974d93a1015acfca95c2155dbcffc5fd6a6c0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/peer-collections bumped from ^6.0.12 to ^6.0.13 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [2.0.12](https://github.com/libp2p/js-libp2p/compare/autonat-v2.0.11...autonat-v2.0.12) (2024-11-18) diff --git a/packages/protocol-autonat/package.json b/packages/protocol-autonat/package.json index 98e2e353de..65b3332619 100644 --- a/packages/protocol-autonat/package.json +++ b/packages/protocol-autonat/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/autonat", - "version": "2.0.12", + "version": "2.0.14", "description": "Implementation of Autonat Protocol", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/protocol-autonat#readme", @@ -52,26 +52,28 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/peer-collections": "^6.0.13", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", - "it-first": "^3.0.6", - "it-length-prefixed": "^9.1.0", - "it-map": "^3.1.1", - "it-parallel": "^3.0.8", - "it-pipe": "^3.0.1", + "any-signal": "^4.1.1", + "it-protobuf-stream": "^1.1.5", "multiformats": "^13.3.1", "protons-runtime": "^5.5.0", "uint8arraylist": "^2.4.8" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/logger": "^5.1.4", + "@libp2p/crypto": "^5.0.8", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "it-all": "^3.0.6", + "it-drain": "^3.0.7", + "it-length-prefixed": "^9.1.0", + "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", + "p-retry": "^6.2.1", "protons": "^7.6.0", "sinon": "^19.0.2", "sinon-ts": "^2.0.0" diff --git a/packages/protocol-autonat/src/autonat.ts b/packages/protocol-autonat/src/autonat.ts index ef456be702..bb2e5bc8f9 100644 --- a/packages/protocol-autonat/src/autonat.ts +++ b/packages/protocol-autonat/src/autonat.ts @@ -1,51 +1,113 @@ -import { AbortError, serviceCapabilities, setMaxListeners } from '@libp2p/interface' +import { serviceCapabilities, serviceDependencies, setMaxListeners } from '@libp2p/interface' +import { peerSet } from '@libp2p/peer-collections' import { peerIdFromMultihash } from '@libp2p/peer-id' -import { isPrivateIp } from '@libp2p/utils/private-ip' +import { createScalableCuckooFilter } from '@libp2p/utils/filters' +import { isGlobalUnicast } from '@libp2p/utils/multiaddr/is-global-unicast' +import { isPrivate } from '@libp2p/utils/multiaddr/is-private' +import { PeerQueue } from '@libp2p/utils/peer-queue' +import { repeatingTask } from '@libp2p/utils/repeating-task' import { multiaddr, protocols } from '@multiformats/multiaddr' -import first from 'it-first' -import * as lp from 'it-length-prefixed' -import map from 'it-map' -import parallel from 'it-parallel' -import { pipe } from 'it-pipe' +import { anySignal } from 'any-signal' +import { pbStream } from 'it-protobuf-stream' import * as Digest from 'multiformats/hashes/digest' -import { - MAX_INBOUND_STREAMS, - MAX_OUTBOUND_STREAMS, - PROTOCOL_NAME, PROTOCOL_PREFIX, PROTOCOL_VERSION, REFRESH_INTERVAL, STARTUP_DELAY, TIMEOUT -} from './constants.js' +import { DEFAULT_CONNECTION_THRESHOLD, MAX_INBOUND_STREAMS, MAX_MESSAGE_SIZE, MAX_OUTBOUND_STREAMS, PROTOCOL_NAME, PROTOCOL_PREFIX, PROTOCOL_VERSION, TIMEOUT } from './constants.js' import { Message } from './pb/index.js' import type { AutoNATComponents, AutoNATServiceInit } from './index.js' -import type { Logger, Connection, PeerId, PeerInfo, Startable, AbortOptions } from '@libp2p/interface' -import type { IncomingStreamData } from '@libp2p/interface-internal' +import type { Logger, Connection, PeerId, Startable, AbortOptions } from '@libp2p/interface' +import type { AddressType, IncomingStreamData } from '@libp2p/interface-internal' +import type { PeerSet } from '@libp2p/peer-collections' +import type { Filter } from '@libp2p/utils/filters' +import type { RepeatingTask } from '@libp2p/utils/repeating-task' +import type { Multiaddr } from '@multiformats/multiaddr' // if more than 3 peers manage to dial us on what we believe to be our external // IP then we are convinced that it is, in fact, our external IP -// https://github.com/libp2p/specs/blob/master/autonat/README.md#autonat-protocol +// https://github.com/libp2p/specs/blob/master/autonat/autonat-v1.md#autonat-protocol const REQUIRED_SUCCESSFUL_DIALS = 4 +const REQUIRED_FAILED_DIALS = 8 + +interface TestAddressOptions extends AbortOptions { + multiaddr: Multiaddr + peerId: PeerId +} + +interface DialResults { + /** + * The address being tested + */ + multiaddr: Multiaddr + + /** + * The number of successful dials from peers + */ + success: number + + /** + * The number of dial failures from peers + */ + failure: number + + /** + * For the multiaddr corresponding the the string key of the `dialResults` + * map, these are the IP segments that a successful dial result has been + * received from + */ + networkSegments: string[] + + /** + * Ensure that the same peer id can't verify multiple times + */ + verifyingPeers: PeerSet + + /** + * The number of peers currently verifying this address + */ + queue: PeerQueue + + /** + * Updated when this address is verified or failed + */ + result?: boolean + + /** + * The type of address + */ + type: AddressType + + /** + * The last time the address was verified + */ + lastVerified?: number +} export class AutoNATService implements Startable { private readonly components: AutoNATComponents - private readonly startupDelay: number - private readonly refreshInterval: number private readonly protocol: string private readonly timeout: number private readonly maxInboundStreams: number private readonly maxOutboundStreams: number - private verifyAddressTimeout?: ReturnType + private readonly maxMessageSize: number private started: boolean private readonly log: Logger + private topologyId?: string + private readonly dialResults: Map + private readonly findPeers: RepeatingTask + private readonly addressFilter: Filter + private readonly connectionThreshold: number constructor (components: AutoNATComponents, init: AutoNATServiceInit) { this.components = components - this.log = components.logger.forComponent('libp2p:autonat') + this.log = components.logger.forComponent('libp2p:auto-nat') 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._verifyExternalAddresses = this._verifyExternalAddresses.bind(this) + this.connectionThreshold = init.connectionThreshold ?? DEFAULT_CONNECTION_THRESHOLD + this.maxMessageSize = init.maxMessageSize ?? MAX_MESSAGE_SIZE + this.dialResults = new Map() + this.findPeers = repeatingTask(this.findRandomPeers.bind(this), 60_000) + this.addressFilter = createScalableCuckooFilter(1024) } readonly [Symbol.toStringTag] = '@libp2p/autonat' @@ -54,6 +116,12 @@ export class AutoNATService implements Startable { '@libp2p/autonat' ] + get [serviceDependencies] (): string[] { + return [ + '@libp2p/identify' + ] + } + isStarted (): boolean { return this.started } @@ -66,100 +134,121 @@ export class AutoNATService implements Startable { await this.components.registrar.handle(this.protocol, (data) => { void this.handleIncomingAutonatStream(data) .catch(err => { - this.log.error('error handling incoming autonat stream', err) + this.log.error('error handling incoming autonat stream - %e', err) }) }, { maxInboundStreams: this.maxInboundStreams, maxOutboundStreams: this.maxOutboundStreams }) - this.verifyAddressTimeout = setTimeout(this._verifyExternalAddresses, this.startupDelay) + this.topologyId = await this.components.registrar.register(this.protocol, { + onConnect: (peerId, connection) => { + this.verifyExternalAddresses(connection) + .catch(err => { + this.log.error('could not verify addresses - %e', err) + }) + } + }) + this.findPeers.start() this.started = true } async stop (): Promise { await this.components.registrar.unhandle(this.protocol) - clearTimeout(this.verifyAddressTimeout) + if (this.topologyId != null) { + await this.components.registrar.unhandle(this.topologyId) + } + + this.dialResults.clear() + this.findPeers.stop() this.started = false } - /** - * Handle an incoming AutoNAT request - */ - async handleIncomingAutonatStream (data: IncomingStreamData): Promise { - const signal = AbortSignal.timeout(this.timeout) + private allAddressesAreVerified (): boolean { + return this.components.addressManager.getAddressesWithMetadata().every(addr => { + if (addr.expires > Date.now()) { + // ignore any unverified addresses within their TTL + return true + } - const onAbort = (): void => { - data.stream.abort(new AbortError()) - } + return addr.verified + }) + } - signal.addEventListener('abort', onAbort, { once: true }) + async findRandomPeers (options?: AbortOptions): Promise { + // skip if all addresses are verified + if (this.allAddressesAreVerified()) { + return + } - // this controller may be used while dialing lots of peers so prevent MaxListenersExceededWarning - // appearing in the console - setMaxListeners(Infinity, signal) + const signal = anySignal([ + AbortSignal.timeout(10_000), + options?.signal + ]) + // spend a few seconds finding random peers - dial them which will run + // identify to trigger the topology callbacks and run AutoNAT try { - const self = this - - await pipe( - data.stream, - (source) => lp.decode(source), - async function * (stream) { - const buf = await first(stream) - - if (buf == null) { - self.log('no message received') - yield Message.encode({ - type: Message.MessageType.DIAL_RESPONSE, - dialResponse: { - status: Message.ResponseStatus.E_BAD_REQUEST, - statusText: 'No message was sent' - } - }) - - return - } + this.log('starting random walk to find peers to run AutoNAT') - let request: Message + for await (const peer of this.components.randomWalk.walk({ signal })) { + if (!(await this.components.connectionManager.isDialable(peer.multiaddrs))) { + this.log.trace('random peer %p was not dialable %s', peer.id, peer.multiaddrs.map(ma => ma.toString()).join(', ')) - try { - request = Message.decode(buf) - } catch (err) { - self.log.error('could not decode message', err) + // skip peers we can't dial + continue + } - yield Message.encode({ - type: Message.MessageType.DIAL_RESPONSE, - dialResponse: { - status: Message.ResponseStatus.E_BAD_REQUEST, - statusText: 'Could not decode message' - } - }) + try { + this.log.trace('dial random peer %p', peer.id) + await this.components.connectionManager.openConnection(peer.multiaddrs, { + signal + }) + } catch {} - return - } + if (this.allAddressesAreVerified()) { + this.log('stopping random walk, all addresses are verified') + return + } - yield Message.encode(await self.handleAutonatMessage(request, data.connection, { - signal - })) - }, - (source) => lp.encode(source), - data.stream - ) - } catch (err) { - this.log.error('error handling incoming autonat stream', err) - } finally { - signal.removeEventListener('abort', onAbort) - } + if (!this.hasConnectionCapacity()) { + this.log('stopping random walk, too close to max connections') + return + } + } + } catch {} } - _verifyExternalAddresses (): void { - void this.verifyExternalAddresses() - .catch(err => { - this.log.error('error verifying external address', err) + /** + * Handle an incoming AutoNAT request + */ + async handleIncomingAutonatStream (data: IncomingStreamData): Promise { + const signal = AbortSignal.timeout(this.timeout) + setMaxListeners(Infinity, signal) + + const messages = pbStream(data.stream, { + maxDataLength: this.maxMessageSize + }).pb(Message) + + try { + const request = await messages.read({ + signal }) + const response = await this.handleAutonatMessage(request, data.connection, { + signal + }) + await messages.write(response, { + signal + }) + await messages.unwrap().unwrap().close({ + signal + }) + } catch (err: any) { + this.log.error('error handling incoming autonat stream - %e', err) + data.stream.abort(err) + } } private async handleAutonatMessage (message: Message, connection: Connection, options?: AbortOptions): Promise { @@ -199,7 +288,7 @@ export class AutoNATService implements Startable { const digest = Digest.decode(peer.id) peerId = peerIdFromMultihash(digest) } catch (err) { - this.log.error('invalid PeerId', err) + this.log.error('invalid PeerId - %e', err) return { type: Message.MessageType.DIAL_RESPONSE, @@ -229,34 +318,31 @@ export class AutoNATService implements Startable { const multiaddrs = peer.addrs .map(buf => multiaddr(buf)) .filter(ma => { - const isFromSameHost = ma.toOptions().host === connection.remoteAddr.toOptions().host + const options = ma.toOptions() - this.log.trace('request to dial %a was sent from %a is same host %s', ma, connection.remoteAddr, isFromSameHost) - // skip any Multiaddrs where the target node's IP does not match the sending node's IP - return isFromSameHost - }) - .filter(ma => { - const host = ma.toOptions().host - const isPublicIp = !(isPrivateIp(host) ?? false) + if (isPrivate(ma)) { + // don't try to dial private addresses + return false + } - this.log.trace('host %s was public %s', host, isPublicIp) - // don't try to dial private addresses - return isPublicIp - }) - .filter(ma => { - const host = ma.toOptions().host - const isNotOurHost = !ourHosts.includes(host) + if (options.host !== connection.remoteAddr.toOptions().host) { + // skip any Multiaddrs where the target node's IP does not match the sending node's IP + this.log.trace('not dialing %a - target host did not match remote host %a', ma, connection.remoteAddr) + return false + } - this.log.trace('host %s was not our host %s', host, isNotOurHost) - // don't try to dial nodes on the same host as us - return isNotOurHost - }) - .filter(ma => { - const isSupportedTransport = Boolean(this.components.transportManager.dialTransportForMultiaddr(ma)) + if (ourHosts.includes(options.host)) { + // don't try to dial nodes on the same host as us + return false + } - this.log.trace('transport for %a is supported %s', ma, isSupportedTransport) - // skip any Multiaddrs that have transports we do not support - return isSupportedTransport + if (this.components.transportManager.dialTransportForMultiaddr(ma) == null) { + // skip any Multiaddrs that have transports we do not support + this.log.trace('not dialing %a - transport unsupported', ma) + return false + } + + return true }) .map(ma => { if (ma.getPeerId() == null) { @@ -269,7 +355,7 @@ export class AutoNATService implements Startable { // make sure we have something to dial if (multiaddrs.length === 0) { - this.log('no valid multiaddrs for %p in message', peerId) + this.log('refused to dial all multiaddrs for %p from message', peerId) return { type: Message.MessageType.DIAL_RESPONSE, @@ -297,7 +383,7 @@ export class AutoNATService implements Startable { throw new Error('Unexpected remote address') } - this.log('Success %p', peerId) + this.log('successfully dialed %p via %a', peerId, multiaddr) return { type: Message.MessageType.DIAL_RESPONSE, @@ -307,7 +393,7 @@ export class AutoNATService implements Startable { } } } catch (err: any) { - this.log('could not dial %p', peerId, err) + this.log.error('could not dial %p - %e', peerId, err) errorMessage = err.message } finally { if (connection != null) { @@ -327,191 +413,342 @@ export class AutoNATService implements Startable { } /** - * Our multicodec topology noticed a new peer that supports autonat + * The AutoNAT v1 server is not required to send us the address that it + * dialed successfully. + * + * When addresses fail, it can be because they are NATed, or because the peer + * did't support the transport, we have no way of knowing, so just send them + * one address so we can treat the response as: + * + * - OK - the dial request worked and the address is not NATed + * - E_DIAL_ERROR - the dial request failed and the address may be NATed + * - E_DIAL_REFUSED/E_BAD_REQUEST/E_INTERNAL_ERROR - the remote didn't dial the address + */ + private getFirstUnverifiedMultiaddr (segment: string, supportsIPv6: boolean): DialResults | undefined { + const addrs = this.components.addressManager.getAddressesWithMetadata() + .sort((a, b) => { + // sort addresses, de-prioritize observed addresses + if (a.type === 'observed' && b.type !== 'observed') { + return 1 + } + + if (b.type === 'observed' && a.type !== 'observed') { + return -1 + } + + return 0 + }) + .filter(addr => { + const expired = addr.expires < Date.now() + + if (!expired) { + // skip verified/non-verified addresses within their TTL + return false + } + + const options = addr.multiaddr.toOptions() + + if (options.family === 6) { + // do not send IPv6 addresses to peers without IPv6 addresses + if (!supportsIPv6) { + return false + } + + if (!isGlobalUnicast(addr.multiaddr)) { + // skip non-globally routable addresses + return false + } + } + + if (isPrivate(addr.multiaddr)) { + // skip private addresses + return false + } + + return true + }) + + for (const addr of addrs) { + const addrString = addr.multiaddr.toString() + let results = this.dialResults.get(addrString) + + if (results != null) { + if (results.networkSegments.includes(segment)) { + this.log.trace('%a already has a network segment result from %s', results.multiaddr, segment) + // skip this address if we already have a dial result from the + // network segment the peer is in + continue + } + + if (results.queue.size > 10) { + this.log.trace('%a already has enough peers queued', results.multiaddr) + // already have enough peers verifying this address, skip on to the + // next one + continue + } + } + + // will include this multiaddr, ensure we have a results object + if (results == null) { + const needsRevalidating = addr.expires < Date.now() + + // allow re-validating addresses that worked previously + if (needsRevalidating) { + this.addressFilter.remove?.(addrString) + } + + if (this.addressFilter.has(addrString)) { + continue + } + + // only try to validate the address once + this.addressFilter.add(addrString) + + this.log.trace('creating dial result %s %s', needsRevalidating ? 'to revalidate' : 'for', addrString) + results = { + multiaddr: addr.multiaddr, + success: 0, + failure: 0, + networkSegments: [], + verifyingPeers: peerSet(), + queue: new PeerQueue({ + concurrency: 3, + maxSize: 50 + }), + type: addr.type, + lastVerified: addr.lastVerified + } + + this.dialResults.set(addrString, results) + } + + return results + } + } + + /** + * Removes any multiaddr result objects created for old multiaddrs that we are + * no longer waiting on */ - async verifyExternalAddresses (): Promise { - clearTimeout(this.verifyAddressTimeout) + private removeOutdatedMultiaddrResults (): void { + const unverifiedMultiaddrs = new Set(this.components.addressManager.getAddressesWithMetadata() + .filter(({ expires }) => { + if (expires < Date.now()) { + return true + } - // Do not try to push if we are not running + return false + }) + .map(({ multiaddr }) => multiaddr.toString()) + ) + + for (const multiaddr of this.dialResults.keys()) { + if (!unverifiedMultiaddrs.has(multiaddr)) { + this.log.trace('remove results for %a', multiaddr) + this.dialResults.delete(multiaddr) + } + } + } + + /** + * Our multicodec topology noticed a new peer that supports autonat + */ + async verifyExternalAddresses (connection: Connection): Promise { + // do nothing if we are not running if (!this.isStarted()) { return } - const addressManager = this.components.addressManager + // perform cleanup + this.removeOutdatedMultiaddrResults() - const multiaddrs = addressManager.getObservedAddrs() - .filter(ma => { - const options = ma.toOptions() + const peer = await this.components.peerStore.get(connection.remotePeer) + + // if the remote peer has IPv6 addresses, we can probably send them an IPv6 + // address to verify, otherwise only send them IPv4 addresses + const supportsIPv6 = peer.addresses.some(({ multiaddr }) => { + return multiaddr.toOptions().family === 6 + }) + + // get multiaddrs this peer is eligible to verify + const segment = this.getNetworkSegment(connection.remoteAddr) + const results = this.getFirstUnverifiedMultiaddr(segment, supportsIPv6) - return !(isPrivateIp(options.host) ?? false) + if (results == null) { + this.log.trace('no unverified public addresses found for peer %p to verify, not requesting verification', connection.remotePeer) + return + } + + if (!this.hasConnectionCapacity()) { + // we are near the max connection limit - any dial attempts from remote + // peers may be rejected which will get flagged as false dial errors and + // lead us to un-verify an otherwise reachable address + + if (results.lastVerified != null) { + this.log('automatically re-verifying %a because we are too close to the connection limit', results.multiaddr) + this.confirmAddress(results) + } else { + this.log('skipping verifying %a because we are too close to the connection limit', results.multiaddr) + } + + return + } + + results.queue.add(async (options: TestAddressOptions) => { + await this.askPeerToVerify(connection, segment, options) + }, { + peerId: connection.remotePeer, + multiaddr: results.multiaddr + }) + .catch(err => { + if (results?.result == null) { + this.log.error('error from %p verifying address %a - %e', connection.remotePeer, results?.multiaddr, err) + } }) + } - if (multiaddrs.length === 0) { - this.log('no public addresses found, not requesting verification') - this.verifyAddressTimeout = setTimeout(this._verifyExternalAddresses, this.refreshInterval) + private async askPeerToVerify (connection: Connection, segment: string, options: TestAddressOptions): Promise { + let results = this.dialResults.get(options.multiaddr.toString()) + if (results == null) { + this.log('%a was verified while %p was queued', options.multiaddr, connection.remotePeer) return } const signal = AbortSignal.timeout(this.timeout) - - // this controller may be used while dialing lots of peers so prevent MaxListenersExceededWarning - // appearing in the console setMaxListeners(Infinity, signal) - const self = this + this.log.trace('asking %p to verify multiaddr %s', connection.remotePeer, options.multiaddr) + + const stream = await connection.newStream(this.protocol, { + signal + }) try { - this.log('verify multiaddrs %s', multiaddrs.map(ma => ma.toString()).join(', ')) - - const request = Message.encode({ - type: Message.MessageType.DIAL, - dial: { - peer: { - id: this.components.peerId.toMultihash().bytes, - addrs: multiaddrs.map(map => map.bytes) + const messages = pbStream(stream).pb(Message) + const [, response] = await Promise.all([ + messages.write({ + type: Message.MessageType.DIAL, + dial: { + peer: { + id: this.components.peerId.toMultihash().bytes, + addrs: [options.multiaddr.bytes] + } } - } - }) + }, { signal }), + messages.read({ signal }) + ]) - const results: Record = {} - const networkSegments: string[] = [] + if (response.type !== Message.MessageType.DIAL_RESPONSE || response.dialResponse == null) { + this.log('invalid autonat response from %p - %j', connection.remotePeer, response) + return + } - const verifyAddress = async (peer: PeerInfo): Promise => { - let onAbort = (): void => {} + const status = response.dialResponse.status - try { - this.log('asking %p to verify multiaddr', peer.id) + this.log.trace('autonat response from %p for %a is %s', connection.remotePeer, options.multiaddr, status) - const connection = await self.components.connectionManager.openConnection(peer.id, { - signal - }) - - const stream = await connection.newStream(this.protocol, { - signal - }) + if (status !== Message.ResponseStatus.OK && status !== Message.ResponseStatus.E_DIAL_ERROR) { + return + } - onAbort = () => { stream.abort(new AbortError()) } + results = this.dialResults.get(options.multiaddr.toString()) - signal.addEventListener('abort', onAbort, { once: true }) + if (results == null) { + this.log.trace('peer reported %a as %s but there is no result object', options.multiaddr, response.dialResponse.status) + return + } - const buf = await pipe( - [request], - (source) => lp.encode(source), - stream, - (source) => lp.decode(source), - async (stream) => first(stream) - ) - if (buf == null) { - this.log('no response received from %p', connection.remotePeer) - return undefined - } - const response = Message.decode(buf) + if (results.networkSegments.includes(segment)) { + this.log.trace('%a results included network segment %s', options.multiaddr, segment) + return + } - if (response.type !== Message.MessageType.DIAL_RESPONSE || response.dialResponse == null) { - this.log('invalid autonat response from %p', connection.remotePeer) - return undefined - } + if (results.result != null) { + this.log.trace('already resolved result for %a, ignoring response from', options.multiaddr, connection.remotePeer) + return + } - if (response.dialResponse.status === Message.ResponseStatus.OK) { - // make sure we use different network segments - const options = connection.remoteAddr.toOptions() - let segment: string - - if (options.family === 4) { - const octets = options.host.split('.') - segment = octets[0] - } else if (options.family === 6) { - const octets = options.host.split(':') - segment = octets[0] - } else { - this.log('remote address "%s" was not IP4 or IP6?', options.host) - return undefined - } + if (results.verifyingPeers.has(connection.remotePeer)) { + this.log.trace('peer %p has already verified %a, ignoring response', connection.remotePeer, options.multiaddr) + return + } - if (networkSegments.includes(segment)) { - this.log('already have response from network segment %d - %s', segment, options.host) - return undefined - } + results.verifyingPeers.add(connection.remotePeer) + results.networkSegments.push(segment) - networkSegments.push(segment) - } + if (status === Message.ResponseStatus.OK) { + results.success++ - return response.dialResponse - } catch (err) { - this.log.error('error asking remote to verify multiaddr', err) - } finally { - signal.removeEventListener('abort', onAbort) + // observed addresses require more confirmations + if (results.type !== 'observed') { + this.confirmAddress(results) + return } + } else if (status === Message.ResponseStatus.E_DIAL_ERROR) { + results.failure++ } - // find some random peers - for await (const dialResponse of parallel(map(this.components.randomWalk.walk({ - signal - }), (peer) => async () => verifyAddress(peer)), { - concurrency: REQUIRED_SUCCESSFUL_DIALS - })) { - try { - if (dialResponse == null) { - continue - } - - // they either told us which address worked/didn't work, or we only sent them one address - const addr = dialResponse.addr == null ? multiaddrs[0] : multiaddr(dialResponse.addr) + this.log('%a success %d failure %d', results.multiaddr, results.success, results.failure) - this.log('autonat response for %a is %s', addr, dialResponse.status) + if (results.success === REQUIRED_SUCCESSFUL_DIALS) { + this.confirmAddress(results) + } - if (dialResponse.status === Message.ResponseStatus.E_BAD_REQUEST) { - // the remote could not parse our request - continue - } + if (results.failure === REQUIRED_FAILED_DIALS) { + this.unconfirmAddress(results) + } + } finally { + try { + await stream.close({ + signal + }) + } catch (err: any) { + stream.abort(err) + } + } + } - if (dialResponse.status === Message.ResponseStatus.E_DIAL_REFUSED) { - // the remote could not honour our request - continue - } + private hasConnectionCapacity (): boolean { + const connections = this.components.connectionManager.getConnections() + const currentConnectionCount = connections.length + const maxConnections = this.components.connectionManager.getMaxConnections() - if (dialResponse.addr == null && multiaddrs.length > 1) { - // we sent the remote multiple addrs but they didn't tell us which ones worked/didn't work - continue - } + return ((currentConnectionCount / maxConnections) * 100) < this.connectionThreshold + } - if (!multiaddrs.some(ma => ma.equals(addr))) { - this.log('peer reported %a as %s but it was not in our observed address list', addr, dialResponse.status) - continue - } + private confirmAddress (results: DialResults): void { + // we are now convinced + this.log('%s address %a is externally dialable', results.type, results.multiaddr) + this.components.addressManager.confirmObservedAddr(results.multiaddr) + this.dialResults.delete(results.multiaddr.toString()) - const addrStr = addr.toString() + // abort & remove any outstanding verification jobs for this multiaddr + results.result = true + results.queue.abort() + } - if (results[addrStr] == null) { - results[addrStr] = { success: 0, failure: 0 } - } + private unconfirmAddress (results: DialResults): void { + // we are now unconvinced + this.log('%s address %a is not externally dialable', results.type, results.multiaddr) + this.components.addressManager.removeObservedAddr(results.multiaddr) + this.dialResults.delete(results.multiaddr.toString()) - if (dialResponse.status === Message.ResponseStatus.OK) { - results[addrStr].success++ - } else if (dialResponse.status === Message.ResponseStatus.E_DIAL_ERROR) { - results[addrStr].failure++ - } + // abort & remove any outstanding verification jobs for this multiaddr + results.result = false + results.queue.abort() + } - if (results[addrStr].success === REQUIRED_SUCCESSFUL_DIALS) { - // we are now convinced - this.log('%a is externally dialable', addr) - addressManager.confirmObservedAddr(addr) - return - } + private getNetworkSegment (ma: Multiaddr): string { + // make sure we use different network segments + const options = ma.toOptions() - if (results[addrStr].failure === REQUIRED_SUCCESSFUL_DIALS) { - // we are now unconvinced - this.log('%a is not externally dialable', addr) - addressManager.removeObservedAddr(addr) - return - } - } catch (err) { - this.log.error('could not verify external address', err) - } - } - } finally { - this.verifyAddressTimeout = setTimeout(this._verifyExternalAddresses, this.refreshInterval) + if (options.family === 4) { + const octets = options.host.split('.') + return octets[0].padStart(3, '0') } + + const octets = options.host.split(':') + return octets[0].padStart(4, '0') } } diff --git a/packages/protocol-autonat/src/constants.ts b/packages/protocol-autonat/src/constants.ts index 2b15ab68d1..2b3b476a54 100644 --- a/packages/protocol-autonat/src/constants.ts +++ b/packages/protocol-autonat/src/constants.ts @@ -13,7 +13,7 @@ export const PROTOCOL_NAME = 'autonat' */ export const PROTOCOL_VERSION = '1.0.0' export const TIMEOUT = 30000 -export const STARTUP_DELAY = 5000 -export const REFRESH_INTERVAL = 60000 -export const MAX_INBOUND_STREAMS = 1 -export const MAX_OUTBOUND_STREAMS = 1 +export const MAX_INBOUND_STREAMS = 2 +export const MAX_OUTBOUND_STREAMS = 20 +export const DEFAULT_CONNECTION_THRESHOLD = 80 +export const MAX_MESSAGE_SIZE = 8192 diff --git a/packages/protocol-autonat/src/index.ts b/packages/protocol-autonat/src/index.ts index 254a076d47..31d7f1687e 100644 --- a/packages/protocol-autonat/src/index.ts +++ b/packages/protocol-autonat/src/index.ts @@ -31,7 +31,7 @@ */ import { AutoNATService } from './autonat.js' -import type { ComponentLogger, PeerId } from '@libp2p/interface' +import type { ComponentLogger, Libp2pEvents, PeerId, PeerStore, TypedEventTarget } from '@libp2p/interface' import type { AddressManager, ConnectionManager, RandomWalk, Registrar, TransportManager } from '@libp2p/interface-internal' export interface AutoNATServiceInit { @@ -64,6 +64,25 @@ export interface AutoNATServiceInit { * How many parallel outbound autoNAT streams we allow per-connection */ maxOutboundStreams?: number + + /** + * If the number of currently open connections is higher than this value as + * a percentage of the maximum number of allowed connections, automatically + * reverify previously verified addresses since auto nat peers may find it + * hard to dial and will report that the address is not dialable leading this + * node to delist it. + * + * @default 80 + */ + connectionThreshold?: number + + /** + * How large incoming autonat messages are allowed to be in bytes. If messages + * larger than this are received the stream will be reset. + * + * @default 8192 + */ + maxMessageSize?: number } export interface AutoNATComponents { @@ -74,6 +93,8 @@ export interface AutoNATComponents { connectionManager: ConnectionManager logger: ComponentLogger randomWalk: RandomWalk + events: TypedEventTarget + peerStore: PeerStore } export function autoNAT (init: AutoNATServiceInit = {}): (components: AutoNATComponents) => unknown { diff --git a/packages/protocol-autonat/test/index.spec.ts b/packages/protocol-autonat/test/index.spec.ts index 07af351912..c4e1b06303 100644 --- a/packages/protocol-autonat/test/index.spec.ts +++ b/packages/protocol-autonat/test/index.spec.ts @@ -2,15 +2,17 @@ /* eslint max-nested-callbacks: ["error", 5] */ import { generateKeyPair } from '@libp2p/crypto/keys' -import { start, stop } from '@libp2p/interface' +import { TypedEventEmitter, start, stop } from '@libp2p/interface' import { defaultLogger } from '@libp2p/logger' import { peerIdFromPrivateKey } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' import all from 'it-all' +import drain from 'it-drain' import * as lp from 'it-length-prefixed' import { pipe } from 'it-pipe' import { pushable } from 'it-pushable' +import pRetry from 'p-retry' import sinon from 'sinon' import { stubInterface } from 'sinon-ts' import { Uint8ArrayList } from 'uint8arraylist' @@ -18,7 +20,7 @@ import { AutoNATService } from '../src/autonat.js' import { PROTOCOL_NAME, PROTOCOL_PREFIX, PROTOCOL_VERSION } from '../src/constants.js' import { Message } from '../src/pb/index.js' import type { AutoNATComponents, AutoNATServiceInit } from '../src/index.js' -import type { Connection, Stream, PeerId, PeerInfo, Transport } from '@libp2p/interface' +import type { Connection, Stream, PeerId, Transport, Libp2pEvents, PeerStore, Peer } from '@libp2p/interface' import type { AddressManager, ConnectionManager, RandomWalk, Registrar, TransportManager } from '@libp2p/interface-internal' import type { Multiaddr } from '@multiformats/multiaddr' import type { StubbedInstance } from 'sinon-ts' @@ -40,6 +42,7 @@ describe('autonat', () => { let addressManager: StubbedInstance let connectionManager: StubbedInstance let transportManager: StubbedInstance + let peerStore: StubbedInstance beforeEach(async () => { randomWalk = stubInterface() @@ -47,8 +50,12 @@ describe('autonat', () => { addressManager = stubInterface() addressManager.getAddresses.returns([]) - connectionManager = stubInterface() + connectionManager = stubInterface({ + getConnections: () => [], + getMaxConnections: () => 100 + }) transportManager = stubInterface() + peerStore = stubInterface() components = { peerId: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), @@ -57,7 +64,9 @@ describe('autonat', () => { registrar, addressManager, connectionManager, - transportManager + transportManager, + events: new TypedEventEmitter(), + peerStore } service = new AutoNATService(components, defaultInit) @@ -74,45 +83,61 @@ describe('autonat', () => { }) describe('verify our observed addresses', () => { - async function stubPeerResponse (host: string, dialResponse: Message.DialResponse, peerId?: PeerId): Promise { + async function stubPeerResponse (host: string, dialResponse: Message.DialResponse, peerId?: PeerId): Promise { // stub random peer lookup - const peer = { + const peer: Peer = { id: peerId ?? peerIdFromPrivateKey(await generateKeyPair('Ed25519')), - multiaddrs: [], - protocols: [] + addresses: [{ + multiaddr: multiaddr(`/ip4/${host}/tcp/28319`), + isCertified: true + }], + protocols: [], + metadata: new Map(), + tags: new Map() } + peerStore.get.withArgs(peer.id).resolves(peer) + // stub connection to remote peer const connection = stubInterface() connection.remoteAddr = multiaddr(`/ip4/${host}/tcp/28319/p2p/${peer.id.toString()}`) + connection.remotePeer = peer.id connectionManager.openConnection.withArgs(peer.id).resolves(connection) connection.newStream.withArgs(`/${PROTOCOL_PREFIX}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`).callsFake(async () => { - // stub autonat protocol stream - const stream = stubInterface() - // stub autonat response const response = Message.encode({ type: Message.MessageType.DIAL_RESPONSE, dialResponse }) - stream.source = (async function * () { - yield lp.encode.single(response) - }()) - stream.sink.returns(Promise.resolve()) + + // stub autonat protocol stream + const stream = stubInterface({ + source: (async function * () { + yield lp.encode.single(response) + }()), + sink: async (source) => { + await drain(source) + } + }) return stream }) - return peer + return connection } it('should request peers verify our observed address', async () => { const observedAddress = multiaddr('/ip4/123.123.123.123/tcp/28319') - addressManager.getObservedAddrs.returns([observedAddress]) + addressManager.getAddressesWithMetadata.returns([{ + multiaddr: observedAddress, + verified: false, + type: 'observed', + expires: 0 + }]) // The network says OK - const peers = [ + const connections = [ await stubPeerResponse('124.124.124.124', { status: Message.ResponseStatus.OK }), @@ -127,11 +152,50 @@ describe('autonat', () => { }) ] - randomWalk.walk.returns(async function * () { - yield * peers - }()) + for (const conn of connections) { + await service.verifyExternalAddresses(conn) + } - await service.verifyExternalAddresses() + await pRetry(() => { + expect(addressManager.confirmObservedAddr).to.have.property('called', true) + }) + + expect(addressManager.confirmObservedAddr.calledWith(observedAddress)) + .to.be.true('Did not confirm observed multiaddr') + }) + + it('should request peers re-verify our observed address', async () => { + const observedAddress = multiaddr('/ip4/123.123.123.123/tcp/28319') + addressManager.getAddressesWithMetadata.returns([{ + multiaddr: observedAddress, + verified: true, + type: 'observed', + expires: Date.now() - 1000 + }]) + + // The network says OK + const connections = [ + await stubPeerResponse('124.124.124.124', { + status: Message.ResponseStatus.OK + }), + await stubPeerResponse('125.124.124.124', { + status: Message.ResponseStatus.OK + }), + await stubPeerResponse('126.124.124.124', { + status: Message.ResponseStatus.OK + }), + await stubPeerResponse('127.124.124.124', { + status: Message.ResponseStatus.OK + }) + ] + + for (const conn of connections) { + await service.verifyExternalAddresses(conn) + } + + await pRetry(() => { + expect(addressManager.confirmObservedAddr).to.have.property('called', true) + }) expect(addressManager.confirmObservedAddr.calledWith(observedAddress)) .to.be.true('Did not confirm observed multiaddr') @@ -139,10 +203,15 @@ describe('autonat', () => { it('should mark observed address as low confidence when dialing fails', async () => { const observedAddress = multiaddr('/ip4/123.123.123.123/tcp/28319') - addressManager.getObservedAddrs.returns([observedAddress]) + addressManager.getAddressesWithMetadata.returns([{ + multiaddr: observedAddress, + verified: false, + type: 'observed', + expires: 0 + }]) // The network says ERROR - const peers = [ + const connections = [ await stubPeerResponse('124.124.124.124', { status: Message.ResponseStatus.E_DIAL_ERROR }), @@ -154,14 +223,28 @@ describe('autonat', () => { }), await stubPeerResponse('127.124.124.124', { status: Message.ResponseStatus.E_DIAL_ERROR + }), + await stubPeerResponse('128.124.124.124', { + status: Message.ResponseStatus.E_DIAL_ERROR + }), + await stubPeerResponse('129.124.124.124', { + status: Message.ResponseStatus.E_DIAL_ERROR + }), + await stubPeerResponse('130.124.124.124', { + status: Message.ResponseStatus.E_DIAL_ERROR + }), + await stubPeerResponse('131.124.124.124', { + status: Message.ResponseStatus.E_DIAL_ERROR }) ] - randomWalk.walk.returns(async function * () { - yield * peers - }()) + for (const conn of connections) { + await service.verifyExternalAddresses(conn) + } - await service.verifyExternalAddresses() + await pRetry(() => { + expect(addressManager.removeObservedAddr).to.have.property('called', true) + }) expect(addressManager.removeObservedAddr.calledWith(observedAddress)) .to.be.true('Did not verify external multiaddr') @@ -169,10 +252,15 @@ describe('autonat', () => { it('should ignore non error or success statuses', async () => { const observedAddress = multiaddr('/ip4/123.123.123.123/tcp/28319') - addressManager.getObservedAddrs.returns([observedAddress]) + addressManager.getAddressesWithMetadata.returns([{ + multiaddr: observedAddress, + verified: false, + type: 'observed', + expires: 0 + }]) // Mix of responses, mostly OK - const peers = [ + const connections = [ await stubPeerResponse('124.124.124.124', { status: Message.ResponseStatus.OK }), @@ -196,25 +284,29 @@ describe('autonat', () => { }) ] - randomWalk.walk.returns(async function * () { - yield * peers - }()) + for (const conn of connections) { + await service.verifyExternalAddresses(conn) + } - await service.verifyExternalAddresses() + await pRetry(() => { + expect(addressManager.confirmObservedAddr).to.have.property('called', true) + }) expect(addressManager.confirmObservedAddr.calledWith(observedAddress)) .to.be.true('Did not confirm external multiaddr') - - expect(connectionManager.openConnection.callCount) - .to.equal(peers.length, 'Did not open connections to all peers') }) it('should require confirmation from diverse networks', async () => { const observedAddress = multiaddr('/ip4/123.123.123.123/tcp/28319') - addressManager.getObservedAddrs.returns([observedAddress]) + addressManager.getAddressesWithMetadata.returns([{ + multiaddr: observedAddress, + verified: false, + type: 'observed', + expires: 0 + }]) // an attacker says OK, the rest of the network says ERROR - const peers = [ + const connections = [ await stubPeerResponse('124.124.124.124', { status: Message.ResponseStatus.OK }), @@ -238,30 +330,46 @@ describe('autonat', () => { }), await stubPeerResponse('130.124.124.124', { status: Message.ResponseStatus.E_DIAL_ERROR + }), + await stubPeerResponse('131.124.124.124', { + status: Message.ResponseStatus.E_DIAL_ERROR + }), + await stubPeerResponse('132.124.124.124', { + status: Message.ResponseStatus.E_DIAL_ERROR + }), + await stubPeerResponse('133.124.124.124', { + status: Message.ResponseStatus.E_DIAL_ERROR + }), + await stubPeerResponse('134.124.124.124', { + status: Message.ResponseStatus.E_DIAL_ERROR }) ] - randomWalk.walk.returns(async function * () { - yield * peers - }()) + for (const conn of connections) { + await service.verifyExternalAddresses(conn) + } - await service.verifyExternalAddresses() + await pRetry(() => { + expect(addressManager.removeObservedAddr).to.have.property('called', true) + }) expect(addressManager.removeObservedAddr.calledWith(observedAddress)) .to.be.true('Did not verify external multiaddr') - - expect(connectionManager.openConnection.callCount) - .to.equal(peers.length, 'Did not open connections to all peers') }) it('should require confirmation from diverse peers', async () => { const observedAddress = multiaddr('/ip4/123.123.123.123/tcp/28319') - addressManager.getObservedAddrs.returns([observedAddress]) + addressManager.getAddressesWithMetadata.returns([{ + multiaddr: observedAddress, + verified: false, + type: 'observed', + expires: 0 + }]) const peerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) // an attacker says OK, the rest of the network says ERROR - const peers = [ + const connections = [ await stubPeerResponse('124.124.124.124', { status: Message.ResponseStatus.OK }, peerId), @@ -285,80 +393,44 @@ describe('autonat', () => { }), await stubPeerResponse('131.124.124.124', { status: Message.ResponseStatus.E_DIAL_ERROR - }) - ] - - randomWalk.walk.returns(async function * () { - yield * peers - }()) - - await service.verifyExternalAddresses() - - expect(addressManager.removeObservedAddr.calledWith(observedAddress)) - .to.be.true('Did not verify external multiaddr') - - expect(connectionManager.openConnection.callCount) - .to.equal(peers.length, 'Did not open connections to all peers') - }) - - it('should only accept observed addresses', async () => { - const observedAddress = multiaddr('/ip4/123.123.123.123/tcp/28319') - const reportedAddress = multiaddr('/ip4/100.100.100.100/tcp/28319') - - // our observed addresses - addressManager.getObservedAddrs.returns([observedAddress]) - - // an attacker says OK, the rest of the network says ERROR - const peers = [ - await stubPeerResponse('124.124.124.124', { - status: Message.ResponseStatus.OK, - addr: reportedAddress.bytes - }), - await stubPeerResponse('125.124.124.125', { - status: Message.ResponseStatus.OK, - addr: reportedAddress.bytes - }), - await stubPeerResponse('126.124.124.126', { - status: Message.ResponseStatus.OK, - addr: reportedAddress.bytes }), - await stubPeerResponse('127.124.124.127', { - status: Message.ResponseStatus.OK, - addr: reportedAddress.bytes - }), - await stubPeerResponse('128.124.124.124', { + await stubPeerResponse('132.124.124.124', { status: Message.ResponseStatus.E_DIAL_ERROR }), - await stubPeerResponse('129.124.124.124', { + await stubPeerResponse('133.124.124.124', { status: Message.ResponseStatus.E_DIAL_ERROR }), - await stubPeerResponse('130.124.124.124', { + await stubPeerResponse('134.124.124.124', { status: Message.ResponseStatus.E_DIAL_ERROR }), - await stubPeerResponse('131.124.124.124', { + await stubPeerResponse('135.124.124.124', { status: Message.ResponseStatus.E_DIAL_ERROR }) ] - randomWalk.walk.returns(async function * () { - yield * peers - }()) + for (const conn of connections) { + await service.verifyExternalAddresses(conn) + } - await service.verifyExternalAddresses() + await pRetry(() => { + expect(addressManager.removeObservedAddr).to.have.property('called', true) + }) expect(addressManager.removeObservedAddr.calledWith(observedAddress)) .to.be.true('Did not verify external multiaddr') - - expect(connectionManager.openConnection.callCount) - .to.equal(peers.length, 'Did not open connections to all peers') }) it('should time out when verifying an observed address', async () => { const observedAddress = multiaddr('/ip4/123.123.123.123/tcp/28319') - addressManager.getObservedAddrs.returns([observedAddress]) + addressManager.getAddressesWithMetadata.returns([{ + multiaddr: observedAddress, + verified: false, + type: 'observed', + expires: 0 + }]) // The network says OK - const peers = [ + const connections = [ await stubPeerResponse('124.124.124.124', { status: Message.ResponseStatus.OK }), @@ -373,28 +445,9 @@ describe('autonat', () => { }) ] - randomWalk.walk.returns(async function * () { - yield * peers - }()) - - connectionManager.openConnection.reset() - connectionManager.openConnection.callsFake(async (peer, options = {}) => { - return Promise.race([ - new Promise((resolve, reject) => { - options.signal?.addEventListener('abort', () => { - reject(new Error('Dial aborted!')) - }) - }), - new Promise((resolve, reject) => { - // longer than the timeout - setTimeout(() => { - reject(new Error('Dial Timeout!')) - }, 1000) - }) - ]) - }) - - await service.verifyExternalAddresses() + for (const conn of connections) { + await service.verifyExternalAddresses(conn) + } expect(addressManager.addObservedAddr.called) .to.be.false('Verify external multiaddr when we should have timed out') @@ -424,11 +477,13 @@ describe('autonat', () => { for await (const buf of stream) { sink.push(new Uint8ArrayList(buf)) } - sink.end() }, abort: (err) => { void stream.source.throw(err) + }, + close: async () => { + sink.end() } } const connection = { @@ -511,26 +566,6 @@ describe('autonat', () => { expect(message).to.have.nested.property('dialResponse.status', Message.ResponseStatus.OK) }) - it('should expect a message', async () => { - const message = await stubIncomingStream({ - message: false - }) - - expect(message).to.have.property('type', Message.MessageType.DIAL_RESPONSE) - expect(message).to.have.nested.property('dialResponse.status', Message.ResponseStatus.E_BAD_REQUEST) - expect(message).to.have.nested.property('dialResponse.statusText', 'No message was sent') - }) - - it('should expect a valid message', async () => { - const message = await stubIncomingStream({ - message: Uint8Array.from([3, 2, 1, 0]) - }) - - expect(message).to.have.property('type', Message.MessageType.DIAL_RESPONSE) - expect(message).to.have.nested.property('dialResponse.status', Message.ResponseStatus.E_BAD_REQUEST) - expect(message).to.have.nested.property('dialResponse.statusText', 'Could not decode message') - }) - it('should expect a dial message', async () => { const message = await stubIncomingStream({ message: {} @@ -634,31 +669,5 @@ describe('autonat', () => { expect(message).to.have.nested.property('dialResponse.status', Message.ResponseStatus.E_DIAL_ERROR) expect(message).to.have.nested.property('dialResponse.statusText', 'Could not dial') }) - - it('should time out when dialing a requested address', async () => { - connectionManager.openConnection.callsFake(async function (ma, options = {}) { - return Promise.race([ - new Promise((resolve, reject) => { - options.signal?.addEventListener('abort', () => { - reject(new Error('Dial aborted!')) - }) - }), - new Promise((resolve, reject) => { - // longer than the timeout - setTimeout(() => { - reject(new Error('Dial Timeout!')) - }, 1000) - }) - ]) - }) - - const message = await stubIncomingStream({ - canDial: undefined - }) - - expect(message).to.have.property('type', Message.MessageType.DIAL_RESPONSE) - expect(message).to.have.nested.property('dialResponse.status', Message.ResponseStatus.E_DIAL_ERROR) - expect(message).to.have.nested.property('dialResponse.statusText', 'Dial aborted!') - }) }) }) diff --git a/packages/protocol-dcutr/CHANGELOG.md b/packages/protocol-dcutr/CHANGELOG.md index badef97a55..36e05b514d 100644 --- a/packages/protocol-dcutr/CHANGELOG.md +++ b/packages/protocol-dcutr/CHANGELOG.md @@ -44,6 +44,26 @@ * dependencies * @libp2p/utils bumped from ^5.2.4 to ^5.2.5 +## [2.0.14](https://github.com/libp2p/js-libp2p/compare/dcutr-v2.0.13...dcutr-v2.0.14) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [2.0.13](https://github.com/libp2p/js-libp2p/compare/dcutr-v2.0.12...dcutr-v2.0.13) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + ## [2.0.12](https://github.com/libp2p/js-libp2p/compare/dcutr-v2.0.11...dcutr-v2.0.12) (2024-11-18) diff --git a/packages/protocol-dcutr/package.json b/packages/protocol-dcutr/package.json index 01e1f80a95..65ba40e685 100644 --- a/packages/protocol-dcutr/package.json +++ b/packages/protocol-dcutr/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/dcutr", - "version": "2.0.12", + "version": "2.0.14", "description": "Implementation of the DCUtR Protocol", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/protocol-dcutr#readme", @@ -52,9 +52,9 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "delay": "^6.0.0", diff --git a/packages/protocol-echo/CHANGELOG.md b/packages/protocol-echo/CHANGELOG.md index cb03e5b674..5e8afa4581 100644 --- a/packages/protocol-echo/CHANGELOG.md +++ b/packages/protocol-echo/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [2.1.5](https://github.com/libp2p/js-libp2p/compare/echo-v2.1.4...echo-v2.1.5) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [2.1.4](https://github.com/libp2p/js-libp2p/compare/echo-v2.1.3...echo-v2.1.4) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [2.1.3](https://github.com/libp2p/js-libp2p/compare/echo-v2.1.2...echo-v2.1.3) (2024-11-18) diff --git a/packages/protocol-echo/package.json b/packages/protocol-echo/package.json index 912a982edf..d47ac6f102 100644 --- a/packages/protocol-echo/package.json +++ b/packages/protocol-echo/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/echo", - "version": "2.1.3", + "version": "2.1.5", "description": "Implementation of an Echo protocol", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/protocol-echo#readme", @@ -51,14 +51,14 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", "@multiformats/multiaddr": "^12.3.3", "it-byte-stream": "^1.1.0", "it-pipe": "^3.0.1" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "it-all": "^3.0.6", "it-pair": "^2.0.6", diff --git a/packages/protocol-fetch/CHANGELOG.md b/packages/protocol-fetch/CHANGELOG.md index 0e439f3b86..47dcc3c20a 100644 --- a/packages/protocol-fetch/CHANGELOG.md +++ b/packages/protocol-fetch/CHANGELOG.md @@ -48,6 +48,29 @@ * devDependencies * @libp2p/peer-id-factory bumped from ^4.0.3 to ^4.0.4 +## [2.0.14](https://github.com/libp2p/js-libp2p/compare/fetch-v2.0.13...fetch-v2.0.14) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [2.0.13](https://github.com/libp2p/js-libp2p/compare/fetch-v2.0.12...fetch-v2.0.13) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + ## [2.0.12](https://github.com/libp2p/js-libp2p/compare/fetch-v2.0.11...fetch-v2.0.12) (2024-11-18) diff --git a/packages/protocol-fetch/package.json b/packages/protocol-fetch/package.json index d3138d0139..5e1147bfe8 100644 --- a/packages/protocol-fetch/package.json +++ b/packages/protocol-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/fetch", - "version": "2.0.12", + "version": "2.0.14", "description": "Implementation of the Fetch Protocol", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/protocol-fetch#readme", @@ -52,17 +52,17 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", "it-protobuf-stream": "^1.1.5", "protons-runtime": "^5.5.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/crypto": "^5.0.8", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-id": "^5.0.9", "aegir": "^45.0.5", "it-pair": "^2.0.6", "protons": "^7.6.0", diff --git a/packages/protocol-identify/CHANGELOG.md b/packages/protocol-identify/CHANGELOG.md index 1021300d5c..64753c5b4d 100644 --- a/packages/protocol-identify/CHANGELOG.md +++ b/packages/protocol-identify/CHANGELOG.md @@ -76,6 +76,37 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [3.0.14](https://github.com/libp2p/js-libp2p/compare/identify-v3.0.13...identify-v3.0.14) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [3.0.13](https://github.com/libp2p/js-libp2p/compare/identify-v3.0.12...identify-v3.0.13) (2024-12-09) + + +### Bug Fixes + +* ignore observed IPv6 addresses that are not global unicast ([#2873](https://github.com/libp2p/js-libp2p/issues/2873)) ([4e55fe8](https://github.com/libp2p/js-libp2p/commit/4e55fe8ff5bef906fd8ba21037c55861d9fefae0)) +* use isPrivate to detect private multiaddrs ([#2868](https://github.com/libp2p/js-libp2p/issues/2868)) ([2c182d2](https://github.com/libp2p/js-libp2p/commit/2c182d2e23d3246ece92ac937dfd91275b39bdc0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/peer-record bumped from ^8.0.12 to ^8.0.13 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [3.0.12](https://github.com/libp2p/js-libp2p/compare/identify-v3.0.11...identify-v3.0.12) (2024-11-18) diff --git a/packages/protocol-identify/package.json b/packages/protocol-identify/package.json index d1ddc99288..0f91d51514 100644 --- a/packages/protocol-identify/package.json +++ b/packages/protocol-identify/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/identify", - "version": "3.0.12", + "version": "3.0.14", "description": "Implementation of the Identify Protocol", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/protocol-identify#readme", @@ -52,12 +52,12 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/peer-record": "^8.0.12", - "@libp2p/utils": "^6.2.1", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/peer-record": "^8.0.13", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "it-drain": "^3.0.7", @@ -69,7 +69,7 @@ "wherearewe": "^2.0.1" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "delay": "^6.0.0", "it-length-prefixed": "^9.1.0", diff --git a/packages/protocol-identify/src/identify.ts b/packages/protocol-identify/src/identify.ts index 88b7076077..7ab98ac572 100644 --- a/packages/protocol-identify/src/identify.ts +++ b/packages/protocol-identify/src/identify.ts @@ -4,9 +4,10 @@ import { publicKeyFromProtobuf, publicKeyToProtobuf } from '@libp2p/crypto/keys' import { InvalidMessageError, UnsupportedProtocolError, serviceCapabilities, setMaxListeners } from '@libp2p/interface' import { peerIdFromCID } from '@libp2p/peer-id' import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' +import { isGlobalUnicast } from '@libp2p/utils/multiaddr/is-global-unicast' import { isPrivate } from '@libp2p/utils/multiaddr/is-private' import { protocols } from '@multiformats/multiaddr' -import { IP_OR_DOMAIN } from '@multiformats/multiaddr-matcher' +import { IP_OR_DOMAIN, TCP } from '@multiformats/multiaddr-matcher' import { pbStream } from 'it-protobuf-stream' import { MULTICODEC_IDENTIFY_PROTOCOL_NAME, @@ -18,6 +19,8 @@ import type { Identify as IdentifyInterface, IdentifyComponents, IdentifyInit } import type { IdentifyResult, AbortOptions, Connection, Stream, Startable } from '@libp2p/interface' import type { IncomingStreamData } from '@libp2p/interface-internal' +const CODEC_IP6 = 0x29 + export class Identify extends AbstractIdentify implements Startable, IdentifyInterface { constructor (components: IdentifyComponents, init: IdentifyInit = {}) { super(components, { @@ -104,23 +107,46 @@ export class Identify extends AbstractIdentify implements Startable, IdentifyInt throw new InvalidMessageError('identified peer is our own peer id?') } - // Get the observedAddr if there is one - const cleanObservedAddr = getCleanMultiaddr(observedAddr) + // if the observed address is publicly routable, add it to the address + // manager for verification via AutoNAT + this.maybeAddObservedAddress(observedAddr) this.log('identify completed for peer %p and protocols %o', id, protocols) - if (cleanObservedAddr != null) { - this.log('our observed address was %a', cleanObservedAddr) + return consumeIdentifyMessage(this.peerStore, this.events, this.log, connection, message) + } + + private maybeAddObservedAddress (observedAddr: Uint8Array | undefined): void { + const cleanObservedAddr = getCleanMultiaddr(observedAddr) - if (isPrivate(cleanObservedAddr)) { - this.log('our observed address was private') - } else if (this.addressManager.getObservedAddrs().length < (this.maxObservedAddresses ?? Infinity)) { - this.log('storing our observed address') - this.addressManager.addObservedAddr(cleanObservedAddr) - } + if (cleanObservedAddr == null) { + return } - return consumeIdentifyMessage(this.peerStore, this.events, this.log, connection, message) + this.log.trace('our observed address was %a', cleanObservedAddr) + + if (isPrivate(cleanObservedAddr)) { + this.log.trace('our observed address was private') + return + } + + const tuples = cleanObservedAddr.stringTuples() + + if (tuples[0][0] === CODEC_IP6 && !isGlobalUnicast(cleanObservedAddr)) { + this.log.trace('our observed address was IPv6 but not a global unicast address') + return + } + + if (TCP.exactMatch(cleanObservedAddr)) { + // TODO: because socket dials can't use the same local port as the TCP + // listener, many unique observed addresses are reported so ignore all + // TCP addresses until https://github.com/libp2p/js-libp2p/issues/2620 + // is resolved + return + } + + this.log.trace('storing the observed address') + this.addressManager.addObservedAddr(cleanObservedAddr) } /** diff --git a/packages/protocol-identify/test/index.spec.ts b/packages/protocol-identify/test/index.spec.ts index af2d2827c9..b637d1873b 100644 --- a/packages/protocol-identify/test/index.spec.ts +++ b/packages/protocol-identify/test/index.spec.ts @@ -373,4 +373,28 @@ describe('identify', () => { expect(result.observedAddr).to.be.undefined() }) + + it('should ignore observed non global unicast IPv6 addresses', async () => { + identify = new Identify(components) + + await start(identify) + + const remotePeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + const message: IdentifyMessage = { + protocolVersion: 'protocol version', + agentVersion: 'agent version', + listenAddrs: [multiaddr('/ip4/127.0.0.1/tcp/1234').bytes], + protocols: ['protocols'], + publicKey: publicKeyToProtobuf(remotePeer.publicKey), + observedAddr: multiaddr('/ip6/fe80::2892:aef3:af04:735a%en').bytes + } + + const connection = identifyConnection(remotePeer, message) + + // run identify + await identify.identify(connection) + + expect(components.addressManager.addObservedAddr.called).to.be.false() + }) }) diff --git a/packages/protocol-perf/CHANGELOG.md b/packages/protocol-perf/CHANGELOG.md index baebc5255a..fdeb6154e2 100644 --- a/packages/protocol-perf/CHANGELOG.md +++ b/packages/protocol-perf/CHANGELOG.md @@ -82,6 +82,30 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [4.0.15](https://github.com/libp2p/js-libp2p/compare/perf-v4.0.14...perf-v4.0.15) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [4.0.14](https://github.com/libp2p/js-libp2p/compare/perf-v4.0.13...perf-v4.0.14) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [4.0.13](https://github.com/libp2p/js-libp2p/compare/perf-v4.0.12...perf-v4.0.13) (2024-11-19) diff --git a/packages/protocol-perf/package.json b/packages/protocol-perf/package.json index 4cd1e1a65a..4e0deddfa8 100644 --- a/packages/protocol-perf/package.json +++ b/packages/protocol-perf/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/perf", - "version": "4.0.13", + "version": "4.0.15", "description": "Implementation of Perf Protocol", "author": "@maschad / @marcopolo", "license": "Apache-2.0 OR MIT", @@ -52,14 +52,14 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", "@multiformats/multiaddr": "^12.3.3", "it-pushable": "^3.2.3" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "it-last": "^3.0.6", "it-pair": "^2.0.6", diff --git a/packages/protocol-ping/CHANGELOG.md b/packages/protocol-ping/CHANGELOG.md index e4949a74d9..f2f62117c2 100644 --- a/packages/protocol-ping/CHANGELOG.md +++ b/packages/protocol-ping/CHANGELOG.md @@ -50,6 +50,29 @@ * devDependencies * @libp2p/peer-id-factory bumped from ^4.0.3 to ^4.0.4 +## [2.0.14](https://github.com/libp2p/js-libp2p/compare/ping-v2.0.13...ping-v2.0.14) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [2.0.13](https://github.com/libp2p/js-libp2p/compare/ping-v2.0.12...ping-v2.0.13) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + ## [2.0.12](https://github.com/libp2p/js-libp2p/compare/ping-v2.0.11...ping-v2.0.12) (2024-11-18) diff --git a/packages/protocol-ping/package.json b/packages/protocol-ping/package.json index bb3e8609bc..3fff29ce74 100644 --- a/packages/protocol-ping/package.json +++ b/packages/protocol-ping/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/ping", - "version": "2.0.12", + "version": "2.0.14", "description": "Implementation of Ping Protocol", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/protocol-ping#readme", @@ -50,16 +50,16 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", "@multiformats/multiaddr": "^12.3.3", "it-byte-stream": "^1.1.0", "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-id": "^5.0.9", "aegir": "^45.0.5", "it-pair": "^2.0.6", "p-defer": "^4.0.1", diff --git a/packages/pubsub-floodsub/CHANGELOG.md b/packages/pubsub-floodsub/CHANGELOG.md index a76334b24e..3ab0956208 100644 --- a/packages/pubsub-floodsub/CHANGELOG.md +++ b/packages/pubsub-floodsub/CHANGELOG.md @@ -98,6 +98,33 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [10.1.13](https://github.com/libp2p/js-libp2p/compare/floodsub-v10.1.12...floodsub-v10.1.13) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/pubsub bumped from ^10.0.13 to ^10.0.14 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [10.1.12](https://github.com/libp2p/js-libp2p/compare/floodsub-v10.1.11...floodsub-v10.1.12) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/pubsub bumped from ^10.0.12 to ^10.0.13 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-collections bumped from ^6.0.12 to ^6.0.13 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + ## [10.1.11](https://github.com/libp2p/js-libp2p/compare/floodsub-v10.1.10...floodsub-v10.1.11) (2024-11-19) diff --git a/packages/pubsub-floodsub/package.json b/packages/pubsub-floodsub/package.json index 6557c7d42c..c50bc41c13 100644 --- a/packages/pubsub-floodsub/package.json +++ b/packages/pubsub-floodsub/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/floodsub", - "version": "10.1.11", + "version": "10.1.13", "description": "libp2p-floodsub, also known as pubsub-flood or just dumbsub, this implementation of pubsub focused on delivering an API for Publish/Subscribe, but with no CastTree Forming (it just floods the network).", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/pubsub-floodsub#readme", @@ -60,18 +60,18 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/pubsub": "^10.0.12", + "@libp2p/interface": "^2.3.0", + "@libp2p/pubsub": "^10.0.14", "protons-runtime": "^5.5.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-collections": "^6.0.12", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-collections": "^6.0.13", + "@libp2p/peer-id": "^5.0.9", "@multiformats/multiaddr": "^12.3.3", "@types/sinon": "^17.0.3", "aegir": "^45.0.5", diff --git a/packages/pubsub/CHANGELOG.md b/packages/pubsub/CHANGELOG.md index f7575eee80..78e9aab809 100644 --- a/packages/pubsub/CHANGELOG.md +++ b/packages/pubsub/CHANGELOG.md @@ -75,6 +75,31 @@ * dependencies * @libp2p/utils bumped from ^5.2.4 to ^5.2.5 +## [10.0.14](https://github.com/libp2p/js-libp2p/compare/pubsub-v10.0.13...pubsub-v10.0.14) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [10.0.13](https://github.com/libp2p/js-libp2p/compare/pubsub-v10.0.12...pubsub-v10.0.13) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/peer-collections bumped from ^6.0.12 to ^6.0.13 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [10.0.12](https://github.com/libp2p/js-libp2p/compare/pubsub-v10.0.11...pubsub-v10.0.12) (2024-11-18) diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json index ac68bbaae0..0a559d681a 100644 --- a/packages/pubsub/package.json +++ b/packages/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/pubsub", - "version": "10.0.12", + "version": "10.0.14", "description": "libp2p pubsub base class", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/pubsub#readme", @@ -84,12 +84,12 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/peer-collections": "^6.0.12", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/utils": "^6.2.1", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/peer-collections": "^6.0.13", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/utils": "^6.3.0", "it-length-prefixed": "^9.1.0", "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", @@ -99,7 +99,7 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", + "@libp2p/logger": "^5.1.5", "@types/sinon": "^17.0.3", "aegir": "^45.0.5", "delay": "^6.0.0", diff --git a/packages/stream-multiplexer-mplex/CHANGELOG.md b/packages/stream-multiplexer-mplex/CHANGELOG.md index 5812d4721a..9541e1b26a 100644 --- a/packages/stream-multiplexer-mplex/CHANGELOG.md +++ b/packages/stream-multiplexer-mplex/CHANGELOG.md @@ -78,6 +78,28 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [11.0.15](https://github.com/libp2p/js-libp2p/compare/mplex-v11.0.14...mplex-v11.0.15) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [11.0.14](https://github.com/libp2p/js-libp2p/compare/mplex-v11.0.13...mplex-v11.0.14) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [11.0.13](https://github.com/libp2p/js-libp2p/compare/mplex-v11.0.12...mplex-v11.0.13) (2024-11-19) diff --git a/packages/stream-multiplexer-mplex/package.json b/packages/stream-multiplexer-mplex/package.json index 80bf22d17d..8d1253162e 100644 --- a/packages/stream-multiplexer-mplex/package.json +++ b/packages/stream-multiplexer-mplex/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/mplex", - "version": "11.0.13", + "version": "11.0.15", "description": "JavaScript implementation of https://github.com/libp2p/mplex", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/stream-multiplexer-mplex#readme", @@ -62,8 +62,8 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/utils": "^6.3.0", "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.2", @@ -72,8 +72,8 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "benchmark": "^2.1.4", "cborg": "^4.2.6", diff --git a/packages/transport-circuit-relay-v2/CHANGELOG.md b/packages/transport-circuit-relay-v2/CHANGELOG.md index 7a9ec6b7f0..4d03116a9b 100644 --- a/packages/transport-circuit-relay-v2/CHANGELOG.md +++ b/packages/transport-circuit-relay-v2/CHANGELOG.md @@ -101,6 +101,41 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [3.1.5](https://github.com/libp2p/js-libp2p/compare/circuit-relay-v2-v3.1.4...circuit-relay-v2-v3.1.5) (2024-12-10) + + +### Bug Fixes + +* auto-confirm relay addresses ([#2886](https://github.com/libp2p/js-libp2p/issues/2886)) ([5c4a79e](https://github.com/libp2p/js-libp2p/commit/5c4a79e5a6e8d0db1ef6464075841a0b9de507ef)), closes [#2883](https://github.com/libp2p/js-libp2p/issues/2883) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [3.1.4](https://github.com/libp2p/js-libp2p/compare/circuit-relay-v2-v3.1.3...circuit-relay-v2-v3.1.4) (2024-12-09) + + +### Dependencies + +* bump retimeable-signal in /packages/transport-circuit-relay-v2 ([#2853](https://github.com/libp2p/js-libp2p/issues/2853)) ([ce2f45e](https://github.com/libp2p/js-libp2p/commit/ce2f45ee91c37a056be67f3b47d14c0511231141)) +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/peer-collections bumped from ^6.0.12 to ^6.0.13 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/peer-record bumped from ^8.0.12 to ^8.0.13 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [3.1.3](https://github.com/libp2p/js-libp2p/compare/circuit-relay-v2-v3.1.2...circuit-relay-v2-v3.1.3) (2024-11-19) diff --git a/packages/transport-circuit-relay-v2/package.json b/packages/transport-circuit-relay-v2/package.json index ddd653918c..fbe8614aff 100644 --- a/packages/transport-circuit-relay-v2/package.json +++ b/packages/transport-circuit-relay-v2/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/circuit-relay-v2", - "version": "3.1.3", + "version": "3.1.5", "description": "Implementation of Circuit Relay v2", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/transport-circuit-relay-v2#readme", @@ -52,13 +52,13 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/peer-collections": "^6.0.12", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/peer-record": "^8.0.12", - "@libp2p/utils": "^6.2.1", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/peer-collections": "^6.0.13", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/peer-record": "^8.0.13", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "any-signal": "^4.1.1", @@ -73,8 +73,8 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "delay": "^6.0.0", "it-drain": "^3.0.7", diff --git a/packages/transport-circuit-relay-v2/src/transport/listener.ts b/packages/transport-circuit-relay-v2/src/transport/listener.ts index 556edd0a32..85d9c13244 100644 --- a/packages/transport-circuit-relay-v2/src/transport/listener.ts +++ b/packages/transport-circuit-relay-v2/src/transport/listener.ts @@ -2,15 +2,16 @@ import { ListenError, TypedEventEmitter, setMaxListeners } from '@libp2p/interfa import { multiaddr } from '@multiformats/multiaddr' import { DEFAULT_RESERVATION_COMPLETION_TIMEOUT } from '../constants.js' import { CircuitListen, CircuitSearch } from '../utils.js' -import type { RelayDiscovery } from './discovery.js' import type { RelayReservation, ReservationStore } from './reservation-store.js' import type { ComponentLogger, Logger, Listener, ListenerEvents, PeerId } from '@libp2p/interface' -import type { ConnectionManager } from '@libp2p/interface-internal' +import type { AddressManager, ConnectionManager } from '@libp2p/interface-internal' import type { Multiaddr } from '@multiformats/multiaddr' export interface CircuitRelayTransportListenerComponents { + peerId: PeerId connectionManager: ConnectionManager - relayStore: ReservationStore + addressManager: AddressManager + reservationStore: ReservationStore logger: ComponentLogger } @@ -19,9 +20,10 @@ export interface CircuitRelayTransportListenerInit { } class CircuitRelayTransportListener extends TypedEventEmitter implements Listener { + private readonly peerId: PeerId private readonly connectionManager: ConnectionManager + private readonly addressManager: AddressManager private readonly reservationStore: ReservationStore - private readonly discovery?: RelayDiscovery private listeningAddrs: Multiaddr[] private readonly log: Logger private readonly listenTimeout: number @@ -32,8 +34,10 @@ class CircuitRelayTransportListener extends TypedEventEmitter im super() this.log = components.logger.forComponent('libp2p:circuit-relay:transport:listener') + this.peerId = components.peerId this.connectionManager = components.connectionManager - this.reservationStore = components.relayStore + this.addressManager = components.addressManager + this.reservationStore = components.reservationStore this.listeningAddrs = [] this.listenTimeout = init.listenTimeout ?? DEFAULT_RESERVATION_COMPLETION_TIMEOUT @@ -51,6 +55,11 @@ class CircuitRelayTransportListener extends TypedEventEmitter im this.log('relay peer removed %p', evt.detail.relay) + this.listeningAddrs.forEach(ma => { + // mark as externally dialable + this.addressManager.removeObservedAddr(ma) + }) + this.listeningAddrs = [] // announce listen addresses change @@ -59,7 +68,7 @@ class CircuitRelayTransportListener extends TypedEventEmitter im _onAddRelayPeer = (evt: CustomEvent): void => { const { - relay, details + details } = evt.detail if (details.type === 'configured') { @@ -70,16 +79,7 @@ class CircuitRelayTransportListener extends TypedEventEmitter im return } - this.log('relay peer added %p', relay) - - this.relay = relay - - // add all addresses from the relay reservation - this.listeningAddrs = details.reservation.addrs - .map(buf => multiaddr(buf).encapsulate('/p2p-circuit')) - - // announce listen addresses change - this.safeDispatchEvent('listening') + this.addedRelay(evt.detail) } async listen (addr: Multiaddr): Promise { @@ -102,18 +102,7 @@ class CircuitRelayTransportListener extends TypedEventEmitter im if (!this.reservationStore.hasReservation(relayConn.remotePeer)) { this.log('making reservation on peer %p', relayConn.remotePeer) const reservation = await this.reservationStore.addRelay(relayConn.remotePeer, 'configured') - this.log('made reservation on peer %p', relayConn.remotePeer) - - this.relay = reservation.relay - - // add all addresses from the relay reservation - this.listeningAddrs = reservation.details.reservation.addrs - .map(buf => multiaddr(buf).encapsulate('/p2p-circuit')) - - // if that succeeded announce listen addresses change - queueMicrotask(() => { - this.safeDispatchEvent('listening') - }) + this.addedRelay(reservation) } } else { throw new ListenError(`Could not listen on p2p-circuit address "${addr}"`) @@ -136,6 +125,28 @@ class CircuitRelayTransportListener extends TypedEventEmitter im this.safeDispatchEvent('close') }) } + + private addedRelay (reservation: RelayReservation): void { + this.log('relay peer added %p', reservation.relay) + + this.relay = reservation.relay + + // add all addresses from the relay reservation + this.listeningAddrs = reservation.details.reservation.addrs + .map(buf => multiaddr(buf).encapsulate('/p2p-circuit')) + + this.listeningAddrs.forEach(ma => { + // mark as externally dialable + this.addressManager.confirmObservedAddr(ma, { + type: 'transport' + }) + }) + + // if that succeeded announce listen addresses change + queueMicrotask(() => { + this.safeDispatchEvent('listening') + }) + } } export function createListener (options: CircuitRelayTransportListenerComponents): Listener { diff --git a/packages/transport-circuit-relay-v2/src/transport/transport.ts b/packages/transport-circuit-relay-v2/src/transport/transport.ts index 24ab08bf56..6f0a254b60 100644 --- a/packages/transport-circuit-relay-v2/src/transport/transport.ts +++ b/packages/transport-circuit-relay-v2/src/transport/transport.ts @@ -246,8 +246,10 @@ export class CircuitRelayTransport implements Transport */ createListener (options: CreateListenerOptions): Listener { return createListener({ + peerId: this.peerId, connectionManager: this.connectionManager, - relayStore: this.reservationStore, + addressManager: this.addressManager, + reservationStore: this.reservationStore, logger: this.logger }) } diff --git a/packages/transport-circuit-relay-v2/test/listener.spec.ts b/packages/transport-circuit-relay-v2/test/listener.spec.ts new file mode 100644 index 0000000000..4d1a8ddc81 --- /dev/null +++ b/packages/transport-circuit-relay-v2/test/listener.spec.ts @@ -0,0 +1,101 @@ +import { generateKeyPair } from '@libp2p/crypto/keys' +import { defaultLogger } from '@libp2p/logger' +import { peerIdFromPrivateKey } from '@libp2p/peer-id' +import { multiaddr } from '@multiformats/multiaddr' +import { expect } from 'aegir/chai' +import { stubInterface } from 'sinon-ts' +import { createListener } from '../src/transport/listener.js' +import { type ReservationStore } from '../src/transport/reservation-store.js' +import type { ComponentLogger, Connection, Listener, PeerId } from '@libp2p/interface' +import type { AddressManager, ConnectionManager } from '@libp2p/interface-internal' +import type { StubbedInstance } from 'sinon-ts' + +export interface CircuitRelayTransportListenerComponents { + peerId: PeerId + connectionManager: StubbedInstance + addressManager: StubbedInstance + reservationStore: StubbedInstance + logger: ComponentLogger +} + +describe('listener', () => { + let listener: Listener + let components: CircuitRelayTransportListenerComponents + + beforeEach(async () => { + components = { + peerId: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), + connectionManager: stubInterface(), + addressManager: stubInterface(), + reservationStore: stubInterface(), + logger: defaultLogger() + } + + listener = createListener(components) + }) + + it('should auto-confirm discovered relay addresses', async () => { + await listener.listen(multiaddr('/p2p-circuit')) + + expect(components.reservationStore.reserveRelay).to.have.property('called', true, 'did not begin relay search') + + const relayPeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const relayAddr = multiaddr(`/ip4/123.123.123.123/tcp/1234/p2p/${relayPeer}`) + + const createdReservationListener = components.reservationStore.addEventListener.getCall(1).args[1] + + if (typeof createdReservationListener === 'function') { + createdReservationListener( + new CustomEvent('relay:created-reservation', { + detail: { + relay: relayPeer, + details: { + type: 'discovered', + reservation: { + addrs: [ + relayAddr + ] + } + } + } + }) + ) + } + + expect(components.addressManager.confirmObservedAddr.calledWith( + relayAddr.encapsulate('/p2p-circuit') + )).to.be.true() + }) + + it('should auto-confirm configured relay addresses', async () => { + const relayPeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const relayAddr = multiaddr(`/ip4/123.123.123.123/tcp/1234/p2p/${relayPeer}/p2p-circuit`) + const conn = stubInterface({ + id: 'connection-id-1234', + remotePeer: relayPeer + }) + + components.connectionManager.openConnection.withArgs(relayAddr.decapsulate('/p2p-circuit')).resolves(conn) + + components.reservationStore.addRelay.withArgs(relayPeer).resolves({ + relay: relayPeer, + details: { + type: 'configured', + reservation: { + addrs: [ + relayAddr.bytes + ], + expire: 100n + }, + timeout: 0 as any, + connection: conn.id + } + }) + + await listener.listen(relayAddr) + + expect(components.addressManager.confirmObservedAddr.calledWith( + relayAddr.encapsulate('/p2p-circuit') + )).to.be.true() + }) +}) diff --git a/packages/transport-memory/CHANGELOG.md b/packages/transport-memory/CHANGELOG.md index 4a06aa1991..2e5b25d634 100644 --- a/packages/transport-memory/CHANGELOG.md +++ b/packages/transport-memory/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [1.0.2](https://github.com/libp2p/js-libp2p/compare/memory-v1.0.1...memory-v1.0.2) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + ## [1.0.1](https://github.com/libp2p/js-libp2p/compare/memory-v1.0.0...memory-v1.0.1) (2024-11-18) diff --git a/packages/transport-memory/package.json b/packages/transport-memory/package.json index 51e9ab1861..de15054ff8 100644 --- a/packages/transport-memory/package.json +++ b/packages/transport-memory/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/memory", - "version": "1.0.1", + "version": "1.0.2", "description": "A memory transport for libp2p", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/transport-memory#readme", @@ -51,7 +51,7 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "^2.2.1", + "@libp2p/interface": "^2.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "@types/sinon": "^17.0.3", @@ -62,8 +62,8 @@ "uint8arraylist": "^2.4.8" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-id": "^5.0.9", "aegir": "^45.0.5", "sinon": "^19.0.2", "sinon-ts": "^2.0.0" diff --git a/packages/transport-tcp/CHANGELOG.md b/packages/transport-tcp/CHANGELOG.md index 4351e50acc..96b52dede0 100644 --- a/packages/transport-tcp/CHANGELOG.md +++ b/packages/transport-tcp/CHANGELOG.md @@ -89,6 +89,18 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [10.0.14](https://github.com/libp2p/js-libp2p/compare/tcp-v10.0.13...tcp-v10.0.14) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [10.0.13](https://github.com/libp2p/js-libp2p/compare/tcp-v10.0.12...tcp-v10.0.13) (2024-11-18) diff --git a/packages/transport-tcp/package.json b/packages/transport-tcp/package.json index 73c61d712d..5dd4c392d0 100644 --- a/packages/transport-tcp/package.json +++ b/packages/transport-tcp/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/tcp", - "version": "10.0.13", + "version": "10.0.14", "description": "A TCP transport for libp2p", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/transport-tcp#readme", @@ -60,8 +60,8 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/utils": "^6.3.0", "@multiformats/mafmt": "^12.1.6", "@multiformats/multiaddr": "^12.3.3", "@types/sinon": "^17.0.3", @@ -72,7 +72,7 @@ "stream-to-it": "^1.0.1" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "p-wait-for": "^5.0.2", "sinon": "^19.0.2", diff --git a/packages/transport-webrtc/CHANGELOG.md b/packages/transport-webrtc/CHANGELOG.md index 3f9ff4c3f8..8a5f94247e 100644 --- a/packages/transport-webrtc/CHANGELOG.md +++ b/packages/transport-webrtc/CHANGELOG.md @@ -111,6 +111,33 @@ * @libp2p/websockets bumped from ^8.0.14 to ^8.0.15 * libp2p bumped from ^1.2.2 to ^1.2.3 +## [5.0.21](https://github.com/libp2p/js-libp2p/compare/webrtc-v5.0.20...webrtc-v5.0.21) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^6.2.0 to ^6.2.1 + +## [5.0.20](https://github.com/libp2p/js-libp2p/compare/webrtc-v5.0.19...webrtc-v5.0.20) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface-compliance-tests bumped from ^6.1.11 to ^6.2.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [5.0.19](https://github.com/libp2p/js-libp2p/compare/webrtc-v5.0.18...webrtc-v5.0.19) (2024-11-19) diff --git a/packages/transport-webrtc/package.json b/packages/transport-webrtc/package.json index 4c02459b2f..f68ff530eb 100644 --- a/packages/transport-webrtc/package.json +++ b/packages/transport-webrtc/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/webrtc", - "version": "5.0.19", + "version": "5.0.21", "description": "A libp2p transport using WebRTC connections", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/transport-webrtc#readme", @@ -51,10 +51,10 @@ }, "dependencies": { "@chainsafe/libp2p-noise": "^16.0.0", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "detect-browser": "^5.3.0", @@ -76,9 +76,9 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface-compliance-tests": "^6.1.11", - "@libp2p/logger": "^5.1.4", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface-compliance-tests": "^6.2.1", + "@libp2p/logger": "^5.1.5", "@types/sinon": "^17.0.3", "aegir": "^45.0.5", "delay": "^6.0.0", diff --git a/packages/transport-websockets/CHANGELOG.md b/packages/transport-websockets/CHANGELOG.md index d042916413..d43ad42131 100644 --- a/packages/transport-websockets/CHANGELOG.md +++ b/packages/transport-websockets/CHANGELOG.md @@ -70,6 +70,29 @@ * devDependencies * @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1 +## [9.1.0](https://github.com/libp2p/js-libp2p/compare/websockets-v9.0.13...websockets-v9.1.0) (2024-12-09) + + +### Features + +* auto-tls for websockets ([#2800](https://github.com/libp2p/js-libp2p/issues/2800)) ([8a9258a](https://github.com/libp2p/js-libp2p/commit/8a9258a24168d13172eb139d32bc6889e71f81dc)) + + +### Bug Fixes + +* ignore IPv6 link-local addresses ([#2865](https://github.com/libp2p/js-libp2p/issues/2865)) ([f8da60e](https://github.com/libp2p/js-libp2p/commit/f8da60e73fede669986b8d48218f66c4e55fd776)) +* remove browser dial filter ([#2838](https://github.com/libp2p/js-libp2p/issues/2838)) ([d6cd25d](https://github.com/libp2p/js-libp2p/commit/d6cd25d0deca292420093d894edbfbc47b347e5d)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + ## [9.0.13](https://github.com/libp2p/js-libp2p/compare/websockets-v9.0.12...websockets-v9.0.13) (2024-11-18) diff --git a/packages/transport-websockets/package.json b/packages/transport-websockets/package.json index 746f6d75b3..9b1a070c17 100644 --- a/packages/transport-websockets/package.json +++ b/packages/transport-websockets/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/websockets", - "version": "9.0.13", + "version": "9.1.0", "description": "JavaScript implementation of the WebSockets module that libp2p uses and that implements the interface-transport spec", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/transport-websockets#readme", @@ -74,8 +74,8 @@ "test:electron-main": "aegir test -t electron-main -f ./dist/test/node.js --cov" }, "dependencies": { - "@libp2p/interface": "^2.2.1", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "@multiformats/multiaddr-to-uri": "^11.0.0", @@ -88,7 +88,7 @@ "ws": "^8.18.0" }, "devDependencies": { - "@libp2p/logger": "^5.1.4", + "@libp2p/logger": "^5.1.5", "aegir": "^45.0.5", "is-loopback-addr": "^2.0.2", "p-wait-for": "^5.0.2", diff --git a/packages/transport-webtransport/CHANGELOG.md b/packages/transport-webtransport/CHANGELOG.md index e5f2f4d5a8..fb5d1a0c23 100644 --- a/packages/transport-webtransport/CHANGELOG.md +++ b/packages/transport-webtransport/CHANGELOG.md @@ -96,6 +96,32 @@ * devDependencies * libp2p bumped from ^1.2.2 to ^1.2.3 +## [5.0.20](https://github.com/libp2p/js-libp2p/compare/webtransport-v5.0.19...webtransport-v5.0.20) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * devDependencies + * @libp2p/ping bumped from ^2.0.13 to ^2.0.14 + * libp2p bumped from ^2.4.0 to ^2.4.1 + +## [5.0.19](https://github.com/libp2p/js-libp2p/compare/webtransport-v5.0.18...webtransport-v5.0.19) (2024-12-09) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/ping bumped from ^2.0.12 to ^2.0.13 + * libp2p bumped from ^2.3.1 to ^2.4.0 + ## [5.0.18](https://github.com/libp2p/js-libp2p/compare/webtransport-v5.0.17...webtransport-v5.0.18) (2024-11-18) diff --git a/packages/transport-webtransport/package.json b/packages/transport-webtransport/package.json index 47a3243cc6..ff66a1b46b 100644 --- a/packages/transport-webtransport/package.json +++ b/packages/transport-webtransport/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/webtransport", - "version": "5.0.18", + "version": "5.0.20", "description": "JavaScript implementation of the WebTransport module that libp2p uses and that implements the interface-transport spec", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/transport-webtransport#readme", @@ -51,9 +51,9 @@ }, "dependencies": { "@chainsafe/libp2p-noise": "^16.0.0", - "@libp2p/interface": "^2.2.1", - "@libp2p/peer-id": "^5.0.8", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/peer-id": "^5.0.9", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "it-stream-types": "^2.0.2", @@ -64,17 +64,17 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", + "@libp2p/crypto": "^5.0.8", "@libp2p/daemon-client": "^9.0.4", - "@libp2p/logger": "^5.1.4", - "@libp2p/ping": "^2.0.12", + "@libp2p/logger": "^5.1.5", + "@libp2p/ping": "^2.0.14", "@noble/hashes": "^1.6.1", "aegir": "^45.0.5", "execa": "^9.5.1", "go-libp2p": "^1.6.0", "it-map": "^3.1.1", "it-to-buffer": "^4.0.7", - "libp2p": "^2.3.1", + "libp2p": "^2.4.1", "p-defer": "^4.0.1", "p-wait-for": "^5.0.2" }, diff --git a/packages/upnp-nat/CHANGELOG.md b/packages/upnp-nat/CHANGELOG.md index 6d6d6f6621..edce9ec615 100644 --- a/packages/upnp-nat/CHANGELOG.md +++ b/packages/upnp-nat/CHANGELOG.md @@ -64,6 +64,50 @@ * dependencies * @libp2p/utils bumped from ^5.2.4 to ^5.2.5 +## [3.0.2](https://github.com/libp2p/js-libp2p/compare/upnp-nat-v3.0.1...upnp-nat-v3.0.2) (2024-12-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface-internal bumped from ^2.2.0 to ^2.2.1 + +## [3.0.1](https://github.com/libp2p/js-libp2p/compare/upnp-nat-v3.0.0...upnp-nat-v3.0.1) (2024-12-10) + + +### Bug Fixes + +* unhandled promise rejection when finding gateway ([#2884](https://github.com/libp2p/js-libp2p/issues/2884)) ([127abe2](https://github.com/libp2p/js-libp2p/commit/127abe24b567e462b5fb8809960201635bee2202)) + +## [3.0.0](https://github.com/libp2p/js-libp2p/compare/upnp-nat-v2.0.12...upnp-nat-v3.0.0) (2024-12-09) + + +### ⚠ BREAKING CHANGES + +* use ip/port mapping ([#2840](https://github.com/libp2p/js-libp2p/issues/2840)) + +### Bug Fixes + +* require external confirmation of public addresses ([#2867](https://github.com/libp2p/js-libp2p/issues/2867)) ([d19974d](https://github.com/libp2p/js-libp2p/commit/d19974d93a1015acfca95c2155dbcffc5fd6a6c0)) +* support IPv6 with IPv4 ([#2864](https://github.com/libp2p/js-libp2p/issues/2864)) ([406b391](https://github.com/libp2p/js-libp2p/commit/406b3916cac688cd98c02b61ee2e52a9cd041704)) +* use addresses with metadata to map ports ([#2878](https://github.com/libp2p/js-libp2p/issues/2878)) ([d51c21f](https://github.com/libp2p/js-libp2p/commit/d51c21f0b0e87d54841876a652fd9985dfafd030)) +* use ip/port mapping ([#2840](https://github.com/libp2p/js-libp2p/issues/2840)) ([a82b07d](https://github.com/libp2p/js-libp2p/commit/a82b07d8c69640b6c72824a584b55bb7c30ca06e)) + + +### Dependencies + +* update nat-port-mapper to v3 ([#2843](https://github.com/libp2p/js-libp2p/issues/2843)) ([b02ea9b](https://github.com/libp2p/js-libp2p/commit/b02ea9b6edf1a6ef2e059ea8570ba57cc9052229)) +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/interface-internal bumped from ^2.1.1 to ^2.2.0 + * @libp2p/utils bumped from ^6.2.1 to ^6.3.0 + * devDependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + ## [2.0.12](https://github.com/libp2p/js-libp2p/compare/upnp-nat-v2.0.11...upnp-nat-v2.0.12) (2024-11-18) diff --git a/packages/upnp-nat/package.json b/packages/upnp-nat/package.json index 3693b1a4db..1134b6c98b 100644 --- a/packages/upnp-nat/package.json +++ b/packages/upnp-nat/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/upnp-nat", - "version": "2.0.12", + "version": "3.0.2", "description": "UPnP NAT hole punching", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/upnp-nat#readme", @@ -52,18 +52,18 @@ "dependencies": { "@achingbrain/nat-port-mapper": "^4.0.0", "@chainsafe/is-ip": "^2.0.2", - "@libp2p/interface": "^2.2.1", - "@libp2p/interface-internal": "^2.1.1", - "@libp2p/utils": "^6.2.1", + "@libp2p/interface": "^2.3.0", + "@libp2p/interface-internal": "^2.2.1", + "@libp2p/utils": "^6.3.0", "@multiformats/multiaddr": "^12.3.3", "@multiformats/multiaddr-matcher": "^1.6.0", "p-defer": "^4.0.1", "race-signal": "^1.1.0" }, "devDependencies": { - "@libp2p/crypto": "^5.0.7", - "@libp2p/logger": "^5.1.4", - "@libp2p/peer-id": "^5.0.8", + "@libp2p/crypto": "^5.0.8", + "@libp2p/logger": "^5.1.5", + "@libp2p/peer-id": "^5.0.9", "aegir": "^45.0.5", "sinon-ts": "^2.0.0" }, diff --git a/packages/upnp-nat/src/check-external-address.ts b/packages/upnp-nat/src/check-external-address.ts index 38be3aab2c..e6a884b6c3 100644 --- a/packages/upnp-nat/src/check-external-address.ts +++ b/packages/upnp-nat/src/check-external-address.ts @@ -33,7 +33,7 @@ class ExternalAddressChecker implements ExternalAddress, Startable { private readonly addressManager: AddressManager private started: boolean private lastPublicIp?: string - private readonly lastPublicIpPromise: DeferredPromise + private lastPublicIpPromise?: DeferredPromise private readonly check: RepeatingTask private readonly onExternalAddressChange?: (newExternalAddress: string) => void @@ -46,8 +46,6 @@ class ExternalAddressChecker implements ExternalAddress, Startable { this.checkExternalAddress = this.checkExternalAddress.bind(this) - this.lastPublicIpPromise = pDefer() - this.check = repeatingTask(this.checkExternalAddress, init.interval ?? 30000, { timeout: init.timeout ?? 10000, runImmediately: true @@ -76,7 +74,13 @@ class ExternalAddressChecker implements ExternalAddress, Startable { throw new NotStartedError('Not started yet') } - return this.lastPublicIp ?? raceSignal(this.lastPublicIpPromise.promise, options?.signal, { + if (this.lastPublicIp != null) { + return this.lastPublicIp + } + + this.lastPublicIpPromise = pDefer() + + return raceSignal(this.lastPublicIpPromise.promise, options?.signal, { errorMessage: 'Requesting the public IP from the network gateway timed out - UPnP may not be enabled' }) } @@ -94,7 +98,7 @@ class ExternalAddressChecker implements ExternalAddress, Startable { } this.lastPublicIp = externalAddress - this.lastPublicIpPromise.resolve(externalAddress) + this.lastPublicIpPromise?.resolve(externalAddress) } catch (err: any) { this.log.error('could not resolve external address - %e', err) @@ -103,7 +107,7 @@ class ExternalAddressChecker implements ExternalAddress, Startable { return } - this.lastPublicIpPromise.reject(err) + this.lastPublicIpPromise?.reject(err) } } } diff --git a/packages/upnp-nat/src/index.ts b/packages/upnp-nat/src/index.ts index 0f224750ab..5f7baadf5b 100644 --- a/packages/upnp-nat/src/index.ts +++ b/packages/upnp-nat/src/index.ts @@ -98,6 +98,17 @@ export interface UPnPNATInit { * otherwise one will be created */ portMappingClient?: UPnPNATClient + + /** + * Any mapped addresses are added to the observed address list. These + * addresses require additional verification by the `@libp2p/autonat` protocol + * or similar before they are trusted. + * + * To skip this verification and trust them immediately pass `true` here + * + * @default false + */ + autoConfirmAddress?: boolean } export interface UPnPNATComponents { diff --git a/packages/upnp-nat/src/upnp-nat.ts b/packages/upnp-nat/src/upnp-nat.ts index 7439e1a399..cf917f401c 100644 --- a/packages/upnp-nat/src/upnp-nat.ts +++ b/packages/upnp-nat/src/upnp-nat.ts @@ -1,5 +1,5 @@ import { upnpNat } from '@achingbrain/nat-port-mapper' -import { serviceCapabilities, setMaxListeners, start, stop } from '@libp2p/interface' +import { serviceCapabilities, serviceDependencies, setMaxListeners, start, stop } from '@libp2p/interface' import { debounce } from '@libp2p/utils/debounce' import { GatewayFinder } from './gateway-finder.js' import { UPnPPortMapper } from './upnp-port-mapper.js' @@ -18,6 +18,7 @@ export class UPnPNAT implements Startable, UPnPNATInterface { private readonly mapIpAddressesDebounced: DebouncedFunction private readonly gatewayFinder: GatewayFinder private readonly portMappers: UPnPPortMapper[] + private readonly autoConfirmAddress: boolean constructor (components: UPnPNATComponents, init: UPnPNATInit) { this.log = components.logger.forComponent('libp2p:upnp-nat') @@ -25,6 +26,7 @@ export class UPnPNAT implements Startable, UPnPNATInterface { this.init = init this.started = false this.portMappers = [] + this.autoConfirmAddress = init.autoConfirmAddress ?? false this.portMappingClient = init.portMappingClient ?? upnpNat({ description: init.portMappingDescription ?? `${components.nodeInfo.name}@${components.nodeInfo.version} ${components.peerId.toString()}`, @@ -56,6 +58,16 @@ export class UPnPNAT implements Startable, UPnPNATInterface { '@libp2p/nat-traversal' ] + get [serviceDependencies] (): string[] { + if (!this.autoConfirmAddress) { + return [ + '@libp2p/autonat' + ] + } + + return [] + } + isStarted (): boolean { return this.started } @@ -102,7 +114,9 @@ export class UPnPNAT implements Startable, UPnPNATInterface { async mapIpAddresses (): Promise { try { await Promise.all( - this.portMappers.map(async mapper => mapper.mapIpAddresses()) + this.portMappers.map(async mapper => mapper.mapIpAddresses({ + autoConfirmAddress: this.autoConfirmAddress + })) ) } catch (err: any) { this.log.error('error mapping IP addresses - %e', err) diff --git a/packages/upnp-nat/src/upnp-port-mapper.ts b/packages/upnp-nat/src/upnp-port-mapper.ts index c1b2289bb7..9e242eb81a 100644 --- a/packages/upnp-nat/src/upnp-port-mapper.ts +++ b/packages/upnp-nat/src/upnp-port-mapper.ts @@ -4,15 +4,18 @@ import { isLinkLocal } from '@libp2p/utils/multiaddr/is-link-local' import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' import { isPrivate } from '@libp2p/utils/multiaddr/is-private' import { isPrivateIp } from '@libp2p/utils/private-ip' +import { multiaddr } from '@multiformats/multiaddr' import { QUICV1, TCP, WebSockets, WebSocketsSecure, WebTransport } from '@multiformats/multiaddr-matcher' import { dynamicExternalAddress } from './check-external-address.js' import { DoubleNATError } from './errors.js' import type { ExternalAddress } from './check-external-address.js' import type { Gateway } from '@achingbrain/nat-port-mapper' import type { ComponentLogger, Logger } from '@libp2p/interface' -import type { AddressManager } from '@libp2p/interface-internal' +import type { AddressManager, NodeAddress } from '@libp2p/interface-internal' import type { Multiaddr } from '@multiformats/multiaddr' +const MAX_DATE = 8_640_000_000_000_000 + export interface UPnPPortMapperInit { gateway: Gateway externalAddressCheckInterval?: number @@ -29,6 +32,10 @@ interface PortMapping { externalPort: number } +export interface MapPortsOptions { + autoConfirmAddress?: boolean +} + export class UPnPPortMapper { private readonly gateway: Gateway private readonly externalAddress: ExternalAddress @@ -99,10 +106,15 @@ export class UPnPPortMapper { /** * Return any eligible multiaddrs that are not mapped on the detected gateway */ - private getUnmappedAddresses (multiaddrs: Multiaddr[], publicAddresses: string[]): Multiaddr[] { + private getUnmappedAddresses (multiaddrs: NodeAddress[], publicAddresses: string[]): Multiaddr[] { const output: Multiaddr[] = [] - for (const ma of multiaddrs) { + for (const { multiaddr: ma, type } of multiaddrs) { + // only consider transport addresses, ignore mapped/observed addrs + if (type !== 'transport') { + continue + } + const stringTuples = ma.stringTuples() const address = `${stringTuples[0][1]}` @@ -127,13 +139,7 @@ export class UPnPPortMapper { } // only IP based addresses - if (!( - TCP.exactMatch(ma) || - WebSockets.exactMatch(ma) || - WebSocketsSecure.exactMatch(ma) || - QUICV1.exactMatch(ma) || - WebTransport.exactMatch(ma) - )) { + if (!this.isIPAddress(ma)) { continue } @@ -149,13 +155,13 @@ export class UPnPPortMapper { return output } - async mapIpAddresses (): Promise { + async mapIpAddresses (options?: MapPortsOptions): Promise { try { const externalHost = await this.externalAddress.getPublicIp() // filter addresses to get private, non-relay, IP based addresses that we // haven't mapped yet - const addresses = this.getUnmappedAddresses(this.addressManager.getAddresses(), [externalHost]) + const addresses = this.getUnmappedAddresses(this.addressManager.getAddressesWithMetadata(), [externalHost]) if (addresses.length === 0) { this.log('no private, non-relay, unmapped, IP based addresses found') @@ -194,6 +200,14 @@ export class UPnPPortMapper { this.mappedPorts.set(key, mapping) this.addressManager.addPublicAddressMapping(mapping.internalHost, mapping.internalPort, mapping.externalHost, mapping.externalPort, transport === 'tcp' ? 'tcp' : 'udp') this.log('created mapping of %s:%s to %s:%s for protocol %s', mapping.internalHost, mapping.internalPort, mapping.externalHost, mapping.externalPort, transport) + + if (options?.autoConfirmAddress === true) { + const ma = multiaddr(`/ip${family}/${host}/${transport}/${port}`) + this.log('auto-confirming IP address %a', ma) + this.addressManager.confirmObservedAddr(ma, { + ttl: MAX_DATE - Date.now() + }) + } } catch (err) { this.log.error('failed to create mapping for %s:%d for protocol - %e', host, port, transport, err) } @@ -217,4 +231,12 @@ export class UPnPPortMapper { throw new InvalidParametersError(`${publicIp} is not an IP address`) } } + + private isIPAddress (ma: Multiaddr): boolean { + return TCP.exactMatch(ma) || + WebSockets.exactMatch(ma) || + WebSocketsSecure.exactMatch(ma) || + QUICV1.exactMatch(ma) || + WebTransport.exactMatch(ma) + } } diff --git a/packages/upnp-nat/test/index.spec.ts b/packages/upnp-nat/test/index.spec.ts index 63a539e248..6c31fe0211 100644 --- a/packages/upnp-nat/test/index.spec.ts +++ b/packages/upnp-nat/test/index.spec.ts @@ -81,10 +81,17 @@ describe('UPnP NAT (TCP)', () => { gateway.externalIp.resolves(externalHost) - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/127.0.0.1/tcp/4002'), - multiaddr(`/ip4/${internalHost}/tcp/${internalPort}`) - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4002'), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }, { + multiaddr: multiaddr(`/ip4/${internalHost}/tcp/${internalPort}`), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }]) gateway.map.withArgs(internalPort, internalHost).resolves({ internalHost, @@ -120,10 +127,17 @@ describe('UPnP NAT (TCP)', () => { gateway.externalIp.resolves(externalHost) - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/127.0.0.1/tcp/4002'), - multiaddr(`/ip4/${internalHost}/tcp/${internalPort}`) - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4002'), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }, { + multiaddr: multiaddr(`/ip4/${internalHost}/tcp/${internalPort}`), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }]) gateway.map.withArgs(internalPort, internalHost).resolves({ internalHost, @@ -153,10 +167,17 @@ describe('UPnP NAT (TCP)', () => { gateway.externalIp.resolves('192.168.1.1') - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/127.0.0.1/tcp/4002'), - multiaddr('/ip4/192.168.1.12/tcp/4002') - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4002'), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }, { + multiaddr: multiaddr('/ip4/192.168.1.12/tcp/4002'), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }]) await start(natManager) await natManager.mapIpAddresses() @@ -177,6 +198,13 @@ describe('UPnP NAT (TCP)', () => { multiaddr('/ip6/fe80::9400:67ff:fe19:2a0f/tcp/0') ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip6/fe80::9400:67ff:fe19:2a0f/tcp/0'), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }]) + await start(natManager) await natManager.mapIpAddresses() @@ -192,9 +220,12 @@ describe('UPnP NAT (TCP)', () => { gateway.externalIp.resolves('82.3.1.5') - components.addressManager.getAddresses.returns([ - multiaddr('/ip6/::1/tcp/0') - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip6/::1/tcp/0'), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }]) await start(natManager) await natManager.mapIpAddresses() @@ -211,9 +242,12 @@ describe('UPnP NAT (TCP)', () => { gateway.externalIp.resolves('82.3.1.5') - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/192.168.1.12/udp/4001') - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/192.168.1.12/udp/4001'), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }]) await start(natManager) await natManager.mapIpAddresses() @@ -230,9 +264,12 @@ describe('UPnP NAT (TCP)', () => { gateway.externalIp.resolves('82.3.1.5') - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/127.0.0.1/tcp/4001') - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4001'), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }]) await start(natManager) await natManager.mapIpAddresses() @@ -249,9 +286,12 @@ describe('UPnP NAT (TCP)', () => { gateway.externalIp.resolves('82.3.1.5') - components.addressManager.getAddresses.returns([ - multiaddr('/ip4/127.0.0.1/tcp/4001/sctp/0') - ]) + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4001/sctp/0'), + verified: true, + type: 'transport', + expires: Date.now() + 10_000 + }]) await start(natManager) await natManager.mapIpAddresses() diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index a15f98dfff..a7a090bf73 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -36,6 +36,31 @@ * @libp2p/logger bumped from ^4.0.2 to ^4.0.3 * @libp2p/peer-id-factory bumped from ^4.0.1 to ^4.0.2 +## [6.3.0](https://github.com/libp2p/js-libp2p/compare/utils-v6.2.1...utils-v6.3.0) (2024-12-09) + + +### Features + +* add isGlobalUnicast function ([#2872](https://github.com/libp2p/js-libp2p/issues/2872)) ([b5a2d3e](https://github.com/libp2p/js-libp2p/commit/b5a2d3e29a82dd075d68928120d4bb1403caf50f)) +* add isLinkLocal function ([#2856](https://github.com/libp2p/js-libp2p/issues/2856)) ([5ac8c8b](https://github.com/libp2p/js-libp2p/commit/5ac8c8b5e06be915080f0798a1c0a3abad939a08)) +* add isLinkLocalIp function ([#2863](https://github.com/libp2p/js-libp2p/issues/2863)) ([97978b9](https://github.com/libp2p/js-libp2p/commit/97978b93e5f014d26d127136d7025aa4e76bec3c)) + + +### Bug Fixes + +* isPrivate should handle more types of addresses ([#2846](https://github.com/libp2p/js-libp2p/issues/2846)) ([671bc47](https://github.com/libp2p/js-libp2p/commit/671bc47656199d90410719824f1af5cdf989fdf1)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^5.0.7 to ^5.0.8 + * @libp2p/interface bumped from ^2.2.1 to ^2.3.0 + * @libp2p/logger bumped from ^5.1.4 to ^5.1.5 + * devDependencies + * @libp2p/peer-id bumped from ^5.0.8 to ^5.0.9 + ## [6.2.1](https://github.com/libp2p/js-libp2p/compare/utils-v6.2.0...utils-v6.2.1) (2024-11-18) diff --git a/packages/utils/package.json b/packages/utils/package.json index feb753eb82..6cc407f8f2 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/utils", - "version": "6.2.1", + "version": "6.3.0", "description": "Package to aggregate shared logic and dependencies for the libp2p ecosystem", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/utils#readme", @@ -76,6 +76,10 @@ "types": "./dist/src/filters/index.d.ts", "import": "./dist/src/filters/index.js" }, + "./global-unicast-ip": { + "types": "./dist/src/global-unicast-ip.d.ts", + "import": "./dist/src/global-unicast-ip.js" + }, "./ip-port-to-multiaddr": { "types": "./dist/src/ip-port-to-multiaddr.d.ts", "import": "./dist/src/ip-port-to-multiaddr.js" @@ -92,6 +96,10 @@ "types": "./dist/src/moving-average.d.ts", "import": "./dist/src/moving-average.js" }, + "./multiaddr/is-global-unicast": { + "types": "./dist/src/multiaddr/is-global-unicast.d.ts", + "import": "./dist/src/multiaddr/is-global-unicast.js" + }, "./multiaddr/is-link-local": { "types": "./dist/src/multiaddr/is-link-local.d.ts", "import": "./dist/src/multiaddr/is-link-local.js" @@ -164,9 +172,10 @@ }, "dependencies": { "@chainsafe/is-ip": "^2.0.2", - "@libp2p/crypto": "^5.0.7", - "@libp2p/interface": "^2.2.1", - "@libp2p/logger": "^5.1.4", + "@chainsafe/netmask": "^2.0.0", + "@libp2p/crypto": "^5.0.8", + "@libp2p/interface": "^2.3.0", + "@libp2p/logger": "^5.1.5", "@multiformats/multiaddr": "^12.3.3", "@sindresorhus/fnv1a": "^3.1.0", "@types/murmurhash3js-revisited": "^3.0.3", @@ -187,7 +196,7 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/peer-id": "^5.0.8", + "@libp2p/peer-id": "^5.0.9", "@types/netmask": "^2.0.5", "aegir": "^45.0.5", "benchmark": "^2.1.4", diff --git a/packages/utils/src/global-unicast-ip.ts b/packages/utils/src/global-unicast-ip.ts new file mode 100644 index 0000000000..22a8d0d532 --- /dev/null +++ b/packages/utils/src/global-unicast-ip.ts @@ -0,0 +1,10 @@ +import { isIPv6 } from '@chainsafe/is-ip' +import { cidrContains } from '@chainsafe/netmask' + +export function isGlobalUnicastIp (ip: string): boolean { + if (isIPv6(ip)) { + return cidrContains('2000::/3', ip) + } + + return false +} diff --git a/packages/utils/src/multiaddr/is-global-unicast.ts b/packages/utils/src/multiaddr/is-global-unicast.ts new file mode 100644 index 0000000000..eeffb71aac --- /dev/null +++ b/packages/utils/src/multiaddr/is-global-unicast.ts @@ -0,0 +1,25 @@ +import { cidrContains } from '@chainsafe/netmask' +import type { Multiaddr } from '@multiformats/multiaddr' + +const CODEC_IP6 = 0x29 + +/** + * Check if a given multiaddr is an IPv6 global unicast address + */ +export function isGlobalUnicast (ma: Multiaddr): boolean { + try { + const [[codec, value]] = ma.stringTuples() + + if (value == null) { + return false + } + + if (codec === CODEC_IP6) { + return cidrContains('2000::/3', value) + } + } catch { + + } + + return false +} diff --git a/packages/utils/test/global-unicast-ip.spec.ts b/packages/utils/test/global-unicast-ip.spec.ts new file mode 100644 index 0000000000..e9568e587d --- /dev/null +++ b/packages/utils/test/global-unicast-ip.spec.ts @@ -0,0 +1,53 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import { isGlobalUnicastIp } from '../src/global-unicast-ip.js' + +describe('isGlobalUnicastIp', () => { + it('identifies ip4 multiaddrs as non-global unicast', () => { + [ + '169.254.35.4', + '169.254.35.4', + '169.254.0.0', + '169.254.255.255', + '101.0.26.90', + '10.0.0.1', + '192.168.0.1', + '172.16.0.1' + ].forEach(ma => { + expect(isGlobalUnicastIp(ma)).to.be.false(`"${ma}" was identified as global unicast`) + }) + }) + + it('identifies global unicast ip6 multiaddrs', () => { + [ + '2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095', + '2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095%en0' + ].forEach(ma => { + expect(isGlobalUnicastIp(ma)).to.be.true(`"${ma}" was not identified as global unicast`) + }) + }) + + it('identifies non global unicast ip6 multiaddrs', () => { + [ + '::', + 'fe80::1%lo0', + 'fe80::1%lo0', + 'fe80::1893:def4:af04:635a%en', + 'fe80::1893:def4:af04:635a', + 'fe80::1893:def4:af04:635a', + '::2:0:59c:a24:801' + ].forEach(ma => { + expect(isGlobalUnicastIp(ma)).to.be.false(`"${ma}" was identified as global unicast`) + }) + }) + + it('identifies other multiaddrs as not global unicast addresses', () => { + [ + 'wss0.bootstrap.libp2p.io', + 'wss0.bootstrap.libp2p.io' + ].forEach(ma => { + expect(isGlobalUnicastIp(ma)).to.be.false(`"${ma}" was identified as global unicast`) + }) + }) +}) diff --git a/packages/utils/test/link-local-ip.spec.ts b/packages/utils/test/link-local-ip.spec.ts index 66b9c7e94d..1142f65f63 100644 --- a/packages/utils/test/link-local-ip.spec.ts +++ b/packages/utils/test/link-local-ip.spec.ts @@ -40,7 +40,7 @@ describe('isLinkLocalIp', () => { it('identifies non link-local ip6 multiaddrs', () => { [ - '2001:8a0:7ac5:4201:3ac9:86ff:fe31', + '2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095', '::' ].forEach(ma => { expect(isLinkLocalIp(ma)).to.be.false() diff --git a/packages/utils/test/multiaddr/is-global-unicast.spec.ts b/packages/utils/test/multiaddr/is-global-unicast.spec.ts new file mode 100644 index 0000000000..378c04c1e9 --- /dev/null +++ b/packages/utils/test/multiaddr/is-global-unicast.spec.ts @@ -0,0 +1,54 @@ +/* eslint-env mocha */ + +import { multiaddr } from '@multiformats/multiaddr' +import { expect } from 'aegir/chai' +import { isGlobalUnicast } from '../../src/multiaddr/is-global-unicast.js' + +describe('multiaddr isGlobalUnicast', () => { + it('identifies ip4 multiaddrs as non-global unicast', () => { + [ + multiaddr('/ip4/169.254.35.4'), + multiaddr('/ip4/169.254.35.4/tcp/1000'), + multiaddr('/ip4/169.254.0.0/tcp/1000'), + multiaddr('/ip4/169.254.255.255/tcp/1000'), + multiaddr('/ip4/101.0.26.90/tcp/1000'), + multiaddr('/ip4/10.0.0.1/tcp/1000'), + multiaddr('/ip4/192.168.0.1/tcp/1000'), + multiaddr('/ip4/172.16.0.1/tcp/1000') + ].forEach(ma => { + expect(isGlobalUnicast(ma)).to.be.false(`"${ma}" was identified as global unicast`) + }) + }) + + it('identifies global unicast ip6 multiaddrs', () => { + [ + multiaddr('/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/1000'), + multiaddr('/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095%en0/tcp/1000') + ].forEach(ma => { + expect(isGlobalUnicast(ma)).to.be.true(`"${ma}" was not identified as global unicast`) + }) + }) + + it('identifies non global unicast ip6 multiaddrs', () => { + [ + multiaddr('/ip6/fe80::1%lo0'), + multiaddr('/ip6/fe80::1%lo0/tcp/1000'), + multiaddr('/ip6/fe80::1893:def4:af04:635a%en'), + multiaddr('/ip6/fe80::1893:def4:af04:635a'), + multiaddr('/ip6/fe80::1893:def4:af04:635a/udp/2183'), + multiaddr('/ip6/::/tcp/1000'), + multiaddr('/ip6/::2:0:59c:a24:801/tcp/64142') + ].forEach(ma => { + expect(isGlobalUnicast(ma)).to.be.false(`"${ma}" was identified as global unicast`) + }) + }) + + it('identifies other multiaddrs as not global unicast addresses', () => { + [ + multiaddr('/dns4/wss0.bootstrap.libp2p.io/tcp/443'), + multiaddr('/dns6/wss0.bootstrap.libp2p.io/tcp/443') + ].forEach(ma => { + expect(isGlobalUnicast(ma)).to.be.false(`"${ma}" was identified as global unicast`) + }) + }) +})