@libp2p/tcp
Version:
A TCP transport for libp2p
185 lines • 6.94 kB
JavaScript
/**
* @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';
export class TCP {
opts;
metrics;
components;
log;
constructor(components, options = {}) {
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'
})
};
}
}
[transportSymbol] = true;
[Symbol.toStringTag] = '@libp2p/tcp';
[serviceCapabilities] = [
'@libp2p/transport'
];
async dial(ma, options) {
options.keepAlive = options.keepAlive ?? true;
options.noDelay = options.noDelay ?? true;
// options.signal destroys the socket before 'connect' event
const socket = await this._connect(ma, options);
let maConn;
try {
maConn = toMultiaddrConnection(socket, {
remoteAddr: ma,
socketInactivityTimeout: this.opts.outboundSocketInactivityTimeout,
socketCloseTimeout: this.opts.socketCloseTimeout,
metrics: this.metrics?.events,
logger: this.components.logger,
direction: 'outbound'
});
}
catch (err) {
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) {
this.metrics?.errors.increment({ outbound_upgrade: true });
this.log.error('error upgrading outbound connection', err);
maConn.abort(err);
throw err;
}
}
async _connect(ma, options) {
options.signal.throwIfAborted();
options.onProgress?.(new CustomProgressEvent('tcp:open-connection'));
let rawSocket;
return new Promise((resolve, reject) => {
const start = Date.now();
const cOpts = multiaddrToNetConfig(ma, {
...(this.opts.dialOpts ?? {}),
...options
});
this.log('dialing %a', ma);
rawSocket = net.connect(cOpts);
const onError = (err) => {
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 = () => {
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 = () => {
this.log('connection opened %a', ma);
this.metrics?.events.increment({ connect: true });
done();
};
const onAbort = () => {
this.log('connection aborted %a', ma);
this.metrics?.events.increment({ abort: true });
done(new AbortError());
};
const done = (err) => {
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) {
return new TCPListener({
...(this.opts.listenOpts ?? {}),
...options,
maxConnections: this.opts.maxConnections,
backlog: this.opts.backlog,
closeServerOnMaxConnections: this.opts.closeServerOnMaxConnections,
socketInactivityTimeout: this.opts.inboundSocketInactivityTimeout,
socketCloseTimeout: this.opts.socketCloseTimeout,
metrics: this.components.metrics,
logger: this.components.logger
});
}
/**
* Takes a list of `Multiaddr`s and returns only valid TCP addresses
*/
listenFilter(multiaddrs) {
return multiaddrs.filter(ma => TCPMatcher.exactMatch(ma) || ma.toString().startsWith('/unix/'));
}
/**
* Filter check for all Multiaddrs that this transport can dial
*/
dialFilter(multiaddrs) {
return this.listenFilter(multiaddrs);
}
}
//# sourceMappingURL=tcp.js.map