@libp2p/tcp 10.1.19 → 11.0.0-049bfa0fa

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.19",
3
+ "version": "11.0.0-049bfa0fa",
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",
57
- "@libp2p/utils": "^6.7.2",
58
- "@multiformats/multiaddr": "^12.4.4",
59
- "@multiformats/multiaddr-matcher": "^2.0.0",
56
+ "@libp2p/interface": "3.0.0-049bfa0fa",
57
+ "@libp2p/utils": "7.0.0-049bfa0fa",
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.2.0",
70
- "aegir": "^47.0.14",
67
+ "@libp2p/logger": "6.0.0-049bfa0fa",
68
+ "aegir": "^47.0.22",
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,230 +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 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
- }
43
-
44
- if (options.remoteAddr?.getPath() != null) {
45
- options.localAddr = options.remoteAddr
46
- }
47
-
48
- // handle socket errors
49
- socket.on('error', err => {
50
- errored = true
51
-
52
- if (!timedOut) {
53
- maConn.log.error('%s socket error - %e', direction, err)
54
- metrics?.increment({ [`${metricPrefix}error`]: true })
55
- }
56
-
57
- socket.destroy()
58
- maConn.timeline.close = Date.now()
59
- })
16
+ class TCPSocketMultiaddrConnection extends AbstractMultiaddrConnection {
17
+ private socket: Socket
60
18
 
61
- let remoteAddr: Multiaddr
19
+ constructor (init: TCPSocketMultiaddrConnectionInit) {
20
+ let remoteAddr = init.remoteAddr
62
21
 
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
- }
71
-
72
- remoteAddr = toMultiaddr(socket.remoteAddress, socket.remotePort)
73
- }
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
+ }
74
31
 
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 })
32
+ remoteAddr = ipPortToMultiaddr(init.socket.remoteAddress, init.socket.remotePort)
99
33
  }
100
34
 
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
- }
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
135
83
  }
136
84
 
137
- // we have finished writing, send the FIN message
138
- socket.end()
139
- },
85
+ this.onTransportClosed()
86
+ })
140
87
 
141
- source,
88
+ // the socket can accept more data
89
+ this.socket.on('drain', () => {
90
+ this.log('tcp drain')
142
91
 
143
- // If the remote address was passed, use it - it may have the peer ID encapsulated
144
- remoteAddr,
145
-
146
- timeline: { open: Date.now() },
92
+ this.safeDispatchEvent('drain')
93
+ })
94
+ }
147
95
 
148
- async close (options: AbortOptions = {}) {
149
- if (socket.closed) {
150
- maConn.log('the %s %s socket is already closed', direction, lOptsStr)
151
- return
152
- }
96
+ sendData (data: Uint8ArrayList): SendResult {
97
+ let sentBytes = 0
98
+ let canSendMore = true
153
99
 
154
- if (socket.destroyed) {
155
- maConn.log('the %s %s socket is already destroyed', direction, lOptsStr)
156
- return
157
- }
100
+ for (const buf of data) {
101
+ sentBytes += buf.byteLength
102
+ canSendMore = this.socket.write(buf)
158
103
 
159
- if (closePromise != null) {
160
- return closePromise.promise
104
+ if (!canSendMore) {
105
+ break
161
106
  }
107
+ }
162
108
 
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
- socket.destroy()
191
- ])
192
- } catch (err: any) {
193
- this.abort(err)
194
- } finally {
195
- closePromise.resolve()
196
- }
197
- },
109
+ return {
110
+ sentBytes,
111
+ canSendMore
112
+ }
113
+ }
198
114
 
199
- abort: (err: Error) => {
200
- maConn.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
+ }
201
119
 
202
- // the abortSignalListener may already destroyed the socket with an error
203
- socket.destroy()
120
+ this.socket.destroySoon()
204
121
 
205
- // closing a socket is always asynchronous (must wait for "close" event)
206
- // but the tests expect this to be a synchronous operation so we have to
207
- // set the close time here. the tests should be refactored to reflect
208
- // reality.
209
- maConn.timeline.close = Date.now()
210
- },
122
+ await pEvent(this.socket, 'close', options)
123
+ }
211
124
 
212
- log: options.logger.forComponent('libp2p:tcp:connection')
125
+ sendReset (): void {
126
+ this.socket.resetAndDestroy()
213
127
  }
214
128
 
215
- return maConn
216
- }
129
+ sendPause (): void {
130
+ this.socket.pause()
131
+ }
217
132
 
218
- function socketToEventTarget (obj?: any): EventTarget {
219
- const eventTarget = {
220
- addEventListener: (type: any, cb: any) => {
221
- obj.addListener(type, cb)
222
- },
223
- removeEventListener: (type: any, cb: any) => {
224
- obj.removeListener(type, cb)
225
- }
133
+ sendResume (): void {
134
+ this.socket.resume()
226
135
  }
136
+ }
227
137
 
228
- // @ts-expect-error partial implementation
229
- 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)
230
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
  }