@libp2p/tcp 9.1.5 → 9.1.6-2bbaf4361
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/dist/index.min.js +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -161
- package/dist/src/index.js.map +1 -1
- package/dist/src/listener.d.ts.map +1 -1
- package/dist/src/listener.js +7 -7
- package/dist/src/listener.js.map +1 -1
- package/dist/src/socket-to-conn.js +4 -4
- package/dist/src/socket-to-conn.js.map +1 -1
- package/dist/src/tcp.browser.d.ts +47 -0
- package/dist/src/tcp.browser.d.ts.map +1 -0
- package/dist/src/tcp.browser.js +56 -0
- package/dist/src/tcp.browser.js.map +1 -0
- package/dist/src/tcp.d.ts +61 -0
- package/dist/src/tcp.d.ts.map +1 -0
- package/dist/src/tcp.js +190 -0
- package/dist/src/tcp.js.map +1 -0
- package/package.json +14 -6
- package/src/index.ts +5 -201
- package/src/listener.ts +7 -7
- package/src/socket-to-conn.ts +4 -4
- package/src/tcp.browser.ts +68 -0
- package/src/tcp.ts +230 -0
- package/dist/typedoc-urls.json +0 -18
package/src/index.ts
CHANGED
|
@@ -27,17 +27,10 @@
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
import
|
|
31
|
-
import {
|
|
32
|
-
import
|
|
33
|
-
import {
|
|
34
|
-
import { CODE_CIRCUIT, CODE_P2P, CODE_UNIX } from './constants.js'
|
|
35
|
-
import { type CloseServerOnMaxConnectionsOpts, TCPListener } from './listener.js'
|
|
36
|
-
import { toMultiaddrConnection } from './socket-to-conn.js'
|
|
37
|
-
import { multiaddrToNetConfig } from './utils.js'
|
|
38
|
-
import type { ComponentLogger, Logger, Connection, CounterGroup, Metrics, CreateListenerOptions, DialTransportOptions, Transport, Listener, OutboundConnectionUpgradeEvents } from '@libp2p/interface'
|
|
39
|
-
import type { AbortOptions, Multiaddr } from '@multiformats/multiaddr'
|
|
40
|
-
import type { Socket, IpcSocketConnectOpts, TcpSocketConnectOpts } from 'net'
|
|
30
|
+
import { TCP } from './tcp.js'
|
|
31
|
+
import type { CloseServerOnMaxConnectionsOpts } from './listener.js'
|
|
32
|
+
import type { ComponentLogger, CounterGroup, Metrics, CreateListenerOptions, DialTransportOptions, Transport, OutboundConnectionUpgradeEvents } from '@libp2p/interface'
|
|
33
|
+
import type { AbortOptions } from '@multiformats/multiaddr'
|
|
41
34
|
import type { ProgressEvent } from 'progress-events'
|
|
42
35
|
|
|
43
36
|
export interface TCPOptions {
|
|
@@ -128,196 +121,7 @@ export interface TCPComponents {
|
|
|
128
121
|
}
|
|
129
122
|
|
|
130
123
|
export interface TCPMetrics {
|
|
131
|
-
dialerEvents: CounterGroup
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
class TCP implements Transport<TCPDialEvents> {
|
|
135
|
-
private readonly opts: TCPOptions
|
|
136
|
-
private readonly metrics?: TCPMetrics
|
|
137
|
-
private readonly components: TCPComponents
|
|
138
|
-
private readonly log: Logger
|
|
139
|
-
|
|
140
|
-
constructor (components: TCPComponents, options: TCPOptions = {}) {
|
|
141
|
-
this.log = components.logger.forComponent('libp2p:tcp')
|
|
142
|
-
this.opts = options
|
|
143
|
-
this.components = components
|
|
144
|
-
|
|
145
|
-
if (components.metrics != null) {
|
|
146
|
-
this.metrics = {
|
|
147
|
-
dialerEvents: components.metrics.registerCounterGroup('libp2p_tcp_dialer_events_total', {
|
|
148
|
-
label: 'event',
|
|
149
|
-
help: 'Total count of TCP dialer events by type'
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
readonly [transportSymbol] = true
|
|
156
|
-
|
|
157
|
-
readonly [Symbol.toStringTag] = '@libp2p/tcp'
|
|
158
|
-
|
|
159
|
-
readonly [serviceCapabilities]: string[] = [
|
|
160
|
-
'@libp2p/transport'
|
|
161
|
-
]
|
|
162
|
-
|
|
163
|
-
async dial (ma: Multiaddr, options: TCPDialOptions): Promise<Connection> {
|
|
164
|
-
options.keepAlive = options.keepAlive ?? true
|
|
165
|
-
options.noDelay = options.noDelay ?? true
|
|
166
|
-
|
|
167
|
-
// options.signal destroys the socket before 'connect' event
|
|
168
|
-
const socket = await this._connect(ma, options)
|
|
169
|
-
|
|
170
|
-
// Avoid uncaught errors caused by unstable connections
|
|
171
|
-
socket.on('error', err => {
|
|
172
|
-
this.log('socket error', err)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
const maConn = toMultiaddrConnection(socket, {
|
|
176
|
-
remoteAddr: ma,
|
|
177
|
-
socketInactivityTimeout: this.opts.outboundSocketInactivityTimeout,
|
|
178
|
-
socketCloseTimeout: this.opts.socketCloseTimeout,
|
|
179
|
-
metrics: this.metrics?.dialerEvents,
|
|
180
|
-
logger: this.components.logger
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
const onAbort = (): void => {
|
|
184
|
-
maConn.close().catch(err => {
|
|
185
|
-
this.log.error('Error closing maConn after abort', err)
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
options.signal?.addEventListener('abort', onAbort, { once: true })
|
|
189
|
-
|
|
190
|
-
this.log('new outbound connection %s', maConn.remoteAddr)
|
|
191
|
-
const conn = await options.upgrader.upgradeOutbound(maConn)
|
|
192
|
-
this.log('outbound connection %s upgraded', maConn.remoteAddr)
|
|
193
|
-
|
|
194
|
-
options.signal?.removeEventListener('abort', onAbort)
|
|
195
|
-
|
|
196
|
-
if (options.signal?.aborted === true) {
|
|
197
|
-
conn.close().catch(err => {
|
|
198
|
-
this.log.error('Error closing conn after abort', err)
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
throw new AbortError()
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return conn
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async _connect (ma: Multiaddr, options: TCPDialOptions): Promise<Socket> {
|
|
208
|
-
options.signal?.throwIfAborted()
|
|
209
|
-
options.onProgress?.(new CustomProgressEvent('tcp:open-connection'))
|
|
210
|
-
|
|
211
|
-
return new Promise<Socket>((resolve, reject) => {
|
|
212
|
-
const start = Date.now()
|
|
213
|
-
const cOpts = multiaddrToNetConfig(ma, {
|
|
214
|
-
...(this.opts.dialOpts ?? {}),
|
|
215
|
-
...options
|
|
216
|
-
}) as (IpcSocketConnectOpts & TcpSocketConnectOpts)
|
|
217
|
-
|
|
218
|
-
this.log('dialing %a', ma)
|
|
219
|
-
const rawSocket = net.connect(cOpts)
|
|
220
|
-
|
|
221
|
-
const onError = (err: Error): void => {
|
|
222
|
-
const cOptsStr = cOpts.path ?? `${cOpts.host ?? ''}:${cOpts.port}`
|
|
223
|
-
err.message = `connection error ${cOptsStr}: ${err.message}`
|
|
224
|
-
this.metrics?.dialerEvents.increment({ error: true })
|
|
225
|
-
|
|
226
|
-
done(err)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const onTimeout = (): void => {
|
|
230
|
-
this.log('connection timeout %a', ma)
|
|
231
|
-
this.metrics?.dialerEvents.increment({ timeout: true })
|
|
232
|
-
|
|
233
|
-
const err = new CodeError(`connection timeout after ${Date.now() - start}ms`, 'ERR_CONNECT_TIMEOUT')
|
|
234
|
-
// Note: this will result in onError() being called
|
|
235
|
-
rawSocket.emit('error', err)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const onConnect = (): void => {
|
|
239
|
-
this.log('connection opened %a', ma)
|
|
240
|
-
this.metrics?.dialerEvents.increment({ connect: true })
|
|
241
|
-
done()
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const onAbort = (): void => {
|
|
245
|
-
this.log('connection aborted %a', ma)
|
|
246
|
-
this.metrics?.dialerEvents.increment({ abort: true })
|
|
247
|
-
rawSocket.destroy()
|
|
248
|
-
done(new AbortError())
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const done = (err?: Error): void => {
|
|
252
|
-
rawSocket.removeListener('error', onError)
|
|
253
|
-
rawSocket.removeListener('timeout', onTimeout)
|
|
254
|
-
rawSocket.removeListener('connect', onConnect)
|
|
255
|
-
|
|
256
|
-
if (options.signal != null) {
|
|
257
|
-
options.signal.removeEventListener('abort', onAbort)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (err != null) {
|
|
261
|
-
reject(err); return
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
resolve(rawSocket)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
rawSocket.on('error', onError)
|
|
268
|
-
rawSocket.on('timeout', onTimeout)
|
|
269
|
-
rawSocket.on('connect', onConnect)
|
|
270
|
-
|
|
271
|
-
if (options.signal != null) {
|
|
272
|
-
options.signal.addEventListener('abort', onAbort)
|
|
273
|
-
}
|
|
274
|
-
})
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Creates a TCP listener. The provided `handler` function will be called
|
|
279
|
-
* anytime a new incoming Connection has been successfully upgraded via
|
|
280
|
-
* `upgrader.upgradeInbound`.
|
|
281
|
-
*/
|
|
282
|
-
createListener (options: TCPCreateListenerOptions): Listener {
|
|
283
|
-
return new TCPListener({
|
|
284
|
-
...(this.opts.listenOpts ?? {}),
|
|
285
|
-
...options,
|
|
286
|
-
maxConnections: this.opts.maxConnections,
|
|
287
|
-
backlog: this.opts.backlog,
|
|
288
|
-
closeServerOnMaxConnections: this.opts.closeServerOnMaxConnections,
|
|
289
|
-
socketInactivityTimeout: this.opts.inboundSocketInactivityTimeout,
|
|
290
|
-
socketCloseTimeout: this.opts.socketCloseTimeout,
|
|
291
|
-
metrics: this.components.metrics,
|
|
292
|
-
logger: this.components.logger
|
|
293
|
-
})
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Takes a list of `Multiaddr`s and returns only valid TCP addresses
|
|
298
|
-
*/
|
|
299
|
-
listenFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
300
|
-
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
|
|
301
|
-
|
|
302
|
-
return multiaddrs.filter(ma => {
|
|
303
|
-
if (ma.protoCodes().includes(CODE_CIRCUIT)) {
|
|
304
|
-
return false
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (ma.protoCodes().includes(CODE_UNIX)) {
|
|
308
|
-
return true
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return mafmt.TCP.matches(ma.decapsulateCode(CODE_P2P))
|
|
312
|
-
})
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Filter check for all Multiaddrs that this transport can dial
|
|
317
|
-
*/
|
|
318
|
-
dialFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
319
|
-
return this.listenFilter(multiaddrs)
|
|
320
|
-
}
|
|
124
|
+
dialerEvents: CounterGroup<'error' | 'timeout' | 'connect' | 'abort'>
|
|
321
125
|
}
|
|
322
126
|
|
|
323
127
|
export function tcp (init: TCPOptions = {}): (components: TCPComponents) => Transport {
|
package/src/listener.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import net from 'net'
|
|
2
|
-
import {
|
|
2
|
+
import { AbortError, AlreadyStartedError, InvalidParametersError, NotStartedError, TypedEventEmitter } from '@libp2p/interface'
|
|
3
3
|
import { CODE_P2P } from './constants.js'
|
|
4
4
|
import { toMultiaddrConnection } from './socket-to-conn.js'
|
|
5
5
|
import {
|
|
@@ -107,7 +107,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
|
|
|
107
107
|
if (context.closeServerOnMaxConnections != null) {
|
|
108
108
|
// Sanity check options
|
|
109
109
|
if (context.closeServerOnMaxConnections.closeAbove < context.closeServerOnMaxConnections.listenBelow) {
|
|
110
|
-
throw new
|
|
110
|
+
throw new InvalidParametersError('closeAbove must be >= listenBelow')
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -179,7 +179,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
|
|
|
179
179
|
|
|
180
180
|
private onSocket (socket: net.Socket): void {
|
|
181
181
|
if (this.status.code !== TCPListenerStatusCode.ACTIVE) {
|
|
182
|
-
throw new
|
|
182
|
+
throw new NotStartedError('Server is not listening yet')
|
|
183
183
|
}
|
|
184
184
|
// Avoid uncaught errors caused by unstable connections
|
|
185
185
|
socket.on('error', err => {
|
|
@@ -304,7 +304,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
|
|
|
304
304
|
|
|
305
305
|
async listen (ma: Multiaddr): Promise<void> {
|
|
306
306
|
if (this.status.code === TCPListenerStatusCode.ACTIVE || this.status.code === TCPListenerStatusCode.PAUSED) {
|
|
307
|
-
throw new
|
|
307
|
+
throw new AlreadyStartedError('server is already listening')
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
const peerId = ma.getPeerId()
|
|
@@ -327,7 +327,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
async close (): Promise<void> {
|
|
330
|
-
const err = new
|
|
330
|
+
const err = new AbortError('Listener is closing')
|
|
331
331
|
|
|
332
332
|
// synchronously close each connection
|
|
333
333
|
this.connections.forEach(conn => {
|
|
@@ -372,9 +372,9 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
|
|
|
372
372
|
this.log('closing server on %s', this.server.address())
|
|
373
373
|
|
|
374
374
|
// NodeJS implementation tracks listening status with `this._handle` property.
|
|
375
|
-
// - Server.close() sets this._handle to null immediately. If this._handle is null,
|
|
375
|
+
// - Server.close() sets this._handle to null immediately. If this._handle is null, NotStartedError is thrown
|
|
376
376
|
// - Server.listening returns `this._handle !== null` https://github.com/nodejs/node/blob/386d761943bb1b217fba27d6b80b658c23009e60/lib/net.js#L1675
|
|
377
|
-
// - Server.listen() if `this._handle !== null` throws
|
|
377
|
+
// - Server.listen() if `this._handle !== null` throws AlreadyStartedError
|
|
378
378
|
//
|
|
379
379
|
// NOTE: Both listen and close are technically not async actions, so it's not necessary to track
|
|
380
380
|
// states 'pending-close' or 'pending-listen'
|
package/src/socket-to-conn.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbortError, InvalidParametersError, TimeoutError } from '@libp2p/interface'
|
|
2
2
|
import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr'
|
|
3
3
|
import { duplex } from 'stream-to-it'
|
|
4
4
|
import { CLOSE_TIMEOUT, SOCKET_TIMEOUT } from './constants.js'
|
|
@@ -47,7 +47,7 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio
|
|
|
47
47
|
if (socket.remoteAddress == null || socket.remotePort == null) {
|
|
48
48
|
// this can be undefined if the socket is destroyed (for example, if the client disconnected)
|
|
49
49
|
// https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketremoteaddress
|
|
50
|
-
throw new
|
|
50
|
+
throw new InvalidParametersError('Could not determine remote address or port')
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
remoteAddr = toMultiaddr(socket.remoteAddress, socket.remotePort)
|
|
@@ -66,7 +66,7 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio
|
|
|
66
66
|
// only destroy with an error if the remote has not sent the FIN message
|
|
67
67
|
let err: Error | undefined
|
|
68
68
|
if (socket.readable) {
|
|
69
|
-
err = new
|
|
69
|
+
err = new TimeoutError('Socket read timeout')
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// if the socket times out due to inactivity we must manually close the connection
|
|
@@ -147,7 +147,7 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const abortSignalListener = (): void => {
|
|
150
|
-
socket.destroy(new
|
|
150
|
+
socket.destroy(new AbortError('Destroying socket after timeout'))
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
options.signal?.addEventListener('abort', abortSignalListener)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* A [libp2p transport](https://docs.libp2p.io/concepts/transports/overview/) based on the TCP networking stack.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
*
|
|
8
|
+
* ```TypeScript
|
|
9
|
+
* import { createLibp2p } from 'libp2p'
|
|
10
|
+
* import { tcp } from '@libp2p/tcp'
|
|
11
|
+
* import { multiaddr } from '@multiformats/multiaddr'
|
|
12
|
+
*
|
|
13
|
+
* const node = await createLibp2p({
|
|
14
|
+
* transports: [
|
|
15
|
+
* tcp()
|
|
16
|
+
* ]
|
|
17
|
+
* })
|
|
18
|
+
*
|
|
19
|
+
* const ma = multiaddr('/ip4/123.123.123.123/tcp/1234')
|
|
20
|
+
*
|
|
21
|
+
* // dial a TCP connection, timing out after 10 seconds
|
|
22
|
+
* const connection = await node.dial(ma, {
|
|
23
|
+
* signal: AbortSignal.timeout(10_000)
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* // use connection...
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { serviceCapabilities, transportSymbol } from '@libp2p/interface'
|
|
31
|
+
import type { TCPComponents, TCPDialEvents, TCPMetrics, TCPOptions } from './index.js'
|
|
32
|
+
import type { Logger, Connection, Transport, Listener } from '@libp2p/interface'
|
|
33
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
34
|
+
|
|
35
|
+
export class TCP implements Transport<TCPDialEvents> {
|
|
36
|
+
private readonly opts: TCPOptions
|
|
37
|
+
private readonly metrics?: TCPMetrics
|
|
38
|
+
private readonly components: TCPComponents
|
|
39
|
+
private readonly log: Logger
|
|
40
|
+
|
|
41
|
+
constructor () {
|
|
42
|
+
throw new Error('TCP connections are not possible in browsers')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
readonly [transportSymbol] = true
|
|
46
|
+
|
|
47
|
+
readonly [Symbol.toStringTag] = '@libp2p/tcp'
|
|
48
|
+
|
|
49
|
+
readonly [serviceCapabilities]: string[] = [
|
|
50
|
+
'@libp2p/transport'
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
async dial (): Promise<Connection> {
|
|
54
|
+
throw new Error('TCP connections are not possible in browsers')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
createListener (): Listener {
|
|
58
|
+
throw new Error('TCP connections are not possible in browsers')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
listenFilter (): Multiaddr[] {
|
|
62
|
+
return []
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dialFilter (): Multiaddr[] {
|
|
66
|
+
return []
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/tcp.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* A [libp2p transport](https://docs.libp2p.io/concepts/transports/overview/) based on the TCP networking stack.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
*
|
|
8
|
+
* ```TypeScript
|
|
9
|
+
* import { createLibp2p } from 'libp2p'
|
|
10
|
+
* import { tcp } from '@libp2p/tcp'
|
|
11
|
+
* import { multiaddr } from '@multiformats/multiaddr'
|
|
12
|
+
*
|
|
13
|
+
* const node = await createLibp2p({
|
|
14
|
+
* transports: [
|
|
15
|
+
* tcp()
|
|
16
|
+
* ]
|
|
17
|
+
* })
|
|
18
|
+
*
|
|
19
|
+
* const ma = multiaddr('/ip4/123.123.123.123/tcp/1234')
|
|
20
|
+
*
|
|
21
|
+
* // dial a TCP connection, timing out after 10 seconds
|
|
22
|
+
* const connection = await node.dial(ma, {
|
|
23
|
+
* signal: AbortSignal.timeout(10_000)
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* // use connection...
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import net from 'net'
|
|
31
|
+
import { AbortError, TimeoutError, serviceCapabilities, transportSymbol } from '@libp2p/interface'
|
|
32
|
+
import * as mafmt from '@multiformats/mafmt'
|
|
33
|
+
import { CustomProgressEvent } from 'progress-events'
|
|
34
|
+
import { CODE_CIRCUIT, CODE_P2P, CODE_UNIX } from './constants.js'
|
|
35
|
+
import { TCPListener } from './listener.js'
|
|
36
|
+
import { toMultiaddrConnection } from './socket-to-conn.js'
|
|
37
|
+
import { multiaddrToNetConfig } from './utils.js'
|
|
38
|
+
import type { TCPComponents, TCPCreateListenerOptions, TCPDialEvents, TCPDialOptions, TCPMetrics, TCPOptions } from './index.js'
|
|
39
|
+
import type { Logger, Connection, Transport, Listener } from '@libp2p/interface'
|
|
40
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
41
|
+
import type { Socket, IpcSocketConnectOpts, TcpSocketConnectOpts } from 'net'
|
|
42
|
+
|
|
43
|
+
export class TCP implements Transport<TCPDialEvents> {
|
|
44
|
+
private readonly opts: TCPOptions
|
|
45
|
+
private readonly metrics?: TCPMetrics
|
|
46
|
+
private readonly components: TCPComponents
|
|
47
|
+
private readonly log: Logger
|
|
48
|
+
|
|
49
|
+
constructor (components: TCPComponents, options: TCPOptions = {}) {
|
|
50
|
+
this.log = components.logger.forComponent('libp2p:tcp')
|
|
51
|
+
this.opts = options
|
|
52
|
+
this.components = components
|
|
53
|
+
|
|
54
|
+
if (components.metrics != null) {
|
|
55
|
+
this.metrics = {
|
|
56
|
+
dialerEvents: components.metrics.registerCounterGroup('libp2p_tcp_dialer_events_total', {
|
|
57
|
+
label: 'event',
|
|
58
|
+
help: 'Total count of TCP dialer events by type'
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
readonly [transportSymbol] = true
|
|
65
|
+
|
|
66
|
+
readonly [Symbol.toStringTag] = '@libp2p/tcp'
|
|
67
|
+
|
|
68
|
+
readonly [serviceCapabilities]: string[] = [
|
|
69
|
+
'@libp2p/transport'
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
async dial (ma: Multiaddr, options: TCPDialOptions): Promise<Connection> {
|
|
73
|
+
options.keepAlive = options.keepAlive ?? true
|
|
74
|
+
options.noDelay = options.noDelay ?? true
|
|
75
|
+
|
|
76
|
+
// options.signal destroys the socket before 'connect' event
|
|
77
|
+
const socket = await this._connect(ma, options)
|
|
78
|
+
|
|
79
|
+
// Avoid uncaught errors caused by unstable connections
|
|
80
|
+
socket.on('error', err => {
|
|
81
|
+
this.log('socket error', err)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const maConn = toMultiaddrConnection(socket, {
|
|
85
|
+
remoteAddr: ma,
|
|
86
|
+
socketInactivityTimeout: this.opts.outboundSocketInactivityTimeout,
|
|
87
|
+
socketCloseTimeout: this.opts.socketCloseTimeout,
|
|
88
|
+
metrics: this.metrics?.dialerEvents,
|
|
89
|
+
logger: this.components.logger
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const onAbort = (): void => {
|
|
93
|
+
maConn.close().catch(err => {
|
|
94
|
+
this.log.error('Error closing maConn after abort', err)
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
options.signal?.addEventListener('abort', onAbort, { once: true })
|
|
98
|
+
|
|
99
|
+
this.log('new outbound connection %s', maConn.remoteAddr)
|
|
100
|
+
const conn = await options.upgrader.upgradeOutbound(maConn)
|
|
101
|
+
this.log('outbound connection %s upgraded', maConn.remoteAddr)
|
|
102
|
+
|
|
103
|
+
options.signal?.removeEventListener('abort', onAbort)
|
|
104
|
+
|
|
105
|
+
if (options.signal?.aborted === true) {
|
|
106
|
+
conn.close().catch(err => {
|
|
107
|
+
this.log.error('Error closing conn after abort', err)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
throw new AbortError()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return conn
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async _connect (ma: Multiaddr, options: TCPDialOptions): Promise<Socket> {
|
|
117
|
+
options.signal?.throwIfAborted()
|
|
118
|
+
options.onProgress?.(new CustomProgressEvent('tcp:open-connection'))
|
|
119
|
+
|
|
120
|
+
return new Promise<Socket>((resolve, reject) => {
|
|
121
|
+
const start = Date.now()
|
|
122
|
+
const cOpts = multiaddrToNetConfig(ma, {
|
|
123
|
+
...(this.opts.dialOpts ?? {}),
|
|
124
|
+
...options
|
|
125
|
+
}) as (IpcSocketConnectOpts & TcpSocketConnectOpts)
|
|
126
|
+
|
|
127
|
+
this.log('dialing %a', ma)
|
|
128
|
+
const rawSocket = net.connect(cOpts)
|
|
129
|
+
|
|
130
|
+
const onError = (err: Error): void => {
|
|
131
|
+
const cOptsStr = cOpts.path ?? `${cOpts.host ?? ''}:${cOpts.port}`
|
|
132
|
+
err.message = `connection error ${cOptsStr}: ${err.message}`
|
|
133
|
+
this.metrics?.dialerEvents.increment({ error: true })
|
|
134
|
+
|
|
135
|
+
done(err)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const onTimeout = (): void => {
|
|
139
|
+
this.log('connection timeout %a', ma)
|
|
140
|
+
this.metrics?.dialerEvents.increment({ timeout: true })
|
|
141
|
+
|
|
142
|
+
const err = new TimeoutError(`connection timeout after ${Date.now() - start}ms`)
|
|
143
|
+
// Note: this will result in onError() being called
|
|
144
|
+
rawSocket.emit('error', err)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const onConnect = (): void => {
|
|
148
|
+
this.log('connection opened %a', ma)
|
|
149
|
+
this.metrics?.dialerEvents.increment({ connect: true })
|
|
150
|
+
done()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const onAbort = (): void => {
|
|
154
|
+
this.log('connection aborted %a', ma)
|
|
155
|
+
this.metrics?.dialerEvents.increment({ abort: true })
|
|
156
|
+
rawSocket.destroy()
|
|
157
|
+
done(new AbortError())
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const done = (err?: Error): void => {
|
|
161
|
+
rawSocket.removeListener('error', onError)
|
|
162
|
+
rawSocket.removeListener('timeout', onTimeout)
|
|
163
|
+
rawSocket.removeListener('connect', onConnect)
|
|
164
|
+
|
|
165
|
+
if (options.signal != null) {
|
|
166
|
+
options.signal.removeEventListener('abort', onAbort)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (err != null) {
|
|
170
|
+
reject(err); return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
resolve(rawSocket)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
rawSocket.on('error', onError)
|
|
177
|
+
rawSocket.on('timeout', onTimeout)
|
|
178
|
+
rawSocket.on('connect', onConnect)
|
|
179
|
+
|
|
180
|
+
if (options.signal != null) {
|
|
181
|
+
options.signal.addEventListener('abort', onAbort)
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Creates a TCP listener. The provided `handler` function will be called
|
|
188
|
+
* anytime a new incoming Connection has been successfully upgraded via
|
|
189
|
+
* `upgrader.upgradeInbound`.
|
|
190
|
+
*/
|
|
191
|
+
createListener (options: TCPCreateListenerOptions): Listener {
|
|
192
|
+
return new TCPListener({
|
|
193
|
+
...(this.opts.listenOpts ?? {}),
|
|
194
|
+
...options,
|
|
195
|
+
maxConnections: this.opts.maxConnections,
|
|
196
|
+
backlog: this.opts.backlog,
|
|
197
|
+
closeServerOnMaxConnections: this.opts.closeServerOnMaxConnections,
|
|
198
|
+
socketInactivityTimeout: this.opts.inboundSocketInactivityTimeout,
|
|
199
|
+
socketCloseTimeout: this.opts.socketCloseTimeout,
|
|
200
|
+
metrics: this.components.metrics,
|
|
201
|
+
logger: this.components.logger
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Takes a list of `Multiaddr`s and returns only valid TCP addresses
|
|
207
|
+
*/
|
|
208
|
+
listenFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
209
|
+
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
|
|
210
|
+
|
|
211
|
+
return multiaddrs.filter(ma => {
|
|
212
|
+
if (ma.protoCodes().includes(CODE_CIRCUIT)) {
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (ma.protoCodes().includes(CODE_UNIX)) {
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return mafmt.TCP.matches(ma.decapsulateCode(CODE_P2P))
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Filter check for all Multiaddrs that this transport can dial
|
|
226
|
+
*/
|
|
227
|
+
dialFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
228
|
+
return this.listenFilter(multiaddrs)
|
|
229
|
+
}
|
|
230
|
+
}
|
package/dist/typedoc-urls.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"TCPComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPComponents.html",
|
|
3
|
-
".:TCPComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPComponents.html",
|
|
4
|
-
"TCPCreateListenerOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPCreateListenerOptions.html",
|
|
5
|
-
".:TCPCreateListenerOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPCreateListenerOptions.html",
|
|
6
|
-
"TCPDialOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPDialOptions.html",
|
|
7
|
-
".:TCPDialOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPDialOptions.html",
|
|
8
|
-
"TCPMetrics": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPMetrics.html",
|
|
9
|
-
".:TCPMetrics": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPMetrics.html",
|
|
10
|
-
"TCPOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPOptions.html",
|
|
11
|
-
".:TCPOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPOptions.html",
|
|
12
|
-
"TCPSocketOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPSocketOptions.html",
|
|
13
|
-
".:TCPSocketOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tcp.TCPSocketOptions.html",
|
|
14
|
-
"TCPDialEvents": "https://libp2p.github.io/js-libp2p/types/_libp2p_tcp.TCPDialEvents.html",
|
|
15
|
-
".:TCPDialEvents": "https://libp2p.github.io/js-libp2p/types/_libp2p_tcp.TCPDialEvents.html",
|
|
16
|
-
"tcp": "https://libp2p.github.io/js-libp2p/functions/_libp2p_tcp.tcp.html",
|
|
17
|
-
".:tcp": "https://libp2p.github.io/js-libp2p/functions/_libp2p_tcp.tcp.html"
|
|
18
|
-
}
|