@libp2p/tcp
Version:
A TCP transport for libp2p
216 lines (185 loc) • 6.93 kB
text/typescript
/**
* @packageDocumentation
*
* A [libp2p transport](https://docs.libp2p.io/concepts/transports/overview/) based on the TCP networking stack.
*
* @example
*
* ```TypeScript
* import { createLibp2p } from 'libp2p'
* import { tcp } from '@libp2p/tcp'
* import { multiaddr } from '@multiformats/multiaddr'
*
* const node = await createLibp2p({
* transports: [
* tcp()
* ]
* })
*
* const ma = multiaddr('/ip4/123.123.123.123/tcp/1234')
*
* // dial a TCP connection, timing out after 10 seconds
* const connection = await node.dial(ma, {
* signal: AbortSignal.timeout(10_000)
* })
*
* // use connection...
* ```
*/
import net from 'net'
import { AbortError, TimeoutError, serviceCapabilities, transportSymbol } from '@libp2p/interface'
import { TCP as TCPMatcher } from '@multiformats/multiaddr-matcher'
import { CustomProgressEvent } from 'progress-events'
import { TCPListener } from './listener.js'
import { toMultiaddrConnection } from './socket-to-conn.js'
import { multiaddrToNetConfig } from './utils.js'
import type { TCPComponents, TCPCreateListenerOptions, TCPDialEvents, TCPDialOptions, TCPMetrics, TCPOptions } from './index.js'
import type { Logger, Connection, Transport, Listener, MultiaddrConnection } from '@libp2p/interface'
import type { Multiaddr } from '@multiformats/multiaddr'
import type { Socket, IpcSocketConnectOpts, TcpSocketConnectOpts } from 'net'
export class TCP implements Transport<TCPDialEvents> {
private readonly opts: TCPOptions
private readonly metrics?: TCPMetrics
private readonly components: TCPComponents
private readonly log: Logger
constructor (components: TCPComponents, options: TCPOptions = {}) {
this.log = components.logger.forComponent('libp2p:tcp')
this.opts = options
this.components = components
if (components.metrics != null) {
this.metrics = {
events: components.metrics.registerCounterGroup('libp2p_tcp_dialer_events_total', {
label: 'event',
help: 'Total count of TCP dialer events by type'
}),
errors: components.metrics.registerCounterGroup('libp2p_tcp_dialer_errors_total', {
label: 'event',
help: 'Total count of TCP dialer events by type'
})
}
}
}
readonly [transportSymbol] = true
readonly [Symbol.toStringTag] = '@libp2p/tcp'
readonly [serviceCapabilities]: string[] = [
'@libp2p/transport'
]
async dial (ma: Multiaddr, options: TCPDialOptions): Promise<Connection> {
options.keepAlive = options.keepAlive ?? true
options.noDelay = options.noDelay ?? true
options.allowHalfOpen = options.allowHalfOpen ?? false
// options.signal destroys the socket before 'connect' event
const socket = await this._connect(ma, options)
let maConn: MultiaddrConnection
try {
maConn = toMultiaddrConnection({
socket,
inactivityTimeout: this.opts.outboundSocketInactivityTimeout,
metrics: this.metrics?.events,
direction: 'outbound',
remoteAddr: ma,
log: this.log.newScope('connection')
})
} catch (err: any) {
this.metrics?.errors.increment({ outbound_to_connection: true })
socket.destroy(err)
throw err
}
try {
this.log('new outbound connection %s', maConn.remoteAddr)
return await options.upgrader.upgradeOutbound(maConn, options)
} catch (err: any) {
this.metrics?.errors.increment({ outbound_upgrade: true })
this.log.error('error upgrading outbound connection - %e', err)
maConn.abort(err)
throw err
}
}
async _connect (ma: Multiaddr, options: TCPDialOptions): Promise<Socket> {
options.signal.throwIfAborted()
options.onProgress?.(new CustomProgressEvent('tcp:open-connection'))
let rawSocket: Socket
return new Promise<Socket>((resolve, reject) => {
const start = Date.now()
const cOpts = multiaddrToNetConfig(ma, {
...(this.opts.dialOpts ?? {}),
...options
}) as (IpcSocketConnectOpts & TcpSocketConnectOpts)
this.log('dialing %a with opts %o', ma, cOpts)
rawSocket = net.connect(cOpts)
const onError = (err: Error): void => {
this.log.error('dial to %a errored - %e', ma, err)
const cOptsStr = cOpts.path ?? `${cOpts.host ?? ''}:${cOpts.port}`
err.message = `connection error ${cOptsStr}: ${err.message}`
this.metrics?.events.increment({ error: true })
done(err)
}
const onTimeout = (): void => {
this.log('connection timeout %a', ma)
this.metrics?.events.increment({ timeout: true })
const err = new TimeoutError(`Connection timeout after ${Date.now() - start}ms`)
// Note: this will result in onError() being called
rawSocket.emit('error', err)
}
const onConnect = (): void => {
this.log('connection opened %a', ma)
this.metrics?.events.increment({ connect: true })
done()
}
const onAbort = (): void => {
this.log('connection aborted %a', ma)
this.metrics?.events.increment({ abort: true })
done(new AbortError())
}
const done = (err?: Error): void => {
rawSocket.removeListener('error', onError)
rawSocket.removeListener('timeout', onTimeout)
rawSocket.removeListener('connect', onConnect)
if (options.signal != null) {
options.signal.removeEventListener('abort', onAbort)
}
if (err != null) {
reject(err); return
}
resolve(rawSocket)
}
rawSocket.on('error', onError)
rawSocket.on('timeout', onTimeout)
rawSocket.on('connect', onConnect)
options.signal.addEventListener('abort', onAbort)
})
.catch(err => {
rawSocket?.destroy()
throw err
})
}
/**
* Creates a TCP listener. The provided `handler` function will be called
* anytime a new incoming Connection has been successfully upgraded via
* `upgrader.upgradeInbound`.
*/
createListener (options: TCPCreateListenerOptions): Listener {
return new TCPListener({
...(this.opts.listenOpts ?? {}),
...options,
maxConnections: this.opts.maxConnections,
backlog: this.opts.backlog,
closeServerOnMaxConnections: this.opts.closeServerOnMaxConnections,
inactivityTimeout: this.opts.inboundSocketInactivityTimeout,
metrics: this.components.metrics,
logger: this.components.logger
})
}
/**
* Takes a list of `Multiaddr`s and returns only valid TCP addresses
*/
listenFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
return multiaddrs.filter(ma => TCPMatcher.exactMatch(ma) || ma.toString().startsWith('/unix/'))
}
/**
* Filter check for all Multiaddrs that this transport can dial
*/
dialFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
return this.listenFilter(multiaddrs)
}
}