@libp2p/tcp 10.1.19-8484de8a2 → 10.1.19-87bc8d4fb

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.
@@ -1,5 +1,5 @@
1
1
  import type { Multiaddr } from '@multiformats/multiaddr';
2
2
  import type { ListenOptions, IpcSocketConnectOpts, TcpSocketConnectOpts } from 'net';
3
3
  export type NetConfig = ListenOptions | (IpcSocketConnectOpts & TcpSocketConnectOpts);
4
- export declare function multiaddrToNetConfig(addr: Multiaddr, options?: NetConfig): NetConfig;
4
+ export declare function multiaddrToNetConfig(addr: Multiaddr, config?: NetConfig): NetConfig;
5
5
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,KAAK,CAAA;AAEpF,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,CAAC,oBAAoB,GAAG,oBAAoB,CAAC,CAAA;AAErF,wBAAgB,oBAAoB,CAAE,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,SAAc,GAAG,SAAS,CA4BzF"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,KAAK,CAAA;AAEpF,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,CAAC,oBAAoB,GAAG,oBAAoB,CAAC,CAAA;AAErF,wBAAgB,oBAAoB,CAAE,IAAI,EAAE,SAAS,EAAE,MAAM,GAAE,SAAc,GAAG,SAAS,CAqBxF"}
package/dist/src/utils.js CHANGED
@@ -1,16 +1,9 @@
1
1
  import os from 'os';
2
2
  import path from 'path';
