@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.
- package/README.md +1 -1
- package/dist/index.min.js +1 -1
- package/dist/src/autonat.d.ts +34 -7
- package/dist/src/autonat.d.ts.map +1 -1
- package/dist/src/autonat.js +389 -228
- package/dist/src/autonat.js.map +1 -1
- package/dist/src/constants.d.ts +1 -2
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +1 -2
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +13 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/package.json +20 -18
- package/src/autonat.ts +489 -253
- package/src/constants.ts +1 -2
- package/src/index.ts +14 -1
- package/LICENSE +0 -4
package/dist/src/autonat.js
CHANGED
|
@@ -1,46 +1,57 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
|
6
|
-
import
|
|
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,
|
|
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/
|
|
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:
|
|
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.
|
|
37
|
-
this.
|
|
38
|
-
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.
|
|
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
|
-
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
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
|