presets,test: remove "auto-conf" preset

This commit is contained in:
Micooz 2018-04-15 21:45:10 +08:00
parent 100f1fe48d
commit 18a91d3b61
8 changed files with 2 additions and 402 deletions

@ -17,7 +17,6 @@ import {
CONNECT_TO_REMOTE,
CONNECTION_CLOSED,
CONNECTION_WILL_CLOSE,
CHANGE_PRESET_SUITE,
PRESET_FAILED,
} from '../presets/actions';
@ -102,12 +101,10 @@ export class Relay extends EventEmitter {
this._outbound.setInbound(this._inbound);
this._outbound.on('_error', (err) => this.emit('_error', err));
this._outbound.on('close', () => this.onBoundClose(outbound, inbound));
this._outbound.on('updatePresets', this.updatePresets);
// inbound
this._inbound.setOutbound(this._outbound);
this._inbound.on('_error', (err) => this.emit('_error', err));
this._inbound.on('close', () => this.onBoundClose(inbound, outbound));
this._inbound.on('updatePresets', this.updatePresets);
// acl
if (config.acl) {
this._acl = new ACL({ remoteInfo: this._remoteInfo, rules: config.acl_rules });
@ -202,35 +199,10 @@ export class Relay extends EventEmitter {
return;
}
}
if (action.type === CHANGE_PRESET_SUITE) {
this.onChangePresetSuite(action);
return;
}
this._inbound && this._inbound.onBroadcast(action);
this._outbound && this._outbound.onBroadcast(action);
}
onChangePresetSuite = (action) => {
const { type, suite, data } = action.payload;
logger.verbose(`[relay] changing presets suite to: ${JSON.stringify(suite)}`);
// 1. update preset list
this.updatePresets(this.preparePresets([
...suite.presets,
{ 'name': 'auto-conf' },
]));
// 2. initialize newly created presets
const proxyRequest = this._proxyRequest;
if (this._config.is_client) {
this._pipe.initTargetAddress(proxyRequest);
this.onBroadcast({
type: CONNECT_TO_REMOTE,
payload: { ...proxyRequest, keepAlive: true }, // keep previous connection alive, don't re-connect
});
}
// 3. re-pipe
this._pipe.feed(type, data);
};
onPreDecode = (buffer, cb) => {
if (this._tracker !== null) {
this._tracker.trace(PIPE_DECODE, buffer.length);
@ -294,15 +266,6 @@ export class Relay extends EventEmitter {
return presets;
}
/**
* update presets of pipe
* @param value
*/
updatePresets = (value) => {
this._presets = typeof value === 'function' ? value(this._presets) : value;
this._pipe.updatePresets(this._presets);
};
/**
* create pipes for both data forward and backward
*/

@ -60,18 +60,6 @@ export const CONNECTED_TO_REMOTE = '@action:connected_to_remote';
*/
export const PRESET_FAILED = '@action:preset_failed';
/**
* {
* type: CHANGE_PRESET_SUITE,
* payload: {
* type: <PIPE_ENCODE|PIPE_DECODE>,
* suite: [...],
* data: <Buffer>
* }
* }
*/
export const CHANGE_PRESET_SUITE = '@action:change_preset_suite';
export const MUX_NEW_CONN = '@action:mux_new_conn';
export const MUX_DATA_FRAME = '@action:mux_data_frame';
export const MUX_CLOSE_CONN = '@action:mux_close_conn';

@ -1,215 +0,0 @@
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import fetch from 'node-fetch';
import { IPreset } from './defs';
import { CHANGE_PRESET_SUITE } from './actions';
import {
logger,
hmac,
hash,
dumpHex,
numberToBuffer as ntb, BYTE_ORDER_LE,
getCurrentTimestampInt,
EVP_BytesToKey,
} from '../utils';
import { PIPE_DECODE, PIPE_ENCODE } from '../constants';
const MAX_TIME_DIFF = 30; // seconds
const NOOP = Buffer.alloc(0);
/**
* @description
* Auto configure preset suite.
*
* @notice
* This is an experimental preset, protocol can be changed at any time.
*
* @params
* suites: A json file includes a set of preset combinations.
*
* @examples
*
* // use local file
* {"name": "auto-conf", "params": {"suites": "suites.json"}}
*
* // load from remote use http(s)
* {"name": "auto-conf", "params": {"suites": "https://some.where/suites.json"}}
*
* @protocol
*
* # TCP handshake request & UDP packets (client -> server)
* +------------+-----------+------------+-------------+
* | Suite ID | UTC | HMAC-MD5 | PAYLOAD |
* +------------+-----------+------------+-------------+
* | 2 | 4 | 16 | Variable |
* +------------+-----------+------------+-------------+
* |<-- RC4 -->|
*
* # TCP chunks (client <-> server)
* +-------------+
* | PAYLOAD |
* +-------------+
* | Variable |
* +-------------+
*
* # UDP packets (client <- server)
* +-------------+
* | PAYLOAD |
* +-------------+
* | Variable |
* +-------------+
*
* @explain
* 1. Suite ID should be randomly generated and mapped to real one (Suite ID % suites.length) in the pre-shared suites.
* 2. Suite ID and UTC are little-endian.
* 3. UTC is encrypted by RC4.
* 4. HMAC-MD5 is HMAC(Suite ID + RC4(UTC)).
* 5. RC4 and HMAC-MD5 key are EVP_BytesToKey(base64(orgKey) + base64(md5(Suite ID)), 16, 16).
*/
export default class AutoConfPreset extends IPreset {
_isSuiteChanged = false;
_isHeaderSent = false;
_header = null;
static onCheckParams({ suites }) {
if (typeof suites !== 'string' || suites.length < 1) {
throw Error('\'suites\' is invalid');
}
}
static async onCache({ suites: uri }) {
logger.info(`[auto-conf] loading suites from: ${uri}`);
let suites = [];
if (uri.startsWith('http')) {
// load from remote
const res = await fetch(uri);
suites = await res.json();
} else {
// load from file system
const suiteJson = path.resolve(process.cwd(), uri);
const rawText = fs.readFileSync(suiteJson, { encoding: 'utf-8' });
suites = JSON.parse(rawText);
}
if (suites.length < 1) {
throw Error(`you must provide at least one suite in ${uri}`);
}
logger.info(`[auto-conf] ${suites.length} suites loaded`);
return { suites };
}
onDestroy() {
this._header = null;
}
createRequestHeader(suites) {
const sid = crypto.randomBytes(2);
const utc = ntb(getCurrentTimestampInt(), 4, BYTE_ORDER_LE);
const key = EVP_BytesToKey(Buffer.from(this._config.key).toString('base64') + hash('md5', sid).toString('base64'), 16, 16);
const cipher = crypto.createCipheriv('rc4', key, NOOP);
const enc_utc = cipher.update(utc);
const request_hmac = hmac('md5', key, Buffer.concat([sid, enc_utc]));
return {
header: Buffer.concat([sid, enc_utc, request_hmac]),
suite: suites[sid.readUInt16LE(0) % suites.length]
};
}
encodeChangeSuite({ buffer, broadcast, fail }) {
const { suites } = this.getStore();
if (suites.length < 1) {
return fail('suites are not initialized properly');
}
const { header, suite } = this.createRequestHeader(suites);
this._header = header;
this._isSuiteChanged = true;
return broadcast({
type: CHANGE_PRESET_SUITE,
payload: {
type: PIPE_ENCODE,
suite: suite,
data: buffer
}
});
}
decodeChangeSuite({ buffer, broadcast, fail }) {
const { suites } = this.getStore();
if (suites.length < 1) {
return fail('suites are not initialized properly');
}
if (buffer.length < 22) {
return fail(`client request is too short, dump=${dumpHex(buffer)}`);
}
const sid = buffer.slice(0, 2);
const request_hmac = buffer.slice(6, 22);
const key = EVP_BytesToKey(Buffer.from(this._config.key).toString('base64') + hash('md5', sid).toString('base64'), 16, 16);
const hmac_calc = hmac('md5', key, buffer.slice(0, 6));
if (!hmac_calc.equals(request_hmac)) {
return fail(`unexpected hmac of client request, dump=${dumpHex(buffer)}`);
}
const enc_utc = buffer.slice(2, 6);
const decipher = crypto.createDecipheriv('rc4', key, NOOP);
const utc = decipher.update(enc_utc).readUInt32LE(0);
const time_diff = Math.abs(utc - getCurrentTimestampInt());
if (time_diff > MAX_TIME_DIFF) {
return fail(`timestamp diff is over ${MAX_TIME_DIFF}s, dump=${dumpHex(buffer)}`);
}
const suite = suites[sid.readUInt16LE(0) % suites.length];
this._isSuiteChanged = true;
return broadcast({
type: CHANGE_PRESET_SUITE,
payload: {
type: PIPE_DECODE,
suite: suite,
data: buffer.slice(22)
}
});
}
// tcp
clientOut({ buffer, broadcast, fail }) {
if (!this._isSuiteChanged) {
return this.encodeChangeSuite({ buffer, broadcast, fail })
}
if (!this._isHeaderSent) {
this._isHeaderSent = true;
return Buffer.concat([this._header, buffer]);
} else {
return buffer;
}
}
serverIn({ buffer, broadcast, fail }) {
if (!this._isSuiteChanged) {
return this.decodeChangeSuite({ buffer, broadcast, fail });
} else {
return buffer;
}
}
// udp
clientOutUdp({ buffer, broadcast, fail }) {
if (!this._isSuiteChanged) {
return this.encodeChangeSuite({ buffer, broadcast, fail });
} else {
this._isSuiteChanged = false;
return Buffer.concat([this._header, buffer]);
}
}
serverInUdp({ buffer, broadcast, fail }) {
if (!this._isSuiteChanged) {
return this.decodeChangeSuite({ buffer, broadcast, fail });
} else {
this._isSuiteChanged = false;
return buffer;
}
}
}

@ -1,5 +1,4 @@
// functional
import AutoConfPreset from './auto-conf';
import MuxPreset from './mux';
// basic
@ -29,7 +28,6 @@ import AeadRandomCipherPreset from './aead-random-cipher';
const presetMap = {
// functional
'auto-conf': AutoConfPreset,
'mux': MuxPreset,
// basic

@ -1,7 +1,6 @@
/* eslint-disable no-unused-vars */
import EventEmitter from 'events';
// .on('updatePresets')
class Bound extends EventEmitter {
_ctx = null;
@ -54,10 +53,6 @@ class Bound extends EventEmitter {
}
updatePresets(value) {
this.emit('updatePresets', value);
}
broadcast(action) {
!this.ctx.pipe.destroyed && this.ctx.pipe.broadcast('pipe', action);
}

@ -173,8 +173,8 @@ export class TcpInbound extends Inbound {
logger.warn(`[${this.name}] [${this.remote}] connection is redirecting to: ${host}:${port}`);
// replace presets to tracker only
this.updatePresets([{ name: 'tracker' }]);
// clear preset list
this.ctx.pipe.updatePresets([]);
// connect to "redirect" remote
await this._outbound.connect({ host, port: +port });

@ -1,25 +0,0 @@
import path from 'path';
import run from '../common/run-e2e';
const suites = path.resolve(__dirname, 'resources', 'auto-conf-suites.json');
const clientJson = {
"service": "socks5://127.0.0.1:1081",
"server": {
"service": "tcp://127.0.0.1:1082",
"key": "9{*2gdBSdCrgnSBD",
"presets": [
{ "name": "auto-conf", "params": { "suites": suites } },
]
}
};
const serverJson = {
"service": "tcp://127.0.0.1:1082",
"key": "9{*2gdBSdCrgnSBD",
"presets": [
{ "name": "auto-conf", "params": { "suites": suites } },
]
};
test('auto-conf', async () => await run({ clientJson, serverJson, repeat: 5 }));

@ -1,104 +0,0 @@
[
{
"description": "shadowsocks stream cipher with random padding",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
]
},
{
"description": "shadowsocks aead cipher with random padding",
"presets": [
{
"name": "ss-base"
},
{
"name": "obfs-random-padding"
},
{
"name": "ss-aead-cipher",
"params": {
"method": "aes-128-gcm"
}
}
]
},
{
"description": "shadowsocksr auth_aes128_md5",
"presets": [
{
"name": "ss-base"
},
{
"name": "ssr-auth-aes128-md5"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
]
},
{
"description": "shadowsocksr auth_aes128_sha1",
"presets": [
{
"name": "ss-base"
},
{
"name": "ssr-auth-aes128-sha1"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
]
},
{
"description": "shadowsocksr auth_chain_a",
"presets": [
{
"name": "ss-base"
},
{
"name": "ssr-auth-chain-a"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
]
},
{
"description": "shadowsocksr auth_chain_b",
"presets": [
{
"name": "ss-base"
},
{
"name": "ssr-auth-chain-b"
},
{
"name": "ss-stream-cipher",
"params": {
"method": "aes-128-ctr"
}
}
]
}
]