blinksocks/src/presets/ss-base.js

210 lines
5.1 KiB
JavaScript
Raw Permalink Normal View History

import net from 'net';
import ip from 'ip';
2018-02-17 04:19:31 +00:00
import { isValidHostname, numberToBuffer } from '../utils';
import { IPresetAddressing } from './defs';
const ATYP_V4 = 0x01;
const ATYP_V6 = 0x04;
const ATYP_DOMAIN = 0x03;
function getHostType(host) {
if (net.isIPv4(host)) {
return ATYP_V4;
}
if (net.isIPv6(host)) {
return ATYP_V6;
}
return ATYP_DOMAIN;
}
/**
* @description
* Deliver destination address.
*
* @examples
* {"name": "ss-base"}
*
* @protocol
*
2017-10-26 09:59:09 +00:00
* # TCP stream (client -> server)
* +------+----------+----------+----------+---------+
* | ATYP | DST.ADDR | DST.PORT | DATA | ... |
* +------+----------+----------+----------+---------+
* | 1 | Variable | 2 | Variable | ... |
* +------+----------+----------+----------+---------+
*
* # TCP chunks
* +----------+
* | DATA |
* +----------+
* | Variable |
* +----------+
*
* # UDP packet (client <-> server)
* +------+----------+----------+----------+
* | ATYP | DST.ADDR | DST.PORT | DATA |
* +------+----------+----------+----------+
* | 1 | Variable | 2 | Variable |
* +------+----------+----------+----------+
*
* @explain
* 1. ATYP is one of [0x01(ipv4), 0x03(hostname), 0x04(ipv6)].
* 2. If ATYP is 0x03, DST.ADDR[0] is len(DST.ADDR).
* 3. If ATYP is 0x04, DST.ADDR must be a 16 bytes ipv6 address.
*
* @reference
* https://shadowsocks.org/en/spec/Protocol.html
*/
export default class SsBasePreset extends IPresetAddressing {
_isConnecting = false;
_pending = Buffer.alloc(0);
_isHeaderSent = false;
2017-08-11 09:18:56 +00:00
_isHeaderRecv = false;
_atyp = ATYP_V4;
2017-06-10 08:23:03 +00:00
_host = null; // buffer
_port = null; // buffer
_headSize = 0;
get headSize() {
return this._headSize;
}
onInitTargetAddress({ host, port }) {
const type = getHostType(host);
this._atyp = type;
this._port = numberToBuffer(port);
this._host = type === ATYP_DOMAIN ? Buffer.from(host) : ip.toBuffer(host);
}
onDestroy() {
this._pending = null;
this._host = null;
this._port = null;
}
2017-10-26 09:59:09 +00:00
encodeHeader() {
const head = Buffer.from([
2017-10-26 09:59:09 +00:00
this._atyp,
...(this._atyp === ATYP_DOMAIN ? [this._host.length] : []),
...this._host,
...this._port
]);
this._headSize = head.length;
return head;
2017-10-26 09:59:09 +00:00
}
2018-02-17 04:19:31 +00:00
decodeHeader({ buffer, fail }) {
2017-10-26 09:59:09 +00:00
if (buffer.length < 7) {
return fail(`invalid length: ${buffer.length}`);
}
const atyp = buffer[0];
let addr; // string
let port; // number
let offset = 3;
switch (atyp) {
case ATYP_V4:
addr = ip.toString(buffer.slice(1, 5));
port = buffer.slice(5, 7).readUInt16BE(0);
offset += 4;
break;
case ATYP_V6:
if (buffer.length < 19) {
return fail(`invalid length: ${buffer.length}`);
}
addr = ip.toString(buffer.slice(1, 17));
port = buffer.slice(17, 19).readUInt16BE(0);
offset += 16;
break;
2018-11-03 03:22:19 +00:00
case ATYP_DOMAIN: {
2017-10-26 09:59:09 +00:00
const domainLen = buffer[1];
if (buffer.length < domainLen + 4) {
return fail(`invalid length: ${buffer.length}`);
}
addr = buffer.slice(2, 2 + domainLen).toString();
if (!isValidHostname(addr)) {
return fail(`addr=${addr} is an invalid hostname`);
}
port = buffer.slice(2 + domainLen, 4 + domainLen).readUInt16BE(0);
offset += (domainLen + 1);
break;
2018-11-03 03:22:19 +00:00
}
2017-10-26 09:59:09 +00:00
default:
return fail(`invalid atyp: ${atyp}`);
}
const data = buffer.slice(offset);
2018-02-17 04:19:31 +00:00
return { host: addr, port, data };
2017-10-26 09:59:09 +00:00
}
// tcp
2018-02-17 04:19:31 +00:00
clientOut({ buffer }) {
if (!this._isHeaderSent) {
this._isHeaderSent = true;
2017-10-26 09:59:09 +00:00
return Buffer.concat([this.encodeHeader(), buffer]);
} else {
return buffer;
}
}
serverIn({ buffer, next, fail }) {
if (!this._isHeaderRecv) {
2017-08-11 09:18:56 +00:00
// shadowsocks(python) aead cipher put [atyp][dst.addr][dst.port] into the first chunk
// we must wait onConnected() before next().
if (this._isConnecting) {
this._pending = Buffer.concat([this._pending, buffer]);
return;
}
2018-02-17 04:19:31 +00:00
const decoded = this.decodeHeader({ buffer, fail });
2017-10-26 09:59:09 +00:00
if (!decoded) {
return;
}
2018-02-17 04:19:31 +00:00
const { host, port, data } = decoded;
// notify to connect to the real server
this._isConnecting = true;
this.resolveTargetAddress({ host, port }, () => {
if (this._pending !== null) {
next(Buffer.concat([data, this._pending]));
}
this._isHeaderRecv = true;
this._isConnecting = false;
this._pending = null;
});
} else {
return buffer;
}
}
2017-10-26 09:59:09 +00:00
// udp
2018-02-17 04:19:31 +00:00
beforeOutUdp({ buffer }) {
2017-10-26 09:59:09 +00:00
return Buffer.concat([this.encodeHeader(), buffer]);
}
serverInUdp({ buffer, next, fail }) {
2018-02-17 04:19:31 +00:00
const decoded = this.decodeHeader({ buffer, fail });
2017-10-26 09:59:09 +00:00
if (!decoded) {
return;
}
2018-02-17 04:19:31 +00:00
const { host, port, data } = decoded;
this._atyp = getHostType(host);
this._host = this._atyp === ATYP_DOMAIN ? Buffer.from(host) : ip.toBuffer(host);
this._port = numberToBuffer(port);
this.resolveTargetAddress({ host, port }, () => next(data));
2017-10-26 09:59:09 +00:00
}
}