@libp2p/tcp 9.1.5-e211b46cc → 9.1.6

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/src/tcp.ts ADDED
@@ -0,0 +1,230 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * A [libp2p transport](https://docs.libp2p.io/concepts/transports/overview/) based on the TCP networking stack.
5
+ *
6
+ * @example
7
+ *
8
+ * ```TypeScript
9
+ * import { createLibp2p } from 'libp2p'
10
+ * import { tcp } from '@libp2p/tcp'
11
+ * import { multiaddr } from '@multiformats/multiaddr'
12
+ *
13
+ * const node = await createLibp2p({
14
+ * transports: [
15
+ * tcp()
16
+ * ]
17
+ * })
18
+ *
19
+ * const ma = multiaddr('/ip4/123.123.123.123/tcp/1234')
20
+ *
21
+ * // dial a TCP connection, timing out after 10 seconds
22
+ * const connection = await node.dial(ma, {
23
+ * signal: AbortSignal.timeout(10_000)
24
+ * })
25
+ *
26
+ * // use connection...
27
+ * ```
28
+ */
29
+
30
+ import net from 'net'
31
+ import { AbortError, CodeError, serviceCapabilities, transportSymbol } from '@libp2p/interface'
32
+ import * as mafmt from '@multiformats/mafmt'
33
+ import { CustomProgressEvent } from 'progress-events'
34
+ import { CODE_CIRCUIT, CODE_P2P, CODE_UNIX } from './constants.js'
35
+ import { TCPListener } from './listener.js'
36
+ import { toMultiaddrConnection } from './socket-to-conn.js'
37
+ import { multiaddrToNetConfig } from './utils.js'
38
+ import type { TCPComponents, TCPCreateListenerOptions, TCPDialEvents, TCPDialOptions, TCPMetrics, TCPOptions } from './index.js'
39
+ import type { Logger, Connection, Transport, Listener } from '@libp2p/interface'
40
+ import type { Multiaddr } from '@multiformats/multiaddr'
41
+ import type { Socket, IpcSocketConnectOpts, TcpSocketConnectOpts } from 'net'
42
+
43
+ export class TCP implements Transport<TCPDialEvents> {
44
+ private readonly opts: TCPOptions
45
+ private readonly metrics?: TCPMetrics
46
+ private readonly components: TCPComponents
47
+ private readonly log: Logger
48
+
49
+ constructor (components: TCPComponents, options: TCPOptions = {}) {
50
+ this.log = components.logger.forComponent('libp2p:tcp')
51
+ this.opts = options
52
+ this.components = components
53
+
54
+ if (components.metrics != null) {
55
+ this.metrics = {
56
+ dialerEvents: components.metrics.registerCounterGroup('libp2p_tcp_dialer_events_total', {
57
+ label: 'event',
58
+ help: 'Total count of TCP dialer events by type'
59
+ })
60
+ }
61
+ }
62
+ }
63
+
64
+ readonly [transportSymbol] = true
65
+
66
+ readonly [Symbol.toStringTag] = '@libp2p/tcp'
67
+
68
+ readonly [serviceCapabilities]: string[] = [
69
+ '@libp2p/transport'
70
+ ]
71
+
72
+ async dial (ma: Multiaddr, options: TCPDialOptions): Promise<Connection> {
73
+ options.keepAlive = options.keepAlive ?? true
74
+ options.noDelay = options.noDelay ?? true
75
+
76
+ // options.signal destroys the socket before 'connect' event
77
+ const socket = await this._connect(ma, options)
78
+
79
+ // Avoid uncaught errors caused by unstable connections
80
+ socket.on('error', err => {
81
+ this.log('socket error', err)
82
+ })
83
+
84
+ const maConn = toMultiaddrConnection(socket, {
85
+ remoteAddr: ma,
86
+ socketInactivityTimeout: this.opts.outboundSocketInactivityTimeout,
87
+ socketCloseTimeout: this.opts.socketCloseTimeout,
88
+ metrics: this.metrics?.dialerEvents,
89
+ logger: this.components.logger
90
+ })
91
+
92
+ const onAbort = (): void => {
93
+ maConn.close().catch(err => {
94
+ this.log.error('Error closing maConn after abort', err)
95
+ })
96
+ }
97
+ options.signal?.addEventListener('abort', onAbort, { once: true })
98
+
99
+ this.log('new outbound connection %s', maConn.remoteAddr)
100
+ const conn = await options.upgrader.upgradeOutbound(maConn)
101
+ this.log('outbound connection %s upgraded', maConn.remoteAddr)
102
+
103
+ options.signal?.removeEventListener('abort', onAbort)
104
+
105
+ if (options.signal?.aborted === true) {
106
+ conn.close().catch(err => {
107
+ this.log.error('Error closing conn after abort', err)
108
+ })
109
+
110
+ throw new AbortError()
111
+ }
112
+
113
+ return conn
114
+ }
115
+
116
+ async _connect (ma: Multiaddr, options: TCPDialOptions): Promise<Socket> {
117
+ options.signal?.throwIfAborted()
118
+ options.onProgress?.(new CustomProgressEvent('tcp:open-connection'))
119
+
120
+ return new Promise<Socket>((resolve, reject) => {
121
+ const start = Date.now()
122
+ const cOpts = multiaddrToNetConfig(ma, {
123
+ ...(this.opts.dialOpts ?? {}),
124
+ ...options
125
+ }) as (IpcSocketConnectOpts & TcpSocketConnectOpts)
126
+
127
+ this.log('dialing %a', ma)
128
+ const rawSocket = net.connect(cOpts)
129
+
130
+ const onError = (err: Error): void => {
131
+ const cOptsStr = cOpts.path ?? `${cOpts.host ?? ''}:${cOpts.port}`
132
+ err.message = `connection error ${cOptsStr}: ${err.message}`
133
+ this.metrics?.dialerEvents.increment({ error: true })
134
+
135
+ done(err)
136
+ }
137
+
138
+ const onTimeout = (): void => {
139
+ this.log('connection timeout %a', ma)
140
+ this.metrics?.dialerEvents.increment({ timeout: true })
141
+
142
+ const err = new CodeError(`connection timeout after ${Date.now() - start}ms`, 'ERR_CONNECT_TIMEOUT')
143
+ // Note: this will result in onError() being called
144
+ rawSocket.emit('error', err)
145
+ }
146
+
147
+ const onConnect = (): void => {
148
+ this.log('connection opened %a', ma)
149
+ this.metrics?.dialerEvents.increment({ connect: true })
150
+ done()
151
+ }
152
+
153
+ const onAbort = (): void => {
154
+ this.log('connection aborted %a', ma)
155
+ this.metrics?.dialerEvents.increment({ abort: true })
156
+ rawSocket.destroy()
157
+ done(new AbortError())
158
+ }
159
+
160
+ const done = (err?: Error): void => {
161
+ rawSocket.removeListener('error', onError)
162
+ rawSocket.removeListener('timeout', onTimeout)
163
+ rawSocket.removeListener('connect', onConnect)
164
+
165
+ if (options.signal != null) {
166
+ options.signal.removeEventListener('abort', onAbort)
167
+ }
168
+
169
+ if (err != null) {
170
+ reject(err); return
171
+ }
172
+
173
+ resolve(rawSocket)
174
+ }
175
+
176
+ rawSocket.on('error', onError)
177
+ rawSocket.on('timeout', onTimeout)
178
+ rawSocket.on('connect', onConnect)
179
+
180
+ if (options.signal != null) {
181
+ options.signal.addEventListener('abort', onAbort)
182
+ }
183
+ })
184
+ }
185
+
186
+ /**
187
+ * Creates a TCP listener. The provided `handler` function will be called
188
+ * anytime a new incoming Connection has been successfully upgraded via
189
+ * `upgrader.upgradeInbound`.
190
+ */
191
+ createListener (options: TCPCreateListenerOptions): Listener {
192
+ return new TCPListener({
193
+ ...(this.opts.listenOpts ?? {}),
194
+ ...options,
195
+ maxConnections: this.opts.maxConnections,
196
+ backlog: this.opts.backlog,
197
+ closeServerOnMaxConnections: this.opts.closeServerOnMaxConnections,
198
+ socketInactivityTimeout: this.opts.inboundSocketInactivityTimeout,
199
+ socketCloseTimeout: this.opts.socketCloseTimeout,
200
+ metrics: this.components.metrics,
201
+ logger: this.components.logger
202
+ })
203
+ }
204
+
205
+ /**
206
+ * Takes a list of `Multiaddr`s and returns only valid TCP addresses
207
+ */
208
+ listenFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
209
+ multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
210
+
211
+ return multiaddrs.filter(ma => {
212
+ if (ma.protoCodes().includes(CODE_CIRCUIT)) {
213
+ return false
214
+ }
215
+
216
+ if (ma.protoCodes().includes(CODE_UNIX)) {
217
+ return true
218
+ }
219
+
220
+ return mafmt.TCP.matches(ma.decapsulateCode(CODE_P2P))
221
+ })
222
+ }
223
+
224
+ /**
225
+ * Filter check for all Multiaddrs that this transport can dial
226
+ */
227
+ dialFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
228
+ return this.listenFilter(multiaddrs)
229
+ }
230
+ }