@libp2p/tcp 9.1.5 → 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/dist/index.min.js +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -161
- package/dist/src/index.js.map +1 -1
- package/dist/src/tcp.browser.d.ts +47 -0
- package/dist/src/tcp.browser.d.ts.map +1 -0
- package/dist/src/tcp.browser.js +56 -0
- package/dist/src/tcp.browser.js.map +1 -0
- package/dist/src/tcp.d.ts +61 -0
- package/dist/src/tcp.d.ts.map +1 -0
- package/dist/src/tcp.js +190 -0
- package/dist/src/tcp.js.map +1 -0
- package/package.json +10 -2
- package/src/index.ts +4 -200
- package/src/tcp.browser.ts +68 -0
- package/src/tcp.ts +230 -0
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
|
+
}
|