@libp2p/autonat-v2 0.0.0-2d6079bc1
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 +74 -0
- package/dist/index.min.js +19 -0
- package/dist/index.min.js.map +7 -0
- package/dist/src/autonat.d.ts +14 -0
- package/dist/src/autonat.d.ts.map +1 -0
- package/dist/src/autonat.js +38 -0
- package/dist/src/autonat.js.map +1 -0
- package/dist/src/client.d.ts +58 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +476 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/constants.d.ts +22 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +22 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/index.d.ts +93 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +30 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/pb/index.d.ts +90 -0
- package/dist/src/pb/index.d.ts.map +1 -0
- package/dist/src/pb/index.js +488 -0
- package/dist/src/pb/index.js.map +1 -0
- package/dist/src/server.d.ts +27 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +191 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +4 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +75 -0
- package/src/autonat.ts +47 -0
- package/src/client.ts +628 -0
- package/src/constants.ts +24 -0
- package/src/index.ts +110 -0
- package/src/pb/index.proto +56 -0
- package/src/pb/index.ts +614 -0
- package/src/server.ts +237 -0
- package/src/utils.ts +3 -0
package/src/server.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { ProtocolError } from '@libp2p/interface'
|
|
2
|
+
import { isPrivateIp } from '@libp2p/utils/private-ip'
|
|
3
|
+
import { CODE_IP4, CODE_IP6, multiaddr } from '@multiformats/multiaddr'
|
|
4
|
+
import { pbStream } from 'it-protobuf-stream'
|
|
5
|
+
import { setMaxListeners } from 'main-event'
|
|
6
|
+
import { MAX_INBOUND_STREAMS, MAX_MESSAGE_SIZE, MAX_OUTBOUND_STREAMS, TIMEOUT } from './constants.ts'
|
|
7
|
+
import { DialBack, DialBackResponse, DialResponse, DialStatus, Message } from './pb/index.ts'
|
|
8
|
+
import { randomNumber } from './utils.ts'
|
|
9
|
+
import type { AutoNATv2Components, AutoNATv2ServiceInit } from './index.ts'
|
|
10
|
+
import type { Logger, Connection, Startable, AbortOptions, IncomingStreamData, Stream } from '@libp2p/interface'
|
|
11
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
12
|
+
import type { MessageStream } from 'it-protobuf-stream'
|
|
13
|
+
|
|
14
|
+
export interface AutoNATv2ServerInit extends AutoNATv2ServiceInit {
|
|
15
|
+
dialRequestProtocol: string
|
|
16
|
+
dialBackProtocol: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class AutoNATv2Server implements Startable {
|
|
20
|
+
private readonly components: AutoNATv2Components
|
|
21
|
+
private readonly dialRequestProtocol: string
|
|
22
|
+
private readonly dialBackProtocol: string
|
|
23
|
+
private readonly timeout: number
|
|
24
|
+
private readonly maxInboundStreams: number
|
|
25
|
+
private readonly maxOutboundStreams: number
|
|
26
|
+
private readonly maxMessageSize: number
|
|
27
|
+
private started: boolean
|
|
28
|
+
private readonly log: Logger
|
|
29
|
+
|
|
30
|
+
constructor (components: AutoNATv2Components, init: AutoNATv2ServerInit) {
|
|
31
|
+
this.components = components
|
|
32
|
+
this.log = components.logger.forComponent('libp2p:auto-nat-v2:server')
|
|
33
|
+
this.started = false
|
|
34
|
+
this.dialRequestProtocol = init.dialRequestProtocol
|
|
35
|
+
this.dialBackProtocol = init.dialBackProtocol
|
|
36
|
+
this.timeout = init.timeout ?? TIMEOUT
|
|
37
|
+
this.maxInboundStreams = init.maxInboundStreams ?? MAX_INBOUND_STREAMS
|
|
38
|
+
this.maxOutboundStreams = init.maxOutboundStreams ?? MAX_OUTBOUND_STREAMS
|
|
39
|
+
this.maxMessageSize = init.maxMessageSize ?? MAX_MESSAGE_SIZE
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async start (): Promise<void> {
|
|
43
|
+
if (this.started) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// AutoNat server
|
|
48
|
+
await this.components.registrar.handle(this.dialRequestProtocol, (data) => {
|
|
49
|
+
void this.handleDialRequestStream(data)
|
|
50
|
+
.catch(err => {
|
|
51
|
+
this.log.error('error handling incoming autonat stream - %e', err)
|
|
52
|
+
})
|
|
53
|
+
}, {
|
|
54
|
+
maxInboundStreams: this.maxInboundStreams,
|
|
55
|
+
maxOutboundStreams: this.maxOutboundStreams
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
this.started = true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async stop (): Promise<void> {
|
|
62
|
+
await this.components.registrar.unhandle(this.dialRequestProtocol)
|
|
63
|
+
|
|
64
|
+
this.started = false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Handle an incoming AutoNAT request
|
|
69
|
+
*/
|
|
70
|
+
async handleDialRequestStream (data: IncomingStreamData): Promise<void> {
|
|
71
|
+
const signal = AbortSignal.timeout(this.timeout)
|
|
72
|
+
setMaxListeners(Infinity, signal)
|
|
73
|
+
|
|
74
|
+
const messages = pbStream(data.stream, {
|
|
75
|
+
maxDataLength: this.maxMessageSize
|
|
76
|
+
}).pb(Message)
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const connectionIp = getIpAddress(data.connection.remoteAddr)
|
|
80
|
+
|
|
81
|
+
if (connectionIp == null) {
|
|
82
|
+
throw new ProtocolError(`Could not find IP address in connection address "${data.connection.remoteAddr}"`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { dialRequest } = await messages.read({
|
|
86
|
+
signal
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
if (dialRequest == null) {
|
|
90
|
+
throw new ProtocolError('Did not receive DialRequest message on incoming dial request stream')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (dialRequest.addrs.length === 0) {
|
|
94
|
+
throw new ProtocolError('Did not receive any addresses to dial')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < dialRequest.addrs.length; i++) {
|
|
98
|
+
try {
|
|
99
|
+
const ma = multiaddr(dialRequest.addrs[i])
|
|
100
|
+
const isDialable = await this.components.connectionManager.isDialable(ma, {
|
|
101
|
+
signal
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (!isDialable) {
|
|
105
|
+
await messages.write({
|
|
106
|
+
dialResponse: {
|
|
107
|
+
addrIdx: i,
|
|
108
|
+
status: DialResponse.ResponseStatus.E_DIAL_REFUSED,
|
|
109
|
+
dialStatus: DialStatus.UNUSED
|
|
110
|
+
}
|
|
111
|
+
}, {
|
|
112
|
+
signal
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const ip = getIpAddress(ma)
|
|
119
|
+
|
|
120
|
+
if (ip == null) {
|
|
121
|
+
throw new ProtocolError(`Could not find IP address in requested address "${ma}"`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (isPrivateIp(ip)) {
|
|
125
|
+
throw new ProtocolError(`Requested address had private IP "${ma}"`)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (ip !== connectionIp) {
|
|
129
|
+
// amplification attack protection - request the client sends us a
|
|
130
|
+
// random number of bytes before we'll dial the address
|
|
131
|
+
await this.preventAmplificationAttack(messages, i, {
|
|
132
|
+
signal
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const dialStatus = await this.dialClientBack(ma, dialRequest.nonce, {
|
|
137
|
+
signal
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
await messages.write({
|
|
141
|
+
dialResponse: {
|
|
142
|
+
addrIdx: i,
|
|
143
|
+
status: DialResponse.ResponseStatus.OK,
|
|
144
|
+
dialStatus
|
|
145
|
+
}
|
|
146
|
+
}, {
|
|
147
|
+
signal
|
|
148
|
+
})
|
|
149
|
+
} catch (err) {
|
|
150
|
+
this.log.error('could not parse multiaddr - %e', err)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await data.stream.close({
|
|
155
|
+
signal
|
|
156
|
+
})
|
|
157
|
+
} catch (err: any) {
|
|
158
|
+
this.log.error('error handling incoming autonat stream - %e', err)
|
|
159
|
+
data.stream.abort(err)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async preventAmplificationAttack (messages: MessageStream<Message, Stream>, index: number, options: AbortOptions): Promise<void> {
|
|
164
|
+
const numBytes = randomNumber(30_000, 100_000)
|
|
165
|
+
|
|
166
|
+
await messages.write({
|
|
167
|
+
dialDataRequest: {
|
|
168
|
+
addrIdx: index,
|
|
169
|
+
numBytes: BigInt(numBytes)
|
|
170
|
+
}
|
|
171
|
+
}, options)
|
|
172
|
+
|
|
173
|
+
let received = 0
|
|
174
|
+
|
|
175
|
+
while (received < numBytes) {
|
|
176
|
+
const { dialDataResponse } = await messages.read(options)
|
|
177
|
+
|
|
178
|
+
if (dialDataResponse == null) {
|
|
179
|
+
throw new ProtocolError('Did not receive DialDataResponse message on incoming dial request stream')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
received += dialDataResponse.data.byteLength
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async dialClientBack (ma: Multiaddr, nonce: bigint, options: AbortOptions): Promise<DialStatus> {
|
|
187
|
+
let connection: Connection
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
connection = await this.components.connectionManager.openConnection(ma, {
|
|
191
|
+
force: true,
|
|
192
|
+
...options
|
|
193
|
+
})
|
|
194
|
+
} catch (err: any) {
|
|
195
|
+
this.log.error('failed to open connection to %a - %e', err, ma)
|
|
196
|
+
|
|
197
|
+
return DialStatus.E_DIAL_ERROR
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const stream = await connection.newStream(this.dialBackProtocol, options)
|
|
202
|
+
const dialBackMessages = pbStream(stream, {
|
|
203
|
+
maxDataLength: this.maxMessageSize
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
await dialBackMessages.write({
|
|
207
|
+
nonce
|
|
208
|
+
}, DialBack, options)
|
|
209
|
+
|
|
210
|
+
const response = await dialBackMessages.read(DialBackResponse)
|
|
211
|
+
|
|
212
|
+
if (response.status !== DialBackResponse.DialBackStatus.OK) {
|
|
213
|
+
throw new ProtocolError('DialBackResponse status was not OK')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await connection.close(options)
|
|
217
|
+
} catch (err: any) {
|
|
218
|
+
this.log.error('could not perform dial back - %e', err)
|
|
219
|
+
|
|
220
|
+
connection.abort(err)
|
|
221
|
+
|
|
222
|
+
return DialStatus.E_DIAL_BACK_ERROR
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// dial back was successful
|
|
226
|
+
return DialStatus.OK
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getIpAddress (ma: Multiaddr): string | undefined {
|
|
231
|
+
return ma.getComponents()
|
|
232
|
+
.filter(component => {
|
|
233
|
+
return component.code === CODE_IP4 || component.code === CODE_IP6
|
|
234
|
+
})
|
|
235
|
+
.map(component => component.value)
|
|
236
|
+
.pop()
|
|
237
|
+
}
|
package/src/utils.ts
ADDED