@libp2p/autonat 2.0.12-b248eefc0 → 2.0.12-d19974d93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/autonat.ts CHANGED
@@ -1,39 +1,86 @@
1
- import { AbortError, serviceCapabilities, setMaxListeners } from '@libp2p/interface'
1
+ import { serviceCapabilities, serviceDependencies, setMaxListeners } from '@libp2p/interface'
2
+ import { peerSet } from '@libp2p/peer-collections'
2
3
  import { peerIdFromMultihash } from '@libp2p/peer-id'
3
- import { isPrivateIp } from '@libp2p/utils/private-ip'
4
- import { multiaddr, protocols } from '@multiformats/multiaddr'
5
- import first from 'it-first'
6
- import * as lp from 'it-length-prefixed'
7
- import map from 'it-map'
8
- import parallel from 'it-parallel'
9
- import { pipe } from 'it-pipe'
4
+ import { createScalableCuckooFilter } from '@libp2p/utils/filters'
5
+ import { isPrivate } from '@libp2p/utils/multiaddr/is-private'
6
+ import { PeerQueue } from '@libp2p/utils/peer-queue'
7
+ import { repeatingTask } from '@libp2p/utils/repeating-task'
8
+ import { multiaddr, protocols, type Multiaddr } from '@multiformats/multiaddr'
9
+ import { anySignal } from 'any-signal'
10
+ import { pbStream } from 'it-protobuf-stream'
10
11
  import * as Digest from 'multiformats/hashes/digest'
11
12
  import {
12
- MAX_INBOUND_STREAMS,
13
- MAX_OUTBOUND_STREAMS,
14
- PROTOCOL_NAME, PROTOCOL_PREFIX, PROTOCOL_VERSION, REFRESH_INTERVAL, STARTUP_DELAY, TIMEOUT
13
+ MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, PROTOCOL_NAME, PROTOCOL_PREFIX, PROTOCOL_VERSION, TIMEOUT
15
14
  } from './constants.js'
16
15
  import { Message } from './pb/index.js'
17
16
  import type { AutoNATComponents, AutoNATServiceInit } from './index.js'
18
- import type { Logger, Connection, PeerId, PeerInfo, Startable, AbortOptions } from '@libp2p/interface'
17
+ import type { Logger, Connection, PeerId, Startable, AbortOptions } from '@libp2p/interface'
19
18
  import type { IncomingStreamData } from '@libp2p/interface-internal'
19
+ import type { PeerSet } from '@libp2p/peer-collections'
20
+ import type { Filter } from '@libp2p/utils/filters'
21
+ import type { RepeatingTask } from '@libp2p/utils/repeating-task'
20
22
 
21
23
  // if more than 3 peers manage to dial us on what we believe to be our external
22
24
  // IP then we are convinced that it is, in fact, our external IP
23
- // https://github.com/libp2p/specs/blob/master/autonat/README.md#autonat-protocol
25
+ // https://github.com/libp2p/specs/blob/master/autonat/autonat-v1.md#autonat-protocol
24
26
  const REQUIRED_SUCCESSFUL_DIALS = 4
25
27
 
