UNPKG

shadowsocks-single

Version:

Yet another shadowsocks implementation for nodejs to help you bypass firewalls.

330 lines (261 loc) 9.33 kB
'use strict'; exports.__esModule = true; exports.resolveServerAddr = resolveServerAddr; exports.getConfig = getConfig; exports.startSSLocal = startSSLocal; exports.default = client; var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _minimist = require('minimist'); var _minimist2 = _interopRequireDefault(_minimist); var _child_process = require('child_process'); var _fs = require('fs'); var _ip = require('ip'); var _dns = require('dns'); var _defaultConfig = require('./defaultConfig'); var _defaultConfig2 = _interopRequireDefault(_defaultConfig); var _package = require('../package.json'); var _ssLocal = require('./ssLocal'); var ssLocal = _interopRequireWildcard(_ssLocal); var _ssServer = require('./ssServer'); var ssServer = _interopRequireWildcard(_ssServer); var _pid = require('./pid'); var _gfwlistUtils = require('./gfwlistUtils'); var _utils = require('./utils'); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var PROXY_ARGUMENT_PAIR = { c: 'configFilePath', s: 'serverAddr', p: 'serverPort', pac_port: 'pacServerPort', l: 'localAddr', b: 'localPort', k: 'password', m: 'method', t: 'timeout', level: 'level', // private mem: '_recordMemoryUsage' }; // import fileConfig from '../config.json'; var GENERAL_ARGUMENT_PAIR = { h: 'help', help: 'help', d: 'daemon', pac_update_gfwlist: 'pacUpdateGFWList' }; var SPAWN_OPTIONS = { detached: true, stdio: ['ignore', 'ignore', 'ignore', 'ipc'] }; var DAEMON_COMMAND = { start: 'start', stop: 'stop', restart: 'restart' }; var log = console.log; // eslint-disable-line function getDaemonType(isServer) { return isServer ? 'server' : 'local'; } function isRunning(pid) { try { // signal 0 to test existence return process.kill(pid, 0); } catch (e) { // NOTE: 'EPERM' permissions, 'ESRCH' process group doesn't exist return e.code !== 'ESRCH'; } } function getArgvOptions(argv) { var generalOptions = {}; var proxyOptions = {}; var configPair = (0, _minimist2.default)(argv); var optionsType = [{ options: proxyOptions, keys: Object.keys(PROXY_ARGUMENT_PAIR), values: PROXY_ARGUMENT_PAIR }, { options: generalOptions, keys: Object.keys(GENERAL_ARGUMENT_PAIR), values: GENERAL_ARGUMENT_PAIR }]; var invalidOption = null; Object.keys(configPair).forEach(function (key) { if (key === '_') { return; } var hit = false; optionsType.forEach(function (optType) { var i = optType.keys.indexOf(key); if (i >= 0) { optType.options[optType.values[optType.keys[i]]] = configPair[key]; // eslint-disable-line hit = true; } }); if (!hit) { invalidOption = key; } }); if (invalidOption) { invalidOption = invalidOption.length === 1 ? '-' + invalidOption : '--' + invalidOption; } else if (generalOptions.daemon && Object.keys(DAEMON_COMMAND).indexOf(generalOptions.daemon) < 0) { invalidOption = 'invalid daemon command: ' + generalOptions.daemon; } return { generalOptions: generalOptions, proxyOptions: proxyOptions, invalidOption: invalidOption }; } function readConfig(_filePath) { if (!_filePath) { return null; } var filePath = _path2.default.resolve(process.cwd(), _filePath); try { (0, _fs.accessSync)(filePath); } catch (e) { throw new Error('failed to find config file in: ' + filePath); } return JSON.parse((0, _fs.readFileSync)(filePath)); } // export for test function resolveServerAddr(config, next) { var serverAddr = config.proxyOptions.serverAddr; if ((0, _ip.isV4Format)(serverAddr)) { next(null, config); } else { (0, _dns.lookup)(serverAddr, function (err, addresses) { if (err) { next(new Error('failed to resolve \'serverAddr\': ' + serverAddr), config); } else { // NOTE: mutate data config.proxyOptions.serverAddr = addresses; // eslint-disable-line next(null, config); } }); } } function getConfig() { var argv = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; var next = arguments[1]; var _getArgvOptions = getArgvOptions(argv), generalOptions = _getArgvOptions.generalOptions, proxyOptions = _getArgvOptions.proxyOptions, invalidOption = _getArgvOptions.invalidOption; var specificFileConfig = readConfig(proxyOptions.configFilePath) || fileConfig; var config = { generalOptions: generalOptions, invalidOption: invalidOption, proxyOptions: Object.assign({}, _defaultConfig2.default, specificFileConfig, proxyOptions) }; resolveServerAddr(config, next); } function logHelp(invalidOption) { log('\n' + (invalidOption ? invalidOption + '\n' : '') + 'shadowsocks-js ' + _package.version + '\nYou can supply configurations via either config file or command line arguments.\n\nProxy options:\n -c config path to config file\n -s SERVER_ADDR server address, default: 0.0.0.0\n -p SERVER_PORT server port, default: 8083\n -l LOCAL_ADDR local binding address, default: 127.0.0.1\n -b LOCAL_PORT local port, default: 1080\n -k PASSWORD password\n -m METHOD encryption method, default: aes-128-cfb\n -t TIMEOUT timeout in seconds, default: 600\n --pac_port PAC_PORT PAC file server port, default: 8090\n --pac_update_gfwlist [URL] [localssjs] Update the gfwlist\n for PAC server. You can specify the\n request URL.\n --level LOG_LEVEL log level, default: warn\n example: --level verbose\nGeneral options:\n -h, --help show this help message and exit\n -d start/stop/restart daemon mode\n'); } function updateGFWList(flag) { log('Updating gfwlist...'); var next = function next(err) { if (err) { throw err; } else { log('Updating finished. You can checkout the file here: ' + _gfwlistUtils.GFWLIST_FILE_PATH); } }; if (typeof flag === 'string') { (0, _gfwlistUtils.updateGFWList)(flag, next); } else { (0, _gfwlistUtils.updateGFWList)(next); } } function startDaemon(isServer) { // TODO: `node` or with path? var child = (0, _child_process.spawn)('node', [_path2.default.join(__dirname, 'daemon'), getDaemonType(isServer)].concat(process.argv.slice(2)), SPAWN_OPTIONS); child.disconnect(); // do not wait for child child.unref(); (0, _pid.writePidFile)(getDaemonType(isServer), child.pid); log('start'); return child; } function stopDaemon(isServer, pid) { if (pid) { (0, _pid.deletePidFile)(getDaemonType(isServer)); (0, _utils.safelyKill)(pid, 'SIGHUP'); log('stop'); } else { log('already stopped'); } } function runDaemon(isServer, cmd) { var pid = (0, _pid.getPid)(getDaemonType(isServer)); var running = isRunning(pid); if (pid && !running) { log('previous daemon unexpectedly exited'); (0, _pid.deletePidFile)(getDaemonType(isServer)); pid = null; } switch (cmd) { case DAEMON_COMMAND.start: if (pid) { log('already started'); } else { startDaemon(isServer); } return; case DAEMON_COMMAND.stop: stopDaemon(isServer, pid); return; case DAEMON_COMMAND.restart: stopDaemon(isServer, pid); startDaemon(isServer); break; default: } } function startSSLocal(config) { return new Promise(function (resolve, reject) { resolveServerAddr(config, function (err, newConfig) { if (err) { reject(err); return; } newConfig.proxyOptions.listenError = function (e) { if (e.code == 'EADDRINUSE') { // 端口冲突则自动重连 ssLocal.startServer(newConfig.proxyOptions, true); } else { // 其它错误则抛出 reject(e); } }; newConfig.proxyOptions.listenSuccess = resolve; ssLocal.startServer(newConfig.proxyOptions, true); }); }); } function runSingle(isServer, proxyOptions) { var willLogToConsole = true; return isServer ? ssServer.startServer(proxyOptions, willLogToConsole) : ssLocal.startServer(proxyOptions, willLogToConsole); } function client(isServer) { var argv = process.argv.slice(2); getConfig(argv, function (err, config) { if (err) { throw err; } var generalOptions = config.generalOptions, proxyOptions = config.proxyOptions, invalidOption = config.invalidOption; log(config); if (generalOptions.help || invalidOption) { logHelp(invalidOption); } else if (generalOptions.pacUpdateGFWList) { updateGFWList(generalOptions.pacUpdateGFWList); } else if (generalOptions.daemon) { runDaemon(isServer, generalOptions.daemon); } else { runSingle(isServer, proxyOptions); } }); }