@libp2p/autonat 2.0.12 → 2.0.13

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.
@@ -1,46 +1,57 @@
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 { createScalableCuckooFilter } from '@libp2p/utils/filters';
5
+ import { isGlobalUnicast } from '@libp2p/utils/multiaddr/is-global-unicast';
6
+ import { isPrivate } from '@libp2p/utils/multiaddr/is-private';
7
+ import { PeerQueue } from '@libp2p/utils/peer-queue';
8
+ import { repeatingTask } from '@libp2p/utils/repeating-task';
4
9
  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';
10
+ import { anySignal } from 'any-signal';
11
+ import { pbStream } from 'it-protobuf-stream';
10
12
  import * as Digest from 'multiformats/hashes/digest';
11
- import { MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, PROTOCOL_NAME, PROTOCOL_PREFIX, PROTOCOL_VERSION, REFRESH_INTERVAL, STARTUP_DELAY, TIMEOUT } from './constants.js';
13
+ import { DEFAULT_CONNECTION_THRESHOLD, MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, PROTOCOL_NAME, PROTOCOL_PREFIX, PROTOCOL_VERSION, TIMEOUT } from './constants.js';
12
14
  import { Message } from './pb/index.js';
13
15
  // if more than 3 peers manage to dial us on what we believe to be our external
14
16
  // IP then we are convinced that it is, in fact, our external IP
15
- // https://github.com/libp2p/specs/blob/master/autonat/README.md#autonat-protocol
17
+ // https://github.com/libp2p/specs/blob/master/autonat/autonat-v1.md#autonat-protocol
16
18
  const REQUIRED_SUCCESSFUL_DIALS = 4;
