Merge branch 'master' into feature-http2-transport

* master: (39 commits)
  docs: update
  package: bump to v3.3.1
  bin: should delete more items in simple mode
  docs: add examples
  transports: set handshake timeout of ws to 10s
  lib: regen
  config: fix new URL() default port issue
  lint: fix
  package: upgrade eslint to v5.2.0
  docs: add "Test Using curl"
  docs: update
  src: add client side https proxy support
  package: upgrade winston to v3.0.0
  build: keep comments in lib/*.js
  ci: change NEXT_VERSION to 3.3.1
  docs: update
  lib: regen
  package: bump to v3.3.0
  docs: update
  transports,test: add "wss" transport
  ...

# Conflicts:
#	src/core/config.js
This commit is contained in:
Micooz 2018-06-15 23:12:07 +08:00
commit 9c3cf648e8
102 changed files with 3693 additions and 3680 deletions

@ -5,7 +5,7 @@ node_js:
- "node" - "node"
before_deploy: before_deploy:
- export NEXT_VERSION=3.2.1 - export NEXT_VERSION=3.3.1
- export COMMIT_HASH=$(git log --format=%h -1) - export COMMIT_HASH=$(git log --format=%h -1)
- export DIST_PATH=build - export DIST_PATH=build
- export PUBLISH_REPO=blinksocks/blinksocks-nightly-releases - export PUBLISH_REPO=blinksocks/blinksocks-nightly-releases

@ -1,31 +1,138 @@
# Change Log # Change Log
## 3.3.1 (2018-06-15)
### :rocket: Features & Improvements
- package: upgrade winston to v3.0.0.
- proxies: add `https` support.
- transport: set handshake timeout of ws to 10s.
- docs: add `Test Using curl`.
- docs: add a batch of [examples](./docs/examples).
### :bug: Bug Fixes:
- config: fix default port issue.
### Migrating from 3.3.0 to 3.3.1
```
$ npm install -g blinksocks@3.3.1
```
## 3.3.0 (2018-06-12)
### :exclamation: Notable Changes
- **transports**: add `wss`([WebSocket/TLS](docs/config#blinksocks-over-websockettls)).
If you encounter the following warning, and the certificate is `self-signed`, please add `"tls_cert_self_signed": true` to client configuration and provide server certificate in `"tls_cert"` as well:
```
warn: [xxx:outbound] [x.x.x.x:xxxxx] self signed certificate
```
```diff
--- a/blinksocks.client.old.json
+++ b/blinksocks.client.new.json
{
"service": "socks5://127.0.0.1:1081",
"server": {
"service": "tls://localhost:1082",
"key": "AuM3R$]Pnj^Cqg^9",
"presets": [
@@ -18,11 +17,10 @@
"mux": false,
"mux_concurrency": 10,
"tls_cert": "cert.pem",
+ "tls_cert_self_signed": true
},
"dns": [],
"dns_expire": 3600,
"timeout": 300,
```
### :rocket: Features & Improvements
- **config**: add `tls_cert_self_signed` option.
### :bug: Bug Fixes:
- **transports/tls**: require `"tls_cert_self_signed": true` if use `self-singed` certificate.
### Migrating from 3.2.2 to 3.3.0
```
$ npm install -g blinksocks@3.3.0
```
## 3.2.2 (2018-06-10)
### :exclamation: Notable Changes
> For security reason, **executables** are no longer uploaded in the following releases, we only publish npm packages.
### :rocket: Features & Improvements
- **deploy**: add `install-run-debian.sh`.
- **transports**: add `pathname` support for websocket.
### :bug: Bug Fixes:
- **proxies**: fix a security bug of authorization bypass when use `socks` or `http` protocol.
### Migrating from 3.2.1 to 3.2.2
```
$ npm install -g blinksocks@3.2.2
```
## 3.2.1 (2018-05-12)
### :rocket: Features & Improvements
- **benchmark**: add test for chacha20-ietf.
- **hub**: set a timer to prune udp relays.
- **src**: move constants from `presets/actions.js` to `src/constants.js`.
- **src**: remove `preset.js`, implement `_write()` in `IPreset`.
- **test**: add udp test for `obfs-tls1.2-ticket`.
### :bug: Bug Fixes:
- **core**: add `speed-tester.js`, fix speed measurement.
### Migrating from 3.2.0 to 3.2.1
```
$ npm install -g blinksocks@3.2.1
```
## 3.2.0 (2018-04-28) ## 3.2.0 (2018-04-28)
> Node.js 10 is supported in this version! > Node.js 10 is supported in this version!
### :exclamation: Notable Changes ### :exclamation: Notable Changes
- src: support socks4(a), socks5, http `basic authorization`(username/password). - **src**: support socks4(a), socks5, http `basic authorization`(username/password).
- presets: add `IPresetAddressing::onInitTargetAddress()` and `IPresetAddressing::resolveTargetAddress()`. - **presets**: add `IPresetAddressing::onInitTargetAddress()` and `IPresetAddressing::resolveTargetAddress()`.
- presets: add `chacha20-ietf` method for `ss-stream-cipher`, but require Node.js 10.x. - **presets**: add `chacha20-ietf` method for `ss-stream-cipher`, but require Node.js 10.x.
- presets: deprecated `IPreset::onNotified()`. - **presets**: deprecated `IPreset::onNotified()`.
- presets: deprecated `auto-conf` preset. - **presets**: deprecated `auto-conf` preset.
### :rocket: Features & Improvements ### :rocket: Features & Improvements
- core: add `Pipe::initTargetAddress()`. - **core**: add `Pipe::initTargetAddress()`.
- utils: add `uint64ToBuffer()`, `incrementLE()` and `incrementBE()`. - **utils**: add `uint64ToBuffer()`, `incrementLE()` and `incrementBE()`.
- package: upgrade ws from v3.3.3 to v5.1.1. - **package**: upgrade ws from v3.3.3 to v5.1.1.
- package: use WHATWG URL API instead of legacy qs api. - **package**: use WHATWG URL API instead of legacy qs api.
- package: add package-lock.json. - **package**: add package-lock.json.
- package: remove unused dependencies. - **package**: remove unused dependencies.
- ci: add test on the latest stable Node.js. - **ci**: add test on the latest stable Node.js.
### :bug: Bug Fixes: ### :bug: Bug Fixes:
- hub: fix sequence between calling `relay::on()` and `relay.init()`. - **hub**: fix sequence between calling `relay::on()` and `relay.init()`.
- hub: patch `server.getConnections()` for ws. - **hub**: patch `server.getConnections()` for ws.
### Migrating from 3.1.1 to 3.2.0 ### Migrating from 3.1.1 to 3.2.0
@ -37,13 +144,13 @@ $ npm install -g blinksocks@3.2.0
### :rocket: Features & Improvements ### :rocket: Features & Improvements
- api: remove Hub::getPerformance(). - **api**: remove Hub::getPerformance().
- api: add Hub::getUploadSpeed(), Hub::getDownloadSpeed() and Hub::getConnStatuses(). - **api**: add Hub::getUploadSpeed(), Hub::getDownloadSpeed() and Hub::getConnStatuses().
- benchmark: enlarge wait time before kill iperf server. - **benchmark**: enlarge wait time before kill iperf server.
- package: upgrade winston logger to v3. - **package**: upgrade winston logger to v3.
- package: remove husky and precommit hook, it's annoying. - **package**: remove husky and precommit hook, it's annoying.
- test: add udp tests for multiplexing. - **test**: add udp tests for multiplexing.
- docs: add usage for systemd. - **docs**: add usage for systemd.
### :bug: Bug Fixes: ### :bug: Bug Fixes:
@ -61,18 +168,18 @@ $ npm install -g blinksocks@3.1.1
### :rocket: Features & Improvements ### :rocket: Features & Improvements
- lib: compiled to Node.js 8. - **lib**: compiled to Node.js 8.
- core: start udp server only when use socks protocol on client. - **core**: start udp server only when use socks protocol on client.
- core: add performance.js to collect upload/download speed. - **core**: add performance.js to collect upload/download speed.
- core: add Hub::getConnections(), Hub::getTotalRead(), Hub::getTotalWritten() and Hub::getPerformance(). - **core**: add Hub::getConnections(), Hub::getTotalRead(), Hub::getTotalWritten() and Hub::getPerformance().
- test: add tests for udp relay. - **test**: add tests for udp relay.
- test: add tests for multiplexing over ws and tls. - **test**: add tests for multiplexing over ws and tls.
### :bug: Bug Fixes: ### :bug: Bug Fixes:
- src: reduce error rate when enable multiplexing on server. - **src**: reduce error rate when enable multiplexing on server.
- core: include target address in tracker's log when enable multiplexing. - **core**: include target address in tracker's log when enable multiplexing.
- core: avoid putting duplicate target address in tracker's log. - **core**: avoid putting duplicate target address in tracker's log.
### Migrating from 3.0.0 to 3.1.0 ### Migrating from 3.0.0 to 3.1.0
@ -84,30 +191,30 @@ $ npm install -g blinksocks@3.1.0
### :boom: Breaking Changes: ### :boom: Breaking Changes:
- bin/init: remove "workers". - **bin/init**: remove "workers".
- bin/init: replace "servers: []" to "server: {}". - **bin/init**: replace "servers: []" to "server: {}".
- core: remove balancer. - **core**: remove balancer.
- presets: remove `stats` preset. - **presets**: remove `stats` preset.
- presets: remove `tracker` preset, re-implement in core. - **presets**: remove `tracker` preset, re-implement in core.
- presets: remove `access-control` preset, re-implement in core. - **presets**: remove `access-control` preset, re-implement in core.
### :rocket: Features & Improvements ### :rocket: Features & Improvements
- bin/init: add "acl" and "acl_conf" on server side. - **bin/init**: add "acl" and "acl_conf" on server side.
- src: refactor src/dns-cache.js and move it to utils/. - **src**: refactor src/dns-cache.js and move it to utils/.
- src: move config items from global to local. - **src**: move config items from global to local.
- core: expose Config::getLogFilePath(). - **core**: expose Config::getLogFilePath().
- benchmark: use json output of iperf. - **benchmark**: use json output of iperf.
- presets/mux: discretize cid and other improvements. - **presets/mux**: discretize cid and other improvements.
- ci: add scripts to do nightly release automatically. - **ci**: add scripts to do nightly release automatically.
- test: add e2e tests. - **test**: add e2e tests.
### :bug: Bug Fixes: ### :bug: Bug Fixes:
- src: fix "send() is not a function" when using mux though http proxy. - **src**: fix "send() is not a function" when using mux though http proxy.
- core: fix "cannot read property 'remoteInfo' of null" of mux-relay.js. - **core**: fix "cannot read property 'remoteInfo' of null" of mux-relay.js.
- core: fix sub connection id collision. - **core**: fix sub connection id collision.
- core: handle listen "error" event. - **core**: handle listen "error" event.
### Committers: 2 ### Committers: 2
@ -181,21 +288,21 @@ After v3, blinksocks no longer support multiple servers and cluster mode, so you
### :rocket: Features & Improvements ### :rocket: Features & Improvements
- core: add mux-relay. - **core**: add mux-relay.
- benchmark: archive reports of 2017. - **benchmark**: archive reports of 2017.
- package: upgrade pkg to v4.3.0. - **package**: upgrade pkg to v4.3.0.
- package: compile before running benchmark. - **package**: compile before running benchmark.
- presets: add mux preset. - **presets**: add mux preset.
- transports: refactor and optimize websocket transport. - **transports**: refactor and optimize websocket transport.
- transports: add this.ctx. - **transports**: add this.ctx.
- transports: add mux transport. - **transports**: add mux transport.
- utils: add a faster version of crypto.randomBytes(). - **utils**: add a faster version of crypto.randomBytes().
**
### :bug: Bug Fixes: ### :bug: Bug Fixes:
- proxies: fix crash when client reset the socks connection later. - **proxies**: fix crash when client reset the socks connection later.
- utils: fix getRandomInt(). - **utils**: fix getRandomInt().
- utils: remove generateMutexId(). - **utils**: remove generateMutexId().
### Upgrade from 2.8.5 to 2.9.0 ### Upgrade from 2.8.5 to 2.9.0

@ -20,7 +20,7 @@
* Cross-platform: running on Linux, Windows and macOS. * Cross-platform: running on Linux, Windows and macOS.
* Lightweight proxy interfaces: Socks5/Socks4/Socks4a and HTTP. * Lightweight proxy interfaces: Socks5/Socks4/Socks4a and HTTP.
* Multiple Transport Layers: TCP, UDP, [TLS] and [WebSocket]. * Multiple Transport Layers: TCP, UDP, [TLS], [WebSocket] and [WebSocket/TLS].
* TLS/TLS/WebSocket [multiplexing]. * TLS/TLS/WebSocket [multiplexing].
* Convenient protocol [customization]. * Convenient protocol [customization].
* Access Control List([ACL]) support. * Access Control List([ACL]) support.
@ -51,21 +51,10 @@ Please check out [blinksocks-nightly-releases](https://github.com/blinksocks/bli
## Run blinksocks ## Run blinksocks
**npm version(require Node.js, recommended)**
``` ```
$ blinksocks --help $ blinksocks --help
``` ```
**executable version(~~Node.js~~, not GUI)**
Tips: You can [download](https://github.com/blinksocks/blinksocks/releases) precompiled executables for different platforms and launch it directly without having Node.js installed.
```
$ ./blinksocks --help // Linux and macOS
$ blinksocks.exe --help // Windows
```
For configuring blinksocks, please refer to [Configuration](docs/config). For configuring blinksocks, please refer to [Configuration](docs/config).
## Documents ## Documents
@ -75,6 +64,7 @@ For configuring blinksocks, please refer to [Configuration](docs/config).
1. [Usage](docs/usage) 1. [Usage](docs/usage)
2. [Configuration](docs/config) 2. [Configuration](docs/config)
3. [Presets](docs/presets) 3. [Presets](docs/presets)
4. [Examples](docs/examples)
### For Developers ### For Developers
@ -91,11 +81,12 @@ See [contributors](https://github.com/blinksocks/blinksocks/graphs/contributors)
Apache License 2.0 Apache License 2.0
[TLS]: docs/config#blinksocks-over-tls
[WebSocket]: docs/config#blinksocks-over-websocket
[multiplexing]: docs/config#multiplexing
[customization]: docs/development/api [customization]: docs/development/api
[ACL]: docs/config#access-control-list [ACL]: docs/config#access-control-list
[shadowsocks]: docs/presets/RECOMMENDATIONS.md#work-with-shadowsocks [TLS]: docs/examples/tls
[shadowsocksR]: docs/presets/RECOMMENDATIONS.md#work-with-shadowsocksr [WebSocket]: docs/examples/websocket
[v2ray vmess]: docs/presets/RECOMMENDATIONS.md#work-with-v2ray-vmess [WebSocket/TLS]: docs/examples/websocket-tls
[multiplexing]: docs/examples/multiplexing
[shadowsocks]: docs/examples/shadowsocks
[shadowsocksR]: docs/examples/shadowsocksr
[v2ray vmess]: docs/examples/v2ray-vmess

@ -101,12 +101,8 @@ function main() {
} }
if (hasOption('--list-presets')) { if (hasOption('--list-presets')) {
const { presets, legacyPresets } = modules; const { builtInPresetMap } = modules;
console.log(chalk.bold.underline('[Built-In]')); console.log(Object.keys(builtInPresetMap).join(os.EOL));
console.log(presets.join(os.EOL));
console.log('');
console.log(chalk.bold.underline('[Deprecated]'));
console.log(legacyPresets ? legacyPresets.map((name) => `${chalk.gray(name)}`).join(os.EOL) : '-');
return; return;
} }

@ -38,24 +38,30 @@ module.exports = function init({ isMinimal, isOverwrite, isDryRun = false }) {
'presets': [ 'presets': [
{ 'name': 'ss-base' }, { 'name': 'ss-base' },
{ 'name': 'obfs-random-padding' }, { 'name': 'obfs-random-padding' },
{ 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
'tls_cert': 'cert.pem', 'tls_cert': 'cert.pem',
'tls_cert_self_signed': false,
'mux': false, 'mux': false,
'mux_concurrency': 10 'mux_concurrency': 10,
}, },
'https_key': 'https_key.pem',
'https_cert': 'https_cert.pem',
'dns': [], 'dns': [],
'dns_expire': 3600, 'dns_expire': 3600,
'timeout': timeout, 'timeout': timeout,
'log_path': 'bs-client.log', 'log_path': 'bs-client.log',
'log_level': 'info', 'log_level': 'info',
'log_max_days': 30 'log_max_days': 30,
}; };
if (isMinimal) { if (isMinimal) {
delete clientJson.server.tls_cert; delete clientJson.server.tls_cert;
delete clientJson.server.tls_cert_self_signed;
delete clientJson.server.mux; delete clientJson.server.mux;
delete clientJson.server.mux_concurrency; delete clientJson.server.mux_concurrency;
delete clientJson.https_key;
delete clientJson.https_cert;
delete clientJson.dns; delete clientJson.dns;
delete clientJson.dns_expire; delete clientJson.dns_expire;
delete clientJson.timeout; delete clientJson.timeout;
@ -70,7 +76,7 @@ module.exports = function init({ isMinimal, isOverwrite, isDryRun = false }) {
'presets': [ 'presets': [
{ 'name': 'ss-base' }, { 'name': 'ss-base' },
{ 'name': 'obfs-random-padding' }, { 'name': 'obfs-random-padding' },
{ 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
'tls_key': 'key.pem', 'tls_key': 'key.pem',
'tls_cert': 'cert.pem', 'tls_cert': 'cert.pem',
@ -83,7 +89,7 @@ module.exports = function init({ isMinimal, isOverwrite, isDryRun = false }) {
'redirect': '', 'redirect': '',
'log_path': 'bs-server.log', 'log_path': 'bs-server.log',
'log_level': 'info', 'log_level': 'info',
'log_max_days': 30 'log_max_days': 30,
}; };
if (isMinimal) { if (isMinimal) {

25
deploy/install-run-debian.sh Executable file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs
npm install --global pm2 blinksocks
echo "$ node --version"
node --version
echo "$ npm --version"
npm --version
echo "$ blinksocks --version"
blinksocks --version
echo "$ blinksocks init"
blinksocks init
echo "$ pm2 start blinksocks -- --config blinksocks.server.json"
pm2 start blinksocks -- --config blinksocks.server.json
echo "$ cat blinksocks.server.json"
cat blinksocks.server.json
echo ""

@ -5,6 +5,7 @@
1. [Usage](usage) 1. [Usage](usage)
2. [Configuration](config) 2. [Configuration](config)
3. [Presets](presets) 3. [Presets](presets)
4. [Examples](examples)
## For Developers ## For Developers

@ -31,9 +31,12 @@ $ blinksocks init
} }
], ],
"tls_cert": "cert.pem", "tls_cert": "cert.pem",
"tls_cert_self_signed": false,
"mux": false, "mux": false,
"mux_concurrency": 10 "mux_concurrency": 10
}, },
"https_key": "https_key.pem",
"https_cert": "https_cert.pem",
"dns": [], "dns": [],
"dns_expire": 3600, "dns_expire": 3600,
"timeout": 221, "timeout": 221,
@ -76,28 +79,31 @@ $ blinksocks init
} }
``` ```
| KEY | DESCRIPTION | DEFAULT | REMARKS | | KEY | DESCRIPTION | DEFAULT | REMARKS |
| :---------------- | :----------------------------------------------------------- | :-------------- | :---------------------------------------------------------- | | :------------------- | :----------------------------------------------------------- | :-------------- | :---------------------------------------------------------- |
| service | local service address | - | a [WHATWG URL] e.g, "socks://127.0.0.1:1080" | | service | local service address | - | [WHATWG URL] e.g, "socks://127.0.0.1:1080" |
| server | remote server config | - | **CLIENT ONLY** | | server | remote server config | - | **CLIENT ONLY** |
| server.service | remote service address | - | `<protocol>://<host>:<port>` | | server.service | remote service address | - | [WHATWG URL] e.g, "tls://example.com:443" |
| server.key | remote server master key | - | - | | server.key | remote server master key | - | - |
| presets | an ordered list of presets to build a protocol stack | - | see [presets] | | presets | an ordered list of presets to build a protocol stack | - | see [presets] |
| presets[i].name | preset name | - | - | | presets[i].name | preset name | - | - |
| presets[i].params | preset params | - | - | | presets[i].params | preset params | - | - |
| tls_key | private key for TLS | - | required on server if `<protocol>` is "tls" | | tls_key | private key path for TLS | - | required on server if `<protocol>` is "tls" |
| tls_cert | certificate for TLS | - | required on both client and server if `<protocol>` is "tls" | | tls_cert | certificate path for TLS | - | required on both client and server if `<protocol>` is "tls" |
| acl | enable access control list or not | false | **SERVER ONLY** | | tls_cert_self_signed | whether "tls_cert" is `self-signed` or not | false | **CLIENT ONLY** |
| acl_conf | access control list configuration file | - | **SERVER ONLY**, see below | | https_key | private key path for HTTPS | - | **CLIENT ONLY** |
| timeout | timeout for each connection | 600 | in seconds | | https_cert | certificate path for HTTPS | - | **CLIENT ONLY** |
| mux | enable multiplexing or not | false | - | | acl | enable access control list or not | false | **SERVER ONLY** |
| mux_concurrency | the max mux connection established between client and server | 10 | **CLIENT ONLY** | | acl_conf | access control list configuration file | - | **SERVER ONLY**, see below |
| redirect | target address to redirect when preset fail to process | "" | **SERVER ONLY** `<host>:<port>` | | timeout | timeout for each connection | 600 | in seconds |
| dns | a list of DNS server IPs | [] | - | | mux | enable multiplexing or not | false | - |
| dns_expire | in-memory DNS cache expiration time | 3600 | in seconds | | mux_concurrency | the max mux connection established between client and server | 10 | **CLIENT ONLY** |
| log_path | log file path | "bs-[type].log" | a relative/absolute directory or a file to put logs in | | redirect | target address to redirect when preset fail to process | "" | **SERVER ONLY** `<host>:<port>` |
| log_level | log level | "info" | ['error', 'warn', 'info', 'verbose', 'debug', 'silly'] | | dns | a list of DNS server IPs | [] | - |
| log_max_days | the max of days a log file will be saved | 30 | remove this option if you want to keep all log files | | dns_expire | in-memory DNS cache expiration time | 3600 | in seconds |
| log_path | log file path | "bs-[type].log" | a relative/absolute file path to put logs in |
| log_level | log level | "info" | ['error', 'warn', 'info', 'verbose', 'debug', 'silly'] |
| log_max_days | the max of days a log file will be saved | 30 | remove this option if you want to keep all log files |
### Service ### Service
@ -105,12 +111,12 @@ $ blinksocks init
The `<protocol>` should be: The `<protocol>` should be:
* On client side: `tcp`, `socks`/`socks5`/`socks4`/`socks4a` or `http`/`https`. * On client side: `tcp`, `socks`/`socks5`/`socks4`/`socks4a`, `http` or `https`.
* On server side: `tcp`, `tls` or `ws`. * On server side: `tcp`, `tls`, `ws` or `wss`.
#### Service Authentication #### Service Authentication
* Create a **http** service with [Basic Authentication](https://www.iana.org/go/rfc7617). * Create a **http/https** service with [Basic Authentication](https://www.iana.org/go/rfc7617).
``` ```
// blinksocks.client.json // blinksocks.client.json
@ -177,77 +183,6 @@ In this case, it uses [iperf](https://en.wikipedia.org/wiki/Iperf) to test netwo
For more information about presets, please check out [presets]. For more information about presets, please check out [presets].
### blinksocks over TLS
By default, blinksocks use "tcp" as transport, but you can take advantage of TLS technology to protect your data well.
To enable blinksocks over TLS, you should:
1. Generate `key.pem` and `cert.pem` on server
```
// self-signed
$ openssl req -x509 -newkey rsa:4096 -nodes -sha256 -subj '/CN=localhost' \
-keyout key.pem -out cert.pem
```
> NOTE: Remember the **Common Name(CN)** you typed in the command line.
2. Server config
Change `tcp://` to `tls://`, then provide `tls_key` and `tls_cert`:
```
{
"service": "tls://<host>:<port>",
"tls_key": "key.pem",
"tls_cert": "cert.pem",
...
}
```
3. Client config
Change server's `tcp://` to `tls://`, then provide `tls_cert`:
```
{
...
"server": {
"service": "tls://<Common Name>:<port>", // take care of <Common Name>
"tls_cert": "cert.pem",
...
},
...
}
```
### blinksocks over WebSocket
Like blinksocks over TLS, it's much easier to setup a websocket tunnel:
1. Server config
```
{
"service": "ws://<host>:<port>",
...
}
```
2. Client config
```
{
...
"server": {
"service": "ws://<host>:<port>",
...
},
...
}
```
### Access Control List ### Access Control List
You can enable ACL on **server** by setting **acl: true** and provide a acl configuration file in **acl_conf**: You can enable ACL on **server** by setting **acl: true** and provide a acl configuration file in **acl_conf**:
@ -281,36 +216,6 @@ Rules in **acl.txt** has a priority from lower to higher.
> NOTE: acl requires a restart each time you updated **acl_conf**. > NOTE: acl requires a restart each time you updated **acl_conf**.
### Multiplexing
Since blinksocks v2.9.0, blinksocks supports TCP/TLS/WS multiplexing.
You can enable this feature easily by setting `mux: true` on both client and server, and set `mux_concurrency: <number>` on client.
1. Server config
```
{
"mux": true,
...
}
```
2. Client config
```
{
...
"server": {
...
"mux": true,
"mux_concurrency": 10
...
},
...
}
```
### Log Path ### Log Path
Specify a relative or absolute path to store log file, if no `log_path` provided, log file named `bs-[type].log` will be stored in the working directory. Specify a relative or absolute path to store log file, if no `log_path` provided, log file named `bs-[type].log` will be stored in the working directory.
@ -352,4 +257,4 @@ apps <--SOCKS5--> [blinksocks client] <--UDP--> [blinksocks server] <--UDP--> de
[WHATWG URL]: https://nodejs.org/dist/latest/docs/api/url.html#url_url_strings_and_url_objects [WHATWG URL]: https://nodejs.org/dist/latest/docs/api/url.html#url_url_strings_and_url_objects
[presets]: ../presets [presets]: ../presets
[winston]: https://github.com/winstonjs/winston [winston]: https://github.com/winstonjs/winston
[UDP ASSOCIATE]: https://tools.ietf.org/html/rfc1928#section-4 [UDP ASSOCIATE]: https://tools.ietf.org/html/rfc1928#section-4

@ -65,13 +65,3 @@ After compile, we can change version in `package.json` then publish a package to
``` ```
$ npm publish $ npm publish
``` ```
## Package
For users don't have Node.js installed, we use [zeit/pkg](https://github.com/zeit/pkg) to prepare compiled executables:
```
$ npm run pkg
```
This will generate compressed executables for different platforms named `blinksocks-{platform}-${arch}-${version}.gz`. And can be distribute to target platform at once.

3
docs/examples/README.md Normal file

@ -0,0 +1,3 @@
# Examples
Here I post some particle configuration of blinksocks, please enter each directory for more details.

@ -0,0 +1,31 @@
# Multiplexing
**Minimal Version Required: v2.9.x**
blinksocks supports TCP/TLS/WS multiplexing.
You can enable this feature easily by setting `"mux": true` on both client and server, and set `"mux_concurrency": <number>` on client.
1. Client config
```
{
...
"server": {
...
"mux": true,
"mux_concurrency": 10
...
},
...
}
```
2. Server config
```
{
"mux": true,
...
}
```

@ -0,0 +1,23 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "tcp://example.com:17720",
"key": "#{N-^!6{SExKTQ|b",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
],
"mux": true,
"mux_concurrency": 10
}
}

@ -0,0 +1,19 @@
{
"service": "tcp://0.0.0.0:17720",
"key": "#{N-^!6{SExKTQ|b",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
],
"mux": true
}

@ -0,0 +1,23 @@
# obfs-random-padding
`obfs-random-padding` provides ability to prevent traffic analysis(based on sequence of round trip packet length between client and server):
## To prevent traffic analysis
```
"presets": [
{"name": "ss-base"},
{"name": "obfs-random-padding"},
{"name": "ss-stream-cipher","params": {"method": "aes-128-ctr"}}
]
```
## To prevent traffic analysis and ensure integrity as well
```
"presets": [
{"name": "ss-base"},
{"name": "obfs-random-padding"},
{"name": "ss-aead-cipher","params": {"method": "aes-128-gcm"}}
]
```

@ -0,0 +1,21 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "tcp://example.com:50102",
"key": "&8[j;(>fjLGm,db]",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
]
}
}

@ -0,0 +1,18 @@
{
"service": "tcp://0.0.0.0:50102",
"key": "&8[j;(>fjLGm,db]",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
]
}

@ -0,0 +1,7 @@
# obfs-tls-session-ticket
**Minimal Version Required: v2.x**
You can append a **http** or **tls** obfuscator to preset list to avoid bad [QoS], **obfs-tls1.2-ticket** is recommended.
[QoS]: https://en.wikipedia.org/wiki/Quality_of_service

@ -0,0 +1,26 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "tcp://example.com:14938",
"key": "had2a8#(kVKA2&gx",
"presets": [
{
"name": "ss-base"
},
{
"name": "ss-aead-cipher",
"params": {
"method": "aes-256-gcm"
}
},
{
"name": "obfs-tls1.2-ticket",
"params": {
"sni": [
"example.com"
]
}
}
]
}
}

@ -0,0 +1,23 @@
{
"service": "tcp://0.0.0.0:14938",
"key": "had2a8#(kVKA2&gx",
"presets": [
{
"name": "ss-base"
},
{
"name": "ss-aead-cipher",
"params": {
"method": "aes-256-gcm"
}
},
{
"name": "obfs-tls1.2-ticket",
"params": {
"sni": [
"example.com"
]
}
}
]
}

@ -0,0 +1,23 @@
# shadowsocks
**Minimal Version Required: v1.x**
To work with **shadowsocks**, you can just add two presets:
**AEAD Ciphers(Newer Versions), Recommend**
```
"presets": [
{"name": "ss-base"},
{"name": "ss-aead-cipher", "params": {"method": "aes-256-gcm"}}
]
```
**Steam Ciphers(Older Versions)**
```
"presets": [
{"name": "ss-base"},
{"name": "ss-stream-cipher", "params": {"method": "aes-256-cfb"}}
]
```

@ -0,0 +1,18 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "tcp://example.com:49849",
"key": "XyDw&JndtwhV?m<6",
"presets": [
{
"name": "ss-base"
},
{
"name": "ss-aead-cipher",
"params": {
"method": "aes-256-gcm"
}
}
]
}
}

@ -0,0 +1,15 @@
{
"service": "tcp://0.0.0.0:49849",
"key": "XyDw&JndtwhV?m<6",
"presets": [
{
"name": "ss-base"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-256-gcm"
}
}
]
}

@ -0,0 +1,42 @@
# shadowsocksr
**Minimal Version Required: v2.x**
> NOTE: To work with shadowsocksR, you must add both "ss-base" and "ss-stream-cipher".
<details>
<summary>Notice in shadowsocksR config</summary>
```
{
...
"method": "aes-128-ctr",
"protocol": "auth_aes128_md5",
"protocol_param": "", // protocol_param must be empty
"obfs": "plain", // obfs must be "plain"
"obfs_param": "",
...
}
```
</details>
**auth_aes128_md5 / auth_aes128_sha1**
```
"presets": [
{"name": "ss-base"},
{"name": "ssr-auth-aes128-md5"},
{"name": "ss-stream-cipher", "params": {"method": "aes-256-ctr"}}
]
```
**auth_chain_a / auth_chain_b**
```
"presets": [
{"name": "ss-base"},
{"name": "ssr-auth-chain-a"},
{"name": "ss-stream-cipher", "params": {"method": "none"}}
]
```

@ -0,0 +1,21 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "tcp://example.com:24045",
"key": "?4H}vrE_[WQj9[>F",
"presets": [
{
"name": "ss-base"
},
{
"name": "ssr-auth-chain-a"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "none"
}
}
]
}
}

@ -0,0 +1,18 @@
{
"service": "tcp://0.0.0.0:24045",
"key": "?4H}vrE_[WQj9[>F",
"presets": [
{
"name": "ss-base"
},
{
"name": "ssr-auth-chain-a"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "none"
}
}
]
}

@ -0,0 +1,40 @@
# tls
**Minimal Version Required: v2.x**
blinksocks can transfer data using `tls`:
```
+-------------+ +-------------+ +------------+
| | tls://site.com/path | | tcp:// | |
| bs-client <-----------------------> bs-server <-----------> Target |
| | | | | |
+-------------+ +-------------+ +------------+
```
When use `tls://` as transport, make sure both `tls_cert` and `tls_key` is provided to `bs-server`.
> If your are using self-signed certificate on server, please also provide the same `tls_cert` on client and also set `"tls_cert_self_signed": true`.
Make sure you provide **Common Name** of certificate NOT IP in client config:
```
{
...
"server": {
"service": "tls://<Common Name>:<port>",
"tls_cert": "cert.pem",
"tls_cert_self_signed": true
...
},
...
}
```
## Generate key.pem and cert.pem
```
// self-signed certificate
$ openssl req -x509 -newkey rsa:4096 -nodes -sha256 -subj '/CN=example.com' \
-keyout key.pem -out cert.pem
```

@ -0,0 +1,17 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "tls://example.com:11902",
"key": "NdFCdXFK/TTP2GdU",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
}
],
"tls_cert": "cert.pem",
"tls_cert_self_signed": true
}
}

@ -0,0 +1,14 @@
{
"service": "tls://0.0.0.0:11902",
"key": "NdFCdXFK/TTP2GdU",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
}
],
"tls_key": "key.pem",
"tls_cert": "cert.pem"
}

@ -0,0 +1,67 @@
# v2ray-vmess
**Minimal Version Required: v2.x**
> NOTE: To work with v2ray vmess, you should only provide "v2ray-vmess" in preset list.
<details>
<summary>v2ray client</summary>
```
"outbound": {
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "127.0.0.1",
"port": 10086,
"users": [
{
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"security": "aes-128-gcm",
"alterId": 0 // [must be the default value: 0]
}
]
}
]
},
"mux": {
"enabled": false // [must be false]
}
},
```
</details>
<details>
<summary>v2ray server</summary>
```
"inbound": {
"port": 10086,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"level": 1,
"alterId": 0 // [must be the default value: 0]
}
]
}
},
```
</details>
```
"presets": [
{
"name": "v2ray-vmess",
"params": {
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"security": "aes-128-gcm"
}
}
]
```

@ -0,0 +1,16 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "tcp://example.com:65282",
"key": "z{]5AWaxEFCMTKA,",
"presets": [
{
"name": "v2ray-vmess",
"params": {
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"security": "aes-128-gcm"
}
}
]
}
}

@ -0,0 +1,13 @@
{
"service": "tcp://0.0.0.0:65282",
"key": "z{]5AWaxEFCMTKA,",
"presets": [
{
"name": "v2ray-vmess",
"params": {
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"security": "aes-128-gcm"
}
}
]
}

@ -0,0 +1,9 @@
example.com {
proxy /<your-custom-path> 127.0.0.1:59463 {
websocket
header_upstream Host {host}
header_upstream X-Real-IP {remote}
header_upstream X-Forwarded-For {remote}
header_upstream X-Forwarded-Proto {scheme}
}
}

@ -0,0 +1,20 @@
# websocket-caddy-tls
**Minimal Version Required: v3.3.1**
blinksocks can transfer data through [caddy] proxy server using [WebSocket/TLS]:
```
+--------------------------------------------------+
| Caddy Server |
+-------------+ | +-----------+ | +------------+
| | wss://site.com/path | :433 ws://127.0.0.1:1234 | | | tcp:// | |
| bs-client <-----------------------> proxy /path +--------------------> bs-server <-------------> Target |
| | (encrypted) | (encrypted) | | | (raw) | |
+-------------+ | +-----------+ | +------------+
| |
+--------------------------------------------------+
```
[caddy]: https://caddyserver.com
[WebSocket/TLS]: ../websocket-tls

@ -0,0 +1,15 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "wss://example.com:443/your-custom-path",
"key": "8;:2%]zTbPc[2g-%",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
}
]
}
}

@ -0,0 +1,12 @@
{
"service": "ws://0.0.0.0:59463/your-custom-path",
"key": "8;:2%]zTbPc[2g-%",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
}
]
}

@ -0,0 +1,20 @@
# websocket-tls
**Minimal Version Required: v3.3.1**
blinksocks can transfer data using [WebSocket/TLS]:
```
+-------------+ +-------------+ +------------+
| | wss://site.com/path | | tcp:// | |
| bs-client <-----------------------> bs-server <-----------> Target |
| | (encrypted) | | (raw) | |
+-------------+ +-------------+ +------------+
```
When use `wss://` as transport, make sure both `tls_cert` and `tls_key` is provided to `bs-server`.
> If your are using self-signed certificate on server, please also provide the same `tls_cert` on client and set `"tls_cert_self_signed": true`.
[WebSocket/TLS]: ../websocket-tls

@ -0,0 +1,17 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "wss://example.com:48079",
"key": "98{U64+Z4bX#d,Ra",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
}
],
"tls_cert": "cert.pem",
"tls_cert_self_signed": true
}
}

@ -0,0 +1,14 @@
{
"service": "wss://0.0.0.0:48079",
"key": "98{U64+Z4bX#d,Ra",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
}
],
"tls_key": "key.pem",
"tls_cert": "cert.pem"
}

@ -0,0 +1,13 @@
# websocket
**Minimal Version Required: v2.6.2**
blinksocks can transfer data using `websocket`:
```
+-------------+ +-------------+ +------------+
| | ws://site.com/path | | tcp:// | |
| bs-client <----------------------> bs-server <-----------> Target |
| | | | | |
+-------------+ +-------------+ +------------+
```

@ -0,0 +1,21 @@
{
"service": "socks5://127.0.0.1:1080",
"server": {
"service": "ws://127.0.0.1:6336",
"key": "?B4-y[tsFQCV/zK%",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
]
}
}

@ -0,0 +1,18 @@
{
"service": "ws://0.0.0.0:6336",
"key": "?B4-y[tsFQCV/zK%",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
]
}

@ -326,7 +326,7 @@ all features from **ss-aead-cipher** and prevent server from being detected by p
## Have trouble in choosing presets? ## Have trouble in choosing presets?
Here is a [list](./RECOMMENDATIONS.md) of recommended conbinations. Here is a [list](../examples) of recommended conbinations.
[base-auth]: ../../src/presets/base-auth.js [base-auth]: ../../src/presets/base-auth.js
[ss-base]: ../../src/presets/ss-base.js [ss-base]: ../../src/presets/ss-base.js

@ -1,167 +0,0 @@
# Recommended Combinations
## Work with shadowsocks
To work with **shadowsocks**, please choose one of the following configurations:
**Steam Ciphers(Older Versions)**
```
"presets": [
{"name": "ss-base"},
{"name": "ss-stream-cipher", "params": {"method": "aes-256-cfb"}}
]
```
**AEAD Ciphers(Newer Versions)**
```
"presets": [
{"name": "ss-base"},
{"name": "ss-aead-cipher", "params": {"method": "aes-256-gcm"}}
]
```
## Work with shadowsocksR
> NOTE: To work with shadowsocksR, you must add both "ss-base" and "ss-stream-cipher".
<details>
<summary>Notice in shadowsocksR config</summary>
```
{
...
"method": "aes-128-ctr",
"protocol": "auth_aes128_md5",
"protocol_param": "", // protocol_param must be empty
"obfs": "plain", // obfs must be "plain"
"obfs_param": "",
...
}
```
</details>
**auth_aes128_md5 / auth_aes128_sha1**
```
"presets": [
{"name": "ss-base"},
{"name": "ssr-auth-aes128-md5"},
{"name": "ss-stream-cipher", "params": {"method": "aes-256-ctr"}}
]
```
**auth_chain_a / auth_chain_b**
```
"presets": [
{"name": "ss-base"},
{"name": "ssr-auth-chain-a"},
{"name": "ss-stream-cipher", "params": {"method": "none"}}
]
```
## Work with v2ray vmess
> Notice in v2ray configs:
<details>
<summary>v2ray client</summary>
```
"outbound": {
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "127.0.0.1",
"port": 10086,
"users": [
{
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"security": "aes-128-gcm",
"alterId": 0 // [must be the default value: 0]
}
]
}
]
},
"mux": {
"enabled": false // [must be false]
}
},
```
</details>
<details>
<summary>v2ray server</summary>
```
"inbound": {
"port": 10086,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"level": 1,
"alterId": 0 // [must be the default value: 0]
}
]
}
},
```
</details>
```
"presets": [
{
"name": "v2ray-vmess",
"params": {
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"security": "aes-128-gcm"
}
}
]
```
## Avoid Bad QoS
You can use **http** or **tls** obfuscator to avoid bad [QoS], **tls** is recommended.
```
"presets": [
{"name": "ss-base"},
{"name": "ss-aead-cipher", "params": {"method": "aes-256-gcm"}},
{"name": "obfs-tls1.2-ticket", "params": {"sni": ["example.com"]}}
]
```
## To prevent traffic analysis
```
"presets": [
{"name": "ss-base"},
{"name": "obfs-random-padding"},
{"name": "ss-stream-cipher","params": {"method": "aes-128-ctr"}}
]
```
## To prevent traffic analysis and ensure integrity as well
```
"presets": [
{"name": "ss-base"},
{"name": "obfs-random-padding"},
{"name": "ss-aead-cipher","params": {"method": "aes-128-gcm"}}
]
```
> You can also check out [benchmark] to choose a combination you prefer.
[QoS]: https://en.wikipedia.org/wiki/Quality_of_service
[benchmark]: ../benchmark/README.md

@ -52,6 +52,23 @@ After init, you should edit `blinksocks.client.json` to tell blinksocks client w
You can also check out [Configuration](../config) for explanation of every option. You can also check out [Configuration](../config) for explanation of every option.
## Test Using curl
[curl](https://curl.haxx.se/) is an useful tool which can send requests via a variety of proxy protocols:
```
# SOCKS
$ curl -Lx socks5://localhost:1080 www.google.com
$ curl -Lx socks4://localhost:1080 www.google.com
$ curl -Lx socks4a://localhost:1080 www.google.com
# HTTP
$ curl -Lx http://localhost:1080 www.google.com
# HTTPS
$ curl --proxy-insecure -Lx https://localhost:1080 www.google.com
```
## Run in production ## Run in production
### Using pm2 ### Using pm2
@ -117,25 +134,6 @@ WantedBy=multi-user.target
# journalctl -u blinksocks.service # journalctl -u blinksocks.service
``` ```
### Using executables
```
// download archive from releases page
$ wget https://github.com/blinksocks/blinksocks/releases/download/v2.5.3/blinksocks-linux-x64-v2.5.3.gz
// you'd better check sha256sum listed in sha256sum.txt
$ wget https://github.com/blinksocks/blinksocks/releases/download/v2.5.3/sha256sum.txt
// decompress
$ gunzip blinksocks-linux-x64-v2.5.3.gz
// grant executable permission
$ chmod +x blinksocks-linux-x64-v2.5.3
// run directly
$ ./blinksocks-linux-x64-v2.5.3 --help
```
## Work with browsers ## Work with browsers
Most of the time, you are surfing the Internet via web browsers such as Firefox or Google Chrome. Most of the time, you are surfing the Internet via web browsers such as Firefox or Google Chrome.

@ -3,7 +3,7 @@
Object.defineProperty(exports, "__esModule", { Object.defineProperty(exports, "__esModule", {
value: true value: true
}); });
exports.MUX_CLOSE_CONN = exports.MUX_DATA_FRAME = exports.MUX_NEW_CONN = exports.CONNECTED_TO_REMOTE = exports.CONNECT_TO_REMOTE = exports.PRESET_FAILED = exports.MAX_BUFFERED_SIZE = exports.PIPE_DECODE = exports.PIPE_ENCODE = exports.APP_ID = undefined; exports.PROTOCOL_DEFAULT_PORTS = exports.MUX_CLOSE_CONN = exports.MUX_DATA_FRAME = exports.MUX_NEW_CONN = exports.CONNECTED_TO_REMOTE = exports.CONNECT_TO_REMOTE = exports.PRESET_FAILED = exports.MAX_BUFFERED_SIZE = exports.PIPE_DECODE = exports.PIPE_ENCODE = exports.APP_ID = undefined;
var _crypto = require('./utils/crypto'); var _crypto = require('./utils/crypto');
@ -15,4 +15,13 @@ const CONNECT_TO_REMOTE = exports.CONNECT_TO_REMOTE = 'CONNECT_TO_REMOTE';
const CONNECTED_TO_REMOTE = exports.CONNECTED_TO_REMOTE = 'CONNECTED_TO_REMOTE'; const CONNECTED_TO_REMOTE = exports.CONNECTED_TO_REMOTE = 'CONNECTED_TO_REMOTE';
const MUX_NEW_CONN = exports.MUX_NEW_CONN = 'MUX_NEW_CONN'; const MUX_NEW_CONN = exports.MUX_NEW_CONN = 'MUX_NEW_CONN';
const MUX_DATA_FRAME = exports.MUX_DATA_FRAME = 'MUX_DATA_FRAME'; const MUX_DATA_FRAME = exports.MUX_DATA_FRAME = 'MUX_DATA_FRAME';
const MUX_CLOSE_CONN = exports.MUX_CLOSE_CONN = 'MUX_CLOSE_CONN'; const MUX_CLOSE_CONN = exports.MUX_CLOSE_CONN = 'MUX_CLOSE_CONN';
const PROTOCOL_DEFAULT_PORTS = exports.PROTOCOL_DEFAULT_PORTS = {
'ftp:': 21,
'gopher:': 70,
'http:': 80,
'https:': 443,
'ws:': 80,
'wss:': 443
};

@ -41,6 +41,8 @@ var _lodash2 = _interopRequireDefault(_lodash);
var _acl = require('./acl'); var _acl = require('./acl');
var _constants = require('../constants');
var _presets = require('../presets'); var _presets = require('../presets');
var _defs = require('../presets/defs'); var _defs = require('../presets/defs');
@ -62,17 +64,22 @@ class Config {
this.local_search_params = null; this.local_search_params = null;
this.local_host = null; this.local_host = null;
this.local_port = null; this.local_port = null;
this.local_pathname = null;
this.server = null; this.server = null;
this.is_client = null; this.is_client = null;
this.is_server = null; this.is_server = null;
this.https_key = null;
this.https_cert = null;
this.timeout = null; this.timeout = null;
this.redirect = null; this.redirect = null;
this.dns_expire = null; this.dns_expire = null;
this.dns = null; this.dns = null;
this.transport = null; this.server_protocol = null;
this.server_host = null; this.server_host = null;
this.server_port = null; this.server_port = null;
this.server_pathname = null;
this.tls_cert = null; this.tls_cert = null;
this.tls_cert_self_signed = false;
this.tls_key = null; this.tls_key = null;
this.key = null; this.key = null;
this.acl = false; this.acl = false;
@ -81,20 +88,21 @@ class Config {
this.acl_tries = {}; this.acl_tries = {};
this.presets = null; this.presets = null;
this.udp_presets = null; this.udp_presets = null;
this.mux = null; this.mux = false;
this.mux_concurrency = null; this.mux_concurrency = null;
this.log_path = null; this.log_path = null;
this.log_level = null; this.log_level = null;
this.log_max_days = null; this.log_max_days = null;
this.stores = []; this.stores = [];
const { protocol, hostname, port, searchParams, username, password } = new _url.URL(json.service); const { protocol, hostname, port, pathname, searchParams, username, password } = new _url.URL(json.service);
this.local_protocol = protocol.slice(0, -1); this.local_protocol = protocol.slice(0, -1);
this.local_username = username; this.local_username = username;
this.local_password = password; this.local_password = password;
this.local_search_params = searchParams; this.local_search_params = searchParams;
this.local_host = hostname; this.local_host = hostname;
this.local_port = +port; this.local_port = +port || +_constants.PROTOCOL_DEFAULT_PORTS[protocol];
this.local_pathname = pathname;
let server; let server;
@ -121,6 +129,13 @@ class Config {
this._initServer(server); this._initServer(server);
} }
if (this.is_client && this.local_protocol === 'https') {
_utils.logger.info(`[config] loading ${json.https_cert}`);
this.https_cert = loadFileSync(json.https_cert);
_utils.logger.info(`[config] loading ${json.https_key}`);
this.https_key = loadFileSync(json.https_key);
}
this.timeout = json.timeout !== undefined ? json.timeout * 1e3 : 600 * 1e3; this.timeout = json.timeout !== undefined ? json.timeout * 1e3 : 600 * 1e3;
this.dns_expire = json.dns_expire !== undefined ? json.dns_expire * 1e3 : _utils.DNS_DEFAULT_EXPIRE; this.dns_expire = json.dns_expire !== undefined ? json.dns_expire * 1e3 : _utils.DNS_DEFAULT_EXPIRE;
@ -133,14 +148,20 @@ class Config {
} }
_initServer(server) { _initServer(server) {
const { protocol, hostname, port } = new _url.URL(server.service); const { protocol, hostname, port, pathname } = new _url.URL(server.service);
this.transport = protocol.slice(0, -1); this.server_protocol = protocol.slice(0, -1);
this.server_host = hostname; this.server_host = hostname;
this.server_port = +port; this.server_port = +port || +_constants.PROTOCOL_DEFAULT_PORTS[protocol];
this.server_pathname = pathname;
if (this.transport === 'tls') { if (this.server_protocol === 'tls' || this.server_protocol === 'wss') {
_utils.logger.info(`[config] loading ${server.tls_cert}`); if (this.is_client) {
this.tls_cert = loadFileSync(server.tls_cert); this.tls_cert_self_signed = !!server.tls_cert_self_signed;
}
if (this.tls_cert_self_signed || this.is_server) {
_utils.logger.info(`[config] loading ${server.tls_cert}`);
this.tls_cert = loadFileSync(server.tls_cert);
}
if (this.is_server) { if (this.is_server) {
_utils.logger.info(`[config] loading ${server.tls_key}`); _utils.logger.info(`[config] loading ${server.tls_key}`);
this.tls_key = loadFileSync(server.tls_key); this.tls_key = loadFileSync(server.tls_key);
@ -164,7 +185,7 @@ class Config {
this.redirect = server.redirect; this.redirect = server.redirect;
} }
this.mux = !!server.mux; this.mux = server.mux === true;
if (this.is_client) { if (this.is_client) {
this.mux_concurrency = server.mux_concurrency || 10; this.mux_concurrency = server.mux_concurrency || 10;
} }
@ -226,15 +247,15 @@ class Config {
throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"'); throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"');
} }
const { protocol: _protocol, hostname, port, searchParams } = new _url.URL(json.service); const { protocol, hostname, port: _port, searchParams } = new _url.URL(json.service);
if (typeof _protocol !== 'string') { if (typeof protocol !== 'string') {
throw Error('service.protocol is invalid'); throw Error('service.protocol is invalid');
} }
const protocol = _protocol.slice(0, -1); const proto = protocol.slice(0, -1);
const available_client_protocols = ['tcp', 'http', 'https', 'socks', 'socks5', 'socks4', 'socks4a']; const available_client_protocols = ['tcp', 'http', 'https', 'socks', 'socks5', 'socks4', 'socks4a'];
if (!available_client_protocols.includes(protocol)) { if (!available_client_protocols.includes(proto)) {
throw Error(`service.protocol must be: ${available_client_protocols.join(', ')}`); throw Error(`service.protocol must be: ${available_client_protocols.join(', ')}`);
} }
@ -242,11 +263,12 @@ class Config {
throw Error('service.host is invalid'); throw Error('service.host is invalid');
} }
const port = _port || _constants.PROTOCOL_DEFAULT_PORTS[protocol] || '';
if (!(0, _utils.isValidPort)(+port)) { if (!(0, _utils.isValidPort)(+port)) {
throw Error('service.port is invalid'); throw Error('service.port is invalid');
} }
if (protocol === 'tcp') { if (proto === 'tcp') {
const forward = searchParams.get('forward'); const forward = searchParams.get('forward');
if (!forward) { if (!forward) {
@ -262,6 +284,15 @@ class Config {
} }
} }
if (proto === 'https') {
if (typeof json.https_cert !== 'string' || json.https_cert === '') {
throw Error('"https_cert" must be provided');
}
if (typeof json.https_key !== 'string' || json.https_key === '') {
throw Error('"https_key" must be provided');
}
}
let server; let server;
if (json.servers) { if (json.servers) {
@ -308,23 +339,29 @@ class Config {
throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"'); throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"');
} }
const { protocol: _protocol, hostname, port } = new _url.URL(server.service); const { protocol, hostname, port: _port } = new _url.URL(server.service);
if (typeof _protocol !== 'string') { if (typeof protocol !== 'string') {
throw Error('service.protocol is invalid'); throw Error('service.protocol is invalid');
} }
const protocol = _protocol.slice(0, -1); const proto = protocol.slice(0, -1);
const available_server_protocols = ['tcp', 'ws', 'tls']; const available_server_protocols = ['tcp', 'ws', 'wss', 'tls'];
if (!available_server_protocols.includes(protocol)) { if (!available_server_protocols.includes(proto)) {
throw Error(`service.protocol must be: ${available_server_protocols.join(', ')}`); throw Error(`service.protocol must be: ${available_server_protocols.join(', ')}`);
} }
if (protocol === 'tls') { if (proto === 'tls' || proto === 'wss') {
if (typeof server.tls_cert !== 'string' || server.tls_cert === '') { if (from_client && server.tls_cert_self_signed) {
throw Error('"tls_cert" must be provided'); if (typeof server.tls_cert !== 'string' || server.tls_cert === '') {
throw Error('"tls_cert" must be provided when "tls_cert_self_signed" is set');
}
} }
if (!from_client) { if (!from_client) {
if (typeof server.tls_cert !== 'string' || server.tls_cert === '') {
throw Error('"tls_cert" must be provided');
}
if (typeof server.tls_key !== 'string' || server.tls_key === '') { if (typeof server.tls_key !== 'string' || server.tls_key === '') {
throw Error('"tls_key" must be provided'); throw Error('"tls_key" must be provided');
} }
@ -335,6 +372,7 @@ class Config {
throw Error('service.host is invalid'); throw Error('service.host is invalid');
} }
const port = _port || _constants.PROTOCOL_DEFAULT_PORTS[protocol] || '';
if (!(0, _utils.isValidPort)(+port)) { if (!(0, _utils.isValidPort)(+port)) {
throw Error('service.port is invalid'); throw Error('service.port is invalid');
} }

@ -19,6 +19,14 @@ var _net = require('net');
var _net2 = _interopRequireDefault(_net); var _net2 = _interopRequireDefault(_net);
var _http = require('http');
var _http2 = _interopRequireDefault(_http);
var _https = require('https');
var _https2 = _interopRequireDefault(_https);
var _url = require('url'); var _url = require('url');
var _tls = require('tls'); var _tls = require('tls');
@ -43,6 +51,8 @@ var _relay = require('./relay');
var _muxRelay = require('./mux-relay'); var _muxRelay = require('./mux-relay');
var _speedTester = require('./speed-tester');
var _utils = require('../utils'); var _utils = require('../utils');
var _proxies = require('../proxies'); var _proxies = require('../proxies');
@ -67,14 +77,23 @@ class Hub {
this._tcpRelays = new Map(); this._tcpRelays = new Map();
this._muxRelays = new Map(); this._muxRelays = new Map();
this._udpRelays = null; this._udpRelays = null;
this._prevHrtime = process.hrtime(); this._upSpeedTester = null;
this._dlSpeedTester = null;
this._totalRead = 0; this._totalRead = 0;
this._totalWritten = 0; this._totalWritten = 0;
this._prevTotalRead = 0;
this._prevTotalWritten = 0;
this._connQueue = []; this._connQueue = [];
this._udpCleanerTimer = null; this._udpCleanerTimer = null;
this._onRead = size => {
this._totalRead += size;
this._dlSpeedTester.feed(size);
};
this._onWrite = size => {
this._totalWritten += size;
this._upSpeedTester.feed(size);
};
this._onConnection = (socket, proxyRequest = null) => { this._onConnection = (socket, proxyRequest = null) => {
_utils.logger.verbose(`[hub] [${socket.remoteAddress}:${socket.remotePort}] connected`); _utils.logger.verbose(`[hub] [${socket.remoteAddress}:${socket.remotePort}] connected`);
@ -117,8 +136,8 @@ class Hub {
relay.on('_error', err => updateConnStatus('error', err.message)); relay.on('_error', err => updateConnStatus('error', err.message));
relay.on('_connect', targetAddress => updateConnStatus('target', targetAddress)); relay.on('_connect', targetAddress => updateConnStatus('target', targetAddress));
relay.on('_read', size => this._totalRead += size); relay.on('_read', this._onRead);
relay.on('_write', size => this._totalWritten += size); relay.on('_write', this._onWrite);
relay.on('close', () => { relay.on('close', () => {
updateConnStatus('close'); updateConnStatus('close');
this._tcpRelays.delete(relay.id); this._tcpRelays.delete(relay.id);
@ -130,6 +149,8 @@ class Hub {
this._config = new _config.Config(config); this._config = new _config.Config(config);
this._udpRelays = (0, _lruCache2.default)({ max: 500, maxAge: 1e5, dispose: (_, relay) => relay.destroy() }); this._udpRelays = (0, _lruCache2.default)({ max: 500, maxAge: 1e5, dispose: (_, relay) => relay.destroy() });
this._upSpeedTester = new _speedTester.SpeedTester();
this._dlSpeedTester = new _speedTester.SpeedTester();
} }
async run() { async run() {
@ -190,21 +211,11 @@ class Hub {
} }
getUploadSpeed() { getUploadSpeed() {
const [sec, nano] = process.hrtime(this._prevHrtime); return this._upSpeedTester.getSpeed();
const totalWritten = this._totalWritten;
const diff = totalWritten - this._prevTotalWritten;
const speed = Math.ceil(diff / (sec + nano / 1e9));
this._prevTotalWritten = totalWritten;
return speed;
} }
getDownloadSpeed() { getDownloadSpeed() {
const [sec, nano] = process.hrtime(this._prevHrtime); return this._dlSpeedTester.getSpeed();
const totalRead = this._totalRead;
const diff = totalRead - this._prevTotalRead;
const speed = Math.ceil(diff / (sec + nano / 1e9));
this._prevTotalRead = totalRead;
return speed;
} }
getConnStatuses() { getConnStatuses() {
@ -227,6 +238,7 @@ class Hub {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { local_protocol, local_search_params, local_host, local_port } = this._config; const { local_protocol, local_search_params, local_host, local_port } = this._config;
const { local_username: username, local_password: password } = this._config; const { local_username: username, local_password: password } = this._config;
const { https_key, https_cert } = this._config;
let server = null; let server = null;
switch (local_protocol) { switch (local_protocol) {
case 'tcp': case 'tcp':
@ -242,11 +254,22 @@ class Hub {
case 'socks5': case 'socks5':
case 'socks4': case 'socks4':
case 'socks4a': case 'socks4a':
server = _proxies.socks.createServer({ bindAddress: local_host, bindPort: local_port, username, password }); server = _proxies.socks.createServer({
bindAddress: local_host,
bindPort: local_port,
username,
password
});
break; break;
case 'http': case 'http':
case 'https': case 'https':
server = _proxies.http.createServer({ username, password }); server = _proxies.http.createServer({
secure: local_protocol === 'https',
https_key,
https_cert,
username,
password
});
break; break;
default: default:
return reject(Error(`unsupported protocol: "${local_protocol}"`)); return reject(Error(`unsupported protocol: "${local_protocol}"`));
@ -266,18 +289,19 @@ class Hub {
} }
async _createServerOnServer() { async _createServerOnServer() {
const { local_protocol, local_host, local_port, local_pathname, tls_key, tls_cert } = this._config;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const address = { const address = {
host: this._config.local_host, host: local_host,
port: this._config.local_port port: local_port
}; };
const onListening = server => { const onListening = server => {
const service = `${this._config.local_protocol}://${this._config.local_host}:${this._config.local_port}`; const service = `${local_protocol}://${local_host}:${local_port}` + (local_pathname ? local_pathname : '');
_utils.logger.info(`[hub] blinksocks server is running at ${service}`); _utils.logger.info(`[hub] blinksocks server is running at ${service}`);
resolve(server); resolve(server);
}; };
let server = null; let server = null;
switch (this._config.local_protocol) { switch (local_protocol) {
case 'tcp': case 'tcp':
{ {
server = _net2.default.createServer(); server = _net2.default.createServer();
@ -285,29 +309,38 @@ class Hub {
server.listen(address, () => onListening(server)); server.listen(address, () => onListening(server));
break; break;
} }
case 'wss':
case 'ws': case 'ws':
{ {
server = new _ws2.default.Server(_extends({}, address, { let http_s_server = null;
if (local_protocol === 'wss') {
http_s_server = _https2.default.createServer({ key: tls_key, cert: tls_cert });
} else {
http_s_server = _http2.default.createServer();
}
server = new _ws2.default.Server({
server: http_s_server,
path: local_pathname,
perMessageDeflate: false perMessageDeflate: false
})); });
server.getConnections = server._server.getConnections.bind(server._server); server.getConnections = server._server.getConnections.bind(server._server);
server.on('connection', (ws, req) => { server.on('connection', (ws, req) => {
ws.remoteAddress = req.connection.remoteAddress; ws.remoteAddress = req.connection.remoteAddress;
ws.remotePort = req.connection.remotePort; ws.remotePort = req.connection.remotePort;
this._onConnection(ws); this._onConnection(ws);
}); });
server.on('listening', () => onListening(server)); http_s_server.listen(address, () => onListening(http_s_server));
break; break;
} }
case 'tls': case 'tls':
{ {
server = _tls2.default.createServer({ key: [this._config.tls_key], cert: [this._config.tls_cert] }); server = _tls2.default.createServer({ key: tls_key, cert: tls_cert });
server.on('secureConnection', this._onConnection); server.on('secureConnection', this._onConnection);
server.listen(address, () => onListening(server)); server.listen(address, () => onListening(server));
break; break;
} }
default: default:
return reject(Error(`unsupported protocol: "${this._config.local_protocol}"`)); return reject(Error(`unsupported protocol: "${local_protocol}"`));
} }
server.on('error', reject); server.on('error', reject);
}); });
@ -385,8 +418,8 @@ class Hub {
muxRelay = this._createRelay(context, true); muxRelay = this._createRelay(context, true);
muxRelay.on('_error', err => updateConnStatus('error', err.message)); muxRelay.on('_error', err => updateConnStatus('error', err.message));
muxRelay.on('_connect', targetAddress => updateConnStatus('target', targetAddress)); muxRelay.on('_connect', targetAddress => updateConnStatus('target', targetAddress));
muxRelay.on('_read', size => this._totalRead += size); muxRelay.on('_read', this._onRead);
muxRelay.on('_write', size => this._totalWritten += size); muxRelay.on('_write', this._onWrite);
muxRelay.on('close', () => { muxRelay.on('close', () => {
updateConnStatus('close'); updateConnStatus('close');
this._muxRelays.delete(muxRelay.id); this._muxRelays.delete(muxRelay.id);
@ -411,9 +444,9 @@ class Hub {
_createRelay(context, isMux = false) { _createRelay(context, isMux = false) {
const props = { const props = {
config: this._config,
context: context, context: context,
transport: this._config.transport, config: this._config,
transport: this._config.server_protocol,
presets: this._config.presets presets: this._config.presets
}; };
if (isMux) { if (isMux) {

@ -26,40 +26,4 @@ Object.keys(_hub).forEach(function (key) {
return _hub[key]; return _hub[key];
} }
}); });
});
var _pipe = require('./pipe');
Object.keys(_pipe).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _pipe[key];
}
});
});
var _relay = require('./relay');
Object.keys(_relay).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _relay[key];
}
});
});
var _muxRelay = require('./mux-relay');
Object.keys(_muxRelay).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _muxRelay[key];
}
});
}); });

@ -144,6 +144,7 @@ class Relay extends _events2.default {
'udp': [_transports.UdpInbound, _transports.UdpOutbound], 'udp': [_transports.UdpInbound, _transports.UdpOutbound],
'tls': [_transports.TlsInbound, _transports.TlsOutbound], 'tls': [_transports.TlsInbound, _transports.TlsOutbound],
'ws': [_transports.WsInbound, _transports.WsOutbound], 'ws': [_transports.WsInbound, _transports.WsOutbound],
'wss': [_transports.WssInbound, _transports.WssOutbound],
'mux': [_transports.MuxInbound, _transports.MuxOutbound] 'mux': [_transports.MuxInbound, _transports.MuxOutbound]
}; };
let Inbound = null, let Inbound = null,

26
lib/core/speed-tester.js Normal file

@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
class SpeedTester {
constructor() {
this.totalBytes = 0;
this.lastTime = Date.now();
}
feed(bytes) {
this.totalBytes += bytes;
}
getSpeed() {
const now = Date.now();
const timeDiff = now - this.lastTime;
const speed = this.totalBytes / (timeDiff / 1e3);
this.lastTime = now;
this.totalBytes = 0;
return speed;
}
}
exports.SpeedTester = SpeedTester;

@ -3,7 +3,7 @@
Object.defineProperty(exports, "__esModule", { Object.defineProperty(exports, "__esModule", {
value: true value: true
}); });
exports.presets = undefined; exports.builtInPresetMap = undefined;
exports.getPresetClassByName = getPresetClassByName; exports.getPresetClassByName = getPresetClassByName;
var _mux = require('./_mux'); var _mux = require('./_mux');
@ -64,7 +64,20 @@ var _aeadRandomCipher2 = _interopRequireDefault(_aeadRandomCipher);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const presetMap = { function checkPresetClass(clazz) {
if (typeof clazz !== 'function') {
return false;
}
const requiredMethods = ['onDestroy', 'onInit', 'beforeOut', 'beforeIn', 'clientOut', 'serverIn', 'serverOut', 'clientIn', 'beforeOutUdp', 'beforeInUdp', 'clientOutUdp', 'serverInUdp', 'serverOutUdp', 'clientInUdp'];
if (requiredMethods.some(method => typeof clazz.prototype[method] !== 'function')) {
return false;
}
const requiredStaticMethods = ['onCheckParams', 'onCache'];
return !requiredStaticMethods.some(method => typeof clazz[method] !== 'function');
}
const builtInPresetMap = exports.builtInPresetMap = {
'mux': _mux2.default, 'mux': _mux2.default,
'base-auth': _baseAuth2.default, 'base-auth': _baseAuth2.default,
@ -87,24 +100,8 @@ const presetMap = {
'aead-random-cipher': _aeadRandomCipher2.default 'aead-random-cipher': _aeadRandomCipher2.default
}; };
function checkPresetClass(clazz) {
if (typeof clazz !== 'function') {
return false;
}
const requiredMethods = ['onDestroy', 'onInit', 'beforeOut', 'beforeIn', 'clientOut', 'serverIn', 'serverOut', 'clientIn', 'beforeOutUdp', 'beforeInUdp', 'clientOutUdp', 'serverInUdp', 'serverOutUdp', 'clientInUdp'];
if (requiredMethods.some(method => typeof clazz.prototype[method] !== 'function')) {
return false;
}
const requiredStaticMethods = ['onCheckParams', 'onCache'];
if (requiredStaticMethods.some(method => typeof clazz[method] !== 'function')) {
return false;
}
return true;
}
function getPresetClassByName(name, allowPrivate = false) { function getPresetClassByName(name, allowPrivate = false) {
let clazz = presetMap[name]; let clazz = builtInPresetMap[name];
if (clazz === undefined) { if (clazz === undefined) {
try { try {
clazz = require(name); clazz = require(name);
@ -119,6 +116,4 @@ function getPresetClassByName(name, allowPrivate = false) {
throw Error(`cannot load private preset "${name}"`); throw Error(`cannot load private preset "${name}"`);
} }
return clazz; return clazz;
} }
const presets = exports.presets = Object.keys(presetMap);

@ -11,6 +11,10 @@ var _http = require('http');
var _http2 = _interopRequireDefault(_http); var _http2 = _interopRequireDefault(_http);
var _https = require('https');
var _https2 = _interopRequireDefault(_https);
var _utils = require('../utils'); var _utils = require('../utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@ -27,8 +31,16 @@ function checkBasicAuthorization(credentials, { username, password }) {
return true; return true;
} }
function createServer({ username, password }) { function createServer({ secure, https_key, https_cert, username, password }) {
const server = _http2.default.createServer(); const name = secure ? 'https' : 'http';
let server = null;
if (secure) {
server = _https2.default.createServer({ key: https_key, cert: https_cert });
} else {
server = _http2.default.createServer();
}
const isAuthRequired = username !== '' && password !== '';
server.on('request', (req, res) => { server.on('request', (req, res) => {
const { hostname, port, pathname } = new _url.URL(req.url); const { hostname, port, pathname } = new _url.URL(req.url);
@ -39,15 +51,15 @@ function createServer({ username, password }) {
if (hostname === null || !(0, _utils.isValidPort)(_port)) { if (hostname === null || !(0, _utils.isValidPort)(_port)) {
const remote = `${socket.remoteAddress}:${socket.remotePort}`; const remote = `${socket.remoteAddress}:${socket.remotePort}`;
_utils.logger.warn(`[http] drop invalid http request sent from ${remote}`); _utils.logger.warn(`[${name}] drop invalid http request sent from ${remote}`);
return res.end(); return res.end();
} }
const proxyAuth = headers['proxy-authorization']; if (isAuthRequired) {
if (proxyAuth && username !== '' && password !== '') { const proxyAuth = headers['proxy-authorization'] || '';
const [type, credentials] = proxyAuth.split(' '); const [type, credentials] = proxyAuth.split(' ');
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) { if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
_utils.logger.error(`[http] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`); _utils.logger.error(`[${name}] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
return res.end('HTTP/1.1 401 Unauthorized\r\n\r\n'); return res.end('HTTP/1.1 401 Unauthorized\r\n\r\n');
} }
} }
@ -82,15 +94,15 @@ function createServer({ username, password }) {
if (hostname === null || !(0, _utils.isValidPort)(port)) { if (hostname === null || !(0, _utils.isValidPort)(port)) {
const remote = `${socket.remoteAddress}:${socket.remotePort}`; const remote = `${socket.remoteAddress}:${socket.remotePort}`;
_utils.logger.warn(`[http] [${appAddress}] drop invalid http CONNECT request sent from ${remote}`); _utils.logger.warn(`[${name}] [${appAddress}] drop invalid http CONNECT request sent from ${remote}`);
return socket.destroy(); return socket.destroy();
} }
const proxyAuth = req.headers['proxy-authorization']; if (isAuthRequired) {
if (proxyAuth && username !== '' && password !== '') { const proxyAuth = req.headers['proxy-authorization'] || '';
const [type, credentials] = proxyAuth.split(' '); const [type, credentials] = proxyAuth.split(' ');
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) { if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
_utils.logger.error(`[http] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`); _utils.logger.error(`[${name}] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
return socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); return socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
} }
} }
@ -105,8 +117,8 @@ function createServer({ username, password }) {
}); });
server.on('clientError', (err, socket) => { server.on('clientError', (err, socket) => {
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`; const appAddress = `${socket.remoteAddress || ''}:${socket.remotePort || ''}`;
_utils.logger.error(`[http] [${appAddress}] invalid http request: ${err.message}`); _utils.logger.error(`[${name}] [${appAddress}] invalid http request: ${err.message}`);
socket.destroy(); socket.destroy();
}); });

@ -220,6 +220,7 @@ const STAGE_DONE = 3;
function createServer({ bindAddress, bindPort, username, password }) { function createServer({ bindAddress, bindPort, username, password }) {
const server = _net2.default.createServer(); const server = _net2.default.createServer();
const isAuthRequired = username !== '' && password !== '';
server.on('connection', socket => { server.on('connection', socket => {
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`; const appAddress = `${socket.remoteAddress}:${socket.remotePort}`;
@ -240,6 +241,11 @@ function createServer({ bindAddress, bindPort, username, password }) {
const { method } = request; const { method } = request;
switch (method) { switch (method) {
case METHOD_NO_AUTH: case METHOD_NO_AUTH:
if (isAuthRequired) {
_utils.logger.error(`[socks] [${appAddress}] server requires authorization but got METHOD_NO_AUTH`);
socket.end(Buffer.from([SOCKS_VERSION_V5, METHOD_NOT_ACCEPTABLE]));
break;
}
stage = STAGE_SOCKS5_REQUEST_MESSAGE; stage = STAGE_SOCKS5_REQUEST_MESSAGE;
socket.write(Buffer.from([SOCKS_VERSION_V5, METHOD_NO_AUTH])); socket.write(Buffer.from([SOCKS_VERSION_V5, METHOD_NO_AUTH]));
@ -276,7 +282,7 @@ function createServer({ bindAddress, bindPort, username, password }) {
} else if (stage === STAGE_SOCKS5_INITIAL_NEGOTIATION_MESSAGE) { } else if (stage === STAGE_SOCKS5_INITIAL_NEGOTIATION_MESSAGE) {
request = parseSocks5InitialNegotiation(buffer); request = parseSocks5InitialNegotiation(buffer);
if (request !== null) { if (request !== null) {
if (username !== '' && password !== '') { if (isAuthRequired) {
if (username !== request.username || password !== request.password) { if (username !== request.username || password !== request.password) {
_utils.logger.error(`[socks] [${appAddress}] invalid socks5 authorization, username=${request.username} password=${request.password}`); _utils.logger.error(`[socks] [${appAddress}] invalid socks5 authorization, username=${request.username} password=${request.password}`);
socket.end(Buffer.from([SOCKS_VERSION_V5, 0x01])); socket.end(Buffer.from([SOCKS_VERSION_V5, 0x01]));

@ -52,6 +52,18 @@ Object.keys(_ws).forEach(function (key) {
}); });
}); });
var _wss = require('./wss');
Object.keys(_wss).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _wss[key];
}
});
});
var _mux = require('./mux'); var _mux = require('./mux');
Object.keys(_mux).forEach(function (key) { Object.keys(_mux).forEach(function (key) {

@ -332,7 +332,11 @@ class TcpOutbound extends _defs.Outbound {
await this.connect({ host, port }); await this.connect({ host, port });
} }
if (this._config.is_client) { if (this._config.is_client) {
await this.connect({ host: this._config.server_host, port: this._config.server_port }); await this.connect({
host: this._config.server_host,
port: this._config.server_port,
pathname: this._config.server_pathname
});
} }
this._socket.on('connect', () => { this._socket.on('connect', () => {
if (typeof onConnected === 'function') { if (typeof onConnected === 'function') {
@ -360,11 +364,11 @@ class TcpOutbound extends _defs.Outbound {
} }
} }
async connect({ host, port }) { async connect(target) {
if (this._socket && !this._socket.destroyed) { if (this._socket && !this._socket.destroyed) {
this._socket.destroy(); this._socket.destroy();
} }
this._socket = await this._connect({ host, port }); this._socket = await this._connect(target);
this._socket.on('error', this.onError); this._socket.on('error', this.onError);
this._socket.on('end', this.onHalfClose); this._socket.on('end', this.onHalfClose);
this._socket.on('close', this.onClose); this._socket.on('close', this.onClose);

@ -40,7 +40,11 @@ class TlsOutbound extends _tcp.TcpOutbound {
async _connect({ host, port }) { async _connect({ host, port }) {
_utils.logger.info(`[tls:outbound] [${this.remote}] connecting to tls://${host}:${port}`); _utils.logger.info(`[tls:outbound] [${this.remote}] connecting to tls://${host}:${port}`);
return _tls2.default.connect({ host, port, ca: [this._config.tls_cert] }); const options = { host, port };
if (this._config.tls_cert_self_signed) {
options.ca = [this._config.tls_cert];
}
return _tls2.default.connect(options);
} }
} }

@ -68,13 +68,25 @@ class WsOutbound extends _tcp.TcpOutbound {
return this._socket && this._socket.readyState === _ws2.default.OPEN; return this._socket && this._socket.readyState === _ws2.default.OPEN;
} }
async _connect({ host, port }) { async _connect(target) {
_utils.logger.info(`[${this.name}] [${this.remote}] connecting to ws://${host}:${port}`); const address = this.getConnAddress(target);
const socket = new _ws2.default(`ws://${host}:${port}`, { perMessageDeflate: false }); _utils.logger.info(`[${this.name}] [${this.remote}] connecting to ${address}`);
const socket = new _ws2.default(address, this.getConnOptions({
handshakeTimeout: 1e4,
perMessageDeflate: false
}));
socket.on('message', this.onReceive); socket.on('message', this.onReceive);
socket.on('close', () => socket.destroyed = true); socket.on('close', () => socket.destroyed = true);
return patchWebsocket.call(this, socket); return patchWebsocket.call(this, socket);
} }
getConnAddress({ host, port, pathname }) {
return `ws://${host}:${port}` + (pathname ? pathname : '');
}
getConnOptions(options) {
return options;
}
} }
exports.WsOutbound = WsOutbound; exports.WsOutbound = WsOutbound;

40
lib/transports/wss.js Normal file

@ -0,0 +1,40 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WssOutbound = exports.WssInbound = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _ws = require('./ws');
class WssInbound extends _ws.WsInbound {
get name() {
return 'wss:inbound';
}
}
exports.WssInbound = WssInbound;
class WssOutbound extends _ws.WsOutbound {
get name() {
return 'wss:outbound';
}
getConnAddress({ host, port, pathname }) {
return `wss://${host}:${port}` + (pathname ? pathname : '');
}
getConnOptions(options) {
const _options = _extends({}, options);
if (this._config.tls_cert_self_signed) {
_options.ca = [this._config.tls_cert];
}
return _options;
}
}
exports.WssOutbound = WssOutbound;

4429
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "blinksocks", "name": "blinksocks",
"version": "3.2.0", "version": "3.3.1",
"description": "A framework for building composable proxy protocol stack", "description": "A framework for building composable proxy protocol stack",
"main": "lib/index.js", "main": "lib/index.js",
"files": [ "files": [
@ -13,17 +13,15 @@
"scripts": { "scripts": {
"test": "npm run lint && npm run test:coverage", "test": "npm run lint && npm run test:coverage",
"test:coverage": "jest --runInBand --silent --coverage --bail", "test:coverage": "jest --runInBand --silent --coverage --bail",
"lint": "eslint bin src test pkg", "lint": "eslint bin src test",
"compile": "cross-env NODE_ENV=production babel src --out-dir lib --ignore __tests__,__mocks__", "compile": "cross-env NODE_ENV=production babel src --out-dir lib --ignore __tests__,__mocks__",
"prepkg": "npm run compile && rimraf pkg/blinksocks-* pkg/sha256sum.txt",
"pkg": "pkg --out-path pkg/ --targets node8.9.0-linux-x64,node8.9.0-macos-x64,node8.9.0-win-x64 .",
"postpkg": "node pkg/postpkg.js",
"debug:client": "cross-env NODE_ENV=development node --inspect --inspect-port=9200 bin/cli.js blinksocks.client.json", "debug:client": "cross-env NODE_ENV=development node --inspect --inspect-port=9200 bin/cli.js blinksocks.client.json",
"debug:server": "cross-env NODE_ENV=development node --inspect --inspect-port=9300 bin/cli.js blinksocks.server.json", "debug:server": "cross-env NODE_ENV=development node --inspect --inspect-port=9300 bin/cli.js blinksocks.server.json",
"client": "cross-env NODE_ENV=production node bin/cli.js blinksocks.client.json", "client": "cross-env NODE_ENV=production node bin/cli.js blinksocks.client.json",
"server": "cross-env NODE_ENV=production node bin/cli.js blinksocks.server.json", "server": "cross-env NODE_ENV=production node bin/cli.js blinksocks.server.json",
"prebenchmark": "npm run compile", "prebenchmark": "npm run compile",
"benchmark": "node benchmark/bootstrap.js" "benchmark": "node benchmark/bootstrap.js",
"prepublishOnly": "npm run compile"
}, },
"dependencies": { "dependencies": {
"chalk": "^2.4.1", "chalk": "^2.4.1",
@ -34,30 +32,28 @@
"lodash.isplainobject": "^4.0.6", "lodash.isplainobject": "^4.0.6",
"lodash.uniqueid": "^4.0.1", "lodash.uniqueid": "^4.0.1",
"long": "^4.0.0", "long": "^4.0.0",
"lru-cache": "^4.1.2", "lru-cache": "^4.1.3",
"winston": "^3.0.0-rc4", "winston": "^3.0.0",
"winston-daily-rotate-file": "^3.1.3", "winston-daily-rotate-file": "^3.2.1",
"ws": "^5.1.1" "ws": "^5.2.0"
}, },
"devDependencies": { "devDependencies": {
"babel-cli": "^6.26.0", "babel-cli": "^6.26.0",
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"babel-eslint": "^8.2.3", "babel-eslint": "^8.2.3",
"babel-jest": "^22.4.3", "babel-jest": "^23.0.1",
"babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.7.0",
"babel-register": "^6.26.0", "babel-register": "^6.26.0",
"cross-env": "^5.1.4", "cross-env": "^5.2.0",
"eslint": "^4.19.1", "eslint": "^4.19.1",
"eslint-config-babel": "^7.0.2", "eslint-config-babel": "^7.0.2",
"eslint-plugin-babel": "^5.1.0", "eslint-plugin-babel": "^5.1.0",
"eslint-plugin-flowtype": "^2.46.3", "eslint-plugin-flowtype": "^2.49.3",
"jest": "^22.4.3", "jest": "^23.1.0",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"pkg": "^4.3.1",
"rimraf": "^2.6.2",
"socks": "^2.2.0" "socks": "^2.2.0"
}, },
"repository": { "repository": {

@ -1,46 +0,0 @@
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const utils = require('util');
const zlib = require('zlib');
const { version } = require('../package.json');
const readdir = utils.promisify(fs.readdir);
const remove = utils.promisify(fs.unlink);
const appendFile = utils.promisify(fs.appendFile);
const readFile = utils.promisify(fs.readFile);
async function sha256sum(file) {
const sha256 = crypto.createHash('sha256');
const input = await readFile(file);
return sha256.update(input).digest('hex');
}
(async function main() {
try {
let files = await readdir(path.resolve(__dirname));
files = files.filter((f) => f.startsWith('blinksocks-'));
for (const file of files) {
const name = path.basename(file, '.exe');
const ext = path.extname(file);
const newName = `${name}-x64-v${version}${ext}.gz`;
const input = path.join(__dirname, file);
const output = path.join(__dirname, newName);
const hashFile = path.join(__dirname, 'sha256sum.txt');
// compress into .gz
const stream = fs.createReadStream(input).pipe(zlib.createGzip()).pipe(fs.createWriteStream(output));
stream.on('finish', async () => {
// calc sha256sum
await appendFile(hashFile, `${path.basename(output)} ${await sha256sum(output)}\n`);
// remove original
await remove(input);
});
}
} catch (err) {
console.error(err.message);
}
})();

@ -11,3 +11,13 @@ export const CONNECTED_TO_REMOTE = 'CONNECTED_TO_REMOTE';
export const MUX_NEW_CONN = 'MUX_NEW_CONN'; export const MUX_NEW_CONN = 'MUX_NEW_CONN';
export const MUX_DATA_FRAME = 'MUX_DATA_FRAME'; export const MUX_DATA_FRAME = 'MUX_DATA_FRAME';
export const MUX_CLOSE_CONN = 'MUX_CLOSE_CONN'; export const MUX_CLOSE_CONN = 'MUX_CLOSE_CONN';
// https://url.spec.whatwg.org/#url-miscellaneous
export const PROTOCOL_DEFAULT_PORTS = {
'ftp:': 21,
'gopher:': 70,
'http:': 80,
'https:': 443,
'ws:': 80,
'wss:': 443,
};

@ -8,6 +8,7 @@ import winston from 'winston';
import WinstonDailyRotateFile from 'winston-daily-rotate-file'; import WinstonDailyRotateFile from 'winston-daily-rotate-file';
import isPlainObject from 'lodash.isplainobject'; import isPlainObject from 'lodash.isplainobject';
import { ACL } from './acl'; import { ACL } from './acl';
import { PROTOCOL_DEFAULT_PORTS } from '../constants';
import { getPresetClassByName } from '../presets'; import { getPresetClassByName } from '../presets';
import { IPresetAddressing } from '../presets/defs'; import { IPresetAddressing } from '../presets/defs';
import { DNSCache, isValidHostname, isValidPort, logger, DNS_DEFAULT_EXPIRE } from '../utils'; import { DNSCache, isValidHostname, isValidPort, logger, DNS_DEFAULT_EXPIRE } from '../utils';
@ -24,21 +25,28 @@ export class Config {
local_search_params = null; local_search_params = null;
local_host = null; local_host = null;
local_port = null; local_port = null;
local_pathname = null;
server = null; server = null;
is_client = null; is_client = null;
is_server = null; is_server = null;
https_key = null;
https_cert = null;
timeout = null; timeout = null;
redirect = null; redirect = null;
dns_expire = null; dns_expire = null;
dns = null; dns = null;
transport = null; server_protocol = null;
server_host = null; server_host = null;
server_port = null; server_port = null;
server_pathname = null;
tls_cert = null; tls_cert = null;
tls_cert_self_signed = false;
tls_key = null; tls_key = null;
key = null; key = null;
@ -62,14 +70,17 @@ export class Config {
stores = []; stores = [];
constructor(json) { constructor(json) {
const { protocol, hostname, port, searchParams, username, password } = new URL(json.service); // service
const { protocol, hostname, port, pathname, searchParams, username, password } = new URL(json.service);
this.local_protocol = protocol.slice(0, -1); this.local_protocol = protocol.slice(0, -1);
this.local_username = username; this.local_username = username;
this.local_password = password; this.local_password = password;
this.local_search_params = searchParams; this.local_search_params = searchParams;
this.local_host = hostname; this.local_host = hostname;
this.local_port = +port; this.local_port = +port || +PROTOCOL_DEFAULT_PORTS[protocol];
this.local_pathname = pathname;
// server
let server; let server;
// TODO(remove in next version): make backwards compatibility to "json.servers" // TODO(remove in next version): make backwards compatibility to "json.servers"
if (json.servers !== undefined) { if (json.servers !== undefined) {
@ -78,7 +89,7 @@ export class Config {
chalk.bgYellowBright('WARN'), chalk.bgYellowBright('WARN'),
'"servers" will be deprecated in the next version,' + '"servers" will be deprecated in the next version,' +
' please configure only one server in "server: {...}",' + ' please configure only one server in "server: {...}",' +
' for migration guide please refer to CHANGELOG.md.' ' for migration guide please refer to CHANGELOG.md.',
); );
} else { } else {
server = json.server; server = json.server;
@ -100,8 +111,15 @@ export class Config {
this._initServer(server); this._initServer(server);
} }
// common // https_cert, https_key
if (this.is_client && this.local_protocol === 'https') {
logger.info(`[config] loading ${json.https_cert}`);
this.https_cert = loadFileSync(json.https_cert);
logger.info(`[config] loading ${json.https_key}`);
this.https_key = loadFileSync(json.https_key);
}
// common
this.timeout = (json.timeout !== undefined) ? json.timeout * 1e3 : 600 * 1e3; this.timeout = (json.timeout !== undefined) ? json.timeout * 1e3 : 600 * 1e3;
this.dns_expire = (json.dns_expire !== undefined) ? json.dns_expire * 1e3 : DNS_DEFAULT_EXPIRE; this.dns_expire = (json.dns_expire !== undefined) ? json.dns_expire * 1e3 : DNS_DEFAULT_EXPIRE;
@ -117,18 +135,22 @@ export class Config {
_initServer(server) { _initServer(server) {
// service // service
const { protocol, hostname, port } = new URL(server.service); const { protocol, hostname, port, pathname } = new URL(server.service);
this.transport = protocol.slice(0, -1); this.server_protocol = protocol.slice(0, -1);
this.server_host = hostname; this.server_host = hostname;
this.server_port = +port; this.server_port = +port || +PROTOCOL_DEFAULT_PORTS[protocol];
this.server_pathname = pathname;
// preload tls_cert and tls_key // preload tls_cert or tls_key
if (this.transport === 'tls' || this.transport === 'h2') { if (['tls', 'wss', 'h2'].includes(this.server_protocol)) {
if (server.tls_cert) { if (this.is_client) {
this.tls_cert_self_signed = !!server.tls_cert_self_signed;
}
if (this.tls_cert_self_signed || this.is_server) {
logger.info(`[config] loading ${server.tls_cert}`); logger.info(`[config] loading ${server.tls_cert}`);
this.tls_cert = loadFileSync(server.tls_cert); this.tls_cert = loadFileSync(server.tls_cert);
} }
if (this.is_server && server.tls_key) { if (this.is_server) {
logger.info(`[config] loading ${server.tls_key}`); logger.info(`[config] loading ${server.tls_key}`);
this.tls_key = loadFileSync(server.tls_key); this.tls_key = loadFileSync(server.tls_key);
} }
@ -164,7 +186,7 @@ export class Config {
// remove unnecessary presets // remove unnecessary presets
if (this.mux) { if (this.mux) {
this.presets = this.presets.filter( this.presets = this.presets.filter(
({ name }) => !IPresetAddressing.isPrototypeOf(getPresetClassByName(name)) ({ name }) => !IPresetAddressing.isPrototypeOf(getPresetClassByName(name)),
); );
} }
@ -208,7 +230,7 @@ export class Config {
// TODO: Enable coloring. Currently we have to prevent dumping color characters in log files. // TODO: Enable coloring. Currently we have to prevent dumping color characters in log files.
// colorize(), // colorize(),
prettyPrint(), prettyPrint(),
printf((info) => `${info.timestamp} - ${info.level}: ${info.message}`) printf((info) => `${info.timestamp} - ${info.level}: ${info.message}`),
), ),
transports: trans, transports: trans,
}); });
@ -233,19 +255,18 @@ export class Config {
throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"'); throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"');
} }
const { protocol: _protocol, hostname, port, searchParams } = new URL(json.service); const { protocol, hostname, port: _port, searchParams } = new URL(json.service);
// service.protocol // service.protocol
if (typeof _protocol !== 'string') { if (typeof protocol !== 'string') {
throw Error('service.protocol is invalid'); throw Error('service.protocol is invalid');
} }
const protocol = _protocol.slice(0, -1); const proto = protocol.slice(0, -1);
const available_client_protocols = [ const available_client_protocols = [
'tcp', 'http', 'https', 'tcp', 'http', 'https', 'socks', 'socks5', 'socks4', 'socks4a',
'socks', 'socks5', 'socks4', 'socks4a'
]; ];
if (!available_client_protocols.includes(protocol)) { if (!available_client_protocols.includes(proto)) {
throw Error(`service.protocol must be: ${available_client_protocols.join(', ')}`); throw Error(`service.protocol must be: ${available_client_protocols.join(', ')}`);
} }
@ -255,12 +276,13 @@ export class Config {
} }
// service.port // service.port
const port = _port || PROTOCOL_DEFAULT_PORTS[protocol] || '';
if (!isValidPort(+port)) { if (!isValidPort(+port)) {
throw Error('service.port is invalid'); throw Error('service.port is invalid');
} }
// service.query // service.query
if (protocol === 'tcp') { if (proto === 'tcp') {
const forward = searchParams.get('forward'); const forward = searchParams.get('forward');
// ?forward // ?forward
@ -277,6 +299,16 @@ export class Config {
} }
} }
// https_cert, https_key
if (proto === 'https') {
if (typeof json.https_cert !== 'string' || json.https_cert === '') {
throw Error('"https_cert" must be provided');
}
if (typeof json.https_key !== 'string' || json.https_key === '') {
throw Error('"https_key" must be provided');
}
}
// server // server
let server; let server;
// TODO(remove in next version): make backwards compatibility to "json.servers" // TODO(remove in next version): make backwards compatibility to "json.servers"
@ -329,27 +361,33 @@ export class Config {
throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"'); throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"');
} }
const { protocol: _protocol, hostname, port } = new URL(server.service); const { protocol, hostname, port: _port } = new URL(server.service);
// service.protocol // service.protocol
if (typeof _protocol !== 'string') { if (typeof protocol !== 'string') {
throw Error('service.protocol is invalid'); throw Error('service.protocol is invalid');
} }
const protocol = _protocol.slice(0, -1); const proto = protocol.slice(0, -1);
const available_server_protocols = [ const available_server_protocols = [
'tcp', 'ws', 'tls', 'h2' 'tcp', 'ws', 'wss', 'tls', 'h2'
]; ];
if (!available_server_protocols.includes(protocol)) { if (!available_server_protocols.includes(proto)) {
throw Error(`service.protocol must be: ${available_server_protocols.join(', ')}`); throw Error(`service.protocol must be: ${available_server_protocols.join(', ')}`);
} }
// tls_cert, tls_key // tls_cert, tls_key
if (protocol === 'tls' || protocol === 'h2') { if (['tls', 'wss', 'h2'].includes(proto)) {
if (typeof server.tls_cert !== 'string' || server.tls_cert === '') { if (from_client && server.tls_cert_self_signed) {
throw Error('"tls_cert" must be provided'); if (typeof server.tls_cert !== 'string' || server.tls_cert === '') {
throw Error('"tls_cert" must be provided when "tls_cert_self_signed" is set');
}
} }
// on server, both tls_cert and tls_key must be set
if (!from_client) { if (!from_client) {
if (typeof server.tls_cert !== 'string' || server.tls_cert === '') {
throw Error('"tls_cert" must be provided');
}
if (typeof server.tls_key !== 'string' || server.tls_key === '') { if (typeof server.tls_key !== 'string' || server.tls_key === '') {
throw Error('"tls_key" must be provided'); throw Error('"tls_key" must be provided');
} }
@ -362,6 +400,7 @@ export class Config {
} }
// service.port // service.port
const port = _port || PROTOCOL_DEFAULT_PORTS[protocol] || '';
if (!isValidPort(+port)) { if (!isValidPort(+port)) {
throw Error('service.port is invalid'); throw Error('service.port is invalid');
} }

@ -1,6 +1,8 @@
import _sodium from 'libsodium-wrappers'; import _sodium from 'libsodium-wrappers';
import dgram from 'dgram'; import dgram from 'dgram';
import net from 'net'; import net from 'net';
import http from 'http';
import https from 'https';
import { URL } from 'url'; import { URL } from 'url';
import http2 from 'http2'; import http2 from 'http2';
import tls from 'tls'; import tls from 'tls';
@ -12,7 +14,7 @@ import { Relay } from './relay';
import { MuxRelay } from './mux-relay'; import { MuxRelay } from './mux-relay';
import { SpeedTester } from './speed-tester'; import { SpeedTester } from './speed-tester';
import { dumpHex, getRandomInt, hash, logger } from '../utils'; import { dumpHex, getRandomInt, hash, logger } from '../utils';
import { http, socks, tcp } from '../proxies'; import { http as httpProxy, socks, tcp } from '../proxies';
import { APP_ID } from '../constants'; import { APP_ID } from '../constants';
export const MAX_CONNECTIONS = 50; export const MAX_CONNECTIONS = 50;
@ -149,6 +151,7 @@ export class Hub {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { local_protocol, local_search_params, local_host, local_port } = this._config; const { local_protocol, local_search_params, local_host, local_port } = this._config;
const { local_username: username, local_password: password } = this._config; const { local_username: username, local_password: password } = this._config;
const { https_key, https_cert } = this._config;
let server = null; let server = null;
switch (local_protocol) { switch (local_protocol) {
case 'tcp': { case 'tcp': {
@ -163,11 +166,22 @@ export class Hub {
case 'socks5': case 'socks5':
case 'socks4': case 'socks4':
case 'socks4a': case 'socks4a':
server = socks.createServer({ bindAddress: local_host, bindPort: local_port, username, password }); server = socks.createServer({
bindAddress: local_host,
bindPort: local_port,
username,
password,
});
break; break;
case 'http': case 'http':
case 'https': case 'https':
server = http.createServer({ username, password }); server = httpProxy.createServer({
secure: local_protocol === 'https',
https_key,
https_cert,
username,
password,
});
break; break;
default: default:
return reject(Error(`unsupported protocol: "${local_protocol}"`)); return reject(Error(`unsupported protocol: "${local_protocol}"`));
@ -187,14 +201,14 @@ export class Hub {
} }
async _createServerOnServer() { async _createServerOnServer() {
const { local_protocol, local_host, local_port, tls_key, tls_cert } = this._config; const { local_protocol, local_host, local_port, local_pathname, tls_key, tls_cert } = this._config;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const address = { const address = {
host: local_host, host: local_host,
port: local_port, port: local_port,
}; };
const onListening = (server) => { const onListening = (server) => {
const service = `${local_protocol}://${local_host}:${local_port}`; const service = `${local_protocol}://${local_host}:${local_port}` + (local_pathname ? local_pathname : '');
logger.info(`[hub] blinksocks server is running at ${service}`); logger.info(`[hub] blinksocks server is running at ${service}`);
resolve(server); resolve(server);
}; };
@ -206,9 +220,17 @@ export class Hub {
server.listen(address, () => onListening(server)); server.listen(address, () => onListening(server));
break; break;
} }
case 'wss':
case 'ws': { case 'ws': {
let http_s_server = null;
if (local_protocol === 'wss') {
http_s_server = https.createServer({ key: tls_key, cert: tls_cert });
} else {
http_s_server = http.createServer();
}
server = new ws.Server({ server = new ws.Server({
...address, server: http_s_server,
path: local_pathname,
perMessageDeflate: false, perMessageDeflate: false,
}); });
server.getConnections = server._server.getConnections.bind(server._server); server.getConnections = server._server.getConnections.bind(server._server);
@ -217,7 +239,7 @@ export class Hub {
ws.remotePort = req.connection.remotePort; ws.remotePort = req.connection.remotePort;
this._onConnection(ws); this._onConnection(ws);
}); });
server.on('listening', () => onListening(server)); http_s_server.listen(address, () => onListening(http_s_server));
break; break;
} }
case 'tls': { case 'tls': {
@ -423,9 +445,9 @@ export class Hub {
_createRelay(context, isMux = false) { _createRelay(context, isMux = false) {
const props = { const props = {
config: this._config,
context: context, context: context,
transport: this._config.transport, config: this._config,
transport: this._config.server_protocol,
presets: this._config.presets, presets: this._config.presets,
}; };
if (isMux) { if (isMux) {

@ -10,6 +10,7 @@ import {
TlsInbound, TlsOutbound, TlsInbound, TlsOutbound,
Http2Inbound, Http2Outbound, Http2Inbound, Http2Outbound,
WsInbound, WsOutbound, WsInbound, WsOutbound,
WssInbound, WssOutbound,
MuxInbound, MuxOutbound, MuxInbound, MuxOutbound,
} from '../transports'; } from '../transports';
@ -134,6 +135,7 @@ export class Relay extends EventEmitter {
'tls': [TlsInbound, TlsOutbound], 'tls': [TlsInbound, TlsOutbound],
'h2': [Http2Inbound, Http2Outbound], 'h2': [Http2Inbound, Http2Outbound],
'ws': [WsInbound, WsOutbound], 'ws': [WsInbound, WsOutbound],
'wss': [WssInbound, WssOutbound],
'mux': [MuxInbound, MuxOutbound], 'mux': [MuxInbound, MuxOutbound],
}; };
let Inbound = null, Outbound = null; let Inbound = null, Outbound = null;

@ -26,7 +26,29 @@ import ObfsTls12TicketPreset from './obfs-tls1.2-ticket';
// others // others
import AeadRandomCipherPreset from './aead-random-cipher'; import AeadRandomCipherPreset from './aead-random-cipher';
const presetMap = { /**
* check if a class is a valid preset class
* @param clazz
* @returns {boolean}
*/
function checkPresetClass(clazz) {
if (typeof clazz !== 'function') {
return false;
}
// check require hooks
const requiredMethods = [
'onDestroy', 'onInit',
'beforeOut', 'beforeIn', 'clientOut', 'serverIn', 'serverOut', 'clientIn',
'beforeOutUdp', 'beforeInUdp', 'clientOutUdp', 'serverInUdp', 'serverOutUdp', 'clientInUdp'
];
if (requiredMethods.some((method) => typeof clazz.prototype[method] !== 'function')) {
return false;
}
const requiredStaticMethods = ['onCheckParams', 'onCache'];
return !requiredStaticMethods.some((method) => typeof clazz[method] !== 'function');
}
export const builtInPresetMap = {
// functional // functional
'mux': MuxPreset, 'mux': MuxPreset,
@ -56,35 +78,12 @@ const presetMap = {
'aead-random-cipher': AeadRandomCipherPreset 'aead-random-cipher': AeadRandomCipherPreset
}; };
/**
* check if a class is a valid preset class
* @param clazz
* @returns {boolean}
*/
function checkPresetClass(clazz) {
if (typeof clazz !== 'function') {
return false;
}
// check require hooks
const requiredMethods = [
'onDestroy', 'onInit',
'beforeOut', 'beforeIn', 'clientOut', 'serverIn', 'serverOut', 'clientIn',
'beforeOutUdp', 'beforeInUdp', 'clientOutUdp', 'serverInUdp', 'serverOutUdp', 'clientInUdp'
];
if (requiredMethods.some((method) => typeof clazz.prototype[method] !== 'function')) {
return false;
}
const requiredStaticMethods = ['onCheckParams', 'onCache'];
if (requiredStaticMethods.some((method) => typeof clazz[method] !== 'function')) {
return false;
}
return true;
}
export function getPresetClassByName(name, allowPrivate = false) { export function getPresetClassByName(name, allowPrivate = false) {
let clazz = presetMap[name]; // load from built-in
let clazz = builtInPresetMap[name];
if (clazz === undefined) { if (clazz === undefined) {
try { try {
// load from external
clazz = require(name); clazz = require(name);
} catch (err) { } catch (err) {
throw Error(`cannot load preset "${name}" from built-in modules or external`); throw Error(`cannot load preset "${name}" from built-in modules or external`);
@ -98,5 +97,3 @@ export function getPresetClassByName(name, allowPrivate = false) {
} }
return clazz; return clazz;
} }
export const presets = Object.keys(presetMap);

@ -1,5 +1,6 @@
import { URL } from 'url'; import { URL } from 'url';
import http from 'http'; import http from 'http';
import https from 'https';
import { logger, isValidPort } from '../utils'; import { logger, isValidPort } from '../utils';
function checkBasicAuthorization(credentials, { username, password }) { function checkBasicAuthorization(credentials, { username, password }) {
@ -14,8 +15,16 @@ function checkBasicAuthorization(credentials, { username, password }) {
return true; return true;
} }
export function createServer({ username, password }) { export function createServer({ secure, https_key, https_cert, username, password }) {
const server = http.createServer(); const name = secure ? 'https' : 'http';
let server = null;
if (secure) {
server = https.createServer({ key: https_key, cert: https_cert });
} else {
server = http.createServer();
}
const isAuthRequired = username !== '' && password !== '';
// Simple HTTP Proxy // Simple HTTP Proxy
server.on('request', (req, res) => { server.on('request', (req, res) => {
@ -27,16 +36,16 @@ export function createServer({ username, password }) {
if (hostname === null || !isValidPort(_port)) { if (hostname === null || !isValidPort(_port)) {
const remote = `${socket.remoteAddress}:${socket.remotePort}`; const remote = `${socket.remoteAddress}:${socket.remotePort}`;
logger.warn(`[http] drop invalid http request sent from ${remote}`); logger.warn(`[${name}] drop invalid http request sent from ${remote}`);
return res.end(); return res.end();
} }
// Basic Authorization // Basic Authorization
const proxyAuth = headers['proxy-authorization']; if (isAuthRequired) {
if (proxyAuth && username !== '' && password !== '') { const proxyAuth = headers['proxy-authorization'] || '';
const [type, credentials] = proxyAuth.split(' '); const [type, credentials] = proxyAuth.split(' ');
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) { if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
logger.error(`[http] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`); logger.error(`[${name}] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
return res.end('HTTP/1.1 401 Unauthorized\r\n\r\n'); return res.end('HTTP/1.1 401 Unauthorized\r\n\r\n');
} }
} }
@ -63,7 +72,7 @@ export function createServer({ username, password }) {
// free to recv from application now // free to recv from application now
socket.resume(); socket.resume();
} },
}); });
}); });
@ -76,16 +85,16 @@ export function createServer({ username, password }) {
if (hostname === null || !isValidPort(port)) { if (hostname === null || !isValidPort(port)) {
const remote = `${socket.remoteAddress}:${socket.remotePort}`; const remote = `${socket.remoteAddress}:${socket.remotePort}`;
logger.warn(`[http] [${appAddress}] drop invalid http CONNECT request sent from ${remote}`); logger.warn(`[${name}] [${appAddress}] drop invalid http CONNECT request sent from ${remote}`);
return socket.destroy(); return socket.destroy();
} }
// Basic Authorization // Basic Authorization
const proxyAuth = req.headers['proxy-authorization']; if (isAuthRequired) {
if (proxyAuth && username !== '' && password !== '') { const proxyAuth = req.headers['proxy-authorization'] || '';
const [type, credentials] = proxyAuth.split(' '); const [type, credentials] = proxyAuth.split(' ');
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) { if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
logger.error(`[http] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`); logger.error(`[${name}] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
return socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); return socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
} }
} }
@ -95,14 +104,14 @@ export function createServer({ username, password }) {
port: port, port: port,
onConnected: () => { onConnected: () => {
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n'); socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
} },
}); });
}); });
// errors // errors
server.on('clientError', (err, socket) => { server.on('clientError', (err, socket) => {
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`; const appAddress = `${socket.remoteAddress || ''}:${socket.remotePort || ''}`;
logger.error(`[http] [${appAddress}] invalid http request: ${err.message}`); logger.error(`[${name}] [${appAddress}] invalid http request: ${err.message}`);
socket.destroy(); socket.destroy();
}); });

@ -287,6 +287,7 @@ const STAGE_DONE = 3;
export function createServer({ bindAddress, bindPort, username, password }) { export function createServer({ bindAddress, bindPort, username, password }) {
const server = net.createServer(); const server = net.createServer();
const isAuthRequired = username !== '' && password !== '';
server.on('connection', (socket) => { server.on('connection', (socket) => {
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`; const appAddress = `${socket.remoteAddress}:${socket.remotePort}`;
@ -308,6 +309,11 @@ export function createServer({ bindAddress, bindPort, username, password }) {
const { method } = request; const { method } = request;
switch (method) { switch (method) {
case METHOD_NO_AUTH: case METHOD_NO_AUTH:
if (isAuthRequired) {
logger.error(`[socks] [${appAddress}] server requires authorization but got METHOD_NO_AUTH`);
socket.end(Buffer.from([SOCKS_VERSION_V5, METHOD_NOT_ACCEPTABLE]));
break;
}
stage = STAGE_SOCKS5_REQUEST_MESSAGE; stage = STAGE_SOCKS5_REQUEST_MESSAGE;
// Socks5 Select Message // Socks5 Select Message
socket.write(Buffer.from([SOCKS_VERSION_V5, METHOD_NO_AUTH])); socket.write(Buffer.from([SOCKS_VERSION_V5, METHOD_NO_AUTH]));
@ -347,7 +353,7 @@ export function createServer({ bindAddress, bindPort, username, password }) {
request = parseSocks5InitialNegotiation(buffer); request = parseSocks5InitialNegotiation(buffer);
if (request !== null) { if (request !== null) {
// Username/Password Authentication // Username/Password Authentication
if (username !== '' && password !== '') { if (isAuthRequired) {
if (username !== request.username || password !== request.password) { if (username !== request.username || password !== request.password) {
logger.error(`[socks] [${appAddress}] invalid socks5 authorization, username=${request.username} password=${request.password}`); logger.error(`[socks] [${appAddress}] invalid socks5 authorization, username=${request.username} password=${request.password}`);
socket.end(Buffer.from([SOCKS_VERSION_V5, 0x01])); socket.end(Buffer.from([SOCKS_VERSION_V5, 0x01]));

@ -3,4 +3,5 @@ export * from './udp';
export * from './tls'; export * from './tls';
export * from './h2'; export * from './h2';
export * from './ws'; export * from './ws';
export * from './wss';
export * from './mux'; export * from './mux';

@ -343,7 +343,11 @@ export class TcpOutbound extends Outbound {
await this.connect({ host, port }); await this.connect({ host, port });
} }
if (this._config.is_client) { if (this._config.is_client) {
await this.connect({ host: this._config.server_host, port: this._config.server_port }); await this.connect({
host: this._config.server_host,
port: this._config.server_port,
pathname: this._config.server_pathname,
});
} }
this._socket.on('connect', () => { this._socket.on('connect', () => {
if (typeof onConnected === 'function') { if (typeof onConnected === 'function') {
@ -371,12 +375,12 @@ export class TcpOutbound extends Outbound {
} }
} }
async connect({ host, port }) { async connect(target) {
// close alive connection before create a new one // close alive connection before create a new one
if (this._socket && !this._socket.destroyed) { if (this._socket && !this._socket.destroyed) {
this._socket.destroy(); this._socket.destroy();
} }
this._socket = await this._connect({ host, port }); this._socket = await this._connect(target);
this._socket.on('error', this.onError); this._socket.on('error', this.onError);
this._socket.on('end', this.onHalfClose); this._socket.on('end', this.onHalfClose);
this._socket.on('close', this.onClose); this._socket.on('close', this.onClose);

@ -29,7 +29,11 @@ export class TlsOutbound extends TcpOutbound {
// overwrite _connect of tcp outbound using tls.connect() // overwrite _connect of tcp outbound using tls.connect()
async _connect({ host, port }) { async _connect({ host, port }) {
logger.info(`[tls:outbound] [${this.remote}] connecting to tls://${host}:${port}`); logger.info(`[tls:outbound] [${this.remote}] connecting to tls://${host}:${port}`);
return tls.connect({ host, port, ca: [this._config.tls_cert] }); const options = { host, port };
if (this._config.tls_cert_self_signed) {
options.ca = [this._config.tls_cert];
}
return tls.connect(options);
} }
} }

@ -6,7 +6,7 @@ function patchWebsocket(ws) {
ws.write = (buffer) => ws.send(buffer, { ws.write = (buffer) => ws.send(buffer, {
compress: false, compress: false,
mask: false, mask: false,
fin: true // send data out immediately fin: true, // send data out immediately
}, () => this.emit('drain')); }, () => this.emit('drain'));
ws.end = () => ws.close(); ws.end = () => ws.close();
ws.destroy = () => ws.close(); ws.destroy = () => ws.close();
@ -57,12 +57,24 @@ export class WsOutbound extends TcpOutbound {
return this._socket && this._socket.readyState === WebSocket.OPEN; return this._socket && this._socket.readyState === WebSocket.OPEN;
} }
async _connect({ host, port }) { async _connect(target) {
logger.info(`[${this.name}] [${this.remote}] connecting to ws://${host}:${port}`); const address = this.getConnAddress(target);
const socket = new WebSocket(`ws://${host}:${port}`, { perMessageDeflate: false }); logger.info(`[${this.name}] [${this.remote}] connecting to ${address}`);
const socket = new WebSocket(address, this.getConnOptions({
handshakeTimeout: 1e4, // 10s
perMessageDeflate: false,
}));
socket.on('message', this.onReceive); socket.on('message', this.onReceive);
socket.on('close', () => socket.destroyed = true); socket.on('close', () => socket.destroyed = true);
return patchWebsocket.call(this, socket); return patchWebsocket.call(this, socket);
} }
getConnAddress({ host, port, pathname }) {
return `ws://${host}:${port}` + (pathname ? pathname : '');
}
getConnOptions(options) {
return options;
}
} }

29
src/transports/wss.js Normal file

@ -0,0 +1,29 @@
import { WsInbound, WsOutbound } from './ws';
export class WssInbound extends WsInbound {
get name() {
return 'wss:inbound';
}
}
export class WssOutbound extends WsOutbound {
get name() {
return 'wss:outbound';
}
getConnAddress({ host, port, pathname }) {
return `wss://${host}:${port}` + (pathname ? pathname : '');
}
getConnOptions(options) {
const _options = { ...options };
if (this._config.tls_cert_self_signed) {
_options.ca = [this._config.tls_cert];
}
return _options;
}
}

@ -9,10 +9,10 @@ const clientJson = {
'presets': [ 'presets': [
{ 'name': 'ss-base' }, { 'name': 'ss-base' },
{ 'name': 'obfs-random-padding' }, { 'name': 'obfs-random-padding' },
{ 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
}, },
'log_level': 'debug' 'log_level': 'debug',
}; };
const serverJson = { const serverJson = {
@ -21,11 +21,11 @@ const serverJson = {
'presets': [ 'presets': [
{ 'name': 'ss-base' }, { 'name': 'ss-base' },
{ 'name': 'obfs-random-padding' }, { 'name': 'obfs-random-padding' },
{ 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
'acl': true, 'acl': true,
'acl_conf': path.resolve(__dirname, 'resources', 'acl.txt'), 'acl_conf': path.resolve(__dirname, 'resources', 'acl.txt'),
'log_level': 'debug' 'log_level': 'debug',
}; };
test('acl', async () => await run({ clientJson, serverJson, not: true })); test('acl', async () => await run({ clientJson, serverJson, not: true }));

@ -1,24 +1,24 @@
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "aead-random-cipher", "params": { "method": "aes-256-gcm" } }, { 'name': 'aead-random-cipher', 'params': { 'method': 'aes-256-gcm' } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "aead-random-cipher", "params": { "method": "aes-256-gcm" } }, { 'name': 'aead-random-cipher', 'params': { 'method': 'aes-256-gcm' } },
] ],
}; };
test('aead-random-cipher', async () => await run({ clientJson, serverJson })); test('aead-random-cipher', async () => await run({ clientJson, serverJson }));

@ -1,22 +1,22 @@
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "base-auth", "params": { "method": "sha1" } }, { 'name': 'base-auth', 'params': { 'method': 'sha1' } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "base-auth", "params": { "method": "sha1" } }, { 'name': 'base-auth', 'params': { 'method': 'sha1' } },
] ],
}; };
test('base-auth', async () => await run({ clientJson, serverJson })); test('base-auth', async () => await run({ clientJson, serverJson }));

@ -1,26 +1,26 @@
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
] ],
}; };
test('init-configs-minimal, tcp', async () => { test('init-configs-minimal, tcp', async () => {

@ -1,45 +1,46 @@
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "2/tS:7|.-ec.7cxk", 'key': '2/tS:7|.-ec.7cxk',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
"tls_cert": "cert.pem", 'tls_cert': 'cert.pem',
"mux": false, 'tls_cert_self_signed': false,
"mux_concurrency": 10 'mux': false,
'mux_concurrency': 10,
}, },
"dns": [], 'dns': [],
"dns_expire": 3600, 'dns_expire': 3600,
"timeout": 610, 'timeout': 610,
"log_path": "bs-client.log", 'log_path': 'bs-client.log',
"log_level": "info", 'log_level': 'info',
"log_max_days": 30 'log_max_days': 30,
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "2/tS:7|.-ec.7cxk", 'key': '2/tS:7|.-ec.7cxk',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
"tls_key": "key.pem", 'tls_key': 'key.pem',
"tls_cert": "cert.pem", 'tls_cert': 'cert.pem',
"mux": false, 'mux': false,
"dns": [], 'dns': [],
"dns_expire": 3600, 'dns_expire': 3600,
"timeout": 610, 'timeout': 610,
"redirect": "", 'redirect': '',
"log_path": "bs-server.log", 'log_path': 'bs-server.log',
"log_level": "info", 'log_level': 'info',
"log_max_days": 30 'log_max_days': 30,
}; };
test('init-configs', async () => { test('init-configs', async () => {

@ -6,34 +6,35 @@ const tlsKey = path.resolve(__dirname, 'resources', 'key.pem');
const tlsCert = path.resolve(__dirname, 'resources', 'cert.pem'); const tlsCert = path.resolve(__dirname, 'resources', 'cert.pem');
const client = { const client = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
// "service": "tcp://127.0.0.1:1082", // "service": "tcp://127.0.0.1:1082",
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
"mux": true, 'mux': true,
"mux_concurrency": 5, 'mux_concurrency': 5,
"tls_cert": tlsCert, 'tls_cert': tlsCert,
'tls_cert_self_signed': true,
}, },
"log_level": "debug" 'log_level': 'debug',
}; };
const server = { const server = {
// "service": "tcp://127.0.0.1:1082", // "service": "tcp://127.0.0.1:1082",
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
"mux": true, 'mux': true,
"tls_cert": tlsCert, 'tls_cert': tlsCert,
"tls_key": tlsKey, 'tls_key': tlsKey,
"log_level": "debug" 'log_level': 'debug',
}; };
test('multiplexing over tcp', async () => { test('multiplexing over tcp', async () => {

@ -4,24 +4,24 @@ import run from '../common/run-e2e';
const mockfile = path.resolve(__dirname, 'resources', 'http-mock.txt'); const mockfile = path.resolve(__dirname, 'resources', 'http-mock.txt');
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-http", "params": { "file": mockfile } } { 'name': 'obfs-http', 'params': { 'file': mockfile } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-http", "params": { "file": mockfile } } { 'name': 'obfs-http', 'params': { 'file': mockfile } },
] ],
}; };
test('obfs-http', async () => await run({ clientJson, serverJson, repeat: 5 })); test('obfs-http', async () => await run({ clientJson, serverJson, repeat: 5 }));

@ -1,24 +1,24 @@
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-tls1.2-ticket", "params": { "sni": ["test.com", "example.com"] } } { 'name': 'obfs-tls1.2-ticket', 'params': { 'sni': ['test.com', 'example.com'] } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-tls1.2-ticket" } { 'name': 'obfs-tls1.2-ticket' },
] ],
}; };
test('obfs-tls1.2-ticket', async () => { test('obfs-tls1.2-ticket', async () => {

@ -2,25 +2,25 @@ import run from '../common/run-e2e';
const clientJson = { const clientJson = {
// "service": "", // "service": "",
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
] ],
}; };
test('http-proxy', async () => await run({ test('http-proxy', async () => await run({

@ -2,26 +2,26 @@ import clone from 'lodash.clonedeep';
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const client = { const client = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" } { 'name': 'ss-base' },
] ],
} },
}; };
const server = { const server = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" } { 'name': 'ss-base' },
] ],
}; };
test('ss-aead-cipher, aes-128-gcm', async () => { test('ss-aead-cipher, aes-128-gcm', async () => {
const cipher = { "name": "ss-aead-cipher", "params": { "method": "aes-128-gcm" } }; const cipher = { 'name': 'ss-aead-cipher', 'params': { 'method': 'aes-128-gcm' } };
const clientJson = clone(client); const clientJson = clone(client);
const serverJson = clone(server); const serverJson = clone(server);
@ -34,7 +34,7 @@ test('ss-aead-cipher, aes-128-gcm', async () => {
}); });
test('ss-aead-cipher, chacha20-ietf-poly1305', async () => { test('ss-aead-cipher, chacha20-ietf-poly1305', async () => {
const cipher = { "name": "ss-aead-cipher", "params": { "method": "chacha20-ietf-poly1305" } }; const cipher = { 'name': 'ss-aead-cipher', 'params': { 'method': 'chacha20-ietf-poly1305' } };
const clientJson = clone(client); const clientJson = clone(client);
const serverJson = clone(server); const serverJson = clone(server);
@ -47,7 +47,7 @@ test('ss-aead-cipher, chacha20-ietf-poly1305', async () => {
}); });
test('ss-aead-cipher, xchacha20-ietf-poly1305', async () => { test('ss-aead-cipher, xchacha20-ietf-poly1305', async () => {
const cipher = { "name": "ss-aead-cipher", "params": { "method": "xchacha20-ietf-poly1305" } }; const cipher = { 'name': 'ss-aead-cipher', 'params': { 'method': 'xchacha20-ietf-poly1305' } };
const clientJson = clone(client); const clientJson = clone(client);
const serverJson = clone(server); const serverJson = clone(server);

@ -2,26 +2,26 @@ import clone from 'lodash.clonedeep';
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const client = { const client = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" } { 'name': 'ss-base' },
] ],
} },
}; };
const server = { const server = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" } { 'name': 'ss-base' },
] ],
}; };
test('ss-stream-cipher, aes-256-cfb', async () => { test('ss-stream-cipher, aes-256-cfb', async () => {
const cipher = { "name": "ss-stream-cipher", "params": { "method": "aes-256-cfb" } }; const cipher = { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-256-cfb' } };
const clientJson = clone(client); const clientJson = clone(client);
const serverJson = clone(server); const serverJson = clone(server);
@ -34,7 +34,7 @@ test('ss-stream-cipher, aes-256-cfb', async () => {
}); });
test('ss-stream-cipher, rc4-md5', async () => { test('ss-stream-cipher, rc4-md5', async () => {
const cipher = { "name": "ss-stream-cipher", "params": { "method": "rc4-md5" } }; const cipher = { 'name': 'ss-stream-cipher', 'params': { 'method': 'rc4-md5' } };
const clientJson = clone(client); const clientJson = clone(client);
const serverJson = clone(server); const serverJson = clone(server);
@ -47,7 +47,7 @@ test('ss-stream-cipher, rc4-md5', async () => {
}); });
test('ss-stream-cipher, rc4-md5-6', async () => { test('ss-stream-cipher, rc4-md5-6', async () => {
const cipher = { "name": "ss-stream-cipher", "params": { "method": "rc4-md5-6" } }; const cipher = { 'name': 'ss-stream-cipher', 'params': { 'method': 'rc4-md5-6' } };
const clientJson = clone(client); const clientJson = clone(client);
const serverJson = clone(server); const serverJson = clone(server);

@ -1,26 +1,26 @@
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "ssr-auth-aes128-md5" }, { 'name': 'ssr-auth-aes128-md5' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-256-cfb" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-256-cfb' } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "ssr-auth-aes128-md5" }, { 'name': 'ssr-auth-aes128-md5' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-256-cfb" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-256-cfb' } },
] ],
}; };
test('ssr-auth-aes128-md5', async () => { test('ssr-auth-aes128-md5', async () => {

@ -1,26 +1,26 @@
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "ssr-auth-aes128-sha1" }, { 'name': 'ssr-auth-aes128-sha1' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-256-cfb" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-256-cfb' } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "ssr-auth-aes128-sha1" }, { 'name': 'ssr-auth-aes128-sha1' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-256-cfb" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-256-cfb' } },
] ],
}; };
test('ssr-auth-aes128-sha1', async () => { test('ssr-auth-aes128-sha1', async () => {

@ -1,26 +1,26 @@
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "ssr-auth-chain-a" }, { 'name': 'ssr-auth-chain-a' },
{ "name": "ss-stream-cipher", "params": { "method": "none" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'none' } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "ssr-auth-chain-a" }, { 'name': 'ssr-auth-chain-a' },
{ "name": "ss-stream-cipher", "params": { "method": "none" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'none' } },
] ],
}; };
test('ssr-auth-chain-a', async () => { test('ssr-auth-chain-a', async () => {

@ -1,26 +1,26 @@
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const clientJson = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "ssr-auth-chain-b" }, { 'name': 'ssr-auth-chain-b' },
{ "name": "ss-stream-cipher", "params": { "method": "none" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'none' } },
] ],
} },
}; };
const serverJson = { const serverJson = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "ssr-auth-chain-b" }, { 'name': 'ssr-auth-chain-b' },
{ "name": "ss-stream-cipher", "params": { "method": "none" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'none' } },
] ],
}; };
test('ssr-auth-chain-b', async () => { test('ssr-auth-chain-b', async () => {

@ -1,33 +1,43 @@
import path from 'path'; import path from 'path';
import clone from 'lodash.clonedeep';
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const tlsKey = path.resolve(__dirname, 'resources', 'key.pem'); const tlsKey = path.resolve(__dirname, 'resources', 'key.pem');
const tlsCert = path.resolve(__dirname, 'resources', 'cert.pem'); const tlsCert = path.resolve(__dirname, 'resources', 'cert.pem');
const clientJson = { const client = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tls://localhost:1082", 'service': 'tls://localhost:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
"tls_cert": tlsCert 'tls_cert': tlsCert,
} 'tls_cert_self_signed': false,
},
}; };
const serverJson = { const server = {
"service": "tls://localhost:1082", 'service': 'tls://localhost:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
], ],
"tls_cert": tlsCert, 'tls_cert': tlsCert,
"tls_key": tlsKey 'tls_key': tlsKey,
}; };
test('transport-layer-tls', async () => await run({ clientJson, serverJson })); test('transport-layer-tls, tls_cert_self_signed is false', async () => {
await run({ clientJson: client, serverJson: server, not: true });
});
test('transport-layer-tls, tls_cert_self_signed is true', async () => {
const clientJson = clone(client);
clientJson.server['tls_cert_self_signed'] = true;
await run({ clientJson, serverJson: server });
});

