2017-08-28 14:41:45 +00:00
|
|
|
import crypto from 'crypto';
|
2018-02-17 04:19:31 +00:00
|
|
|
import { IPreset } from './defs';
|
|
|
|
import { AdvancedBuffer, getRandomChunks, numberToBuffer as ntb } from '../utils';
|
2017-08-28 14:41:45 +00:00
|
|
|
|
2017-08-29 02:54:31 +00:00
|
|
|
/**
|
|
|
|
* @description
|
|
|
|
* A simple obfuscator to significantly randomize the length of each packet.
|
|
|
|
* It can be used to prevent statistical analysis based on packet length.
|
|
|
|
*
|
|
|
|
* @examples
|
2017-10-26 09:59:09 +00:00
|
|
|
* {"name": "obfs-random-padding"}
|
2017-08-29 02:54:31 +00:00
|
|
|
*
|
|
|
|
* @protocol
|
|
|
|
*
|
|
|
|
* # TCP stream
|
|
|
|
* +-----------+-----------+-----+
|
|
|
|
* | Chunk_0 | Chunk_1 | ... |
|
|
|
|
* +-----------+-----------+-----+
|
|
|
|
* | Variable | Variable | ... |
|
|
|
|
* +-----------+-----------+-----+
|
|
|
|
*
|
2017-10-26 09:59:09 +00:00
|
|
|
* # TCP chunk_i
|
2017-08-29 02:54:31 +00:00
|
|
|
* +------------+-----------+----------+-----------+
|
|
|
|
* | PaddingLen | Padding | DataLen | Data |
|
|
|
|
* +------------+-----------+----------+-----------+
|
|
|
|
* | 1 | Variable | 2 | Variable |
|
|
|
|
* +------------+-----------+----------+-----------+
|
|
|
|
*
|
2017-10-26 09:59:09 +00:00
|
|
|
* # UDP packet
|
|
|
|
* +------------+-----------+------------+
|
|
|
|
* | PaddingLen | Padding | Data |
|
|
|
|
* +------------+-----------+------------+
|
|
|
|
* | 1 | Variable | Variable |
|
|
|
|
* +------------+-----------+------------+
|
|
|
|
*
|
2017-08-29 02:54:31 +00:00
|
|
|
* @explain
|
|
|
|
* 1. PaddingLen is randomly picked from [0, 0xFF].
|
|
|
|
* 2. PADDING is filled with random bytes.
|
|
|
|
* 3. Because DataLen occupies 2 bytes, the length of each Data is therefore limited to [0, 0xFFFF].
|
|
|
|
* 4. PaddingLen and DataLen are big-endian.
|
|
|
|
*/
|
2017-08-28 14:41:45 +00:00
|
|
|
export default class ObfsRandomPaddingPreset extends IPreset {
|
|
|
|
|
|
|
|
_adBuf = null;
|
|
|
|
|
2018-02-15 03:01:24 +00:00
|
|
|
onInit() {
|
2018-02-17 04:19:31 +00:00
|
|
|
this._adBuf = new AdvancedBuffer({ getPacketLength: this.onReceiving.bind(this) });
|
2017-08-29 02:54:31 +00:00
|
|
|
this._adBuf.on('data', this.onChunkReceived.bind(this));
|
2017-08-28 14:41:45 +00:00
|
|
|
}
|
|
|
|
|
2017-09-08 02:47:36 +00:00
|
|
|
onDestroy() {
|
|
|
|
this._adBuf.clear();
|
|
|
|
this._adBuf = null;
|
|
|
|
}
|
|
|
|
|
2017-10-26 09:59:09 +00:00
|
|
|
// tcp
|
|
|
|
|
2017-11-07 01:40:13 +00:00
|
|
|
/**
|
|
|
|
* return length of random bytes base on dataLen,
|
|
|
|
* idea is took from ssr auth_chain_a.
|
|
|
|
* @param dataLen
|
|
|
|
* @returns {Number}
|
|
|
|
*/
|
|
|
|
getRandomBytesLength(dataLen) {
|
|
|
|
if (dataLen > 1440) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
const rand = crypto.randomBytes(1)[0];
|
|
|
|
let random_bytes_len;
|
|
|
|
if (dataLen > 1300) {
|
|
|
|
random_bytes_len = rand % 31;
|
|
|
|
} else if (dataLen > 900) {
|
|
|
|
random_bytes_len = rand % 127;
|
|
|
|
} else if (dataLen > 400) {
|
|
|
|
random_bytes_len = rand % 521;
|
|
|
|
} else {
|
|
|
|
random_bytes_len = rand % 1021;
|
|
|
|
}
|
|
|
|
return random_bytes_len;
|
|
|
|
}
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
beforeOut({ buffer }) {
|
2017-10-17 02:20:15 +00:00
|
|
|
const chunks = getRandomChunks(buffer, 0x3fff, 0xffff).map((data) => {
|
2017-11-07 01:40:13 +00:00
|
|
|
const pLen = this.getRandomBytesLength(data.length);
|
2017-08-29 02:54:31 +00:00
|
|
|
const padding = crypto.randomBytes(pLen);
|
2017-11-07 01:40:13 +00:00
|
|
|
return Buffer.concat([ntb(pLen, 1), padding, ntb(data.length), data]);
|
2017-08-29 02:54:31 +00:00
|
|
|
});
|
|
|
|
return Buffer.concat(chunks);
|
2017-08-28 14:41:45 +00:00
|
|
|
}
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
beforeIn({ buffer, next }) {
|
|
|
|
this._adBuf.put(buffer, { next });
|
2017-08-28 14:41:45 +00:00
|
|
|
}
|
|
|
|
|
2017-08-29 04:50:21 +00:00
|
|
|
onReceiving(buffer) {
|
2017-08-29 02:54:31 +00:00
|
|
|
if (buffer.length < 3) {
|
2017-08-29 04:50:21 +00:00
|
|
|
return; // too short to get PaddingLen
|
2017-08-28 14:41:45 +00:00
|
|
|
}
|
2017-08-29 02:54:31 +00:00
|
|
|
const pLen = buffer[0];
|
2017-08-29 04:50:21 +00:00
|
|
|
if (buffer.length < 1 + pLen + 2) {
|
|
|
|
return; // too short to drop Padding and get DataLen
|
2017-08-29 02:54:31 +00:00
|
|
|
}
|
|
|
|
const dLen = buffer.readUInt16BE(1 + pLen);
|
|
|
|
if (buffer.length < 1 + pLen + 2 + dLen) {
|
|
|
|
return; // too short to get Data
|
2017-08-28 14:41:45 +00:00
|
|
|
}
|
2017-08-29 02:54:31 +00:00
|
|
|
return 1 + pLen + 2 + dLen;
|
2017-08-28 14:41:45 +00:00
|
|
|
}
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
onChunkReceived(chunk, { next }) {
|
2017-08-29 02:54:31 +00:00
|
|
|
const pLen = chunk[0];
|
|
|
|
next(chunk.slice(1 + pLen + 2));
|
2017-08-28 14:41:45 +00:00
|
|
|
}
|
|
|
|
|
2017-10-26 09:59:09 +00:00
|
|
|
// udp
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
beforeOutUdp({ buffer }) {
|
2017-11-07 01:40:13 +00:00
|
|
|
const pLen = crypto.randomBytes(1)[0] % 128;
|
2017-10-26 09:59:09 +00:00
|
|
|
const padding = crypto.randomBytes(pLen);
|
2017-11-07 01:40:13 +00:00
|
|
|
return Buffer.concat([ntb(pLen, 1), padding, buffer]);
|
2017-10-26 09:59:09 +00:00
|
|
|
}
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
beforeInUdp({ buffer, fail }) {
|
2017-10-26 09:59:09 +00:00
|
|
|
if (buffer.length < 1) {
|
|
|
|
return fail(`too short to get PaddingLen, len=${buffer.length} dump=${buffer.toString('hex')}`);
|
|
|
|
}
|
|
|
|
const pLen = buffer[0];
|
|
|
|
if (buffer.length < 1 + pLen) {
|
|
|
|
return fail(`too short to drop Padding, len=${buffer.length} dump=${buffer.slice(0, 60).toString('hex')}`);
|
|
|
|
}
|
|
|
|
return buffer.slice(1 + pLen);
|
|
|
|
}
|
|
|
|
|
2017-08-28 14:41:45 +00:00
|
|
|
}
|