19
+ const REQUIRED_FAILED_DIALS = 8;
17
20
  export class AutoNATService {
18
21
  components;
19
- startupDelay;
20
- refreshInterval;
21
22
  protocol;
22
23
  timeout;
23
24
  maxInboundStreams;
24
25
  maxOutboundStreams;
25
- verifyAddressTimeout;
26
26
  started;
27
27
  log;
28
+ topologyId;
29
+ dialResults;
30
+ findPeers;
31
+ addressFilter;
32
+ connectionThreshold;
28
33
  constructor(components, init) {
29
34
  this.components = components;
30
- this.log = components.logger.forComponent('libp2p:autonat');
35
+ this.log = components.logger.forComponent('libp2p:auto-nat');
31
36
  this.started = false;
32
37
  this.protocol = `/${init.protocolPrefix ?? PROTOCOL_PREFIX}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`;
33
38
  this.timeout = init.timeout ?? TIMEOUT;
34
39
  this.maxInboundStreams = init.maxInboundStreams ?? MAX_INBOUND_STREAMS;
35
40
  this.maxOutboundStreams = init.maxOutboundStreams ?? MAX_OUTBOUND_STREAMS;
36
- this.startupDelay = init.startupDelay ?? STARTUP_DELAY;
37
- this.refreshInterval = init.refreshInterval ?? REFRESH_INTERVAL;
38
- this._verifyExternalAddresses = this._verifyExternalAddresses.bind(this);
41
+ this.connectionThreshold = init.connectionThreshold ?? DEFAULT_CONNECTION_THRESHOLD;
42
+ this.dialResults = new Map();
43
+ this.findPeers = repeatingTask(this.findRandomPeers.bind(this), 60_000);
44
+ this.addressFilter = createScalableCuckooFilter(1024);
39
45
  }
40
46
  [Symbol.toStringTag] = '@libp2p/autonat';
41
47
  [serviceCapabilities] = [
42
48
  '@libp2p/autonat'
43
49
  ];
50
+ get [serviceDependencies]() {
51
+ return [
52
+ '@libp2p/identify'
53
+ ];
54
+ }
44
55
  isStarted() {
45
56
  return this.started;
46
57
  }
@@ -51,80 +62,105 @@ export class AutoNATService {
51
62
  await this.components.registrar.handle(this.protocol, (data) => {
52
63
  void this.handleIncomingAutonatStream(data)
53
64
  .catch(err => {
54
- this.log.error('error handling incoming autonat stream', err);
65
+ this.log.error('error handling incoming autonat stream - %e', err);
55
66
  });
56
67
  }, {
57
68
  maxInboundStreams: this.maxInboundStreams,
58
69
  maxOutboundStreams: this.maxOutboundStreams
59
70
  });
60
- this.verifyAddressTimeout = setTimeout(this._verifyExternalAddresses, this.startupDelay);
71
+ this.topologyId = await this.components.registrar.register(this.protocol, {
72
+ onConnect: (peerId, connection) => {
73
+ this.verifyExternalAddresses(connection)
74
+ .catch(err => {
75
+ this.log.error('could not verify addresses - %e', err);
76
+ });
77
+ }
78
+ });
79
+ this.findPeers.start();
61
80
  this.started = true;
62
81
  }
63
82
  async stop() {
64
83
  await this.components.registrar.unhandle(this.protocol);
65
- clearTimeout(this.verifyAddressTimeout);
84
+ if (this.topologyId != null) {
85
+ await this.components.registrar.unhandle(this.topologyId);
86
+ }
87
+ this.dialResults.clear();
88
+ this.findPeers.stop();
66
89
  this.started = false;
67
90
  }
91
+ allAddressesAreVerified() {
92
+ return this.components.addressManager.getAddressesWithMetadata().every(addr => {
93
+ if (addr.expires > Date.now()) {
94
+ // ignore any unverified addresses within their TTL
95
+ return true;
96
+ }
97
+ return addr.verified;
98
+ });
99
+ }
100
+ async findRandomPeers(options) {
101
+ // skip if all addresses are verified
102
+ if (this.allAddressesAreVerified()) {
103
+ return;
104
+ }
105
+ const signal = anySignal([
106
+ AbortSignal.timeout(10_000),
107
+ options?.signal
108
+ ]);
109
+ // spend a few seconds finding random peers - dial them which will run
110
+ // identify to trigger the topology callbacks and run AutoNAT
111
+ try {
112
+ this.log('starting random walk to find peers to run AutoNAT');
113
+ for await (const peer of this.components.randomWalk.walk({ signal })) {
114
+ if (!(await this.components.connectionManager.isDialable(peer.multiaddrs))) {
115
+ this.log.trace('random peer %p was not dialable %s', peer.id, peer.multiaddrs.map(ma => ma.toString()).join(', '));
116
+ // skip peers we can't dial
117
+ continue;
118
+ }
119
+ try {
120
+ this.log.trace('dial random peer %p', peer.id);
121
+ await this.components.connectionManager.openConnection(peer.multiaddrs, {
122
+ signal
123
+ });
124
+ }
125
+ catch { }
126
+ if (this.allAddressesAreVerified()) {
127
+ this.log('stopping random walk, all addresses are verified');
128
+ return;
129
+ }
130
+ if (!this.hasConnectionCapacity()) {
131
+ this.log('stopping random walk, too close to max connections');
132
+ return;
133
+ }
134
+ }
135
+ }
136
+ catch { }
137
+ }
68
138
  /**
69
139
  * Handle an incoming AutoNAT request
70
140
  */
71
141
  async handleIncomingAutonatStream(data) {
72
142
  const signal = AbortSignal.timeout(this.timeout);
73
- const onAbort = () => {
74
- data.stream.abort(new AbortError());
75
- };
76
- signal.addEventListener('abort', onAbort, { once: true });
77
- // this controller may be used while dialing lots of peers so prevent MaxListenersExceededWarning
78
- // appearing in the console
79
143
  setMaxListeners(Infinity, signal);
144
+ const messages = pbStream(data.stream).pb(Message);
80
145
  try {
81
- const self = this;
82
- await pipe(data.stream, (source) => lp.decode(source), async function* (stream) {
83
- const buf = await first(stream);
84
- if (buf == null) {
85
- self.log('no message received');
86
- yield Message.encode({
87
- type: Message.MessageType.DIAL_RESPONSE,
88
- dialResponse: {
89
- status: Message.ResponseStatus.E_BAD_REQUEST,
90
- statusText: 'No message was sent'
91
- }
92
- });
93
- return;
94
- }
95
- let request;
96
- try {
97
- request = Message.decode(buf);
98
- }
99
- catch (err) {
100
- self.log.error('could not decode message', err);
101
- yield Message.encode({
102
- type: Message.MessageType.DIAL_RESPONSE,
103
- dialResponse: {
104
- status: Message.ResponseStatus.E_BAD_REQUEST,
105
- statusText: 'Could not decode message'
106
- }
107
- });
108
- return;
109
- }
110
- yield Message.encode(await self.handleAutonatMessage(request, data.connection, {
111
- signal
112
- }));
113
- }, (source) => lp.encode(source), data.stream);
146
+ const request = await messages.read({
147
+ signal
148
+ });
149
+ const response = await this.handleAutonatMessage(request, data.connection, {
150
+ signal
151
+ });
152
+ await messages.write(response, {
153
+ signal
154
+ });
155
+ await messages.unwrap().unwrap().close({
156
+ signal
157
+ });
114
158
  }
115
159
  catch (err) {
116
- this.log.error('error handling incoming autonat stream', err);
117
- }
118
- finally {
119
- signal.removeEventListener('abort', onAbort);
160
+ this.log.error('error handling incoming autonat stream - %e', err);
161
+ data.stream.abort(err);
120
162
  }
121
163
  }
122
- _verifyExternalAddresses() {
123
- void this.verifyExternalAddresses()
124
- .catch(err => {
125
- this.log.error('error verifying external address', err);
126
- });
127
- }
128
164
  async handleAutonatMessage(message, connection, options) {
129
165
  const ourHosts = this.components.addressManager.getAddresses()
130
166
  .map(ma => ma.toOptions().host);
@@ -156,7 +192,7 @@ export class AutoNATService {
156
192
  peerId = peerIdFromMultihash(digest);
157
193
  }
158
194
  catch (err) {
159
- this.log.error('invalid PeerId', err);
195
+ this.log.error('invalid PeerId - %e', err);
160
196
  return {
161
197
  type: Message.MessageType.DIAL_RESPONSE,
162
198
  dialResponse: {
@@ -181,30 +217,26 @@ export class AutoNATService {
181
217
  const multiaddrs = peer.addrs
182
218
  .map(buf => multiaddr(buf))
183
219
  .filter(ma => {
184
- const isFromSameHost = ma.toOptions().host === connection.remoteAddr.toOptions().host;
185
- this.log.trace('request to dial %a was sent from %a is same host %s', ma, connection.remoteAddr, isFromSameHost);
186
- // skip any Multiaddrs where the target node's IP does not match the sending node's IP
187
- return isFromSameHost;
188
- })
189
- .filter(ma => {
190
- const host = ma.toOptions().host;
191
- const isPublicIp = !(isPrivateIp(host) ?? false);
192
- this.log.trace('host %s was public %s', host, isPublicIp);
193
- // don't try to dial private addresses
194
- return isPublicIp;
195
- })
196
- .filter(ma => {
197
- const host = ma.toOptions().host;
198
- const isNotOurHost = !ourHosts.includes(host);
199
- this.log.trace('host %s was not our host %s', host, isNotOurHost);
200
- // don't try to dial nodes on the same host as us
201
- return isNotOurHost;
202
- })
203
- .filter(ma => {
204
- const isSupportedTransport = Boolean(this.components.transportManager.dialTransportForMultiaddr(ma));
205
- this.log.trace('transport for %a is supported %s', ma, isSupportedTransport);
206
- // skip any Multiaddrs that have transports we do not support
207
- return isSupportedTransport;
220
+ const options = ma.toOptions();
221
+ if (isPrivate(ma)) {
222
+ // don't try to dial private addresses
223
+ return false;
224
+ }
225
+ if (options.host !== connection.remoteAddr.toOptions().host) {
226
+ // skip any Multiaddrs where the target node's IP does not match the sending node's IP
227
+ this.log.trace('not dialing %a - target host did not match remote host %a', ma, connection.remoteAddr);
228
+ return false;
229
+ }
230
+ if (ourHosts.includes(options.host)) {
231
+ // don't try to dial nodes on the same host as us
232
+ return false;
233
+ }
234
+ if (this.components.transportManager.dialTransportForMultiaddr(ma) == null) {
235
+ // skip any Multiaddrs that have transports we do not support
236
+ this.log.trace('not dialing %a - transport unsupported', ma);
237
+ return false;
238
+ }
239
+ return true;
208
240
  })
209
241
  .map(ma => {
210
242
  if (ma.getPeerId() == null) {
@@ -215,7 +247,7 @@ export class AutoNATService {
215
247
  });
216
248
  // make sure we have something to dial
217
249
  if (multiaddrs.length === 0) {
218
- this.log('no valid multiaddrs for %p in message', peerId);
250
+ this.log('refused to dial all multiaddrs for %p from message', peerId);
219
251
  return {
220
252
  type: Message.MessageType.DIAL_RESPONSE,
221
253
  dialResponse: {
@@ -236,7 +268,7 @@ export class AutoNATService {
236
268
  this.log.error('tried to dial %a but dialed %a', multiaddr, connection.remoteAddr);
237
269
  throw new Error('Unexpected remote address');
238
270
  }
239
- this.log('Success %p', peerId);
271
+ this.log('successfully dialed %p via %a', peerId, multiaddr);
240
272
  return {
241
273
  type: Message.MessageType.DIAL_RESPONSE,
242
274
  dialResponse: {
@@ -246,7 +278,7 @@ export class AutoNATService {
246
278
  };
247
279
  }
248
280
  catch (err) {
249
- this.log('could not dial %p', peerId, err);
281
+ this.log.error('could not dial %p - %e', peerId, err);
250
282
  errorMessage = err.message;
251
283
  }
252
284
  finally {
@@ -264,157 +296,286 @@ export class AutoNATService {
264
296
  }
265
297
  };
266
298
  }
299
+ /**
300
+ * The AutoNAT v1 server is not required to send us the address that it
301
+ * dialed successfully.
302
+ *
303
+ * When addresses fail, it can be because they are NATed, or because the peer
304
+ * did't support the transport, we have no way of knowing, so just send them
305
+ * one address so we can treat the response as:
306
+ *
307
+ * - OK - the dial request worked and the address is not NATed
308
+ * - E_DIAL_ERROR - the dial request failed and the address may be NATed
309
+ * - E_DIAL_REFUSED/E_BAD_REQUEST/E_INTERNAL_ERROR - the remote didn't dial the address
310
+ */
311
+ getFirstUnverifiedMultiaddr(segment, supportsIPv6) {
312
+ const addrs = this.components.addressManager.getAddressesWithMetadata()
313
+ .sort((a, b) => {
314
+ // sort addresses, de-prioritize observed addresses
315
+ if (a.type === 'observed' && b.type !== 'observed') {
316
+ return 1;
317
+ }
318
+ if (b.type === 'observed' && a.type !== 'observed') {
319
+ return -1;
320
+ }
321
+ return 0;
322
+ })
323
+ .filter(addr => {
324
+ const expired = addr.expires < Date.now();
325
+ if (!expired) {
326
+ // skip verified/non-verified addresses within their TTL
327
+ return false;
328
+ }
329
+ const options = addr.multiaddr.toOptions();
330
+ if (options.family === 6) {
331
+ // do not send IPv6 addresses to peers without IPv6 addresses
332
+ if (!supportsIPv6) {
333
+ return false;
334
+ }
335
+ if (!isGlobalUnicast(addr.multiaddr)) {
336
+ // skip non-globally routable addresses
337
+ return false;
338
+ }
339
+ }
340
+ if (isPrivate(addr.multiaddr)) {
341
+ // skip private addresses
342
+ return false;
343
+ }
344
+ return true;
345
+ });
346
+ for (const addr of addrs) {
347
+ const addrString = addr.multiaddr.toString();
348
+ let results = this.dialResults.get(addrString);
349
+ if (results != null) {
350
+ if (results.networkSegments.includes(segment)) {
351
+ this.log.trace('%a already has a network segment result from %s', results.multiaddr, segment);
352
+ // skip this address if we already have a dial result from the
353
+ // network segment the peer is in
354
+ continue;
355
+ }
356
+ if (results.queue.size > 10) {
357
+ this.log.trace('%a already has enough peers queued', results.multiaddr);
358
+ // already have enough peers verifying this address, skip on to the
359
+ // next one
360
+ continue;
361
+ }
362
+ }
363
+ // will include this multiaddr, ensure we have a results object
364
+ if (results == null) {
365
+ const needsRevalidating = addr.expires < Date.now();
366
+ // allow re-validating addresses that worked previously
367
+ if (needsRevalidating) {
368
+ this.addressFilter.remove?.(addrString);
369
+ }
370
+ if (this.addressFilter.has(addrString)) {
371
+ continue;
372
+ }
373
+ // only try to validate the address once
374
+ this.addressFilter.add(addrString);
375
+ this.log.trace('creating dial result %s %s', needsRevalidating ? 'to revalidate' : 'for', addrString);
376
+ results = {
377
+ multiaddr: addr.multiaddr,
378
+ success: 0,
379
+ failure: 0,
380
+ networkSegments: [],
381
+ verifyingPeers: peerSet(),
382
+ queue: new PeerQueue({
383
+ concurrency: 3,
384
+ maxSize: 50
385
+ }),
386
+ type: addr.type,
387
+ lastVerified: addr.lastVerified
388
+ };
389
+ this.dialResults.set(addrString, results);
390
+ }
391
+ return results;
392
+ }
393
+ }
394
+ /**
395
+ * Removes any multiaddr result objects created for old multiaddrs that we are
396
+ * no longer waiting on
397
+ */
398
+ removeOutdatedMultiaddrResults() {
399
+ const unverifiedMultiaddrs = new Set(this.components.addressManager.getAddressesWithMetadata()
400
+ .filter(({ expires }) => {
401
+ if (expires < Date.now()) {
402
+ return true;
403
+ }
404
+ return false;
405
+ })
406
+ .map(({ multiaddr }) => multiaddr.toString()));
407
+ for (const multiaddr of this.dialResults.keys()) {
408
+ if (!unverifiedMultiaddrs.has(multiaddr)) {
409
+ this.log.trace('remove results for %a', multiaddr);
410
+ this.dialResults.delete(multiaddr);
411
+ }
412
+ }
413
+ }
267
414
  /**
268
415
  * Our multicodec topology noticed a new peer that supports autonat
269
416
  */
270
- async verifyExternalAddresses() {
271
- clearTimeout(this.verifyAddressTimeout);
272
- // Do not try to push if we are not running
417
+ async verifyExternalAddresses(connection) {
418
+ // do nothing if we are not running
273
419
  if (!this.isStarted()) {
274
420
  return;
275
421
  }
276
- const addressManager = this.components.addressManager;
277
- const multiaddrs = addressManager.getObservedAddrs()
278
- .filter(ma => {
279
- const options = ma.toOptions();
280
- return !(isPrivateIp(options.host) ?? false);
422
+ // perform cleanup
423
+ this.removeOutdatedMultiaddrResults();
424
+ const peer = await this.components.peerStore.get(connection.remotePeer);
425
+ // if the remote peer has IPv6 addresses, we can probably send them an IPv6
426
+ // address to verify, otherwise only send them IPv4 addresses
427
+ const supportsIPv6 = peer.addresses.some(({ multiaddr }) => {
428
+ return multiaddr.toOptions().family === 6;
281
429
  });
282
- if (multiaddrs.length === 0) {
283
- this.log('no public addresses found, not requesting verification');
284
- this.verifyAddressTimeout = setTimeout(this._verifyExternalAddresses, this.refreshInterval);
430
+ // get multiaddrs this peer is eligible to verify
431
+ const segment = this.getNetworkSegment(connection.remoteAddr);
432
+ const results = this.getFirstUnverifiedMultiaddr(segment, supportsIPv6);
433
+ if (results == null) {
434
+ this.log.trace('no unverified public addresses found for peer %p to verify, not requesting verification', connection.remotePeer);
435
+ return;
436
+ }
437
+ if (!this.hasConnectionCapacity()) {
438
+ // we are near the max connection limit - any dial attempts from remote
439
+ // peers may be rejected which will get flagged as false dial errors and
440
+ // lead us to un-verify an otherwise reachable address
441
+ if (results.lastVerified != null) {
442
+ this.log('automatically re-verifying %a because we are too close to the connection limit', results.multiaddr);
443
+ this.confirmAddress(results);
444
+ }
445
+ else {
446
+ this.log('skipping verifying %a because we are too close to the connection limit', results.multiaddr);
447
+ }
448
+ return;
449
+ }
450
+ results.queue.add(async (options) => {
451
+ await this.askPeerToVerify(connection, segment, options);
452
+ }, {
453
+ peerId: connection.remotePeer,
454
+ multiaddr: results.multiaddr
455
+ })
456
+ .catch(err => {
457
+ if (results?.result == null) {
458
+ this.log.error('error from %p verifying address %a - %e', connection.remotePeer, results?.multiaddr, err);
459
+ }
460
+ });
461
+ }
462
+ async askPeerToVerify(connection, segment, options) {
463
+ let results = this.dialResults.get(options.multiaddr.toString());
464
+ if (results == null) {
465
+ this.log('%a was verified while %p was queued', options.multiaddr, connection.remotePeer);
285
466
  return;
286
467
  }
287
468
  const signal = AbortSignal.timeout(this.timeout);
288
- // this controller may be used while dialing lots of peers so prevent MaxListenersExceededWarning
289
- // appearing in the console
290
469
  setMaxListeners(Infinity, signal);
291
- const self = this;
470
+ this.log.trace('asking %p to verify multiaddr %s', connection.remotePeer, options.multiaddr);
471
+ const stream = await connection.newStream(this.protocol, {
472
+ signal
473
+ });
292
474
  try {
293
- this.log('verify multiaddrs %s', multiaddrs.map(ma => ma.toString()).join(', '));
294
- const request = Message.encode({
295
- type: Message.MessageType.DIAL,
296
- dial: {
297
- peer: {
298
- id: this.components.peerId.toMultihash().bytes,
299
- addrs: multiaddrs.map(map => map.bytes)
300
- }
301
- }
302
- });
303
- const results = {};
304
- const networkSegments = [];
305
- const verifyAddress = async (peer) => {
306
- let onAbort = () => { };
307
- try {
308
- this.log('asking %p to verify multiaddr', peer.id);
309
- const connection = await self.components.connectionManager.openConnection(peer.id, {
310
- signal
311
- });
312
- const stream = await connection.newStream(this.protocol, {
313
- signal
314
- });
315
- onAbort = () => { stream.abort(new AbortError()); };
316
- signal.addEventListener('abort', onAbort, { once: true });
317
- const buf = await pipe([request], (source) => lp.encode(source), stream, (source) => lp.decode(source), async (stream) => first(stream));
318
- if (buf == null) {
319
- this.log('no response received from %p', connection.remotePeer);
320
- return undefined;
321
- }
322
- const response = Message.decode(buf);
323
- if (response.type !== Message.MessageType.DIAL_RESPONSE || response.dialResponse == null) {
324
- this.log('invalid autonat response from %p', connection.remotePeer);
325
- return undefined;
326
- }
327
- if (response.dialResponse.status === Message.ResponseStatus.OK) {
328
- // make sure we use different network segments
329
- const options = connection.remoteAddr.toOptions();
330
- let segment;
331
- if (options.family === 4) {
332
- const octets = options.host.split('.');
333
- segment = octets[0];
334
- }
335
- else if (options.family === 6) {
336
- const octets = options.host.split(':');
337
- segment = octets[0];
338
- }
339
- else {
340
- this.log('remote address "%s" was not IP4 or IP6?', options.host);
341
- return undefined;
342
- }
343
- if (networkSegments.includes(segment)) {
344
- this.log('already have response from network segment %d - %s', segment, options.host);
345
- return undefined;
475
+ const messages = pbStream(stream).pb(Message);
476
+ const [, response] = await Promise.all([
477
+ messages.write({
478
+ type: Message.MessageType.DIAL,
479
+ dial: {
480
+ peer: {
481
+ id: this.components.peerId.toMultihash().bytes,
482
+ addrs: [options.multiaddr.bytes]
346
483
  }
347
- networkSegments.push(segment);
348
484
  }
349
- return response.dialResponse;
350
- }
351
- catch (err) {
352
- this.log.error('error asking remote to verify multiaddr', err);
353
- }
354
- finally {
355
- signal.removeEventListener('abort', onAbort);
356
- }
357
- };
358
- // find some random peers
359
- for await (const dialResponse of parallel(map(this.components.randomWalk.walk({
360
- signal
361
- }), (peer) => async () => verifyAddress(peer)), {
362
- concurrency: REQUIRED_SUCCESSFUL_DIALS
363
- })) {
364
- try {
365
- if (dialResponse == null) {
366
- continue;
367
- }
368
- // they either told us which address worked/didn't work, or we only sent them one address
369
- const addr = dialResponse.addr == null ? multiaddrs[0] : multiaddr(dialResponse.addr);
370
- this.log('autonat response for %a is %s', addr, dialResponse.status);
371
- if (dialResponse.status === Message.ResponseStatus.E_BAD_REQUEST) {
372
- // the remote could not parse our request
373
- continue;
374
- }
375
- if (dialResponse.status === Message.ResponseStatus.E_DIAL_REFUSED) {
376
- // the remote could not honour our request
377
- continue;
378
- }
379
- if (dialResponse.addr == null && multiaddrs.length > 1) {
380
- // we sent the remote multiple addrs but they didn't tell us which ones worked/didn't work
381
- continue;
382
- }
383
- if (!multiaddrs.some(ma => ma.equals(addr))) {
384
- this.log('peer reported %a as %s but it was not in our observed address list', addr, dialResponse.status);
385
- continue;
386
- }
387
- const addrStr = addr.toString();
388
- if (results[addrStr] == null) {
389
- results[addrStr] = { success: 0, failure: 0 };
390
- }
391
- if (dialResponse.status === Message.ResponseStatus.OK) {
392
- results[addrStr].success++;
393
- }
394
- else if (dialResponse.status === Message.ResponseStatus.E_DIAL_ERROR) {
395
- results[addrStr].failure++;
396
- }
397
- if (results[addrStr].success === REQUIRED_SUCCESSFUL_DIALS) {
398
- // we are now convinced
399
- this.log('%a is externally dialable', addr);
400
- addressManager.confirmObservedAddr(addr);
401
- return;
402
- }
403
- if (results[addrStr].failure === REQUIRED_SUCCESSFUL_DIALS) {
404
- // we are now unconvinced
405
- this.log('%a is not externally dialable', addr);
406
- addressManager.removeObservedAddr(addr);
407
- return;
408
- }
409
- }
410
- catch (err) {
411
- this.log.error('could not verify external address', err);
485
+ }, { signal }),
486
+ messages.read({ signal })
487
+ ]);
488
+ if (response.type !== Message.MessageType.DIAL_RESPONSE || response.dialResponse == null) {
489
+ this.log('invalid autonat response from %p - %j', connection.remotePeer, response);
490
+ return;
491
+ }
492
+ const status = response.dialResponse.status;
493
+ this.log.trace('autonat response from %p for %a is %s', connection.remotePeer, options.multiaddr, status);
494
+ if (status !== Message.ResponseStatus.OK && status !== Message.ResponseStatus.E_DIAL_ERROR) {
495
+ return;
496
+ }
497
+ results = this.dialResults.get(options.multiaddr.toString());
498
+ if (results == null) {
499
+ this.log.trace('peer reported %a as %s but there is no result object', options.multiaddr, response.dialResponse.status);
500
+ return;
501
+ }
502
+ if (results.networkSegments.includes(segment)) {
503
+ this.log.trace('%a results included network segment %s', options.multiaddr, segment);
504
+ return;
505
+ }
506
+ if (results.result != null) {
507
+ this.log.trace('already resolved result for %a, ignoring response from', options.multiaddr, connection.remotePeer);
508
+ return;
509
+ }
510
+ if (results.verifyingPeers.has(connection.remotePeer)) {
511
+ this.log.trace('peer %p has already verified %a, ignoring response', connection.remotePeer, options.multiaddr);
512
+ return;
513
+ }
514
+ results.verifyingPeers.add(connection.remotePeer);
515
+ results.networkSegments.push(segment);
516
+ if (status === Message.ResponseStatus.OK) {
517
+ results.success++;
518
+ // observed addresses require more confirmations
519
+ if (results.type !== 'observed') {
520
+ this.confirmAddress(results);
521
+ return;
412
522
  }
413
523
  }
524
+ else if (status === Message.ResponseStatus.E_DIAL_ERROR) {
525
+ results.failure++;
526
+ }
527
+ this.log('%a success %d failure %d', results.multiaddr, results.success, results.failure);
528
+ if (results.success === REQUIRED_SUCCESSFUL_DIALS) {
529
+ this.confirmAddress(results);
530
+ }
531
+ if (results.failure === REQUIRED_FAILED_DIALS) {
532
+ this.unconfirmAddress(results);
533
+ }
414
534
  }
415
535
  finally {
416
- this.verifyAddressTimeout = setTimeout(this._verifyExternalAddresses, this.refreshInterval);
536
+ try {
537
+ await stream.close({
538
+ signal
539
+ });
540
+ }
541
+ catch (err) {
542
+ stream.abort(err);
543
+ }
544
+ }
545
+ }
546
+ hasConnectionCapacity() {
547
+ const connections = this.components.connectionManager.getConnections();
548
+ const currentConnectionCount = connections.length;
549
+ const maxConnections = this.components.connectionManager.getMaxConnections();
550
+ return ((currentConnectionCount / maxConnections) * 100) < this.connectionThreshold;
551
+ }
552
+ confirmAddress(results) {
553
+ // we are now convinced
554
+ this.log('%s address %a is externally dialable', results.type, results.multiaddr);
555
+ this.components.addressManager.confirmObservedAddr(results.multiaddr);
556
+ this.dialResults.delete(results.multiaddr.toString());
557
+ // abort & remove any outstanding verification jobs for this multiaddr
558
+ results.result = true;
559
+ results.queue.abort();
560
+ }
561
+ unconfirmAddress(results) {
562
+ // we are now unconvinced
563
+ this.log('%s address %a is not externally dialable', results.type, results.multiaddr);
564
+ this.components.addressManager.removeObservedAddr(results.multiaddr);
565
+ this.dialResults.delete(results.multiaddr.toString());
566
+ // abort & remove any outstanding verification jobs for this multiaddr
567
+ results.result = false;
568
+ results.queue.abort();
569
+ }
570
+ getNetworkSegment(ma) {
571
+ // make sure we use different network segments
572
+ const options = ma.toOptions();
573
+ if (options.family === 4) {
574
+ const octets = options.host.split('.');
575
+ return octets[0].padStart(3, '0');
417
576
  }
577
+ const octets = options.host.split(':');
578
+ return octets[0].padStart(4, '0');
418
579
  }
419
580
  }
420
581
  //# sourceMappingURL=autonat.js.map