shadowsocks-single
Version:
Yet another shadowsocks implementation for nodejs to help you bypass firewalls.
435 lines (340 loc) • 10.7 kB
JavaScript
;
exports.__esModule = true;
exports.usernamePasswordAuthetication = usernamePasswordAuthetication;
exports.startServer = startServer;
var _ip = require('ip');
var _ip2 = _interopRequireDefault(_ip);
var _net = require('net');
var _utils = require('./utils');
var _logger = require('./logger');
var _encryptor = require('./encryptor');
var _pacServer = require('./pacServer');
var _createUDPRelay = require('./createUDPRelay');
var _createUDPRelay2 = _interopRequireDefault(_createUDPRelay);
var _auth = require('./auth');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var NAME = 'ss_local';
var logger = void 0;
function handleMethod(connection, data, authInfo) {
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
var forceAuth = authInfo.forceAuth;
var buf = new Buffer(2);
var method = -1;
if (forceAuth && data.indexOf(0x02, 2) >= 0) {
method = 2;
}
if (!forceAuth && data.indexOf(0x00, 2) >= 0) {
method = 0;
}
// allow `no authetication` or any usename/password
if (method === -1) {
// logger.warn(`unsupported method: ${data.toString('hex')}`);
buf.writeUInt16BE(0x05FF);
connection.write(buf);
connection.end();
return -1;
}
buf.writeUInt16BE(0x0500);
connection.write(buf);
return method === 0 ? 1 : 3;
}
function fetchUsernamePassword(data) {
// suppose all VER is 0x01
if (!(data instanceof Buffer)) {
return null;
}
var ulen = data[1];
var username = data.slice(2, ulen + 2).toString('ascii');
var plenStart = ulen + 2;
var plen = data[plenStart];
var password = data.slice(plenStart + 1, plenStart + 1 + plen).toString('ascii');
return { username: username, password: password };
}
function responseAuth(success, connection) {
var buf = new Buffer(2);
var toWrite = success ? 0x0100 : 0x0101;
var nextProcedure = success ? 2 : -1;
buf.writeUInt16BE(toWrite);
connection.write(buf);
connection.end();
return nextProcedure;
}
function usernamePasswordAuthetication(connection, data, authInfo) {
// +----+------+----------+------+----------+
// |VER | ULEN | UNAME | PLEN | PASSWD |
// +----+------+----------+------+----------+
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
// +----+------+----------+------+----------+
var usernamePassword = fetchUsernamePassword(data);
if (!usernamePassword) {
return responseAuth(false, connection);
}
var username = usernamePassword.username,
password = usernamePassword.password;
if (!(0, _auth.validate)(authInfo, username, password)) {
return responseAuth(false, connection);
}
return responseAuth(true, connection);
}
function handleRequest(connection, data, _ref, dstInfo, onConnect, onDestroy, isClientConnected) {
var serverAddr = _ref.serverAddr,
serverPort = _ref.serverPort,
password = _ref.password,
method = _ref.method,
localAddr = _ref.localAddr,
localPort = _ref.localPort,
localAddrIPv6 = _ref.localAddrIPv6;
var cmd = data[1];
var clientOptions = {
port: serverPort,
host: serverAddr
};
var isUDPRelay = cmd === 0x03;
var repBuf = void 0;
var tmp = null;
var decipher = null;
var decipheredData = null;
var cipher = null;
var cipheredData = null;
if (cmd !== 0x01 && !isUDPRelay) {
logger.warn('unsupported cmd: ' + cmd);
return {
stage: -1
};
}
// prepare data
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
if (isUDPRelay) {
var isUDP4 = dstInfo.atyp === 1;
repBuf = new Buffer(4);
repBuf.writeUInt32BE(isUDP4 ? 0x05000001 : 0x05000004);
tmp = new Buffer(2);
tmp.writeUInt16BE(localPort);
repBuf = Buffer.concat([repBuf, _ip2.default.toBuffer(isUDP4 ? localAddr : localAddrIPv6), tmp]);
connection.write(repBuf);
return {
stage: -1
};
}
logger.verbose('connecting: ' + dstInfo.dstAddr.toString('utf8') + (':' + dstInfo.dstPort.readUInt16BE()));
repBuf = new Buffer(10);
repBuf.writeUInt32BE(0x05000001);
repBuf.writeUInt32BE(0x00000000, 4, 4);
repBuf.writeUInt16BE(0, 8, 2);
tmp = (0, _encryptor.createCipher)(password, method, data.slice(3)); // skip VER, CMD, RSV
cipher = tmp.cipher;
cipheredData = tmp.data;
// connect
var clientToRemote = (0, _net.connect)(clientOptions, function () {
onConnect();
});
clientToRemote.on('data', function (remoteData) {
if (!decipher) {
tmp = (0, _encryptor.createDecipher)(password, method, remoteData);
if (!tmp) {
logger.warn(NAME + ' get invalid msg');
onDestroy();
return;
}
decipher = tmp.decipher;
decipheredData = tmp.data;
} else {
decipheredData = decipher.update(remoteData);
}
if (isClientConnected()) {
(0, _utils.writeOrPause)(clientToRemote, connection, decipheredData);
} else {
clientToRemote.destroy();
}
});
clientToRemote.on('drain', function () {
connection.resume();
});
clientToRemote.on('end', function () {
connection.end();
});
clientToRemote.on('error', function (e) {
logger.warn('ssLocal error happened in clientToRemote when' + (' connecting to ' + (0, _utils.getDstStr)(dstInfo) + ': ' + e.message));
onDestroy();
});
clientToRemote.on('close', function (e) {
if (e) {
connection.destroy();
} else {
connection.end();
}
});
// write
connection.write(repBuf);
(0, _utils.writeOrPause)(connection, clientToRemote, cipheredData);
return {
stage: 2,
cipher: cipher,
clientToRemote: clientToRemote
};
}
function handleConnection(config, connection) {
var authInfo = config.authInfo;
var stage = 0;
var clientToRemote = void 0;
var tmp = void 0;
var cipher = void 0;
var dstInfo = void 0;
var remoteConnected = false;
var clientConnected = true;
var timer = null;
connection.on('data', function (data) {
switch (stage) {
case 0:
stage = handleMethod(connection, data, authInfo);
break;
case 1:
dstInfo = (0, _utils.getDstInfo)(data);
if (!dstInfo) {
logger.warn('Failed to get \'dstInfo\' from parsing data: ' + data);
connection.destroy();
return;
}
tmp = handleRequest(connection, data, config, dstInfo, function () {
// after connected
remoteConnected = true;
}, function () {
// get invalid msg or err happened
if (remoteConnected) {
remoteConnected = false;
clientToRemote.destroy();
}
if (clientConnected) {
clientConnected = false;
connection.destroy();
}
}, function () {
return clientConnected;
});
stage = tmp.stage;
if (stage === 2) {
clientToRemote = tmp.clientToRemote;
cipher = tmp.cipher;
} else {
// udp relay
clientConnected = false;
connection.end();
}
break;
case 2:
tmp = cipher.update(data);
(0, _utils.writeOrPause)(connection, clientToRemote, tmp);
break;
case 3:
// rfc 1929 username/password authetication
stage = usernamePasswordAuthetication(connection, data, authInfo);
break;
default:
}
});
connection.on('drain', function () {
if (remoteConnected) {
clientToRemote.resume();
}
});
connection.on('end', function () {
clientConnected = false;
if (remoteConnected) {
clientToRemote.end();
}
});
connection.on('close', function (e) {
if (timer) {
clearTimeout(timer);
}
clientConnected = false;
if (remoteConnected) {
if (e) {
clientToRemote.destroy();
} else {
clientToRemote.end();
}
}
});
connection.on('error', function (e) {
logger.warn(NAME + ' error happened in client connection: ' + e.message);
});
timer = setTimeout(function () {
if (clientConnected) {
connection.destroy();
}
if (remoteConnected) {
clientToRemote.destroy();
}
}, config.timeout * 1000);
}
function closeAll() {
(0, _utils.closeSilently)(this.server);
// closeSilently(this.pacServer);
// this.udpRelay.close();
if (this.httpProxyServer) {
this.httpProxyServer.close();
}
}
function createServer(config) {
var server = (0, _net.createServer)(handleConnection.bind(null, config));
// const udpRelay = createUDPRelay(config, false, logger);
// const pacServer = createPACServer(config, logger);
server.on('close', function () {
logger.warn(NAME + ' server closed');
});
server.on('error', function (e) {
config.listenError(e);
logger.error(NAME + ' server error: ' + e.message);
});
// 此处开始random port 10000-20000
config.localPort = 10000 + Math.floor(Math.random() * 10000);
server.listen(config.localPort, function () {
config.listenSuccess({ port: config.localPort, server: server });
});
logger.verbose(NAME + ' is listening on ' + config.localAddr + ':' + config.localPort);
return {
server: server,
// udpRelay,
// pacServer,
closeAll: closeAll
};
}
// eslint-disable-next-line
function startServer(config) {
var willLogToConsole = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
logger = logger || (0, _logger.createLogger)(config.level, _logger.LOG_NAMES.LOCAL, willLogToConsole);
var _createAuthInfo = (0, _auth.createAuthInfo)(config),
info = _createAuthInfo.info,
warn = _createAuthInfo.warn,
error = _createAuthInfo.error;
if (error) {
logger.error(NAME + ' error: ' + error);
return null;
}
if (warn) {
logger.warn(NAME + ': ' + warn);
}
return createServer(Object.assign({}, config, {
authInfo: info
}));
}
if (module === require.main) {
process.on('message', function (config) {
logger = (0, _logger.createLogger)(config.level, _logger.LOG_NAMES.LOCAL, false);
startServer(config, false);
});
process.on('uncaughtException', function (err) {
logger.error(NAME + ' uncaughtException: ' + err.stack + ' ', (0, _utils.createSafeAfterHandler)(logger, function () {
process.exit(1);
}));
});
}