diff --git a/bin/client b/bin/client index 72d2d72..cbea50f 100644 --- a/bin/client +++ b/bin/client @@ -12,7 +12,7 @@ program .parse(process.argv); const filePath = program.config; -const udpTimeout = 10; +const udpTimeout = 60 * 5; const udpBindPortThreshold = 10000; const parseResult = parseClientConfig(filePath); diff --git a/lib/common/log.js b/lib/common/log.js index 01d4647..1248679 100644 --- a/lib/common/log.js +++ b/lib/common/log.js @@ -33,7 +33,15 @@ const logServerError = (error, port, serverName='A') => { }; const logWarning = (msg) => { - console.log('warning: ' + msg); + console.log('Warning: ' + msg); +}; + +const logError = (error) => { + console.log('Error: ' + error.message + ' stack: ' + error.stack); +}; + +const logSendError = (error, sender, type) => { + console.log(type + ' msg send error: ' + error.message + ' stack: ' + error.stack); }; module.exports = { @@ -43,5 +51,7 @@ module.exports = { logServerListening, logSocketConnection, logServerError, - logWarning + logWarning, + logError, + logSendError }; diff --git a/lib/common/socket.js b/lib/common/socket.js index 9b1386e..a067191 100644 --- a/lib/common/socket.js +++ b/lib/common/socket.js @@ -1,8 +1,8 @@ const net = require('net'); const dgram = require('dgram'); const TextEncoding = require('text-encoding'); -const {TunnelServerInfoType} = require('./constant'); -const {logServerListening, logServerError} = require('./log'); +const {TunnelServerInfoType, SocketType} = require('./constant'); +const {logError, logServerError, logSendError} = require('./log'); const decoder = new TextEncoding.TextDecoder('utf-8'); @@ -26,17 +26,11 @@ const createUdpSocket = (port, onListening, onError) => { return socket; }; -const createClientTunnelSocket = (tunnelSockets, uuid, socketType, - bindPort, serverPort, serverIP) => { +const createClientTunnelSocket = (uuid, socketType, bindPort, + serverPort, serverIP) => { const tunnelSocket = net.createConnection(serverPort, serverIP); handleSocketError(tunnelSocket); - tunnelSocket.on('close', () => { - if (tunnelSockets[uuid]) { - delete tunnelSockets[uuid]; - } - }); - tunnelSocket.on('connect', () => { const replyInfo = { type: TunnelServerInfoType.TUNNEL, @@ -45,14 +39,12 @@ const createClientTunnelSocket = (tunnelSockets, uuid, socketType, sendTcpInfo(tunnelSocket, replyInfo); }); - tunnelSockets[uuid] = tunnelSocket; - return tunnelSocket; }; const handleSocketError = (socket) => { socket.on('error', (e) => { - console.log(e); + logError(e); }); }; @@ -75,20 +67,36 @@ const parseMsgWithMetaData = (msg) => { } }; +const trySendTcp = (sender, msg) => { + try { + sender.write(msg); + } catch (e) { + logSendError(e, sender, SocketType.TCP); + } +}; + +const trySendUdp = (sender, msg, remotePort, remoteIP) => { + try { + sender.send(msg, 0, msg.length, remotePort, remoteIP); + } catch (e) { + logSendError(e, sender, SocketType.UDP); + } +}; + const sendTcpInfo = (sender, info, metaData) => { metaData = decodeMetaData(metaData); const msg = JSON.stringify(info) + (metaData ? '|' + metaData : ''); - sender.write(msg); + trySendTcp(sender, msg); }; const sendTcpMetaData = (sender, metaData) => { metaData = decodeMetaData(metaData); - sender.write(metaData); + trySendTcp(sender, metaData); }; const sendUdpMetaData = (sender, metaData, remotePort, remoteIP) => { metaData = decodeMetaData(metaData); - sender.send(metaData, 0, metaData.length, remotePort, remoteIP); + trySendUdp(sender, metaData, remotePort, remoteIP); }; module.exports = { diff --git a/lib/tcpClient.js b/lib/tcpClient.js index 234aee0..8c5aa32 100644 --- a/lib/tcpClient.js +++ b/lib/tcpClient.js @@ -7,14 +7,15 @@ const { sendTcpInfo, createClientTunnelSocket } = require('./common/socket'); -const {logSocketData, logSocketConnection} = require('./common/log'); +const {logSocketData, logSocketConnection, logError} = require('./common/log'); const { SocketType, TunnelClientInfoType, TunnelServerInfoType } = require('./common/constant'); -const pipeTunnelAndDataTcpSocket = (tunnelSocket, dataSocket, uuid, bindPort) => { +const pipeTunnelAndDataTcpSocket = (tunnelSocket, dataSocket, uuid, bindPort, + tunnelSockets, dataSockets) => { tunnelSocket.on('data', (data) => { logSocketData(data, 'Tunnel'); const {metaData} = parseMsgWithMetaData(data); @@ -29,10 +30,27 @@ const pipeTunnelAndDataTcpSocket = (tunnelSocket, dataSocket, uuid, bindPort) => }; sendTcpInfo(tunnelSocket, info, data); }); + tunnelSocket.on('close', () => { + if (tunnelSockets[uuid]) { + // delete tunnelSockets[uuid]; + } + if (!dataSocket.destroyed) { + dataSocket.end(); + } + }); + dataSocket.on('close', () => { + if (dataSockets[uuid]) { + // delete dataSockets[uuid]; + } + if (!tunnelSocket.destroyed) { + tunnelSocket.end(); + } + }); }; const createTcpController = (serverPort, serverIP, proxies) => { const tunnelSockets = {}; + const dataSockets = {}; // TODO: set interval time to reconnect const tcpControlSocket = net.createConnection(serverPort, serverIP); @@ -42,18 +60,22 @@ const createTcpController = (serverPort, serverIP, proxies) => { tcpControlSocket.on('data', (data) => { logSocketData(data, 'TCP Control'); const {info} = parseMsgWithMetaData(data); + const uuid = info.uuid; if (info.type === TunnelClientInfoType.CREATE_TUNNEL) { const proxy = _.find(proxies, {remotePort: info.bindPort}); if (!proxy || !proxy.localPort) { return; } - const tunnelSocket = createClientTunnelSocket(tunnelSockets, info.uuid, - SocketType.TCP, info.bindPort, serverPort, serverIP); + const tunnelSocket = createClientTunnelSocket(uuid, SocketType.TCP, + info.bindPort, serverPort, serverIP); + tunnelSockets[uuid] = tunnelSocket; const dataSocket = net.createConnection(proxy.localPort, '127.0.0.1'); + dataSockets[uuid] = dataSocket; + dataSocket.on('error', (e) => {logError(e);}); pipeTunnelAndDataTcpSocket(tunnelSocket, dataSocket, - info.uuid, info.bindPort); + uuid, info.bindPort, tunnelSockets, dataSockets); } }); diff --git a/lib/tunnelServer.js b/lib/tunnelServer.js index 2a0300c..fb8b214 100644 --- a/lib/tunnelServer.js +++ b/lib/tunnelServer.js @@ -76,6 +76,10 @@ const createTunnelServer = (eventEmitter, listenPort) => { delete tunnelSockets[uuid]; if (info.socketType === SocketType.UDP) { delete remoteUdpSocketInfos[uuid]; + } else { + if (remoteTcpSocketInfos[uuid]) { + remoteTcpSocketInfos[uuid].socket.end(); + } } }); if (info.socketType === SocketType.TCP) { @@ -122,7 +126,9 @@ const createTunnelServer = (eventEmitter, listenPort) => { remoteSocket.on('close', () => { delete remoteTcpSocketInfos[uuid]; const tunnelSocket = tunnelSockets[uuid]; - tunnelSocket.end(); + if (tunnelSocket && !tunnelSocket.destroyed) { + tunnelSocket.end(); + } // No need to delete tunnelSocket from tunnelSockets, // it will be performed by tunnelSocket's onClose callback. }); diff --git a/lib/udpClient.js b/lib/udpClient.js index 19919f6..ac7595b 100644 --- a/lib/udpClient.js +++ b/lib/udpClient.js @@ -20,7 +20,7 @@ let randomPort = 20000; // TODO: need to reduce the number of parameter of pipeTunnelAndDataUdpSocket... const pipeTunnelAndDataUdpSocket = (tunnelSocket, dataSocket, uuid, bindPort, localPort, - dataSocketInfos) => { + tunnelSockets, dataSocketInfos) => { tunnelSocket.on('data', (data) => { logSocketData(data, 'Tunnel'); @@ -38,6 +38,17 @@ const pipeTunnelAndDataUdpSocket = (tunnelSocket, dataSocket, updateDataSocketTime(uuid, dataSocketInfos); sendTcpInfo(tunnelSocket, info, msg); }); + tunnelSocket.on('close', () => { + if (tunnelSockets[uuid]) { + delete tunnelSockets[uuid]; + } + if (dataSocketInfos[uuid]) { + const dataSocketInfo = dataSocketInfos[uuid]; + dataSocketInfo.socket.close(); + dataSocketInfo.timeoutController.clear(); + delete dataSocketInfos[uuid]; + } + }); }; const getNextRandomPort = () => { @@ -81,8 +92,12 @@ const dataSocketTimeout = (uuid, dataSocketInfos, tunnelSockets, udpTimeout) => timeout = setTimeout(timeoutCb, udpTimeout * 1000); }; + const clear = () => { + clearTimeout(timeout); + }; + return { - timeout, resetTimeout + timeout, resetTimeout, clear }; }; @@ -90,7 +105,7 @@ const createUdpController = (serverPort, serverIP, proxies, udpTimeout) => { const tunnelSockets = {}; // TODO: set interval time to reconnect - // item: {socket, timeout} + // item: {socket, lastTime, timeoutController} const dataSocketInfos = {}; const udpControlSocket = net.createConnection(serverPort, serverIP); @@ -107,12 +122,13 @@ const createUdpController = (serverPort, serverIP, proxies, udpTimeout) => { return; } - const tunnelSocket = createClientTunnelSocket(tunnelSockets, uuid, - SocketType.UDP, info.bindPort, serverPort, serverIP); + const tunnelSocket = createClientTunnelSocket(uuid, SocketType.UDP, + info.bindPort, serverPort, serverIP); + tunnelSockets[uuid] = tunnelSocket; const dataSocket = createUdpDataSocket(); pipeTunnelAndDataUdpSocket(tunnelSocket, dataSocket, uuid, - info.bindPort, proxy.localPort, dataSocketInfos); + info.bindPort, proxy.localPort, tunnelSockets, dataSocketInfos); dataSocketInfos[uuid] = { socket: dataSocket,