@libp2p/tcp 10.1.18 → 10.1.19-8484de8a2

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, config?: NetConfig): NetConfig;
4
+ export declare function multiaddrToNetConfig(addr: Multiaddr, options?: 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":"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"}
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"}
package/dist/src/utils.js CHANGED
@@ -1,9 +1,16 @@
1
1
  import os from 'os';
2
2
  import path from 'path';
3
- export function multiaddrToNetConfig(addr, config = {}) {
4
- const listenPath = addr.getPath();
5
- // unix socket listening
6
- if (listenPath != null) {
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
7
14
  if (os.platform() === 'win32') {
8
15
  // Use named pipes on Windows systems.
9
16
  return { path: path.join('\\\\.\\pipe\\', listenPath) };
@@ -12,12 +19,15 @@ export function multiaddrToNetConfig(addr, config = {}) {
12
19
  return { path: listenPath };
13
20
  }
14
21
  }
15
- const options = addr.toOptions();
22
+ const config = getNetConfig(addr);
23
+ const host = config.host;
24
+ const port = config.port;
16
25
  // tcp listening
17
26
  return {
18
- ...config,
19
- ...options,
20
- ipv6Only: options.family === 6
27
+ host,
28
+ port,
29
+ ipv6Only: config.type === 'ip6',
30
+ ...options
21
31
  };
22
32
  }
23
33
  //# 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;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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/tcp",
3
- "version": "10.1.18",
3
+ "version": "10.1.19-8484de8a2",
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.10.5",
57
- "@libp2p/utils": "^6.7.1",
58
- "@multiformats/multiaddr": "^12.4.4",
59
- "@multiformats/multiaddr-matcher": "^2.0.0",
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",
60
60
  "@types/sinon": "^17.0.4",
61
61
  "main-event": "^1.0.1",
62
- "p-defer": "^4.0.1",
63
62
  "p-event": "^6.0.1",
64
63
  "progress-events": "^1.0.1",
65
- "race-event": "^1.3.0",
66
- "stream-to-it": "^1.0.1"
64
+ "uint8arraylist": "^2.4.8"
67
65
  },
68
66
  "devDependencies": {
69
- "@libp2p/logger": "^5.1.21",
70
- "aegir": "^47.0.14",
67
+ "@libp2p/logger": "5.2.0-8484de8a2",
68
+ "aegir": "^47.0.21",
69
+ "delay": "^6.0.0",
70
+ "p-defer": "^4.0.1",
71
71
  "p-wait-for": "^5.0.2",
72
- "sinon": "^20.0.0",
72
+ "sinon": "^21.0.0",
73
73
  "sinon-ts": "^2.0.0",
74
74
  "wherearewe": "^2.0.1"
75
75
  },
package/src/index.ts CHANGED
@@ -59,11 +59,6 @@ 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
-
67
62
  /**
68
63
  * Set this property to reject connections when the server's connection count gets high.
69
64
  * https://nodejs.org/api/net.html#servermaxconnections
package/src/listener.ts CHANGED
@@ -1,6 +1,6 @@
1
- import net from 'net'
1
+ import net from 'node:net'
2
2
  import { AlreadyStartedError, InvalidParametersError, NotStartedError } from '@libp2p/interface'
3
- import { getThinWaistAddresses } from '@libp2p/utils/get-thin-waist-addresses'
3
+ import { getThinWaistAddresses } from '@libp2p/utils'
4
4
  import { multiaddr } from '@multiformats/multiaddr'
5
5
  import { TypedEventEmitter, setMaxListeners } from 'main-event'
6
6
  import { pEvent } from 'p-event'
@@ -8,13 +8,12 @@ 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 } from '@libp2p/interface'
11
+ import type { ComponentLogger, Logger, MultiaddrConnection, CounterGroup, MetricGroup, Metrics, Listener, ListenerEvents, Upgrader, AbortOptions } from '@libp2p/interface'
12
12
  import type { Multiaddr } from '@multiformats/multiaddr'
13
13
 
14
14
  interface Context extends TCPCreateListenerOptions {
15
15
  upgrader: Upgrader
16
- socketInactivityTimeout?: number
17
- socketCloseTimeout?: number
16
+ inactivityTimeout?: number
18
17
  maxConnections?: number
19
18
  backlog?: number
20
19
  metrics?: Metrics
@@ -61,6 +60,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
61
60
 
62
61
  context.keepAlive = context.keepAlive ?? true
63
62
  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(socket, {
165
- listeningAddr: this.status.listeningAddr,
166
- socketInactivityTimeout: this.context.socketInactivityTimeout,
167
- socketCloseTimeout: this.context.socketCloseTimeout,
164
+ maConn = toMultiaddrConnection({
165
+ socket,
166
+ inactivityTimeout: this.context.inactivityTimeout,
168
167
  metrics: this.metrics?.events,
169
168
  metricPrefix: `${this.addr} `,
170
- logger: this.context.logger,
171
- direction: 'inbound'
169
+ direction: 'inbound',
170
+ localAddr: this.status.listeningAddr,
171
+ log: this.context.logger.forComponent('libp2p:tcp:connection')
172
172
  })
173
173
  } catch (err: any) {
174
174
  this.log.error('inbound connection failed', err)
@@ -210,6 +210,7 @@ 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)
213
214
  this.pause()
214
215
  }
215
216
  })
@@ -264,11 +265,11 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
264
265
  }
265
266
  }
266
267
 
267
- async close (): Promise<void> {
268
+ async close (options?: AbortOptions): Promise<void> {
268
269
  const events: Array<Promise<void>> = []
269
270
 
270
271
  if (this.server.listening) {
271
- events.push(pEvent(this.server, 'close'))
272
+ events.push(pEvent(this.server, 'close', options))
272
273
  }
273
274
 
274
275
  // shut down the server socket, permanently
@@ -281,7 +282,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
281
282
  // the server socket in case new sockets are opened during the shutdown
282
283
  this.sockets.forEach(socket => {
283
284
  if (socket.readable) {
284
- events.push(pEvent(socket, 'close'))
285
+ events.push(pEvent(socket, 'close', options))
285
286
  socket.destroy()
286
287
  }
287
288
  })
@@ -320,7 +321,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
320
321
  return
321
322
  }
322
323
 
323
- this.log('closing server on %s', this.server.address())
324
+ this.log('%s server on %s', permanent ? 'closing' : 'pausing', this.server.address())
324
325
 
325
326
  // NodeJS implementation tracks listening status with `this._handle` property.
326
327
  // - Server.close() sets this._handle to null immediately. If this._handle is null, NotStartedError is thrown
@@ -1,231 +1,144 @@
1
1
  import { InvalidParametersError, TimeoutError } from '@libp2p/interface'
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'
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'
10
8
  import type { Socket } from 'net'
11
- import type { DeferredPromise } from 'p-defer'
9
+ import type { Uint8ArrayList } from 'uint8arraylist'
12
10
 
13
- interface ToConnectionOptions {
14
- listeningAddr?: Multiaddr
11
+ export interface TCPSocketMultiaddrConnectionInit extends Omit<AbstractMultiaddrConnectionInit, 'name' | 'stream' | 'remoteAddr'> {
12
+ socket: Socket
15
13
  remoteAddr?: Multiaddr
16
- localAddr?: Multiaddr
17
- socketInactivityTimeout?: number
18
- socketCloseTimeout?: number
19
- metrics?: CounterGroup
20
- metricPrefix?: string
21
- logger: ComponentLogger
22
- direction: 'inbound' | 'outbound'
23
14
  }
24
15
 
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 log = options.logger.forComponent('libp2p:tcp:socket')
32
- const direction = options.direction
33
- const metrics = options.metrics
34
- const metricPrefix = options.metricPrefix ?? ''
35
- const inactivityTimeout = options.socketInactivityTimeout ?? SOCKET_TIMEOUT
36
- const closeTimeout = options.socketCloseTimeout ?? CLOSE_TIMEOUT
37
- let timedOut = false
38
- let errored = false
39
-
40
- // Check if we are connected on a unix path
41
- if (options.listeningAddr?.getPath() != null) {
42
- options.remoteAddr = options.listeningAddr
43
- }
44
-
45
- if (options.remoteAddr?.getPath() != null) {
46
- options.localAddr = options.remoteAddr
47
- }
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
- })
16
+ class TCPSocketMultiaddrConnection extends AbstractMultiaddrConnection {
17
+ private socket: Socket
61
18
 
62
- let remoteAddr: Multiaddr
19
+ constructor (init: TCPSocketMultiaddrConnectionInit) {
20
+ let remoteAddr = init.remoteAddr
63
21
 
64
- if (options.remoteAddr != null) {
65
- remoteAddr = options.remoteAddr
66
- } else {
67
- if (socket.remoteAddress == null || socket.remotePort == null) {
68
- // this can be undefined if the socket is destroyed (for example, if the client disconnected)
69
- // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketremoteaddress
70
- throw new InvalidParametersError('Could not determine remote address or port')
71
- }
72
-
73
- remoteAddr = toMultiaddr(socket.remoteAddress, socket.remotePort)
74
- }
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
+ }
75
31
 
76
- const lOpts = multiaddrToNetConfig(remoteAddr)
77
- const lOptsStr = lOpts.path ?? `${lOpts.host ?? ''}:${lOpts.port ?? ''}`
78
- const { sink, source } = duplex(socket)
79
-
80
- // by default there is no timeout
81
- // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketsettimeouttimeout-callback
82
- socket.setTimeout(inactivityTimeout)
83
-
84
- socket.once('timeout', () => {
85
- timedOut = true
86
- log('%s %s socket read timeout', direction, lOptsStr)
87
- metrics?.increment({ [`${metricPrefix}timeout`]: true })
88
-
89
- // if the socket times out due to inactivity we must manually close the connection
90
- // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#event-timeout
91
- socket.destroy(new TimeoutError())
92
- maConn.timeline.close = Date.now()
93
- })
94
-
95
- socket.once('close', () => {
96
- // record metric for clean exit
97
- if (!timedOut && !errored) {
98
- log('%s %s socket close', direction, lOptsStr)
99
- metrics?.increment({ [`${metricPrefix}close`]: true })
32
+ remoteAddr = ipPortToMultiaddr(init.socket.remoteAddress, init.socket.remotePort)
100
33
  }
101
34
 
102
- // In instances where `close` was not explicitly called,
103
- // such as an iterable stream ending, ensure we have set the close
104
- // timeline
105
- socket.destroy()
106
- maConn.timeline.close = Date.now()
107
- })
108
-
109
- socket.once('end', () => {
110
- // the remote sent a FIN packet which means no more data will be sent
111
- // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#event-end
112
- log('%s %s socket end', direction, lOptsStr)
113
- metrics?.increment({ [`${metricPrefix}end`]: true })
114
- })
115
-
116
- const maConn: MultiaddrConnection = {
117
- async sink (source) {
118
- try {
119
- await sink((async function * () {
120
- for await (const buf of source) {
121
- if (buf instanceof Uint8Array) {
122
- yield buf
123
- } else {
124
- yield buf.subarray()
125
- }
126
- }
127
- })())
128
- } catch (err: any) {
129
- // If aborted we can safely ignore
130
- if (err.type !== 'aborted') {
131
- // If the source errored the socket will already have been destroyed by
132
- // duplex(). If the socket errored it will already be
133
- // destroyed. There's nothing to do here except log the error & return.
134
- log.error('%s %s error in sink - %e', direction, lOptsStr, err)
135
- }
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
136
83
  }
137
84
 
138
- // we have finished writing, send the FIN message
139
- socket.end()
140
- },
85
+ this.onTransportClosed()
86
+ })
141
87
 
142
- source,
88
+ // the socket can accept more data
89
+ this.socket.on('drain', () => {
90
+ this.log('tcp drain')
143
91
 
144
- // If the remote address was passed, use it - it may have the peer ID encapsulated
145
- remoteAddr,
146
-
147
- timeline: { open: Date.now() },
92
+ this.safeDispatchEvent('drain')
93
+ })
94
+ }
148
95
 
149
- async close (options: AbortOptions = {}) {
150
- if (socket.closed) {
151
- log('the %s %s socket is already closed', direction, lOptsStr)
152
- return
153
- }
96
+ sendData (data: Uint8ArrayList): SendResult {
97
+ let sentBytes = 0
98
+ let canSendMore = true
154
99
 
155
- if (socket.destroyed) {
156
- log('the %s %s socket is already destroyed', direction, lOptsStr)
157
- return
158
- }
100
+ for (const buf of data) {
101
+ sentBytes += buf.byteLength
102
+ canSendMore = this.socket.write(buf)
159
103
 
160
- if (closePromise != null) {
161
- return closePromise.promise
104
+ if (!canSendMore) {
105
+ break
162
106
  }
107
+ }
163
108
 
164
- try {
165
- closePromise = pDefer()
166
-
167
- // close writable end of socket
168
- socket.end()
169
-
170
- // convert EventEmitter to EventTarget
171
- const eventTarget = socketToEventTarget(socket)
172
-
173
- // don't wait forever to close
174
- const signal = options.signal ?? AbortSignal.timeout(closeTimeout)
175
-
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'
181
- })
182
- log('%s %s socket drained', direction, lOptsStr)
183
- }
184
-
185
- await Promise.all([
186
- raceEvent(eventTarget, 'close', signal, {
187
- errorEvent: 'error'
188
- }),
189
-
190
- // all bytes have been sent we can destroy the socket
191
- socket.destroy()
192
- ])
193
- } catch (err: any) {
194
- this.abort(err)
195
- } finally {
196
- closePromise.resolve()
197
- }
198
- },
109
+ return {
110
+ sentBytes,
111
+ canSendMore
112
+ }
113
+ }
199
114
 
200
- abort: (err: Error) => {
201
- log('%s %s socket abort due to error - %e', direction, lOptsStr, err)
115
+ async sendClose (options?: AbortOptions): Promise<void> {
116
+ if (this.socket.destroyed) {
117
+ return
118
+ }
202
119
 
203
- // the abortSignalListener may already destroyed the socket with an error
204
- socket.destroy()
120
+ this.socket.destroySoon()
205
121
 
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
- },
122
+ await pEvent(this.socket, 'close', options)
123
+ }
212
124
 
213
- log
125
+ sendReset (): void {
126
+ this.socket.resetAndDestroy()
214
127
  }
215
128
 
216
- return maConn
217
- }
129
+ sendPause (): void {
130
+ this.socket.pause()
131
+ }
218
132
 
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
- }
133
+ sendResume (): void {
134
+ this.socket.resume()
227
135
  }
136
+ }
228
137
 
229
- // @ts-expect-error partial implementation
230
- return eventTarget
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)
231
144
  }
package/src/tcp.ts CHANGED
@@ -75,6 +75,7 @@ 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
78
79
 
79
80
  // options.signal destroys the socket before 'connect' event
80
81
  const socket = await this._connect(ma, options)
@@ -82,13 +83,13 @@ export class TCP implements Transport<TCPDialEvents> {
82
83
  let maConn: MultiaddrConnection
83
84
 
84
85
  try {
85
- maConn = toMultiaddrConnection(socket, {
86
- remoteAddr: ma,
87
- socketInactivityTimeout: this.opts.outboundSocketInactivityTimeout,
88
- socketCloseTimeout: this.opts.socketCloseTimeout,
86
+ maConn = toMultiaddrConnection({
87
+ socket,
88
+ inactivityTimeout: this.opts.outboundSocketInactivityTimeout,
89
89
  metrics: this.metrics?.events,
90
- logger: this.components.logger,
91
- direction: 'outbound'
90
+ direction: 'outbound',
91
+ remoteAddr: ma,
92
+ log: this.log.newScope('connection')
92
93
  })
93
94
  } catch (err: any) {
94
95
  this.metrics?.errors.increment({ outbound_to_connection: true })
@@ -120,7 +121,7 @@ export class TCP implements Transport<TCPDialEvents> {
120
121
  ...options
121
122
  }) as (IpcSocketConnectOpts & TcpSocketConnectOpts)
122
123
 
123
- this.log('dialing %a', ma)
124
+ this.log('dialing %a with opts %o', ma, cOpts)
124
125
  rawSocket = net.connect(cOpts)
125
126
 
126
127
  const onError = (err: Error): void => {
@@ -192,8 +193,7 @@ export class TCP implements Transport<TCPDialEvents> {
192
193
  maxConnections: this.opts.maxConnections,
193
194
  backlog: this.opts.backlog,
194
195
  closeServerOnMaxConnections: this.opts.closeServerOnMaxConnections,
195
- socketInactivityTimeout: this.opts.inboundSocketInactivityTimeout,
196
- socketCloseTimeout: this.opts.socketCloseTimeout,
196
+ inactivityTimeout: this.opts.inboundSocketInactivityTimeout,
197
197
  metrics: this.components.metrics,
198
198
  logger: this.components.logger
199
199
  })
package/src/utils.ts CHANGED
@@ -1,15 +1,23 @@
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'
3
7
  import type { Multiaddr } from '@multiformats/multiaddr'
4
8
  import type { ListenOptions, IpcSocketConnectOpts, TcpSocketConnectOpts } from 'net'
5
9
 
6
10
  export type NetConfig = ListenOptions | (IpcSocketConnectOpts & TcpSocketConnectOpts)
7
11
 
8
- export function multiaddrToNetConfig (addr: Multiaddr, config: NetConfig = {}): NetConfig {
9
- const listenPath = addr.getPath()
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
10
15
 
11
- // unix socket listening
12
- if (listenPath != null) {
16
+ if (listenPath == null) {
17
+ throw new InvalidParametersError(`Multiaddr ${addr} was not a Unix address`)
18
+ }
19
+
20
+ // unix socket listening
13
21
  if (os.platform() === 'win32') {
14
22
  // Use named pipes on Windows systems.
15
23
  return { path: path.join('\\\\.\\pipe\\', listenPath) }
@@ -18,12 +26,15 @@ export function multiaddrToNetConfig (addr: Multiaddr, config: NetConfig = {}):
18
26
  }
19
27
  }
20
28
 
21
- const options = addr.toOptions()
29
+ const config = getNetConfig(addr)
30
+ const host = config.host
31
+ const port = config.port
22
32
 
23
33
  // tcp listening
24
34
  return {
25
- ...config,
26
- ...options,
27
- ipv6Only: options.family === 6
35
+ host,
36
+ port,
37
+ ipv6Only: config.type === 'ip6',
38
+ ...options
28
39
  }
29
40
  }