28
+ interface TestAddressOptions extends AbortOptions {
29
+ multiaddr: Multiaddr
30
+ peerId: PeerId
31
+ }
32
+
33
+ interface DialResults {
34
+ /**
35
+ * The address being tested
36
+ */
37
+ multiaddr: Multiaddr
38
+
39
+ /**
40
+ * The number of successful dials from peers
41
+ */
42
+ success: number
43
+
44
+ /**
45
+ * The number of dial failures from peers
46
+ */
47
+ failure: number
48
+
49
+ /**
50
+ * For the multiaddr corresponding the the string key of the `dialResults`
51
+ * map, these are the IP segments that a successful dial result has been
52
+ * received from
53
+ */
54
+ networkSegments: string[]
55
+
56
+ /**
57
+ * Ensure that the same peer id can't verify multiple times
58
+ */
59
+ verifyingPeers: PeerSet
60
+
61
+ /**
62
+ * The number of peers currently verifying this address
63
+ */
64
+ queue: PeerQueue<void, TestAddressOptions>
65
+
66
+ /**
67
+ * Updated when this address is verified or failed
68
+ */
69
+ result?: boolean
70
+ }
71
+
26
72
  export class AutoNATService implements Startable {
27
73
  private readonly components: AutoNATComponents
28
- private readonly startupDelay: number
29
- private readonly refreshInterval: number
30
74
  private readonly protocol: string
31
75
  private readonly timeout: number
32
76
  private readonly maxInboundStreams: number
33
77
  private readonly maxOutboundStreams: number
34
- private verifyAddressTimeout?: ReturnType<typeof setTimeout>
35
78
  private started: boolean
36
79
  private readonly log: Logger
80
+ private topologyId?: string
81
+ private readonly dialResults: Map<string, DialResults>
82
+ private readonly findPeers: RepeatingTask
83
+ private readonly addressFilter: Filter
37
84
 
38
85
  constructor (components: AutoNATComponents, init: AutoNATServiceInit) {
39
86
  this.components = components
@@ -43,9 +90,9 @@ export class AutoNATService implements Startable {
43
90
  this.timeout = init.timeout ?? TIMEOUT
44
91
  this.maxInboundStreams = init.maxInboundStreams ?? MAX_INBOUND_STREAMS
45
92
  this.maxOutboundStreams = init.maxOutboundStreams ?? MAX_OUTBOUND_STREAMS
46
- this.startupDelay = init.startupDelay ?? STARTUP_DELAY
47
- this.refreshInterval = init.refreshInterval ?? REFRESH_INTERVAL
48
- this._verifyExternalAddresses = this._verifyExternalAddresses.bind(this)
93
+ this.dialResults = new Map()
94
+ this.findPeers = repeatingTask(this.findRandomPeers.bind(this), 60_000)
95
+ this.addressFilter = createScalableCuckooFilter(1024)
49
96
  }
50
97
 
51
98
  readonly [Symbol.toStringTag] = '@libp2p/autonat'
@@ -54,6 +101,12 @@ export class AutoNATService implements Startable {
54
101
  '@libp2p/autonat'
55
102
  ]
56
103
 
104
+ get [serviceDependencies] (): string[] {
105
+ return [
106
+ '@libp2p/identify'
107
+ ]
108
+ }
109
+
57
110
  isStarted (): boolean {
58
111
  return this.started
59
112
  }
@@ -66,100 +119,107 @@ export class AutoNATService implements Startable {
66
119
  await this.components.registrar.handle(this.protocol, (data) => {
67
120
  void this.handleIncomingAutonatStream(data)
68
121
  .catch(err => {
69
- this.log.error('error handling incoming autonat stream', err)
122
+ this.log.error('error handling incoming autonat stream - %e', err)
70
123
  })
71
124
  }, {
72
125
  maxInboundStreams: this.maxInboundStreams,
73
126
  maxOutboundStreams: this.maxOutboundStreams
74
127
  })
75
128
 
76
- this.verifyAddressTimeout = setTimeout(this._verifyExternalAddresses, this.startupDelay)
129
+ this.topologyId = await this.components.registrar.register(this.protocol, {
130
+ onConnect: (peerId, connection) => {
131
+ this.verifyExternalAddresses(connection)
132
+ .catch(err => {
133
+ this.log.error('could not verify addresses - %e', err)
134
+ })
135
+ }
136
+ })
77
137
 
138
+ this.findPeers.start()
78
139
  this.started = true
79
140
  }
80
141
 
81
142
  async stop (): Promise<void> {
82
143
  await this.components.registrar.unhandle(this.protocol)
83
- clearTimeout(this.verifyAddressTimeout)
84
144
 
145
+ if (this.topologyId != null) {
146
+ await this.components.registrar.unhandle(this.topologyId)
147
+ }
148
+
149
+ this.dialResults.clear()
150
+ this.findPeers.stop()
85
151
  this.started = false
86
152
  }
87
153
 
88
- /**
89
- * Handle an incoming AutoNAT request
90
- */
91
- async handleIncomingAutonatStream (data: IncomingStreamData): Promise<void> {
92
- const signal = AbortSignal.timeout(this.timeout)
154
+ private allAddressesAreVerified (): boolean {
155
+ return this.components.addressManager.getAddressesWithMetadata().every(addr => addr.verified)
156
+ }
93
157
 
94
- const onAbort = (): void => {
95
- data.stream.abort(new AbortError())
158
+ async findRandomPeers (options?: AbortOptions): Promise<void> {
159
+ // skip if all addresses are verified
160
+ if (this.allAddressesAreVerified()) {
161
+ return
96
162
  }
97
163
 
98
- signal.addEventListener('abort', onAbort, { once: true })
99
-
100
- // this controller may be used while dialing lots of peers so prevent MaxListenersExceededWarning
101
- // appearing in the console
102
- setMaxListeners(Infinity, signal)
164
+ const signal = anySignal([
165
+ AbortSignal.timeout(10_000),
166
+ options?.signal
167
+ ])
103
168
 
169
+ // spend a few seconds finding random peers - dial them which will run
170
+ // identify to trigger the topology callbacks and run AutoNAT
104
171
  try {
105
- const self = this
106
-
107
- await pipe(
108
- data.stream,
109
- (source) => lp.decode(source),
110
- async function * (stream) {
111
- const buf = await first(stream)
112
-
113
- if (buf == null) {
114
- self.log('no message received')
115
- yield Message.encode({
116
- type: Message.MessageType.DIAL_RESPONSE,
117
- dialResponse: {
118
- status: Message.ResponseStatus.E_BAD_REQUEST,
119
- statusText: 'No message was sent'
120
- }
121
- })
172
+ this.log('starting random walk to find peers to run AutoNAT')
122
173
 
123
- return
124
- }
174
+ for await (const peer of this.components.randomWalk.walk({ signal })) {
175
+ if (!(await this.components.connectionManager.isDialable(peer.multiaddrs))) {
176
+ this.log.trace('random peer %p was not dialable %s', peer.id, peer.multiaddrs.map(ma => ma.toString()).join(', '))
125
177
 
126
- let request: Message
178
+ // skip peers we can't dial
179
+ continue
180
+ }
127
181
 
128
- try {
129
- request = Message.decode(buf)
130
- } catch (err) {
131
- self.log.error('could not decode message', err)
182
+ try {
183
+ this.log.trace('dial random peer %p', peer.id)
184
+ await this.components.connectionManager.openConnection(peer.multiaddrs, {
185
+ signal
186
+ })
187
+ } catch {}
132
188
 
133
- yield Message.encode({
134
- type: Message.MessageType.DIAL_RESPONSE,
135
- dialResponse: {
136
- status: Message.ResponseStatus.E_BAD_REQUEST,
137
- statusText: 'Could not decode message'
138
- }
139
- })
189
+ if (this.allAddressesAreVerified()) {
190
+ this.log('stopping random walk, all addresses are verified')
191
+ return
192
+ }
193
+ }
194
+ } catch {}
195
+ }
140
196
 
141
- return
142
- }
197
+ /**
198
+ * Handle an incoming AutoNAT request
199
+ */
200
+ async handleIncomingAutonatStream (data: IncomingStreamData): Promise<void> {
201
+ const signal = AbortSignal.timeout(this.timeout)
202
+ setMaxListeners(Infinity, signal)
143
203
 
144
- yield Message.encode(await self.handleAutonatMessage(request, data.connection, {
145
- signal
146
- }))
147
- },
148
- (source) => lp.encode(source),
149
- data.stream
150
- )
151
- } catch (err) {
152
- this.log.error('error handling incoming autonat stream', err)
153
- } finally {
154
- signal.removeEventListener('abort', onAbort)
155
- }
156
- }
204
+ const messages = pbStream(data.stream).pb(Message)
157
205
 
158
- _verifyExternalAddresses (): void {
159
- void this.verifyExternalAddresses()
160
- .catch(err => {
161
- this.log.error('error verifying external address', err)
206
+ try {
207
+ const request = await messages.read({
208
+ signal
209
+ })
210
+ const response = await this.handleAutonatMessage(request, data.connection, {
211
+ signal
162
212
  })
213
+ await messages.write(response, {
214
+ signal
215
+ })
216
+ await messages.unwrap().unwrap().close({
217
+ signal
218
+ })
219
+ } catch (err: any) {
220
+ this.log.error('error handling incoming autonat stream - %e', err)
221
+ data.stream.abort(err)
222
+ }
163
223
  }
164
224
 
165
225
  private async handleAutonatMessage (message: Message, connection: Connection, options?: AbortOptions): Promise<Message> {
@@ -199,7 +259,7 @@ export class AutoNATService implements Startable {
199
259
  const digest = Digest.decode(peer.id)
200
260
  peerId = peerIdFromMultihash(digest)
201
261
  } catch (err) {
202
- this.log.error('invalid PeerId', err)
262
+ this.log.error('invalid PeerId - %e', err)
203
263
 
204
264
  return {
205
265
  type: Message.MessageType.DIAL_RESPONSE,
@@ -236,10 +296,9 @@ export class AutoNATService implements Startable {
236
296
  return isFromSameHost
237
297
  })
238
298
  .filter(ma => {
239
- const host = ma.toOptions().host
240
- const isPublicIp = !(isPrivateIp(host) ?? false)
299
+ const isPublicIp = !isPrivate(ma)
241
300
 
242
- this.log.trace('host %s was public %s', host, isPublicIp)
301
+ this.log.trace('%a was public %s', ma, isPublicIp)
243
302
  // don't try to dial private addresses
244
303
  return isPublicIp
245
304
  })
@@ -297,7 +356,7 @@ export class AutoNATService implements Startable {
297
356
  throw new Error('Unexpected remote address')
298
357
  }
299
358
 
300
- this.log('Success %p', peerId)
359
+ this.log('successfully dialed %p via %a', peerId, multiaddr)
301
360
 
302
361
  return {
303
362
  type: Message.MessageType.DIAL_RESPONSE,
@@ -307,7 +366,7 @@ export class AutoNATService implements Startable {
307
366
  }
308
367
  }
309
368
  } catch (err: any) {
310
- this.log('could not dial %p', peerId, err)
369
+ this.log('could not dial %p - %e', peerId, err)
311
370
  errorMessage = err.message
312
371
  } finally {
313
372
  if (connection != null) {
@@ -327,191 +386,277 @@ export class AutoNATService implements Startable {
327
386
  }
328
387
 
329
388
  /**
330
- * Our multicodec topology noticed a new peer that supports autonat
389
+ * The AutoNAT v1 server is only required to send us the address that it
390
+ * dialed successfully.
391
+ *
392
+ * When addresses fail, it can be because they are NATed, or because the peer
393
+ * did't support the transport, we have no way of knowing, so just send them
394
+ * one address so we can treat the response as:
395
+ *
396
+ * - OK - the dial request worked and the address is not NATed
397
+ * - E_DIAL_ERROR - the dial request failed and the address may be NATed
398
+ * - E_DIAL_REFUSED/E_BAD_REQUEST/E_INTERNAL_ERROR - the remote didn't dial the address
331
399
  */
332
- async verifyExternalAddresses (): Promise<void> {
333
- clearTimeout(this.verifyAddressTimeout)
400
+ private getFirstUnverifiedMultiaddr (segment: string): DialResults | undefined {
401
+ const addrs = this.components.addressManager.getAddressesWithMetadata()
402
+ .sort((a, b) => {
403
+ // sort addresses, prioritize DNS/IP mapped addresses over observed ones
404
+ if (a.type === 'dns-mapping') {
405
+ return -1
406
+ }
334
407
 
335
- // Do not try to push if we are not running
336
- if (!this.isStarted()) {
337
- return
338
- }
408
+ if (b.type === 'dns-mapping') {
409
+ return 1
410
+ }
339
411
 
340
- const addressManager = this.components.addressManager
412
+ if (a.type === 'ip-mapping') {
413
+ return -1
414
+ }
341
415
 
342
- const multiaddrs = addressManager.getObservedAddrs()
343
- .filter(ma => {
344
- const options = ma.toOptions()
416
+ if (b.type === 'ip-mapping') {
417
+ return 1
418
+ }
345
419
 
346
- return !(isPrivateIp(options.host) ?? false)
420
+ return 0
347
421
  })
422
+ .filter(addr => {
423
+ if (addr.verified && addr.expires > Date.now()) {
424
+ // skip verified addresses within their TTL
425
+ return false
426
+ }
348
427
 
349
- if (multiaddrs.length === 0) {
350
- this.log('no public addresses found, not requesting verification')
351
- this.verifyAddressTimeout = setTimeout(this._verifyExternalAddresses, this.refreshInterval)
352
-
353
- return
354
- }
428
+ if (isPrivate(addr.multiaddr)) {
429
+ // skip private addresses
430
+ return false
431
+ }
355
432
 
356
- const signal = AbortSignal.timeout(this.timeout)
433
+ return true
434
+ })
357
435
 
358
- // this controller may be used while dialing lots of peers so prevent MaxListenersExceededWarning
359
- // appearing in the console
360
- setMaxListeners(Infinity, signal)
436
+ for (const addr of addrs) {
437
+ const addrString = addr.multiaddr.toString()
438
+ let results = this.dialResults.get(addrString)
361
439
 
362
- const self = this
440
+ if (results != null) {
441
+ if (results.networkSegments.includes(segment)) {
442
+ this.log.trace('%a already has a network segment result from %s', results.multiaddr, segment)
443
+ // skip this address if we already have a dial result from the
444
+ // network segment the peer is in
445
+ continue
446
+ }
363
447
 
364
- try {
365
- this.log('verify multiaddrs %s', multiaddrs.map(ma => ma.toString()).join(', '))
366
-
367
- const request = Message.encode({
368
- type: Message.MessageType.DIAL,
369
- dial: {
370
- peer: {
371
- id: this.components.peerId.toMultihash().bytes,
372
- addrs: multiaddrs.map(map => map.bytes)
373
- }
448
+ if (results.queue.size > 10) {
449
+ this.log.trace('%a already has enough peers queued', results.multiaddr)
450
+ // already have enough peers verifying this address, skip on to the
451
+ // next one
452
+ continue
374
453
  }
375
- })
454
+ }
376
455
 
377
- const results: Record<string, { success: number, failure: number }> = {}
378
- const networkSegments: string[] = []
456
+ // will include this multiaddr, ensure we have a results object
457
+ if (results == null) {
458
+ const needsRevalidating = addr.expires < Date.now()
379
459
 
380
- const verifyAddress = async (peer: PeerInfo): Promise<Message.DialResponse | undefined> => {
381
- let onAbort = (): void => {}
460
+ // allow re-validating addresses that worked previously
461
+ if (needsRevalidating) {
462
+ this.addressFilter.remove?.(addrString)
463
+ }
382
464
 
383
- try {
384
- this.log('asking %p to verify multiaddr', peer.id)
465
+ if (this.addressFilter.has(addrString)) {
466
+ continue
467
+ }
385
468
 
386
- const connection = await self.components.connectionManager.openConnection(peer.id, {
387
- signal
469
+ // only try to validate the address once
470
+ this.addressFilter.add(addrString)
471
+
472
+ this.log.trace('creating dial result %s %s', needsRevalidating ? 'to revalidate' : 'for', addrString)
473
+ results = {
474
+ multiaddr: addr.multiaddr,
475
+ success: 0,
476
+ failure: 0,
477
+ networkSegments: [],
478
+ verifyingPeers: peerSet(),
479
+ queue: new PeerQueue({
480
+ concurrency: 3,
481
+ maxSize: 50
388
482
  })
483
+ }
389
484
 
390
- const stream = await connection.newStream(this.protocol, {
391
- signal
392
- })
485
+ this.dialResults.set(addrString, results)
486
+ }
393
487
 
394
- onAbort = () => { stream.abort(new AbortError()) }
488
+ return results
489
+ }
490
+ }
395
491
 
396
- signal.addEventListener('abort', onAbort, { once: true })
492
+ /**
493
+ * Removes any multiaddr result objects created for old multiaddrs that we are
494
+ * no longer waiting on
495
+ */
496
+ private removeOutdatedMultiaddrResults (): void {
497
+ const unverifiedMultiaddrs = new Set(this.components.addressManager.getAddressesWithMetadata()
498
+ .filter(({ verified }) => !verified)
499
+ .map(({ multiaddr }) => multiaddr.toString())
500
+ )
501
+
502
+ for (const multiaddr of this.dialResults.keys()) {
503
+ if (!unverifiedMultiaddrs.has(multiaddr)) {
504
+ this.log.trace('remove results for %a', multiaddr)
505
+ this.dialResults.delete(multiaddr)
506
+ }
507
+ }
508
+ }
397
509
 
398
- const buf = await pipe(
399
- [request],
400
- (source) => lp.encode(source),
401
- stream,
402
- (source) => lp.decode(source),
403
- async (stream) => first(stream)
404
- )
405
- if (buf == null) {
406
- this.log('no response received from %p', connection.remotePeer)
407
- return undefined
408
- }
409
- const response = Message.decode(buf)
510
+ /**
511
+ * Our multicodec topology noticed a new peer that supports autonat
512
+ */
513
+ async verifyExternalAddresses (connection: Connection): Promise<void> {
514
+ // do nothing if we are not running
515
+ if (!this.isStarted()) {
516
+ return
517
+ }
410
518
 
411
- if (response.type !== Message.MessageType.DIAL_RESPONSE || response.dialResponse == null) {
412
- this.log('invalid autonat response from %p', connection.remotePeer)
413
- return undefined
414
- }
519
+ // perform cleanup
520
+ this.removeOutdatedMultiaddrResults()
415
521
 
416
- if (response.dialResponse.status === Message.ResponseStatus.OK) {
417
- // make sure we use different network segments
418
- const options = connection.remoteAddr.toOptions()
419
- let segment: string
420
-
421
- if (options.family === 4) {
422
- const octets = options.host.split('.')
423
- segment = octets[0]
424
- } else if (options.family === 6) {
425
- const octets = options.host.split(':')
426
- segment = octets[0]
427
- } else {
428
- this.log('remote address "%s" was not IP4 or IP6?', options.host)
429
- return undefined
430
- }
522
+ // get multiaddrs this peer is eligible to verify
523
+ const segment = this.getNetworkSegment(connection.remoteAddr)
524
+ let results = this.getFirstUnverifiedMultiaddr(segment)
431
525
 
432
- if (networkSegments.includes(segment)) {
433
- this.log('already have response from network segment %d - %s', segment, options.host)
434
- return undefined
435
- }
526
+ if (results == null) {
527
+ this.log.trace('no unverified public addresses found for peer %p to verify, not requesting verification', connection.remotePeer)
528
+ return
529
+ }
436
530
 
437
- networkSegments.push(segment)
438
- }
531
+ results.queue.add(async (options: TestAddressOptions) => {
532
+ results = this.dialResults.get(options.multiaddr.toString())
439
533
 
440
- return response.dialResponse
441
- } catch (err) {
442
- this.log.error('error asking remote to verify multiaddr', err)
443
- } finally {
444
- signal.removeEventListener('abort', onAbort)
445
- }
534
+ if (results == null) {
535
+ this.log('%a was verified while %p was queued', options.multiaddr, connection.remotePeer)
536
+ return
446
537
  }
447
538
 
448
- // find some random peers
449
- for await (const dialResponse of parallel(map(this.components.randomWalk.walk({
539
+ const signal = AbortSignal.timeout(this.timeout)
540
+ setMaxListeners(Infinity, signal)
541
+
542
+ this.log.trace('asking %p to verify multiaddr %s', connection.remotePeer, options.multiaddr)
543
+
544
+ const stream = await connection.newStream(this.protocol, {
450
545
  signal
451
- }), (peer) => async () => verifyAddress(peer)), {
452
- concurrency: REQUIRED_SUCCESSFUL_DIALS
453
- })) {
454
- try {
455
- if (dialResponse == null) {
456
- continue
457
- }
546
+ })
458
547
 
459
- // they either told us which address worked/didn't work, or we only sent them one address
460
- const addr = dialResponse.addr == null ? multiaddrs[0] : multiaddr(dialResponse.addr)
548
+ try {
549
+ const messages = pbStream(stream).pb(Message)
550
+ const [, response] = await Promise.all([
551
+ messages.write({
552
+ type: Message.MessageType.DIAL,
553
+ dial: {
554
+ peer: {
555
+ id: this.components.peerId.toMultihash().bytes,
556
+ addrs: [options.multiaddr.bytes]
557
+ }
558
+ }
559
+ }, { signal }),
560
+ messages.read({ signal })
561
+ ])
461
562
 
462
- this.log('autonat response for %a is %s', addr, dialResponse.status)
563
+ if (response.type !== Message.MessageType.DIAL_RESPONSE || response.dialResponse == null) {
564
+ this.log('invalid autonat response from %p - %j', connection.remotePeer, response)
565
+ return
566
+ }
463
567
 
464
- if (dialResponse.status === Message.ResponseStatus.E_BAD_REQUEST) {
465
- // the remote could not parse our request
466
- continue
467
- }
568
+ const status = response.dialResponse.status
468
569
 
469
- if (dialResponse.status === Message.ResponseStatus.E_DIAL_REFUSED) {
470
- // the remote could not honour our request
471
- continue
472
- }
570
+ this.log.trace('autonat response from %p for %a is %s', connection.remotePeer, options.multiaddr, status)
473
571
 
474
- if (dialResponse.addr == null && multiaddrs.length > 1) {
475
- // we sent the remote multiple addrs but they didn't tell us which ones worked/didn't work
476
- continue
477
- }
572
+ if (status !== Message.ResponseStatus.OK && status !== Message.ResponseStatus.E_DIAL_ERROR) {
573
+ return
574
+ }
478
575
 
479
- if (!multiaddrs.some(ma => ma.equals(addr))) {
480
- this.log('peer reported %a as %s but it was not in our observed address list', addr, dialResponse.status)
481
- continue
482
- }
576
+ results = this.dialResults.get(options.multiaddr.toString())
483
577
 
484
- const addrStr = addr.toString()
578
+ if (results == null) {
579
+ this.log.trace('peer reported %a as %s but there is no result object', options.multiaddr, response.dialResponse.status)
580
+ return
581
+ }
485
582
 
486
- if (results[addrStr] == null) {
487
- results[addrStr] = { success: 0, failure: 0 }
488
- }
583
+ if (results.networkSegments.includes(segment)) {
584
+ this.log.trace('%a results included network segment %s', options.multiaddr, segment)
585
+ return
586
+ }
489
587
 
490
- if (dialResponse.status === Message.ResponseStatus.OK) {
491
- results[addrStr].success++
492
- } else if (dialResponse.status === Message.ResponseStatus.E_DIAL_ERROR) {
493
- results[addrStr].failure++
494
- }
588
+ if (results.result != null) {
589
+ this.log.trace('already resolved result for %a, ignoring response from', options.multiaddr, connection.remotePeer)
590
+ return
591
+ }
495
592
 
496
- if (results[addrStr].success === REQUIRED_SUCCESSFUL_DIALS) {
497
- // we are now convinced
498
- this.log('%a is externally dialable', addr)
499
- addressManager.confirmObservedAddr(addr)
500
- return
501
- }
593
+ if (results.verifyingPeers.has(connection.remotePeer)) {
594
+ this.log.trace('peer %p has already verified %a, ignoring response', connection.remotePeer, options.multiaddr)
595
+ return
596
+ }
502
597
 
503
- if (results[addrStr].failure === REQUIRED_SUCCESSFUL_DIALS) {
504
- // we are now unconvinced
505
- this.log('%a is not externally dialable', addr)
506
- addressManager.removeObservedAddr(addr)
507
- return
508
- }
509
- } catch (err) {
510
- this.log.error('could not verify external address', err)
598
+ results.verifyingPeers.add(connection.remotePeer)
599
+ results.networkSegments.push(segment)
600
+
601
+ if (status === Message.ResponseStatus.OK) {
602
+ results.success++
603
+ } else if (status === Message.ResponseStatus.E_DIAL_ERROR) {
604
+ results.failure++
605
+ }
606
+
607
+ this.log('%a success %d failure %d', results.multiaddr, results.success, results.failure)
608
+
609
+ if (results.success === REQUIRED_SUCCESSFUL_DIALS) {
610
+ // we are now convinced
611
+ this.log('%a is externally dialable', results.multiaddr)
612
+ this.components.addressManager.confirmObservedAddr(results.multiaddr)
613
+ this.dialResults.delete(results.multiaddr.toString())
614
+
615
+ // abort & remove any outstanding verification jobs for this multiaddr
616
+ results.result = true
617
+ results.queue.abort()
618
+ }
619
+
620
+ if (results.failure === REQUIRED_SUCCESSFUL_DIALS) {
621
+ // we are now unconvinced
622
+ this.log('%a is not externally dialable', results.multiaddr)
623
+ this.components.addressManager.removeObservedAddr(results.multiaddr)
624
+ this.dialResults.delete(results.multiaddr.toString())
625
+
626
+ // abort & remove any outstanding verification jobs for this multiaddr
627
+ results.result = false
628
+ results.queue.abort()
629
+ }
630
+ } finally {
631
+ try {
632
+ await stream.close({
633
+ signal
634
+ })
635
+ } catch (err: any) {
636
+ stream.abort(err)
511
637
  }
512
638
  }
513
- } finally {
514
- this.verifyAddressTimeout = setTimeout(this._verifyExternalAddresses, this.refreshInterval)
639
+ }, {
640
+ peerId: connection.remotePeer,
641
+ multiaddr: results.multiaddr
642
+ })
643
+ .catch(err => {
644
+ if (results?.result == null) {
645
+ this.log.error('error from %p verifying address %a - %e', connection.remotePeer, results?.multiaddr, err)
646
+ }
647
+ })
648
+ }
649
+
650
+ private getNetworkSegment (ma: Multiaddr): string {
651
+ // make sure we use different network segments
652
+ const options = ma.toOptions()
653
+
654
+ if (options.family === 4) {
655
+ const octets = options.host.split('.')
656
+ return octets[0].padStart(3, '0')
515
657
  }
658
+
659
+ const octets = options.host.split(':')
660
+ return octets[0].padStart(4, '0')
516
661
  }
517
662
  }