@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/README.md +1 -1
- package/dist/index.min.js +1 -1
- package/dist/src/autonat.d.ts +29 -7
- package/dist/src/autonat.d.ts.map +1 -1
- package/dist/src/autonat.js +303 -205
- package/dist/src/autonat.js.map +1 -1
- package/dist/src/constants.d.ts +0 -2
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +0 -2
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +2 -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 +378 -233
- package/src/constants.ts +0 -2
- package/src/index.ts +2 -1
- package/LICENSE +0 -4
package/src/autonat.ts
CHANGED
|
@@ -1,39 +1,86 @@
|
|
|
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 {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
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,
|
|
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/
|
|
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.
|
|
47
|
-
this.
|
|
48
|
-
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.
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
// skip peers we can't dial
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
127
181
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
240
|
-
const isPublicIp = !(isPrivateIp(host) ?? false)
|
|
299
|
+
const isPublicIp = !isPrivate(ma)
|
|
241
300
|
|
|
242
|
-
this.log.trace('
|
|
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('
|
|
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
|
-
*
|
|
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
|
-
|
|
333
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
408
|
+
if (b.type === 'dns-mapping') {
|
|
409
|
+
return 1
|
|
410
|
+
}
|
|
339
411
|
|
|
340
|
-
|
|
412
|
+
if (a.type === 'ip-mapping') {
|
|
413
|
+
return -1
|
|
414
|
+
}
|
|
341
415
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
416
|
+
if (b.type === 'ip-mapping') {
|
|
417
|
+
return 1
|
|
418
|
+
}
|
|
345
419
|
|
|
346
|
-
return
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return
|
|
354
|
-
}
|
|
428
|
+
if (isPrivate(addr.multiaddr)) {
|
|
429
|
+
// skip private addresses
|
|
430
|
+
return false
|
|
431
|
+
}
|
|
355
432
|
|
|
356
|
-
|
|
433
|
+
return true
|
|
434
|
+
})
|
|
357
435
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
436
|
+
for (const addr of addrs) {
|
|
437
|
+
const addrString = addr.multiaddr.toString()
|
|
438
|
+
let results = this.dialResults.get(addrString)
|
|
361
439
|
|
|
362
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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
|
-
|
|
381
|
-
|
|
460
|
+
// allow re-validating addresses that worked previously
|
|
461
|
+
if (needsRevalidating) {
|
|
462
|
+
this.addressFilter.remove?.(addrString)
|
|
463
|
+
}
|
|
382
464
|
|
|
383
|
-
|
|
384
|
-
|
|
465
|
+
if (this.addressFilter.has(addrString)) {
|
|
466
|
+
continue
|
|
467
|
+
}
|
|
385
468
|
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
})
|
|
485
|
+
this.dialResults.set(addrString, results)
|
|
486
|
+
}
|
|
393
487
|
|
|
394
|
-
|
|
488
|
+
return results
|
|
489
|
+
}
|
|
490
|
+
}
|
|
395
491
|
|
|
396
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
return undefined
|
|
414
|
-
}
|
|
519
|
+
// perform cleanup
|
|
520
|
+
this.removeOutdatedMultiaddrResults()
|
|
415
521
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
438
|
-
|
|
531
|
+
results.queue.add(async (options: TestAddressOptions) => {
|
|
532
|
+
results = this.dialResults.get(options.multiaddr.toString())
|
|
439
533
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
449
|
-
|
|
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
|
-
})
|
|
452
|
-
concurrency: REQUIRED_SUCCESSFUL_DIALS
|
|
453
|
-
})) {
|
|
454
|
-
try {
|
|
455
|
-
if (dialResponse == null) {
|
|
456
|
-
continue
|
|
457
|
-
}
|
|
546
|
+
})
|
|
458
547
|
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
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
|
-
|
|
465
|
-
// the remote could not parse our request
|
|
466
|
-
continue
|
|
467
|
-
}
|
|
568
|
+
const status = response.dialResponse.status
|
|
468
569
|
|
|
469
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
572
|
+
if (status !== Message.ResponseStatus.OK && status !== Message.ResponseStatus.E_DIAL_ERROR) {
|
|
573
|
+
return
|
|
574
|
+
}
|
|
478
575
|
|
|
479
|
-
|
|
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
|
-
|
|
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
|
-
|
|
487
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
}
|
|
514
|
-
|
|
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
|
}
|