core: re-implement ACL in core
This commit is contained in:
parent
f18868c800
commit
a60d3fcc9d
@ -74,6 +74,8 @@ module.exports = function init({ isMinimal, isOverwrite }) {
|
||||
],
|
||||
'tls_key': 'key.pem',
|
||||
'tls_cert': 'cert.pem',
|
||||
'acl': true,
|
||||
'acl_conf': 'acl.txt',
|
||||
'mux': false,
|
||||
'dns': [],
|
||||
'dns_expire': 3600,
|
||||
@ -87,6 +89,8 @@ module.exports = function init({ isMinimal, isOverwrite }) {
|
||||
if (isMinimal) {
|
||||
delete serverJson.tls_key;
|
||||
delete serverJson.tls_cert;
|
||||
delete serverJson.acl;
|
||||
delete serverJson.acl_conf;
|
||||
delete serverJson.mux;
|
||||
delete serverJson.dns;
|
||||
delete serverJson.dns_expire;
|
||||
|
366
src/core/acl.js
Normal file
366
src/core/acl.js
Normal file
@ -0,0 +1,366 @@
|
||||
import fs from 'fs';
|
||||
import net from 'net';
|
||||
import EventEmitter from 'events';
|
||||
import readline from 'readline';
|
||||
import ip from 'ip';
|
||||
import { PIPE_ENCODE } from '../constants';
|
||||
import { logger, isValidHostname, isValidPort } from '../utils';
|
||||
|
||||
export const ACL_CLOSE_CONNECTION = 'acl_close_connection';
|
||||
export const ACL_PAUSE_RECV = 'acl_pause_recv';
|
||||
export const ACL_PAUSE_SEND = 'acl_pause_send';
|
||||
export const ACL_RESUME_RECV = 'acl_resume_recv';
|
||||
export const ACL_RESUME_SEND = 'acl_resume_send';
|
||||
|
||||
// rule's methods
|
||||
|
||||
function ruleIsMatch(host, port) {
|
||||
const { host: rHost, port: rPort } = this;
|
||||
const slashIndex = rHost.indexOf('/');
|
||||
|
||||
let isHostMatch = false;
|
||||
if (slashIndex !== -1 && net.isIP(host)) {
|
||||
isHostMatch = ip.cidrSubnet(rHost).contains(host);
|
||||
} else {
|
||||
isHostMatch = (rHost === host);
|
||||
}
|
||||
|
||||
if (rHost === '*' || isHostMatch) {
|
||||
if (rPort === '*' || port === rPort) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function ruleToString() {
|
||||
return `${this.host}:${this.port} ${this.isBan ? 1 : 0} ${this.upLimit} ${this.dlLimit}`;
|
||||
}
|
||||
|
||||
// rule parsing
|
||||
|
||||
function parseHost(host) {
|
||||
const slashIndex = host.indexOf('/');
|
||||
if (slashIndex < 0) {
|
||||
if (host !== '*' && !net.isIP(host) && !isValidHostname(host)) {
|
||||
return null;
|
||||
}
|
||||
return host;
|
||||
}
|
||||
if (slashIndex < 7) {
|
||||
return null;
|
||||
}
|
||||
const parts = host.split('/');
|
||||
const ip = parts[0];
|
||||
const mask = parts[parts.length - 1];
|
||||
if (!net.isIP(ip)) {
|
||||
return null;
|
||||
}
|
||||
if (mask === '' || !Number.isInteger(+mask) || +mask < 0 || +mask > 32) {
|
||||
return null;
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
function parseSpeed(speed) {
|
||||
const regex = /^(\d+)(b|k|kb|m|mb|g|gb)$/g;
|
||||
const results = regex.exec(speed.toLowerCase());
|
||||
if (results !== null) {
|
||||
const [, num, unit] = results;
|
||||
return +num * {
|
||||
'b': 1,
|
||||
'k': 1024,
|
||||
'kb': 1024,
|
||||
'm': 1048576,
|
||||
'mb': 1048576,
|
||||
'g': 1073741824,
|
||||
'gb': 1073741824,
|
||||
}[unit];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseLine(line) {
|
||||
if (line.length > 300) {
|
||||
return null;
|
||||
}
|
||||
line = line.trim();
|
||||
if (line.length < 1) {
|
||||
return null;
|
||||
}
|
||||
if (line[0] === '#') {
|
||||
return null;
|
||||
}
|
||||
if (line.indexOf('#') > 0) {
|
||||
line = line.substr(0, line.indexOf('#'));
|
||||
}
|
||||
const [addr, ban, up, dl] = line.split(' ').filter(p => p.length > 0);
|
||||
|
||||
let _host = null;
|
||||
let _port = null;
|
||||
let _isBan = false;
|
||||
let _upLimit = '-';
|
||||
let _dlLimit = '-';
|
||||
|
||||
// [addr[/mask][:port]]
|
||||
if (addr.indexOf(':') > 0) {
|
||||
const parts = addr.split(':');
|
||||
const host = parts[0];
|
||||
const port = parts[parts.length - 1];
|
||||
_host = parseHost(host);
|
||||
if (port !== '*') {
|
||||
if (!isValidPort(+port)) {
|
||||
return null;
|
||||
}
|
||||
_port = +port;
|
||||
} else {
|
||||
_port = port;
|
||||
}
|
||||
} else {
|
||||
_host = parseHost(addr);
|
||||
_port = '*';
|
||||
}
|
||||
|
||||
if (_host === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// [ban]
|
||||
if (ban !== undefined) {
|
||||
if (ban !== '0' && ban !== '1') {
|
||||
return null;
|
||||
}
|
||||
_isBan = ban !== '0';
|
||||
}
|
||||
|
||||
// [max_upload_speed(/s)]
|
||||
if (up !== undefined && up !== '-') {
|
||||
_upLimit = parseSpeed(up);
|
||||
if (!_upLimit) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// [max_download_speed(/s)]
|
||||
if (dl !== undefined && dl !== '-') {
|
||||
_dlLimit = parseSpeed(dl);
|
||||
if (!_dlLimit) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
host: _host,
|
||||
port: _port,
|
||||
isBan: _isBan,
|
||||
upLimit: _upLimit,
|
||||
dlLimit: _dlLimit,
|
||||
isMatch: ruleIsMatch,
|
||||
toString: ruleToString,
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_TRIES = 2;
|
||||
|
||||
/**
|
||||
* Access Control List
|
||||
*
|
||||
* // acl.txt
|
||||
* # [addr[/mask][:port]] [ban] [max_upload_speed(/s)] [max_download_speed(/s)]
|
||||
*
|
||||
* example.com 1 # prevent access to example.com
|
||||
* example.com:* 1 # prevent access to example.com:*, equal to above
|
||||
* example.com:443 1 # prevent access to example.com:443 only
|
||||
* *:25 1 # prevent access to SMTP servers
|
||||
* *:* 1 # prevent all access from/to all endpoints
|
||||
* 127.0.0.1 1 # ban localhost
|
||||
* 192.168.0.0/16 1 # ban hosts in 192.168.*.*
|
||||
* 172.27.1.100 0 120K # limit upload speed to 120KB/s
|
||||
* 172.27.1.100 0 - 120K # limit download speed to 120KB/s
|
||||
* 172.27.1.100 0 120K 120K # limit upload and download speed to 120KB/s
|
||||
*/
|
||||
export class ACL extends EventEmitter {
|
||||
|
||||
_rules = [];
|
||||
|
||||
_cachedRules = {
|
||||
// <host:port>: <rule>
|
||||
};
|
||||
|
||||
_maxTries = 0;
|
||||
|
||||
// members
|
||||
|
||||
_hrTimeBegin = process.hrtime();
|
||||
|
||||
_remoteHost = null;
|
||||
|
||||
_remotePort = null;
|
||||
|
||||
_dstHost = null;
|
||||
|
||||
_dstPort = null;
|
||||
|
||||
_totalOut = 0;
|
||||
|
||||
_totalIn = 0;
|
||||
|
||||
// flags
|
||||
|
||||
_isDlPaused = false;
|
||||
|
||||
_isUpPaused = false;
|
||||
|
||||
static async loadRules(aclPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.verbose('[acl] loading access control list');
|
||||
const rs = fs.createReadStream(aclPath, { encoding: 'utf-8' });
|
||||
rs.on('error', (err) => {
|
||||
logger.warn(`[acl] fail to reload access control list: ${err.message}`);
|
||||
reject(err);
|
||||
});
|
||||
const rl = readline.createInterface({ input: rs });
|
||||
const _rules = [];
|
||||
rl.on('line', (line) => {
|
||||
const rule = parseLine(line);
|
||||
if (rule !== null) {
|
||||
_rules.push(rule);
|
||||
}
|
||||
});
|
||||
rl.on('close', () => {
|
||||
const rules = _rules.reverse();
|
||||
logger.info(`[acl] ${rules.length} rules loaded`);
|
||||
resolve(rules);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
constructor({ remoteInfo, rules, max_tries = DEFAULT_MAX_TRIES }) {
|
||||
super();
|
||||
this._remoteHost = remoteInfo.host;
|
||||
this._remotePort = remoteInfo.port;
|
||||
this._rules = rules;
|
||||
this._maxTries = max_tries;
|
||||
}
|
||||
|
||||
findRule(host, port) {
|
||||
const cacheKey = `${host}:${port}`;
|
||||
const cacheRule = this._cachedRules[cacheKey];
|
||||
if (cacheRule !== undefined) {
|
||||
return cacheRule;
|
||||
} else {
|
||||
for (const rule of this._rules) {
|
||||
if (rule.isMatch(host, port)) {
|
||||
return this._cachedRules[cacheKey] = rule;
|
||||
}
|
||||
}
|
||||
// rule not found
|
||||
return this._cachedRules[cacheKey] = null;
|
||||
}
|
||||
}
|
||||
|
||||
applyRule(rule) {
|
||||
const { isBan, upLimit, dlLimit } = rule;
|
||||
logger.debug(`[acl] [${this._remoteHost}:${this._remotePort}] apply rule: "${rule}"`);
|
||||
|
||||
// ban
|
||||
if (isBan) {
|
||||
logger.info(`[acl] baned by rule: "${rule}"`);
|
||||
this.emit('action', { type: ACL_CLOSE_CONNECTION });
|
||||
return true;
|
||||
}
|
||||
|
||||
// max_upload_speed
|
||||
if (upLimit !== '-') {
|
||||
// calculate average download speed
|
||||
const [sec, nano] = process.hrtime(this._hrTimeBegin);
|
||||
const speed = Math.ceil(this._totalIn / (sec + nano / 1e9)); // b/s
|
||||
|
||||
logger.debug(`[acl] upload speed: ${speed}b/s`);
|
||||
|
||||
if (speed > upLimit && !this._isUpPaused) {
|
||||
this._isUpPaused = true;
|
||||
|
||||
// determine timeout to resume
|
||||
const timeout = speed / upLimit * 1.1; // more 10% cost
|
||||
const direction = `[${this._remoteHost}:${this._remotePort}] -> [${this._dstHost}:${this._dstPort}]`;
|
||||
logger.info(`[acl] ${direction} upload speed exceed: ${speed}b/s > ${upLimit}b/s, pause for ${timeout}s...`);
|
||||
|
||||
this.emit('action', { type: ACL_PAUSE_RECV });
|
||||
setTimeout(() => {
|
||||
this.emit('action', { type: ACL_RESUME_RECV });
|
||||
this._isUpPaused = false;
|
||||
}, timeout * 1e3);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// max_download_speed
|
||||
if (dlLimit !== '-') {
|
||||
// calculate average download speed
|
||||
const [sec, nano] = process.hrtime(this._hrTimeBegin);
|
||||
const speed = Math.ceil(this._totalOut / (sec + nano / 1e9)); // b/s
|
||||
|
||||
logger.debug(`[acl] download speed: ${speed}b/s`);
|
||||
|
||||
if (speed > dlLimit && !this._isDlPaused) {
|
||||
this._isDlPaused = true;
|
||||
|
||||
// determine timeout to resume
|
||||
const timeout = speed / dlLimit * 1.1; // more 10% cost
|
||||
const direction = `[${this._remoteHost}:${this._remotePort}] <- [${this._dstHost}:${this._dstPort}]`;
|
||||
logger.info(`[acl] ${direction} download speed exceed: ${speed}b/s > ${dlLimit}b/s, pause for ${timeout}s...`);
|
||||
|
||||
this.emit('action', { type: ACL_PAUSE_SEND });
|
||||
setTimeout(() => {
|
||||
this.emit('action', { type: ACL_RESUME_SEND });
|
||||
this._isDlPaused = false;
|
||||
}, timeout * 1e3);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
checkRule(host, port) {
|
||||
const rule = this.findRule(host, port);
|
||||
if (rule !== null) {
|
||||
return this.applyRule(rule, host, port);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setTargetAddress({ host, port }) {
|
||||
this._dstHost = host;
|
||||
this._dstPort = port;
|
||||
this.checkRule(host, port);
|
||||
}
|
||||
|
||||
checkFailTimes(tries) {
|
||||
const host = this._remoteHost;
|
||||
const maxTries = this._maxTries;
|
||||
if (tries[host] === undefined) {
|
||||
tries[host] = 0;
|
||||
}
|
||||
if (++tries[host] >= maxTries) {
|
||||
logger.warn(`[acl] [${host}] max tries=${maxTries} exceed, ban it`);
|
||||
if (this.findRule(host, '*') === null) {
|
||||
this._rules.push(parseLine(`${host}:* 1`));
|
||||
}
|
||||
this.emit('action', { type: ACL_CLOSE_CONNECTION });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
collect(type, size) {
|
||||
if (type === PIPE_ENCODE) {
|
||||
this._totalOut += size;
|
||||
} else {
|
||||
this._totalIn += size;
|
||||
}
|
||||
this.checkRule(this._remoteHost, this._remotePort);
|
||||
this.checkRule(this._dstHost, this._dstPort);
|
||||
}
|
||||
|
||||
}
|
@ -8,7 +8,9 @@ import qs from 'qs';
|
||||
import chalk from 'chalk';
|
||||
import winston from 'winston';
|
||||
import isPlainObject from 'lodash.isplainobject';
|
||||
import { getPresetClassByName, IPresetAddressing } from '../presets';
|
||||
import { ACL } from './acl';
|
||||
import { getPresetClassByName } from '../presets';
|
||||
import { IPresetAddressing } from '../presets/defs';
|
||||
import { DNSCache, isValidHostname, isValidPort, logger, DNS_DEFAULT_EXPIRE } from '../utils';
|
||||
|
||||
function loadFileSync(file) {
|
||||
@ -30,7 +32,6 @@ export class Config {
|
||||
|
||||
timeout = null;
|
||||
redirect = null;
|
||||
workers = null;
|
||||
|
||||
dns_expire = null;
|
||||
dns = null;
|
||||
@ -42,6 +43,11 @@ export class Config {
|
||||
tls_key = null;
|
||||
key = null;
|
||||
|
||||
acl = false;
|
||||
acl_conf = '';
|
||||
acl_rules = [];
|
||||
acl_tries = {};
|
||||
|
||||
presets = null;
|
||||
udp_presets = null;
|
||||
|
||||
@ -99,9 +105,9 @@ export class Config {
|
||||
this.forward_port = +port;
|
||||
}
|
||||
|
||||
// common
|
||||
|
||||
this.timeout = (json.timeout !== undefined) ? json.timeout * 1e3 : 600 * 1e3;
|
||||
this.redirect = (json.redirect !== '') ? json.redirect : null;
|
||||
this.workers = (json.workers !== undefined) ? json.workers : 0;
|
||||
this.dns_expire = (json.dns_expire !== undefined) ? json.dns_expire * 1e3 : DNS_DEFAULT_EXPIRE;
|
||||
|
||||
// dns
|
||||
@ -135,6 +141,23 @@ export class Config {
|
||||
this.presets = server.presets;
|
||||
this.udp_presets = server.presets;
|
||||
|
||||
// acl
|
||||
if (server.acl !== undefined) {
|
||||
this.acl = server.acl;
|
||||
}
|
||||
|
||||
// acl_conf, acl_rules
|
||||
if (server.acl_conf !== undefined && server.acl) {
|
||||
this.acl_conf = server.acl_conf;
|
||||
ACL.loadRules(path.resolve(process.cwd(), server.acl_conf))
|
||||
.then((rules) => this.acl_rules = rules);
|
||||
}
|
||||
|
||||
// redirect
|
||||
if (server.redirect !== undefined) {
|
||||
this.redirect = server.redirect;
|
||||
}
|
||||
|
||||
// mux
|
||||
this.mux = !!server.mux;
|
||||
if (this.is_client) {
|
||||
@ -357,6 +380,20 @@ export class Config {
|
||||
throw Error('"server.key" must be a non-empty string');
|
||||
}
|
||||
|
||||
// acl, acl_conf
|
||||
if (!from_client && server.acl !== undefined) {
|
||||
if (typeof server.acl !== 'boolean') {
|
||||
throw Error('"server.acl" must be true or false');
|
||||
}
|
||||
if (typeof server.acl_conf !== 'string' || server.acl_conf === '') {
|
||||
throw Error('"server.acl_conf" must be a non-empty string');
|
||||
}
|
||||
const conf = path.resolve(process.cwd(), server.acl_conf);
|
||||
if (!fs.existsSync(conf)) {
|
||||
throw Error(`"server.acl_conf" "${conf}" not exist`);
|
||||
}
|
||||
}
|
||||
|
||||
// mux
|
||||
if (server.mux !== undefined) {
|
||||
if (typeof server.mux !== 'boolean') {
|
||||
@ -438,20 +475,6 @@ export class Config {
|
||||
}
|
||||
}
|
||||
|
||||
// workers
|
||||
if (common.workers !== undefined) {
|
||||
const { workers } = common;
|
||||
if (typeof workers !== 'number') {
|
||||
throw Error('"workers" must be a number');
|
||||
}
|
||||
if (workers < 0) {
|
||||
throw Error('"workers" must be an integer');
|
||||
}
|
||||
if (workers > os.cpus().length) {
|
||||
console.warn(`[config] "workers" is greater than the number of CPUs, is ${workers} workers expected?`);
|
||||
}
|
||||
}
|
||||
|
||||
// dns
|
||||
if (common.dns !== undefined) {
|
||||
const { dns } = common;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import EventEmitter from 'events';
|
||||
import { ACL } from './acl';
|
||||
import { Pipe } from './pipe';
|
||||
import { logger } from '../utils';
|
||||
import { PIPE_ENCODE, PIPE_DECODE } from '../constants';
|
||||
@ -17,6 +18,7 @@ import {
|
||||
CONNECTION_CLOSED,
|
||||
CONNECTION_WILL_CLOSE,
|
||||
CHANGE_PRESET_SUITE,
|
||||
PRESET_FAILED,
|
||||
} from '../presets/actions';
|
||||
|
||||
/**
|
||||
@ -34,8 +36,12 @@ export class Relay extends EventEmitter {
|
||||
|
||||
static idcounter = 0;
|
||||
|
||||
_config = null;
|
||||
|
||||
_id = null;
|
||||
|
||||
_acl = null;
|
||||
|
||||
_ctx = null;
|
||||
|
||||
_transport = null;
|
||||
@ -54,8 +60,6 @@ export class Relay extends EventEmitter {
|
||||
|
||||
_destroyed = false;
|
||||
|
||||
_config = null;
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
@ -67,10 +71,6 @@ export class Relay extends EventEmitter {
|
||||
|
||||
constructor({ config, transport, context, presets = [] }) {
|
||||
super();
|
||||
this.updatePresets = this.updatePresets.bind(this);
|
||||
this.onBroadcast = this.onBroadcast.bind(this);
|
||||
this.onEncoded = this.onEncoded.bind(this);
|
||||
this.onDecoded = this.onDecoded.bind(this);
|
||||
this._id = Relay.idcounter++;
|
||||
this._config = config;
|
||||
this._transport = transport;
|
||||
@ -78,6 +78,7 @@ export class Relay extends EventEmitter {
|
||||
// pipe
|
||||
this._presets = this.preparePresets(presets);
|
||||
this._pipe = this.createPipe(this._presets);
|
||||
// ctx
|
||||
this._ctx = {
|
||||
pipe: this._pipe,
|
||||
thisRelay: this,
|
||||
@ -98,6 +99,11 @@ export class Relay extends EventEmitter {
|
||||
this._inbound.setOutbound(this._outbound);
|
||||
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 });
|
||||
this._acl.on('action', this.onBroadcast);
|
||||
}
|
||||
}
|
||||
|
||||
init({ proxyRequest }) {
|
||||
@ -163,24 +169,31 @@ export class Relay extends EventEmitter {
|
||||
|
||||
// hooks of pipe
|
||||
|
||||
onBroadcast(action) {
|
||||
const type = action.type;
|
||||
if (type === CONNECT_TO_REMOTE) {
|
||||
onBroadcast = (action) => {
|
||||
if (action.type === CONNECT_TO_REMOTE) {
|
||||
const remote = `${this._remoteInfo.host}:${this._remoteInfo.port}`;
|
||||
const target = `${action.payload.host}:${action.payload.port}`;
|
||||
if (this._config.mux && this._config.is_client && this._transport !== 'udp') {
|
||||
logger.info(`[relay-${this.id}] [${remote}] request over mux-${this._ctx.muxRelay.id}: ${target}`);
|
||||
return;
|
||||
}
|
||||
if (this._acl) {
|
||||
this._acl.setTargetAddress(action.payload);
|
||||
}
|
||||
logger.info(`[relay] [${remote}] request: ${target}`);
|
||||
}
|
||||
if (type === CHANGE_PRESET_SUITE) {
|
||||
if (action.type === PRESET_FAILED) {
|
||||
if (this._acl) {
|
||||
this._acl.checkFailTimes(this._config.acl_tries);
|
||||
}
|
||||
}
|
||||
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;
|
||||
@ -207,21 +220,27 @@ export class Relay extends EventEmitter {
|
||||
this._pipe.feed(type, data);
|
||||
}
|
||||
|
||||
onEncoded(buffer) {
|
||||
onEncoded = (buffer) => {
|
||||
if (this._config.is_client) {
|
||||
this._outbound.write(buffer);
|
||||
} else {
|
||||
if (this._acl) {
|
||||
this._acl.collect(PIPE_ENCODE, buffer.length);
|
||||
}
|
||||
this._inbound.write(buffer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onDecoded(buffer) {
|
||||
onDecoded = (buffer) => {
|
||||
if (this._config.is_client) {
|
||||
this._inbound.write(buffer);
|
||||
} else {
|
||||
if (this._acl !== null) {
|
||||
this._acl.collect(PIPE_DECODE, buffer.length);
|
||||
}
|
||||
this._outbound.write(buffer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// methods
|
||||
|
||||
@ -263,10 +282,10 @@ export class Relay extends EventEmitter {
|
||||
* update presets of pipe
|
||||
* @param value
|
||||
*/
|
||||
updatePresets(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
|
||||
@ -295,7 +314,6 @@ export class Relay extends EventEmitter {
|
||||
this._remoteInfo = null;
|
||||
this._proxyRequest = null;
|
||||
this._destroyed = true;
|
||||
this._config = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,13 +83,6 @@ export const PRESET_FAILED = '@action:preset_failed';
|
||||
*/
|
||||
export const CHANGE_PRESET_SUITE = '@action:change_preset_suite';
|
||||
|
||||
export const PRESET_CLOSE_CONNECTION = '@action:preset_close_connection';
|
||||
|
||||
export const PRESET_PAUSE_RECV = '@action:preset_pause_recv';
|
||||
export const PRESET_PAUSE_SEND = '@action:preset_pause_send';
|
||||
export const PRESET_RESUME_RECV = '@action:preset_resume_recv';
|
||||
export const PRESET_RESUME_SEND = '@action:preset_resume_send';
|
||||
|
||||
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';
|
||||
|
@ -2,15 +2,19 @@ import net from 'net';
|
||||
import { Inbound, Outbound } from './defs';
|
||||
import { MAX_BUFFERED_SIZE, PIPE_ENCODE, PIPE_DECODE } from '../constants';
|
||||
import { DNSCache, logger, getRandomInt } from '../utils';
|
||||
|
||||
import {
|
||||
ACL_CLOSE_CONNECTION,
|
||||
ACL_PAUSE_RECV,
|
||||
ACL_PAUSE_SEND,
|
||||
ACL_RESUME_RECV,
|
||||
ACL_RESUME_SEND,
|
||||
} from '../core/acl';
|
||||
|
||||
import {
|
||||
CONNECT_TO_REMOTE,
|
||||
CONNECTED_TO_REMOTE,
|
||||
PRESET_FAILED,
|
||||
PRESET_CLOSE_CONNECTION,
|
||||
PRESET_PAUSE_RECV,
|
||||
PRESET_PAUSE_SEND,
|
||||
PRESET_RESUME_RECV,
|
||||
PRESET_RESUME_SEND,
|
||||
} from '../presets/actions';
|
||||
|
||||
export class TcpInbound extends Inbound {
|
||||
@ -134,14 +138,15 @@ export class TcpInbound extends Inbound {
|
||||
case PRESET_FAILED:
|
||||
this.onPresetFailed(action);
|
||||
break;
|
||||
case PRESET_CLOSE_CONNECTION:
|
||||
this.onPresetCloseConnection();
|
||||
case ACL_CLOSE_CONNECTION:
|
||||
logger.info(`[${this.name}] [${this.remote}] acl request to close connection`);
|
||||
this.close();
|
||||
break;
|
||||
case PRESET_PAUSE_RECV:
|
||||
this.onPresetPauseRecv();
|
||||
case ACL_PAUSE_RECV:
|
||||
this._socket && this._socket.pause();
|
||||
break;
|
||||
case PRESET_RESUME_RECV:
|
||||
this.onPresetResumeRecv();
|
||||
case ACL_RESUME_RECV:
|
||||
this._socket && this._socket.resume();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -183,19 +188,6 @@ export class TcpInbound extends Inbound {
|
||||
}
|
||||
}
|
||||
|
||||
onPresetCloseConnection() {
|
||||
logger.info(`[${this.name}] [${this.remote}] preset request to close connection`);
|
||||
this.close();
|
||||
}
|
||||
|
||||
onPresetPauseRecv() {
|
||||
this._config.is_server && (this._socket && this._socket.pause())
|
||||
}
|
||||
|
||||
onPresetResumeRecv() {
|
||||
this._config.is_server && (this._socket && this._socket.resume());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class TcpOutbound extends Outbound {
|
||||
@ -303,11 +295,11 @@ export class TcpOutbound extends Outbound {
|
||||
case CONNECT_TO_REMOTE:
|
||||
this.onConnectToRemote(action);
|
||||
break;
|
||||
case PRESET_PAUSE_SEND:
|
||||
this.onPresetPauseSend();
|
||||
case ACL_PAUSE_SEND:
|
||||
this._socket && this._socket.pause();
|
||||
break;
|
||||
case PRESET_RESUME_SEND:
|
||||
this.onPresetResumeSend();
|
||||
case ACL_RESUME_SEND:
|
||||
this._socket && this._socket.resume();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -344,14 +336,6 @@ export class TcpOutbound extends Outbound {
|
||||
}
|
||||
}
|
||||
|
||||
onPresetPauseSend() {
|
||||
this._config.is_server && (this._socket && this._socket.pause());
|
||||
}
|
||||
|
||||
onPresetResumeSend() {
|
||||
this._config.is_server && (this._socket && this._socket.resume());
|
||||
}
|
||||
|
||||
async connect({ host, port }) {
|
||||
// close alive connection before create a new one
|
||||
if (this._socket && !this._socket.destroyed) {
|
||||
|
@ -67,13 +67,6 @@ describe('Config#test', () => {
|
||||
expect(() => Config.test({ ...baseConf, log_max_days: 1 })).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw when workers(if provided) is invalid', () => {
|
||||
expect(() => Config.test({ ...baseConf, workers: '0' })).toThrow();
|
||||
expect(() => Config.test({ ...baseConf, workers: -1 })).toThrow();
|
||||
expect(() => Config.test({ ...baseConf, workers: 0 })).not.toThrow();
|
||||
expect(() => Config.test({ ...baseConf, workers: 100 })).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw when dns(is provided) is invalid', () => {
|
||||
expect(() => Config.test({ ...baseConf, dns: null })).toThrow();
|
||||
expect(() => Config.test({ ...baseConf, dns: [''] })).toThrow();
|
||||
@ -199,7 +192,6 @@ describe('Config#init', () => {
|
||||
expect(config.is_client).toBe(true);
|
||||
expect(config.is_server).toBe(false);
|
||||
expect(config.timeout).toBe(600 * 1e3);
|
||||
expect(config.workers).toBe(0);
|
||||
expect(config.log_level).toBe('warn');
|
||||
expect(config.log_path.endsWith('blinksocks.log')).toBe(true);
|
||||
expect(config.log_max_days).toBe(30);
|
||||
|
Loading…
Reference in New Issue
Block a user