Skip to content

Commit

Permalink
fix: fix the bug of unhandled socket closing
Browse files Browse the repository at this point in the history
  • Loading branch information
purpose233 committed May 22, 2019
1 parent 20c9987 commit d12f030
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 31 deletions.
2 changes: 1 addition & 1 deletion bin/client
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 12 additions & 2 deletions lib/common/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -43,5 +51,7 @@ module.exports = {
logServerListening,
logSocketConnection,
logServerError,
logWarning
logWarning,
logError,
logSendError
};
40 changes: 24 additions & 16 deletions lib/common/socket.js
Original file line number Diff line number Diff line change
@@ -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');

Expand All @@ -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,
Expand All @@ -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);
});
};

Expand All @@ -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 = {
Expand Down
32 changes: 27 additions & 5 deletions lib/tcpClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}
});

Expand Down
8 changes: 7 additions & 1 deletion lib/tunnelServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
});
Expand Down
28 changes: 22 additions & 6 deletions lib/udpClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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 = () => {
Expand Down Expand Up @@ -81,16 +92,20 @@ const dataSocketTimeout = (uuid, dataSocketInfos, tunnelSockets, udpTimeout) =>
timeout = setTimeout(timeoutCb, udpTimeout * 1000);
};

const clear = () => {
clearTimeout(timeout);
};

return {
timeout, resetTimeout
timeout, resetTimeout, clear
};
};

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);
Expand All @@ -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,
Expand Down

0 comments on commit d12f030

Please sign in to comment.