@libp2p/tcp 10.0.6 → 10.0.7
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/constants.js +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/dist/src/listener.d.ts.map +1 -1
- package/dist/src/listener.js +7 -6
- package/dist/src/listener.js.map +1 -1
- package/dist/src/socket-to-conn.d.ts +1 -0
- package/dist/src/socket-to-conn.d.ts.map +1 -1
- package/dist/src/socket-to-conn.js +74 -74
- package/dist/src/socket-to-conn.js.map +1 -1
- package/dist/src/tcp.d.ts.map +1 -1
- package/dist/src/tcp.js +34 -19
- package/dist/src/tcp.js.map +1 -1
- package/package.json +3 -2
- package/src/constants.ts +1 -1
- package/src/index.ts +2 -1
- package/src/listener.ts +9 -8
- package/src/socket-to-conn.ts +81 -77
- package/src/tcp.ts +35 -21
package/src/socket-to-conn.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InvalidParametersError, TimeoutError } from '@libp2p/interface'
|
|
2
2
|
import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr'
|
|
3
|
+
import pDefer from 'p-defer'
|
|
4
|
+
import { raceEvent } from 'race-event'
|
|
3
5
|
import { duplex } from 'stream-to-it'
|
|
4
6
|
import { CLOSE_TIMEOUT, SOCKET_TIMEOUT } from './constants.js'
|
|
5
7
|
import { multiaddrToNetConfig } from './utils.js'
|
|
6
8
|
import type { ComponentLogger, MultiaddrConnection, CounterGroup } from '@libp2p/interface'
|
|
7
9
|
import type { AbortOptions, Multiaddr } from '@multiformats/multiaddr'
|
|
8
10
|
import type { Socket } from 'net'
|
|
11
|
+
import type { DeferredPromise } from 'p-defer'
|
|
9
12
|
|
|
10
13
|
interface ToConnectionOptions {
|
|
11
14
|
listeningAddr?: Multiaddr
|
|
@@ -16,6 +19,7 @@ interface ToConnectionOptions {
|
|
|
16
19
|
metrics?: CounterGroup
|
|
17
20
|
metricPrefix?: string
|
|
18
21
|
logger: ComponentLogger
|
|
22
|
+
direction: 'inbound' | 'outbound'
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
/**
|
|
@@ -23,12 +27,15 @@ interface ToConnectionOptions {
|
|
|
23
27
|
* https://github.com/libp2p/interface-transport#multiaddrconnection
|
|
24
28
|
*/
|
|
25
29
|
export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptions): MultiaddrConnection => {
|
|
26
|
-
let closePromise:
|
|
30
|
+
let closePromise: DeferredPromise<void>
|
|
27
31
|
const log = options.logger.forComponent('libp2p:tcp:socket')
|
|
32
|
+
const direction = options.direction
|
|
28
33
|
const metrics = options.metrics
|
|
29
34
|
const metricPrefix = options.metricPrefix ?? ''
|
|
30
35
|
const inactivityTimeout = options.socketInactivityTimeout ?? SOCKET_TIMEOUT
|
|
31
36
|
const closeTimeout = options.socketCloseTimeout ?? CLOSE_TIMEOUT
|
|
37
|
+
let timedout = false
|
|
38
|
+
let errored = false
|
|
32
39
|
|
|
33
40
|
// Check if we are connected on a unix path
|
|
34
41
|
if (options.listeningAddr?.getPath() != null) {
|
|
@@ -39,6 +46,19 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio
|
|
|
39
46
|
options.localAddr = options.remoteAddr
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
// handle socket errors
|
|
50
|
+
socket.on('error', err => {
|
|
51
|
+
errored = true
|
|
52
|
+
|
|
53
|
+
if (!timedout) {
|
|
54
|
+
log.error('%s socket error - %e', direction, err)
|
|
55
|
+
metrics?.increment({ [`${metricPrefix}error`]: true })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
socket.destroy()
|
|
59
|
+
maConn.timeline.close = Date.now()
|
|
60
|
+
})
|
|
61
|
+
|
|
42
62
|
let remoteAddr: Multiaddr
|
|
43
63
|
|
|
44
64
|
if (options.remoteAddr != null) {
|
|
@@ -59,37 +79,37 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio
|
|
|
59
79
|
|
|
60
80
|
// by default there is no timeout
|
|
61
81
|
// https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketsettimeouttimeout-callback
|
|
62
|
-
socket.setTimeout(inactivityTimeout
|
|
63
|
-
log('%s socket read timeout', lOptsStr)
|
|
64
|
-
metrics?.increment({ [`${metricPrefix}timeout`]: true })
|
|
82
|
+
socket.setTimeout(inactivityTimeout)
|
|
65
83
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
84
|
+
socket.once('timeout', () => {
|
|
85
|
+
timedout = true
|
|
86
|
+
log('%s %s socket read timeout', direction, lOptsStr)
|
|
87
|
+
metrics?.increment({ [`${metricPrefix}timeout`]: true })
|
|
71
88
|
|
|
72
89
|
// if the socket times out due to inactivity we must manually close the connection
|
|
73
90
|
// https://nodejs.org/dist/latest-v16.x/docs/api/net.html#event-timeout
|
|
74
|
-
socket.destroy(
|
|
91
|
+
socket.destroy(new TimeoutError())
|
|
92
|
+
maConn.timeline.close = Date.now()
|
|
75
93
|
})
|
|
76
94
|
|
|
77
95
|
socket.once('close', () => {
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
// record metric for clean exit
|
|
97
|
+
if (!timedout && !errored) {
|
|
98
|
+
log('%s %s socket close', direction, lOptsStr)
|
|
99
|
+
metrics?.increment({ [`${metricPrefix}close`]: true })
|
|
100
|
+
}
|
|
80
101
|
|
|
81
102
|
// In instances where `close` was not explicitly called,
|
|
82
103
|
// such as an iterable stream ending, ensure we have set the close
|
|
83
104
|
// timeline
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
105
|
+
socket.destroy()
|
|
106
|
+
maConn.timeline.close = Date.now()
|
|
87
107
|
})
|
|
88
108
|
|
|
89
109
|
socket.once('end', () => {
|
|
90
110
|
// the remote sent a FIN packet which means no more data will be sent
|
|
91
111
|
// https://nodejs.org/dist/latest-v16.x/docs/api/net.html#event-end
|
|
92
|
-
log('%s socket end', lOptsStr)
|
|
112
|
+
log('%s %s socket end', direction, lOptsStr)
|
|
93
113
|
metrics?.increment({ [`${metricPrefix}end`]: true })
|
|
94
114
|
})
|
|
95
115
|
|
|
@@ -111,7 +131,7 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio
|
|
|
111
131
|
// If the source errored the socket will already have been destroyed by
|
|
112
132
|
// duplex(). If the socket errored it will already be
|
|
113
133
|
// destroyed. There's nothing to do here except log the error & return.
|
|
114
|
-
log.error('%s error in sink', lOptsStr, err)
|
|
134
|
+
log.error('%s %s error in sink - %e', direction, lOptsStr, err)
|
|
115
135
|
}
|
|
116
136
|
}
|
|
117
137
|
|
|
@@ -128,96 +148,66 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio
|
|
|
128
148
|
|
|
129
149
|
async close (options: AbortOptions = {}) {
|
|
130
150
|
if (socket.closed) {
|
|
131
|
-
log('
|
|
151
|
+
log('the %s %s socket is already closed', direction, lOptsStr)
|
|
132
152
|
return
|
|
133
153
|
}
|
|
134
154
|
|
|
135
155
|
if (socket.destroyed) {
|
|
136
|
-
log('
|
|
156
|
+
log('the %s %s socket is already destroyed', direction, lOptsStr)
|
|
137
157
|
return
|
|
138
158
|
}
|
|
139
159
|
|
|
140
|
-
|
|
141
|
-
|
|
160
|
+
if (closePromise != null) {
|
|
161
|
+
return closePromise.promise
|
|
142
162
|
}
|
|
143
163
|
|
|
144
164
|
try {
|
|
145
|
-
|
|
146
|
-
log('The %s socket is already closing', lOptsStr)
|
|
147
|
-
await closePromise
|
|
148
|
-
return
|
|
149
|
-
}
|
|
165
|
+
closePromise = pDefer()
|
|
150
166
|
|
|
151
|
-
|
|
152
|
-
|
|
167
|
+
// close writable end of socket
|
|
168
|
+
socket.end()
|
|
153
169
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
signal
|
|
157
|
-
}
|
|
158
|
-
}
|
|
170
|
+
// convert EventEmitter to EventTarget
|
|
171
|
+
const eventTarget = socketToEventTarget(socket)
|
|
159
172
|
|
|
160
|
-
|
|
173
|
+
// don't wait forever to close
|
|
174
|
+
const signal = options.signal ?? AbortSignal.timeout(closeTimeout)
|
|
161
175
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
resolve()
|
|
176
|
+
// wait for any unsent data to be sent
|
|
177
|
+
if (socket.writableLength > 0) {
|
|
178
|
+
log('%s %s draining socket', direction, lOptsStr)
|
|
179
|
+
await raceEvent(eventTarget, 'drain', signal, {
|
|
180
|
+
errorEvent: 'error'
|
|
168
181
|
})
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!socket.destroyed) {
|
|
173
|
-
reject(err)
|
|
174
|
-
}
|
|
175
|
-
// if socket is destroyed, 'closed' event will be emitted later to resolve the promise
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
// shorten inactivity timeout
|
|
179
|
-
socket.setTimeout(closeTimeout)
|
|
180
|
-
|
|
181
|
-
// close writable end of the socket
|
|
182
|
-
socket.end()
|
|
183
|
-
|
|
184
|
-
if (socket.writableLength > 0) {
|
|
185
|
-
// there are outgoing bytes waiting to be sent
|
|
186
|
-
socket.once('drain', () => {
|
|
187
|
-
log('%s socket drained', lOptsStr)
|
|
182
|
+
log('%s %s socket drained', direction, lOptsStr)
|
|
183
|
+
}
|
|
188
184
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
// nothing to send, destroy immediately, no need for the timeout
|
|
194
|
-
socket.destroy()
|
|
195
|
-
}
|
|
196
|
-
})
|
|
185
|
+
await Promise.all([
|
|
186
|
+
raceEvent(eventTarget, 'close', signal, {
|
|
187
|
+
errorEvent: 'error'
|
|
188
|
+
}),
|
|
197
189
|
|
|
198
|
-
|
|
190
|
+
// all bytes have been sent we can destroy the socket
|
|
191
|
+
socket.destroy()
|
|
192
|
+
])
|
|
199
193
|
} catch (err: any) {
|
|
200
194
|
this.abort(err)
|
|
201
195
|
} finally {
|
|
202
|
-
|
|
196
|
+
closePromise.resolve()
|
|
203
197
|
}
|
|
204
198
|
},
|
|
205
199
|
|
|
206
200
|
abort: (err: Error) => {
|
|
207
|
-
log('%s socket abort due to error', lOptsStr, err)
|
|
201
|
+
log('%s %s socket abort due to error - %e', direction, lOptsStr, err)
|
|
208
202
|
|
|
209
203
|
// the abortSignalListener may already destroyed the socket with an error
|
|
210
|
-
|
|
211
|
-
socket.destroy(err)
|
|
212
|
-
}
|
|
204
|
+
socket.destroy()
|
|
213
205
|
|
|
214
206
|
// closing a socket is always asynchronous (must wait for "close" event)
|
|
215
207
|
// but the tests expect this to be a synchronous operation so we have to
|
|
216
208
|
// set the close time here. the tests should be refactored to reflect
|
|
217
209
|
// reality.
|
|
218
|
-
|
|
219
|
-
maConn.timeline.close = Date.now()
|
|
220
|
-
}
|
|
210
|
+
maConn.timeline.close = Date.now()
|
|
221
211
|
},
|
|
222
212
|
|
|
223
213
|
log
|
|
@@ -225,3 +215,17 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio
|
|
|
225
215
|
|
|
226
216
|
return maConn
|
|
227
217
|
}
|
|
218
|
+
|
|
219
|
+
function socketToEventTarget (obj?: any): EventTarget {
|
|
220
|
+
const eventTarget = {
|
|
221
|
+
addEventListener: (type: any, cb: any) => {
|
|
222
|
+
obj.addListener(type, cb)
|
|
223
|
+
},
|
|
224
|
+
removeEventListener: (type: any, cb: any) => {
|
|
225
|
+
obj.removeListener(type, cb)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// @ts-expect-error partial implementation
|
|
230
|
+
return eventTarget
|
|
231
|
+
}
|
package/src/tcp.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { TCPListener } from './listener.js'
|
|
|
36
36
|
import { toMultiaddrConnection } from './socket-to-conn.js'
|
|
37
37
|
import { multiaddrToNetConfig } from './utils.js'
|
|
38
38
|
import type { TCPComponents, TCPCreateListenerOptions, TCPDialEvents, TCPDialOptions, TCPMetrics, TCPOptions } from './index.js'
|
|
39
|
-
import type { Logger, Connection, Transport, Listener } from '@libp2p/interface'
|
|
39
|
+
import type { Logger, Connection, Transport, Listener, MultiaddrConnection } from '@libp2p/interface'
|
|
40
40
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
41
41
|
import type { Socket, IpcSocketConnectOpts, TcpSocketConnectOpts } from 'net'
|
|
42
42
|
|
|
@@ -53,7 +53,11 @@ export class TCP implements Transport<TCPDialEvents> {
|
|
|
53
53
|
|
|
54
54
|
if (components.metrics != null) {
|
|
55
55
|
this.metrics = {
|
|
56
|
-
|
|
56
|
+
events: components.metrics.registerCounterGroup('libp2p_tcp_dialer_events_total', {
|
|
57
|
+
label: 'event',
|
|
58
|
+
help: 'Total count of TCP dialer events by type'
|
|
59
|
+
}),
|
|
60
|
+
errors: components.metrics.registerCounterGroup('libp2p_tcp_dialer_errors_total', {
|
|
57
61
|
label: 'event',
|
|
58
62
|
help: 'Total count of TCP dialer events by type'
|
|
59
63
|
})
|
|
@@ -76,23 +80,28 @@ export class TCP implements Transport<TCPDialEvents> {
|
|
|
76
80
|
// options.signal destroys the socket before 'connect' event
|
|
77
81
|
const socket = await this._connect(ma, options)
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
socket.on('error', err => {
|
|
81
|
-
this.log('socket error', err)
|
|
82
|
-
})
|
|
83
|
+
let maConn: MultiaddrConnection
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
try {
|
|
86
|
+
maConn = toMultiaddrConnection(socket, {
|
|
87
|
+
remoteAddr: ma,
|
|
88
|
+
socketInactivityTimeout: this.opts.outboundSocketInactivityTimeout,
|
|
89
|
+
socketCloseTimeout: this.opts.socketCloseTimeout,
|
|
90
|
+
metrics: this.metrics?.events,
|
|
91
|
+
logger: this.components.logger,
|
|
92
|
+
direction: 'outbound'
|
|
93
|
+
})
|
|
94
|
+
} catch (err: any) {
|
|
95
|
+
this.metrics?.errors.increment({ outbound_to_connection: true })
|
|
96
|
+
socket.destroy(err)
|
|
97
|
+
throw err
|
|
98
|
+
}
|
|
91
99
|
|
|
92
100
|
try {
|
|
93
101
|
this.log('new outbound connection %s', maConn.remoteAddr)
|
|
94
102
|
return await options.upgrader.upgradeOutbound(maConn, options)
|
|
95
103
|
} catch (err: any) {
|
|
104
|
+
this.metrics?.errors.increment({ outbound_upgrade: true })
|
|
96
105
|
this.log.error('error upgrading outbound connection', err)
|
|
97
106
|
maConn.abort(err)
|
|
98
107
|
throw err
|
|
@@ -103,6 +112,8 @@ export class TCP implements Transport<TCPDialEvents> {
|
|
|
103
112
|
options.signal?.throwIfAborted()
|
|
104
113
|
options.onProgress?.(new CustomProgressEvent('tcp:open-connection'))
|
|
105
114
|
|
|
115
|
+
let rawSocket: Socket
|
|
116
|
+
|
|
106
117
|
return new Promise<Socket>((resolve, reject) => {
|
|
107
118
|
const start = Date.now()
|
|
108
119
|
const cOpts = multiaddrToNetConfig(ma, {
|
|
@@ -111,35 +122,34 @@ export class TCP implements Transport<TCPDialEvents> {
|
|
|
111
122
|
}) as (IpcSocketConnectOpts & TcpSocketConnectOpts)
|
|
112
123
|
|
|
113
124
|
this.log('dialing %a', ma)
|
|
114
|
-
|
|
125
|
+
rawSocket = net.connect(cOpts)
|
|
115
126
|
|
|
116
127
|
const onError = (err: Error): void => {
|
|
128
|
+
this.log.error('dial to %a errored - %e', ma, err)
|
|
117
129
|
const cOptsStr = cOpts.path ?? `${cOpts.host ?? ''}:${cOpts.port}`
|
|
118
130
|
err.message = `connection error ${cOptsStr}: ${err.message}`
|
|
119
|
-
this.metrics?.
|
|
120
|
-
|
|
131
|
+
this.metrics?.events.increment({ error: true })
|
|
121
132
|
done(err)
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
const onTimeout = (): void => {
|
|
125
136
|
this.log('connection timeout %a', ma)
|
|
126
|
-
this.metrics?.
|
|
137
|
+
this.metrics?.events.increment({ timeout: true })
|
|
127
138
|
|
|
128
|
-
const err = new TimeoutError(`
|
|
139
|
+
const err = new TimeoutError(`Connection timeout after ${Date.now() - start}ms`)
|
|
129
140
|
// Note: this will result in onError() being called
|
|
130
141
|
rawSocket.emit('error', err)
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
const onConnect = (): void => {
|
|
134
145
|
this.log('connection opened %a', ma)
|
|
135
|
-
this.metrics?.
|
|
146
|
+
this.metrics?.events.increment({ connect: true })
|
|
136
147
|
done()
|
|
137
148
|
}
|
|
138
149
|
|
|
139
150
|
const onAbort = (): void => {
|
|
140
151
|
this.log('connection aborted %a', ma)
|
|
141
|
-
this.metrics?.
|
|
142
|
-
rawSocket.destroy()
|
|
152
|
+
this.metrics?.events.increment({ abort: true })
|
|
143
153
|
done(new AbortError())
|
|
144
154
|
}
|
|
145
155
|
|
|
@@ -167,6 +177,10 @@ export class TCP implements Transport<TCPDialEvents> {
|
|
|
167
177
|
options.signal.addEventListener('abort', onAbort)
|
|
168
178
|
}
|
|
169
179
|
})
|
|
180
|
+
.catch(err => {
|
|
181
|
+
rawSocket?.destroy()
|
|
182
|
+
throw err
|
|
183
|
+
})
|
|
170
184
|
}
|
|
171
185
|
|
|
172
186
|
/**
|