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"
before_deploy:
- export NEXT_VERSION=3.2.1
- export NEXT_VERSION=3.3.1
- export COMMIT_HASH=$(git log --format=%h -1)
- export DIST_PATH=build
- export PUBLISH_REPO=blinksocks/blinksocks-nightly-releases

@ -1,31 +1,138 @@
# 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)
> Node.js 10 is supported in this version!
### :exclamation: Notable Changes
- src: support socks4(a), socks5, http `basic authorization`(username/password).
- presets: add `IPresetAddressing::onInitTargetAddress()` and `IPresetAddressing::resolveTargetAddress()`.
- presets: add `chacha20-ietf` method for `ss-stream-cipher`, but require Node.js 10.x.
- presets: deprecated `IPreset::onNotified()`.
- presets: deprecated `auto-conf` preset.
- **src**: support socks4(a), socks5, http `basic authorization`(username/password).
- **presets**: add `IPresetAddressing::onInitTargetAddress()` and `IPresetAddressing::resolveTargetAddress()`.
- **presets**: add `chacha20-ietf` method for `ss-stream-cipher`, but require Node.js 10.x.
- **presets**: deprecated `IPreset::onNotified()`.
- **presets**: deprecated `auto-conf` preset.
### :rocket: Features & Improvements
- core: add `Pipe::initTargetAddress()`.
- utils: add `uint64ToBuffer()`, `incrementLE()` and `incrementBE()`.
- package: upgrade ws from v3.3.3 to v5.1.1.
- package: use WHATWG URL API instead of legacy qs api.
- package: add package-lock.json.
- package: remove unused dependencies.
- ci: add test on the latest stable Node.js.
- **core**: add `Pipe::initTargetAddress()`.
- **utils**: add `uint64ToBuffer()`, `incrementLE()` and `incrementBE()`.
- **package**: upgrade ws from v3.3.3 to v5.1.1.
- **package**: use WHATWG URL API instead of legacy qs api.
- **package**: add package-lock.json.
- **package**: remove unused dependencies.
- **ci**: add test on the latest stable Node.js.
### :bug: Bug Fixes:
- hub: fix sequence between calling `relay::on()` and `relay.init()`.
- hub: patch `server.getConnections()` for ws.
- **hub**: fix sequence between calling `relay::on()` and `relay.init()`.
- **hub**: patch `server.getConnections()` for ws.
### Migrating from 3.1.1 to 3.2.0
@ -37,13 +144,13 @@ $ npm install -g blinksocks@3.2.0
### :rocket: Features & Improvements
- api: remove Hub::getPerformance().
- api: add Hub::getUploadSpeed(), Hub::getDownloadSpeed() and Hub::getConnStatuses().
- benchmark: enlarge wait time before kill iperf server.
- package: upgrade winston logger to v3.
- package: remove husky and precommit hook, it's annoying.
- test: add udp tests for multiplexing.
- docs: add usage for systemd.
- **api**: remove Hub::getPerformance().
- **api**: add Hub::getUploadSpeed(), Hub::getDownloadSpeed() and Hub::getConnStatuses().
- **benchmark**: enlarge wait time before kill iperf server.
- **package**: upgrade winston logger to v3.
- **package**: remove husky and precommit hook, it's annoying.
- **test**: add udp tests for multiplexing.
- **docs**: add usage for systemd.
### :bug: Bug Fixes:
@ -61,18 +168,18 @@ $ npm install -g blinksocks@3.1.1
### :rocket: Features & Improvements
- lib: compiled to Node.js 8.
- core: start udp server only when use socks protocol on client.
- core: add performance.js to collect upload/download speed.
- core: add Hub::getConnections(), Hub::getTotalRead(), Hub::getTotalWritten() and Hub::getPerformance().
- test: add tests for udp relay.
- test: add tests for multiplexing over ws and tls.
- **lib**: compiled to Node.js 8.
- **core**: start udp server only when use socks protocol on client.
- **core**: add performance.js to collect upload/download speed.
- **core**: add Hub::getConnections(), Hub::getTotalRead(), Hub::getTotalWritten() and Hub::getPerformance().
- **test**: add tests for udp relay.
- **test**: add tests for multiplexing over ws and tls.
### :bug: Bug Fixes:
- src: reduce error rate when enable multiplexing on server.
- core: include target address in tracker's log when enable multiplexing.
- core: avoid putting duplicate target address in tracker's log.
- **src**: reduce error rate when enable multiplexing on server.
- **core**: include target address in tracker's log when enable multiplexing.
- **core**: avoid putting duplicate target address in tracker's log.
### Migrating from 3.0.0 to 3.1.0
@ -84,30 +191,30 @@ $ npm install -g blinksocks@3.1.0
### :boom: Breaking Changes:
- bin/init: remove "workers".
- bin/init: replace "servers: []" to "server: {}".
- core: remove balancer.
- presets: remove `stats` preset.
- presets: remove `tracker` preset, re-implement in core.
- presets: remove `access-control` preset, re-implement in core.
- **bin/init**: remove "workers".
- **bin/init**: replace "servers: []" to "server: {}".
- **core**: remove balancer.
- **presets**: remove `stats` preset.
- **presets**: remove `tracker` preset, re-implement in core.
- **presets**: remove `access-control` preset, re-implement in core.
### :rocket: Features & Improvements
- bin/init: add "acl" and "acl_conf" on server side.
- src: refactor src/dns-cache.js and move it to utils/.
- src: move config items from global to local.
- core: expose Config::getLogFilePath().
- benchmark: use json output of iperf.
- presets/mux: discretize cid and other improvements.
- ci: add scripts to do nightly release automatically.
- test: add e2e tests.
- **bin/init**: add "acl" and "acl_conf" on server side.
- **src**: refactor src/dns-cache.js and move it to utils/.
- **src**: move config items from global to local.
- **core**: expose Config::getLogFilePath().
- **benchmark**: use json output of iperf.
- **presets/mux**: discretize cid and other improvements.
- **ci**: add scripts to do nightly release automatically.
- **test**: add e2e tests.
### :bug: Bug Fixes:
- 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 sub connection id collision.
- core: handle listen "error" event.
- **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 sub connection id collision.
- **core**: handle listen "error" event.
### Committers: 2
@ -181,21 +288,21 @@ After v3, blinksocks no longer support multiple servers and cluster mode, so you
### :rocket: Features & Improvements
- core: add mux-relay.
- benchmark: archive reports of 2017.
- package: upgrade pkg to v4.3.0.
- package: compile before running benchmark.
- presets: add mux preset.
- transports: refactor and optimize websocket transport.
- transports: add this.ctx.
- transports: add mux transport.
- utils: add a faster version of crypto.randomBytes().
- **core**: add mux-relay.
- **benchmark**: archive reports of 2017.
- **package**: upgrade pkg to v4.3.0.
- **package**: compile before running benchmark.
- **presets**: add mux preset.
- **transports**: refactor and optimize websocket transport.
- **transports**: add this.ctx.
- **transports**: add mux transport.
- **utils**: add a faster version of crypto.randomBytes().
**
### :bug: Bug Fixes:
- proxies: fix crash when client reset the socks connection later.
- utils: fix getRandomInt().
- utils: remove generateMutexId().
- **proxies**: fix crash when client reset the socks connection later.
- **utils**: fix getRandomInt().
- **utils**: remove generateMutexId().
### Upgrade from 2.8.5 to 2.9.0