3
- import { InvalidParametersError } from '@libp2p/interface';
4
- import { getNetConfig } from '@libp2p/utils';
5
- import { CODE_UNIX } from '@multiformats/multiaddr';
6
- import { Unix } from '@multiformats/multiaddr-matcher';
7
- export function multiaddrToNetConfig(addr, options = {}) {
8
- if (Unix.exactMatch(addr)) {
9
- const listenPath = addr.getComponents().find(c => c.code === CODE_UNIX)?.value;
10
- if (listenPath == null) {
11
- throw new InvalidParametersError(`Multiaddr ${addr} was not a Unix address`);
12
- }
13
- // unix socket listening
3
+ export function multiaddrToNetConfig(addr, config = {}) {
4
+ const listenPath = addr.getPath();
5
+ // unix socket listening
6
+ if (listenPath != null) {
14
7
  if (os.platform() === 'win32') {
15
8
  // Use named pipes on Windows systems.
16
9
  return { path: path.join('\\\\.\\pipe\\', listenPath) };
@@ -19,15 +12,12 @@ export function multiaddrToNetConfig(addr, options = {}) {
19
12
  return { path: listenPath };
20
13
  }
21
14
  }
22
- const config = getNetConfig(addr);
23
- const host = config.host;
24
- const port = config.port;
15
+ const options = addr.toOptions();
25
16
  // tcp listening
26
17
  return {
27
- host,
28
- port,
29
- ipv6Only: config.type === 'ip6',
30
- ...options
18
+ ...config,
19
+ ...options,
20
+ ipv6Only: options.family === 6
31
21
  };
32
22
  }
33
23
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAA;AAMtD,MAAM,UAAU,oBAAoB,CAAE,IAAe,EAAE,UAAqB,EAAE;IAC5E,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,KAAK,CAAA;QAE9E,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,IAAI,sBAAsB,CAAC,aAAa,IAAI,yBAAyB,CAAC,CAAA;QAC9E,CAAC;QAED,wBAAwB;QACxB,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;YAC9B,sCAAsC;YACtC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,EAAE,CAAA;QACzD,CAAC;aAAM,CAAC;YACN,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;QAC7B,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;IACxB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;IAExB,gBAAgB;IAChB,OAAO;QACL,IAAI;QACJ,IAAI;QACJ,QAAQ,EAAE,MAAM,CAAC,IAAI,KAAK,KAAK;QAC/B,GAAG,OAAO;KACX,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAMvB,MAAM,UAAU,oBAAoB,CAAE,IAAe,EAAE,SAAoB,EAAE;IAC3E,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;IAEjC,wBAAwB;IACxB,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;YAC9B,sCAAsC;YACtC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,EAAE,CAAA;QACzD,CAAC;aAAM,CAAC;YACN,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;QAC7B,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,CAAA;IAEhC,gBAAgB;IAChB,OAAO;QACL,GAAG,MAAM;QACT,GAAG,OAAO;QACV,QAAQ,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;KAC/B,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/tcp",
3
- "version": "10.1.19-8484de8a2",
3
+ "version": "10.1.19-87bc8d4fb",
4
4
  "description": "A TCP transport for libp2p",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/transport-tcp#readme",
@@ -53,23 +53,23 @@
53
53
  "test:electron-main": "aegir test -t electron-main"
54
54
  },
55
55
  "dependencies": {
56
- "@libp2p/interface": "2.11.0-8484de8a2",
57
- "@libp2p/utils": "6.7.2-8484de8a2",
58
- "@multiformats/multiaddr": "^13.0.1",
59
- "@multiformats/multiaddr-matcher": "^3.0.1",
56
+ "@libp2p/interface": "2.11.0-87bc8d4fb",
57
+ "@libp2p/utils": "6.7.2-87bc8d4fb",
58
+ "@multiformats/multiaddr": "^12.4.4",
59
+ "@multiformats/multiaddr-matcher": "^2.0.0",
60
60
  "@types/sinon": "^17.0.4",
61
61
  "main-event": "^1.0.1",
62
+ "p-defer": "^4.0.1",
62
63
  "p-event": "^6.0.1",
63
64
  "progress-events": "^1.0.1",
64
- "uint8arraylist": "^2.4.8"
65
+ "race-event": "^1.3.0",
66
+ "stream-to-it": "^1.0.1"
65
67
  },
66
68
  "devDependencies": {
67
- "@libp2p/logger": "5.2.0-8484de8a2",
68
- "aegir": "^47.0.21",
69
- "delay": "^6.0.0",
70
- "p-defer": "^4.0.1",
69
+ "@libp2p/logger": "5.2.0-87bc8d4fb",
70
+ "aegir": "^47.0.14",
71
71
  "p-wait-for": "^5.0.2",
72
- "sinon": "^21.0.0",
72
+ "sinon": "^20.0.0",
73
73
  "sinon-ts": "^2.0.0",
74
74
  "wherearewe": "^2.0.1"
75
75
  },
package/src/index.ts CHANGED
@@ -59,6 +59,11 @@ export interface TCPOptions {
59
59
  */
60
60
  outboundSocketInactivityTimeout?: number
61
61
 
62
+ /**
63
+ * When closing a socket, wait this long for it to close gracefully before it is closed more forcibly
64
+ */
65
+ socketCloseTimeout?: number
66
+
62
67
  /**
63
68
  * Set this property to reject connections when the server's connection count gets high.
64
69
  * https://nodejs.org/api/net.html#servermaxconnections
package/src/listener.ts CHANGED
@@ -1,6 +1,6 @@
1
- import net from 'node:net'
1
+ import net from 'net'
2
2
  import { AlreadyStartedError, InvalidParametersError, NotStartedError } from '@libp2p/interface'
3
- import { getThinWaistAddresses } from '@libp2p/utils'
3
+ import { getThinWaistAddresses } from '@libp2p/utils/get-thin-waist-addresses'
4
4
  import { multiaddr } from '@multiformats/multiaddr'
5
5
  import { TypedEventEmitter, setMaxListeners } from 'main-event'
6
6
  import { pEvent } from 'p-event'
@@ -8,12 +8,13 @@ import { toMultiaddrConnection } from './socket-to-conn.js'
8
8
  import { multiaddrToNetConfig } from './utils.js'
9
9
  import type { CloseServerOnMaxConnectionsOpts, TCPCreateListenerOptions } from './index.js'
10
10
  import type { NetConfig } from './utils.js'
11
- import type { ComponentLogger, Logger, MultiaddrConnection, CounterGroup, MetricGroup, Metrics, Listener, ListenerEvents, Upgrader, AbortOptions } from '@libp2p/interface'
11
+ import type { ComponentLogger, Logger, MultiaddrConnection, CounterGroup, MetricGroup, Metrics, Listener, ListenerEvents, Upgrader } from '@libp2p/interface'
12
12
  import type { Multiaddr } from '@multiformats/multiaddr'
13
13
 
14
14
  interface Context extends TCPCreateListenerOptions {
15
15
  upgrader: Upgrader
16
- inactivityTimeout?: number
16
+ socketInactivityTimeout?: number
17
+ socketCloseTimeout?: number
17
18
  maxConnections?: number
18
19
  backlog?: number
19
20
  metrics?: Metrics
@@ -60,7 +61,6 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
60
61
 
61
62
  context.keepAlive = context.keepAlive ?? true
62
63
  context.noDelay = context.noDelay ?? true
63
- context.allowHalfOpen = context.allowHalfOpen ?? false
64
64
 
65
65
  this.shutdownController = new AbortController()
66
66
  setMaxListeners(Infinity, this.shutdownController.signal)
@@ -161,14 +161,14 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
161
161
 
162
162
  let maConn: MultiaddrConnection
163
163
  try {
164
- maConn = toMultiaddrConnection({
165
- socket,
166
- inactivityTimeout: this.context.inactivityTimeout,
164
+ maConn = toMultiaddrConnection(socket, {
165
+ listeningAddr: this.status.listeningAddr,
166
+ socketInactivityTimeout: this.context.socketInactivityTimeout,
167
+ socketCloseTimeout: this.context.socketCloseTimeout,
167
168
  metrics: this.metrics?.events,
168
169
  metricPrefix: `${this.addr} `,
169
- direction: 'inbound',
170
- localAddr: this.status.listeningAddr,
171
- log: this.context.logger.forComponent('libp2p:tcp:connection')
170
+ logger: this.context.logger,
171
+ direction: 'inbound'
172
172
  })
173
173
  } catch (err: any) {
174
174
  this.log.error('inbound connection failed', err)
@@ -210,7 +210,6 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
210
210
  this.context.closeServerOnMaxConnections != null &&
211
211
  this.sockets.size >= this.context.closeServerOnMaxConnections.closeAbove
212
212
  ) {
213
- this.log('pausing incoming connections as limit is exceeded - %d/%d', this.sockets.size, this.context.closeServerOnMaxConnections.closeAbove)
214
213
  this.pause()
215
214
  }
216
215
  })
@@ -265,11 +264,11 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
265
264
  }
266
265
  }
267
266
 
268
- async close (options?: AbortOptions): Promise<void> {
267
+ async close (): Promise<void> {
269
268
  const events: Array<Promise<void>> = []
270
269
 
271
270
  if (this.server.listening) {
272
- events.push(pEvent(this.server, 'close', options))
271
+ events.push(pEvent(this.server, 'close'))
273
272
  }
274
273
 
275
274
  // shut down the server socket, permanently
@@ -282,7 +281,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
282
281
  // the server socket in case new sockets are opened during the shutdown
283
282
  this.sockets.forEach(socket => {
284
283
  if (socket.readable) {
285
- events.push(pEvent(socket, 'close', options))
284
+ events.push(pEvent(socket, 'close'))
286
285
  socket.destroy()
287
286
  }
288
287
  })
@@ -321,7 +320,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
321
320
  return
322
321
  }
323
322
 
324
- this.log('%s server on %s', permanent ? 'closing' : 'pausing', this.server.address())
323
+ this.log('closing server on %s', this.server.address())
325
324
 
326
325
  // NodeJS implementation tracks listening status with `this._handle` property.
327
326
  // - Server.close() sets this._handle to null immediately. If this._handle is null, NotStartedError is thrown
@@ -1,144 +1,231 @@
1
1
  import { InvalidParametersError, TimeoutError } from '@libp2p/interface'
2
- import { AbstractMultiaddrConnection, ipPortToMultiaddr } from '@libp2p/utils'
3
- import { Unix } from '@multiformats/multiaddr-matcher'
4
- import { pEvent } from 'p-event'
5
- import type { AbortOptions, MultiaddrConnection } from '@libp2p/interface'
6
- import type { AbstractMultiaddrConnectionInit, SendResult } from '@libp2p/utils'
7
- import type { Multiaddr } from '@multiformats/multiaddr'
2
+ import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr'
3
+ import pDefer from 'p-defer'
4
+ import { raceEvent } from 'race-event'
5
+ import { duplex } from 'stream-to-it'
6
+ import { CLOSE_TIMEOUT, SOCKET_TIMEOUT } from './constants.js'
7
+ import { multiaddrToNetConfig } from './utils.js'
8
+ import type { ComponentLogger, MultiaddrConnection, CounterGroup } from '@libp2p/interface'
9
+ import type { AbortOptions, Multiaddr } from '@multiformats/multiaddr'
8
10
  import type { Socket } from 'net'
9
- import type { Uint8ArrayList } from 'uint8arraylist'
11
+ import type { DeferredPromise } from 'p-defer'
10
12
 
11
- export interface TCPSocketMultiaddrConnectionInit extends Omit<AbstractMultiaddrConnectionInit, 'name' | 'stream' | 'remoteAddr'> {
12
- socket: Socket
13
+ interface ToConnectionOptions {
14
+ listeningAddr?: Multiaddr
13
15
  remoteAddr?: Multiaddr
16
+ localAddr?: Multiaddr
17
+ socketInactivityTimeout?: number
18
+ socketCloseTimeout?: number
19
+ metrics?: CounterGroup
20
+ metricPrefix?: string
21
+ logger: ComponentLogger
22
+ direction: 'inbound' | 'outbound'
14
23
  }
15
24
 
16
- class TCPSocketMultiaddrConnection extends AbstractMultiaddrConnection {
17
- private socket: Socket
25
+ /**
26
+ * Convert a socket into a MultiaddrConnection
27
+ * https://github.com/libp2p/interface-transport#multiaddrconnection
28
+ */
29
+ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptions): MultiaddrConnection => {
30
+ let closePromise: DeferredPromise<void>
31
+ const direction = options.direction
32
+ const metrics = options.metrics
33
+ const metricPrefix = options.metricPrefix ?? ''
34
+ const inactivityTimeout = options.socketInactivityTimeout ?? SOCKET_TIMEOUT
35
+ const closeTimeout = options.socketCloseTimeout ?? CLOSE_TIMEOUT
36
+ let timedOut = false
37
+ let errored = false
38
+
39
+ // Check if we are connected on a unix path
40
+ if (options.listeningAddr?.getPath() != null) {
41
+ options.remoteAddr = options.listeningAddr
42
+ }
18
43
 
19
- constructor (init: TCPSocketMultiaddrConnectionInit) {
20
- let remoteAddr = init.remoteAddr
44
+ if (options.remoteAddr?.getPath() != null) {
45
+ options.localAddr = options.remoteAddr
46
+ }
21
47
 
22
- // check if we are connected on a unix path
23
- if (init.localAddr != null && Unix.matches(init.localAddr)) {
24
- remoteAddr = init.localAddr
25
- } else if (remoteAddr == null) {
26
- if (init.socket.remoteAddress == null || init.socket.remotePort == null) {
27
- // this can be undefined if the socket is destroyed (for example, if the client disconnected)
28
- // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketremoteaddress
29
- throw new InvalidParametersError('Could not determine remote address or port')
30
- }
48
+ // handle socket errors
49
+ socket.on('error', err => {
50
+ errored = true
31
51
 
32
- remoteAddr = ipPortToMultiaddr(init.socket.remoteAddress, init.socket.remotePort)
52
+ if (!timedOut) {
53
+ maConn.log.error('%s socket error - %e', direction, err)
54
+ metrics?.increment({ [`${metricPrefix}error`]: true })
33
55
  }
34
56
 
35
- super({
36
- ...init,
37
- remoteAddr
38
- })
39
-
40
- this.socket = init.socket
41
-
42
- // handle incoming data
43
- this.socket.on('data', buf => {
44
- this.onData(buf)
45
- })
46
-
47
- // handle socket errors
48
- this.socket.on('error', err => {
49
- this.log('tcp error', remoteAddr, err)
50
-
51
- this.abort(err)
52
- })
53
-
54
- // by default there is no timeout
55
- // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketsettimeouttimeout-callback
56
- this.socket.setTimeout(init.inactivityTimeout ?? (2 * 60 * 1_000))
57
-
58
- this.socket.once('timeout', () => {
59
- this.log('tcp timeout', remoteAddr)
60
- // if the socket times out due to inactivity we must manually close the connection
61
- // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#event-timeout
62
- this.abort(new TimeoutError())
63
- })
64
-
65
- this.socket.once('end', () => {
66
- this.log('tcp end', remoteAddr)
67
-
68
- // the remote sent a FIN packet which means no more data will be sent
69
- // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#event-end
70
- // half open TCP sockets are disabled by default so Node.js should send a
71
- // FIN in response to this event and then emit a 'close' event, during
72
- // which we tear down the MultiaddrConnection so there is nothing to do
73
- // until that occurs
74
- this.onTransportClosed()
75
- })
76
-
77
- this.socket.once('close', hadError => {
78
- this.log('tcp close', remoteAddr)
79
-
80
- if (hadError) {
81
- this.abort(new Error('TCP transmission error'))
82
- return
83
- }
57
+ socket.destroy()
58
+ maConn.timeline.close = Date.now()
59
+ })
84
60
 
85
- this.onTransportClosed()
86
- })
61
+ let remoteAddr: Multiaddr
87
62
 
88
- // the socket can accept more data
89
- this.socket.on('drain', () => {
90
- this.log('tcp drain')
63
+ if (options.remoteAddr != null) {
64
+ remoteAddr = options.remoteAddr
65
+ } else {
66
+ if (socket.remoteAddress == null || socket.remotePort == null) {
67
+ // this can be undefined if the socket is destroyed (for example, if the client disconnected)
68
+ // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketremoteaddress
69
+ throw new InvalidParametersError('Could not determine remote address or port')
70
+ }
91
71
 
92
- this.safeDispatchEvent('drain')
93
- })
72
+ remoteAddr = toMultiaddr(socket.remoteAddress, socket.remotePort)
94
73
  }
95
74
 
96
- sendData (data: Uint8ArrayList): SendResult {
97
- let sentBytes = 0
98
- let canSendMore = true
75
+ const lOpts = multiaddrToNetConfig(remoteAddr)
76
+ const lOptsStr = lOpts.path ?? `${lOpts.host ?? ''}:${lOpts.port ?? ''}`
77
+ const { sink, source } = duplex(socket)
78
+
79
+ // by default there is no timeout
80
+ // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketsettimeouttimeout-callback
81
+ socket.setTimeout(inactivityTimeout)
82
+
83
+ socket.once('timeout', () => {
84
+ timedOut = true
85
+ maConn.log('%s %s socket read timeout', direction, lOptsStr)
86
+ metrics?.increment({ [`${metricPrefix}timeout`]: true })
87
+
88
+ // if the socket times out due to inactivity we must manually close the connection
89
+ // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#event-timeout
90
+ socket.destroy(new TimeoutError())
91
+ maConn.timeline.close = Date.now()
92
+ })
93
+
94
+ socket.once('close', () => {
95
+ // record metric for clean exit
96
+ if (!timedOut && !errored) {
97
+ maConn.log('%s %s socket close', direction, lOptsStr)
98
+ metrics?.increment({ [`${metricPrefix}close`]: true })
99
+ }
100
+
101
+ // In instances where `close` was not explicitly called,
102
+ // such as an iterable stream ending, ensure we have set the close
103
+ // timeline
104
+ socket.destroy()
105
+ maConn.timeline.close = Date.now()
106
+ })
107
+
108
+ socket.once('end', () => {
109
+ // the remote sent a FIN packet which means no more data will be sent
110
+ // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#event-end
111
+ maConn.log('%s %s socket end', direction, lOptsStr)
112
+ metrics?.increment({ [`${metricPrefix}end`]: true })
113
+ })
114
+
115
+ const maConn: MultiaddrConnection = {
116
+ async sink (source) {
117
+ try {
118
+ await sink((async function * () {
119
+ for await (const buf of source) {
120
+ if (buf instanceof Uint8Array) {
121
+ yield buf
122
+ } else {
123
+ yield buf.subarray()
124
+ }
125
+ }
126
+ })())
127
+ } catch (err: any) {
128
+ // If aborted we can safely ignore
129
+ if (err.type !== 'aborted') {
130
+ // If the source errored the socket will already have been destroyed by
131
+ // duplex(). If the socket errored it will already be
132
+ // destroyed. There's nothing to do here except log the error & return.
133
+ maConn.log.error('%s %s error in sink - %e', direction, lOptsStr, err)
134
+ }
135
+ }
136
+
137
+ // we have finished writing, send the FIN message
138
+ socket.end()
139
+ },
99
140
 
100
- for (const buf of data) {
101
- sentBytes += buf.byteLength
102
- canSendMore = this.socket.write(buf)
141
+ source,
103
142
 
104
- if (!canSendMore) {
105
- break
143
+ // If the remote address was passed, use it - it may have the peer ID encapsulated
144
+ remoteAddr,
145
+
146
+ timeline: { open: Date.now() },
147
+
148
+ async close (options: AbortOptions = {}) {
149
+ if (socket.closed) {
150
+ maConn.log('the %s %s socket is already closed', direction, lOptsStr)
151
+ return
106
152
  }
107
- }
108
153
 
109
- return {
110
- sentBytes,
111
- canSendMore
112
- }
113
- }
154
+ if (socket.destroyed) {
155
+ maConn.log('the %s %s socket is already destroyed', direction, lOptsStr)
156
+ return
157
+ }
114
158
 
115
- async sendClose (options?: AbortOptions): Promise<void> {
116
- if (this.socket.destroyed) {
117
- return
118
- }
159
+ if (closePromise != null) {
160
+ return closePromise.promise
161
+ }
119
162
 
120
- this.socket.destroySoon()
163
+ try {
164
+ closePromise = pDefer()
165
+
166
+ // close writable end of socket
167
+ socket.end()
168
+
169
+ // convert EventEmitter to EventTarget
170
+ const eventTarget = socketToEventTarget(socket)
171
+
172
+ // don't wait forever to close
173
+ const signal = options.signal ?? AbortSignal.timeout(closeTimeout)
174
+
175
+ // wait for any unsent data to be sent
176
+ if (socket.writableLength > 0) {
177
+ maConn.log('%s %s draining socket', direction, lOptsStr)
178
+ await raceEvent(eventTarget, 'drain', signal, {
179
+ errorEvent: 'error'
180
+ })
181
+ maConn.log('%s %s socket drained', direction, lOptsStr)
182
+ }
183
+
184
+ await Promise.all([
185
+ raceEvent(eventTarget, 'close', signal, {
186
+ errorEvent: 'error'
187
+ }),
188
+
189
+ // all bytes have been sent we can destroy the socket
190
+ // eslint-disable-next-line @typescript-eslint/await-thenable
191
+ socket.destroy()
192
+ ])
193
+ } catch (err: any) {
194
+ this.abort(err)
195
+ } finally {
196
+ closePromise.resolve()
197
+ }
198
+ },
121
199
 
122
- await pEvent(this.socket, 'close', options)
123
- }
200
+ abort: (err: Error) => {
201
+ maConn.log('%s %s socket abort due to error - %e', direction, lOptsStr, err)
124
202
 
125
- sendReset (): void {
126
- this.socket.resetAndDestroy()
127
- }
203
+ // the abortSignalListener may already destroyed the socket with an error
204
+ socket.destroy()
128
205
 
129
- sendPause (): void {
130
- this.socket.pause()
131
- }
206
+ // closing a socket is always asynchronous (must wait for "close" event)
207
+ // but the tests expect this to be a synchronous operation so we have to
208
+ // set the close time here. the tests should be refactored to reflect
209
+ // reality.
210
+ maConn.timeline.close = Date.now()
211
+ },
132
212
 
133
- sendResume (): void {
134
- this.socket.resume()
213
+ log: options.logger.forComponent('libp2p:tcp:connection')
135
214
  }
215
+
216
+ return maConn
136
217
  }
137
218
 
138
- /**
139
- * Convert a socket into a MultiaddrConnection
140
- * https://github.com/libp2p/interface-transport#multiaddrconnection
141
- */
142
- export const toMultiaddrConnection = (init: TCPSocketMultiaddrConnectionInit): MultiaddrConnection => {
143
- return new TCPSocketMultiaddrConnection(init)
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
144
231
  }
package/src/tcp.ts CHANGED
@@ -75,7 +75,6 @@ export class TCP implements Transport<TCPDialEvents> {
75
75
  async dial (ma: Multiaddr, options: TCPDialOptions): Promise<Connection> {
76
76
  options.keepAlive = options.keepAlive ?? true
77
77
  options.noDelay = options.noDelay ?? true
78
- options.allowHalfOpen = options.allowHalfOpen ?? false
79
78
 
80
79
  // options.signal destroys the socket before 'connect' event
81
80
  const socket = await this._connect(ma, options)
@@ -83,13 +82,13 @@ export class TCP implements Transport<TCPDialEvents> {
83
82
  let maConn: MultiaddrConnection
84
83
 
85
84
  try {
86
- maConn = toMultiaddrConnection({
87
- socket,
88
- inactivityTimeout: this.opts.outboundSocketInactivityTimeout,
89
- metrics: this.metrics?.events,
90
- direction: 'outbound',
85
+ maConn = toMultiaddrConnection(socket, {
91
86
  remoteAddr: ma,
92
- log: this.log.newScope('connection')
87
+ socketInactivityTimeout: this.opts.outboundSocketInactivityTimeout,
88
+ socketCloseTimeout: this.opts.socketCloseTimeout,
89
+ metrics: this.metrics?.events,
90
+ logger: this.components.logger,
91
+ direction: 'outbound'
93
92
  })
94
93
  } catch (err: any) {
95
94
  this.metrics?.errors.increment({ outbound_to_connection: true })
@@ -121,7 +120,7 @@ export class TCP implements Transport<TCPDialEvents> {
121
120
  ...options
122
121
  }) as (IpcSocketConnectOpts & TcpSocketConnectOpts)
123
122
 
124
- this.log('dialing %a with opts %o', ma, cOpts)
123
+ this.log('dialing %a', ma)
125
124
  rawSocket = net.connect(cOpts)
126
125
 
127
126
  const onError = (err: Error): void => {
@@ -193,7 +192,8 @@ export class TCP implements Transport<TCPDialEvents> {
193
192
  maxConnections: this.opts.maxConnections,
194
193
  backlog: this.opts.backlog,
195
194
  closeServerOnMaxConnections: this.opts.closeServerOnMaxConnections,
196
- inactivityTimeout: this.opts.inboundSocketInactivityTimeout,
195
+ socketInactivityTimeout: this.opts.inboundSocketInactivityTimeout,
196
+ socketCloseTimeout: this.opts.socketCloseTimeout,
197
197
  metrics: this.components.metrics,
198
198
  logger: this.components.logger
199
199
  })
package/src/utils.ts CHANGED
@@ -1,23 +1,15 @@
1
1
  import os from 'os'
2
2
  import path from 'path'
3
- import { InvalidParametersError } from '@libp2p/interface'
4
- import { getNetConfig } from '@libp2p/utils'
5
- import { CODE_UNIX } from '@multiformats/multiaddr'
6
- import { Unix } from '@multiformats/multiaddr-matcher'
7
3
  import type { Multiaddr } from '@multiformats/multiaddr'
8
4
  import type { ListenOptions, IpcSocketConnectOpts, TcpSocketConnectOpts } from 'net'
9
5
 
10
6
  export type NetConfig = ListenOptions | (IpcSocketConnectOpts & TcpSocketConnectOpts)
11
7
 
12
- export function multiaddrToNetConfig (addr: Multiaddr, options: NetConfig = {}): NetConfig {
13
- if (Unix.exactMatch(addr)) {
14
- const listenPath = addr.getComponents().find(c => c.code === CODE_UNIX)?.value
8
+ export function multiaddrToNetConfig (addr: Multiaddr, config: NetConfig = {}): NetConfig {
9
+ const listenPath = addr.getPath()
15
10
 
16
- if (listenPath == null) {
17
- throw new InvalidParametersError(`Multiaddr ${addr} was not a Unix address`)
18
- }
19
-
20
- // unix socket listening
11
+ // unix socket listening
12
+ if (listenPath != null) {
21
13
  if (os.platform() === 'win32') {
22
14
  // Use named pipes on Windows systems.
23
15
  return { path: path.join('\\\\.\\pipe\\', listenPath) }
@@ -26,15 +18,12 @@ export function multiaddrToNetConfig (addr: Multiaddr, options: NetConfig = {}):
26
18
  }
27
19
  }
28
20
 
29
- const config = getNetConfig(addr)
30
- const host = config.host
31
- const port = config.port
21
+ const options = addr.toOptions()
32
22
 
33
23
  // tcp listening
34
24
  return {
35
- host,
36
- port,
37
- ipv6Only: config.type === 'ip6',
38
- ...options
25
+ ...config,
26
+ ...options,
27
+ ipv6Only: options.family === 6
39
28
  }
40
29
  }