├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── tasks.json ├── LICENSE.md ├── README.md ├── bridge ├── README.md ├── app.ts └── bin │ └── cli.ts ├── build ├── bridge │ ├── app.js │ └── bin │ │ └── cli.js ├── client │ ├── app.js │ ├── bin │ │ └── cli.js │ └── socks5 │ │ ├── localProxyServer.js │ │ ├── remoteProxyServer.js │ │ └── socks5Server.js ├── common │ ├── cipher.js │ ├── constant.js │ ├── ipc.js │ ├── socks5constant.js │ └── socks5helper.js ├── lib │ ├── chacha20.js │ ├── chacha20stream.js │ ├── cipher.js │ ├── cipherstream.js │ ├── constant.js │ ├── ipc.js │ ├── pkcs7.js │ ├── socketEx.js │ ├── socks5Constant.js │ ├── socks5Helper.js │ ├── speedstream.js │ └── xorstream.js ├── server │ ├── aplvpn │ │ ├── index.js │ │ ├── protocols.js │ │ ├── tcp.js │ │ └── udp.js │ ├── app.js │ ├── appvpn │ │ └── protocols.js │ ├── bin │ │ └── cli.js │ ├── cluster.js │ ├── lib │ │ └── addressHelper.js │ ├── management │ │ ├── apiRouter.js │ │ ├── cmdController.js │ │ ├── index.js │ │ └── userController.js │ ├── osxcl5 │ │ ├── connectHandler.js │ │ └── index.js │ ├── server.js │ └── socks5 │ │ ├── connectHandler.js │ │ ├── index.js │ │ └── udpHandler.js └── test │ ├── httpManagement.js │ ├── pkcs7.js │ ├── socks5.js │ └── transform.js ├── client ├── README.md ├── app.ts ├── bin │ └── cli.ts └── socks5 │ ├── localProxyServer.ts │ ├── remoteProxyServer.ts │ └── socks5Server.ts ├── common ├── cipher.ts ├── constant.ts ├── ipc.ts ├── socks5constant.ts └── socks5helper.ts ├── docs ├── apple.md └── protocols.md ├── jsconfig.json ├── lib ├── chacha20.ts ├── chacha20stream.ts ├── cipherstream.ts ├── pkcs7.ts ├── socketEx.ts ├── speedstream.ts └── xorstream.ts ├── misc ├── lsserver.conf ├── lsserver.sh ├── onekey_centos.sh ├── onekey_debian.sh ├── onekey_ubuntu.sh └── users.conf ├── package.json ├── server ├── README.md ├── aplvpn │ ├── index.ts │ ├── protocols.ts │ ├── tcp.ts │ └── udp.ts ├── app.ts ├── bin │ └── cli.ts ├── cluster.ts ├── lib │ └── addressHelper.ts ├── management │ ├── apiRouter.ts │ ├── cmdController.ts │ ├── index.ts │ └── userController.ts ├── osxcl5 │ ├── connectHandler.ts │ └── index.ts ├── server.ts └── socks5 │ ├── connectHandler.ts │ ├── index.ts │ └── udpHandler.ts ├── test ├── httpManagement.ts ├── ip.js ├── pkcs7.ts ├── socks5.ts └── transform.ts ├── tsconfig.json └── typings ├── async-node └── async-node.d.ts ├── body-parser └── body-parser.d.ts ├── commander └── commander.d.ts ├── express └── express.d.ts ├── kinq └── kinq.d.ts ├── mime └── mime.d.ts ├── mocha └── mocha.d.ts ├── node └── node.d.ts ├── serve-static └── serve-static.d.ts └── winston └── winston.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | .DS_Store 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | *.js.map 30 | *.ignore 31 | 32 | # Typescript declaration files 33 | build/*.d.ts 34 | build/**/*.d.ts -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | // List of configurations. Add new configurations or edit existing ones. 4 | "configurations": [ 5 | { 6 | "request": "launch", 7 | // Name of configuration; appears in the launch configuration drop down menu. 8 | "name": "Launch Client", 9 | // Type of configuration. 10 | "type": "node", 11 | // Workspace relative or absolute path to the program. 12 | "program": "./build/client/app.js", 13 | // Automatically stop program after launch. 14 | "stopOnEntry": false, 15 | // Command line arguments passed to the program. 16 | "args": [], 17 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 18 | "cwd": ".", 19 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 20 | "runtimeExecutable": null, 21 | // Optional arguments passed to the runtime executable. 22 | "runtimeArgs": ["--nolazy"], 23 | // Environment variables passed to the program. 24 | "env": { 25 | "NODE_ENV": "development" 26 | }, 27 | // Use JavaScript source maps (if they exist). 28 | "sourceMaps": true, 29 | // If JavaScript source maps are enabled, the generated code is expected in this directory. 30 | "outDir": null 31 | }, 32 | { 33 | "request": "launch", 34 | "name": "Launch Server", 35 | "type": "node", 36 | "program": "./build/server/app.js", 37 | "args": [] 38 | }, 39 | { 40 | "request": "attach", 41 | "name": "Attach", 42 | "type": "node", 43 | // Port to attach to. 44 | "port": 5858, 45 | "sourceMaps": false 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls the Typescript compiler (tsc) and 10 | // Compiles a HelloWorld.ts program 11 | // { 12 | // "version": "0.1.0", 13 | 14 | // // The command is tsc. Assumes that tsc has been installed using npm install -g typescript 15 | // "command": "tsc", 16 | 17 | // // The command is a shell script 18 | // "isShellCommand": true, 19 | 20 | // // Show the output window only if unrecognized errors occur. 21 | // "showOutput": "silent", 22 | 23 | // // args is the HelloWorld program to compile. 24 | // "args": [], 25 | 26 | // // use the standard tsc problem matcher to find compile problems 27 | // // in the output. 28 | // "problemMatcher": "$tsc" 29 | // } 30 | 31 | // A task runner that calls the Typescript compiler (tsc) and 32 | // compiles based on a tsconfig.json file that is present in 33 | // the root of the folder open in VSCode 34 | 35 | { 36 | "version": "0.1.0", 37 | 38 | // The command is tsc. Assumes that tsc has been installed using npm install -g typescript 39 | "command": "tsc", 40 | 41 | // The command is a shell script 42 | "isShellCommand": true, 43 | 44 | // Show the output window only if unrecognized errors occur. 45 | "showOutput": "silent", 46 | 47 | // Tell the tsc compiler to use the tsconfig.json from the open folder. 48 | "args": ["-p", "."], 49 | 50 | // use the standard tsc problem matcher to find compile problems 51 | // in the output. 52 | "problemMatcher": "$tsc" 53 | } 54 | 55 | 56 | // A task runner configuration for gulp. Gulp provides a less task 57 | // which compiles less to css. 58 | /* 59 | { 60 | "version": "0.1.0", 61 | "command": "gulp", 62 | "isShellCommand": true, 63 | "tasks": [ 64 | { 65 | "taskName": "less", 66 | // Make this the default build command. 67 | "isBuildCommand": true, 68 | // Show the output window only if unrecognized errors occur. 69 | "showOutput": "silent", 70 | // Use the standard less compilation problem matcher. 71 | "problemMatcher": "$lessCompile" 72 | } 73 | ] 74 | } 75 | */ 76 | 77 | // Uncomment the following section to use jake to build a workspace 78 | // cloned from https://github.com/Microsoft/TypeScript.git 79 | /* 80 | { 81 | "version": "0.1.0", 82 | // Task runner is jake 83 | "command": "jake", 84 | // Need to be executed in shell / cmd 85 | "isShellCommand": true, 86 | "showOutput": "silent", 87 | "tasks": [ 88 | { 89 | // TS build command is local. 90 | "taskName": "local", 91 | // Make this the default build command. 92 | "isBuildCommand": true, 93 | // Show the output window only if unrecognized errors occur. 94 | "showOutput": "silent", 95 | // Use the redefined Typescript output problem matcher. 96 | "problemMatcher": [ 97 | "$tsc" 98 | ] 99 | } 100 | ] 101 | } 102 | */ 103 | 104 | // Uncomment the section below to use msbuild and generate problems 105 | // for csc, cpp, tsc and vb. The configuration assumes that msbuild 106 | // is available on the path and a solution file exists in the 107 | // workspace folder root. 108 | /* 109 | { 110 | "version": "0.1.0", 111 | "command": "msbuild", 112 | "args": [ 113 | // Ask msbuild to generate full paths for file names. 114 | "/property:GenerateFullPaths=true" 115 | ], 116 | "taskSelector": "/t:", 117 | "showOutput": "silent", 118 | "tasks": [ 119 | { 120 | "taskName": "build", 121 | // Show the output window only if unrecognized errors occur. 122 | "showOutput": "silent", 123 | // Use the standard MS compiler pattern to detect errors, warnings 124 | // and infos in the output. 125 | "problemMatcher": "$msCompile" 126 | } 127 | ] 128 | } 129 | */ 130 | 131 | // Uncomment the following section to use msbuild which compiles Typescript 132 | // and less files. 133 | /* 134 | { 135 | "version": "0.1.0", 136 | "command": "msbuild", 137 | "args": [ 138 | // Ask msbuild to generate full paths for file names. 139 | "/property:GenerateFullPaths=true" 140 | ], 141 | "taskSelector": "/t:", 142 | "showOutput": "silent", 143 | "tasks": [ 144 | { 145 | "taskName": "build", 146 | // Show the output window only if unrecognized errors occur. 147 | "showOutput": "silent", 148 | // Use the standard MS compiler pattern to detect errors, warnings 149 | // and infos in the output. 150 | "problemMatcher": [ 151 | "$msCompile", 152 | "$lessCompile" 153 | ] 154 | } 155 | ] 156 | } 157 | */ 158 | // A task runner example that defines a problemMatcher inline instead of using 159 | // a predfined one. 160 | /* 161 | { 162 | "version": "0.1.0", 163 | "command": "tsc", 164 | "isShellCommand": true, 165 | "args": ["HelloWorld.ts"], 166 | "showOutput": "silent", 167 | "problemMatcher": { 168 | // The problem is owned by the typescript language service. Ensure that the problems 169 | // are merged with problems produced by Visual Studio's language service. 170 | "owner": "typescript", 171 | // The file name for reported problems is relative to the current working directory. 172 | "fileLocation": ["relative", "${cwd}"], 173 | // The actual pattern to match problems in the output. 174 | "pattern": { 175 | // The regular expression. Matches HelloWorld.ts(2,10): error TS2339: Property 'logg' does not exist on type 'Console'. 176 | "regexp": "^([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", 177 | // The match group that denotes the file containing the problem. 178 | "file": 1, 179 | // The match group that denotes the problem location. 180 | "location": 2, 181 | // The match group that denotes the problem's severity. Can be omitted. 182 | "severity": 3, 183 | // The match group that denotes the problem code. Can be omitted. 184 | "code": 4, 185 | // The match group that denotes the problem's message. 186 | "message": 5 187 | } 188 | } 189 | } 190 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LightSword 2 | 3 | [![Build Status](https://travis-ci.org/UnsignedInt8/LightSword.svg?branch=master)](https://travis-ci.org/UnsignedInt8/LightSword) 4 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/UnsignedInt8/LightSword?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 5 | 6 | LightSword —— 基于 Node.js 的 SOCKS5 代理 / Apple NE 服务器。 7 | 8 | LightSword 参考了 Shadowsocks 的协议,并用 Typescript 实现。LightSword 基于 Node.js 4.0+,因此需要首先安装 Node.js 4.0 以上版本,点击访问[官方网址](https://nodejs.org)。 9 | 10 | 11 | Quick Start 12 | --- 13 | 14 | 首先,安装Node.js 4.0+(详细方法参见下文),安装好 Node.js 之后,即可使用 Node.js 的包管理器 npm 安装 LightSword 。目前 npmjs.org 可以访问,因此可以直接安装: 15 | 16 | 17 | 1.安装 18 | ``` 19 | [sudo] npm install lightsword -g 20 | ``` 21 | 22 | 安装完成之后,即可使用客户端,服务器端,中转站端。 23 | 24 | 服务器端: lsserver, lightsword-server, lightsword 25 | 26 | 中转站端: lsbridge, lightsword-bridge 27 | 28 | 客户端: lslocal, lightsword-client 29 | 30 | 简单地说明下中转站的作用: 31 | 32 | ~~由于某些不为人知的原因,访问境外IP时不时丢包非常严重,因此可以把中转站搭建在云提供商服务器上,作为中转/中继使用,具有较好的效果(大雾)~~(在一定程度上提高了匿名性)。 33 | 34 | 2.运行 35 | 36 | 服务器: 37 | ``` 38 | lsserver -f 39 | ``` 40 | 41 | 客户端: 42 | ``` 43 | lslocal -s server_addr -f 44 | ``` 45 | 46 | 中转站: 47 | ``` 48 | lsbridge -s server_addr -f 49 | ``` 50 | 51 | 以上配置均使用内置默认设置,详细参数请参见源码文件夹中的 README.md 文件(参见: server, client, bridge 这三个文件夹)。 52 | 53 | > 经实际测试,中转服务器对 Shadowsocks 有效。理论上支持任何形式的 TCP 流量中转。 54 | 55 | 最后配置你的浏览器及其它需要上网的软件使用 LightSword 提供的 SOCKS5 代理。 56 | 57 | 默认监听地址: localhost 58 | 59 | 端口: 1080 60 | 61 | Linux 支持 62 | --- 63 | 64 | 在 `misc` 文件夹下,已经写好了 Linux 启动脚本,你可以根据自己的实际情况,修改运行参数。并放到 init.d 目录下,再 `chkconfig on` 或者其它 Linux 分发版的命令激活自动运行即可。 65 | 66 | 快速安装方法: 67 | 68 | Ubuntu 用户 69 | 70 | ``` 71 | # Using Ubuntu 72 | [sudo] apt-get update -y 73 | [sudo] apt-get install curl -y 74 | 75 | [sudo] curl -sL https://deb.nodesource.com/setup_5.x | sudo -E bash - 76 | [sudo] apt-get install -y nodejs 77 | [sudo] npm install lightsword -g 78 | ``` 79 | 80 | 一键安装 81 | 82 | ``` 83 | curl -sL https://raw.githubusercontent.com/UnsignedInt8/LightSword/master/misc/onekey_ubuntu.sh | sudo -E bash - 84 | ``` 85 | 86 | Debian 用户 87 | 88 | ``` 89 | # Using Debian, as root 90 | [sudo] apt-get update -y 91 | [sudo] apt-get install curl -y 92 | 93 | [sudo] curl -sL https://deb.nodesource.com/setup_5.x | bash - 94 | [sudo] apt-get install -y nodejs 95 | [sudo] npm install lightsword -g 96 | ``` 97 | 98 | 一键安装 99 | 100 | ``` 101 | curl -sL https://raw.githubusercontent.com/UnsignedInt8/LightSword/master/misc/onekey_debian.sh | sudo -E bash - 102 | ``` 103 | 104 | RHEL 6, CentOS 6 用户 105 | 106 | ``` 107 | # Using RHEL 6, CentOS 6 108 | [sudo] yum update -y 109 | [sudo] yum install curl -y 110 | 111 | [sudo] curl -sL https://rpm.nodesource.com/setup_5.x | bash - 112 | [sudo] yum install -y nodejs 113 | [sudo] npm install lightsword -g 114 | ``` 115 | 116 | 一键安装 117 | 118 | ``` 119 | curl -sL https://raw.githubusercontent.com/UnsignedInt8/LightSword/master/misc/onekey_centos.sh | sudo -E bash - 120 | ``` 121 | 122 | 123 | Apple 用户 124 | --- 125 | 126 | Apple 用户只需要运行服务器,即可打开iOS客户端填写配置并投入使用。如需测试 DNS 泄漏,请访问[https://dnsleaktest.com](https://dnsleaktest.com)。 127 | 128 | 建议在运行的时候加入 --cluster 参数,以提升服务器性能和稳定性。 129 | 130 | 默认端口: 8900 131 | 132 | 默认密码: lightsword.neko 133 | 134 | 默认算法: AES-256-CFB 135 | 136 | License 137 | --- 138 | 139 | GPLv2.0 140 | -------------------------------------------------------------------------------- /bridge/README.md: -------------------------------------------------------------------------------- 1 | # LightSword 中转站 2 | 3 | 运行参数 4 | --- 5 | 6 | | 参数 | 完整参数 | 解释 | 7 | |------|----------|------| 8 | | -s | --server | 下个中转服务器地址或目标服务器地址 | 9 | | -p | --port | 下个中转服务器端口或目标服务器端口 | 10 | | -l | --listenport | 本地监听端口 (即客户端连接中转站的该端口) | 11 | | -d | --daemon | 控制命令,支持 restart, stop, status | 12 | | -f | --fork | 作为守护进程运行 (不支持 Windows) | 13 | 14 | 使用方法 15 | --- 16 | 17 | 1.安装并运行 18 | 19 | ``` 20 | $> [sudo] npm install -g lightsword 21 | $> lsbridge -s server_addr -p server_port -l listen_port -f 22 | ``` 23 | 24 | 2.指向中转站 25 | 26 | 把 LightSword 客户端指向中转服务器。把中转服务器搭建在墙内云提供商的主机上,可以加速访问墙外。 27 | 28 | ``` 29 | $> lslocal -s bridge_address -p bridge_port -f 30 | ``` -------------------------------------------------------------------------------- /bridge/app.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | require('../lib/socketEx'); 8 | require('kinq').enable(); 9 | require('async-node'); 10 | import * as net from 'net'; 11 | import { defaultServerPort } from '../common/constant'; 12 | 13 | export class App { 14 | 15 | constructor(options: { dstAddr: string, dstPort?: number, localPort?: number }) { 16 | let dstAddr = options.dstAddr; 17 | let dstPort = options.dstPort || defaultServerPort; 18 | let localPort = options.localPort || defaultServerPort; 19 | 20 | let server = net.createServer((socket) => { 21 | let transitSocket = net.createConnection(dstPort, dstAddr, () => { 22 | socket.pipe(transitSocket); 23 | transitSocket.pipe(socket); 24 | }); 25 | 26 | function dispose() { 27 | transitSocket.dispose(); 28 | socket.dispose(); 29 | } 30 | 31 | transitSocket.on('end', dispose); 32 | transitSocket.on('error', dispose); 33 | socket.on('end', dispose); 34 | socket.on('error', dispose); 35 | }); 36 | 37 | server.on('error', (err) => { 38 | console.log(err.message); 39 | process.exit(1); 40 | }); 41 | 42 | server.listen(localPort); 43 | } 44 | 45 | } 46 | 47 | if (!module.parent) { 48 | process.title = 'LightSword Bridge Debug Mode'; 49 | } -------------------------------------------------------------------------------- /bridge/bin/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | //----------------------------------- 4 | // Copyright(c) 2015 Neko 5 | //----------------------------------- 6 | 7 | import * as program from 'commander'; 8 | import { App } from '../app'; 9 | import * as path from 'path'; 10 | import * as ipc from '../../common/ipc'; 11 | import * as child from 'child_process'; 12 | 13 | program 14 | .version('0.6.0') 15 | .option('-s, --server
', 'Next Node Address', String) 16 | .option('-p, --port ', 'Next Node Server Port', Number.parseInt) 17 | .option('-l, --listenport ', 'Local Port', Number.parseInt) 18 | .option('-d, --daemon ', 'Daemon Control', String) 19 | .option('-f, --fork', 'Run as Daemon') 20 | .parse(process.argv); 21 | 22 | var args = program; 23 | var options = { 24 | dstAddr: args.server, 25 | dstPort: args.port, 26 | localPort: args.listenport 27 | }; 28 | 29 | if (args.daemon && !process.env.__daemon) { 30 | ipc.sendCommand('bridge', args.daemon, (code) => process.exit(code)); 31 | return; 32 | } 33 | 34 | if (!options.dstAddr) { 35 | console.error('Server Address not found.\n'); 36 | console.info('Example: lsbridge -s 127.0.0.1 -p 443\n'); 37 | process.exit(1); 38 | } 39 | 40 | if (args.fork && !process.env.__daemon) { 41 | console.info('Run as daemon'); 42 | process.env.__daemon = true; 43 | var cp = child.spawn(process.argv[1], process.argv.skip(2).toArray(), { detached: true, stdio: 'ignore', env: process.env, cwd: process.cwd() }); 44 | cp.unref(); 45 | console.info('Child PID: ' + cp.pid); 46 | process.exit(0); 47 | } 48 | 49 | if (process.env.__daemon) { 50 | ipc.IpcServer.start('bridge'); 51 | } 52 | 53 | new App(options); 54 | 55 | process.title = process.env.__daemon ? path.basename(process.argv[1]) + 'd' : 'LightSword Bridge'; 56 | -------------------------------------------------------------------------------- /build/bridge/app.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | require('../lib/socketEx'); 6 | require('kinq').enable(); 7 | require('async-node'); 8 | const net = require('net'); 9 | const constant_1 = require('../common/constant'); 10 | class App { 11 | constructor(options) { 12 | let dstAddr = options.dstAddr; 13 | let dstPort = options.dstPort || constant_1.defaultServerPort; 14 | let localPort = options.localPort || constant_1.defaultServerPort; 15 | let server = net.createServer((socket) => { 16 | let transitSocket = net.createConnection(dstPort, dstAddr, () => { 17 | socket.pipe(transitSocket); 18 | transitSocket.pipe(socket); 19 | }); 20 | function dispose() { 21 | transitSocket.dispose(); 22 | socket.dispose(); 23 | } 24 | transitSocket.on('end', dispose); 25 | transitSocket.on('error', dispose); 26 | socket.on('end', dispose); 27 | socket.on('error', dispose); 28 | }); 29 | server.on('error', (err) => { 30 | console.log(err.message); 31 | process.exit(1); 32 | }); 33 | server.listen(localPort); 34 | } 35 | } 36 | exports.App = App; 37 | if (!module.parent) { 38 | process.title = 'LightSword Bridge Debug Mode'; 39 | } 40 | -------------------------------------------------------------------------------- /build/bridge/bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | const program = require('commander'); 4 | const app_1 = require('../app'); 5 | const path = require('path'); 6 | const ipc = require('../../common/ipc'); 7 | const child = require('child_process'); 8 | program 9 | .version('0.6.0') 10 | .option('-s, --server
', 'Next Node Address', String) 11 | .option('-p, --port ', 'Next Node Server Port', Number.parseInt) 12 | .option('-l, --listenport ', 'Local Port', Number.parseInt) 13 | .option('-d, --daemon ', 'Daemon Control', String) 14 | .option('-f, --fork', 'Run as Daemon') 15 | .parse(process.argv); 16 | var args = program; 17 | var options = { 18 | dstAddr: args.server, 19 | dstPort: args.port, 20 | localPort: args.listenport 21 | }; 22 | if (args.daemon && !process.env.__daemon) { 23 | ipc.sendCommand('bridge', args.daemon, (code) => process.exit(code)); 24 | return; 25 | } 26 | if (!options.dstAddr) { 27 | console.error('Server Address not found.\n'); 28 | console.info('Example: lsbridge -s 127.0.0.1 -p 443\n'); 29 | process.exit(1); 30 | } 31 | if (args.fork && !process.env.__daemon) { 32 | console.info('Run as daemon'); 33 | process.env.__daemon = true; 34 | var cp = child.spawn(process.argv[1], process.argv.skip(2).toArray(), { detached: true, stdio: 'ignore', env: process.env, cwd: process.cwd() }); 35 | cp.unref(); 36 | console.info('Child PID: ' + cp.pid); 37 | process.exit(0); 38 | } 39 | if (process.env.__daemon) { 40 | ipc.IpcServer.start('bridge'); 41 | } 42 | new app_1.App(options); 43 | process.title = process.env.__daemon ? path.basename(process.argv[1]) + 'd' : 'LightSword Bridge'; 44 | -------------------------------------------------------------------------------- /build/client/app.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | require('../lib/socketEx'); 6 | require('kinq').enable(); 7 | require('async-node'); 8 | const constant_1 = require('../common/constant'); 9 | const localProxyServer_1 = require('./socks5/localProxyServer'); 10 | const remoteProxyServer_1 = require('./socks5/remoteProxyServer'); 11 | let localAddrs = ['127.0.0.1', 'localhost', undefined, null]; 12 | class App { 13 | constructor(options) { 14 | let defaultOptions = { 15 | listenAddr: 'localhost', 16 | listenPort: 1080, 17 | serverAddr: 'localhost', 18 | serverPort: constant_1.defaultServerPort, 19 | cipherAlgorithm: constant_1.defaultCipherAlgorithm, 20 | password: 'lightsword.neko', 21 | timeout: 60, 22 | bypassLocal: true 23 | }; 24 | options = options || defaultOptions; 25 | Object.getOwnPropertyNames(defaultOptions).forEach(n => options[n] = options[n] === undefined ? defaultOptions[n] : options[n]); 26 | let isLocalProxy = localAddrs.contains(options.serverAddr); 27 | let server = isLocalProxy ? new localProxyServer_1.LocalProxyServer(options) : new remoteProxyServer_1.RemoteProxyServer(options); 28 | server.start(); 29 | } 30 | } 31 | exports.App = App; 32 | if (!module.parent) { 33 | process.title = 'LightSword Client Debug Mode'; 34 | new App({ serverAddr: '::1', listenPort: 2002, bypassLocal: false }); 35 | } 36 | else { 37 | localAddrs.push('::1'); 38 | } 39 | -------------------------------------------------------------------------------- /build/client/bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | const program = require('commander'); 4 | const app_1 = require('../app'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const ipc = require('../../common/ipc'); 8 | const child = require('child_process'); 9 | program 10 | .version('0.6.0') 11 | .option('-s, --server ', 'Server Address', String) 12 | .option('-p, --port ', 'Server Port Number', Number.parseInt) 13 | .option('-l, --listenport ', 'Local Listening Port Number', Number.parseInt) 14 | .option('-m, --method ', 'Cipher Algorithm', String) 15 | .option('-k, --password ', 'Password', String) 16 | .option('-c, --config ', 'Configuration File Path', String) 17 | .option('-a, --any', 'Listen Any Connection') 18 | .option('-t, --timeout [number]', 'Timeout (second)') 19 | .option('-f, --fork', 'Run as Daemon') 20 | .option('-b, --dontbypasslocal', "DON'T Bypass Local Address") 21 | .option('-d, --daemon ', 'Daemon Control', String) 22 | .parse(process.argv); 23 | var args = program; 24 | function parseFile(path) { 25 | if (!path) 26 | return; 27 | if (!fs.existsSync(path)) 28 | return; 29 | var content = fs.readFileSync(path).toString(); 30 | try { 31 | return JSON.parse(content); 32 | } 33 | catch (ex) { 34 | console.warn('Configuration file error'); 35 | console.warn(ex.message); 36 | } 37 | } 38 | var fileOptions = parseFile(args.config) || {}; 39 | if (fileOptions) 40 | Object.getOwnPropertyNames(fileOptions).forEach(n => args[n] = args[n] === undefined ? fileOptions[n] : args[n]); 41 | var argsOptions = { 42 | listenAddr: args.any ? '' : 'localhost', 43 | listenPort: args.listenport, 44 | serverAddr: args.server, 45 | serverPort: args.port, 46 | cipherAlgorithm: args.method, 47 | password: args.password, 48 | timeout: args.timeout, 49 | bypassLocal: args.dontbypasslocal ? false : true 50 | }; 51 | if (args.fork && !process.env.__daemon) { 52 | console.info('Run as daemon'); 53 | process.env.__daemon = true; 54 | var cp = child.spawn(process.argv[1], process.argv.skip(2).toArray(), { detached: true, stdio: 'ignore', env: process.env, cwd: process.cwd() }); 55 | cp.unref(); 56 | console.log('Child PID: ', cp.pid); 57 | process.exit(0); 58 | } 59 | if (process.env.__daemon) { 60 | ipc.IpcServer.start('client'); 61 | } 62 | if (args.daemon && !process.env.__daemon) { 63 | ipc.sendCommand('client', args.daemon, (code) => process.exit(code)); 64 | } 65 | else { 66 | Object.getOwnPropertyNames(argsOptions).forEach(n => argsOptions[n] = argsOptions[n] === undefined ? fileOptions[n] : argsOptions[n]); 67 | if (!program.args.contains('service')) 68 | new app_1.App(argsOptions); 69 | process.title = process.env.__daemon ? path.basename(process.argv[1]) + 'd' : 'LightSword Client'; 70 | } 71 | -------------------------------------------------------------------------------- /build/client/socks5/localProxyServer.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | const net = require('net'); 14 | const dgram = require('dgram'); 15 | const socks5Server_1 = require('./socks5Server'); 16 | const socks5constant_1 = require('../../common/socks5constant'); 17 | const socks5Helper = require('../../common/socks5helper'); 18 | class LocalProxyServer extends socks5Server_1.Socks5Server { 19 | handleRequest(client, request) { 20 | let dst = socks5Helper.refineDestination(request); 21 | switch (dst.cmd) { 22 | case socks5constant_1.REQUEST_CMD.CONNECT: 23 | LocalProxyServer.connectServer(client, dst, request, this.timeout); 24 | break; 25 | case socks5constant_1.REQUEST_CMD.UDP_ASSOCIATE: 26 | LocalProxyServer.udpAssociate(client, dst); 27 | break; 28 | default: 29 | return false; 30 | } 31 | return true; 32 | } 33 | static bind(client, dst) { 34 | } 35 | static udpAssociate(client, dst) { 36 | let udpType = 'udp' + (net.isIP(dst.addr) || 4); 37 | let serverUdp = dgram.createSocket(udpType); 38 | serverUdp.bind(); 39 | serverUdp.unref(); 40 | serverUdp.on('listening', () => __awaiter(this, void 0, void 0, function* () { 41 | let udpAddr = serverUdp.address(); 42 | let reply = socks5Helper.createSocks5TcpReply(0x0, udpAddr.family === 'IPv4' ? socks5constant_1.ATYP.IPV4 : socks5constant_1.ATYP.IPV6, udpAddr.address, udpAddr.port); 43 | yield client.writeAsync(reply); 44 | })); 45 | let udpSet = new Set(); 46 | serverUdp.on('message', (msg, rinfo) => { 47 | let socketId = `${rinfo.address}:${rinfo.port}`; 48 | let dst = socks5Helper.refineDestination(msg); 49 | let proxyUdp = dgram.createSocket(udpType); 50 | proxyUdp.unref(); 51 | proxyUdp.on('message', (msg) => { 52 | let header = socks5Helper.createSocks5UdpHeader(rinfo.address, rinfo.port); 53 | let data = Buffer.concat([header, msg]); 54 | serverUdp.send(data, 0, data.length, rinfo.port, rinfo.address); 55 | }); 56 | proxyUdp.on('error', (err) => console.log(err.message)); 57 | proxyUdp.send(msg, dst.headerSize, msg.length - dst.headerSize, dst.port, dst.addr); 58 | udpSet.add(proxyUdp); 59 | }); 60 | function dispose() { 61 | client.dispose(); 62 | serverUdp.removeAllListeners(); 63 | serverUdp.close(); 64 | udpSet.forEach(udp => { 65 | udp.close(); 66 | udp.removeAllListeners(); 67 | }); 68 | udpSet.clear(); 69 | } 70 | serverUdp.on('error', dispose); 71 | client.on('error', dispose); 72 | client.on('end', dispose); 73 | } 74 | static connectServer(client, dst, request, timeout) { 75 | let proxySocket = net.createConnection(dst.port, dst.addr, () => __awaiter(this, void 0, void 0, function* () { 76 | let reply = new Buffer(request.length); 77 | request.copy(reply); 78 | reply[0] = 0x05; 79 | reply[1] = 0x00; 80 | yield client.writeAsync(reply); 81 | proxySocket.pipe(client); 82 | client.pipe(proxySocket); 83 | })); 84 | function dispose() { 85 | proxySocket.dispose(); 86 | client.dispose(); 87 | } 88 | proxySocket.on('end', dispose); 89 | proxySocket.on('error', dispose); 90 | client.on('end', dispose); 91 | client.on('error', dispose); 92 | proxySocket.setTimeout(timeout); 93 | client.setTimeout(timeout); 94 | } 95 | } 96 | exports.LocalProxyServer = LocalProxyServer; 97 | -------------------------------------------------------------------------------- /build/client/socks5/socks5Server.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | const net = require('net'); 14 | const socks5constant_1 = require('../../common/socks5constant'); 15 | class Socks5Server { 16 | constructor(options) { 17 | this.localArea = ['10.', '192.168.', 'localhost', '127.0.0.1', '172.16.', '::1', '169.254.0.0']; 18 | let me = this; 19 | if (options) 20 | Object.getOwnPropertyNames(options).forEach(n => me[n] = options[n]); 21 | } 22 | start() { 23 | if (this.server) 24 | return; 25 | let me = this; 26 | let server = net.createServer((client) => __awaiter(this, void 0, void 0, function* () { 27 | let data = yield client.readAsync(); 28 | if (!data) 29 | return client.dispose(); 30 | let reply = me.handleHandshake(data); 31 | yield client.writeAsync(reply.data); 32 | if (!reply.success) 33 | return client.dispose(); 34 | data = yield client.readAsync(); 35 | me.handleRequest(client, data); 36 | })); 37 | server.on('error', (err) => console.error(err.message)); 38 | server.listen(this.listenPort, this.listenAddr); 39 | this.server = server; 40 | } 41 | stop() { 42 | this.server.removeAllListeners(); 43 | this.server.close(); 44 | } 45 | handleHandshake(data) { 46 | let methodCount = data[1]; 47 | let code = data.skip(2).take(methodCount).contains(socks5constant_1.AUTHENTICATION.NOAUTH) 48 | ? socks5constant_1.AUTHENTICATION.NOAUTH 49 | : socks5constant_1.AUTHENTICATION.NONE; 50 | let success = code === socks5constant_1.AUTHENTICATION.NOAUTH; 51 | return { success: success, data: new Buffer([socks5constant_1.SOCKS_VER.V5, code]) }; 52 | } 53 | } 54 | exports.Socks5Server = Socks5Server; 55 | -------------------------------------------------------------------------------- /build/common/cipher.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const crypto = require('crypto'); 6 | exports.SupportedCiphers = { 7 | 'aes-128-cfb': [16, 16], 8 | 'aes-128-ofb': [16, 16], 9 | 'aes-192-cfb': [24, 16], 10 | 'aes-192-ofb': [24, 16], 11 | 'aes-256-cfb': [32, 16], 12 | 'aes-256-ofb': [32, 16], 13 | 'bf-cfb': [16, 8], 14 | 'camellia-128-cfb': [16, 16], 15 | 'camellia-192-cfb': [24, 16], 16 | 'camellia-256-cfb': [32, 16], 17 | 'cast5-cfb': [16, 8], 18 | 'des-cfb': [8, 8], 19 | 'idea-cfb': [16, 8], 20 | 'rc2-cfb': [16, 8], 21 | 'rc4': [16, 0], 22 | 'rc4-md5': [16, 16], 23 | 'seed-cfb': [16, 16], 24 | }; 25 | Object.freeze(exports.SupportedCiphers); 26 | function createCipher(algorithm, password, iv) { 27 | return createDeOrCipher('cipher', algorithm, password, iv); 28 | } 29 | exports.createCipher = createCipher; 30 | function createDecipher(algorithm, password, iv) { 31 | return createDeOrCipher('decipher', algorithm, password, iv).cipher; 32 | } 33 | exports.createDecipher = createDecipher; 34 | function createDeOrCipher(type, algorithm, password, iv) { 35 | let cipherAlgorithm = algorithm.toLowerCase(); 36 | let keyIv = exports.SupportedCiphers[cipherAlgorithm]; 37 | if (!keyIv) { 38 | cipherAlgorithm = 'aes-256-cfb'; 39 | keyIv = exports.SupportedCiphers[cipherAlgorithm]; 40 | } 41 | let key = new Buffer(password); 42 | let keyLength = keyIv[0]; 43 | if (key.length > keyLength) 44 | key = key.slice(0, keyLength); 45 | if (key.length < keyLength) 46 | key = new Buffer(password.repeat(keyLength / password.length + 1)).slice(0, keyLength); 47 | iv = iv || crypto.randomBytes(keyIv[1]); 48 | let cipher = type === 'cipher' ? crypto.createCipheriv(cipherAlgorithm, key, iv) : crypto.createDecipheriv(cipherAlgorithm, key, iv); 49 | return { cipher: cipher, iv: iv }; 50 | } 51 | -------------------------------------------------------------------------------- /build/common/constant.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | (function (VPN_TYPE) { 6 | VPN_TYPE[VPN_TYPE["APLVPN"] = 1] = "APLVPN"; 7 | VPN_TYPE[VPN_TYPE["SOCKS5"] = 5] = "SOCKS5"; 8 | VPN_TYPE[VPN_TYPE["OSXCL5"] = 165] = "OSXCL5"; 9 | })(exports.VPN_TYPE || (exports.VPN_TYPE = {})); 10 | var VPN_TYPE = exports.VPN_TYPE; 11 | exports.defaultCipherAlgorithm = 'aes-256-cfb'; 12 | exports.defaultPassword = 'lightsword.neko'; 13 | exports.defaultServerPort = 8900; 14 | class HandshakeOptions { 15 | } 16 | exports.HandshakeOptions = HandshakeOptions; 17 | class OSXCl5Options extends HandshakeOptions { 18 | } 19 | exports.OSXCl5Options = OSXCl5Options; 20 | -------------------------------------------------------------------------------- /build/common/ipc.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | const fs = require('fs'); 14 | const net = require('net'); 15 | const path = require('path'); 16 | const util = require('util'); 17 | const child = require('child_process'); 18 | (function (COMMAND) { 19 | COMMAND[COMMAND["STOP"] = 2] = "STOP"; 20 | COMMAND[COMMAND["RESTART"] = 3] = "RESTART"; 21 | COMMAND[COMMAND["STATUS"] = 101] = "STATUS"; 22 | COMMAND[COMMAND["STATUSJSON"] = 102] = "STATUSJSON"; 23 | })(exports.COMMAND || (exports.COMMAND = {})); 24 | var COMMAND = exports.COMMAND; 25 | class IpcServer { 26 | static start(tag) { 27 | let unixPath = util.format('/tmp/lightsword-%s.sock', tag); 28 | if (fs.existsSync(unixPath)) 29 | fs.unlinkSync(unixPath); 30 | let server = net.createServer((client) => __awaiter(this, void 0, void 0, function* () { 31 | let data = yield client.readAsync(); 32 | let msg = ''; 33 | let mem; 34 | switch (data[0]) { 35 | case COMMAND.STOP: 36 | msg = `${path.basename(process.argv[1])}d (PID: ${process.pid}) is going to quit.`; 37 | yield client.writeAsync(new Buffer(msg)); 38 | process.exit(0); 39 | break; 40 | case COMMAND.RESTART: 41 | let cp = child.spawn(process.argv[1], process.argv.skip(2).toArray(), { detached: true, stdio: 'ignore', env: process.env, cwd: process.cwd() }); 42 | cp.unref(); 43 | process.exit(0); 44 | break; 45 | case COMMAND.STATUS: 46 | mem = process.memoryUsage(); 47 | msg = `${path.basename(process.argv[1])}d (PID: ${process.pid}) is running.`; 48 | msg = util.format('%s\nHeap total: %sMB, heap used: %sMB, rss: %sMB', msg, (mem.heapTotal / 1024 / 1024).toPrecision(2), (mem.heapUsed / 1024 / 1024).toPrecision(2), (mem.rss / 1024 / 1024).toPrecision(2)); 49 | yield client.writeAsync(new Buffer(msg)); 50 | client.dispose(); 51 | break; 52 | case COMMAND.STATUSJSON: 53 | mem = process.memoryUsage(); 54 | let obj = { 55 | process: path.basename(process.argv[1]) + 'd', 56 | pid: process.pid, 57 | heapTotal: mem.heapTotal, 58 | heapUsed: mem.heapUsed, 59 | rss: mem.rss 60 | }; 61 | yield client.writeAsync(new Buffer(JSON.stringify(obj))); 62 | client.dispose(); 63 | break; 64 | } 65 | })); 66 | server.listen(unixPath); 67 | server.on('error', (err) => console.error(err.message)); 68 | } 69 | } 70 | exports.IpcServer = IpcServer; 71 | function sendCommand(tag, cmd, callback) { 72 | let cmdMap = { 73 | 'stop': COMMAND.STOP, 74 | 'restart': COMMAND.RESTART, 75 | 'status': COMMAND.STATUS, 76 | 'statusjson': COMMAND.STATUSJSON, 77 | }; 78 | let command = cmdMap[cmd.toLowerCase()]; 79 | if (!command) { 80 | console.error('Command is not supported'); 81 | return callback(1); 82 | } 83 | let path = util.format('/tmp/lightsword-%s.sock', tag); 84 | let socket = net.createConnection(path, () => __awaiter(this, void 0, void 0, function* () { 85 | yield socket.writeAsync(new Buffer([command])); 86 | let msg = yield socket.readAsync(); 87 | console.info(msg.toString('utf8')); 88 | socket.destroy(); 89 | callback(0); 90 | })); 91 | socket.on('error', (err) => { console.info(`${tag} is not running or unix socket error.`); callback(1); }); 92 | socket.setTimeout(5 * 1000); 93 | } 94 | exports.sendCommand = sendCommand; 95 | -------------------------------------------------------------------------------- /build/common/socks5constant.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | (function (AUTHENTICATION) { 6 | AUTHENTICATION[AUTHENTICATION["NOAUTH"] = 0] = "NOAUTH"; 7 | AUTHENTICATION[AUTHENTICATION["GSSAPI"] = 1] = "GSSAPI"; 8 | AUTHENTICATION[AUTHENTICATION["USERPASS"] = 2] = "USERPASS"; 9 | AUTHENTICATION[AUTHENTICATION["NONE"] = 255] = "NONE"; 10 | })(exports.AUTHENTICATION || (exports.AUTHENTICATION = {})); 11 | var AUTHENTICATION = exports.AUTHENTICATION; 12 | (function (REQUEST_CMD) { 13 | REQUEST_CMD[REQUEST_CMD["CONNECT"] = 1] = "CONNECT"; 14 | REQUEST_CMD[REQUEST_CMD["BIND"] = 2] = "BIND"; 15 | REQUEST_CMD[REQUEST_CMD["UDP_ASSOCIATE"] = 3] = "UDP_ASSOCIATE"; 16 | })(exports.REQUEST_CMD || (exports.REQUEST_CMD = {})); 17 | var REQUEST_CMD = exports.REQUEST_CMD; 18 | (function (ATYP) { 19 | ATYP[ATYP["IPV4"] = 1] = "IPV4"; 20 | ATYP[ATYP["DN"] = 3] = "DN"; 21 | ATYP[ATYP["IPV6"] = 4] = "IPV6"; 22 | })(exports.ATYP || (exports.ATYP = {})); 23 | var ATYP = exports.ATYP; 24 | (function (REPLY_CODE) { 25 | REPLY_CODE[REPLY_CODE["SUCCESS"] = 0] = "SUCCESS"; 26 | REPLY_CODE[REPLY_CODE["SOCKS_SERVER_FAILURE"] = 1] = "SOCKS_SERVER_FAILURE"; 27 | REPLY_CODE[REPLY_CODE["CONNECTION_NOT_ALLOWED"] = 2] = "CONNECTION_NOT_ALLOWED"; 28 | REPLY_CODE[REPLY_CODE["NETWORK_UNREACHABLE"] = 3] = "NETWORK_UNREACHABLE"; 29 | REPLY_CODE[REPLY_CODE["HOST_UNREACHABLE"] = 4] = "HOST_UNREACHABLE"; 30 | REPLY_CODE[REPLY_CODE["CONNECTION_REFUSED"] = 5] = "CONNECTION_REFUSED"; 31 | REPLY_CODE[REPLY_CODE["TTL_EXPIRED"] = 6] = "TTL_EXPIRED"; 32 | REPLY_CODE[REPLY_CODE["CMD_NOT_SUPPORTED"] = 7] = "CMD_NOT_SUPPORTED"; 33 | REPLY_CODE[REPLY_CODE["ADDR_TYPE_NOT_SUPPORTED"] = 8] = "ADDR_TYPE_NOT_SUPPORTED"; 34 | })(exports.REPLY_CODE || (exports.REPLY_CODE = {})); 35 | var REPLY_CODE = exports.REPLY_CODE; 36 | (function (SOCKS_VER) { 37 | SOCKS_VER[SOCKS_VER["V5"] = 5] = "V5"; 38 | })(exports.SOCKS_VER || (exports.SOCKS_VER = {})); 39 | var SOCKS_VER = exports.SOCKS_VER; 40 | -------------------------------------------------------------------------------- /build/common/socks5helper.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const net = require('net'); 6 | const util = require('util'); 7 | const socks5constant_1 = require('./socks5constant'); 8 | // 9 | // TCP 10 | // +----+-----+-------+------+----------+----------+ 11 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 12 | // +----+-----+-------+------+----------+----------+ 13 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 14 | // +----+-----+-------+------+----------+----------+ 15 | // 16 | // UDP 17 | // +----+------+------+----------+----------+----------+ 18 | // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 19 | // +----+------+------+----------+----------+----------+ 20 | // | 2 | 1 | 1 | Variable | 2 | Variable | 21 | // +----+------+------+----------+----------+----------+ 22 | function refineDestination(rawData) { 23 | if (rawData.length < 5) { 24 | return null; 25 | } 26 | let cmd = rawData[1]; 27 | let atyp = rawData[3]; 28 | let addr = ''; 29 | let dnLength = 0; 30 | switch (atyp) { 31 | case socks5constant_1.ATYP.DN: 32 | dnLength = rawData[4]; 33 | addr = rawData.toString('utf8', 5, 5 + dnLength); 34 | break; 35 | case socks5constant_1.ATYP.IPV4: 36 | dnLength = 4; 37 | addr = rawData.skip(4).take(4).aggregate((c, n) => c.length > 1 ? c + util.format('.%d', n) : util.format('%d.%d', c, n)); 38 | break; 39 | case socks5constant_1.ATYP.IPV6: 40 | dnLength = 16; 41 | let bytes = rawData.skip(4).take(16).toArray(); 42 | let ipv6 = ''; 43 | for (let b of bytes) { 44 | ipv6 += ('0' + b.toString(16)).substr(-2); 45 | } 46 | addr = ipv6.substr(0, 4); 47 | for (let i = 1; i < 8; i++) { 48 | addr = util.format('%s:%s', addr, ipv6.substr(4 * i, 4)); 49 | } 50 | break; 51 | } 52 | let headerSize = 4 + (atyp === socks5constant_1.ATYP.DN ? 1 : 0) + dnLength + 2; 53 | let port = rawData.readUInt16BE(headerSize - 2); 54 | return { cmd: cmd, addr: addr, port: port, headerSize: headerSize }; 55 | } 56 | exports.refineDestination = refineDestination; 57 | // +----+-----+-------+------+----------+----------+ 58 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 59 | // +----+-----+-------+------+----------+----------+ 60 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 61 | // +----+-----+-------+------+----------+----------+ 62 | function createSocks5TcpReply(rep, atyp, fullAddr, port) { 63 | let tuple = parseAddrToBytes(fullAddr); 64 | let type = tuple.type; 65 | let addr = tuple.addrBytes; 66 | let reply = [0x05, rep, 0x00, atyp]; 67 | if (type === socks5constant_1.ATYP.DN) 68 | reply.push(addr.length); 69 | reply = reply.concat(addr).concat([0x00, 0x00]); 70 | let buf = new Buffer(reply); 71 | buf.writeUInt16BE(port, buf.length - 2); 72 | return buf; 73 | } 74 | exports.createSocks5TcpReply = createSocks5TcpReply; 75 | // +----+------+------+----------+----------+----------+ 76 | // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 77 | // +----+------+------+----------+----------+----------+ 78 | // | 2 | 1 | 1 | Variable | 2 | Variable | 79 | // +----+------+------+----------+----------+----------+ 80 | function createSocks5UdpHeader(dstAddr, dstPort) { 81 | let tuple = parseAddrToBytes(dstAddr); 82 | let type = tuple.type; 83 | let addr = tuple.addrBytes; 84 | let reply = [0x0, 0x0, 0x0, type]; 85 | if (type === socks5constant_1.ATYP.DN) 86 | reply.push(addr.length); 87 | reply = reply.concat(addr).concat([0x00, 0x00]); 88 | let buf = new Buffer(reply); 89 | buf.writeUInt16BE(dstPort, buf.length - 2); 90 | return buf; 91 | } 92 | exports.createSocks5UdpHeader = createSocks5UdpHeader; 93 | function parseAddrToBytes(fullAddr) { 94 | let type = net.isIP(fullAddr); 95 | let addrBytes = []; 96 | switch (type) { 97 | case 4: 98 | addrBytes = fullAddr.split('.').select(s => Number.parseInt(s)).toArray(); 99 | break; 100 | case 6: 101 | addrBytes = fullAddr.split(':').select(s => [Number.parseInt(s.substr(0, 2), 16), Number.parseInt(s.substr(2, 2), 16)]).aggregate((c, n) => c.concat(n)); 102 | break; 103 | case 0: 104 | fullAddr.each((c, i) => addrBytes.push(fullAddr.charCodeAt(i))); 105 | break; 106 | } 107 | return { addrBytes: addrBytes, type: type ? (type === 4 ? socks5constant_1.ATYP.IPV4 : socks5constant_1.ATYP.IPV6) : socks5constant_1.ATYP.DN }; 108 | } 109 | -------------------------------------------------------------------------------- /build/lib/chacha20.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* chacha20 - 256 bits */ 3 | // Written in 2014 by Devi Mandiri. Public domain. 4 | // 5 | // Implementation derived from chacha-ref.c version 20080118 6 | // See for details: http://cr.yp.to/chacha/chacha-20080128.pdf 7 | function U8TO32_LE(x, i) { 8 | return x[i] | (x[i + 1] << 8) | (x[i + 2] << 16) | (x[i + 3] << 24); 9 | } 10 | function U32TO8_LE(x, i, u) { 11 | x[i] = u; 12 | u >>>= 8; 13 | x[i + 1] = u; 14 | u >>>= 8; 15 | x[i + 2] = u; 16 | u >>>= 8; 17 | x[i + 3] = u; 18 | } 19 | function ROTATE(v, c) { 20 | return (v << c) | (v >>> (32 - c)); 21 | } 22 | class Chacha20 { 23 | constructor(key, nonce, counter) { 24 | this.input = new Uint32Array(16); 25 | // https://tools.ietf.org/html/draft-irtf-cfrg-chacha20-poly1305-01#section-2.3 26 | this.input[0] = 1634760805; 27 | this.input[1] = 857760878; 28 | this.input[2] = 2036477234; 29 | this.input[3] = 1797285236; 30 | this.input[4] = U8TO32_LE(key, 0); 31 | this.input[5] = U8TO32_LE(key, 4); 32 | this.input[6] = U8TO32_LE(key, 8); 33 | this.input[7] = U8TO32_LE(key, 12); 34 | this.input[8] = U8TO32_LE(key, 16); 35 | this.input[9] = U8TO32_LE(key, 20); 36 | this.input[10] = U8TO32_LE(key, 24); 37 | this.input[11] = U8TO32_LE(key, 28); 38 | // be compatible with the reference ChaCha depending on the nonce size 39 | if (nonce.length == 12) { 40 | this.input[12] = counter; 41 | this.input[13] = U8TO32_LE(nonce, 0); 42 | this.input[14] = U8TO32_LE(nonce, 4); 43 | this.input[15] = U8TO32_LE(nonce, 8); 44 | } 45 | else { 46 | this.input[12] = counter; 47 | this.input[13] = 0; 48 | this.input[14] = U8TO32_LE(nonce, 0); 49 | this.input[15] = U8TO32_LE(nonce, 4); 50 | } 51 | } 52 | update(raw) { 53 | let cipherText = new Buffer(raw.length); 54 | this.encrypt(cipherText, raw, raw.length); 55 | return cipherText; 56 | } 57 | final() { 58 | return new Buffer(0); 59 | } 60 | quarterRound(x, a, b, c, d) { 61 | x[a] += x[b]; 62 | x[d] = ROTATE(x[d] ^ x[a], 16); 63 | x[c] += x[d]; 64 | x[b] = ROTATE(x[b] ^ x[c], 12); 65 | x[a] += x[b]; 66 | x[d] = ROTATE(x[d] ^ x[a], 8); 67 | x[c] += x[d]; 68 | x[b] = ROTATE(x[b] ^ x[c], 7); 69 | } 70 | encrypt(dst, src, len) { 71 | let x = new Uint32Array(16); 72 | let output = new Uint8Array(64); 73 | let i, dpos = 0, spos = 0; 74 | while (len > 0) { 75 | for (i = 16; i--;) 76 | x[i] = this.input[i]; 77 | for (i = 20; i > 0; i -= 2) { 78 | this.quarterRound(x, 0, 4, 8, 12); 79 | this.quarterRound(x, 1, 5, 9, 13); 80 | this.quarterRound(x, 2, 6, 10, 14); 81 | this.quarterRound(x, 3, 7, 11, 15); 82 | this.quarterRound(x, 0, 5, 10, 15); 83 | this.quarterRound(x, 1, 6, 11, 12); 84 | this.quarterRound(x, 2, 7, 8, 13); 85 | this.quarterRound(x, 3, 4, 9, 14); 86 | } 87 | for (i = 16; i--;) 88 | x[i] += this.input[i]; 89 | for (i = 16; i--;) 90 | U32TO8_LE(output, 4 * i, x[i]); 91 | this.input[12] += 1; 92 | if (!this.input[12]) { 93 | this.input[13] += 1; 94 | } 95 | if (len <= 64) { 96 | for (i = len; i--;) { 97 | dst[i + dpos] = src[i + spos] ^ output[i]; 98 | } 99 | return; 100 | } 101 | for (i = 64; i--;) { 102 | dst[i + dpos] = src[i + spos] ^ output[i]; 103 | } 104 | len -= 64; 105 | spos += 64; 106 | dpos += 64; 107 | } 108 | } 109 | } 110 | exports.Chacha20 = Chacha20; 111 | -------------------------------------------------------------------------------- /build/lib/chacha20stream.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const chacha20_1 = require('./chacha20'); 6 | const stream = require('stream'); 7 | class Chacha20Stream extends stream.Transform { 8 | constructor(key, iv, counter) { 9 | super(); 10 | this.chacha20 = new chacha20_1.Chacha20(key, iv, counter); 11 | } 12 | _transform(chunk, encoding, done) { 13 | let me = this; 14 | this.push(me.chacha20.update(chunk)); 15 | done(); 16 | } 17 | update(raw) { 18 | return this.chacha20.update(raw); 19 | } 20 | final() { 21 | return new Buffer(0); 22 | } 23 | } 24 | exports.Chacha20Stream = Chacha20Stream; 25 | -------------------------------------------------------------------------------- /build/lib/cipher.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { 6 | return new Promise(function (resolve, reject) { 7 | generator = generator.call(thisArg, _arguments); 8 | function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } 9 | function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } 10 | function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } 11 | function step(verb, value) { 12 | var result = generator[verb](value); 13 | result.done ? resolve(result.value) : cast(result.value).then(onfulfill, onreject); 14 | } 15 | step("next", void 0); 16 | }); 17 | }; 18 | var crypto = require('crypto'); 19 | exports.SupportedCiphers = { 20 | 'aes-128-cfb': [16, 16], 21 | 'aes-128-ofb': [16, 16], 22 | 'aes-192-cfb': [24, 16], 23 | 'aes-192-ofb': [24, 16], 24 | 'aes-256-cfb': [32, 16], 25 | 'aes-256-ofb': [32, 16], 26 | 'bf-cfb': [16, 8], 27 | 'camellia-128-cfb': [16, 16], 28 | 'camellia-192-cfb': [24, 16], 29 | 'camellia-256-cfb': [32, 16], 30 | 'cast5-cfb': [16, 8], 31 | 'des-cfb': [8, 8], 32 | 'idea-cfb': [16, 8], 33 | 'rc2-cfb': [16, 8], 34 | 'rc4': [16, 0], 35 | 'rc4-md5': [16, 16], 36 | 'seed-cfb': [16, 16], 37 | }; 38 | Object.freeze(exports.SupportedCiphers); 39 | function createCipher(algorithm, password, iv) { 40 | return createDeOrCipher('cipher', algorithm, password, iv); 41 | } 42 | exports.createCipher = createCipher; 43 | function createDecipher(algorithm, password, iv) { 44 | return createDeOrCipher('decipher', algorithm, password, iv).cipher; 45 | } 46 | exports.createDecipher = createDecipher; 47 | function createDeOrCipher(type, algorithm, password, iv) { 48 | let cipherAlgorithm = algorithm.toLowerCase(); 49 | let keyIv = exports.SupportedCiphers[cipherAlgorithm]; 50 | if (!keyIv) { 51 | cipherAlgorithm = 'aes-256-cfb'; 52 | keyIv = exports.SupportedCiphers[cipherAlgorithm]; 53 | } 54 | let key = new Buffer(password); 55 | let keyLength = keyIv[0]; 56 | if (key.length > keyLength) 57 | key = key.slice(0, keyLength); 58 | if (key.length < keyLength) 59 | key = new Buffer(password.repeat(keyLength / password.length + 1)).slice(0, keyLength); 60 | iv = iv || crypto.randomBytes(keyIv[1]); 61 | let cipher = type === 'cipher' ? crypto.createCipheriv(cipherAlgorithm, key, iv) : crypto.createDecipheriv(cipherAlgorithm, key, iv); 62 | return { cipher, iv }; 63 | } 64 | -------------------------------------------------------------------------------- /build/lib/cipherstream.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const stream = require('stream'); 6 | const crypto = require('crypto'); 7 | class CipherStream extends stream.Transform { 8 | constructor(encryptOrDecrypt, algorithm, key, iv, segmentSize) { 9 | super(); 10 | this.encrypt = false; 11 | this.algorithm = ''; 12 | this.encrypt = encryptOrDecrypt; 13 | this.algorithm = algorithm; 14 | this.key = key; 15 | this.iv = iv; 16 | this.segmentSize = segmentSize; 17 | } 18 | _transform(chunk, encoding, done) { 19 | let me = this; 20 | let cipher = crypto.createCipheriv(me.algorithm, me.key, me.iv); 21 | } 22 | } 23 | exports.CipherStream = CipherStream; 24 | -------------------------------------------------------------------------------- /build/lib/constant.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | (function (VPN_TYPE) { 6 | VPN_TYPE[VPN_TYPE["SOCKS5"] = 5] = "SOCKS5"; 7 | VPN_TYPE[VPN_TYPE["OSXCL5"] = 165] = "OSXCL5"; 8 | })(exports.VPN_TYPE || (exports.VPN_TYPE = {})); 9 | var VPN_TYPE = exports.VPN_TYPE; 10 | exports.defaultCipherAlgorithm = 'aes-256-cfb'; 11 | exports.defaultPassword = 'lightsword.neko'; 12 | exports.defaultServerPort = 8900; 13 | class Socks5Options { 14 | } 15 | exports.Socks5Options = Socks5Options; 16 | class OSXCl5Options extends Socks5Options { 17 | } 18 | exports.OSXCl5Options = OSXCl5Options; 19 | -------------------------------------------------------------------------------- /build/lib/ipc.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { 6 | return new Promise(function (resolve, reject) { 7 | generator = generator.call(thisArg, _arguments); 8 | function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } 9 | function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } 10 | function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } 11 | function step(verb, value) { 12 | var result = generator[verb](value); 13 | result.done ? resolve(result.value) : cast(result.value).then(onfulfill, onreject); 14 | } 15 | step("next", void 0); 16 | }); 17 | }; 18 | var fs = require('fs'); 19 | var net = require('net'); 20 | var path = require('path'); 21 | var util = require('util'); 22 | var child = require('child_process'); 23 | (function (COMMAND) { 24 | COMMAND[COMMAND["STOP"] = 2] = "STOP"; 25 | COMMAND[COMMAND["RESTART"] = 3] = "RESTART"; 26 | COMMAND[COMMAND["STATUS"] = 101] = "STATUS"; 27 | COMMAND[COMMAND["STATUSJSON"] = 102] = "STATUSJSON"; 28 | })(exports.COMMAND || (exports.COMMAND = {})); 29 | var COMMAND = exports.COMMAND; 30 | class IpcServer { 31 | static start(tag) { 32 | let unixPath = util.format('/tmp/lightsword-%s.sock', tag); 33 | if (fs.existsSync(unixPath)) 34 | fs.unlinkSync(unixPath); 35 | let server = net.createServer((client) => __awaiter(this, void 0, Promise, function* () { 36 | let data = yield client.readAsync(); 37 | let msg = ''; 38 | let mem; 39 | switch (data[0]) { 40 | case COMMAND.STOP: 41 | msg = `${path.basename(process.argv[1])}d (PID: ${process.pid}) is going to quit.`; 42 | yield client.writeAsync(new Buffer(msg)); 43 | process.exit(0); 44 | break; 45 | case COMMAND.RESTART: 46 | let cp = child.spawn(process.argv[1], process.argv.skip(2).toArray(), { detached: true, stdio: 'ignore', env: process.env, cwd: process.cwd() }); 47 | cp.unref(); 48 | process.exit(0); 49 | break; 50 | case COMMAND.STATUS: 51 | mem = process.memoryUsage(); 52 | msg = `${path.basename(process.argv[1])}d (PID: ${process.pid}) is running.`; 53 | msg = util.format('%s\nHeap total: %sMB, heap used: %sMB, rss: %sMB', msg, (mem.heapTotal / 1024 / 1024).toPrecision(2), (mem.heapUsed / 1024 / 1024).toPrecision(2), (mem.rss / 1024 / 1024).toPrecision(2)); 54 | yield client.writeAsync(new Buffer(msg)); 55 | client.dispose(); 56 | break; 57 | case COMMAND.STATUSJSON: 58 | mem = process.memoryUsage(); 59 | let obj = { 60 | process: path.basename(process.argv[1]) + 'd', 61 | pid: process.pid, 62 | heapTotal: mem.heapTotal, 63 | heapUsed: mem.heapUsed, 64 | rss: mem.rss 65 | }; 66 | yield client.writeAsync(new Buffer(JSON.stringify(obj))); 67 | client.dispose(); 68 | break; 69 | } 70 | })); 71 | server.listen(unixPath); 72 | server.on('error', (err) => console.error(err.message)); 73 | } 74 | } 75 | exports.IpcServer = IpcServer; 76 | function sendCommand(tag, cmd, callback) { 77 | let cmdMap = { 78 | 'stop': COMMAND.STOP, 79 | 'restart': COMMAND.RESTART, 80 | 'status': COMMAND.STATUS, 81 | 'statusjson': COMMAND.STATUSJSON, 82 | }; 83 | let command = cmdMap[cmd.toLowerCase()]; 84 | if (!command) { 85 | console.error('Command is not supported'); 86 | return callback(1); 87 | } 88 | let path = util.format('/tmp/lightsword-%s.sock', tag); 89 | let socket = net.createConnection(path, () => __awaiter(this, void 0, Promise, function* () { 90 | yield socket.writeAsync(new Buffer([command])); 91 | let msg = yield socket.readAsync(); 92 | console.info(msg.toString('utf8')); 93 | socket.destroy(); 94 | callback(0); 95 | })); 96 | socket.on('error', (err) => { console.info(`${tag} is not running or unix socket error.`); callback(1); }); 97 | socket.setTimeout(5 * 1000); 98 | } 99 | exports.sendCommand = sendCommand; 100 | -------------------------------------------------------------------------------- /build/lib/pkcs7.js: -------------------------------------------------------------------------------- 1 | /* 2 | * pkcs7.pad 3 | * https://github.com/brightcove/pkcs7 4 | * 5 | * Copyright (c) 2014 Brightcove 6 | * Licensed under the apache2 license. 7 | */ 8 | 'use strict'; 9 | // Pre-define the padding values 10 | const PADDING = [ 11 | [16, 16, 16, 16, 12 | 16, 16, 16, 16, 13 | 16, 16, 16, 16, 14 | 16, 16, 16, 16], 15 | [15, 15, 15, 15, 16 | 15, 15, 15, 15, 17 | 15, 15, 15, 15, 18 | 15, 15, 15], 19 | [14, 14, 14, 14, 20 | 14, 14, 14, 14, 21 | 14, 14, 14, 14, 22 | 14, 14], 23 | [13, 13, 13, 13, 24 | 13, 13, 13, 13, 25 | 13, 13, 13, 13, 26 | 13], 27 | [12, 12, 12, 12, 28 | 12, 12, 12, 12, 29 | 12, 12, 12, 12], 30 | [11, 11, 11, 11, 31 | 11, 11, 11, 11, 32 | 11, 11, 11], 33 | [10, 10, 10, 10, 34 | 10, 10, 10, 10, 35 | 10, 10], 36 | [9, 9, 9, 9, 37 | 9, 9, 9, 9, 38 | 9], 39 | [8, 8, 8, 8, 40 | 8, 8, 8, 8], 41 | [7, 7, 7, 7, 42 | 7, 7, 7], 43 | [6, 6, 6, 6, 44 | 6, 6], 45 | [5, 5, 5, 5, 46 | 5], 47 | [4, 4, 4, 4], 48 | [3, 3, 3], 49 | [2, 2], 50 | [1] 51 | ]; 52 | function pad(plaintext) { 53 | var padding = PADDING[(plaintext.length % 16) || 0], result = new Uint8Array(plaintext.length + padding.length); 54 | result.set(plaintext); 55 | result.set(padding, plaintext.length); 56 | return result; 57 | } 58 | exports.pad = pad; 59 | ; 60 | function unpad(padded) { 61 | return padded.subarray(0, padded.length - padded[padded.length - 1]); 62 | } 63 | exports.unpad = unpad; 64 | ; 65 | exports.PKCS7Size = 16; 66 | -------------------------------------------------------------------------------- /build/lib/socketEx.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const net = require('net'); 6 | net.Socket.prototype.dispose = function () { 7 | this.removeAllListeners(); 8 | this.end(); 9 | this.destroy(); 10 | }; 11 | -------------------------------------------------------------------------------- /build/lib/socks5Constant.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { 6 | return new Promise(function (resolve, reject) { 7 | generator = generator.call(thisArg, _arguments); 8 | function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } 9 | function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } 10 | function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } 11 | function step(verb, value) { 12 | var result = generator[verb](value); 13 | result.done ? resolve(result.value) : cast(result.value).then(onfulfill, onreject); 14 | } 15 | step("next", void 0); 16 | }); 17 | }; 18 | (function (AUTHENTICATION) { 19 | AUTHENTICATION[AUTHENTICATION["NOAUTH"] = 0] = "NOAUTH"; 20 | AUTHENTICATION[AUTHENTICATION["GSSAPI"] = 1] = "GSSAPI"; 21 | AUTHENTICATION[AUTHENTICATION["USERPASS"] = 2] = "USERPASS"; 22 | AUTHENTICATION[AUTHENTICATION["NONE"] = 255] = "NONE"; 23 | })(exports.AUTHENTICATION || (exports.AUTHENTICATION = {})); 24 | var AUTHENTICATION = exports.AUTHENTICATION; 25 | (function (REQUEST_CMD) { 26 | REQUEST_CMD[REQUEST_CMD["CONNECT"] = 1] = "CONNECT"; 27 | REQUEST_CMD[REQUEST_CMD["BIND"] = 2] = "BIND"; 28 | REQUEST_CMD[REQUEST_CMD["UDP_ASSOCIATE"] = 3] = "UDP_ASSOCIATE"; 29 | })(exports.REQUEST_CMD || (exports.REQUEST_CMD = {})); 30 | var REQUEST_CMD = exports.REQUEST_CMD; 31 | (function (ATYP) { 32 | ATYP[ATYP["IPV4"] = 1] = "IPV4"; 33 | ATYP[ATYP["DN"] = 3] = "DN"; 34 | ATYP[ATYP["IPV6"] = 4] = "IPV6"; 35 | })(exports.ATYP || (exports.ATYP = {})); 36 | var ATYP = exports.ATYP; 37 | (function (REPLY_CODE) { 38 | REPLY_CODE[REPLY_CODE["SUCCESS"] = 0] = "SUCCESS"; 39 | REPLY_CODE[REPLY_CODE["SOCKS_SERVER_FAILURE"] = 1] = "SOCKS_SERVER_FAILURE"; 40 | REPLY_CODE[REPLY_CODE["CONNECTION_NOT_ALLOWED"] = 2] = "CONNECTION_NOT_ALLOWED"; 41 | REPLY_CODE[REPLY_CODE["NETWORK_UNREACHABLE"] = 3] = "NETWORK_UNREACHABLE"; 42 | REPLY_CODE[REPLY_CODE["HOST_UNREACHABLE"] = 4] = "HOST_UNREACHABLE"; 43 | REPLY_CODE[REPLY_CODE["CONNECTION_REFUSED"] = 5] = "CONNECTION_REFUSED"; 44 | REPLY_CODE[REPLY_CODE["TTL_EXPIRED"] = 6] = "TTL_EXPIRED"; 45 | REPLY_CODE[REPLY_CODE["CMD_NOT_SUPPORTED"] = 7] = "CMD_NOT_SUPPORTED"; 46 | REPLY_CODE[REPLY_CODE["ADDR_TYPE_NOT_SUPPORTED"] = 8] = "ADDR_TYPE_NOT_SUPPORTED"; 47 | })(exports.REPLY_CODE || (exports.REPLY_CODE = {})); 48 | var REPLY_CODE = exports.REPLY_CODE; 49 | (function (SOCKS_VER) { 50 | SOCKS_VER[SOCKS_VER["V5"] = 5] = "V5"; 51 | })(exports.SOCKS_VER || (exports.SOCKS_VER = {})); 52 | var SOCKS_VER = exports.SOCKS_VER; 53 | -------------------------------------------------------------------------------- /build/lib/socks5Helper.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { 6 | return new Promise(function (resolve, reject) { 7 | generator = generator.call(thisArg, _arguments); 8 | function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } 9 | function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } 10 | function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } 11 | function step(verb, value) { 12 | var result = generator[verb](value); 13 | result.done ? resolve(result.value) : cast(result.value).then(onfulfill, onreject); 14 | } 15 | step("next", void 0); 16 | }); 17 | }; 18 | var net = require('net'); 19 | var util = require('util'); 20 | var socks5Constant_1 = require('./socks5Constant'); 21 | // 22 | // TCP 23 | // +----+-----+-------+------+----------+----------+ 24 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 25 | // +----+-----+-------+------+----------+----------+ 26 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 27 | // +----+-----+-------+------+----------+----------+ 28 | // 29 | // UDP 30 | // +----+------+------+----------+----------+----------+ 31 | // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 32 | // +----+------+------+----------+----------+----------+ 33 | // | 2 | 1 | 1 | Variable | 2 | Variable | 34 | // +----+------+------+----------+----------+----------+ 35 | function refineDestination(rawData) { 36 | if (rawData.length < 5) { 37 | return null; 38 | } 39 | let cmd = rawData[1]; 40 | let atyp = rawData[3]; 41 | let addr = ''; 42 | let dnLength = 0; 43 | switch (atyp) { 44 | case socks5Constant_1.ATYP.DN: 45 | dnLength = rawData[4]; 46 | addr = rawData.toString('utf8', 5, 5 + dnLength); 47 | break; 48 | case socks5Constant_1.ATYP.IPV4: 49 | dnLength = 4; 50 | addr = rawData.skip(4).take(4).aggregate((c, n) => c.length > 1 ? c + util.format('.%d', n) : util.format('%d.%d', c, n)); 51 | break; 52 | case socks5Constant_1.ATYP.IPV6: 53 | dnLength = 16; 54 | let bytes = rawData.skip(4).take(16).toArray(); 55 | let ipv6 = ''; 56 | for (let b of bytes) { 57 | ipv6 += ('0' + b.toString(16)).substr(-2); 58 | } 59 | addr = ipv6.substr(0, 4); 60 | for (let i = 1; i < 8; i++) { 61 | addr = util.format('%s:%s', addr, ipv6.substr(4 * i, 4)); 62 | } 63 | break; 64 | } 65 | let headerSize = 4 + (atyp === socks5Constant_1.ATYP.DN ? 1 : 0) + dnLength + 2; 66 | let port = rawData.readUInt16BE(headerSize - 2); 67 | return { cmd, addr, port, headerSize }; 68 | } 69 | exports.refineDestination = refineDestination; 70 | // +----+-----+-------+------+----------+----------+ 71 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 72 | // +----+-----+-------+------+----------+----------+ 73 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 74 | // +----+-----+-------+------+----------+----------+ 75 | function createSocks5TcpReply(rep, atyp, fullAddr, port) { 76 | let tuple = parseAddrToBytes(fullAddr); 77 | let type = tuple.type; 78 | let addr = tuple.addrBytes; 79 | let reply = [0x05, rep, 0x00, atyp]; 80 | if (type === socks5Constant_1.ATYP.DN) 81 | reply.push(addr.length); 82 | reply = reply.concat(addr).concat([0x00, 0x00]); 83 | let buf = new Buffer(reply); 84 | buf.writeUInt16BE(port, buf.length - 2); 85 | return buf; 86 | } 87 | exports.createSocks5TcpReply = createSocks5TcpReply; 88 | // +----+------+------+----------+----------+----------+ 89 | // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 90 | // +----+------+------+----------+----------+----------+ 91 | // | 2 | 1 | 1 | Variable | 2 | Variable | 92 | // +----+------+------+----------+----------+----------+ 93 | function createSocks5UdpHeader(dstAddr, dstPort) { 94 | let tuple = parseAddrToBytes(dstAddr); 95 | let type = tuple.type; 96 | let addr = tuple.addrBytes; 97 | let reply = [0x0, 0x0, 0x0, type]; 98 | if (type === socks5Constant_1.ATYP.DN) 99 | reply.push(addr.length); 100 | reply = reply.concat(addr).concat([0x00, 0x00]); 101 | let buf = new Buffer(reply); 102 | buf.writeUInt16BE(dstPort, buf.length - 2); 103 | return buf; 104 | } 105 | exports.createSocks5UdpHeader = createSocks5UdpHeader; 106 | function parseAddrToBytes(fullAddr) { 107 | let type = net.isIP(fullAddr); 108 | let addrBytes = []; 109 | switch (type) { 110 | case 4: 111 | addrBytes = fullAddr.split('.').select(s => Number.parseInt(s)).toArray(); 112 | break; 113 | case 6: 114 | addrBytes = fullAddr.split(':').select(s => [Number.parseInt(s.substr(0, 2), 16), Number.parseInt(s.substr(2, 2), 16)]).aggregate((c, n) => c.concat(n)); 115 | break; 116 | case 0: 117 | fullAddr.each((c, i) => addrBytes.push(fullAddr.charCodeAt(i))); 118 | break; 119 | } 120 | return { addrBytes, type: type ? (type === 4 ? socks5Constant_1.ATYP.IPV4 : socks5Constant_1.ATYP.IPV6) : socks5Constant_1.ATYP.DN }; 121 | } 122 | -------------------------------------------------------------------------------- /build/lib/speedstream.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const stream = require('stream'); 6 | class SpeedStream extends stream.Transform { 7 | /** 8 | * speed: KB/s 9 | */ 10 | constructor(speed) { 11 | super(); 12 | this.bytesPerSecond = 0; 13 | this.sentBytes = 0; 14 | this.chunkCount = 0; 15 | this.interval = 0; 16 | if (speed < 1) 17 | throw Error('can be negative speed'); 18 | this.bytesPerSecond = speed * 1024; 19 | } 20 | _transform(chunk, encoding, done) { 21 | let me = this; 22 | if (!me.writable) 23 | return; 24 | setTimeout(() => { 25 | if (!me.writable) { 26 | me.interval = 0; 27 | me.sentBytes = 0; 28 | me.chunkCount = 0; 29 | return; 30 | } 31 | me.push(chunk, encoding); 32 | done(); 33 | if (me.sentBytes > me.bytesPerSecond) { 34 | let avgChunkSize = me.sentBytes / me.chunkCount; 35 | me.interval = avgChunkSize / me.bytesPerSecond * 1000; 36 | me.sentBytes = 0; 37 | me.chunkCount = 0; 38 | } 39 | }, me.interval).unref(); 40 | me.sentBytes += chunk.length; 41 | me.chunkCount++; 42 | } 43 | } 44 | exports.SpeedStream = SpeedStream; 45 | -------------------------------------------------------------------------------- /build/lib/xorstream.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const stream = require('stream'); 6 | class XorStream extends stream.Transform { 7 | constructor(x) { 8 | super(); 9 | this.xor = x; 10 | } 11 | _transform(chunk, encoding, done) { 12 | let me = this; 13 | if (Buffer.isBuffer(chunk)) { 14 | let data = chunk; 15 | this.push(new Buffer(data.select(n => n ^ me.xor).toArray())); 16 | } 17 | else { 18 | this.push(chunk); 19 | } 20 | done(); 21 | } 22 | } 23 | exports.XorStream = XorStream; 24 | -------------------------------------------------------------------------------- /build/server/aplvpn/index.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | const crypto = require('crypto'); 14 | const protocols_1 = require('./protocols'); 15 | const cryptoEx = require('../../common/cipher'); 16 | const addrHelper = require('../lib/addressHelper'); 17 | const udp_1 = require('./udp'); 18 | const tcp_1 = require('./tcp'); 19 | const SupportedIPVers = [protocols_1.IP_VER.V4, protocols_1.IP_VER.V6]; 20 | const SupportedProtocols = [protocols_1.Protocols.TCP, protocols_1.Protocols.UDP]; 21 | function handleAppleVPN(client, handshakeData, options) { 22 | return __awaiter(this, void 0, Promise, function* () { 23 | if (handshakeData.length < 9) 24 | return false; 25 | let handshake = null; 26 | try { 27 | handshake = extractHandeshake(handshakeData); 28 | if (!SupportedIPVers.contains(handshake.ipVer)) 29 | return false; 30 | if (!SupportedProtocols.contains(handshake.payloadProtocol)) 31 | return false; 32 | } 33 | catch (error) { 34 | return false; 35 | } 36 | if (handshake.flags === 0x00 && handshake.destHost === '0.0.0.0' && handshake.destPort === 0) { 37 | try { 38 | yield handleHandshake(client, handshake, options); 39 | } 40 | catch (error) { 41 | return false; 42 | } 43 | return true; 44 | } 45 | if (addrHelper.isIllegalAddress(handshake.destHost)) { 46 | client.dispose(); 47 | return true; 48 | } 49 | switch (handshake.payloadProtocol) { 50 | case protocols_1.Protocols.TCP: 51 | tcp_1.handleTCP(client, handshake, options); 52 | return true; 53 | case protocols_1.Protocols.UDP: 54 | udp_1.handleUDP(client, handshake, options); 55 | return true; 56 | } 57 | return false; 58 | }); 59 | } 60 | exports.handleAppleVPN = handleAppleVPN; 61 | function extractHandeshake(data) { 62 | let ipVer = data[0]; 63 | let payloadProtocol = data[1]; 64 | let flags = data[2]; 65 | let ipLength = ipVer == protocols_1.IP_VER.V4 ? 4 : 16; 66 | let destAddress = data.skip(3).take(ipLength).toArray(); 67 | let destPort = data.readUInt16BE(3 + ipLength); 68 | let extra = data.slice(3 + ipLength + 2); 69 | let destHost = addrHelper.ntoa(destAddress); 70 | return { ipVer: ipVer, payloadProtocol: payloadProtocol, flags: flags, destAddress: destAddress, destHost: destHost, destPort: destPort, extra: extra }; 71 | } 72 | function handleHandshake(client, handshake, options) { 73 | return __awaiter(this, void 0, void 0, function* () { 74 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, handshake.extra).cipher; 75 | let md5 = crypto.createHash('md5').update(handshake.extra).digest(); 76 | let randomPadding = new Buffer(Number((Math.random() * 128).toFixed())); 77 | client.on('error', () => { }); 78 | yield client.writeAsync(Buffer.concat([cipher.update(md5), cipher.update(randomPadding)])); 79 | client.dispose(); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /build/server/aplvpn/protocols.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | (function (IP_VER) { 6 | IP_VER[IP_VER["V4"] = 4] = "V4"; 7 | IP_VER[IP_VER["V6"] = 6] = "V6"; 8 | })(exports.IP_VER || (exports.IP_VER = {})); 9 | var IP_VER = exports.IP_VER; 10 | (function (Protocols) { 11 | Protocols[Protocols["TCP"] = 6] = "TCP"; 12 | Protocols[Protocols["UDP"] = 17] = "UDP"; 13 | })(exports.Protocols || (exports.Protocols = {})); 14 | var Protocols = exports.Protocols; 15 | -------------------------------------------------------------------------------- /build/server/aplvpn/tcp.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | const net = require('net'); 14 | const cryptoEx = require('../../common/cipher'); 15 | const speedstream_1 = require('../../lib/speedstream'); 16 | function handleTCP(client, handshake, options) { 17 | if (handshake.flags == 0x80) { 18 | handleOutbound(client, handshake.destHost, handshake.destPort, handshake.extra, options); 19 | } 20 | } 21 | exports.handleTCP = handleTCP; 22 | function handleOutbound(client, host, port, desiredIv, options) { 23 | let proxy = net.createConnection({ port: port, host: host }, () => __awaiter(this, void 0, void 0, function* () { 24 | let success = new Buffer([0x01, 0x00]); 25 | let randomLength = Number((Math.random() * 64).toFixed()); 26 | let reply = Buffer.concat([success, new Buffer(randomLength)]); 27 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, desiredIv).cipher; 28 | yield client.writeAsync(cipher.update(reply)); 29 | let decipher = cryptoEx.createDecipher(options.cipherAlgorithm, options.password, options.iv); 30 | let speed = options.speed; 31 | let clientStream = speed > 0 ? client.pipe(new speedstream_1.SpeedStream(speed)) : client; 32 | clientStream.pipe(decipher).pipe(proxy); 33 | let proxyStream = speed > 0 ? proxy.pipe(new speedstream_1.SpeedStream(speed)) : proxy; 34 | proxyStream.pipe(cipher).pipe(client); 35 | })); 36 | function dispose() { 37 | client.dispose(); 38 | proxy.dispose(); 39 | } 40 | proxy.on('error', dispose); 41 | proxy.on('end', dispose); 42 | client.on('error', dispose); 43 | client.on('end', dispose); 44 | proxy.setTimeout(options.timeout * 1000); 45 | client.setTimeout(options.timeout * 1000); 46 | } 47 | -------------------------------------------------------------------------------- /build/server/aplvpn/udp.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | const dgram = require('dgram'); 14 | const crypto = require('crypto'); 15 | const cryptoEx = require('../../common/cipher'); 16 | const protocols_1 = require('./protocols'); 17 | function handleUDP(client, handshake, options) { 18 | let communicationPending = false; 19 | let udpType = handshake.ipVer == protocols_1.IP_VER.V4 ? 'udp4' : 'udp6'; 20 | let destAddress = handshake.destHost; 21 | let decipher = null; 22 | let udpSocket = dgram.createSocket(udpType, (msg, rinfo) => __awaiter(this, void 0, void 0, function* () { 23 | let iv = crypto.randomBytes(options.ivLength); 24 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, iv).cipher; 25 | let len = new Buffer(2); 26 | len.writeUInt16LE(msg.length, 0); 27 | let encrypted = cipher.update(Buffer.concat([len, msg])); 28 | yield client.writeAsync(Buffer.concat([iv, encrypted])); 29 | communicationPending = true; 30 | })); 31 | udpSocket.on('error', () => dispose()); 32 | udpSocket.send(handshake.extra, 0, handshake.extra.length, handshake.destPort, destAddress); 33 | client.on('data', (d) => { 34 | if (!decipher) 35 | decipher = cryptoEx.createDecipher(options.cipherAlgorithm, options.password, options.iv); 36 | let msg = decipher.update(d); 37 | udpSocket.send(msg, 0, msg.length, handshake.destPort, destAddress); 38 | communicationPending = true; 39 | }); 40 | let cleanTimer = setInterval(() => { 41 | if (communicationPending) { 42 | communicationPending = false; 43 | return; 44 | } 45 | dispose(); 46 | }, 30 * 1000); 47 | function dispose() { 48 | clearInterval(cleanTimer); 49 | client.dispose(); 50 | udpSocket.close(); 51 | udpSocket.unref(); 52 | udpSocket.removeAllListeners(); 53 | } 54 | client.on('error', () => dispose()); 55 | client.on('end', () => dispose()); 56 | } 57 | exports.handleUDP = handleUDP; 58 | -------------------------------------------------------------------------------- /build/server/app.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | require('async-node'); 6 | require('kinq').enable(); 7 | require('../lib/socketEx'); 8 | const server_1 = require('./server'); 9 | const constant_1 = require('../common/constant'); 10 | class App { 11 | constructor(options) { 12 | let defaultOptions = { 13 | cipherAlgorithm: constant_1.defaultCipherAlgorithm, 14 | password: constant_1.defaultPassword, 15 | port: constant_1.defaultServerPort, 16 | timeout: 10, 17 | expireTime: undefined, 18 | disableSelfProtection: false, 19 | speed: NaN 20 | }; 21 | options = options || defaultOptions; 22 | Object.getOwnPropertyNames(defaultOptions).forEach(n => options[n] = options[n] || defaultOptions[n]); 23 | let server = new server_1.LsServer(options); 24 | server.start(); 25 | server.once('close', () => App.Users.delete(options.port)); 26 | App.Users.set(options.port, server); 27 | } 28 | static addUser(options) { 29 | if (App.Users.has(options.port)) 30 | return false; 31 | new App(options); 32 | return true; 33 | } 34 | static addUsers(options) { 35 | let results = options.map(o => App.addUser(o)); 36 | return results.all(r => r === true); 37 | } 38 | static updateUser(port, options) { 39 | if (!App.Users.has(port)) 40 | return false; 41 | App.Users.get(port).updateConfiguration(options); 42 | return true; 43 | } 44 | static removeUser(port) { 45 | if (!App.Users.has(port)) 46 | return false; 47 | let server = App.Users.get(port); 48 | server.stop(); 49 | return true; 50 | } 51 | } 52 | App.Users = new Map(); 53 | exports.App = App; 54 | if (!module.parent) { 55 | process.title = 'LightSword Server Debug Mode'; 56 | new App({ speed: 20 }); 57 | } 58 | -------------------------------------------------------------------------------- /build/server/appvpn/protocols.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { 6 | return new Promise(function (resolve, reject) { 7 | generator = generator.call(thisArg, _arguments); 8 | function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } 9 | function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } 10 | function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } 11 | function step(verb, value) { 12 | var result = generator[verb](value); 13 | result.done ? resolve(result.value) : cast(result.value).then(onfulfill, onreject); 14 | } 15 | step("next", void 0); 16 | }); 17 | }; 18 | var Protocols; 19 | (function (Protocols) { 20 | Protocols[Protocols["TCP"] = 6] = "TCP"; 21 | Protocols[Protocols["UDP"] = 17] = "UDP"; 22 | })(Protocols || (Protocols = {})); 23 | -------------------------------------------------------------------------------- /build/server/bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | const program = require('commander'); 4 | const app_1 = require('../app'); 5 | const ipc = require('../../common/ipc'); 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const child = require('child_process'); 9 | const cluster_1 = require('../cluster'); 10 | program 11 | .version('0.7.0') 12 | .option('-p, --port [number]', 'Server Listening Port', Number.parseInt) 13 | .option('-k, --password [password]', 'Cipher Password', String) 14 | .option('-m, --method [algorithm]', 'Cipher Algorithm', String) 15 | .option('-c, --config ', 'Configuration File Path', String) 16 | .option('-u, --users ', 'Mutli-users File Path', String) 17 | .option('-t, --timeout [number]', 'Timeout', Number.parseInt) 18 | .option('-f, --fork', 'Run as a Daemon') 19 | .option('-d, --daemon ', 'Daemon Control', String) 20 | .option('-r, --cluster', 'Run as Cluster Mode') 21 | .option('-a, --management', 'Enable HTTP Management') 22 | .option('-x, --user ', 'Run Under Specified Privilege') 23 | .option('-s, --speed ', 'Speed Limitation \(KB\/s\)', Number.parseInt) 24 | .option('--disableSelfProtection', 'Disable Self-Protection') 25 | .parse(process.argv); 26 | var args = program; 27 | function parseOptions(path) { 28 | if (!path) 29 | return; 30 | if (!fs.existsSync(path)) 31 | return; 32 | var content = fs.readFileSync(path).toString(); 33 | try { 34 | var configs = JSON.parse(content); 35 | return { 36 | port: configs.port, 37 | password: configs.password, 38 | cipherAlgorithm: configs.method, 39 | fork: configs.fork, 40 | cluster: configs.cluster, 41 | timeout: configs.timeout, 42 | management: configs.management, 43 | }; 44 | } 45 | catch (ex) { 46 | console.warn('Configuration file error'); 47 | console.warn(ex.message); 48 | } 49 | } 50 | var fileOptions = parseOptions(args.config) || {}; 51 | if (fileOptions) 52 | Object.getOwnPropertyNames(fileOptions).forEach(n => args[n] = args[n] === undefined ? fileOptions[n] : args[n]); 53 | function parseUsers(path) { 54 | if (!path) 55 | return []; 56 | if (!fs.existsSync(path)) 57 | return []; 58 | var content = fs.readFileSync(path).toString(); 59 | return content.split('\n').where((l) => l.length > 0 && !l.trim().startsWith('#')).select((l) => { 60 | var info = l.trim().split(' '); 61 | return { port: Number(info[0]), password: info[1], cipherAlgorithm: info[2], expireDate: info[3], speed: Number(info[4]), disableSelfProtection: args.disableSelfProtection }; 62 | }).toArray(); 63 | } 64 | var users = parseUsers(args.users); 65 | var argsOptions = { 66 | port: args.port, 67 | password: args.password, 68 | cipherAlgorithm: args.method, 69 | timeout: args.timeout, 70 | disableSelfProtection: args.disableSelfProtection, 71 | speed: args.speed 72 | }; 73 | if (fileOptions) 74 | Object.getOwnPropertyNames(argsOptions).forEach(n => argsOptions[n] = argsOptions[n] === undefined ? fileOptions[n] : argsOptions[n]); 75 | if (!users.length) 76 | users.push(argsOptions); 77 | users = users.where(u => !Number.isNaN(u.port)).distinct((u1, u2) => u1.port === u2.port).toArray(); 78 | if (args.fork && !process.env.__daemon) { 79 | console.info('Run as daemon'); 80 | process.env.__daemon = true; 81 | var cp = child.spawn(process.argv[1], process.argv.skip(2).toArray(), { cwd: process.cwd(), stdio: 'ignore', env: process.env, detached: true }); 82 | cp.unref(); 83 | console.info('Child PID: ' + cp.pid); 84 | process.exit(0); 85 | } 86 | function listenDaemonCommands() { 87 | if (process.env.__daemon) { 88 | ipc.IpcServer.start('server'); 89 | } 90 | } 91 | if (args.daemon && !process.env.__daemon) { 92 | ipc.sendCommand('server', args.daemon, (code) => process.exit(code)); 93 | } 94 | else { 95 | if (args.cluster) { 96 | var clusterOptions = { 97 | users: users, 98 | management: args.management, 99 | user: args.user 100 | }; 101 | cluster_1.runAsClusterMode(clusterOptions, listenDaemonCommands); 102 | } 103 | else { 104 | users.forEach(u => new app_1.App(u)); 105 | listenDaemonCommands(); 106 | if (args.management) 107 | require('../management/index'); 108 | if (args.user) 109 | try { 110 | process.setuid(args.user); 111 | } 112 | catch (ex) { 113 | console.error(ex.message); 114 | } 115 | } 116 | process.title = process.env.__daemon ? path.basename(process.argv[1]) + 'd' : 'LightSword Server'; 117 | process.on('uncaughtException', (err) => { console.error(err); process.exit(1); }); 118 | } 119 | -------------------------------------------------------------------------------- /build/server/cluster.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const os = require('os'); 6 | const cluster = require('cluster'); 7 | const app_1 = require('./app'); 8 | function runAsClusterMode(options, callback) { 9 | if (cluster.isMaster) { 10 | os.cpus().forEach(() => { 11 | cluster.fork(); 12 | }); 13 | cluster.on('exit', () => cluster.fork()); 14 | return callback(); 15 | } 16 | options.users.forEach(o => new app_1.App(o)); 17 | if (options.management) 18 | require('./management/index'); 19 | if (options.user) 20 | try { 21 | process.setuid(options.user); 22 | } 23 | catch (ex) { 24 | console.error(ex.message); 25 | } 26 | } 27 | exports.runAsClusterMode = runAsClusterMode; 28 | -------------------------------------------------------------------------------- /build/server/lib/addressHelper.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const os = require('os'); 6 | const util = require('util'); 7 | const illegalAddresses = ['127.0.0.1', '::1', '0.0.0.0', '::0', os.hostname()]; 8 | const illegalPrefix = ['192.168.', '10.', '172.168.', 'fe80:']; 9 | function isIllegalAddress(addr) { 10 | return illegalAddresses.any(a => a === addr) || illegalPrefix.any(a => addr.startsWith(a)); 11 | } 12 | exports.isIllegalAddress = isIllegalAddress; 13 | function ntoa(data) { 14 | let ipVer = data.length; 15 | switch (ipVer) { 16 | case 4: 17 | return util.format('%d.%d.%d.%d', data[0], data[1], data[2], data[3]); 18 | case 6: 19 | return util.format('%s%s:%s%s:%s%s:%s%s:%s%s:%s%s:%s%s:%s%s', data[0].toString(16), data[1].toString(16), data[2].toString(16), data[3].toString(16), data[4].toString(16), data[5].toString(16), data[6].toString(16), data[7].toString(16), data[8].toString(16), data[9].toString(16), data[10].toString(16), data[11].toString(16), data[12].toString(16), data[13].toString(16), data[14].toString(16), data[15].toString(16)); 20 | } 21 | } 22 | exports.ntoa = ntoa; 23 | -------------------------------------------------------------------------------- /build/server/management/apiRouter.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const express = require('express'); 6 | const userController = require('./userController'); 7 | let router = express.Router(); 8 | router.get('/users/count', userController.getUserCount); 9 | router.get('/users', userController.getUsers); 10 | router.post('/users', userController.addUser); 11 | router.put('/users/:port', userController.updateUser); 12 | router.delete('/users/:port', userController.deleteUser); 13 | router.get('/blacklist', userController.getBlacklist); 14 | router.get('/blacklist/count', userController.getBlacklistCount); 15 | router.get('/blacklist/:port', userController.getServerOfPort, userController.getBlacklistOfPort); 16 | router.get('/blacklist/:port/count', userController.getServerOfPort, userController.getBlacklistCountOfPort); 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /build/server/management/cmdController.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | -------------------------------------------------------------------------------- /build/server/management/index.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const express = require('express'); 6 | const bodyParser = require('body-parser'); 7 | const apiRouter = require('./apiRouter'); 8 | let app = express(); 9 | app.use(bodyParser.json()); 10 | app.use(bodyParser.urlencoded({ extended: true })); 11 | app.use('/api', apiRouter); 12 | app.listen(5000, 'localhost'); 13 | -------------------------------------------------------------------------------- /build/server/management/userController.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const app_1 = require('../app'); 6 | const kinq = require('kinq'); 7 | function getUserCount(req, res) { 8 | res.json({ count: app_1.App.Users.size }); 9 | } 10 | exports.getUserCount = getUserCount; 11 | function getUsers(req, res) { 12 | let users = app_1.App.Users.select(item => { 13 | return { 14 | port: item[1].port, 15 | cipherAlgorithm: item[1].cipherAlgorithm, 16 | expireDate: item[1].expireDate, 17 | speed: item[1].speed 18 | }; 19 | }).toArray(); 20 | res.json(users); 21 | } 22 | exports.getUsers = getUsers; 23 | function addUser(req, res) { 24 | var body = req.body; 25 | let success = Array.isArray(body) ? app_1.App.addUsers(body) : app_1.App.addUser(body); 26 | let statusCode = success ? 200 : 400; 27 | let data = { 28 | success: success, 29 | msg: success ? undefined : `Port number: ${body.port} is used or access denied` 30 | }; 31 | res.status(statusCode).json(data); 32 | } 33 | exports.addUser = addUser; 34 | function updateUser(req, res) { 35 | var body = req.body; 36 | let success = app_1.App.updateUser(Number(req.params.port), body); 37 | let statusCode = success ? 200 : 404; 38 | let data = { 39 | success: success, 40 | msg: success ? undefined : 'User Not Found' 41 | }; 42 | res.status(statusCode).json(data); 43 | } 44 | exports.updateUser = updateUser; 45 | function deleteUser(req, res) { 46 | var port = Number(req.params.port); 47 | let success = app_1.App.removeUser(port); 48 | let statusCode = success ? 200 : 404; 49 | let data = { 50 | success: success, 51 | msg: success ? undefined : 'User Not Found' 52 | }; 53 | res.status(404).json(data); 54 | } 55 | exports.deleteUser = deleteUser; 56 | function getBlacklist(req, res) { 57 | let data = kinq.toLinqable(app_1.App.Users.values()).select(server => server.blackIPs).flatten(false).toArray(); 58 | res.status(data.length > 0 ? 200 : 404).json(data); 59 | } 60 | exports.getBlacklist = getBlacklist; 61 | function getBlacklistCount(req, res) { 62 | let count = kinq.toLinqable(app_1.App.Users.values()).select(s => s.blackIPs.size).sum(); 63 | res.json({ count: count }); 64 | } 65 | exports.getBlacklistCount = getBlacklistCount; 66 | function getServerOfPort(req, res, next) { 67 | let server = kinq.toLinqable(app_1.App.Users.values()).singleOrDefault(s => s.port === Number(req.params.port), undefined); 68 | if (!server) { 69 | return res.status(404).json({ success: false, msg: 'User Not Found' }); 70 | } 71 | req.user = server; 72 | next(); 73 | } 74 | exports.getServerOfPort = getServerOfPort; 75 | function getBlacklistOfPort(req, res) { 76 | let server = req.user; 77 | res.json(server.blackIPs.toArray()); 78 | } 79 | exports.getBlacklistOfPort = getBlacklistOfPort; 80 | function getBlacklistCountOfPort(req, res) { 81 | let server = req.user; 82 | res.json({ count: server.blackIPs.size }); 83 | } 84 | exports.getBlacklistCountOfPort = getBlacklistCountOfPort; 85 | -------------------------------------------------------------------------------- /build/server/osxcl5/connectHandler.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | const net = require('net'); 14 | const crypto = require('crypto'); 15 | const cryptoEx = require('../../common/cipher'); 16 | const xorstream_1 = require('../../lib/xorstream'); 17 | const speedstream_1 = require('../../lib/speedstream'); 18 | function connect(client, rawData, dst, options) { 19 | let proxySocket = net.createConnection(dst.port, dst.addr, () => __awaiter(this, void 0, void 0, function* () { 20 | console.log(`connected: ${dst.addr}:${dst.port}`); 21 | let reply = rawData.slice(0, rawData.length); 22 | reply[0] = 0x05; 23 | reply[1] = 0x00; 24 | let encryptor = cryptoEx.createCipher(options.cipherAlgorithm, options.password); 25 | let cipher = encryptor.cipher; 26 | let iv = encryptor.iv; 27 | let pl = Number((Math.random() * 0xff).toFixed()); 28 | let el = new Buffer([pl]); 29 | let pd = crypto.randomBytes(pl); 30 | let er = cipher.update(Buffer.concat([el, pd, reply])); 31 | yield client.writeAsync(Buffer.concat([iv, er])); 32 | let fromClientXorStream = new xorstream_1.XorStream(options.xorNum); 33 | let toClientXorStream = new xorstream_1.XorStream(pl); 34 | let speed = options.speed; 35 | let streamIn = speed > 0 ? client.pipe(new speedstream_1.SpeedStream(speed)) : client; 36 | streamIn.pipe(fromClientXorStream).pipe(proxySocket); 37 | let streamOut = speed > 0 ? proxySocket.pipe(new speedstream_1.SpeedStream(speed)) : proxySocket; 38 | streamOut.pipe(toClientXorStream).pipe(client); 39 | })); 40 | function dispose(err) { 41 | if (err) 42 | console.info(err.message); 43 | client.dispose(); 44 | proxySocket.dispose(); 45 | } 46 | proxySocket.on('error', dispose); 47 | proxySocket.on('end', dispose); 48 | client.on('error', dispose); 49 | client.on('end', dispose); 50 | proxySocket.setTimeout(options.timeout * 1000); 51 | client.setTimeout(options.timeout * 1000); 52 | } 53 | exports.connect = connect; 54 | -------------------------------------------------------------------------------- /build/server/osxcl5/index.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const connectHandler_1 = require('./connectHandler'); 6 | const socks5constant_1 = require('../../common/socks5constant'); 7 | const socks5Helper = require('../../common/socks5helper'); 8 | const addressHelper_1 = require('../lib/addressHelper'); 9 | function handleOSXSocks5(client, data, options) { 10 | let dst = socks5Helper.refineDestination(data); 11 | if (!dst) 12 | return false; 13 | if (addressHelper_1.isIllegalAddress(dst.addr)) { 14 | client.dispose(); 15 | return true; 16 | } 17 | switch (dst.cmd) { 18 | case socks5constant_1.REQUEST_CMD.CONNECT: 19 | connectHandler_1.connect(client, data, dst, options); 20 | break; 21 | case socks5constant_1.REQUEST_CMD.BIND: 22 | break; 23 | case socks5constant_1.REQUEST_CMD.UDP_ASSOCIATE: 24 | break; 25 | default: 26 | return false; 27 | } 28 | return true; 29 | } 30 | exports.handleOSXSocks5 = handleOSXSocks5; 31 | -------------------------------------------------------------------------------- /build/server/socks5/connectHandler.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | const net = require('net'); 14 | const crypto = require('crypto'); 15 | const cryptoEx = require('../../common/cipher'); 16 | const speedstream_1 = require('../../lib/speedstream'); 17 | function connect(client, rawData, dst, options) { 18 | let proxySocket = net.createConnection(dst.port, dst.addr, () => __awaiter(this, void 0, void 0, function* () { 19 | console.log(`connecting: ${dst.addr}:${dst.port}`); 20 | let reply = rawData.slice(0, rawData.length); 21 | reply[0] = 0x05; 22 | reply[1] = 0x00; 23 | var encryptor = cryptoEx.createCipher(options.cipherAlgorithm, options.password); 24 | let cipherHandshake = encryptor.cipher; 25 | let iv = encryptor.iv; 26 | let pl = Number((Math.random() * 0xff).toFixed()); 27 | let el = new Buffer([pl]); 28 | let pd = crypto.randomBytes(pl); 29 | let er = cipherHandshake.update(Buffer.concat([el, pd, reply])); 30 | yield client.writeAsync(Buffer.concat([iv, er])); 31 | let decipher = cryptoEx.createDecipher(options.cipherAlgorithm, options.password, options.iv); 32 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, iv).cipher; 33 | let speed = options.speed; 34 | let streamIn = speed > 0 ? client.pipe(new speedstream_1.SpeedStream(speed)) : client; 35 | streamIn.pipe(decipher).pipe(proxySocket); 36 | let streamOut = speed > 0 ? proxySocket.pipe(new speedstream_1.SpeedStream(speed)) : proxySocket; 37 | streamOut.pipe(cipher).pipe(client); 38 | })); 39 | function dispose(err) { 40 | if (err) 41 | console.info(err.message); 42 | client.dispose(); 43 | proxySocket.dispose(); 44 | } 45 | proxySocket.on('error', dispose); 46 | proxySocket.on('end', dispose); 47 | client.on('error', dispose); 48 | client.on('end', dispose); 49 | proxySocket.setTimeout(options.timeout * 1000); 50 | client.setTimeout(options.timeout * 1000); 51 | } 52 | exports.connect = connect; 53 | -------------------------------------------------------------------------------- /build/server/socks5/index.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const connectHandler_1 = require('./connectHandler'); 6 | const udpHandler_1 = require('./udpHandler'); 7 | const socks5constant_1 = require('../../common/socks5constant'); 8 | const socks5Helper = require('../../common/socks5helper'); 9 | const addressHelper_1 = require('../lib/addressHelper'); 10 | function handleSocks5(client, data, options) { 11 | let dst = socks5Helper.refineDestination(data); 12 | if (!dst) 13 | return false; 14 | if (addressHelper_1.isIllegalAddress(dst.addr)) { 15 | client.dispose(); 16 | return true; 17 | } 18 | switch (dst.cmd) { 19 | case socks5constant_1.REQUEST_CMD.CONNECT: 20 | connectHandler_1.connect(client, data, dst, options); 21 | break; 22 | case socks5constant_1.REQUEST_CMD.BIND: 23 | break; 24 | case socks5constant_1.REQUEST_CMD.UDP_ASSOCIATE: 25 | udpHandler_1.udpAssociate(client, data, dst, options); 26 | break; 27 | default: 28 | return false; 29 | } 30 | return true; 31 | } 32 | exports.handleSocks5 = handleSocks5; 33 | -------------------------------------------------------------------------------- /build/server/socks5/udpHandler.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | const net = require('net'); 14 | const dgram = require('dgram'); 15 | const crypto = require('crypto'); 16 | const cryptoEx = require('../../common/cipher'); 17 | const socks5constant_1 = require('../../common/socks5constant'); 18 | const socksHelper = require('../../common/socks5helper'); 19 | function udpAssociate(client, rawData, dst, options) { 20 | let udpType = 'udp' + (net.isIP(dst.addr) || 4); 21 | let serverUdp = dgram.createSocket(udpType); 22 | let ivLength = cryptoEx.SupportedCiphers[options.cipherAlgorithm][1]; 23 | serverUdp.bind(); 24 | serverUdp.unref(); 25 | serverUdp.once('listening', () => __awaiter(this, void 0, void 0, function* () { 26 | let udpAddr = serverUdp.address(); 27 | let reply = socksHelper.createSocks5TcpReply(0x0, udpAddr.family === 'IPv4' ? socks5constant_1.ATYP.IPV4 : socks5constant_1.ATYP.IPV6, udpAddr.address, udpAddr.port); 28 | let encryptor = cryptoEx.createCipher(options.cipherAlgorithm, options.password); 29 | let cipher = encryptor.cipher; 30 | let iv = encryptor.iv; 31 | let pl = Number((Math.random() * 0xff).toFixed()); 32 | let el = new Buffer([pl]); 33 | let pd = crypto.randomBytes(pl); 34 | let er = cipher.update(Buffer.concat([el, pd, reply])); 35 | yield client.writeAsync(Buffer.concat([iv, er])); 36 | })); 37 | let udpSet = new Set(); 38 | serverUdp.on('message', (msg, cinfo) => __awaiter(this, void 0, void 0, function* () { 39 | let iv = new Buffer(ivLength); 40 | msg.copy(iv, 0, 0, ivLength); 41 | let decipher = cryptoEx.createDecipher(options.cipherAlgorithm, options.password, iv); 42 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, iv).cipher; 43 | let data = decipher.update(msg.slice(iv.length, msg.length)); 44 | let pl = data[0]; 45 | let udpMsg = data.slice(1 + pl, data.length); 46 | let dst = socksHelper.refineDestination(udpMsg); 47 | let socketId = `${cinfo.address}:${cinfo.port}`; 48 | let proxyUdp = dgram.createSocket(udpType); 49 | proxyUdp.unref(); 50 | proxyUdp.send(udpMsg, dst.headerSize, udpMsg.length - dst.headerSize, dst.port, dst.addr); 51 | proxyUdp.on('message', (msg, rinfo) => { 52 | let data = cipher.update(msg); 53 | proxyUdp.send(data, 0, data.length, cinfo.port, cinfo.address); 54 | }); 55 | proxyUdp.on('error', (err) => console.log(err.message)); 56 | udpSet.add(proxyUdp); 57 | })); 58 | function dispose() { 59 | serverUdp.removeAllListeners(); 60 | serverUdp.close(); 61 | serverUdp.unref(); 62 | client.dispose(); 63 | udpSet.forEach(udp => { 64 | udp.close(); 65 | }); 66 | } 67 | client.on('error', dispose); 68 | client.on('end', dispose); 69 | serverUdp.on('error', dispose); 70 | serverUdp.on('close', dispose); 71 | } 72 | exports.udpAssociate = udpAssociate; 73 | -------------------------------------------------------------------------------- /build/test/httpManagement.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const http = require('http'); 6 | const assert = require('assert'); 7 | require('../server/management/index'); 8 | describe('HTTP Management Test Cases', () => { 9 | before((done) => { 10 | let data = JSON.stringify({ 11 | port: 35000, 12 | cipherAlgorithm: 'bf-cfb', 13 | password: 'abc' 14 | }); 15 | let options = { 16 | host: 'localhost', 17 | port: 5000, 18 | path: '/api/users', 19 | method: 'POST', 20 | headers: { 21 | 'Content-Type': 'application/json', 22 | 'Content-Length': Buffer.byteLength(data) 23 | } 24 | }; 25 | let httpReq = http.request(options, res => { 26 | let msg = ''; 27 | res.on('data', d => msg += d); 28 | res.on('end', () => { 29 | let ok = JSON.parse(msg); 30 | assert(ok.success); 31 | done(); 32 | }); 33 | }); 34 | httpReq.write(data); 35 | httpReq.end(); 36 | }); 37 | it('should have 1 user', (done) => { 38 | http.get('http://localhost:5000/api/users/count', res => { 39 | let msg = ''; 40 | res.on('data', d => msg += d); 41 | res.on('end', () => { 42 | let msgObj = JSON.parse(msg); 43 | assert(msgObj.count === 1); 44 | done(); 45 | }); 46 | }); 47 | }); 48 | it('create new users (Array)', (done) => { 49 | let users = JSON.stringify([ 50 | { 51 | port: 49000, 52 | password: '123', 53 | cipherAlgorithm: 'rc4-md5' 54 | }, 55 | { 56 | port: 49001, 57 | password: 'abce', 58 | cipherAlgorithm: 'aes-128-cfb' 59 | } 60 | ]); 61 | let options = { 62 | host: 'localhost', 63 | port: 5000, 64 | path: '/api/users', 65 | method: 'POST', 66 | headers: { 67 | 'Content-Type': 'application/json', 68 | 'Content-Length': Buffer.byteLength(users) 69 | } 70 | }; 71 | let httpReq = http.request(options, res => { 72 | let msg = ''; 73 | res.on('data', d => msg += d); 74 | res.on('end', () => { 75 | let obj = JSON.parse(msg); 76 | assert(obj.success); 77 | done(); 78 | }); 79 | }); 80 | httpReq.write(users); 81 | httpReq.end(); 82 | }); 83 | it('get users count', (done) => { 84 | http.get('http://localhost:5000/api/users/count', (res) => { 85 | let msg = ''; 86 | res.on('data', d => msg += d); 87 | res.on('end', () => { 88 | let obj = JSON.parse(msg); 89 | assert(obj.count === 3); 90 | done(); 91 | }); 92 | }); 93 | }); 94 | it('delete user', (done) => { 95 | let options = { 96 | host: 'localhost', 97 | port: 5000, 98 | path: '/api/users/49000', 99 | method: 'DELETE' 100 | }; 101 | http.request(options, res => { 102 | let msg = ''; 103 | res.on('data', d => msg += d); 104 | res.on('end', () => { 105 | let obj = JSON.parse(msg); 106 | assert(obj.success); 107 | done(); 108 | }); 109 | }).end(); 110 | }); 111 | it('update user expiring', (done) => { 112 | let data = JSON.stringify({ 113 | expireDate: "2017-01-04T03:01:54+09:00" 114 | }); 115 | let options = { 116 | host: 'localhost', 117 | path: '/api/users/35000', 118 | port: 5000, 119 | method: 'PUT', 120 | headers: { 121 | 'Content-Type': 'application/json', 122 | 'Content-Length': Buffer.byteLength(data) 123 | } 124 | }; 125 | let httpReq = http.request(options, res => { 126 | let msg = ''; 127 | res.on('data', d => msg += d); 128 | res.on('end', () => { 129 | let obj = JSON.parse(msg); 130 | assert(obj.success); 131 | done(); 132 | }); 133 | }); 134 | httpReq.write(data); 135 | httpReq.end(); 136 | }); 137 | it('update not exist user expiring', (done) => { 138 | let data = JSON.stringify({ 139 | expireDate: "2017-01-04T03:01:54+09:00" 140 | }); 141 | let options = { 142 | host: 'localhost', 143 | path: '/api/users/350500', 144 | port: 5000, 145 | method: 'PUT', 146 | headers: { 147 | 'Content-Type': 'application/json', 148 | 'Content-Length': Buffer.byteLength(data) 149 | } 150 | }; 151 | let httpReq = http.request(options, res => { 152 | let msg = ''; 153 | res.on('data', d => msg += d); 154 | res.on('end', () => { 155 | let obj = JSON.parse(msg); 156 | assert(obj.success === false); 157 | done(); 158 | }); 159 | }); 160 | httpReq.write(data); 161 | httpReq.end(); 162 | }); 163 | after((done) => { 164 | http.get('http://localhost:5000/api/users/count', res => { 165 | let msg = ''; 166 | res.on('data', d => msg += d); 167 | res.on('end', () => { 168 | assert(JSON.parse(msg).count === 2); 169 | done(); 170 | }); 171 | }); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /build/test/pkcs7.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | const kinq = require('kinq'); 6 | const pkcs7 = require('../lib/pkcs7'); 7 | const assert = require('assert'); 8 | kinq.enable(); 9 | describe('test pkcs7', () => { 10 | it('should be 16 bytes', () => { 11 | assert(pkcs7.pad([0x2]).length === 16); 12 | assert(pkcs7.pad([]).length === 16); 13 | }); 14 | it('should be 32 bytes', () => { 15 | let bytes = pkcs7.pad(new Buffer(17).fill(3)); 16 | assert(bytes.length === 32); 17 | assert(kinq.toLinqable(bytes).skip(17).all(i => i === 15)); 18 | }); 19 | it('should be 1 bytes', () => { 20 | let padded = pkcs7.pad([1]); 21 | assert(pkcs7.unpad(padded).length === 1); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /build/test/socks5.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //--------------------------------------------- 4 | 'use strict'; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | return new (P || (P = Promise))(function (resolve, reject) { 7 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 8 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 9 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 10 | step((generator = generator.apply(thisArg, _arguments)).next()); 11 | }); 12 | }; 13 | require('async-node'); 14 | require('kinq').enable(); 15 | require('../lib/socketEx'); 16 | const server_1 = require('../server/server'); 17 | const remoteProxyServer_1 = require('../client/socks5/remoteProxyServer'); 18 | const assert = require('assert'); 19 | const socks = require('socks'); 20 | const net = require('net'); 21 | describe('socks5 server', () => { 22 | let serverPort = 10000; 23 | let proxyPort = 8900; 24 | let algorithm = 'rc4'; 25 | let pw = '19'; 26 | let serverOpts = { 27 | cipherAlgorithm: algorithm, 28 | password: pw, 29 | port: serverPort, 30 | timeout: 60 31 | }; 32 | let proxyOpts = { 33 | listenAddr: 'localhost', 34 | listenPort: proxyPort, 35 | serverAddr: 'localhost', 36 | serverPort: serverPort, 37 | cipherAlgorithm: algorithm, 38 | password: pw, 39 | timeout: 60, 40 | bypassLocal: true 41 | }; 42 | let clientOpts = { 43 | timeout: 60000, 44 | proxy: { 45 | ipaddress: "localhost", 46 | port: proxyPort, 47 | command: 'connect', 48 | type: 5 // (4 or 5) 49 | }, 50 | target: { 51 | host: "google.com", 52 | port: 80 53 | } 54 | }; 55 | let server = new server_1.LsServer(serverOpts); 56 | server.start(); 57 | let rpServer = new remoteProxyServer_1.RemoteProxyServer(proxyOpts); 58 | rpServer.start(); 59 | it('status test', (done) => __awaiter(this, void 0, void 0, function* () { 60 | socks.createConnection(clientOpts, (err, socket, info) => __awaiter(this, void 0, void 0, function* () { 61 | if (err) 62 | return assert.fail(err, null, err.message); 63 | assert(net.isIP(socket.remoteAddress)); 64 | done(); 65 | })); 66 | })); 67 | }); 68 | -------------------------------------------------------------------------------- /build/test/transform.js: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 'use strict'; 5 | require('kinq').enable(); 6 | const fs = require('fs'); 7 | const assert = require('assert'); 8 | const ms = require('memory-stream'); 9 | const xorstream_1 = require('../lib/xorstream'); 10 | describe('test XorStream', () => { 11 | it('Compare XorStream', (done) => { 12 | let mems = new ms(); 13 | let xor1Stream = new xorstream_1.XorStream(5); 14 | let xor2Stream = new xorstream_1.XorStream(5); 15 | mems.on('finish', () => { 16 | let fc = fs.readFileSync('./README.md').toString(); 17 | assert(mems.toString() === fc); 18 | done(); 19 | }); 20 | fs.createReadStream('./README.md').pipe(xor1Stream).pipe(xor2Stream).pipe(mems); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # LightSword 客户端 2 | 3 | 运行参数: 4 | --- 5 | 6 | | 参数 | 完整参数 | 解释 | 7 | | -----| -------- | ---- | 8 | | -s | --server | 远程服务器地址 | 9 | | -p | --port | 远程服务器端口 | 10 | | -l | --listenport | 本地监听端口 | 11 | | -m | --method | 加密算法(见下) | 12 | | -k | --password | 密码 | 13 | | -t | --timeout | 超时时常 | 14 | | -f | --fork | 作为守护进程运行 (不支持 Windows) | 15 | | -b | --dontbypasslocal | 不绕过本地地址 | 16 | | -c | --config | 配置文件路径 | 17 | | -a | --any | 允许任意地址使用代理 | 18 | | -d | --daemon | 守护进程控制命令,支持: stop, restart, status | 19 | 20 | 控制守护进程 21 | 22 | ``` 23 | // 重启 24 | lslocal -d restart 25 | 26 | // 停止 27 | lslocal -d stop 28 | ``` 29 | 30 | 支持加密算法: 31 | --- 32 | 33 | | 算法 | 34 | |----------| 35 | |aes-128-cfb| 36 | |aes-128-ofb| 37 | |aes-192-cfb| 38 | |aes-192-ofb| 39 | |aes-256-cfb| 40 | |aes-256-ofb| 41 | |bf-cfb| 42 | |camellia-128-cfb| 43 | |camellia-192-cfb| 44 | |camellia-256-cfb| 45 | |cast5-cfb| 46 | |des-cfb| 47 | |idea-cfb| 48 | |rc2-cfb| 49 | |rc4| 50 | |rc4-md5| 51 | |seed-cfb| 52 | 53 | 配置文件: 54 | --- 55 | 56 | 配置文件是使用utf8编码的json格式文件,每个配置关键字和命令行完整参数相同。 57 | 58 | ``` 59 | { 60 | "server": "xxx.xxx.xxx.xxx", 61 | "port": 4433, 62 | "listenport": 1080, 63 | "password": "abcabc", 64 | "method": "aes-256-cfb", 65 | "timeout": 60, 66 | "fork": true, 67 | "dontbypasslocal": false, 68 | "any": false 69 | } 70 | ``` -------------------------------------------------------------------------------- /client/app.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | require('../lib/socketEx'); 8 | require('kinq').enable(); 9 | require('async-node'); 10 | import { defaultServerPort, defaultCipherAlgorithm } from '../common/constant'; 11 | import { ServerOptions } from './socks5/socks5Server'; 12 | import { LocalProxyServer } from './socks5/localProxyServer'; 13 | import { RemoteProxyServer } from './socks5/remoteProxyServer'; 14 | 15 | let localAddrs = ['127.0.0.1', 'localhost', undefined, null]; 16 | 17 | export class App { 18 | 19 | constructor(options) { 20 | let defaultOptions: ServerOptions = { 21 | listenAddr: 'localhost', 22 | listenPort: 1080, 23 | serverAddr: 'localhost', 24 | serverPort: defaultServerPort, 25 | cipherAlgorithm: defaultCipherAlgorithm, 26 | password: 'lightsword.neko', 27 | timeout: 60, 28 | bypassLocal: true 29 | }; 30 | 31 | options = options || defaultOptions; 32 | Object.getOwnPropertyNames(defaultOptions).forEach(n => options[n] = options[n] === undefined ? defaultOptions[n] : options[n]); 33 | 34 | let isLocalProxy = localAddrs.contains(options.serverAddr); 35 | let server = isLocalProxy ? new LocalProxyServer(options) : new RemoteProxyServer(options); 36 | server.start(); 37 | } 38 | } 39 | 40 | if (!module.parent) { 41 | process.title = 'LightSword Client Debug Mode'; 42 | new App({ serverAddr: '::1', listenPort: 2002, bypassLocal: false }); 43 | } else { 44 | localAddrs.push('::1'); 45 | } -------------------------------------------------------------------------------- /client/bin/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | //----------------------------------- 4 | // Copyright(c) 2015 Neko 5 | //----------------------------------- 6 | 7 | import * as program from 'commander'; 8 | import { App } from '../app' 9 | import * as fs from 'fs'; 10 | import * as path from 'path'; 11 | import * as ipc from '../../common/ipc'; 12 | import * as child from 'child_process'; 13 | 14 | program 15 | .version('0.6.0') 16 | .option('-s, --server ', 'Server Address', String) 17 | .option('-p, --port ', 'Server Port Number', Number.parseInt) 18 | .option('-l, --listenport ', 'Local Listening Port Number', Number.parseInt) 19 | .option('-m, --method ', 'Cipher Algorithm', String) 20 | .option('-k, --password ', 'Password', String) 21 | .option('-c, --config ', 'Configuration File Path', String) 22 | .option('-a, --any', 'Listen Any Connection') 23 | .option('-t, --timeout [number]', 'Timeout (second)') 24 | .option('-f, --fork', 'Run as Daemon') 25 | .option('-b, --dontbypasslocal', "DON'T Bypass Local Address") 26 | .option('-d, --daemon ', 'Daemon Control', String) 27 | .parse(process.argv); 28 | 29 | var args = program; 30 | 31 | function parseFile(path: string) { 32 | if (!path) return; 33 | if (!fs.existsSync(path)) return; 34 | 35 | var content = fs.readFileSync(path).toString(); 36 | 37 | try { 38 | return JSON.parse(content); 39 | } catch(ex) { 40 | console.warn('Configuration file error'); 41 | console.warn(ex.message); 42 | } 43 | } 44 | 45 | var fileOptions = parseFile(args.config) || {}; 46 | if (fileOptions) Object.getOwnPropertyNames(fileOptions).forEach(n => args[n] = args[n] === undefined ? fileOptions[n] : args[n]); 47 | 48 | var argsOptions = { 49 | listenAddr: args.any ? '' : 'localhost', 50 | listenPort: args.listenport, 51 | serverAddr: args.server, 52 | serverPort: args.port, 53 | cipherAlgorithm: args.method, 54 | password: args.password, 55 | timeout: args.timeout, 56 | bypassLocal: args.dontbypasslocal ? false : true 57 | } 58 | 59 | if (args.fork && !process.env.__daemon) { 60 | console.info('Run as daemon'); 61 | process.env.__daemon = true; 62 | var cp = child.spawn(process.argv[1], process.argv.skip(2).toArray(), { detached: true, stdio: 'ignore', env: process.env, cwd: process.cwd() }); 63 | cp.unref(); 64 | console.log('Child PID: ', cp.pid); 65 | process.exit(0); 66 | } 67 | 68 | if (process.env.__daemon) { 69 | ipc.IpcServer.start('client'); 70 | } 71 | 72 | if (args.daemon && !process.env.__daemon) { 73 | ipc.sendCommand('client', args.daemon, (code) => process.exit(code)); 74 | } else { 75 | Object.getOwnPropertyNames(argsOptions).forEach(n => argsOptions[n] = argsOptions[n] === undefined ? fileOptions[n] : argsOptions[n]); 76 | if (!program.args.contains('service')) new App(argsOptions); 77 | 78 | process.title = process.env.__daemon ? path.basename(process.argv[1]) + 'd' : 'LightSword Client'; 79 | } 80 | -------------------------------------------------------------------------------- /client/socks5/localProxyServer.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import * as dgram from 'dgram'; 9 | import { Socks5Server } from './socks5Server'; 10 | import { REQUEST_CMD, ATYP } from '../../common/socks5constant'; 11 | import * as socks5Helper from '../../common/socks5helper'; 12 | 13 | export class LocalProxyServer extends Socks5Server { 14 | 15 | handleRequest(client: net.Socket, request: Buffer): boolean { 16 | let dst = socks5Helper.refineDestination(request); 17 | 18 | switch (dst.cmd) { 19 | case REQUEST_CMD.CONNECT: 20 | LocalProxyServer.connectServer(client, dst, request, this.timeout); 21 | break; 22 | case REQUEST_CMD.UDP_ASSOCIATE: 23 | LocalProxyServer.udpAssociate(client, dst); 24 | break; 25 | default: 26 | return false; 27 | } 28 | 29 | return true; 30 | } 31 | 32 | static bind(client, dst: { port: number, addr: string }) { 33 | 34 | } 35 | 36 | static udpAssociate(client: net.Socket, dst: { port: number, addr: string }) { 37 | let udpType = 'udp' + (net.isIP(dst.addr) || 4); 38 | let serverUdp = dgram.createSocket(udpType); 39 | serverUdp.bind(); 40 | serverUdp.unref(); 41 | serverUdp.on('listening', async () => { 42 | let udpAddr = serverUdp.address(); 43 | let reply = socks5Helper.createSocks5TcpReply(0x0, udpAddr.family === 'IPv4' ? ATYP.IPV4 : ATYP.IPV6, udpAddr.address, udpAddr.port); 44 | await client.writeAsync(reply); 45 | }); 46 | 47 | let udpSet = new Set(); 48 | serverUdp.on('message', (msg: Buffer, rinfo: dgram.RemoteInfo) => { 49 | let socketId = `${rinfo.address}:${rinfo.port}`; 50 | let dst = socks5Helper.refineDestination(msg); 51 | 52 | let proxyUdp = dgram.createSocket(udpType); 53 | proxyUdp.unref(); 54 | 55 | proxyUdp.on('message', (msg: Buffer) => { 56 | let header = socks5Helper.createSocks5UdpHeader(rinfo.address, rinfo.port); 57 | let data = Buffer.concat([header, msg]); 58 | serverUdp.send(data, 0, data.length, rinfo.port, rinfo.address); 59 | }); 60 | 61 | proxyUdp.on('error', (err) => console.log(err.message)); 62 | 63 | proxyUdp.send(msg, dst.headerSize, msg.length - dst.headerSize, dst.port, dst.addr); 64 | udpSet.add(proxyUdp); 65 | }); 66 | 67 | function dispose() { 68 | client.dispose(); 69 | 70 | serverUdp.removeAllListeners(); 71 | serverUdp.close(); 72 | 73 | udpSet.forEach(udp => { 74 | udp.close(); 75 | udp.removeAllListeners(); 76 | }); 77 | 78 | udpSet.clear(); 79 | } 80 | 81 | serverUdp.on('error', dispose); 82 | client.on('error', dispose); 83 | client.on('end', dispose); 84 | } 85 | 86 | static connectServer(client: net.Socket, dst: { port: number, addr: string }, request: Buffer, timeout: number) { 87 | 88 | let proxySocket = net.createConnection(dst.port, dst.addr, async () => { 89 | let reply = new Buffer(request.length); 90 | request.copy(reply); 91 | reply[0] = 0x05; 92 | reply[1] = 0x00; 93 | 94 | await client.writeAsync(reply); 95 | 96 | proxySocket.pipe(client); 97 | client.pipe(proxySocket); 98 | }); 99 | 100 | function dispose() { 101 | proxySocket.dispose(); 102 | client.dispose(); 103 | } 104 | 105 | proxySocket.on('end', dispose); 106 | proxySocket.on('error', dispose); 107 | client.on('end', dispose); 108 | client.on('error', dispose); 109 | 110 | proxySocket.setTimeout(timeout); 111 | client.setTimeout(timeout); 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /client/socks5/remoteProxyServer.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import * as dgram from 'dgram'; 9 | import * as crypto from 'crypto'; 10 | import * as cryptoEx from '../../common/cipher'; 11 | import { Socks5Server } from './socks5Server'; 12 | import { VPN_TYPE } from '../../common/constant'; 13 | import { LocalProxyServer } from './localProxyServer'; 14 | import * as socks5Helper from '../../common/socks5helper'; 15 | import { REQUEST_CMD, ATYP } from '../../common/socks5constant'; 16 | 17 | export class RemoteProxyServer extends Socks5Server { 18 | 19 | handleRequest(client: net.Socket, request: Buffer) { 20 | let me = this; 21 | 22 | let req = socks5Helper.refineDestination(request); 23 | if (this.localArea.any((a: string) => req.addr.toLowerCase().startsWith(a)) && this.bypassLocal) { 24 | if (req.cmd === REQUEST_CMD.CONNECT) return LocalProxyServer.connectServer(client, { addr: req.addr, port: req.port }, request, this.timeout); 25 | if (req.cmd === REQUEST_CMD.UDP_ASSOCIATE) return LocalProxyServer.udpAssociate(client, { addr: req.addr, port: req.port }); 26 | } 27 | 28 | let proxySocket = net.createConnection(this.serverPort, this.serverAddr, async () => { 29 | let encryptor = cryptoEx.createCipher(me.cipherAlgorithm, me.password); 30 | let handshakeCipher = encryptor.cipher; 31 | 32 | let iv = encryptor.iv; 33 | let pl = Number((Math.random() * 0xff).toFixed()); 34 | let et = new Buffer([VPN_TYPE.SOCKS5, pl]); 35 | let pa = crypto.randomBytes(pl); 36 | let er = Buffer.concat([handshakeCipher.update(Buffer.concat([et, pa, request])), handshakeCipher.final()]); 37 | 38 | await proxySocket.writeAsync(Buffer.concat([iv, er])); 39 | 40 | let data = await proxySocket.readAsync(); 41 | if (!data) return proxySocket.dispose(); 42 | 43 | let riv = data.slice(0, iv.length); 44 | let handshakeDecipher = cryptoEx.createDecipher(me.cipherAlgorithm, me.password, riv); 45 | 46 | let rlBuf = Buffer.concat([handshakeDecipher.update(data.slice(iv.length, data.length)), handshakeDecipher.final()]); 47 | let paddingSize = rlBuf[0]; 48 | 49 | let reply = rlBuf.slice(1 + paddingSize, rlBuf.length); 50 | 51 | switch (req.cmd) { 52 | case REQUEST_CMD.CONNECT: 53 | console.info(`connected: ${req.addr}:${req.port}`); 54 | await client.writeAsync(reply); 55 | 56 | let cipher = cryptoEx.createCipher(me.cipherAlgorithm, me.password, iv).cipher; 57 | let decipher = cryptoEx.createDecipher(me.cipherAlgorithm, me.password, riv); 58 | 59 | client.pipe(cipher).pipe(proxySocket); 60 | proxySocket.pipe(decipher).pipe(client); 61 | break; 62 | case REQUEST_CMD.UDP_ASSOCIATE: 63 | let udpReply = socks5Helper.refineDestination(reply); 64 | me.udpAssociate(client, { addr: udpReply.addr, port: udpReply.port }, me.cipherAlgorithm, me.password); 65 | break; 66 | } 67 | }); 68 | 69 | function dispose(err: Error) { 70 | if (err) console.info(err.message); 71 | client.dispose(); 72 | proxySocket.dispose(); 73 | } 74 | 75 | proxySocket.on('end', () => dispose); 76 | proxySocket.on('error', () => dispose); 77 | client.on('end', () => dispose); 78 | client.on('error', () => dispose); 79 | 80 | proxySocket.setTimeout(this.timeout); 81 | } 82 | 83 | udpAssociate(client: net.Socket, udpServer: { addr: string, port: number }, cipherAlgorithm: string, password: string) { 84 | let udpType = 'udp' + (net.isIP(udpServer.addr) || 4); 85 | let listeningUdp = dgram.createSocket(udpType); 86 | 87 | listeningUdp.bind(); 88 | listeningUdp.unref(); 89 | listeningUdp.once('listening', async () => { 90 | let udpAddr = listeningUdp.address(); 91 | let reply = socks5Helper.createSocks5TcpReply(0x0, udpAddr.family === 'IPv4' ? ATYP.IPV4 : ATYP.IPV6, udpAddr.address, udpAddr.port); 92 | await client.writeAsync(reply); 93 | }); 94 | 95 | let udpSet = new Set(); 96 | listeningUdp.on('message', async (msg: Buffer, cinfo: dgram.RemoteInfo) => { 97 | let proxyUdp = dgram.createSocket(udpType); 98 | proxyUdp.unref(); 99 | 100 | let encryptor = cryptoEx.createCipher(cipherAlgorithm, password); 101 | let cipher = encryptor.cipher; 102 | let iv = encryptor.iv; 103 | let decipher = cryptoEx.createDecipher(cipherAlgorithm, password, iv); 104 | 105 | let pl = Number((Math.random() * 0xff).toFixed()); 106 | let el = new Buffer([pl]); 107 | let rp = crypto.randomBytes(pl); 108 | let em = cipher.update(Buffer.concat([el, rp, msg])); 109 | 110 | let data = Buffer.concat([iv, em]); 111 | proxyUdp.send(data, 0, data.length, udpServer.port, udpServer.addr); 112 | 113 | proxyUdp.on('message', (sMsg: Buffer, sinfo: dgram.RemoteInfo) => { 114 | let reply = decipher.update(sMsg); 115 | let header = socks5Helper.createSocks5UdpHeader(cinfo.address, cinfo.port); 116 | let data = Buffer.concat([header, reply]); 117 | listeningUdp.send(data, 0, data.length, cinfo.port, cinfo.address); 118 | }); 119 | 120 | proxyUdp.on('error', (err) => console.log(err.message)); 121 | udpSet.add(proxyUdp); 122 | }); 123 | 124 | function dispose() { 125 | listeningUdp.removeAllListeners(); 126 | listeningUdp.close(); 127 | listeningUdp.unref(); 128 | 129 | udpSet.forEach(udp => { 130 | udp.close(); 131 | }); 132 | } 133 | 134 | client.once('error', dispose); 135 | client.once('end', dispose); 136 | listeningUdp.on('error', dispose); 137 | listeningUdp.on('close', dispose); 138 | } 139 | } -------------------------------------------------------------------------------- /client/socks5/socks5Server.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as os from 'os'; 8 | import * as net from 'net'; 9 | import * as crypto from 'crypto'; 10 | import * as cipher from '../../common/cipher'; 11 | import { AUTHENTICATION, SOCKS_VER } from '../../common/socks5constant'; 12 | 13 | export type ServerOptions = { 14 | listenAddr: string; 15 | listenPort: number; 16 | password: string; 17 | cipherAlgorithm: string; 18 | serverAddr: string; 19 | serverPort: number; 20 | timeout: number; 21 | bypassLocal: boolean; 22 | } 23 | 24 | export abstract class Socks5Server { 25 | public listenAddr: string; 26 | public listenPort: number; 27 | public password: string; 28 | public cipherAlgorithm: string; 29 | public serverAddr: string; 30 | public serverPort: number; 31 | public timeout: number; 32 | public bypassLocal: boolean; 33 | 34 | private server: net.Server; 35 | protected localArea = ['10.', '192.168.', 'localhost', '127.0.0.1', '172.16.', '::1', '169.254.0.0']; 36 | 37 | constructor(options: ServerOptions) { 38 | let me = this; 39 | if (options) Object.getOwnPropertyNames(options).forEach(n => me[n] = options[n]); 40 | } 41 | 42 | start() { 43 | if (this.server) return; 44 | let me = this; 45 | 46 | let server = net.createServer(async (client) => { 47 | let data = await client.readAsync(); 48 | if (!data) return client.dispose(); 49 | 50 | let reply = me.handleHandshake(data); 51 | await client.writeAsync(reply.data); 52 | if (!reply.success) return client.dispose(); 53 | 54 | data = await client.readAsync(); 55 | me.handleRequest(client, data); 56 | }); 57 | 58 | server.on('error', (err) => console.error(err.message)); 59 | server.listen(this.listenPort, this.listenAddr); 60 | this.server = server; 61 | } 62 | 63 | stop() { 64 | this.server.removeAllListeners(); 65 | this.server.close(); 66 | } 67 | 68 | private handleHandshake(data: Buffer): { success: boolean, data: Buffer } { 69 | let methodCount = data[1]; 70 | let code = data.skip(2).take(methodCount).contains(AUTHENTICATION.NOAUTH) 71 | ? AUTHENTICATION.NOAUTH 72 | : AUTHENTICATION.NONE; 73 | let success = code === AUTHENTICATION.NOAUTH; 74 | 75 | return { success, data: new Buffer([SOCKS_VER.V5, code]) }; 76 | } 77 | 78 | abstract handleRequest(clientSocket: net.Socket, socksRequest: Buffer); 79 | } -------------------------------------------------------------------------------- /common/cipher.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as crypto from 'crypto'; 8 | 9 | export const SupportedCiphers = { 10 | 'aes-128-cfb': [16, 16], 11 | 'aes-128-ofb': [16, 16], 12 | 'aes-192-cfb': [24, 16], 13 | 'aes-192-ofb': [24, 16], 14 | 'aes-256-cfb': [32, 16], 15 | 'aes-256-ofb': [32, 16], 16 | 'bf-cfb': [16, 8], 17 | 'camellia-128-cfb': [16, 16], 18 | 'camellia-192-cfb': [24, 16], 19 | 'camellia-256-cfb': [32, 16], 20 | 'cast5-cfb': [16, 8], 21 | 'des-cfb': [8, 8], 22 | 'idea-cfb': [16, 8], 23 | 'rc2-cfb': [16, 8], 24 | 'rc4': [16, 0], 25 | 'rc4-md5': [16, 16], 26 | 'seed-cfb': [16, 16], 27 | } 28 | 29 | Object.freeze(SupportedCiphers); 30 | 31 | export function createCipher(algorithm: string, password: string, iv?: Buffer): { cipher: crypto.Cipher, iv: Buffer } { 32 | return createDeOrCipher('cipher', algorithm, password, iv); 33 | } 34 | 35 | export function createDecipher(algorithm: string, password: string, iv: Buffer): crypto.Decipher { 36 | return createDeOrCipher('decipher', algorithm, password, iv).cipher; 37 | } 38 | 39 | function createDeOrCipher(type: string, algorithm: string, password: string, iv?: Buffer): { cipher: crypto.Cipher | crypto.Decipher, iv: Buffer } { 40 | let cipherAlgorithm = algorithm.toLowerCase(); 41 | let keyIv = SupportedCiphers[cipherAlgorithm]; 42 | 43 | if (!keyIv) { 44 | cipherAlgorithm = 'aes-256-cfb'; 45 | keyIv = SupportedCiphers[cipherAlgorithm]; 46 | } 47 | 48 | let key = new Buffer(password); 49 | let keyLength = keyIv[0]; 50 | 51 | if (key.length > keyLength) key = key.slice(0, keyLength); 52 | if (key.length < keyLength) key = new Buffer(password.repeat(keyLength / password.length + 1)).slice(0, keyLength); 53 | 54 | iv = iv || crypto.randomBytes(keyIv[1]); 55 | 56 | let cipher = type === 'cipher' ? crypto.createCipheriv(cipherAlgorithm, key, iv) : crypto.createDecipheriv(cipherAlgorithm, key, iv); 57 | 58 | return { cipher, iv }; 59 | } -------------------------------------------------------------------------------- /common/constant.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | export enum VPN_TYPE { 8 | APLVPN = 0x01, 9 | SOCKS5 = 0x05, 10 | OSXCL5 = 0xa5, 11 | } 12 | 13 | export const defaultCipherAlgorithm = 'aes-256-cfb'; 14 | export const defaultPassword = 'lightsword.neko'; 15 | export const defaultServerPort = 8900; 16 | 17 | export abstract class HandshakeOptions { 18 | password: string; 19 | cipherAlgorithm: string; 20 | timeout: number; 21 | iv: Buffer; 22 | speed: number; 23 | ivLength: number; 24 | // key: number[]; 25 | } 26 | 27 | export abstract class OSXCl5Options extends HandshakeOptions { 28 | xorNum: number; 29 | } -------------------------------------------------------------------------------- /common/ipc.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as fs from 'fs'; 8 | import * as net from 'net'; 9 | import * as path from 'path'; 10 | import * as util from 'util'; 11 | import * as child from 'child_process'; 12 | 13 | export enum COMMAND { 14 | STOP = 2, 15 | RESTART = 3, 16 | STATUS = 101, 17 | STATUSJSON = 102, 18 | } 19 | 20 | export class IpcServer { 21 | 22 | static start(tag: string) { 23 | let unixPath = util.format('/tmp/lightsword-%s.sock', tag); 24 | if (fs.existsSync(unixPath)) fs.unlinkSync(unixPath); 25 | 26 | let server = net.createServer(async (client) => { 27 | let data = await client.readAsync(); 28 | let msg = ''; 29 | let mem: { rss: number, heapTotal: number, heapUsed: number }; 30 | 31 | switch(data[0]) { 32 | case COMMAND.STOP: 33 | msg = `${path.basename(process.argv[1])}d (PID: ${process.pid}) is going to quit.`; 34 | await client.writeAsync(new Buffer(msg)); 35 | process.exit(0); 36 | break; 37 | case COMMAND.RESTART: 38 | let cp = child.spawn(process.argv[1], process.argv.skip(2).toArray(), { detached: true, stdio: 'ignore', env: process.env, cwd: process.cwd() }); 39 | cp.unref(); 40 | process.exit(0); 41 | break; 42 | case COMMAND.STATUS: 43 | mem = process.memoryUsage(); 44 | msg = `${path.basename(process.argv[1])}d (PID: ${process.pid}) is running.`; 45 | msg = util.format('%s\nHeap total: %sMB, heap used: %sMB, rss: %sMB', msg, (mem.heapTotal / 1024 / 1024).toPrecision(2), (mem.heapUsed / 1024 / 1024).toPrecision(2), (mem.rss / 1024 / 1024).toPrecision(2)); 46 | await client.writeAsync(new Buffer(msg)); 47 | client.dispose(); 48 | break; 49 | case COMMAND.STATUSJSON: 50 | mem = process.memoryUsage(); 51 | let obj = { 52 | process: path.basename(process.argv[1]) + 'd', 53 | pid: process.pid, 54 | heapTotal: mem.heapTotal, 55 | heapUsed: mem.heapUsed, 56 | rss: mem.rss 57 | }; 58 | await client.writeAsync(new Buffer(JSON.stringify(obj))); 59 | client.dispose(); 60 | break; 61 | } 62 | }); 63 | 64 | server.listen(unixPath); 65 | server.on('error', (err) => console.error(err.message)); 66 | } 67 | } 68 | 69 | export function sendCommand(tag: string, cmd: string, callback: (code) => void) { 70 | let cmdMap = { 71 | 'stop': COMMAND.STOP, 72 | 'restart': COMMAND.RESTART, 73 | 'status': COMMAND.STATUS, 74 | 'statusjson': COMMAND.STATUSJSON, 75 | }; 76 | 77 | let command = cmdMap[cmd.toLowerCase()]; 78 | 79 | if (!command) { 80 | console.error('Command is not supported'); 81 | return callback(1); 82 | } 83 | 84 | let path = util.format('/tmp/lightsword-%s.sock', tag); 85 | let socket = net.createConnection(path, async () => { 86 | await socket.writeAsync(new Buffer([command])); 87 | let msg = await socket.readAsync(); 88 | console.info(msg.toString('utf8')); 89 | socket.destroy(); 90 | callback(0); 91 | }); 92 | 93 | socket.on('error', (err: Error) => { console.info(`${tag} is not running or unix socket error.`); callback(1); }); 94 | socket.setTimeout(5 * 1000); 95 | } -------------------------------------------------------------------------------- /common/socks5constant.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | export enum AUTHENTICATION { 8 | NOAUTH = 0x00, 9 | GSSAPI = 0x01, 10 | USERPASS = 0x02, 11 | NONE = 0xFF 12 | } 13 | 14 | export enum REQUEST_CMD { 15 | CONNECT = 0x01, 16 | BIND = 0x02, 17 | UDP_ASSOCIATE = 0x03 18 | } 19 | 20 | export enum ATYP { 21 | IPV4 = 0x01, 22 | DN = 0x03, 23 | IPV6 = 0x04 24 | } 25 | 26 | export enum REPLY_CODE { 27 | SUCCESS = 0x00, 28 | SOCKS_SERVER_FAILURE = 0x01, 29 | CONNECTION_NOT_ALLOWED = 0x02, 30 | NETWORK_UNREACHABLE = 0x03, 31 | HOST_UNREACHABLE = 0x04, 32 | CONNECTION_REFUSED = 0x05, 33 | TTL_EXPIRED = 0x06, 34 | CMD_NOT_SUPPORTED = 0x07, 35 | ADDR_TYPE_NOT_SUPPORTED = 0x08, 36 | } 37 | 38 | export enum SOCKS_VER { 39 | V5 = 0x05 40 | } 41 | -------------------------------------------------------------------------------- /common/socks5helper.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as os from 'os'; 8 | import * as net from 'net'; 9 | import * as util from 'util'; 10 | import { ATYP, REQUEST_CMD } from './socks5constant'; 11 | 12 | // 13 | // TCP 14 | // +----+-----+-------+------+----------+----------+ 15 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 16 | // +----+-----+-------+------+----------+----------+ 17 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 18 | // +----+-----+-------+------+----------+----------+ 19 | // 20 | // UDP 21 | // +----+------+------+----------+----------+----------+ 22 | // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 23 | // +----+------+------+----------+----------+----------+ 24 | // | 2 | 1 | 1 | Variable | 2 | Variable | 25 | // +----+------+------+----------+----------+----------+ 26 | export function refineDestination(rawData: Buffer): { cmd: REQUEST_CMD, addr: string, port: number, headerSize: number } { 27 | if (rawData.length < 5) { 28 | return null; 29 | } 30 | 31 | let cmd = rawData[1]; 32 | let atyp = rawData[3]; 33 | let addr = ''; 34 | let dnLength = 0; 35 | 36 | switch (atyp) { 37 | case ATYP.DN: 38 | dnLength = rawData[4]; 39 | addr = rawData.toString('utf8', 5, 5 + dnLength); 40 | break; 41 | 42 | case ATYP.IPV4: 43 | dnLength = 4; 44 | addr = rawData.skip(4).take(4).aggregate((c: string, n) => c.length > 1 ? c + util.format('.%d', n) : util.format('%d.%d', c, n)); 45 | break; 46 | 47 | case ATYP.IPV6: 48 | dnLength = 16; 49 | let bytes = rawData.skip(4).take(16).toArray(); 50 | let ipv6 = ''; 51 | 52 | for (let b of bytes) { 53 | ipv6 += ('0' + b.toString(16)).substr(-2); 54 | } 55 | 56 | addr = ipv6.substr(0, 4); 57 | for (let i = 1; i < 8; i++) { 58 | addr = util.format('%s:%s', addr, ipv6.substr(4 * i, 4)); 59 | } 60 | 61 | break; 62 | } 63 | 64 | let headerSize = 4 + (atyp === ATYP.DN ? 1 : 0) + dnLength + 2; 65 | let port = rawData.readUInt16BE(headerSize - 2); 66 | return { cmd, addr, port, headerSize}; 67 | } 68 | 69 | // +----+-----+-------+------+----------+----------+ 70 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 71 | // +----+-----+-------+------+----------+----------+ 72 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 73 | // +----+-----+-------+------+----------+----------+ 74 | export function createSocks5TcpReply(rep: number, atyp: number, fullAddr: string, port: number): Buffer { 75 | let tuple = parseAddrToBytes(fullAddr); 76 | let type = tuple.type; 77 | let addr = tuple.addrBytes; 78 | 79 | let reply = [0x05, rep, 0x00, atyp]; 80 | if (type === ATYP.DN) reply.push(addr.length); 81 | reply = reply.concat(addr).concat([0x00, 0x00]); 82 | 83 | let buf = new Buffer(reply); 84 | buf.writeUInt16BE(port, buf.length - 2); 85 | return buf; 86 | } 87 | 88 | // +----+------+------+----------+----------+----------+ 89 | // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 90 | // +----+------+------+----------+----------+----------+ 91 | // | 2 | 1 | 1 | Variable | 2 | Variable | 92 | // +----+------+------+----------+----------+----------+ 93 | export function createSocks5UdpHeader(dstAddr: string, dstPort: number): Buffer { 94 | let tuple = parseAddrToBytes(dstAddr); 95 | let type = tuple.type; 96 | let addr = tuple.addrBytes; 97 | 98 | let reply = [0x0, 0x0, 0x0, type]; 99 | if (type === ATYP.DN) reply.push(addr.length); 100 | reply = reply.concat(addr).concat([0x00, 0x00]); 101 | 102 | let buf = new Buffer(reply); 103 | buf.writeUInt16BE(dstPort, buf.length - 2); 104 | return buf; 105 | } 106 | 107 | function parseAddrToBytes(fullAddr: string): { addrBytes: number[], type: ATYP } { 108 | let type = net.isIP(fullAddr); 109 | let addrBytes = []; 110 | switch (type) { 111 | case 4: 112 | addrBytes = fullAddr.split('.').select(s => Number.parseInt(s)).toArray(); 113 | break; 114 | case 6: 115 | addrBytes = fullAddr.split(':').select(s => [Number.parseInt(s.substr(0, 2), 16), Number.parseInt(s.substr(2, 2), 16)]).aggregate((c: Array, n) => c.concat(n)); 116 | break; 117 | case 0: 118 | fullAddr.each((c, i) => addrBytes.push(fullAddr.charCodeAt(i))); 119 | break; 120 | } 121 | 122 | return { addrBytes, type: type ? (type === 4 ? ATYP.IPV4 : ATYP.IPV6) : ATYP.DN } 123 | } -------------------------------------------------------------------------------- /docs/apple.md: -------------------------------------------------------------------------------- 1 | 2 | VPN Protocol 3 | --- 4 | 5 | TCP (UDP over TCP) 6 | 7 | ``` 8 | ------------------------------Request------------------------------- 9 | 10 | +--------+--------------+-------+---------+-----------+-----------+ 11 | | IP VER | PAYLOAD TYPE | FLAGS | DEST IP | DEST PORT | IV/Data | 12 | +--------+--------------+-------+---------+-----------+-----------+ 13 | | 1 | 1 | 1 | 4 / 16 | 2 | 8-16-Var | 14 | +--------+--------------+-------+---------+-----------+-----------+ 15 | 16 | IP TYPE: IPv4, IPv6 17 | PAYLOAD TYPE: TCP(0x06), UDP(0x11) 18 | FLAGS: Option flags 19 | DEST IP: Destination IP 20 | DEST PORT: Destination Port 21 | IV: Client Required IV (TCP) or Data (UDP) 22 | 23 | FLAGS: 24 | 25 | +----------+ 26 | | 00000000 | 27 | +----------+ 28 | | D------- | 29 | +----------+ 30 | 31 | 0b00000000 Authentication 32 | 0b10000000 Data flow 33 | 34 | --------------------TCP Response-------------------- 35 | 36 | +--------+--------+----------+ 37 | | RESULT | REASON | PADDING | 38 | +--------+--------+----------+ 39 | | 1 | 1 | 0-255 | 40 | +--------+--------+----------+ 41 | 42 | RESULT: Connection result. SUCCEED(0x01), FAILED(0x00) 43 | REASON: The reason of failure 44 | 45 | --------------------UDP Response-------------------- 46 | 47 | +------+-----+----------+ 48 | | IV | LEN | PAYLOAD | 49 | +------+-----+----------+ 50 | | 8-16 | 2 | VARIABLE | 51 | +------+-----+----------+ 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/protocols.md: -------------------------------------------------------------------------------- 1 | 2 | TCP 3 | --- 4 | 5 | ``` 6 | ------------------Request--------------------- 7 | 8 | +------+------+------+----------+------------+ 9 | | IV | TYPE | PLEN | RPADDING | PAYLOAD | 10 | +------+------+------+----------+------------+ 11 | | 8-16 | 1 | 1 | 0-255 | VARIABLE | 12 | +------+------+------+----------+------------+ 13 | | RAW | ENCRYPTED | 14 | +------+-------------------------------------+ 15 | 16 | IV: Initialization Vector, 8-16 Bytes. 17 | TYPE: VPN Type, 1 Byte. 18 | PLEN: Random Padding Length, 1 Byte. For OS X, it is used as XOR operator 19 | RPADDING: Random Padding Bytes, 0-255 Bytes. 20 | SOCKS5DATA: Encrypted Socks5 Protocol Data, Variable Bytes. 21 | 22 | ---------------Response----------------- 23 | 24 | +------+------+----------+-------------+ 25 | | IV | PLEN | RPADDING | PAYLOAD | 26 | +------+------+----------+-------------+ 27 | | 8-16 | 1 | 0-255 | VARIABLE | 28 | +------+------+----------+-------------+ 29 | | RAW | ENCRYPTED | 30 | +------+-------------------------------+ 31 | 32 | IV: Initialization Vector, 8-16 Bytes. 33 | PLEN: Random Padding Length, 1 Byte. For OS X, it's used as XOR operator 34 | RPADDING: Random Padding Bytes, 0-255 Bytes. 35 | SOCKS5REPLAY: Encrypted Socks5 Reply Data, Variable Bytes. 36 | 37 | ``` 38 | 39 | UDP 40 | --- 41 | 42 | ``` 43 | 44 | ----------------Request---------------- 45 | 46 | +------+------+----------+------------+ 47 | | IV | PLEN | RPADDING | SOCKETDATA | 48 | +------+------+----------+------------+ 49 | | 8-16 | 1 | 0-255 | VARIABLE | 50 | +------+------+----------+------------+ 51 | | RAW | ENCRYPTED | 52 | +------+------------------------------+ 53 | 54 | 55 | ----------------Response--------------- 56 | 57 | +-----------+ 58 | | UDP DGRAM | 59 | +-----------+ 60 | | VARIABLE | 61 | +-----------+ 62 | | ENCRYPTED | 63 | +-----------+ 64 | 65 | ``` -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6" 4 | } 5 | } -------------------------------------------------------------------------------- /lib/chacha20.ts: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* chacha20 - 256 bits */ 4 | 5 | // Written in 2014 by Devi Mandiri. Public domain. 6 | // 7 | // Implementation derived from chacha-ref.c version 20080118 8 | // See for details: http://cr.yp.to/chacha/chacha-20080128.pdf 9 | 10 | function U8TO32_LE(x, i) { 11 | return x[i] | (x[i+1]<<8) | (x[i+2]<<16) | (x[i+3]<<24); 12 | } 13 | 14 | function U32TO8_LE(x, i, u) { 15 | x[i] = u; u >>>= 8; 16 | x[i+1] = u; u >>>= 8; 17 | x[i+2] = u; u >>>= 8; 18 | x[i+3] = u; 19 | } 20 | 21 | function ROTATE(v, c) { 22 | return (v << c) | (v >>> (32 - c)); 23 | } 24 | 25 | export class Chacha20 { 26 | private input: Uint32Array; 27 | 28 | constructor(key: Buffer, nonce: Buffer, counter?: number) { 29 | this.input = new Uint32Array(16); 30 | 31 | // https://tools.ietf.org/html/draft-irtf-cfrg-chacha20-poly1305-01#section-2.3 32 | this.input[0] = 1634760805; 33 | this.input[1] = 857760878; 34 | this.input[2] = 2036477234; 35 | this.input[3] = 1797285236; 36 | this.input[4] = U8TO32_LE(key, 0); 37 | this.input[5] = U8TO32_LE(key, 4); 38 | this.input[6] = U8TO32_LE(key, 8); 39 | this.input[7] = U8TO32_LE(key, 12); 40 | this.input[8] = U8TO32_LE(key, 16); 41 | this.input[9] = U8TO32_LE(key, 20); 42 | this.input[10] = U8TO32_LE(key, 24); 43 | this.input[11] = U8TO32_LE(key, 28); 44 | 45 | // be compatible with the reference ChaCha depending on the nonce size 46 | if(nonce.length == 12) 47 | { 48 | this.input[12] = counter; 49 | this.input[13] = U8TO32_LE(nonce, 0); 50 | this.input[14] = U8TO32_LE(nonce, 4); 51 | this.input[15] = U8TO32_LE(nonce, 8); 52 | }else{ 53 | this.input[12] = counter; 54 | this.input[13] = 0; 55 | this.input[14] = U8TO32_LE(nonce, 0); 56 | this.input[15] = U8TO32_LE(nonce, 4); 57 | } 58 | 59 | } 60 | 61 | update(raw: Buffer): Buffer { 62 | let cipherText = new Buffer(raw.length); 63 | this.encrypt(cipherText, raw, raw.length); 64 | return cipherText; 65 | } 66 | 67 | final(): Buffer { 68 | return new Buffer(0); 69 | } 70 | 71 | private quarterRound(x, a, b, c, d) { 72 | x[a] += x[b]; x[d] = ROTATE(x[d] ^ x[a], 16); 73 | x[c] += x[d]; x[b] = ROTATE(x[b] ^ x[c], 12); 74 | x[a] += x[b]; x[d] = ROTATE(x[d] ^ x[a], 8); 75 | x[c] += x[d]; x[b] = ROTATE(x[b] ^ x[c], 7); 76 | } 77 | 78 | private encrypt(dst: Buffer, src: Buffer, len: number) { 79 | let x = new Uint32Array(16); 80 | let output = new Uint8Array(64); 81 | let i, dpos = 0, spos = 0; 82 | 83 | while (len > 0 ) { 84 | for (i = 16; i--;) x[i] = this.input[i]; 85 | for (i = 20; i > 0; i -= 2) { 86 | this.quarterRound(x, 0, 4, 8,12); 87 | this.quarterRound(x, 1, 5, 9,13); 88 | this.quarterRound(x, 2, 6,10,14); 89 | this.quarterRound(x, 3, 7,11,15); 90 | this.quarterRound(x, 0, 5,10,15); 91 | this.quarterRound(x, 1, 6,11,12); 92 | this.quarterRound(x, 2, 7, 8,13); 93 | this.quarterRound(x, 3, 4, 9,14); 94 | } 95 | for (i = 16; i--;) x[i] += this.input[i]; 96 | for (i = 16; i--;) U32TO8_LE(output, 4*i, x[i]); 97 | 98 | this.input[12] += 1; 99 | if (!this.input[12]) { 100 | this.input[13] += 1; 101 | } 102 | if (len <= 64) { 103 | for (i = len; i--;) { 104 | dst[i+dpos] = src[i+spos] ^ output[i]; 105 | } 106 | return; 107 | } 108 | for (i = 64; i--;) { 109 | dst[i+dpos] = src[i+spos] ^ output[i]; 110 | } 111 | len -= 64; 112 | spos += 64; 113 | dpos += 64; 114 | } 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /lib/chacha20stream.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import { Chacha20 } from './chacha20'; 8 | import * as stream from 'stream'; 9 | 10 | export class Chacha20Stream extends stream.Transform { 11 | private chacha20: Chacha20; 12 | 13 | constructor(key: Buffer, iv: Buffer, counter?: number) { 14 | super() 15 | this.chacha20 = new Chacha20(key, iv, counter); 16 | } 17 | 18 | _transform(chunk: Buffer, encoding, done: Function) { 19 | let me = this; 20 | 21 | this.push(me.chacha20.update(chunk)); 22 | done(); 23 | } 24 | 25 | update(raw: Buffer): Buffer { 26 | return this.chacha20.update(raw); 27 | } 28 | 29 | final(): Buffer { 30 | return new Buffer(0); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/cipherstream.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as stream from 'stream'; 8 | import * as crypto from 'crypto'; 9 | 10 | export class CipherStream extends stream.Transform { 11 | encrypt = false; 12 | algorithm = ''; 13 | key: Buffer; 14 | iv: Buffer; 15 | segmentSize: number; 16 | 17 | constructor(encryptOrDecrypt: boolean, algorithm: string, key: Buffer, iv: Buffer, segmentSize: number) { 18 | super(); 19 | 20 | this.encrypt = encryptOrDecrypt; 21 | this.algorithm = algorithm; 22 | this.key = key; 23 | this.iv = iv; 24 | this.segmentSize = segmentSize; 25 | } 26 | 27 | _transform(chunk: Buffer, encoding, done) { 28 | let me = this; 29 | 30 | 31 | let cipher = crypto.createCipheriv(me.algorithm, me.key, me.iv); 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /lib/pkcs7.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * pkcs7.pad 3 | * https://github.com/brightcove/pkcs7 4 | * 5 | * Copyright (c) 2014 Brightcove 6 | * Licensed under the apache2 license. 7 | */ 8 | 9 | 'use strict' 10 | 11 | // Pre-define the padding values 12 | 13 | const PADDING = [ 14 | [16, 16, 16, 16, 15 | 16, 16, 16, 16, 16 | 16, 16, 16, 16, 17 | 16, 16, 16, 16], 18 | 19 | [15, 15, 15, 15, 20 | 15, 15, 15, 15, 21 | 15, 15, 15, 15, 22 | 15, 15, 15], 23 | 24 | [14, 14, 14, 14, 25 | 14, 14, 14, 14, 26 | 14, 14, 14, 14, 27 | 14, 14], 28 | 29 | [13, 13, 13, 13, 30 | 13, 13, 13, 13, 31 | 13, 13, 13, 13, 32 | 13], 33 | 34 | [12, 12, 12, 12, 35 | 12, 12, 12, 12, 36 | 12, 12, 12, 12], 37 | 38 | [11, 11, 11, 11, 39 | 11, 11, 11, 11, 40 | 11, 11, 11], 41 | 42 | [10, 10, 10, 10, 43 | 10, 10, 10, 10, 44 | 10, 10], 45 | 46 | [9, 9, 9, 9, 47 | 9, 9, 9, 9, 48 | 9], 49 | 50 | [8, 8, 8, 8, 51 | 8, 8, 8, 8], 52 | 53 | [7, 7, 7, 7, 54 | 7, 7, 7], 55 | 56 | [6, 6, 6, 6, 57 | 6, 6], 58 | 59 | [5, 5, 5, 5, 60 | 5], 61 | 62 | [4, 4, 4, 4], 63 | 64 | [3, 3, 3], 65 | 66 | [2, 2], 67 | 68 | [1] 69 | ]; 70 | 71 | export function pad(plaintext: Buffer | Uint8Array | Array): Uint8Array { 72 | var padding = PADDING[(plaintext.length % 16) || 0], 73 | result = new Uint8Array(plaintext.length + padding.length); 74 | result.set(>plaintext); 75 | result.set(padding, plaintext.length); 76 | return result; 77 | }; 78 | 79 | export function unpad(padded: Buffer | Uint8Array): Uint8Array { 80 | return padded.subarray(0, padded.length - padded[padded.length - 1]); 81 | }; 82 | 83 | export const PKCS7Size = 16; 84 | -------------------------------------------------------------------------------- /lib/socketEx.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | 9 | net.Socket.prototype.dispose = function() { 10 | this.removeAllListeners(); 11 | this.end(); 12 | this.destroy(); 13 | } 14 | -------------------------------------------------------------------------------- /lib/speedstream.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as stream from 'stream'; 8 | 9 | export class SpeedStream extends stream.Transform { 10 | private bytesPerSecond = 0; 11 | private sentBytes = 0; 12 | private chunkCount = 0; 13 | private interval = 0; 14 | 15 | /** 16 | * speed: KB/s 17 | */ 18 | constructor(speed: number) { 19 | super() 20 | 21 | if (speed < 1) throw Error('can be negative speed'); 22 | 23 | this.bytesPerSecond = speed * 1024; 24 | } 25 | 26 | _transform(chunk: Buffer, encoding, done: Function) { 27 | let me = this; 28 | 29 | if (!me.writable) return; 30 | 31 | setTimeout(() => { 32 | if (!me.writable) { 33 | me.interval = 0; 34 | me.sentBytes = 0; 35 | me.chunkCount = 0; 36 | return; 37 | } 38 | 39 | me.push(chunk, encoding); 40 | done(); 41 | 42 | if (me.sentBytes > me.bytesPerSecond) { 43 | let avgChunkSize = me.sentBytes / me.chunkCount; 44 | me.interval = avgChunkSize / me.bytesPerSecond * 1000; 45 | me.sentBytes = 0; 46 | me.chunkCount = 0; 47 | } 48 | 49 | }, me.interval).unref(); 50 | 51 | me.sentBytes += chunk.length; 52 | me.chunkCount++; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /lib/xorstream.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as stream from 'stream'; 8 | 9 | export class XorStream extends stream.Transform { 10 | xor: number; 11 | 12 | constructor(x: number) { 13 | super() 14 | this.xor = x; 15 | } 16 | 17 | _transform(chunk, encoding, done) { 18 | let me = this; 19 | 20 | if (Buffer.isBuffer(chunk)) { 21 | let data = chunk; 22 | this.push(new Buffer(data.select(n => n ^ me.xor).toArray())); 23 | } else { 24 | this.push(chunk); 25 | } 26 | done(); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /misc/lsserver.conf: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8901, 3 | "password": "lightsword.neko", 4 | "method": "aes-256-cfb", 5 | "fork": true, 6 | "cluster": true 7 | } -------------------------------------------------------------------------------- /misc/lsserver.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # /etc/init.d/lsserver.sh 3 | 4 | ### BEGIN INIT INFO 5 | # Provides: LightSword 6 | # Required-Start: 7 | # Required-Stop: 8 | # Default-Start: 2 3 4 5 9 | # Default-Stop: 0 1 6 10 | # Short-Description: LightSword Server 11 | # Description: LightSword Secure Socks5 Proxy 12 | ### END INIT INFO 13 | 14 | case "$1" in 15 | start) 16 | lsserver -m aes-128-cfb -f 17 | ;; 18 | stop) 19 | lsserver -d stop 20 | ;; 21 | *) 22 | echo "Usage: /etc/init.d/lsserver.sh {start|stop}" 23 | exit 1 24 | ;; 25 | esac 26 | 27 | exit 0 -------------------------------------------------------------------------------- /misc/onekey_centos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yum update -y 4 | yum install curl -y 5 | curl -sL https://rpm.nodesource.com/setup_5.x | bash - 6 | yum install -y nodejs 7 | npm install lightsword -g -------------------------------------------------------------------------------- /misc/onekey_debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | apt-get update -y 4 | apt-get install curl -y 5 | curl -sL https://deb.nodesource.com/setup_5.x | bash - 6 | apt-get install -y nodejs 7 | npm install lightsword -g -------------------------------------------------------------------------------- /misc/onekey_ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | apt-get update -y 4 | apt-get install curl -y 5 | curl -sL https://deb.nodesource.com/setup_5.x | sudo -E bash - 6 | apt-get install -y nodejs 7 | npm install lightsword -g -------------------------------------------------------------------------------- /misc/users.conf: -------------------------------------------------------------------------------- 1 | # [ISO-8601-Extended-Date-Format-String] [Speed(KB/s)] 2 | 3 | 25000 abc aes-256-cfb 4 | 25001 128345 bf-cfb 2017-01-04T03:01:54+09:00 5 | 25002 128345 bf-cfb 2016-01-04T03:01:54+09:00 50 6 | 7 | #Error!!! 25002 128345 bf-cfb 50 If you set speed limitation, you must also set expired time. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightsword", 3 | "version": "0.7.2", 4 | "description": "LightSword Secure SOCKS5 Proxy / iOS VPN Server", 5 | "main": "index.js", 6 | "bin": { 7 | "lslocal": "./build/client/bin/cli.js", 8 | "lsclient": "./build/client/bin/cli.js", 9 | "lsserver": "./build/server/bin/cli.js", 10 | "lightsword-client": "./build/client/bin/cli.js", 11 | "lightsword-server": "./build/server/bin/cli.js", 12 | "lightsword": "./build/server/bin/cli.js", 13 | "lsbridge": "./build/bridge/bin/cli.js", 14 | "lightsword-bridge": "./build/bridge/bin/cli.js" 15 | }, 16 | "scripts": { 17 | "test": "mocha --timeout 60000 test/**/*.js test/*.js build/test/*.js" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/UnsignedInt8/LightSword.git" 22 | }, 23 | "keywords": [ 24 | "SOCK5", 25 | "Proxy", 26 | "GFW", 27 | "Secure" 28 | ], 29 | "author": "Neko", 30 | "license": "GPL-2.0", 31 | "bugs": { 32 | "url": "https://github.com/UnsignedInt8/LightSword/issues" 33 | }, 34 | "homepage": "https://github.com/UnsignedInt8/LightSword#readme", 35 | "dependencies": { 36 | "async-node": "", 37 | "body-parser": "^1.14.2", 38 | "commander": "^2.9.0", 39 | "express": "^4.13.3", 40 | "kinq": "^0.0.8" 41 | }, 42 | "devDependencies": { 43 | "memory-stream": "0.0.3", 44 | "mocha": "^2.3.4", 45 | "socks": "^1.1.8" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server/aplvpn/index.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import * as crypto from 'crypto'; 9 | import { IP_VER, Protocols } from './protocols'; 10 | import * as cryptoEx from '../../common/cipher'; 11 | import * as addrHelper from '../lib/addressHelper'; 12 | import { HandshakeOptions } from '../../common/constant'; 13 | import { handleUDP } from './udp'; 14 | import { handleTCP } from './tcp'; 15 | 16 | export type VpnHandshake = { 17 | ipVer: IP_VER, 18 | payloadProtocol: Protocols, 19 | flags: number, 20 | destAddress: number[], 21 | destHost: string, 22 | destPort: number, 23 | extra: Buffer, 24 | } 25 | 26 | const SupportedIPVers = [IP_VER.V4, IP_VER.V6]; 27 | const SupportedProtocols = [Protocols.TCP, Protocols.UDP]; 28 | 29 | export async function handleAppleVPN(client: net.Socket, handshakeData: Buffer, options: HandshakeOptions): Promise { 30 | if (handshakeData.length < 9) return false; 31 | 32 | let handshake: VpnHandshake = null; 33 | 34 | try { 35 | handshake = extractHandeshake(handshakeData); 36 | if (!SupportedIPVers.contains(handshake.ipVer)) return false; 37 | if (!SupportedProtocols.contains(handshake.payloadProtocol)) return false; 38 | } catch (error) { 39 | return false; 40 | } 41 | 42 | if (handshake.flags === 0x00 && handshake.destHost === '0.0.0.0' && handshake.destPort === 0) { 43 | try { await handleHandshake(client, handshake, options); } catch (error) { return false; } 44 | return true; 45 | } 46 | 47 | if (addrHelper.isIllegalAddress(handshake.destHost)) { 48 | client.dispose(); 49 | return true; 50 | } 51 | 52 | switch(handshake.payloadProtocol) { 53 | case Protocols.TCP: 54 | handleTCP(client, handshake, options); 55 | return true; 56 | case Protocols.UDP: 57 | handleUDP(client, handshake, options); 58 | return true; 59 | } 60 | 61 | return false; 62 | } 63 | 64 | function extractHandeshake(data: Buffer): VpnHandshake { 65 | let ipVer = data[0]; 66 | let payloadProtocol = data[1]; 67 | let flags = data[2]; 68 | 69 | let ipLength = ipVer == IP_VER.V4 ? 4 : 16; 70 | let destAddress = data.skip(3).take(ipLength).toArray(); 71 | let destPort = data.readUInt16BE(3 + ipLength); 72 | let extra = data.slice(3 + ipLength + 2); 73 | let destHost = addrHelper.ntoa(destAddress); 74 | 75 | return { ipVer, payloadProtocol, flags, destAddress, destHost, destPort, extra } 76 | } 77 | 78 | async function handleHandshake(client: net.Socket, handshake: VpnHandshake, options: HandshakeOptions) { 79 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, handshake.extra).cipher; 80 | let md5 = crypto.createHash('md5').update(handshake.extra).digest(); 81 | let randomPadding = new Buffer(Number((Math.random() * 128).toFixed())); 82 | client.on('error', () => {}); 83 | 84 | await client.writeAsync(Buffer.concat([cipher.update(md5), cipher.update(randomPadding)])); 85 | 86 | client.dispose(); 87 | } -------------------------------------------------------------------------------- /server/aplvpn/protocols.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | export enum IP_VER { 8 | V4 = 0x04, 9 | V6 = 0x06, 10 | } 11 | 12 | export enum Protocols { 13 | TCP = 0x06, 14 | UDP = 0x11, 15 | } -------------------------------------------------------------------------------- /server/aplvpn/tcp.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import * as crypto from 'crypto'; 9 | import * as cryptoEx from '../../common/cipher'; 10 | import { VpnHandshake } from './index'; 11 | import { HandshakeOptions } from '../../common/constant'; 12 | import { IP_VER, Protocols } from './protocols'; 13 | import { SpeedStream } from '../../lib/speedstream'; 14 | 15 | export function handleTCP(client: net.Socket, handshake: VpnHandshake, options: HandshakeOptions) { 16 | if (handshake.flags == 0x80) { 17 | handleOutbound(client, handshake.destHost, handshake.destPort, handshake.extra, options); 18 | } 19 | } 20 | 21 | function handleOutbound(client: net.Socket, host: string, port: number, desiredIv: Buffer, options: HandshakeOptions) { 22 | 23 | let proxy = net.createConnection({ port, host }, async () => { 24 | let success = new Buffer([0x01, 0x00]); 25 | let randomLength = Number((Math.random() * 64).toFixed()); 26 | let reply = Buffer.concat([success, new Buffer(randomLength)]); 27 | 28 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, desiredIv).cipher; 29 | await client.writeAsync(cipher.update(reply)); 30 | let decipher = cryptoEx.createDecipher(options.cipherAlgorithm, options.password, options.iv); 31 | 32 | let speed = options.speed; 33 | 34 | let clientStream = speed > 0 ? client.pipe(new SpeedStream(speed)) : client; 35 | clientStream.pipe(decipher).pipe(proxy); 36 | 37 | let proxyStream = speed > 0 ? proxy.pipe(new SpeedStream(speed)) : proxy; 38 | proxyStream.pipe(cipher).pipe(client); 39 | }); 40 | 41 | function dispose() { 42 | client.dispose(); 43 | proxy.dispose(); 44 | } 45 | 46 | proxy.on('error', dispose); 47 | proxy.on('end', dispose); 48 | client.on('error', dispose); 49 | client.on('end', dispose); 50 | 51 | proxy.setTimeout(options.timeout * 1000); 52 | client.setTimeout(options.timeout * 1000); 53 | } -------------------------------------------------------------------------------- /server/aplvpn/udp.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2016 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import * as dgram from 'dgram'; 9 | import * as crypto from 'crypto'; 10 | import * as cryptoEx from '../../common/cipher'; 11 | import { HandshakeOptions } from '../../common/constant'; 12 | import { VpnHandshake } from './index'; 13 | import { IP_VER } from './protocols'; 14 | 15 | export function handleUDP(client: net.Socket, handshake: VpnHandshake, options: HandshakeOptions) { 16 | let communicationPending = false; 17 | let udpType = handshake.ipVer == IP_VER.V4 ? 'udp4' : 'udp6'; 18 | let destAddress = handshake.destHost; 19 | let decipher: crypto.Decipher = null; 20 | 21 | let udpSocket = dgram.createSocket(udpType, async (msg: Buffer, rinfo: dgram.RemoteInfo) => { 22 | let iv = crypto.randomBytes(options.ivLength); 23 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, iv).cipher; 24 | let len = new Buffer(2); 25 | len.writeUInt16LE(msg.length, 0); 26 | let encrypted = cipher.update(Buffer.concat([len, msg])); 27 | await client.writeAsync(Buffer.concat([iv, encrypted])); 28 | communicationPending = true; 29 | }); 30 | 31 | udpSocket.on('error', () => dispose()); 32 | udpSocket.send(handshake.extra, 0, handshake.extra.length, handshake.destPort, destAddress); 33 | 34 | client.on('data', (d: Buffer) => { 35 | if (!decipher) decipher = cryptoEx.createDecipher(options.cipherAlgorithm, options.password, options.iv); 36 | let msg = decipher.update(d); 37 | udpSocket.send(msg, 0, msg.length, handshake.destPort, destAddress); 38 | communicationPending = true; 39 | }); 40 | 41 | let cleanTimer = setInterval(() => { 42 | if (communicationPending) { 43 | communicationPending = false; 44 | return; 45 | } 46 | 47 | dispose(); 48 | }, 30 * 1000); 49 | 50 | function dispose() { 51 | clearInterval(cleanTimer); 52 | client.dispose(); 53 | udpSocket.close(); 54 | udpSocket.unref(); 55 | udpSocket.removeAllListeners(); 56 | } 57 | 58 | client.on('error', () => dispose()); 59 | client.on('end', () => dispose()); 60 | } -------------------------------------------------------------------------------- /server/app.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | require('async-node'); 8 | require('kinq').enable(); 9 | require('../lib/socketEx'); 10 | import { LsServer, ServerOptions, UpdateServerOptions } from './server'; 11 | import { defaultCipherAlgorithm, defaultServerPort, defaultPassword } from '../common/constant'; 12 | 13 | export class App { 14 | 15 | constructor(options?) { 16 | let defaultOptions = { 17 | cipherAlgorithm: defaultCipherAlgorithm, 18 | password: defaultPassword, 19 | port: defaultServerPort, 20 | timeout: 10, 21 | expireTime: undefined, 22 | disableSelfProtection: false, 23 | speed: NaN 24 | }; 25 | 26 | options = options || defaultOptions; 27 | Object.getOwnPropertyNames(defaultOptions).forEach(n => options[n] = options[n] || defaultOptions[n]); 28 | 29 | let server = new LsServer(options); 30 | server.start(); 31 | server.once('close', () => App.Users.delete(options.port)); 32 | 33 | App.Users.set(options.port, server); 34 | } 35 | 36 | public static Users = new Map(); 37 | 38 | public static addUser(options: ServerOptions): boolean { 39 | if (App.Users.has(options.port)) return false; 40 | 41 | new App(options); 42 | return true; 43 | } 44 | 45 | public static addUsers(options: ServerOptions[]): boolean { 46 | let results = options.map(o => App.addUser(o)); 47 | return results.all(r => r === true); 48 | } 49 | 50 | public static updateUser(port: number, options: UpdateServerOptions) { 51 | if (!App.Users.has(port)) return false; 52 | 53 | App.Users.get(port).updateConfiguration(options); 54 | return true; 55 | } 56 | 57 | public static removeUser(port) { 58 | if (!App.Users.has(port)) return false; 59 | 60 | let server = App.Users.get(port); 61 | server.stop(); 62 | return true; 63 | } 64 | } 65 | 66 | if (!module.parent) { 67 | process.title = 'LightSword Server Debug Mode'; 68 | new App({ speed: 20 }); 69 | } -------------------------------------------------------------------------------- /server/bin/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | //----------------------------------- 3 | // Copyright(c) 2015 Neko 4 | //----------------------------------- 5 | 6 | import * as program from 'commander'; 7 | import { App } from '../app'; 8 | import * as ipc from '../../common/ipc'; 9 | import * as fs from 'fs'; 10 | import * as path from 'path'; 11 | import * as child from 'child_process'; 12 | import { runAsClusterMode } from '../cluster'; 13 | 14 | program 15 | .version('0.7.0') 16 | .option('-p, --port [number]', 'Server Listening Port', Number.parseInt) 17 | .option('-k, --password [password]', 'Cipher Password', String) 18 | .option('-m, --method [algorithm]', 'Cipher Algorithm', String) 19 | .option('-c, --config ', 'Configuration File Path', String) 20 | .option('-u, --users ', 'Mutli-users File Path', String) 21 | .option('-t, --timeout [number]', 'Timeout', Number.parseInt) 22 | .option('-f, --fork', 'Run as a Daemon') 23 | .option('-d, --daemon ', 'Daemon Control', String) 24 | .option('-r, --cluster', 'Run as Cluster Mode') 25 | .option('-a, --management', 'Enable HTTP Management') 26 | .option('-x, --user ', 'Run Under Specified Privilege') 27 | .option('-s, --speed ', 'Speed Limitation \(KB\/s\)', Number.parseInt) 28 | .option('--disableSelfProtection', 'Disable Self-Protection') 29 | .parse(process.argv); 30 | 31 | var args = program; 32 | 33 | function parseOptions(path: string) { 34 | if (!path) return; 35 | if (!fs.existsSync(path)) return; 36 | 37 | var content = fs.readFileSync(path).toString(); 38 | 39 | try { 40 | var configs = JSON.parse(content); 41 | return { 42 | port: configs.port, 43 | password: configs.password, 44 | cipherAlgorithm: configs.method, 45 | fork: configs.fork, 46 | cluster: configs.cluster, 47 | timeout: configs.timeout, 48 | management: configs.management, 49 | } 50 | } catch(ex) { 51 | console.warn('Configuration file error'); 52 | console.warn(ex.message); 53 | } 54 | } 55 | 56 | var fileOptions = parseOptions(args.config) || {}; 57 | if (fileOptions) Object.getOwnPropertyNames(fileOptions).forEach(n => args[n] = args[n] === undefined ? fileOptions[n] : args[n]); 58 | 59 | function parseUsers(path: string): { port: number, password: string, cipherAlgorithm: string, expireDate?: string, disableSelfProtection?: boolean }[] { 60 | if (!path) return []; 61 | if (!fs.existsSync(path)) return []; 62 | 63 | var content: string = fs.readFileSync(path).toString(); 64 | return content.split('\n').where((l: string) => l.length > 0 && !l.trim().startsWith('#')).select((l: string) => { 65 | var info = l.trim().split(' '); 66 | return { port: Number(info[0]), password: info[1], cipherAlgorithm: info[2], expireDate: info[3], speed: Number(info[4]), disableSelfProtection: args.disableSelfProtection }; 67 | }).toArray(); 68 | } 69 | 70 | var users = parseUsers(args.users); 71 | 72 | var argsOptions = { 73 | port: args.port, 74 | password: args.password, 75 | cipherAlgorithm: args.method, 76 | timeout: args.timeout, 77 | disableSelfProtection: args.disableSelfProtection, 78 | speed: args.speed 79 | } 80 | 81 | if (fileOptions) Object.getOwnPropertyNames(argsOptions).forEach(n => argsOptions[n] = argsOptions[n] === undefined ? fileOptions[n] : argsOptions[n]); 82 | 83 | if (!users.length) users.push(argsOptions); 84 | users = users.where(u => !Number.isNaN(u.port)).distinct((u1, u2) => u1.port === u2.port).toArray(); 85 | 86 | if (args.fork && !process.env.__daemon) { 87 | console.info('Run as daemon'); 88 | process.env.__daemon = true; 89 | var cp = child.spawn(process.argv[1], process.argv.skip(2).toArray(), { cwd: process.cwd(), stdio: 'ignore', env: process.env, detached: true }); 90 | cp.unref(); 91 | console.info('Child PID: ' + cp.pid); 92 | process.exit(0); 93 | } 94 | 95 | function listenDaemonCommands() { 96 | if (process.env.__daemon) { 97 | ipc.IpcServer.start('server'); 98 | } 99 | } 100 | 101 | if (args.daemon && !process.env.__daemon) { 102 | ipc.sendCommand('server', args.daemon, (code) => process.exit(code)); 103 | } else { 104 | if (args.cluster) { 105 | var clusterOptions = { 106 | users, 107 | management: args.management, 108 | user: args.user 109 | }; 110 | 111 | runAsClusterMode(clusterOptions, listenDaemonCommands); 112 | } else { 113 | users.forEach(u => new App(u)); 114 | listenDaemonCommands(); 115 | 116 | if (args.management) require('../management/index'); 117 | if (args.user) try { process.setuid(args.user); } catch(ex) { console.error(ex.message); } 118 | } 119 | 120 | process.title = process.env.__daemon ? path.basename(process.argv[1]) + 'd' : 'LightSword Server'; 121 | process.on('uncaughtException', (err) => { console.error(err); process.exit(1); }); 122 | } 123 | -------------------------------------------------------------------------------- /server/cluster.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as os from 'os'; 8 | import * as cluster from 'cluster'; 9 | import { App } from './app'; 10 | 11 | type clusterOptions = { 12 | management: boolean, 13 | user: string, 14 | users: any[] 15 | } 16 | 17 | export function runAsClusterMode(options: clusterOptions, callback: () => void) { 18 | if (cluster.isMaster) { 19 | os.cpus().forEach(() => { 20 | cluster.fork(); 21 | }); 22 | 23 | cluster.on('exit', () => cluster.fork()); 24 | return callback(); 25 | } 26 | 27 | options.users.forEach(o => new App(o)); 28 | 29 | if (options.management) require('./management/index'); 30 | if (options.user) try { process.setuid(options.user) } catch(ex) { console.error(ex.message); } 31 | } -------------------------------------------------------------------------------- /server/lib/addressHelper.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as os from 'os'; 8 | import * as util from 'util'; 9 | 10 | const illegalAddresses = ['127.0.0.1', '::1', '0.0.0.0', '::0', os.hostname()]; 11 | const illegalPrefix = ['192.168.', '10.', '172.168.', 'fe80:']; 12 | 13 | export function isIllegalAddress(addr: string): boolean { 14 | return illegalAddresses.any(a => a === addr) || illegalPrefix.any(a => addr.startsWith(a)); 15 | } 16 | 17 | export function ntoa(data: ArrayLike): string { 18 | let ipVer = data.length; 19 | switch (ipVer) { 20 | case 4: 21 | return util.format('%d.%d.%d.%d', data[0], data[1], data[2], data[3]); 22 | case 6: 23 | return util.format('%s%s:%s%s:%s%s:%s%s:%s%s:%s%s:%s%s:%s%s', 24 | data[0].toString(16), 25 | data[1].toString(16), 26 | data[2].toString(16), 27 | data[3].toString(16), 28 | data[4].toString(16), 29 | data[5].toString(16), 30 | data[6].toString(16), 31 | data[7].toString(16), 32 | data[8].toString(16), 33 | data[9].toString(16), 34 | data[10].toString(16), 35 | data[11].toString(16), 36 | data[12].toString(16), 37 | data[13].toString(16), 38 | data[14].toString(16), 39 | data[15].toString(16)); 40 | } 41 | } -------------------------------------------------------------------------------- /server/management/apiRouter.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as express from 'express'; 8 | import * as userController from './userController'; 9 | 10 | let router = express.Router(); 11 | 12 | router.get('/users/count', userController.getUserCount); 13 | router.get('/users', userController.getUsers); 14 | router.post('/users', userController.addUser); 15 | router.put('/users/:port', userController.updateUser); 16 | router.delete('/users/:port', userController.deleteUser); 17 | router.get('/blacklist', userController.getBlacklist); 18 | router.get('/blacklist/count', userController.getBlacklistCount); 19 | router.get('/blacklist/:port', userController.getServerOfPort, userController.getBlacklistOfPort); 20 | router.get('/blacklist/:port/count', userController.getServerOfPort, userController.getBlacklistCountOfPort); 21 | 22 | module.exports = router; -------------------------------------------------------------------------------- /server/management/cmdController.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as express from 'express'; 8 | 9 | -------------------------------------------------------------------------------- /server/management/index.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as express from 'express'; 8 | import * as bodyParser from 'body-parser'; 9 | import * as apiRouter from './apiRouter'; 10 | 11 | let app = express(); 12 | app.use(bodyParser.json()); 13 | app.use(bodyParser.urlencoded({ extended: true })); 14 | app.use('/api', >apiRouter); 15 | 16 | app.listen(5000, 'localhost'); -------------------------------------------------------------------------------- /server/management/userController.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import { App } from '../app'; 8 | import { LsServer } from '../server'; 9 | import * as express from 'express'; 10 | import * as kinq from 'kinq'; 11 | 12 | export function getUserCount(req: express.Request, res: express.Response) { 13 | res.json({ count: App.Users.size }); 14 | } 15 | 16 | export function getUsers(req: express.Request, res: express.Response) { 17 | let users = App.Users.select(item => { return { 18 | port: item[1].port, 19 | cipherAlgorithm: item[1].cipherAlgorithm, 20 | expireDate: item[1].expireDate, 21 | speed: item[1].speed 22 | } } ).toArray(); 23 | 24 | res.json(users); 25 | } 26 | 27 | export function addUser(req: express.Request, res: express.Response) { 28 | var body = req.body; 29 | 30 | let success = Array.isArray(body) ? App.addUsers(body) : App.addUser(body); 31 | let statusCode = success ? 200 : 400; 32 | let data = { 33 | success, 34 | msg: success ? undefined : `Port number: ${body.port} is used or access denied` 35 | }; 36 | 37 | res.status(statusCode).json(data); 38 | } 39 | 40 | export function updateUser(req: express.Request, res: express.Response) { 41 | var body = req.body; 42 | 43 | let success = App.updateUser(Number(req.params.port), body); 44 | let statusCode = success ? 200 : 404; 45 | let data = { 46 | success, 47 | msg: success ? undefined : 'User Not Found' 48 | }; 49 | 50 | res.status(statusCode).json(data); 51 | } 52 | 53 | export function deleteUser(req: express.Request, res: express.Response) { 54 | var port = Number(req.params.port); 55 | 56 | let success = App.removeUser(port); 57 | let statusCode = success ? 200 : 404; 58 | let data = { 59 | success, 60 | msg: success ? undefined : 'User Not Found' 61 | }; 62 | 63 | res.status(404).json(data); 64 | } 65 | 66 | export function getBlacklist(req: express.Request, res: express.Response) { 67 | let data = kinq.toLinqable(App.Users.values()).select(server => server.blackIPs).flatten(false).toArray(); 68 | res.status(data.length > 0 ? 200 : 404).json(data); 69 | } 70 | 71 | export function getBlacklistCount(req: express.Request, res: express.Response) { 72 | let count = kinq.toLinqable(App.Users.values()).select(s => s.blackIPs.size).sum(); 73 | res.json({ count }); 74 | } 75 | 76 | export function getServerOfPort(req: express.Request, res: express.Response, next: Function) { 77 | let server = kinq.toLinqable(App.Users.values()).singleOrDefault(s => s.port === Number(req.params.port), undefined); 78 | if (!server) { 79 | return res.status(404).json({ success: false, msg: 'User Not Found' }); 80 | } 81 | 82 | req.user = server; 83 | next(); 84 | } 85 | 86 | export function getBlacklistOfPort(req: express.Request, res: express.Response) { 87 | let server = req.user; 88 | res.json(server.blackIPs.toArray()); 89 | } 90 | 91 | export function getBlacklistCountOfPort(req: express.Request, res: express.Response) { 92 | let server = req.user; 93 | res.json({ count: server.blackIPs.size }); 94 | } -------------------------------------------------------------------------------- /server/osxcl5/connectHandler.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import * as crypto from 'crypto'; 9 | import * as cryptoEx from '../../common/cipher'; 10 | import { XorStream } from '../../lib/xorstream'; 11 | import { SpeedStream } from '../../lib/speedstream'; 12 | import { OSXCl5Options } from '../../common/constant'; 13 | 14 | export function connect(client: net.Socket, rawData: Buffer, dst: { addr: string, port: number }, options: OSXCl5Options) { 15 | 16 | let proxySocket = net.createConnection(dst.port, dst.addr, async () => { 17 | console.log(`connected: ${dst.addr}:${dst.port}`); 18 | 19 | let reply = rawData.slice(0, rawData.length) 20 | reply[0] = 0x05; 21 | reply[1] = 0x00; 22 | 23 | let encryptor = cryptoEx.createCipher(options.cipherAlgorithm, options.password); 24 | let cipher = encryptor.cipher; 25 | let iv = encryptor.iv; 26 | 27 | let pl = Number((Math.random() * 0xff).toFixed()); 28 | let el = new Buffer([pl]); 29 | let pd = crypto.randomBytes(pl); 30 | let er = cipher.update(Buffer.concat([el, pd, reply])); 31 | 32 | await client.writeAsync(Buffer.concat([iv, er])); 33 | 34 | let fromClientXorStream = new XorStream(options.xorNum); 35 | let toClientXorStream = new XorStream(pl); 36 | 37 | let speed = options.speed; 38 | 39 | let streamIn = speed > 0 ? client.pipe(new SpeedStream(speed)) : client; 40 | streamIn.pipe(fromClientXorStream).pipe(proxySocket); 41 | 42 | let streamOut = speed > 0 ? proxySocket.pipe(new SpeedStream(speed)) : proxySocket; 43 | streamOut.pipe(toClientXorStream).pipe(client); 44 | 45 | }); 46 | 47 | function dispose(err: Error) { 48 | if (err) console.info(err.message); 49 | client.dispose(); 50 | proxySocket.dispose(); 51 | } 52 | 53 | proxySocket.on('error', dispose); 54 | proxySocket.on('end', dispose); 55 | client.on('error', dispose); 56 | client.on('end', dispose); 57 | 58 | proxySocket.setTimeout(options.timeout * 1000); 59 | client.setTimeout(options.timeout * 1000); 60 | } -------------------------------------------------------------------------------- /server/osxcl5/index.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import { connect } from './connectHandler'; 9 | import { OSXCl5Options } from '../../common/constant'; 10 | import { REQUEST_CMD } from '../../common/socks5constant'; 11 | import * as socks5Helper from '../../common/socks5helper'; 12 | import { isIllegalAddress } from '../lib/addressHelper'; 13 | 14 | export function handleOSXSocks5(client: net.Socket, data: Buffer, options: OSXCl5Options): boolean { 15 | let dst = socks5Helper.refineDestination(data); 16 | 17 | if (!dst) return false; 18 | 19 | if (isIllegalAddress(dst.addr)) { 20 | client.dispose(); 21 | return true; 22 | } 23 | 24 | switch (dst.cmd) { 25 | case REQUEST_CMD.CONNECT: 26 | connect(client, data, dst, options); 27 | break; 28 | case REQUEST_CMD.BIND: 29 | break; 30 | case REQUEST_CMD.UDP_ASSOCIATE: 31 | break; 32 | 33 | default: 34 | return false; 35 | } 36 | 37 | return true; 38 | } -------------------------------------------------------------------------------- /server/socks5/connectHandler.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import * as crypto from 'crypto'; 9 | import * as cryptoEx from '../../common/cipher'; 10 | import { HandshakeOptions } from '../../common/constant'; 11 | import { SpeedStream } from '../../lib/speedstream'; 12 | 13 | export function connect(client: net.Socket, rawData: Buffer, dst: { addr: string, port: number }, options: HandshakeOptions) { 14 | let proxySocket = net.createConnection(dst.port, dst.addr, async () => { 15 | console.log(`connecting: ${dst.addr}:${dst.port}`); 16 | 17 | let reply = rawData.slice(0, rawData.length); 18 | reply[0] = 0x05; 19 | reply[1] = 0x00; 20 | 21 | var encryptor = cryptoEx.createCipher(options.cipherAlgorithm, options.password); 22 | let cipherHandshake = encryptor.cipher; 23 | let iv = encryptor.iv; 24 | 25 | let pl = Number((Math.random() * 0xff).toFixed()); 26 | let el = new Buffer([pl]); 27 | let pd = crypto.randomBytes(pl); 28 | let er = cipherHandshake.update(Buffer.concat([el, pd, reply])); 29 | 30 | await client.writeAsync(Buffer.concat([iv, er])); 31 | 32 | let decipher = cryptoEx.createDecipher(options.cipherAlgorithm, options.password, options.iv); 33 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, iv).cipher; 34 | 35 | let speed = options.speed; 36 | 37 | let streamIn = speed > 0 ? client.pipe(new SpeedStream(speed)) : client; 38 | streamIn.pipe(decipher).pipe(proxySocket); 39 | 40 | let streamOut = speed > 0 ? proxySocket.pipe(new SpeedStream(speed)) : proxySocket; 41 | streamOut.pipe(cipher).pipe(client); 42 | 43 | }); 44 | 45 | function dispose(err: Error) { 46 | if (err) console.info(err.message); 47 | client.dispose(); 48 | proxySocket.dispose(); 49 | } 50 | 51 | proxySocket.on('error', dispose); 52 | proxySocket.on('end', dispose); 53 | client.on('error', dispose); 54 | client.on('end', dispose); 55 | 56 | proxySocket.setTimeout(options.timeout * 1000); 57 | client.setTimeout(options.timeout * 1000); 58 | } -------------------------------------------------------------------------------- /server/socks5/index.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import { connect } from './connectHandler'; 9 | import { udpAssociate } from './udpHandler'; 10 | import { HandshakeOptions } from '../../common/constant'; 11 | import { REQUEST_CMD } from '../../common/socks5constant'; 12 | import * as socks5Helper from '../../common/socks5helper'; 13 | import { isIllegalAddress } from '../lib/addressHelper'; 14 | 15 | export function handleSocks5(client: net.Socket, data: Buffer, options: HandshakeOptions): boolean { 16 | let dst = socks5Helper.refineDestination(data); 17 | 18 | if (!dst) return false; 19 | 20 | if (isIllegalAddress(dst.addr)) { 21 | client.dispose(); 22 | return true; 23 | } 24 | 25 | switch (dst.cmd) { 26 | case REQUEST_CMD.CONNECT: 27 | connect(client, data, dst, options); 28 | break; 29 | case REQUEST_CMD.BIND: 30 | break; 31 | case REQUEST_CMD.UDP_ASSOCIATE: 32 | udpAssociate(client, data, dst, options); 33 | break; 34 | default: 35 | return false; 36 | } 37 | 38 | return true; 39 | } -------------------------------------------------------------------------------- /server/socks5/udpHandler.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as net from 'net'; 8 | import * as dgram from 'dgram'; 9 | import * as crypto from 'crypto'; 10 | import * as cryptoEx from '../../common/cipher'; 11 | import { ATYP } from '../../common/socks5constant'; 12 | import { HandshakeOptions } from '../../common/constant'; 13 | import * as socksHelper from '../../common/socks5helper'; 14 | 15 | export function udpAssociate(client: net.Socket, rawData: Buffer, dst: { addr: string, port: number }, options: HandshakeOptions) { 16 | let udpType = 'udp' + (net.isIP(dst.addr) || 4); 17 | let serverUdp = dgram.createSocket(udpType); 18 | let ivLength: number = cryptoEx.SupportedCiphers[options.cipherAlgorithm][1]; 19 | 20 | serverUdp.bind(); 21 | serverUdp.unref(); 22 | serverUdp.once('listening', async () => { 23 | let udpAddr = serverUdp.address(); 24 | let reply = socksHelper.createSocks5TcpReply(0x0, udpAddr.family === 'IPv4' ? ATYP.IPV4 : ATYP.IPV6, udpAddr.address, udpAddr.port); 25 | 26 | let encryptor = cryptoEx.createCipher(options.cipherAlgorithm, options.password); 27 | let cipher = encryptor.cipher; 28 | let iv = encryptor.iv; 29 | 30 | let pl = Number((Math.random() * 0xff).toFixed()); 31 | let el = new Buffer([pl]); 32 | let pd = crypto.randomBytes(pl); 33 | let er = cipher.update(Buffer.concat([el, pd, reply])); 34 | 35 | await client.writeAsync(Buffer.concat([iv, er])); 36 | }); 37 | 38 | let udpSet = new Set(); 39 | serverUdp.on('message', async (msg: Buffer, cinfo: dgram.RemoteInfo) => { 40 | let iv = new Buffer(ivLength); 41 | msg.copy(iv, 0, 0, ivLength); 42 | 43 | let decipher = cryptoEx.createDecipher(options.cipherAlgorithm, options.password, iv); 44 | let cipher = cryptoEx.createCipher(options.cipherAlgorithm, options.password, iv).cipher; 45 | 46 | let data = decipher.update(msg.slice(iv.length, msg.length)); 47 | let pl = data[0]; 48 | 49 | let udpMsg = data.slice(1 + pl, data.length); 50 | let dst = socksHelper.refineDestination(udpMsg); 51 | 52 | let socketId = `${cinfo.address}:${cinfo.port}`; 53 | let proxyUdp = dgram.createSocket(udpType); 54 | proxyUdp.unref(); 55 | 56 | proxyUdp.send(udpMsg, dst.headerSize, udpMsg.length - dst.headerSize, dst.port, dst.addr); 57 | proxyUdp.on('message', (msg: Buffer, rinfo: dgram.RemoteInfo) => { 58 | let data = cipher.update(msg); 59 | proxyUdp.send(data, 0, data.length, cinfo.port, cinfo.address); 60 | }); 61 | 62 | proxyUdp.on('error', (err: Error) => console.log(err.message)); 63 | udpSet.add(proxyUdp); 64 | }); 65 | 66 | function dispose() { 67 | serverUdp.removeAllListeners(); 68 | serverUdp.close(); 69 | serverUdp.unref(); 70 | 71 | client.dispose(); 72 | 73 | udpSet.forEach(udp => { 74 | udp.close(); 75 | }); 76 | } 77 | 78 | client.on('error', dispose); 79 | client.on('end', dispose); 80 | serverUdp.on('error', dispose); 81 | serverUdp.on('close', dispose); 82 | } -------------------------------------------------------------------------------- /test/httpManagement.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as http from 'http'; 8 | import * as assert from 'assert'; 9 | require('../server/management/index'); 10 | 11 | describe('HTTP Management Test Cases', () => { 12 | before((done) => { 13 | 14 | let data = JSON.stringify({ 15 | port: 35000, 16 | cipherAlgorithm: 'bf-cfb', 17 | password: 'abc' 18 | }); 19 | 20 | let options: http.RequestOptions = { 21 | host: 'localhost', 22 | port: 5000, 23 | path: '/api/users', 24 | method: 'POST', 25 | headers: { 26 | 'Content-Type': 'application/json', 27 | 'Content-Length': Buffer.byteLength(data) 28 | } 29 | }; 30 | 31 | let httpReq = http.request(options, res => { 32 | let msg = ''; 33 | res.on('data', d => msg += d); 34 | res.on('end', () => { 35 | let ok = JSON.parse(msg); 36 | assert(ok.success); 37 | done(); 38 | }); 39 | }); 40 | 41 | httpReq.write(data); 42 | httpReq.end(); 43 | }); 44 | 45 | it('should have 1 user', (done) => { 46 | http.get('http://localhost:5000/api/users/count', res => { 47 | let msg = ''; 48 | res.on('data', d => msg += d); 49 | res.on('end', () => { 50 | let msgObj = JSON.parse(msg); 51 | assert(msgObj.count === 1); 52 | done(); 53 | }); 54 | }); 55 | }); 56 | 57 | it('create new users (Array)', (done) => { 58 | let users = JSON.stringify([ 59 | { 60 | port: 49000, 61 | password: '123', 62 | cipherAlgorithm: 'rc4-md5' 63 | }, 64 | { 65 | port: 49001, 66 | password: 'abce', 67 | cipherAlgorithm: 'aes-128-cfb' 68 | } 69 | ]); 70 | 71 | let options: http.RequestOptions = { 72 | host: 'localhost', 73 | port: 5000, 74 | path: '/api/users', 75 | method: 'POST', 76 | headers: { 77 | 'Content-Type': 'application/json', 78 | 'Content-Length': Buffer.byteLength(users) 79 | } 80 | }; 81 | 82 | let httpReq = http.request(options, res => { 83 | let msg = ''; 84 | res.on('data', d => msg += d); 85 | res.on('end', () => { 86 | let obj = JSON.parse(msg); 87 | assert(obj.success); 88 | done(); 89 | }); 90 | }); 91 | 92 | httpReq.write(users); 93 | httpReq.end(); 94 | }); 95 | 96 | it('get users count', (done) => { 97 | http.get('http://localhost:5000/api/users/count', (res) => { 98 | let msg = ''; 99 | res.on('data', d => msg += d); 100 | res.on('end', () => { 101 | let obj = JSON.parse(msg); 102 | assert(obj.count === 3); 103 | done(); 104 | }); 105 | }) 106 | }); 107 | 108 | it('delete user', (done) => { 109 | let options: http.RequestOptions = { 110 | host: 'localhost', 111 | port: 5000, 112 | path: '/api/users/49000', 113 | method: 'DELETE' 114 | }; 115 | 116 | http.request(options, res => { 117 | let msg = ''; 118 | res.on('data', d => msg += d); 119 | res.on('end', () => { 120 | let obj = JSON.parse(msg); 121 | assert(obj.success); 122 | done(); 123 | }); 124 | }).end(); 125 | }); 126 | 127 | it('update user expiring', (done) => { 128 | let data = JSON.stringify({ 129 | expireDate: "2017-01-04T03:01:54+09:00" 130 | }); 131 | 132 | let options: http.RequestOptions = { 133 | host: 'localhost', 134 | path: '/api/users/35000', 135 | port: 5000, 136 | method: 'PUT', 137 | headers: { 138 | 'Content-Type': 'application/json', 139 | 'Content-Length': Buffer.byteLength(data) 140 | } 141 | }; 142 | 143 | let httpReq = http.request(options, res => { 144 | let msg = ''; 145 | res.on('data', d => msg += d); 146 | res.on('end', () => { 147 | let obj = JSON.parse(msg); 148 | assert(obj.success); 149 | done(); 150 | }); 151 | }); 152 | 153 | httpReq.write(data); 154 | httpReq.end(); 155 | }); 156 | 157 | it('update not exist user expiring', (done) => { 158 | let data = JSON.stringify({ 159 | expireDate: "2017-01-04T03:01:54+09:00" 160 | }); 161 | 162 | let options: http.RequestOptions = { 163 | host: 'localhost', 164 | path: '/api/users/350500', 165 | port: 5000, 166 | method: 'PUT', 167 | headers: { 168 | 'Content-Type': 'application/json', 169 | 'Content-Length': Buffer.byteLength(data) 170 | } 171 | }; 172 | 173 | let httpReq = http.request(options, res => { 174 | let msg = ''; 175 | res.on('data', d => msg += d); 176 | res.on('end', () => { 177 | let obj = JSON.parse(msg); 178 | assert(obj.success === false); 179 | done(); 180 | }); 181 | }); 182 | 183 | httpReq.write(data); 184 | httpReq.end(); 185 | }); 186 | 187 | after((done) => { 188 | http.get('http://localhost:5000/api/users/count', res => { 189 | let msg = ''; 190 | res.on('data', d => msg += d); 191 | res.on('end', () => { 192 | assert(JSON.parse(msg).count === 2); 193 | done(); 194 | }) 195 | }); 196 | }); 197 | }); -------------------------------------------------------------------------------- /test/ip.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //--------------------------------------------- 4 | 5 | 'use strict' 6 | require('kinq').enable(); 7 | const net = require('net'); 8 | const util = require('util'); 9 | const crypto = require('crypto'); 10 | const assert = require('assert'); 11 | 12 | describe('ip test', () => { 13 | it('should be ipv4', () => { 14 | let rawData = [0x87, 0x58, 0x24, 0xef]; 15 | let addr = rawData.aggregate((c, n) => c.length > 1 ? c + util.format('.%d', n) : util.format('%d.%d', c, n)); 16 | assert(net.isIPv4(addr)); 17 | assert(addr === '135.88.36.239'); 18 | }) 19 | 20 | it('should be ipv6', () => { 21 | let bytes = Array.from(crypto.randomBytes(16)); 22 | let addr = ''; 23 | let ipv6 = ''; 24 | 25 | for (let b of bytes) { 26 | addr += ('0' + b.toString(16)).substr(-2); 27 | } 28 | 29 | ipv6 = addr.substr(0, 4); 30 | for (let i = 1; i < 8; i++) { 31 | ipv6 = util.format('%s:%s', ipv6, addr.substr(4 * i, 4)); 32 | } 33 | 34 | assert(net.isIPv6(ipv6)); 35 | }); 36 | 37 | it('should be bytes', () => { 38 | let ipv4 = '1.2.2.3'; 39 | let ipv6 = '2378:ab31:ba73:1111:0010:8888:eeff:9920'; 40 | let dn = 'google.com'; 41 | 42 | assert(toBytes(ipv4).length === 4); 43 | assert(toBytes(ipv6).length === 16); 44 | assert(toBytes(dn).length === 10); 45 | }); 46 | 47 | function toBytes(fullAddr) { 48 | 49 | let type = net.isIP(fullAddr); 50 | let addr = []; 51 | switch (type) { 52 | case 4: 53 | addr = fullAddr.split('.').select(s => Number.parseInt(s)).toArray(); 54 | break; 55 | case 6: 56 | addr = fullAddr.split(':').select(s => [Number.parseInt(s.substr(0, 2), 16), Number.parseInt(s.substr(2, 2), 16)]).aggregate((c, n) => c.concat(n)); 57 | break; 58 | case 0: 59 | fullAddr.each((c, i) => addr.push(fullAddr.charCodeAt(i))); 60 | break; 61 | } 62 | 63 | return addr; 64 | } 65 | }); -------------------------------------------------------------------------------- /test/pkcs7.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | import * as kinq from 'kinq'; 8 | import * as pkcs7 from '../lib/pkcs7'; 9 | import * as assert from 'assert'; 10 | 11 | kinq.enable(); 12 | 13 | describe('test pkcs7', () => { 14 | it('should be 16 bytes', () => { 15 | assert(pkcs7.pad([0x2]).length === 16); 16 | assert(pkcs7.pad([]).length === 16); 17 | }); 18 | 19 | it('should be 32 bytes', () => { 20 | let bytes = pkcs7.pad(new Buffer(17).fill(3)); 21 | assert(bytes.length === 32); 22 | assert(kinq.toLinqable(bytes).skip(17).all(i => i === 15)); 23 | }); 24 | 25 | it('should be 1 bytes', () => { 26 | let padded = pkcs7.pad([1]); 27 | assert(pkcs7.unpad(padded).length === 1); 28 | }); 29 | }); -------------------------------------------------------------------------------- /test/socks5.ts: -------------------------------------------------------------------------------- 1 | //--------------------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //--------------------------------------------- 4 | 5 | 'use strict' 6 | 7 | require('async-node'); 8 | require('kinq').enable(); 9 | require('../lib/socketEx'); 10 | import { LsServer } from '../server/server'; 11 | import { LocalProxyServer } from '../client/socks5/localProxyServer'; 12 | import { RemoteProxyServer } from '../client/socks5/remoteProxyServer'; 13 | import * as assert from 'assert'; 14 | import * as socks from 'socks'; 15 | import * as net from 'net'; 16 | 17 | 18 | describe('socks5 server', () => { 19 | 20 | let serverPort = 10000; 21 | let proxyPort = 8900; 22 | let algorithm = 'rc4'; 23 | let pw = '19'; 24 | 25 | let serverOpts = { 26 | cipherAlgorithm: algorithm, 27 | password: pw, 28 | port: serverPort, 29 | timeout: 60 30 | }; 31 | 32 | let proxyOpts = { 33 | listenAddr: 'localhost', 34 | listenPort: proxyPort, 35 | serverAddr: 'localhost', 36 | serverPort: serverPort, 37 | cipherAlgorithm: algorithm, 38 | password: pw, 39 | timeout: 60, 40 | bypassLocal: true 41 | }; 42 | 43 | let clientOpts = { 44 | timeout: 60000, 45 | 46 | proxy: { 47 | ipaddress: "localhost", 48 | port: proxyPort, 49 | command: 'connect', 50 | type: 5 // (4 or 5) 51 | }, 52 | 53 | target: { 54 | host: "google.com", // (google.com) 55 | port: 80 56 | } 57 | }; 58 | 59 | let server = new LsServer(serverOpts); 60 | server.start(); 61 | 62 | let rpServer = new RemoteProxyServer(proxyOpts); 63 | rpServer.start(); 64 | 65 | it('status test', async (done) => { 66 | socks.createConnection(clientOpts, async (err, socket, info) => { 67 | if (err) return assert.fail(err, null, err.message); 68 | assert(net.isIP(socket.remoteAddress)); 69 | done(); 70 | }); 71 | }); 72 | }); -------------------------------------------------------------------------------- /test/transform.ts: -------------------------------------------------------------------------------- 1 | //----------------------------------- 2 | // Copyright(c) 2015 Neko 3 | //----------------------------------- 4 | 5 | 'use strict' 6 | 7 | require('kinq').enable(); 8 | import * as fs from 'fs'; 9 | import * as assert from 'assert'; 10 | import * as stream from 'stream'; 11 | import * as ms from 'memory-stream'; 12 | import { XorStream } from '../lib/xorstream'; 13 | 14 | describe('test XorStream', () => { 15 | 16 | it('Compare XorStream', (done) => { 17 | 18 | let mems = new ms(); 19 | let xor1Stream = new XorStream(5); 20 | let xor2Stream = new XorStream(5); 21 | 22 | mems.on('finish', () => { 23 | let fc = fs.readFileSync('./README.md').toString(); 24 | assert(mems.toString() === fc); 25 | done(); 26 | }); 27 | 28 | fs.createReadStream('./README.md').pipe(xor1Stream).pipe(xor2Stream).pipe(mems); 29 | }) 30 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs", 5 | "outDir": "./build" 6 | }, 7 | "exclude": [ 8 | ".vscode", 9 | ".client/build", 10 | ".server/build", 11 | ".bridge/build", 12 | "node_modules" 13 | ] 14 | } -------------------------------------------------------------------------------- /typings/async-node/async-node.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare module 'async-node' { 3 | 4 | import { Socket } from 'net'; 5 | 6 | interface ISocketAsync { 7 | connectAsync(path: string): Promise; 8 | connectAsync(port: number, host: string): Promise; 9 | writeAsync(data: Buffer): Promise; 10 | writeAsync(data: Buffer, encoding: string): Promise; 11 | readAsync(): Promise; 12 | } 13 | 14 | export interface Socket extends ISocketAsync { 15 | } 16 | } -------------------------------------------------------------------------------- /typings/body-parser/body-parser.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for body-parser 2 | // Project: http://expressjs.com 3 | // Definitions by: Santi Albo , VILIC VANE , Jonathan Häberle 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | /// 7 | 8 | declare module "body-parser" { 9 | import * as express from "express"; 10 | 11 | /** 12 | * bodyParser: use individual json/urlencoded middlewares 13 | * @deprecated 14 | */ 15 | 16 | function bodyParser(options?: { 17 | /** 18 | * if deflated bodies will be inflated. (default: true) 19 | */ 20 | inflate?: boolean; 21 | /** 22 | * maximum request body size. (default: '100kb') 23 | */ 24 | limit?: any; 25 | /** 26 | * function to verify body content, the parsing can be aborted by throwing an error. 27 | */ 28 | verify?: (req: express.Request, res: express.Response, buf: Buffer, encoding: string) => void; 29 | /** 30 | * only parse objects and arrays. (default: true) 31 | */ 32 | strict?: boolean; 33 | /** 34 | * passed to JSON.parse(). 35 | */ 36 | receiver?: (key: string, value: any) => any; 37 | /** 38 | * parse extended syntax with the qs module. (default: true) 39 | */ 40 | extended?: boolean; 41 | }): express.RequestHandler; 42 | 43 | module bodyParser { 44 | export function json(options?: { 45 | /** 46 | * if deflated bodies will be inflated. (default: true) 47 | */ 48 | inflate?: boolean; 49 | /** 50 | * maximum request body size. (default: '100kb') 51 | */ 52 | limit?: any; 53 | /** 54 | * request content-type to parse, passed directly to the type-is library. (default: 'json') 55 | */ 56 | type?: any; 57 | /** 58 | * function to verify body content, the parsing can be aborted by throwing an error. 59 | */ 60 | verify?: (req: express.Request, res: express.Response, buf: Buffer, encoding: string) => void; 61 | /** 62 | * only parse objects and arrays. (default: true) 63 | */ 64 | strict?: boolean; 65 | /** 66 | * passed to JSON.parse(). 67 | */ 68 | receiver?: (key: string, value: any) => any; 69 | }): express.RequestHandler; 70 | 71 | export function raw(options?: { 72 | /** 73 | * if deflated bodies will be inflated. (default: true) 74 | */ 75 | inflate?: boolean; 76 | /** 77 | * maximum request body size. (default: '100kb') 78 | */ 79 | limit?: any; 80 | /** 81 | * request content-type to parse, passed directly to the type-is library. (default: 'application/octet-stream') 82 | */ 83 | type?: any; 84 | /** 85 | * function to verify body content, the parsing can be aborted by throwing an error. 86 | */ 87 | verify?: (req: express.Request, res: express.Response, buf: Buffer, encoding: string) => void; 88 | }): express.RequestHandler; 89 | 90 | export function text(options?: { 91 | /** 92 | * if deflated bodies will be inflated. (default: true) 93 | */ 94 | inflate?: boolean; 95 | /** 96 | * maximum request body size. (default: '100kb') 97 | */ 98 | limit?: any; 99 | /** 100 | * request content-type to parse, passed directly to the type-is library. (default: 'text/plain') 101 | */ 102 | type?: any; 103 | /** 104 | * function to verify body content, the parsing can be aborted by throwing an error. 105 | */ 106 | verify?: (req: express.Request, res: express.Response, buf: Buffer, encoding: string) => void; 107 | /** 108 | * the default charset to parse as, if not specified in content-type. (default: 'utf-8') 109 | */ 110 | defaultCharset?: string; 111 | }): express.RequestHandler; 112 | 113 | export function urlencoded(options?: { 114 | /** 115 | * if deflated bodies will be inflated. (default: true) 116 | */ 117 | inflate?: boolean; 118 | /** 119 | * maximum request body size. (default: '100kb') 120 | */ 121 | limit?: any; 122 | /** 123 | * request content-type to parse, passed directly to the type-is library. (default: 'urlencoded') 124 | */ 125 | type?: any; 126 | /** 127 | * function to verify body content, the parsing can be aborted by throwing an error. 128 | */ 129 | verify?: (req: express.Request, res: express.Response, buf: Buffer, encoding: string) => void; 130 | /** 131 | * parse extended syntax with the qs module. (default: true) 132 | */ 133 | extended?: boolean; 134 | }): express.RequestHandler; 135 | } 136 | 137 | export = bodyParser; 138 | } -------------------------------------------------------------------------------- /typings/mime/mime.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for mime 2 | // Project: https://github.com/broofa/node-mime 3 | // Definitions by: Jeff Goddard 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | // Imported from: https://github.com/soywiz/typescript-node-definitions/mime.d.ts 7 | 8 | declare module "mime" { 9 | export function lookup(path: string): string; 10 | export function extension(mime: string): string; 11 | export function load(filepath: string): void; 12 | export function define(mimes: Object): void; 13 | 14 | interface Charsets { 15 | lookup(mime: string): string; 16 | } 17 | 18 | export var charsets: Charsets; 19 | export var default_type: string; 20 | } 21 | -------------------------------------------------------------------------------- /typings/serve-static/serve-static.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for serve-static 1.7.1 2 | // Project: https://github.com/expressjs/serve-static 3 | // Definitions by: Uros Smolnik 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | /* =================== USAGE =================== 7 | 8 | import * as serveStatic from "serve-static"; 9 | app.use(serveStatic("public/ftp", {"index": ["default.html", "default.htm"]})) 10 | 11 | =============================================== */ 12 | 13 | /// 14 | /// 15 | 16 | declare module "serve-static" { 17 | import * as express from "express"; 18 | 19 | /** 20 | * Create a new middleware function to serve files from within a given root directory. 21 | * The file to serve will be determined by combining req.url with the provided root directory. 22 | * When a file is not found, instead of sending a 404 response, this module will instead call next() to move on to the next middleware, allowing for stacking and fall-backs. 23 | */ 24 | function serveStatic(root: string, options?: { 25 | /** 26 | * Set how "dotfiles" are treated when encountered. A dotfile is a file or directory that begins with a dot ("."). 27 | * Note this check is done on the path itself without checking if the path actually exists on the disk. 28 | * If root is specified, only the dotfiles above the root are checked (i.e. the root itself can be within a dotfile when when set to "deny"). 29 | * The default value is 'ignore'. 30 | * 'allow' No special treatment for dotfiles 31 | * 'deny' Send a 403 for any request for a dotfile 32 | * 'ignore' Pretend like the dotfile does not exist and call next() 33 | */ 34 | dotfiles?: string; 35 | 36 | /** 37 | * Enable or disable etag generation, defaults to true. 38 | */ 39 | etag?: boolean; 40 | 41 | /** 42 | * Set file extension fallbacks. When set, if a file is not found, the given extensions will be added to the file name and search for. 43 | * The first that exists will be served. Example: ['html', 'htm']. 44 | * The default value is false. 45 | */ 46 | extensions?: string[]; 47 | 48 | /** 49 | * By default this module will send "index.html" files in response to a request on a directory. 50 | * To disable this set false or to supply a new index pass a string or an array in preferred order. 51 | */ 52 | index?: boolean|string|string[]; 53 | 54 | /** 55 | * Enable or disable Last-Modified header, defaults to true. Uses the file system's last modified value. 56 | */ 57 | lastModified?: boolean; 58 | 59 | /** 60 | * Provide a max-age in milliseconds for http caching, defaults to 0. This can also be a string accepted by the ms module. 61 | */ 62 | maxAge?: number|string; 63 | 64 | /** 65 | * Redirect to trailing "/" when the pathname is a dir. Defaults to true. 66 | */ 67 | redirect?: boolean; 68 | 69 | /** 70 | * Function to set custom headers on response. Alterations to the headers need to occur synchronously. 71 | * The function is called as fn(res, path, stat), where the arguments are: 72 | * res the response object 73 | * path the file path that is being sent 74 | * stat the stat object of the file that is being sent 75 | */ 76 | setHeaders?: (res: express.Response, path: string, stat: any) => any; 77 | }): express.Handler; 78 | 79 | import * as m from "mime"; 80 | 81 | module serveStatic { 82 | var mime: typeof m; 83 | } 84 | 85 | export = serveStatic; 86 | } 87 | --------------------------------------------------------------------------------