@ -20,7 +20,7 @@
* Cross-platform: running on Linux, Windows and macOS.
* 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].
* Convenient protocol [customization].
* Access Control List([ACL]) support.
@ -51,21 +51,10 @@ Please check out [blinksocks-nightly-releases](https://github.com/blinksocks/bli
## Run blinksocks
**npm version(require Node.js, recommended)**
```
$ 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).
## Documents
@ -75,6 +64,7 @@ For configuring blinksocks, please refer to [Configuration](docs/config).
1. [Usage](docs/usage)
2. [Configuration](docs/config)
3. [Presets](docs/presets)
4. [Examples](docs/examples)
### For Developers
@ -91,11 +81,12 @@ See [contributors](https://github.com/blinksocks/blinksocks/graphs/contributors)
Apache License 2.0
[TLS]: docs/config#blinksocks-over-tls
[WebSocket]: docs/config#blinksocks-over-websocket
[multiplexing]: docs/config#multiplexing
[customization]: docs/development/api
[ACL]: docs/config#access-control-list
[shadowsocks]: docs/presets/RECOMMENDATIONS.md#work-with-shadowsocks
[shadowsocksR]: docs/presets/RECOMMENDATIONS.md#work-with-shadowsocksr
[v2ray vmess]: docs/presets/RECOMMENDATIONS.md#work-with-v2ray-vmess
[TLS]: docs/examples/tls
[WebSocket]: docs/examples/websocket
[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')) {
const { presets, legacyPresets } = modules;
console.log(chalk.bold.underline('[Built-In]'));
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) : '-');
const { builtInPresetMap } = modules;
console.log(Object.keys(builtInPresetMap).join(os.EOL));
return;
}

@ -38,24 +38,30 @@ module.exports = function init({ isMinimal, isOverwrite, isDryRun = false }) {
'presets': [
{ 'name': 'ss-base' },
{ '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_self_signed': false,
'mux': false,
'mux_concurrency': 10
'mux_concurrency': 10,
},
'https_key': 'https_key.pem',
'https_cert': 'https_cert.pem',
'dns': [],
'dns_expire': 3600,
'timeout': timeout,
'log_path': 'bs-client.log',
'log_level': 'info',
'log_max_days': 30
'log_max_days': 30,
};
if (isMinimal) {
delete clientJson.server.tls_cert;
delete clientJson.server.tls_cert_self_signed;
delete clientJson.server.mux;
delete clientJson.server.mux_concurrency;
delete clientJson.https_key;
delete clientJson.https_cert;
delete clientJson.dns;
delete clientJson.dns_expire;
delete clientJson.timeout;
@ -70,7 +76,7 @@ module.exports = function init({ isMinimal, isOverwrite, isDryRun = false }) {
'presets': [
{ 'name': 'ss-base' },
{ '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_cert': 'cert.pem',
@ -83,7 +89,7 @@ module.exports = function init({ isMinimal, isOverwrite, isDryRun = false }) {
'redirect': '',
'log_path': 'bs-server.log',
'log_level': 'info',
'log_max_days': 30
'log_max_days': 30,
};
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)
2. [Configuration](config)
3. [Presets](presets)
4. [Examples](examples)
## For Developers

@ -31,9 +31,12 @@ $ blinksocks init
}
],
"tls_cert": "cert.pem",
"tls_cert_self_signed": false,
"mux": false,
"mux_concurrency": 10
},
"https_key": "https_key.pem",
"https_cert": "https_cert.pem",
"dns": [],
"dns_expire": 3600,
"timeout": 221,
@ -76,28 +79,31 @@ $ blinksocks init
}
```
| KEY | DESCRIPTION | DEFAULT | REMARKS |
| :---------------- | :----------------------------------------------------------- | :-------------- | :---------------------------------------------------------- |
| service | local service address | - | a [WHATWG URL] e.g, "socks://127.0.0.1:1080" |
| server | remote server config | - | **CLIENT ONLY** |
| server.service | remote service address | - | `<protocol>://<host>:<port>` |
| server.key | remote server master key | - | - |
| presets | an ordered list of presets to build a protocol stack | - | see [presets] |
| presets[i].name | preset name | - | - |
| presets[i].params | preset params | - | - |
| tls_key | private key for TLS | - | required on server if `<protocol>` is "tls" |
| tls_cert | certificate for TLS | - | required on both client and server if `<protocol>` is "tls" |
| acl | enable access control list or not | false | **SERVER ONLY** |
| acl_conf | access control list configuration file | - | **SERVER ONLY**, see below |
| timeout | timeout for each connection | 600 | in seconds |
| mux | enable multiplexing or not | false | - |
| mux_concurrency | the max mux connection established between client and server | 10 | **CLIENT ONLY** |
| redirect | target address to redirect when preset fail to process | "" | **SERVER ONLY** `<host>:<port>` |
| dns | a list of DNS server IPs | [] | - |
| dns_expire | in-memory DNS cache expiration time | 3600 | in seconds |
| log_path | log file path | "bs-[type].log" | a relative/absolute directory or a file 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 |
| KEY | DESCRIPTION | DEFAULT | REMARKS |
| :------------------- | :----------------------------------------------------------- | :-------------- | :---------------------------------------------------------- |
| service | local service address | - | [WHATWG URL] e.g, "socks://127.0.0.1:1080" |
| server | remote server config | - | **CLIENT ONLY** |
| server.service | remote service address | - | [WHATWG URL] e.g, "tls://example.com:443" |
| server.key | remote server master key | - | - |
| presets | an ordered list of presets to build a protocol stack | - | see [presets] |
| presets[i].name | preset name | - | - |
| presets[i].params | preset params | - | - |
| tls_key | private key path for TLS | - | required on server if `<protocol>` is "tls" |
| tls_cert | certificate path for TLS | - | required on both client and server if `<protocol>` is "tls" |
| tls_cert_self_signed | whether "tls_cert" is `self-signed` or not | false | **CLIENT ONLY** |
| https_key | private key path for HTTPS | - | **CLIENT ONLY** |
| https_cert | certificate path for HTTPS | - | **CLIENT ONLY** |
| acl | enable access control list or not | false | **SERVER ONLY** |
| acl_conf | access control list configuration file | - | **SERVER ONLY**, see below |
| timeout | timeout for each connection | 600 | in seconds |
| mux | enable multiplexing or not | false | - |
| mux_concurrency | the max mux connection established between client and server | 10 | **CLIENT ONLY** |
| redirect | target address to redirect when preset fail to process | "" | **SERVER ONLY** `<host>:<port>` |
| dns | a list of DNS server IPs | [] | - |
| 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
@ -105,12 +111,12 @@ $ blinksocks init
The `<protocol>` should be:
* On client side: `tcp`, `socks`/`socks5`/`socks4`/`socks4a` or `http`/`https`.
* On server side: `tcp`, `tls` or `ws`.
* On client side: `tcp`, `socks`/`socks5`/`socks4`/`socks4a`, `http` or `https`.
* On server side: `tcp`, `tls`, `ws` or `wss`.
#### 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
@ -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].
### 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
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**.
### 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
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
[presets]: ../presets
[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
```
## 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?
Here is a [list](./RECOMMENDATIONS.md) of recommended conbinations.
Here is a [list](../examples) of recommended conbinations.
[base-auth]: ../../src/presets/base-auth.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.
## 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
### Using pm2
@ -117,25 +134,6 @@ WantedBy=multi-user.target
# 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
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", {
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');
@ -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 MUX_NEW_CONN = exports.MUX_NEW_CONN = 'MUX_NEW_CONN';
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 _constants = require('../constants');
var _presets = require('../presets');
var _defs = require('../presets/defs');
@ -62,17 +64,22 @@ class Config {
this.local_search_params = null;
this.local_host = null;
this.local_port = null;
this.local_pathname = null;
this.server = null;
this.is_client = null;
this.is_server = null;
this.https_key = null;
this.https_cert = null;
this.timeout = null;
this.redirect = null;
this.dns_expire = null;
this.dns = null;
this.transport = null;
this.server_protocol = null;
this.server_host = null;
this.server_port = null;
this.server_pathname = null;
this.tls_cert = null;
this.tls_cert_self_signed = false;
this.tls_key = null;
this.key = null;
this.acl = false;
@ -81,20 +88,21 @@ class Config {
this.acl_tries = {};
this.presets = null;
this.udp_presets = null;
this.mux = null;
this.mux = false;
this.mux_concurrency = null;
this.log_path = null;
this.log_level = null;
this.log_max_days = null;
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_username = username;
this.local_password = password;
this.local_search_params = searchParams;
this.local_host = hostname;
this.local_port = +port;
this.local_port = +port || +_constants.PROTOCOL_DEFAULT_PORTS[protocol];
this.local_pathname = pathname;
let server;
@ -121,6 +129,13 @@ class Config {
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.dns_expire = json.dns_expire !== undefined ? json.dns_expire * 1e3 : _utils.DNS_DEFAULT_EXPIRE;
@ -133,14 +148,20 @@ class Config {
}
_initServer(server) {
const { protocol, hostname, port } = new _url.URL(server.service);
this.transport = protocol.slice(0, -1);
const { protocol, hostname, port, pathname } = new _url.URL(server.service);
this.server_protocol = protocol.slice(0, -1);
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') {
_utils.logger.info(`[config] loading ${server.tls_cert}`);
this.tls_cert = loadFileSync(server.tls_cert);
if (this.server_protocol === 'tls' || this.server_protocol === 'wss') {
if (this.is_client) {
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) {
_utils.logger.info(`[config] loading ${server.tls_key}`);
this.tls_key = loadFileSync(server.tls_key);
@ -164,7 +185,7 @@ class Config {
this.redirect = server.redirect;
}
this.mux = !!server.mux;
this.mux = server.mux === true;
if (this.is_client) {
this.mux_concurrency = server.mux_concurrency || 10;
}
@ -226,15 +247,15 @@ class Config {
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');
}
const protocol = _protocol.slice(0, -1);
const proto = protocol.slice(0, -1);
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(', ')}`);
}
@ -242,11 +263,12 @@ class Config {
throw Error('service.host is invalid');
}
const port = _port || _constants.PROTOCOL_DEFAULT_PORTS[protocol] || '';
if (!(0, _utils.isValidPort)(+port)) {
throw Error('service.port is invalid');
}
if (protocol === 'tcp') {
if (proto === 'tcp') {
const forward = searchParams.get('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;
if (json.servers) {
@ -308,23 +339,29 @@ class Config {
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');
}
const protocol = _protocol.slice(0, -1);
const available_server_protocols = ['tcp', 'ws', 'tls'];
if (!available_server_protocols.includes(protocol)) {
const proto = protocol.slice(0, -1);
const available_server_protocols = ['tcp', 'ws', 'wss', 'tls'];
if (!available_server_protocols.includes(proto)) {
throw Error(`service.protocol must be: ${available_server_protocols.join(', ')}`);
}
if (protocol === 'tls') {
if (typeof server.tls_cert !== 'string' || server.tls_cert === '') {
throw Error('"tls_cert" must be provided');
if (proto === 'tls' || proto === 'wss') {
if (from_client && server.tls_cert_self_signed) {
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 (typeof server.tls_cert !== 'string' || server.tls_cert === '') {
throw Error('"tls_cert" must be provided');
}
if (typeof server.tls_key !== 'string' || server.tls_key === '') {
throw Error('"tls_key" must be provided');
}
@ -335,6 +372,7 @@ class Config {
throw Error('service.host is invalid');
}
const port = _port || _constants.PROTOCOL_DEFAULT_PORTS[protocol] || '';
if (!(0, _utils.isValidPort)(+port)) {
throw Error('service.port is invalid');
}

@ -19,6 +19,14 @@ var _net = require('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 _tls = require('tls');
@ -43,6 +51,8 @@ var _relay = require('./relay');
var _muxRelay = require('./mux-relay');
var _speedTester = require('./speed-tester');
var _utils = require('../utils');
var _proxies = require('../proxies');
@ -67,14 +77,23 @@ class Hub {
this._tcpRelays = new Map();
this._muxRelays = new Map();
this._udpRelays = null;
this._prevHrtime = process.hrtime();
this._upSpeedTester = null;
this._dlSpeedTester = null;
this._totalRead = 0;
this._totalWritten = 0;
this._prevTotalRead = 0;
this._prevTotalWritten = 0;
this._connQueue = [];
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) => {
_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('_connect', targetAddress => updateConnStatus('target', targetAddress));
relay.on('_read', size => this._totalRead += size);
relay.on('_write', size => this._totalWritten += size);
relay.on('_read', this._onRead);
relay.on('_write', this._onWrite);
relay.on('close', () => {
updateConnStatus('close');
this._tcpRelays.delete(relay.id);
@ -130,6 +149,8 @@ class Hub {
this._config = new _config.Config(config);
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() {
@ -190,21 +211,11 @@ class Hub {
}
getUploadSpeed() {
const [sec, nano] = process.hrtime(this._prevHrtime);
const totalWritten = this._totalWritten;
const diff = totalWritten - this._prevTotalWritten;
const speed = Math.ceil(diff / (sec + nano / 1e9));
this._prevTotalWritten = totalWritten;
return speed;
return this._upSpeedTester.getSpeed();
}
getDownloadSpeed() {
const [sec, nano] = process.hrtime(this._prevHrtime);
const totalRead = this._totalRead;
const diff = totalRead - this._prevTotalRead;
const speed = Math.ceil(diff / (sec + nano / 1e9));
this._prevTotalRead = totalRead;
return speed;
return this._dlSpeedTester.getSpeed();
}
getConnStatuses() {
@ -227,6 +238,7 @@ class Hub {
return new Promise((resolve, reject) => {
const { local_protocol, local_search_params, local_host, local_port } = this._config;
const { local_username: username, local_password: password } = this._config;
const { https_key, https_cert } = this._config;
let server = null;
switch (local_protocol) {
case 'tcp':
@ -242,11 +254,22 @@ class Hub {
case 'socks5':
case 'socks4':
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;
case 'http':
case 'https':
server = _proxies.http.createServer({ username, password });
server = _proxies.http.createServer({
secure: local_protocol === 'https',
https_key,
https_cert,
username,
password
});
break;
default:
return reject(Error(`unsupported protocol: "${local_protocol}"`));
@ -266,18 +289,19 @@ class Hub {
}
async _createServerOnServer() {
const { local_protocol, local_host, local_port, local_pathname, tls_key, tls_cert } = this._config;
return new Promise((resolve, reject) => {
const address = {
host: this._config.local_host,
port: this._config.local_port
host: local_host,
port: local_port
};
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}`);
resolve(server);
};
let server = null;
switch (this._config.local_protocol) {
switch (local_protocol) {
case 'tcp':
{
server = _net2.default.createServer();
@ -285,29 +309,38 @@ class Hub {
server.listen(address, () => onListening(server));
break;
}
case 'wss':
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
}));
});
server.getConnections = server._server.getConnections.bind(server._server);
server.on('connection', (ws, req) => {
ws.remoteAddress = req.connection.remoteAddress;
ws.remotePort = req.connection.remotePort;
this._onConnection(ws);
});
server.on('listening', () => onListening(server));
http_s_server.listen(address, () => onListening(http_s_server));
break;
}
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.listen(address, () => onListening(server));
break;
}
default:
return reject(Error(`unsupported protocol: "${this._config.local_protocol}"`));
return reject(Error(`unsupported protocol: "${local_protocol}"`));
}
server.on('error', reject);
});
@ -385,8 +418,8 @@ class Hub {
muxRelay = this._createRelay(context, true);
muxRelay.on('_error', err => updateConnStatus('error', err.message));
muxRelay.on('_connect', targetAddress => updateConnStatus('target', targetAddress));
muxRelay.on('_read', size => this._totalRead += size);
muxRelay.on('_write', size => this._totalWritten += size);
muxRelay.on('_read', this._onRead);
muxRelay.on('_write', this._onWrite);
muxRelay.on('close', () => {
updateConnStatus('close');
this._muxRelays.delete(muxRelay.id);
@ -411,9 +444,9 @@ class Hub {
_createRelay(context, isMux = false) {
const props = {
config: this._config,
context: context,
transport: this._config.transport,
config: this._config,
transport: this._config.server_protocol,
presets: this._config.presets
};
if (isMux) {

@ -26,40 +26,4 @@ Object.keys(_hub).forEach(function (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],
'tls': [_transports.TlsInbound, _transports.TlsOutbound],
'ws': [_transports.WsInbound, _transports.WsOutbound],
'wss': [_transports.WssInbound, _transports.WssOutbound],
'mux': [_transports.MuxInbound, _transports.MuxOutbound]
};
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", {
value: true
});
exports.presets = undefined;
exports.builtInPresetMap = undefined;
exports.getPresetClassByName = getPresetClassByName;
var _mux = require('./_mux');
@ -64,7 +64,20 @@ var _aeadRandomCipher2 = _interopRequireDefault(_aeadRandomCipher);
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,
'base-auth': _baseAuth2.default,
@ -87,24 +100,8 @@ const presetMap = {
'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) {
let clazz = presetMap[name];
let clazz = builtInPresetMap[name];
if (clazz === undefined) {
try {
clazz = require(name);
@ -119,6 +116,4 @@ function getPresetClassByName(name, allowPrivate = false) {
throw Error(`cannot load private preset "${name}"`);
}
return clazz;
}
const presets = exports.presets = Object.keys(presetMap);
}

@ -11,6 +11,10 @@ var _http = require('http');
var _http2 = _interopRequireDefault(_http);
var _https = require('https');
var _https2 = _interopRequireDefault(_https);
var _utils = require('../utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@ -27,8 +31,16 @@ function checkBasicAuthorization(credentials, { username, password }) {
return true;
}
function createServer({ username, password }) {
const server = _http2.default.createServer();
function createServer({ secure, https_key, https_cert, username, password }) {
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) => {
const { hostname, port, pathname } = new _url.URL(req.url);
@ -39,15 +51,15 @@ function createServer({ username, password }) {
if (hostname === null || !(0, _utils.isValidPort)(_port)) {
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();
}
const proxyAuth = headers['proxy-authorization'];
if (proxyAuth && username !== '' && password !== '') {
if (isAuthRequired) {
const proxyAuth = headers['proxy-authorization'] || '';
const [type, credentials] = proxyAuth.split(' ');
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');
}
}
@ -82,15 +94,15 @@ function createServer({ username, password }) {
if (hostname === null || !(0, _utils.isValidPort)(port)) {
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();
}
const proxyAuth = req.headers['proxy-authorization'];
if (proxyAuth && username !== '' && password !== '') {
if (isAuthRequired) {
const proxyAuth = req.headers['proxy-authorization'] || '';
const [type, credentials] = proxyAuth.split(' ');
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');
}
}
@ -105,8 +117,8 @@ function createServer({ username, password }) {
});
server.on('clientError', (err, socket) => {
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`;
_utils.logger.error(`[http] [${appAddress}] invalid http request: ${err.message}`);
const appAddress = `${socket.remoteAddress || ''}:${socket.remotePort || ''}`;
_utils.logger.error(`[${name}] [${appAddress}] invalid http request: ${err.message}`);
socket.destroy();
});

@ -220,6 +220,7 @@ const STAGE_DONE = 3;
function createServer({ bindAddress, bindPort, username, password }) {
const server = _net2.default.createServer();
const isAuthRequired = username !== '' && password !== '';
server.on('connection', socket => {
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`;
@ -240,6 +241,11 @@ function createServer({ bindAddress, bindPort, username, password }) {
const { method } = request;
switch (method) {
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;
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) {
request = parseSocks5InitialNegotiation(buffer);
if (request !== null) {
if (username !== '' && password !== '') {
if (isAuthRequired) {
if (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]));

@ -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');
Object.keys(_mux).forEach(function (key) {

@ -332,7 +332,11 @@ class TcpOutbound extends _defs.Outbound {
await this.connect({ host, port });
}
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', () => {
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) {
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('end', this.onHalfClose);
this._socket.on('close', this.onClose);

@ -40,7 +40,11 @@ class TlsOutbound extends _tcp.TcpOutbound {
async _connect({ 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;
}
async _connect({ host, port }) {
_utils.logger.info(`[${this.name}] [${this.remote}] connecting to ws://${host}:${port}`);
const socket = new _ws2.default(`ws://${host}:${port}`, { perMessageDeflate: false });
async _connect(target) {
const address = this.getConnAddress(target);
_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('close', () => socket.destroyed = true);
return patchWebsocket.call(this, socket);
}
getConnAddress({ host, port, pathname }) {
return `ws://${host}:${port}` + (pathname ? pathname : '');
}
getConnOptions(options) {
return options;
}
}
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",
"version": "3.2.0",
"version": "3.3.1",
"description": "A framework for building composable proxy protocol stack",
"main": "lib/index.js",
"files": [
@ -13,17 +13,15 @@
"scripts": {
"test": "npm run lint && npm run test:coverage",
"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__",
"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: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",
"server": "cross-env NODE_ENV=production node bin/cli.js blinksocks.server.json",
"prebenchmark": "npm run compile",
"benchmark": "node benchmark/bootstrap.js"
"benchmark": "node benchmark/bootstrap.js",
"prepublishOnly": "npm run compile"
},
"dependencies": {
"chalk": "^2.4.1",
@ -34,30 +32,28 @@
"lodash.isplainobject": "^4.0.6",
"lodash.uniqueid": "^4.0.1",
"long": "^4.0.0",
"lru-cache": "^4.1.2",
"winston": "^3.0.0-rc4",
"winston-daily-rotate-file": "^3.1.3",
"ws": "^5.1.1"
"lru-cache": "^4.1.3",
"winston": "^3.0.0",
"winston-daily-rotate-file": "^3.2.1",
"ws": "^5.2.0"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.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-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-env": "^1.7.0",
"babel-register": "^6.26.0",
"cross-env": "^5.1.4",
"cross-env": "^5.2.0",
"eslint": "^4.19.1",
"eslint-config-babel": "^7.0.2",
"eslint-plugin-babel": "^5.1.0",
"eslint-plugin-flowtype": "^2.46.3",
"jest": "^22.4.3",
"eslint-plugin-flowtype": "^2.49.3",
"jest": "^23.1.0",
"lodash.clonedeep": "^4.5.0",
"mkdirp": "^0.5.1",
"pkg": "^4.3.1",
"rimraf": "^2.6.2",
"socks": "^2.2.0"
},
"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_DATA_FRAME = 'MUX_DATA_FRAME';
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 isPlainObject from 'lodash.isplainobject';
import { ACL } from './acl';
import { PROTOCOL_DEFAULT_PORTS } from '../constants';
import { getPresetClassByName } from '../presets';
import { IPresetAddressing } from '../presets/defs';
import { DNSCache, isValidHostname, isValidPort, logger, DNS_DEFAULT_EXPIRE } from '../utils';
@ -24,21 +25,28 @@ export class Config {
local_search_params = null;
local_host = null;
local_port = null;
local_pathname = null;
server = null;
is_client = null;
is_server = null;
https_key = null;
https_cert = null;
timeout = null;
redirect = null;
dns_expire = null;
dns = null;
transport = null;
server_protocol = null;
server_host = null;
server_port = null;
server_pathname = null;
tls_cert = null;
tls_cert_self_signed = false;
tls_key = null;
key = null;
@ -62,14 +70,17 @@ export class Config {
stores = [];
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_username = username;
this.local_password = password;
this.local_search_params = searchParams;
this.local_host = hostname;
this.local_port = +port;
this.local_port = +port || +PROTOCOL_DEFAULT_PORTS[protocol];
this.local_pathname = pathname;
// server
let server;
// TODO(remove in next version): make backwards compatibility to "json.servers"
if (json.servers !== undefined) {
@ -78,7 +89,7 @@ export class Config {
chalk.bgYellowBright('WARN'),
'"servers" will be deprecated in the next version,' +
' please configure only one server in "server: {...}",' +
' for migration guide please refer to CHANGELOG.md.'
' for migration guide please refer to CHANGELOG.md.',
);
} else {
server = json.server;
@ -100,8 +111,15 @@ export class Config {
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.dns_expire = (json.dns_expire !== undefined) ? json.dns_expire * 1e3 : DNS_DEFAULT_EXPIRE;
@ -117,18 +135,22 @@ export class Config {
_initServer(server) {
// service
const { protocol, hostname, port } = new URL(server.service);
this.transport = protocol.slice(0, -1);
const { protocol, hostname, port, pathname } = new URL(server.service);
this.server_protocol = protocol.slice(0, -1);
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
if (this.transport === 'tls' || this.transport === 'h2') {
if (server.tls_cert) {
// preload tls_cert or tls_key
if (['tls', 'wss', 'h2'].includes(this.server_protocol)) {
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}`);
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}`);
this.tls_key = loadFileSync(server.tls_key);
}
@ -164,7 +186,7 @@ export class Config {
// remove unnecessary presets
if (this.mux) {
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.
// colorize(),
prettyPrint(),
printf((info) => `${info.timestamp} - ${info.level}: ${info.message}`)
printf((info) => `${info.timestamp} - ${info.level}: ${info.message}`),
),
transports: trans,
});
@ -233,19 +255,18 @@ export class Config {
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
if (typeof _protocol !== 'string') {
if (typeof protocol !== 'string') {
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'
'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(', ')}`);
}
@ -255,12 +276,13 @@ export class Config {
}
// service.port
const port = _port || PROTOCOL_DEFAULT_PORTS[protocol] || '';
if (!isValidPort(+port)) {
throw Error('service.port is invalid');
}
// service.query
if (protocol === 'tcp') {
if (proto === 'tcp') {
const forward = searchParams.get('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
let server;
// 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]"');
}
const { protocol: _protocol, hostname, port } = new URL(server.service);
const { protocol, hostname, port: _port } = new URL(server.service);
// service.protocol
if (typeof _protocol !== 'string') {
if (typeof protocol !== 'string') {
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', '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(', ')}`);
}
// tls_cert, tls_key
if (protocol === 'tls' || protocol === 'h2') {
if (typeof server.tls_cert !== 'string' || server.tls_cert === '') {
throw Error('"tls_cert" must be provided');
if (['tls', 'wss', 'h2'].includes(proto)) {
if (from_client && server.tls_cert_self_signed) {
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 (typeof server.tls_cert !== 'string' || server.tls_cert === '') {
throw Error('"tls_cert" must be provided');
}
if (typeof server.tls_key !== 'string' || server.tls_key === '') {
throw Error('"tls_key" must be provided');
}
@ -362,6 +400,7 @@ export class Config {
}
// service.port
const port = _port || PROTOCOL_DEFAULT_PORTS[protocol] || '';
if (!isValidPort(+port)) {
throw Error('service.port is invalid');
}

@ -1,6 +1,8 @@
import _sodium from 'libsodium-wrappers';
import dgram from 'dgram';
import net from 'net';
import http from 'http';
import https from 'https';
import { URL } from 'url';
import http2 from 'http2';
import tls from 'tls';
@ -12,7 +14,7 @@ import { Relay } from './relay';
import { MuxRelay } from './mux-relay';
import { SpeedTester } from './speed-tester';
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';
export const MAX_CONNECTIONS = 50;
@ -149,6 +151,7 @@ export class Hub {
return new Promise((resolve, reject) => {
const { local_protocol, local_search_params, local_host, local_port } = this._config;
const { local_username: username, local_password: password } = this._config;
const { https_key, https_cert } = this._config;
let server = null;
switch (local_protocol) {
case 'tcp': {
@ -163,11 +166,22 @@ export class Hub {
case 'socks5':
case 'socks4':
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;
case 'http':
case 'https':
server = http.createServer({ username, password });
server = httpProxy.createServer({
secure: local_protocol === 'https',
https_key,
https_cert,
username,
password,
});
break;
default:
return reject(Error(`unsupported protocol: "${local_protocol}"`));
@ -187,14 +201,14 @@ export class Hub {
}
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) => {
const address = {
host: local_host,
port: local_port,
};
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}`);
resolve(server);
};
@ -206,9 +220,17 @@ export class Hub {
server.listen(address, () => onListening(server));
break;
}
case 'wss':
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({
...address,
server: http_s_server,
path: local_pathname,
perMessageDeflate: false,
});
server.getConnections = server._server.getConnections.bind(server._server);
@ -217,7 +239,7 @@ export class Hub {
ws.remotePort = req.connection.remotePort;
this._onConnection(ws);
});
server.on('listening', () => onListening(server));
http_s_server.listen(address, () => onListening(http_s_server));
break;
}
case 'tls': {
@ -423,9 +445,9 @@ export class Hub {
_createRelay(context, isMux = false) {
const props = {
config: this._config,
context: context,
transport: this._config.transport,
config: this._config,
transport: this._config.server_protocol,
presets: this._config.presets,
};
if (isMux) {

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

@ -26,7 +26,29 @@ import ObfsTls12TicketPreset from './obfs-tls1.2-ticket';
// others
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
'mux': MuxPreset,
@ -56,35 +78,12 @@ const presetMap = {
'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) {
let clazz = presetMap[name];
// load from built-in
let clazz = builtInPresetMap[name];
if (clazz === undefined) {
try {
// load from external
clazz = require(name);
} catch (err) {
throw Error(`cannot load preset "${name}" from built-in modules or external`);
@ -98,5 +97,3 @@ export function getPresetClassByName(name, allowPrivate = false) {
}
return clazz;
}
export const presets = Object.keys(presetMap);

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

@ -287,6 +287,7 @@ const STAGE_DONE = 3;
export function createServer({ bindAddress, bindPort, username, password }) {
const server = net.createServer();
const isAuthRequired = username !== '' && password !== '';
server.on('connection', (socket) => {
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`;
@ -308,6 +309,11 @@ export function createServer({ bindAddress, bindPort, username, password }) {
const { method } = request;
switch (method) {
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;
// Socks5 Select Message
socket.write(Buffer.from([SOCKS_VERSION_V5, METHOD_NO_AUTH]));
@ -347,7 +353,7 @@ export function createServer({ bindAddress, bindPort, username, password }) {
request = parseSocks5InitialNegotiation(buffer);
if (request !== null) {
// Username/Password Authentication
if (username !== '' && password !== '') {
if (isAuthRequired) {
if (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]));

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

@ -343,7 +343,11 @@ export class TcpOutbound extends Outbound {
await this.connect({ host, port });
}
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', () => {
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
if (this._socket && !this._socket.destroyed) {
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('end', this.onHalfClose);
this._socket.on('close', this.onClose);

@ -29,7 +29,11 @@ export class TlsOutbound extends TcpOutbound {
// overwrite _connect of tcp outbound using tls.connect()
async _connect({ 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, {
compress: false,
mask: false,
fin: true // send data out immediately
fin: true, // send data out immediately
}, () => this.emit('drain'));
ws.end = () => ws.close();
ws.destroy = () => ws.close();
@ -57,12 +57,24 @@ export class WsOutbound extends TcpOutbound {
return this._socket && this._socket.readyState === WebSocket.OPEN;
}
async _connect({ host, port }) {
logger.info(`[${this.name}] [${this.remote}] connecting to ws://${host}:${port}`);
const socket = new WebSocket(`ws://${host}:${port}`, { perMessageDeflate: false });
async _connect(target) {
const address = this.getConnAddress(target);
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('close', () => socket.destroyed = true);
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': [
{ 'name': 'ss-base' },
{ '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 = {
@ -21,11 +21,11 @@ const serverJson = {
'presets': [
{ 'name': 'ss-base' },
{ '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_conf': path.resolve(__dirname, 'resources', 'acl.txt'),
'log_level': 'debug'
'log_level': 'debug',
};
test('acl', async () => await run({ clientJson, serverJson, not: true }));

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,33 +1,43 @@
import path from 'path';
import clone from 'lodash.clonedeep';
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": "tls://localhost:1082",
"key": "9{*2gdBSdCrgnSBD",
"presets": [
{ "name": "ss-base" },
{ "name": "obfs-random-padding" },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } }
const client = {
'service': 'socks5://127.0.0.1:1081',
'server': {
'service': 'tls://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': tlsCert,
'tls_cert_self_signed': false,
},
};
const serverJson = {
"service": "tls://localhost:1082",
"key": "9{*2gdBSdCrgnSBD",
"presets": [
{ "name": "ss-base" },
{ "name": "obfs-random-padding" },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } }
const server = {
'service': 'tls://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
'tls_cert': tlsCert,
'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';
const clientJson = {
"service": "socks5://127.0.0.1:1081",
"server": {
"service": "ws://127.0.0.1:1082",
"key": "9{*2gdBSdCrgnSBD",
"presets": [
{ "name": "ss-base" },
{ "name": "obfs-random-padding" },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } }
]
}
const client = {
'service': 'socks5://127.0.0.1:1081',
'server': {
'service': 'ws://127.0.0.1:1082',
'key': '9{*2gdBSdCrgnSBD',
'presets': [
{ 'name': 'ss-base' },
{ 'name': 'obfs-random-padding' },
{ 'name': 'ss-stream-cipher', 'params': { 'method': 'aes-128-ctr' } },
],
},
};
const serverJson = {
"service": "ws://127.0.0.1:1082",
"key": "9{*2gdBSdCrgnSBD",
"presets": [
{ "name": "ss-base" },
{ "name": "obfs-random-padding" },
{ "name": "ss-stream-cipher", "params": { "method": "aes-128-ctr" } }
]
const server = {
'service': 'ws://127.0.0.1:1082',
'key': '9{*2gdBSdCrgnSBD',
'presets': [
{ 'name': 'ss-base' },
{ 'name': 'obfs-random-padding' },
{ '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';
const client = {
"service": "socks5://127.0.0.1:1081",
"server": {
"service": "tcp://127.0.0.1:1082",
"key": "9{*2gdBSdCrgnSBD"
}
'service': 'socks5://127.0.0.1:1081',
'server': {
'service': 'tcp://127.0.0.1:1082',
'key': '9{*2gdBSdCrgnSBD',
},
};
const server = {
"service": "tcp://127.0.0.1:1082",
"key": "9{*2gdBSdCrgnSBD"
'service': 'tcp://127.0.0.1:1082',
'key': '9{*2gdBSdCrgnSBD',
};
test('v2ray-vmess, none', async () => {
const presets = [{
"name": "v2ray-vmess",
"params": {
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"security": "none"
}
'name': 'v2ray-vmess',
'params': {
'id': 'c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7',
'security': 'none',
},
}];
const clientJson = clone(client);
const serverJson = clone(server);
@ -33,11 +33,11 @@ test('v2ray-vmess, none', async () => {
test('v2ray-vmess, aes-128-gcm', async () => {
const presets = [{
"name": "v2ray-vmess",
"params": {
"id": "c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7",
"security": "aes-128-gcm"
}
'name': 'v2ray-vmess',
'params': {
'id': 'c2485913-4e9e-41eb-8cc5-b2e7db8d3bc7',
'security': 'aes-128-gcm',
},
}];
const clientJson = clone(client);
const serverJson = clone(server);

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

@ -30,9 +30,9 @@ describe('Config#test', () => {
service: 'tcp://127.0.0.1:1082',
key: 'abc',
presets: [{
name: 'ss-base'
}]
}
name: 'ss-base',
}],
},
};
it('should throw when ?forward is invalid', () => {
@ -96,9 +96,9 @@ describe('Config#testOnClient', () => {
service: 'tcp://127.0.0.1:1082',
key: 'abc',
presets: [{
name: 'ss-base'
}]
}
name: 'ss-base',
}],
},
};
it('should throw when service is not provided', () => {
@ -175,13 +175,13 @@ describe('Config#init', () => {
service: 'tcp://127.0.0.1:1082',
key: 'abc',
presets: [{
name: 'ss-base'
}]
name: 'ss-base',
}],
},
dns: ['8.8.8.8'],
log_level: 'warn',
log_path: 'blinksocks.log',
log_max_days: 30
log_max_days: 30,
};
it('should config set correctly', () => {
@ -205,15 +205,15 @@ describe('Config#initServer', () => {
service: 'tls://127.0.0.1:1082',
key: 'abc',
presets: [{
name: 'ss-base'
name: 'ss-base',
}],
tls_cert: 'mock_cert.pem',
tls_key: 'mock_key.pem'
tls_key: 'mock_key.pem',
};
it('should config set correctly', () => {
const config = new Config(serverConf);
expect(config.transport).toBe('tls');
expect(config.server_protocol).toBe('tls');
expect(config.tls_cert).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';
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();
});
it('should throw when getPacketLength not Function', function () {
it('should throw when getPacketLength not Function', function() {
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({
getPacketLength: () => 0
getPacketLength: () => 0,
});
expect(() => buffer.put()).toThrow();
});
it('should leave 0xff', function () {
it('should leave 0xff', function() {
const buffer = new AdvancedBuffer({
getPacketLength: (chunk) => {
return (chunk.length < 2) ? 0 : chunk.readUInt16BE(0);
}
},
});
const callback = jest.fn();
buffer.on('data', callback);
@ -39,7 +39,7 @@ describe('AdvancedBuffer#put', function () {
expect(callback).toHaveBeenCalledTimes(3);
});
it('should drop the first byte', function () {
it('should drop the first byte', function() {
let dropped = false;
const buffer = new AdvancedBuffer({
getPacketLength: (chunk) => {
@ -49,7 +49,7 @@ describe('AdvancedBuffer#put', function () {
} else {
return chunk.length > 1 ? chunk.readUInt16BE(0) : 0;
}
}
},
});
const callback = jest.fn();
buffer.on('data', callback);
@ -63,9 +63,9 @@ describe('AdvancedBuffer#put', function () {
expect(callback).toHaveBeenCalledTimes(3);
});
it('should drop buffer', function () {
it('should drop buffer', function() {
const buffer = new AdvancedBuffer({
getPacketLength: () => -1
getPacketLength: () => -1,
});
const callback = jest.fn();
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 });
buffer.put(Buffer.from([0x00]));
buffer.clear();

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