diff --git a/constants.js b/constants.js new file mode 100644 index 0000000..0d37360 --- /dev/null +++ b/constants.js @@ -0,0 +1,3 @@ +const VERSION = '0.1.5'; + +module.exports.VERSION = VERSION; \ No newline at end of file diff --git a/local.js b/local.js index 8917689..c0ec343 100644 --- a/local.js +++ b/local.js @@ -1,14 +1,17 @@ const TCPRelay = require('./tcprelay').TCPRelay; const local = require('commander'); +const constants = require('./constants'); local - .version('0.1.4') - .option('-m --method [method]', 'encryption method, default: aes-256-cfb') - .option('-k --password [password]', 'password') - .option('-s --server-address [address]', 'server address') - .option('-p --server-port [port]', 'server port, default: 8388') - .option('-b --local-address [address]', 'local binding address, default: 127.0.0.1') - .option('-l --local-port [port]', 'local port, default: 1080') + .version(constants.VERSION) + .option('-m --method ', 'encryption method, default: aes-256-cfb') + .option('-k --password ', 'password') + .option('-s --server-address
', 'server address') + .option('-p --server-port ', 'server port, default: 8388') + .option('-b --local-address
', 'local binding address, default: 127.0.0.1') + .option('-l --local-port ', 'local port, default: 1080') + .option('--log-level ', 'log level(debug|info|warn|error|fatal)', /^(debug|info|warn|error|fatal)$/i, 'info') + .option('--log-file ', 'log file') .parse(process.argv); var relay = new TCPRelay({ @@ -18,5 +21,7 @@ var relay = new TCPRelay({ serverPort: local.serverPort || 8388, password: local.password || 'shadowsocks-over-websocket', method: local.method || 'aes-256-cfb' -}, true, 'info'); +}, true); +relay.setLogLevel(local.logLevel); +relay.setLogFile(local.logFile); relay.bootstrap(); \ No newline at end of file diff --git a/node_modules/lodash.defaults/LICENSE b/node_modules/lodash.defaults/LICENSE new file mode 100644 index 0000000..e0c69d5 --- /dev/null +++ b/node_modules/lodash.defaults/LICENSE @@ -0,0 +1,47 @@ +Copyright jQuery Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. diff --git a/node_modules/lodash.defaults/README.md b/node_modules/lodash.defaults/README.md new file mode 100644 index 0000000..a129849 --- /dev/null +++ b/node_modules/lodash.defaults/README.md @@ -0,0 +1,18 @@ +# lodash.defaults v4.2.0 + +The [lodash](https://lodash.com/) method `_.defaults` exported as a [Node.js](https://nodejs.org/) module. + +## Installation + +Using npm: +```bash +$ {sudo -H} npm i -g npm +$ npm i --save lodash.defaults +``` + +In Node.js: +```js +var defaults = require('lodash.defaults'); +``` + +See the [documentation](https://lodash.com/docs#defaults) or [package source](https://github.com/lodash/lodash/blob/4.2.0-npm-packages/lodash.defaults) for more details. diff --git a/node_modules/lodash.defaults/index.js b/node_modules/lodash.defaults/index.js new file mode 100644 index 0000000..25eba9c --- /dev/null +++ b/node_modules/lodash.defaults/index.js @@ -0,0 +1,668 @@ +/** + * lodash (Custom Build) + * Build: `lodash modularize exports="npm" -o ./` + * Copyright jQuery Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ + +/** Used as references for various `Number` constants. */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** `Object#toString` result references. */ +var argsTag = '[object Arguments]', + funcTag = '[object Function]', + genTag = '[object GeneratorFunction]'; + +/** Used to detect unsigned integer values. */ +var reIsUint = /^(?:0|[1-9]\d*)$/; + +/** + * A faster alternative to `Function#apply`, this function invokes `func` + * with the `this` binding of `thisArg` and the arguments of `args`. + * + * @private + * @param {Function} func The function to invoke. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} args The arguments to invoke `func` with. + * @returns {*} Returns the result of `func`. + */ +function apply(func, thisArg, args) { + switch (args.length) { + case 0: return func.call(thisArg); + case 1: return func.call(thisArg, args[0]); + case 2: return func.call(thisArg, args[0], args[1]); + case 3: return func.call(thisArg, args[0], args[1], args[2]); + } + return func.apply(thisArg, args); +} + +/** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ +function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; +} + +/** Used for built-in method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ +var objectToString = objectProto.toString; + +/** Built-in value references. */ +var propertyIsEnumerable = objectProto.propertyIsEnumerable; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeMax = Math.max; + +/** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ +function arrayLikeKeys(value, inherited) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + // Safari 9 makes `arguments.length` enumerable in strict mode. + var result = (isArray(value) || isArguments(value)) + ? baseTimes(value.length, String) + : []; + + var length = result.length, + skipIndexes = !!length; + + for (var key in value) { + if ((inherited || hasOwnProperty.call(value, key)) && + !(skipIndexes && (key == 'length' || isIndex(key, length)))) { + result.push(key); + } + } + return result; +} + +/** + * Used by `_.defaults` to customize its `_.assignIn` use. + * + * @private + * @param {*} objValue The destination value. + * @param {*} srcValue The source value. + * @param {string} key The key of the property to assign. + * @param {Object} object The parent object of `objValue`. + * @returns {*} Returns the value to assign. + */ +function assignInDefaults(objValue, srcValue, key, object) { + if (objValue === undefined || + (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) { + return srcValue; + } + return objValue; +} + +/** + * Assigns `value` to `key` of `object` if the existing value is not equivalent + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ +function assignValue(object, key, value) { + var objValue = object[key]; + if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || + (value === undefined && !(key in object))) { + object[key] = value; + } +} + +/** + * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ +function baseKeysIn(object) { + if (!isObject(object)) { + return nativeKeysIn(object); + } + var isProto = isPrototype(object), + result = []; + + for (var key in object) { + if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + return result; +} + +/** + * The base implementation of `_.rest` which doesn't validate or coerce arguments. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + */ +function baseRest(func, start) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = array; + return apply(func, this, otherArgs); + }; +} + +/** + * Copies properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy properties from. + * @param {Array} props The property identifiers to copy. + * @param {Object} [object={}] The object to copy properties to. + * @param {Function} [customizer] The function to customize copied values. + * @returns {Object} Returns `object`. + */ +function copyObject(source, props, object, customizer) { + object || (object = {}); + + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + + var newValue = customizer + ? customizer(object[key], source[key], key, object, source) + : undefined; + + assignValue(object, key, newValue === undefined ? source[key] : newValue); + } + return object; +} + +/** + * Creates a function like `_.assign`. + * + * @private + * @param {Function} assigner The function to assign values. + * @returns {Function} Returns the new assigner function. + */ +function createAssigner(assigner) { + return baseRest(function(object, sources) { + var index = -1, + length = sources.length, + customizer = length > 1 ? sources[length - 1] : undefined, + guard = length > 2 ? sources[2] : undefined; + + customizer = (assigner.length > 3 && typeof customizer == 'function') + ? (length--, customizer) + : undefined; + + if (guard && isIterateeCall(sources[0], sources[1], guard)) { + customizer = length < 3 ? undefined : customizer; + length = 1; + } + object = Object(object); + while (++index < length) { + var source = sources[index]; + if (source) { + assigner(object, source, index, customizer); + } + } + return object; + }); +} + +/** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ +function isIndex(value, length) { + length = length == null ? MAX_SAFE_INTEGER : length; + return !!length && + (typeof value == 'number' || reIsUint.test(value)) && + (value > -1 && value % 1 == 0 && value < length); +} + +/** + * Checks if the given arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, + * else `false`. + */ +function isIterateeCall(value, index, object) { + if (!isObject(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike(object) && isIndex(index, object.length)) + : (type == 'string' && index in object) + ) { + return eq(object[index], value); + } + return false; +} + +/** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ +function isPrototype(value) { + var Ctor = value && value.constructor, + proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; + + return value === proto; +} + +/** + * This function is like + * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * except that it includes inherited enumerable properties. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ +function nativeKeysIn(object) { + var result = []; + if (object != null) { + for (var key in Object(object)) { + result.push(key); + } + } + return result; +} + +/** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ +function eq(value, other) { + return value === other || (value !== value && other !== other); +} + +/** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ +function isArguments(value) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && + (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); +} + +/** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ +var isArray = Array.isArray; + +/** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ +function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); +} + +/** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ +function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); +} + +/** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ +function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 8-9 which returns 'object' for typed array and other constructors. + var tag = isObject(value) ? objectToString.call(value) : ''; + return tag == funcTag || tag == genTag; +} + +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ +function isLength(value) { + return typeof value == 'number' && + value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} + +/** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ +function isObject(value) { + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} + +/** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} + +/** + * This method is like `_.assignIn` except that it accepts `customizer` + * which is invoked to produce the assigned values. If `customizer` returns + * `undefined`, assignment is handled by the method instead. The `customizer` + * is invoked with five arguments: (objValue, srcValue, key, object, source). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias extendWith + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @see _.assignWith + * @example + * + * function customizer(objValue, srcValue) { + * return _.isUndefined(objValue) ? srcValue : objValue; + * } + * + * var defaults = _.partialRight(_.assignInWith, customizer); + * + * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ +var assignInWith = createAssigner(function(object, source, srcIndex, customizer) { + copyObject(source, keysIn(source), object, customizer); +}); + +/** + * Assigns own and inherited enumerable string keyed properties of source + * objects to the destination object for all destination properties that + * resolve to `undefined`. Source objects are applied from left to right. + * Once a property is set, additional values of the same property are ignored. + * + * **Note:** This method mutates `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.defaultsDeep + * @example + * + * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ +var defaults = baseRest(function(args) { + args.push(undefined, assignInDefaults); + return apply(assignInWith, undefined, args); +}); + +/** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + */ +function keysIn(object) { + return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); +} + +module.exports = defaults; diff --git a/node_modules/lodash.defaults/package.json b/node_modules/lodash.defaults/package.json new file mode 100644 index 0000000..51abffe --- /dev/null +++ b/node_modules/lodash.defaults/package.json @@ -0,0 +1,115 @@ +{ + "_args": [ + [ + { + "raw": "lodash.defaults@^4.0.1", + "scope": null, + "escapedName": "lodash.defaults", + "name": "lodash.defaults", + "rawSpec": "^4.0.1", + "spec": ">=4.0.1 <5.0.0", + "type": "range" + }, + "/home/vincent/Projects/SublimeTextProjects/shadowsocks-over-websocket/node_modules/throng" + ] + ], + "_cnpm_publish_time": 1471109872434, + "_from": "lodash.defaults@>=4.0.1 <5.0.0", + "_id": "lodash.defaults@4.2.0", + "_inCache": true, + "_installable": true, + "_location": "/lodash.defaults", + "_nodeVersion": "4.4.7", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/lodash.defaults-4.2.0.tgz_1471109872209_0.6277233152650297" + }, + "_npmUser": { + "name": "jdalton", + "email": "john.david.dalton@gmail.com" + }, + "_npmVersion": "2.15.10", + "_phantomChildren": {}, + "_requested": { + "raw": "lodash.defaults@^4.0.1", + "scope": null, + "escapedName": "lodash.defaults", + "name": "lodash.defaults", + "rawSpec": "^4.0.1", + "spec": ">=4.0.1 <5.0.0", + "type": "range" + }, + "_requiredBy": [ + "/throng" + ], + "_resolved": "https://registry.npm.taobao.org/lodash.defaults/download/lodash.defaults-4.2.0.tgz", + "_shasum": "d09178716ffea4dde9e5fb7b37f6f0802274580c", + "_shrinkwrap": null, + "_spec": "lodash.defaults@^4.0.1", + "_where": "/home/vincent/Projects/SublimeTextProjects/shadowsocks-over-websocket/node_modules/throng", + "author": { + "name": "John-David Dalton", + "email": "john.david.dalton@gmail.com", + "url": "http://allyoucanleet.com/" + }, + "bugs": { + "url": "https://github.com/lodash/lodash/issues" + }, + "contributors": [ + { + "name": "John-David Dalton", + "email": "john.david.dalton@gmail.com", + "url": "http://allyoucanleet.com/" + }, + { + "name": "Blaine Bublitz", + "email": "blaine.bublitz@gmail.com", + "url": "https://github.com/phated" + }, + { + "name": "Mathias Bynens", + "email": "mathias@qiwi.be", + "url": "https://mathiasbynens.be/" + } + ], + "dependencies": {}, + "description": "The lodash method `_.defaults` exported as a module.", + "devDependencies": {}, + "directories": {}, + "dist": { + "shasum": "d09178716ffea4dde9e5fb7b37f6f0802274580c", + "tarball": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz" + }, + "homepage": "https://lodash.com/", + "icon": "https://lodash.com/icon.svg", + "keywords": [ + "lodash-modularized", + "defaults" + ], + "license": "MIT", + "maintainers": [ + { + "name": "jdalton", + "email": "john.david.dalton@gmail.com" + }, + { + "name": "mathias", + "email": "mathias@qiwi.be" + }, + { + "name": "phated", + "email": "blaine@iceddev.com" + } + ], + "name": "lodash.defaults", + "optionalDependencies": {}, + "readme": "ERROR: No README data found!", + "repository": { + "type": "git", + "url": "git+https://github.com/lodash/lodash.git" + }, + "scripts": { + "test": "echo \"See https://travis-ci.org/lodash/lodash-cli for testing details.\"" + }, + "version": "4.2.0" +} diff --git a/node_modules/throng/lib/throng.js b/node_modules/throng/lib/throng.js new file mode 100644 index 0000000..be2f67b --- /dev/null +++ b/node_modules/throng/lib/throng.js @@ -0,0 +1,74 @@ +'use strict'; + +const cluster = require('cluster'); +const EventEmitter = require('events').EventEmitter; +const defaults = require('lodash.defaults'); +const cpuCount = require('os').cpus().length; + +const DEFAULT_OPTIONS = { + workers: cpuCount, + lifetime: Infinity, + grace: 5000 +}; + +const NOOP = () => {}; + +module.exports = function throng(options, startFunction) { + options = options || {}; + let startFn = options.start || startFunction || options; + let masterFn = options.master || NOOP; + + if (typeof startFn !== 'function') { + throw new Error('Start function required'); + } + if (cluster.isWorker) { + return startFn(cluster.worker.id); + } + + let opts = isNaN(options) ? + defaults(options, DEFAULT_OPTIONS) : defaults({ workers: options }, DEFAULT_OPTIONS); + let emitter = new EventEmitter(); + let running = true; + let runUntil = Date.now() + opts.lifetime; + + listen(); + masterFn(); + fork(); + + function listen() { + cluster.on('exit', revive); + emitter.once('shutdown', shutdown); + process + .on('SIGINT', proxySignal) + .on('SIGTERM', proxySignal); + } + + function fork() { + for (var i = 0; i < opts.workers; i++) { + cluster.fork(); + } + } + + function proxySignal() { + emitter.emit('shutdown'); + } + + function shutdown() { + running = false; + for (var id in cluster.workers) { + cluster.workers[id].process.kill(); + } + setTimeout(forceKill, opts.grace).unref(); + } + + function revive(worker, code, signal) { + if (running && Date.now() < runUntil) cluster.fork(); + } + + function forceKill() { + for (var id in cluster.workers) { + cluster.workers[id].kill(); + } + process.exit(); + } +}; diff --git a/node_modules/throng/package.json b/node_modules/throng/package.json new file mode 100644 index 0000000..5f7d7d5 --- /dev/null +++ b/node_modules/throng/package.json @@ -0,0 +1,102 @@ +{ + "_args": [ + [ + { + "raw": "throng", + "scope": null, + "escapedName": "throng", + "name": "throng", + "rawSpec": "", + "spec": "latest", + "type": "tag" + }, + "/home/vincent/Projects/SublimeTextProjects/shadowsocks-over-websocket" + ] + ], + "_from": "throng@latest", + "_id": "throng@4.0.0", + "_inCache": true, + "_installable": true, + "_location": "/throng", + "_nodeVersion": "5.10.1", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/throng-4.0.0.tgz_1460614908610_0.9267617627047002" + }, + "_npmUser": { + "name": "hunterloftis", + "email": "hunter@hunterloftis.com" + }, + "_npmVersion": "3.8.3", + "_phantomChildren": {}, + "_requested": { + "raw": "throng", + "scope": null, + "escapedName": "throng", + "name": "throng", + "rawSpec": "", + "spec": "latest", + "type": "tag" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "http://registry.npmjs.org/throng/-/throng-4.0.0.tgz", + "_shasum": "983c6ba1993b58eae859998aa687ffe88df84c17", + "_shrinkwrap": null, + "_spec": "throng", + "_where": "/home/vincent/Projects/SublimeTextProjects/shadowsocks-over-websocket", + "author": { + "name": "Hunter Loftis", + "email": "hunter@hunterloftis.com" + }, + "bugs": { + "url": "https://github.com/hunterloftis/throng/issues" + }, + "dependencies": { + "lodash.defaults": "^4.0.1" + }, + "description": "A simple worker-manager for clustered apps", + "devDependencies": { + "chai": "^3.5.0", + "mocha": "^2.4.5" + }, + "directories": {}, + "dist": { + "shasum": "983c6ba1993b58eae859998aa687ffe88df84c17", + "tarball": "https://registry.npmjs.org/throng/-/throng-4.0.0.tgz" + }, + "engines": { + "node": ">= 4.0.0" + }, + "files": [ + "lib" + ], + "gitHead": "e585aa4997085b32a603983c36dce6f71ed1b91a", + "homepage": "https://github.com/hunterloftis/throng", + "keywords": [ + "cluster", + "worker", + "process" + ], + "license": "MIT", + "main": "lib/throng.js", + "maintainers": [ + { + "name": "hunterloftis", + "email": "hunter@hunterloftis.com" + } + ], + "name": "throng", + "optionalDependencies": {}, + "readme": "ERROR: No README data found!", + "repository": { + "type": "git", + "url": "git+https://github.com/hunterloftis/throng.git" + }, + "scripts": { + "test": "mocha" + }, + "version": "4.0.0" +} diff --git a/node_modules/throng/readme.md b/node_modules/throng/readme.md new file mode 100644 index 0000000..70831bb --- /dev/null +++ b/node_modules/throng/readme.md @@ -0,0 +1,133 @@ +# Throng + +Dead-simple one-liner for clustered Node.js apps. + +Runs X workers and respawns them if they go down. +Correctly handles signals from the OS. + +```js +const throng = require('throng'); + +throng((id) => { + console.log(`Started worker ${id}`); +}); +``` + +``` +$ node example +Started worker 1 +Started worker 2 +Started worker 3 +Started worker 4 +``` + +## Installation + +``` +npm install --save throng +``` + +For older versions of node (< 4.x), use throng 2.x. + +## Use + +Simplest; automatically fork 1 worker per CPU core: + +```js +throng(startFunction); +``` + +Specify a number of workers: + +```js +throng(3, startFunction); +``` + +Specify more options: + +```js +throng({ + workers: 16, + grace: 1000, + master: masterFunction, + start: startFunction +}); +``` + +Handle signals (for cleanup on a kill signal, for instance): + +```js +throng((id) => { + console.log(`Started worker ${id}`); + + process.on('SIGTERM', function() { + console.log(`Worker ${id} exiting`); + console.log('Cleanup here'); + process.exit(); + }); +}); +``` + +## All Options (with defaults) + +```js +throng({ + workers: 4, // Number of workers (cpu count) + lifetime: 10000, // ms to keep cluster alive (Infinity) + grace: 4000 // ms grace period after worker SIGTERM (5000) +}, startFn); +``` + +## A Complex example + +```js +const throng = require('./lib/throng'); + +throng({ + workers: 4, + master: startMaster, + start: startWorker +}); + +// This will only be called once +function startMaster() { + console.log(`Started master`); +} + +// This will be called four times +function startWorker(id) { + console.log(`Started worker ${ id }`); + + process.on('SIGTERM', () => { + console.log(`Worker ${ id } exiting...`); + console.log('(cleanup would happen here)'); + process.exit(); + }); +} +``` + +``` +$ node example-complex.js +Started master +Started worker 1 +Started worker 2 +Started worker 3 +Started worker 4 + +$ killall node + +Worker 3 exiting... +Worker 4 exiting... +(cleanup would happen here) +(cleanup would happen here) +Worker 2 exiting... +(cleanup would happen here) +Worker 1 exiting... +(cleanup would happen here) +``` + +## Tests + +``` +npm test +``` diff --git a/package.json b/package.json index 12494af..4281d05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shadowsocks-over-websocket", - "version": "0.1.4", + "version": "0.1.5", "description": "A fast tunnel proxy that helps you bypass firewalls", "main": "tcprelay.js", "scripts": { @@ -27,6 +27,7 @@ "commander": "^2.9.0", "log4js": "^1.1.1", "shadowsocks": "^1.5.3", + "throng": "^4.0.0", "ws": "^2.2.3" } } diff --git a/server.js b/server.js index e356977..1c06138 100644 --- a/server.js +++ b/server.js @@ -1,18 +1,41 @@ const TCPRelay = require('./tcprelay').TCPRelay; const server = require('commander'); +const constants = require('./constants'); +const throng = require('throng'); +const log4js = require('log4js'); +const logger = log4js.getLogger('server'); server - .version('0.1.4') - .option('-m --method [method]', 'encryption method, default: aes-256-cfb') - .option('-k --password [password]', 'password') - .option('-s --server-address [address]', 'server address') - .option('-p --server-port [port]', 'server port, default: 8388') + .version(constants.VERSION) + .option('-m --method ', 'encryption method, default: aes-256-cfb') + .option('-k --password ', 'password') + .option('-s --server-address
', 'server address') + .option('-p --server-port ', 'server port, default: 8388') + .option('--log-level ', 'log level(debug|info|warn|error|fatal)', /^(debug|info|warn|error|fatal)$/i, 'info') + .option('--log-file ', 'log file') .parse(process.argv); -var relay = new TCPRelay({ - serverAddress: process.env['SERVER_ADDRESS'] || server.serverAddress || '127.0.0.1', - serverPort: process.env['PORT'] || server.serverPort || 8388, - password: process.env['PASSWORD'] || server.password || 'shadowsocks-over-websocket', - method: process.env['METHOD'] || server.method || 'aes-256-cfb' -}, false, 'info'); -relay.bootstrap(); \ No newline at end of file +throng({ + workers: process.env.WEB_CONCURRENCY || 2, + master: startMaster, + start: startWorker +}); + +function startMaster() { + logger.info('started master'); +} + +function startWorker(id) { + logger.info(`started worker ${id}`); + var relay = new TCPRelay({ + serverAddress: process.env['SERVER_ADDRESS'] || server.serverAddress || '127.0.0.1', + serverPort: process.env['PORT'] || server.serverPort || 8388, + password: process.env['PASSWORD'] || server.password || 'shadowsocks-over-websocket', + method: process.env['METHOD'] || server.method || 'aes-256-cfb' + }, false); + + relay.setLogLevel(server.logLevel); + relay.setLogFile(server.logFile); + relay.setServerName('server-' + id); + relay.bootstrap(); +} \ No newline at end of file diff --git a/tcprelay.js b/tcprelay.js index 4ee3127..61c3dd9 100644 --- a/tcprelay.js +++ b/tcprelay.js @@ -1,4 +1,5 @@ const net = require('net'); +const path = require('path'); const log4js = require('log4js'); const WebSocket = require('ws'); const Encryptor = require('shadowsocks/lib/shadowsocks/encrypt').Encryptor; @@ -93,7 +94,7 @@ function parseAddressHeader(data, offset) { }; } -function TCPRelay(config, isLocal, logLevel) { +function TCPRelay(config, isLocal) { this.isLocal = isLocal; this.server = null; this.status = SERVER_STATUS_INIT; @@ -101,46 +102,59 @@ function TCPRelay(config, isLocal, logLevel) { if (config) { this.config = Object.assign(this.config, config); } - this.logger = log4js.getLogger(isLocal ? 'sslocal' : 'ssserver'); - this.logger.setLevel(logLevel ? logLevel : 'error'); + this.logger = null; + this.logLevel = 'error'; + this.logFile = null; + this.serverName = null; } - -TCPRelay.prototype.getServerName = function() { - return this.isLocal ? 'sslocal' : 'ssserver'; -}; - - -TCPRelay.prototype.bootstrap = function() { - return this.init(); -}; - -TCPRelay.prototype.stop = function() { - var self = this; - var connId = null; - return new Promise(function(resolve, reject) { - if (self.server) { - self.server.close(function() { - resolve(); - }); - - for (connId in connections) { - if (connections[connId]) { - self.isLocal ? connections[connId].destroy() : connections[connId].terminate(); - } - } - - } else { - resolve(); - } - }); -}; - TCPRelay.prototype.getStatus = function() { return this.status; }; -TCPRelay.prototype.init = function() { +TCPRelay.prototype.setServerName = function(serverName) { + this.serverName = serverName; + return this; +}; + +TCPRelay.prototype.getServerName = function() { + if (!this.serverName) { + this.serverName = this.isLocal ? 'local' : 'server'; + } + return this.serverName; +}; + +TCPRelay.prototype.setLogLevel = function(logLevel) { + this.logLevel = logLevel; + return this; +}; + +TCPRelay.prototype.getLogLevel = function() { + return this.logLevel; +}; + +TCPRelay.prototype.setLogFile = function(logFile) { + if (logFile && !path.isAbsolute(logFile)) { + logFile = process.cwd() + '/' + logFile; + } + this.logFile = logFile; + return this; +}; + +TCPRelay.prototype.getLogFile = function() { + return this.logFile; +}; + +TCPRelay.prototype.initLogger = function() { + if (this.logFile) { + log4js.loadAppender('file'); + log4js.addAppender(log4js.appenders.file(this.logFile), this.getServerName()); + } + this.logger = log4js.getLogger(this.getServerName()); + this.logger.setLevel(this.logLevel); +}; + +TCPRelay.prototype.initServer = function() { var self = this; return new Promise(function(resolve, reject) { var config = self.config; @@ -173,7 +187,7 @@ TCPRelay.prototype.init = function() { }); } server.on('error', function(error) { - self.logger.error('an error of', self.getServerName(), 'occured', error); + self.logger.fatal('an error of', self.getServerName(), 'occured', error); self.status = SERVER_STATUS_STOPPED; reject(error); }); @@ -207,7 +221,7 @@ TCPRelay.prototype.handleConnectionByServer = function(connection) { connections[connectionId] = connection; connection.on('message', function(data) { data = encryptor.decrypt(data); - logger.info(`[${connectionId}]: read data[length = ${data.length}] from local connection at stage[${STAGE[stage]}]`); + logger.debug(`[${connectionId}]: read data[length = ${data.length}] from local connection at stage[${STAGE[stage]}]`); switch (stage) { @@ -234,19 +248,19 @@ TCPRelay.prototype.handleConnectionByServer = function(connection) { dataCache = Buffer.concat(dataCache); targetConnection.write(dataCache, function() { - logger.info(`[${connectionId}]: write data[length = ${dataCache.length}] to target connection`); + logger.debug(`[${connectionId}]: write data[length = ${dataCache.length}] to target connection`); dataCache = null; }); stage = STAGE_STREAM; }); targetConnection.on('data', function(data) { - logger.info(`[${connectionId}]: read data[length = ${data.length}] from target connection`); + logger.debug(`[${connectionId}]: read data[length = ${data.length}] from target connection`); if (connection.readyState == WebSocket.OPEN) { connection.send(encryptor.encrypt(data), { binary: true }, function() { - logger.info(`[${connectionId}]: write data[length = ${data.length}] to local connection`); + logger.debug(`[${connectionId}]: write data[length = ${data.length}] to local connection`); }); } }); @@ -272,7 +286,7 @@ TCPRelay.prototype.handleConnectionByServer = function(connection) { case STAGE_STREAM: targetConnection.write(data, function() { - logger.info(`[${connectionId}]: write data[length = ${data.length}] to target connection`); + logger.debug(`[${connectionId}]: write data[length = ${data.length}] to target connection`); }); break; } @@ -291,7 +305,7 @@ TCPRelay.prototype.handleConnectionByServer = function(connection) { connections[connectionId] = null; targetConnection && targetConnection.end(); }); -} +}; //local TCPRelay.prototype.handleConnectionByLocal = function(connection) { @@ -316,7 +330,7 @@ TCPRelay.prototype.handleConnectionByLocal = function(connection) { connections[connectionId] = connection; connection.setKeepAlive(true, 10000); connection.on('data', function(data) { - logger.info(`[${connectionId}]: read data[length = ${data.length}] from client connection at stage[${STAGE[stage]}]`); + logger.debug(`[${connectionId}]: read data[length = ${data.length}] from client connection at stage[${STAGE[stage]}]`); switch (stage) { case STAGE_INIT: @@ -363,7 +377,7 @@ TCPRelay.prototype.handleConnectionByLocal = function(connection) { serverConnection.send(encryptor.encrypt(dataCache), { binary: true }, function() { - logger.info(`[${connectionId}]: write data[length = ${dataCache.length}] to client connection`); + logger.debug(`[${connectionId}]: write data[length = ${dataCache.length}] to client connection`); dataCache = null; }); }); @@ -373,9 +387,9 @@ TCPRelay.prototype.handleConnectionByLocal = function(connection) { }, 30000); }); serverConnection.on('message', function(data) { - logger.info(`[${connectionId}]: read data[length = ${data.length}] from websocket server connection`); + logger.debug(`[${connectionId}]: read data[length = ${data.length}] from websocket server connection`); canWriteToLocalConnection && connection.write(encryptor.decrypt(data), function() { - logger.info(`[${connectionId}]: write data[length = ${data.length}] to client connection`); + logger.debug(`[${connectionId}]: write data[length = ${data.length}] to client connection`); }); }); serverConnection.on('error', function(error) { @@ -385,7 +399,7 @@ TCPRelay.prototype.handleConnectionByLocal = function(connection) { connection.end(); }); serverConnection.on('close', function() { - logger.info(`[${connectionId}]: server connection isclosed`); + logger.info(`[${connectionId}]: server connection is closed`); ping && clearInterval(ping); stage = STAGE_DESTROYED; connection.end(); @@ -404,7 +418,7 @@ TCPRelay.prototype.handleConnectionByLocal = function(connection) { canWriteToLocalConnection && serverConnection.send(encryptor.encrypt(data), { binary: true }, function() { - logger.info(`[${connectionId}]: write data[length = ${data.length}] to websocket server connection`); + logger.debug(`[${connectionId}]: write data[length = ${data.length}] to websocket server connection`); }); break; } @@ -428,6 +442,33 @@ TCPRelay.prototype.handleConnectionByLocal = function(connection) { connections[connectionId] = null; serverConnection && serverConnection.close(); }); -} +}; + + +TCPRelay.prototype.bootstrap = function() { + this.initLogger(); + return this.initServer(); +}; + +TCPRelay.prototype.stop = function() { + var self = this; + var connId = null; + return new Promise(function(resolve, reject) { + if (self.server) { + self.server.close(function() { + resolve(); + }); + + for (connId in connections) { + if (connections[connId]) { + self.isLocal ? connections[connId].destroy() : connections[connId].terminate(); + } + } + + } else { + resolve(); + } + }); +}; module.exports.TCPRelay = TCPRelay; \ No newline at end of file