├── .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 | [](https://travis-ci.org/UnsignedInt8/LightSword)
4 | [](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 |
--------------------------------------------------------------------------------