UNPKG

@cliz/inlets

Version:
131 lines (130 loc) 5.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const cli_1 = require("@cliz/cli"); const net = require("net"); const debug = require('debug')('inlets:tunnel:tcp'); const logger = cli_1.doreamon.logger.getLogger('tunnel:tcp '); async function createServer(ctx, options) { const { containerId, domain } = options; const container = ctx.container.get(containerId); if (!container) { throw new Error(`unknown container with id: ${containerId}`); } if (!container.wsSocket) { throw new Error(`container wsSocket is required with id: ${containerId}`); } const sourcePort = container.sourcePort || await cli_1.api.network.getAvailablePort(); ctx.container.set(containerId, 'sourcePort', sourcePort); container.wsSocket.emit('tcp:ready', { host: domain, port: sourcePort, }); createSourceTCPServer(domain, sourcePort, (server) => { container.sourceServer = server; }, (sourceSocket) => { ctx.container.registerRequest(containerId, sourceSocket.requestId, sourceSocket); const adapter = container.wsSocket.adapter; const useNewProtocol = !!container.wsSocket.useNewProtocol; if (useNewProtocol && adapter) { const streamId = `${containerId}:${sourceSocket.requestId}`; setupTCPStreamOverWebSocket(ctx, containerId, streamId, sourceSocket, adapter, container.wsSocket); } container.wsSocket.emit('tcp:connect', { id: containerId, requestId: sourceSocket.requestId, ip: sourceSocket.remoteAddress, }); }); } exports.default = createServer; function createSourceTCPServer(domain, port, onServerCreate, onSocketConnect) { const server = net.createServer(socket => { const requestId = cli_1.doreamon.uuid(); socket.requestId = requestId; logger.info(`[tunnel:tcp ][user][request][start] request id: ${socket.requestId}, ip: ${socket.remoteAddress}`); socket.on('error', error => { logger.error(`socket error:`, error); }); socket.on('end', () => { logger.info(`[tunnel:tcp ][user][request][end] request id: ${socket.requestId}, ip: ${socket.remoteAddress}`); }); onSocketConnect(socket); }); server.listen(port, '0.0.0.0', () => { logger.info(`[tunnel:tcp ] listen at 0.0.0.0:${port}`); onServerCreate(server); }); } function setupTCPStreamOverWebSocket(ctx, containerId, streamId, sourceSocket, adapter, wsSocket) { const container = ctx.container.get(containerId); const clientId = (container === null || container === void 0 ? void 0 : container.clientId) || null; ctx.trafficStats.addConnection(clientId); sourceSocket.on('data', async (data) => { try { if (!ctx.bandwidthLimiter.checkUpload(clientId, data.length)) { logger.warn(`[${streamId}] Upload bandwidth limit exceeded for client: ${clientId}`); await new Promise(resolve => setTimeout(resolve, 100)); if (!ctx.bandwidthLimiter.checkUpload(clientId, data.length)) { logger.error(`[${streamId}] Upload bandwidth limit still exceeded, dropping data`); return; } } ctx.trafficStats.addUploadBytes(clientId, data.length); await adapter.sendTCPData(streamId, data); } catch (error) { logger.error(`[${streamId}] Failed to send TCP data over WebSocket:`, error); sourceSocket.destroy(); } }); const unsubscribe = adapter.onTCPData(async (id, data) => { if (id !== streamId) return; try { if (!ctx.bandwidthLimiter.checkDownload(clientId, data.length)) { logger.warn(`[${streamId}] Download bandwidth limit exceeded for client: ${clientId}`); await new Promise(resolve => setTimeout(resolve, 100)); if (!ctx.bandwidthLimiter.checkDownload(clientId, data.length)) { logger.error(`[${streamId}] Download bandwidth limit still exceeded, dropping data`); return; } } ctx.trafficStats.addDownloadBytes(clientId, data.length); if (sourceSocket.writable) { const writeResult = sourceSocket.write(data); if (!writeResult) { sourceSocket.once('drain', () => { debug(`[${streamId}] Source socket drained`); }); } } } catch (error) { logger.error(`[${streamId}] Failed to write to source socket:`, error); sourceSocket.destroy(); } }); sourceSocket.on('close', () => { debug(`[${streamId}] Source socket closed, cleaning up`); const statsInfo = ctx.trafficStats.formatStats(clientId); logger.info(`[tunnel:tcp ][${streamId}] connection closed - Traffic Stats: ${statsInfo}`); unsubscribe === null || unsubscribe === void 0 ? void 0 : unsubscribe(); }); sourceSocket.on('error', (error) => { logger.error(`[${streamId}] Source socket error:`, error); }); } function destroy(tcpSocket) { const data = [ 'HTTP/1.1 404 Not Found', 'Content-Type: text/plain; charset=utf-8', 'Content-Length: 9', `Date: ${new Date().toUTCString()}`, 'Connection: keep-alive', 'Keep-Alive: timeout=5', '', 'Not Found' ]; tcpSocket.write(data.join('\r\n')); tcpSocket.destroy(); }