@ -1,26 +1,39 @@
import clone from 'lodash.clonedeep';
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const clientJson = { const client = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "ws://127.0.0.1:1082", 'service': 'ws://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
] ],
} },
}; };
const serverJson = { const server = {
"service": "ws://127.0.0.1:1082", 'service': 'ws://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD", 'key': '9{*2gdBSdCrgnSBD',
"presets": [ 'presets': [
{ "name": "ss-base" }, { 'name': 'ss-base' },
{ "name": "obfs-random-padding" }, { 'name': 'obfs-random-padding' },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } } { 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
] ],
}; };
test('transport-layer-ws', async () => await run({ clientJson, serverJson })); test('transport-layer-ws path=/', async () => {
await run({ clientJson: client, serverJson: server });
});
test('transport-layer-ws path=/test-path', async () => {
const clientJson = clone(client);
const serverJson = clone(server);
clientJson.server.service = 'ws://127.0.0.1:1082/test-path';
serverJson.service = 'ws://127.0.0.1:1082/test-path';
await run({ clientJson, serverJson });
});

@ -0,0 +1,34 @@
import path from 'path';
import run from '../common/run-e2e';
const tlsKey = path.resolve(__dirname, 'resources', 'key.pem');
const tlsCert = path.resolve(__dirname, 'resources', 'cert.pem');
const clientJson = {
'service': 'socks5://127.0.0.1:1081',
'server': {
'service': 'wss://localhost:1082',
'key': '9{*2gdBSdCrgnSBD',
'presets': [
{ 'name': 'ss-base' },
{ 'name': 'obfs-random-padding' },
{ 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
],
'tls_cert': tlsCert,
'tls_cert_self_signed': true,
},
};
const serverJson = {
'service': 'wss://localhost:1082',
'key': '9{*2gdBSdCrgnSBD',
'presets': [
{ 'name': 'ss-base' },
{ 'name': 'obfs-random-padding' },
{ 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
],
'tls_cert': tlsCert,
'tls_key': tlsKey,
};
test('transport-layer-wss', async () => await run({ clientJson, serverJson }));

@ -2,25 +2,25 @@ import clone from 'lodash.clonedeep';
import run from '../common/run-e2e'; import run from '../common/run-e2e';
const client = { const client = {
"service": "socks5://127.0.0.1:1081", 'service': 'socks5://127.0.0.1:1081',
"server": { 'server': {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD" 'key': '9{*2gdBSdCrgnSBD',
} },
}; };
const server = { const server = {
"service": "tcp://127.0.0.1:1082", 'service': 'tcp://127.0.0.1:1082',
"key": "9{*2gdBSdCrgnSBD" 'key': '9{*2gdBSdCrgnSBD',
}; };
test('v2ray-vmess, none', async () => { test('v2ray-vmess, none', async () => {
const presets = [{ const presets = [{
"name": "v2ray-vmess", 'name': 'v2ray-vmess',
"params": { 'params': {
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7", 'id': 'c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7',
"security": "none" 'security': 'none',
} },
}]; }];
const clientJson = clone(client); const clientJson = clone(client);
const serverJson = clone(server); const serverJson = clone(server);
@ -33,11 +33,11 @@ test('v2ray-vmess, none', async () => {
test('v2ray-vmess, aes-128-gcm', async () => { test('v2ray-vmess, aes-128-gcm', async () => {
const presets = [{ const presets = [{
"name": "v2ray-vmess", 'name': 'v2ray-vmess',
"params": { 'params': {
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7", 'id': 'c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7',
"security": "aes-128-gcm" 'security': 'aes-128-gcm',
} },
}]; }];
const clientJson = clone(client); const clientJson = clone(client);
const serverJson = clone(server); const serverJson = clone(server);

@ -1,18 +1,18 @@
const fs = jest.genMockFromModule('fs'); const fs = jest.genMockFromModule('fs');
fs.statSync = function () { fs.statSync = function() {
return { return {
isFile: () => true isFile: () => true,
}; };
}; };
fs.lstatSync = function () { fs.lstatSync = function() {
const err = new Error(); const err = new Error();
err.code = 'ENOENT'; err.code = 'ENOENT';
throw err; throw err;
}; };
fs.readFileSync = function () { fs.readFileSync = function() {
return Buffer.alloc(0); return Buffer.alloc(0);
}; };

@ -30,9 +30,9 @@ describe('Config#test', () => {
service: 'tcp://127.0.0.1:1082', service: 'tcp://127.0.0.1:1082',
key: 'abc', key: 'abc',
presets: [{ presets: [{
name: 'ss-base' name: 'ss-base',
}] }],
} },
}; };
it('should throw when ?forward is invalid', () => { it('should throw when ?forward is invalid', () => {
@ -96,9 +96,9 @@ describe('Config#testOnClient', () => {
service: 'tcp://127.0.0.1:1082', service: 'tcp://127.0.0.1:1082',
key: 'abc', key: 'abc',
presets: [{ presets: [{
name: 'ss-base' name: 'ss-base',
}] }],
} },
}; };
it('should throw when service is not provided', () => { it('should throw when service is not provided', () => {
@ -175,13 +175,13 @@ describe('Config#init', () => {
service: 'tcp://127.0.0.1:1082', service: 'tcp://127.0.0.1:1082',
key: 'abc', key: 'abc',
presets: [{ presets: [{
name: 'ss-base' name: 'ss-base',
}] }],
}, },
dns: ['8.8.8.8'], dns: ['8.8.8.8'],
log_level: 'warn', log_level: 'warn',
log_path: 'blinksocks.log', log_path: 'blinksocks.log',
log_max_days: 30 log_max_days: 30,
}; };
it('should config set correctly', () => { it('should config set correctly', () => {
@ -205,15 +205,15 @@ describe('Config#initServer', () => {
service: 'tls://127.0.0.1:1082', service: 'tls://127.0.0.1:1082',
key: 'abc', key: 'abc',
presets: [{ presets: [{
name: 'ss-base' name: 'ss-base',
}], }],
tls_cert: 'mock_cert.pem', tls_cert: 'mock_cert.pem',
tls_key: 'mock_key.pem' tls_key: 'mock_key.pem',
}; };
it('should config set correctly', () => { it('should config set correctly', () => {
const config = new Config(serverConf); const config = new Config(serverConf);
expect(config.transport).toBe('tls'); expect(config.server_protocol).toBe('tls');
expect(config.tls_cert).toBeDefined(); expect(config.tls_cert).toBeDefined();
expect(config.tls_key).toBeDefined(); expect(config.tls_key).toBeDefined();
}); });

@ -0,0 +1,7 @@
import { SpeedTester } from '../../../src/core/speed-tester';
test('SpeedTester::getSpeed()', () => {
const st = new SpeedTester();
st.feed(10);
expect(st.getSpeed()).toBeGreaterThan(0);
});

@ -0,0 +1,13 @@
import { getPresetClassByName } from '../../../src/presets';
test('getPresetClassByName, fail', () => {
expect(() => getPresetClassByName('_unknown_')).toThrow();
expect(() => getPresetClassByName('mux')).toThrow();
expect(() => getPresetClassByName(require.resolve('./mock_invalid_preset_a'))).toThrow();
expect(() => getPresetClassByName(require.resolve('./mock_invalid_preset_b'))).toThrow();
});
test('getPresetClassByName, success', () => {
expect(() => getPresetClassByName('ss-base')).not.toThrow();
expect(() => getPresetClassByName(require.resolve('./mock_valid_preset'))).not.toThrow();
});

@ -0,0 +1,3 @@
module.exports = class MockPreset {
};

@ -0,0 +1 @@
module.exports = null;

@ -0,0 +1,5 @@
import { IPreset } from '../../../src/presets/defs';
module.exports = class MockPreset extends IPreset {
};

@ -1,31 +1,31 @@
import { AdvancedBuffer } from '../../../src/utils/advanced-buffer'; import { AdvancedBuffer } from '../../../src/utils/advanced-buffer';
describe('AdvancedBuffer#constructor', function () { describe('AdvancedBuffer#constructor', function() {
it('should throw when options is not given', function () { it('should throw when options is not given', function() {
expect(() => new AdvancedBuffer()).toThrow(); expect(() => new AdvancedBuffer()).toThrow();
}); });
it('should throw when getPacketLength not Function', function () { it('should throw when getPacketLength not Function', function() {
expect(() => new AdvancedBuffer({ getPacketLength: null })).toThrow(); expect(() => new AdvancedBuffer({ getPacketLength: null })).toThrow();
}); });
}); });
describe('AdvancedBuffer#put', function () { describe('AdvancedBuffer#put', function() {
it('should throw when pass a non-buffer to put() ', function () { it('should throw when pass a non-buffer to put() ', function() {
const buffer = new AdvancedBuffer({ const buffer = new AdvancedBuffer({
getPacketLength: () => 0 getPacketLength: () => 0,
}); });
expect(() => buffer.put()).toThrow(); expect(() => buffer.put()).toThrow();
}); });
it('should leave 0xff', function () { it('should leave 0xff', function() {
const buffer = new AdvancedBuffer({ const buffer = new AdvancedBuffer({
getPacketLength: (chunk) => { getPacketLength: (chunk) => {
return (chunk.length < 2) ? 0 : chunk.readUInt16BE(0); return (chunk.length < 2) ? 0 : chunk.readUInt16BE(0);
} },
}); });
const callback = jest.fn(); const callback = jest.fn();
buffer.on('data', callback); buffer.on('data', callback);
@ -39,7 +39,7 @@ describe('AdvancedBuffer#put', function () {
expect(callback).toHaveBeenCalledTimes(3); expect(callback).toHaveBeenCalledTimes(3);
}); });
it('should drop the first byte', function () { it('should drop the first byte', function() {
let dropped = false; let dropped = false;
const buffer = new AdvancedBuffer({ const buffer = new AdvancedBuffer({
getPacketLength: (chunk) => { getPacketLength: (chunk) => {
@ -49,7 +49,7 @@ describe('AdvancedBuffer#put', function () {
} else { } else {
return chunk.length > 1 ? chunk.readUInt16BE(0) : 0; return chunk.length > 1 ? chunk.readUInt16BE(0) : 0;
} }
} },
}); });
const callback = jest.fn(); const callback = jest.fn();
buffer.on('data', callback); buffer.on('data', callback);
@ -63,9 +63,9 @@ describe('AdvancedBuffer#put', function () {
expect(callback).toHaveBeenCalledTimes(3); expect(callback).toHaveBeenCalledTimes(3);
}); });
it('should drop buffer', function () { it('should drop buffer', function() {
const buffer = new AdvancedBuffer({ const buffer = new AdvancedBuffer({
getPacketLength: () => -1 getPacketLength: () => -1,
}); });
const callback = jest.fn(); const callback = jest.fn();
buffer.on('data', callback); buffer.on('data', callback);
@ -76,9 +76,9 @@ describe('AdvancedBuffer#put', function () {
}); });
describe('AdvancedBuffer#clear', function () { describe('AdvancedBuffer#clear', function() {
it('should throw when pass a non-buffer to put() ', function () { it('should throw when pass a non-buffer to put() ', function() {
const buffer = new AdvancedBuffer({ getPacketLength: () => 0 }); const buffer = new AdvancedBuffer({ getPacketLength: () => 0 });
buffer.put(Buffer.from([0x00])); buffer.put(Buffer.from([0x00]));
buffer.clear(); buffer.clear();

Some files were not shown because too many files have changed in this diff Show More