Skip to content

Commit 1c1c49e

Browse files
authored
fix: add multiaddr resolvers (#3200)
Use internal multiaddr resolvers so they can be removed from the Multiaddr class to make it more lightweight.
1 parent 82ac83c commit 1c1c49e

File tree

13 files changed

+428
-64
lines changed

13 files changed

+428
-64
lines changed

packages/interface/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"build": "aegir build"
4242
},
4343
"dependencies": {
44+
"@multiformats/dns": "^1.0.6",
4445
"@multiformats/multiaddr": "^12.4.4",
4546
"it-pushable": "^3.2.3",
4647
"it-stream-types": "^2.0.2",

packages/interface/src/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type { Startable } from './startable.js'
2626
import type { StreamHandler, StreamHandlerOptions } from './stream-handler.js'
2727
import type { Topology } from './topology.js'
2828
import type { Listener, OutboundConnectionUpgradeEvents } from './transport.js'
29+
import type { DNS } from '@multiformats/dns'
2930
import type { Multiaddr } from '@multiformats/multiaddr'
3031
import type { TypedEventTarget } from 'main-event'
3132
import type { ProgressOptions, ProgressEvent } from 'progress-events'
@@ -173,6 +174,29 @@ export interface ComponentLogger {
173174
forComponent(name: string): Logger
174175
}
175176

177+
export interface MultiaddrResolveOptions extends AbortOptions, LoggerOptions {
178+
/**
179+
* An optional DNS resolver
180+
*/
181+
dns?: DNS
182+
}
183+
184+
/**
185+
* `MultiaddrResolver`s perform resolution of multiaddr components that require
186+
* translation by external systems (for example DNSADDR to TXT records).
187+
*/
188+
export interface MultiaddrResolver {
189+
/**
190+
* Returns true if this resolver can resolve components of this multiaddr
191+
*/
192+
canResolve (address: Multiaddr): boolean
193+
194+
/**
195+
* Returns one or more multiaddrs with components resolved to other values
196+
*/
197+
resolve (address: Multiaddr, options: MultiaddrResolveOptions): Promise<Multiaddr[]>
198+
}
199+
176200
/**
177201
* Once you have a libp2p instance, you can listen to several events it emits,
178202
* so that you can be notified of relevant network events.

packages/libp2p/src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FaultTolerance, InvalidParametersError } from '@libp2p/interface'
22
import { mergeOptions } from '@libp2p/utils/merge-options'
3-
import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers'
3+
import { dnsaddrResolver } from './connection-manager/resolvers/dnsaddr.ts'
44
import type { Libp2pInit } from './index.js'
55
import type { ServiceMap } from '@libp2p/interface'
66
import type { Multiaddr } from '@multiformats/multiaddr'

packages/libp2p/src/connection-manager/constants.defaults.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,8 @@ export const LAST_DIAL_SUCCESS_KEY = 'last-dial-success'
5454
* @see https://libp2p.github.io/js-libp2p/interfaces/index._internal_.ConnectionManagerConfig.html#maxDialQueueLength
5555
*/
5656
export const MAX_DIAL_QUEUE_LENGTH = 500
57+
58+
/**
59+
* @see https://libp2p.github.io/js-libp2p/interfaces/index._internal_.ConnectionManagerConfig.html#maxRecursiveDepth
60+
*/
61+
export const MAX_RECURSIVE_DEPTH = 32

packages/libp2p/src/connection-manager/dial-queue.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
import { TimeoutError, DialError, AbortError } from '@libp2p/interface'
33
import { PeerMap } from '@libp2p/peer-collections'
44
import { PriorityQueue } from '@libp2p/utils/priority-queue'
5-
import { resolvers, multiaddr } from '@multiformats/multiaddr'
6-
import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers'
5+
import { multiaddr } from '@multiformats/multiaddr'
76
import { Circuit } from '@multiformats/multiaddr-matcher'
87
import { anySignal } from 'any-signal'
98
import { setMaxListeners } from 'main-event'
@@ -20,13 +19,13 @@ import {
2019
MAX_DIAL_QUEUE_LENGTH,
2120
LAST_DIAL_SUCCESS_KEY
2221
} from './constants.js'
23-
import { resolveMultiaddrs } from './utils.js'
22+
import { resolveMultiaddr, dnsaddrResolver } from './resolvers/index.js'
2423
import { DEFAULT_DIAL_PRIORITY } from './index.js'
25-
import type { AddressSorter, ComponentLogger, Logger, Connection, ConnectionGater, Metrics, PeerId, Address, PeerStore, PeerRouting, IsDialableOptions, OpenConnectionProgressEvents } from '@libp2p/interface'
24+
import type { AddressSorter, ComponentLogger, Logger, Connection, ConnectionGater, Metrics, PeerId, Address, PeerStore, PeerRouting, IsDialableOptions, OpenConnectionProgressEvents, MultiaddrResolver } from '@libp2p/interface'
2625
import type { OpenConnectionOptions, TransportManager } from '@libp2p/interface-internal'
2726
import type { PriorityQueueJobOptions } from '@libp2p/utils/priority-queue'
2827
import type { DNS } from '@multiformats/dns'
29-
import type { Multiaddr, Resolver } from '@multiformats/multiaddr'
28+
import type { Multiaddr } from '@multiformats/multiaddr'
3029
import type { ProgressOptions } from 'progress-events'
3130

3231
export interface PendingDialTarget {
@@ -45,7 +44,7 @@ interface DialerInit {
4544
maxDialQueueLength?: number
4645
maxPeerAddrsToDial?: number
4746
dialTimeout?: number
48-
resolvers?: Record<string, Resolver>
47+
resolvers?: Record<string, MultiaddrResolver>
4948
connections?: PeerMap<Connection[]>
5049
}
5150

@@ -80,6 +79,7 @@ export class DialQueue {
8079
private shutDownController: AbortController
8180
private readonly connections: PeerMap<Connection[]>
8281
private readonly log: Logger
82+
private readonly resolvers: Record<string, MultiaddrResolver>
8383

8484
constructor (components: DialQueueComponents, init: DialerInit = {}) {
8585
this.addressSorter = init.addressSorter
@@ -89,14 +89,11 @@ export class DialQueue {
8989
this.connections = init.connections ?? new PeerMap()
9090
this.log = components.logger.forComponent('libp2p:connection-manager:dial-queue')
9191
this.components = components
92+
this.resolvers = init.resolvers ?? defaultOptions.resolvers
9293

9394
this.shutDownController = new AbortController()
9495
setMaxListeners(Infinity, this.shutDownController.signal)
9596

96-
for (const [key, value] of Object.entries(init.resolvers ?? {})) {
97-
resolvers.set(key, value)
98-
}
99-
10097
// controls dial concurrency
10198
this.queue = new PriorityQueue({
10299
concurrency: init.maxParallelDials ?? defaultOptions.maxParallelDials,
@@ -415,10 +412,10 @@ export class DialQueue {
415412
// dnsaddrs are resolved
416413
let resolvedAddresses = (await Promise.all(
417414
addrs.map(async addr => {
418-
const result = await resolveMultiaddrs(addr.multiaddr, {
415+
const result = await resolveMultiaddr(addr.multiaddr, this.resolvers, {
419416
dns: this.components.dns,
420-
...options,
421-
log: this.log
417+
log: this.log,
418+
...options
422419
})
423420

424421
if (result.length === 1 && result[0].equals(addr.multiaddr)) {

packages/libp2p/src/connection-manager/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ import { ConnectionClosedError, InvalidMultiaddrError, InvalidParametersError, I
22
import { PeerMap } from '@libp2p/peer-collections'
33
import { RateLimiter } from '@libp2p/utils/rate-limiter'
44
import { multiaddr } from '@multiformats/multiaddr'
5-
import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers'
65
import { CustomProgressEvent } from 'progress-events'
76
import { getPeerAddress } from '../get-peer.js'
87
import { ConnectionPruner } from './connection-pruner.js'
98
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'
109
import { DialQueue } from './dial-queue.js'
1110
import { ReconnectQueue } from './reconnect-queue.js'
11+
import { dnsaddrResolver } from './resolvers/index.ts'
1212
import { multiaddrToIpNet } from './utils.js'
1313
import type { IpNet } from '@chainsafe/netmask'
14-
import type { PendingDial, AddressSorter, Libp2pEvents, AbortOptions, ComponentLogger, Logger, Connection, MultiaddrConnection, ConnectionGater, Metrics, PeerId, PeerStore, Startable, PendingDialStatus, PeerRouting, IsDialableOptions } from '@libp2p/interface'
14+
import type { PendingDial, AddressSorter, Libp2pEvents, AbortOptions, ComponentLogger, Logger, Connection, MultiaddrConnection, ConnectionGater, Metrics, PeerId, PeerStore, Startable, PendingDialStatus, PeerRouting, IsDialableOptions, MultiaddrResolver } from '@libp2p/interface'
1515
import type { ConnectionManager, OpenConnectionOptions, TransportManager } from '@libp2p/interface-internal'
1616
import type { JobStatus } from '@libp2p/utils/queue'
17-
import type { Multiaddr, Resolver } from '@multiformats/multiaddr'
17+
import type { Multiaddr } from '@multiformats/multiaddr'
1818
import type { TypedEventTarget } from 'main-event'
1919

2020
export const DEFAULT_DIAL_PRIORITY = 50
@@ -112,7 +112,7 @@ export interface ConnectionManagerInit {
112112
/**
113113
* Multiaddr resolvers to use when dialling
114114
*/
115-
resolvers?: Record<string, Resolver>
115+
resolvers?: Record<string, MultiaddrResolver>
116116

117117
/**
118118
* A list of multiaddrs that will always be allowed (except if they are in the
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { dns, RecordType } from '@multiformats/dns'
2+
import { multiaddr } from '@multiformats/multiaddr'
3+
import type { MultiaddrResolver, MultiaddrResolveOptions } from '@libp2p/interface'
4+
import type { DNS } from '@multiformats/dns'
5+
import type { Multiaddr } from '@multiformats/multiaddr'
6+
7+
class DNSAddrResolver implements MultiaddrResolver {
8+
private dns?: DNS
9+
10+
canResolve (ma: Multiaddr): boolean {
11+
return ma.getComponents().some(({ name }) => name === 'dnsaddr')
12+
}
13+
14+
async resolve (ma: Multiaddr, options: MultiaddrResolveOptions): Promise<Multiaddr[]> {
15+
const hostname = ma.getComponents()
16+
.find(component => component.name === 'dnsaddr')
17+
?.value
18+
19+
if (hostname == null) {
20+
return [ma]
21+
}
22+
23+
const resolver = this.getDNS(options)
24+
const result = await resolver.query(`_dnsaddr.${hostname}`, {
25+
signal: options?.signal,
26+
types: [
27+
RecordType.TXT
28+
]
29+
})
30+
31+
const peerId = ma.getComponents()
32+
.find(component => component.name === 'p2p')
33+
?.value
34+
const output: Multiaddr[] = []
35+
36+
for (const answer of result.Answer) {
37+
const addr = answer.data
38+
.replace(/["']/g, '')
39+
.trim()
40+
.split('=')[1]
41+
42+
if (addr == null) {
43+
continue
44+
}
45+
46+
if (peerId != null && !addr.includes(peerId)) {
47+
continue
48+
}
49+
50+
output.push(multiaddr(addr))
51+
}
52+
53+
return output
54+
}
55+
56+
private getDNS (options: MultiaddrResolveOptions): DNS {
57+
if (options.dns != null) {
58+
return options.dns
59+
}
60+
61+
if (this.dns == null) {
62+
this.dns = dns()
63+
}
64+
65+
return this.dns
66+
}
67+
}
68+
69+
export const dnsaddrResolver = new DNSAddrResolver()
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { RecursionLimitError } from '../../errors.ts'
2+
import { MAX_RECURSIVE_DEPTH } from '../constants.defaults.ts'
3+
import type { MultiaddrResolveOptions, MultiaddrResolver } from '@libp2p/interface'
4+
import type { Multiaddr } from '@multiformats/multiaddr'
5+
6+
export interface ResolveOptions extends MultiaddrResolveOptions {
7+
/**
8+
* When resolving DNSADDR Multiaddrs that resolve to other DNSADDR Multiaddrs,
9+
* limit how many times we will recursively resolve them.
10+
*
11+
* @default 32
12+
*/
13+
maxRecursiveDepth?: number
14+
15+
/**
16+
* The current recursive depth
17+
*
18+
* @default 0
19+
*/
20+
depth?: number
21+
}
22+
23+
/**
24+
* Recursively resolve multiaddrs
25+
*/
26+
export async function resolveMultiaddr (address: Multiaddr, resolvers: Record<string, MultiaddrResolver>, options: ResolveOptions): Promise<Multiaddr[]> {
27+
const depth = options.depth ?? 0
28+
29+
if (depth > (options.maxRecursiveDepth ?? MAX_RECURSIVE_DEPTH)) {
30+
throw new RecursionLimitError('Max recursive depth reached')
31+
}
32+
33+
let resolved = false
34+
const output: Multiaddr[] = []
35+
36+
for (const resolver of Object.values(resolvers)) {
37+
if (resolver.canResolve(address)) {
38+
resolved = true
39+
const addresses = await resolver.resolve(address, options)
40+
41+
for (const address of addresses) {
42+
output.push(
43+
...(await resolveMultiaddr(address, resolvers, {
44+
...options,
45+
depth: depth + 1
46+
}))
47+
)
48+
}
49+
}
50+
}
51+
52+
if (resolved === false) {
53+
output.push(address)
54+
}
55+
56+
return output
57+
}
58+
59+
export { dnsaddrResolver } from './dnsaddr.js'

packages/libp2p/src/connection-manager/utils.ts

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,7 @@
1-
import { multiaddr, resolvers } from '@multiformats/multiaddr'
1+
import { multiaddr } from '@multiformats/multiaddr'
22
import { convertToIpNet } from '@multiformats/multiaddr/convert'
33
import type { IpNet } from '@chainsafe/netmask'
4-
import type { LoggerOptions } from '@libp2p/interface'
5-
import type { Multiaddr, ResolveOptions } from '@multiformats/multiaddr'
6-
7-
/**
8-
* Recursively resolve DNSADDR multiaddrs
9-
*/
10-
export async function resolveMultiaddrs (ma: Multiaddr, options: ResolveOptions & LoggerOptions): Promise<Multiaddr[]> {
11-
// check multiaddr resolvers
12-
let resolvable = false
13-
14-
for (const key of resolvers.keys()) {
15-
resolvable = ma.protoNames().includes(key)
16-
17-
if (resolvable) {
18-
break
19-
}
20-
}
21-
22-
// return multiaddr if it is not resolvable
23-
if (!resolvable) {
24-
return [ma]
25-
}
26-
27-
const output = await ma.resolve(options)
28-
29-
options.log('resolved %s to', ma, output.map(ma => ma.toString()))
30-
31-
return output
32-
}
4+
import type { Multiaddr } from '@multiformats/multiaddr'
335

346
/**
357
* Converts a multiaddr string or object to an IpNet object.
@@ -50,9 +22,11 @@ export function multiaddrToIpNet (ma: string | Multiaddr): IpNet {
5022
parsedMa = ma
5123
}
5224

25+
const protoNames = new Set([...parsedMa.getComponents().map(component => component.name)])
26+
5327
// Check if /ipcidr is already present
54-
if (!parsedMa.protoNames().includes('ipcidr')) {
55-
const isIPv6 = parsedMa.protoNames().includes('ip6')
28+
if (!protoNames.has('ipcidr')) {
29+
const isIPv6 = protoNames.has('ip6')
5630
const cidr = isIPv6 ? '/ipcidr/128' : '/ipcidr/32'
5731
parsedMa = parsedMa.encapsulate(cidr)
5832
}

packages/libp2p/src/errors.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,10 @@ export class TransportUnavailableError extends Error {
114114
this.name = 'TransportUnavailableError'
115115
}
116116
}
117+
118+
export class RecursionLimitError extends Error {
119+
constructor (message = 'Max recursive depth reached') {
120+
super(message)
121+
this.name = 'RecursionLimitError'
122+
}
123+
}

0 commit comments

Comments
 (0)