blinksocks/src/core/config.js

269 lines
7.8 KiB
JavaScript
Raw Normal View History

import dns from 'dns';
2016-12-09 14:54:58 +00:00
import fs from 'fs';
2017-08-15 15:05:32 +00:00
import path from 'path';
import os from 'os';
import net from 'net';
import isPlainObject from 'lodash.isplainobject';
import {getBehaviourClassByName, BEHAVIOUR_EVENT_ON_PRESET_FAILED, behaviourEvents} from '../behaviours';
import {getPresetClassByName} from '../presets';
import {isValidHostname, isValidPort, Logger} from '../utils';
import {DNS_DEFAULT_EXPIRE} from './dns-cache';
2016-12-09 14:54:58 +00:00
2017-08-16 02:06:54 +00:00
export const DEFAULT_LOG_LEVEL = 'info';
export const DEFAULT_BEHAVIOURS = {
[BEHAVIOUR_EVENT_ON_PRESET_FAILED]: {
2017-08-20 09:13:25 +00:00
'name': 'random-timeout',
'params': {
'min': 10,
'max': 40
}
}
};
2016-12-09 14:54:58 +00:00
export class Config {
static validate(json) {
if (!isPlainObject(json)) {
throw Error('invalid configuration file');
2016-12-09 14:54:58 +00:00
}
// host
2016-12-09 14:54:58 +00:00
if (typeof json.host !== 'string' || json.host === '') {
throw Error('\'host\' must be provided and is not empty');
}
// port
if (!isValidPort(json.port)) {
throw Error('\'port\' is invalid');
2016-12-09 14:54:58 +00:00
}
// behaviours
if (json.behaviours !== undefined) {
if (!isPlainObject(json.behaviours)) {
throw Error('\'behaviours\' is invalid');
}
const events = Object.keys(json.behaviours);
for (const event of events) {
if (!behaviourEvents.includes(event)) {
throw Error(`unrecognized behaviour event: "${event}"`);
}
const {name, params} = json.behaviours[event];
if (typeof name !== 'string') {
throw Error('\'behaviours[].name\' must be a string');
}
if (name === '') {
throw Error('\'behaviours[].name\' cannot be empty');
}
if (params !== undefined && !isPlainObject(params)) {
throw Error('\'behaviours[].params\' must be an plain object');
}
const behaviour = getBehaviourClassByName(name);
delete new behaviour(params || {});
}
}
// servers
2017-08-16 02:06:54 +00:00
if (json.servers !== undefined) {
if (!Array.isArray(json.servers)) {
2017-03-24 09:09:37 +00:00
throw Error('\'servers\' must be provided as an array');
2016-12-09 14:54:58 +00:00
}
2017-04-16 09:38:57 +00:00
const servers = json.servers.filter((server) => server.enabled === true);
if (servers.length < 1) {
throw Error('\'servers\' must have at least one enabled item');
2016-12-09 14:54:58 +00:00
}
servers.forEach(this.validateServer);
} else {
this.validateServer(json);
}
// timeout
2017-08-16 02:06:54 +00:00
if (json.timeout !== undefined) {
2017-08-15 15:05:32 +00:00
if (typeof json.timeout !== 'number') {
throw Error('\'timeout\' must be a number');
}
if (json.timeout < 1) {
throw Error('\'timeout\' must be greater than 0');
}
if (json.timeout < 60) {
console.warn(`==> [config] 'timeout' is too short, is ${json.timeout}s expected?`);
}
}
2017-08-15 15:05:32 +00:00
// log_path
2017-08-16 02:06:54 +00:00
if (json.log_path !== undefined) {
2017-08-15 15:05:32 +00:00
if (typeof json.log_path !== 'string') {
throw Error('\'log_path\' must be a string');
}
}
2017-08-15 15:05:32 +00:00
// log_level
2017-08-16 02:06:54 +00:00
if (json.log_level !== undefined) {
2017-08-15 15:05:32 +00:00
const levels = ['error', 'warn', 'info', 'verbose', 'debug', 'silly'];
if (!levels.includes(json.log_level)) {
throw Error(`'log_level' must be one of [${levels.toString()}]`);
}
}
// workers
2017-08-16 02:06:54 +00:00
if (json.workers !== undefined) {
if (typeof json.workers !== 'number') {
throw Error('\'workers\' must be a number');
}
if (json.workers < 0) {
throw Error('\'workers\' must be an integer');
}
if (json.workers > os.cpus().length) {
console.warn(`==> [config] 'workers' is greater than the number of cpus, is ${json.workers} workers expected?`);
}
}
2017-08-16 02:06:54 +00:00
// dns
if (json.dns !== undefined) {
if (!Array.isArray(json.dns)) {
throw Error('\'dns\' must be an array');
}
for (const ip of json.dns) {
if (!net.isIP(ip)) {
throw Error(`"${ip}" is not an ip address`);
}
}
}
// dns_expire
2017-08-16 02:06:54 +00:00
if (json.dns_expire !== undefined) {
if (typeof json.dns_expire !== 'number') {
throw Error('\'dns_expire\' must be a number');
}
if (json.dns_expire < 0) {
throw Error('\'dns_expire\' must be greater or equal to 0');
}
if (json.dns_expire > 24 * 60 * 60) {
console.warn(`==> [config] 'dns_expire' is too long, is ${json.dns_expire}s expected?`);
}
}
2016-12-09 14:54:58 +00:00
}
static validateServer(server) {
2017-04-20 09:20:42 +00:00
// transport
2017-08-16 02:06:54 +00:00
if (server.transport !== undefined) {
2017-08-15 15:05:32 +00:00
if (typeof server.transport !== 'string') {
throw Error('\'server.transport\' must be a string');
}
if (!['tcp', 'udp'].includes(server.transport.toLowerCase())) {
throw Error('\'server.transport\' must be one of "tcp" or "udp"');
}
2017-04-20 09:20:42 +00:00
}
// host
if (!isValidHostname(server.host)) {
throw Error('\'server.host\' is invalid');
2017-04-20 09:20:42 +00:00
}
// port
if (!isValidPort(server.port)) {
2017-04-20 09:20:42 +00:00
throw Error('\'server.port\' is invalid');
}
// key
2017-08-16 02:06:54 +00:00
if (typeof server.key !== 'string' || server.key === '') {
throw Error('\'server.key\' must be a non-empty string');
}
// presets
if (!Array.isArray(server.presets)) {
2017-04-20 09:20:42 +00:00
throw Error('\'server.presets\' must be an array');
}
if (server.presets.length < 1) {
2017-04-20 09:20:42 +00:00
throw Error('\'server.presets\' must contain at least one preset');
}
// presets[].parameters
for (const preset of server.presets) {
const {name, params} = preset;
if (typeof name !== 'string') {
2017-04-20 09:20:42 +00:00
throw Error('\'server.presets[].name\' must be a string');
}
if (name === '') {
2017-04-20 09:20:42 +00:00
throw Error('\'server.presets[].name\' cannot be empty');
}
2017-08-16 02:06:54 +00:00
if (params !== undefined) {
if (!isPlainObject(params)) {
2017-08-16 02:06:54 +00:00
throw Error('\'server.presets[].params\' must be an plain object');
}
}
// 1. check for the existence of the preset
const ps = getPresetClassByName(preset.name);
2017-08-10 14:07:15 +00:00
// 2. check parameters
delete new ps(params || {});
}
}
static init(json) {
this.validate(json);
global.__LOCAL_HOST__ = json.host;
global.__LOCAL_PORT__ = json.port;
2017-08-16 02:06:54 +00:00
if (json.servers !== undefined) {
global.__SERVERS__ = json.servers.filter((server) => server.enabled);
global.__IS_CLIENT__ = true;
} else {
global.__IS_CLIENT__ = false;
this.initServer(json);
}
global.__IS_SERVER__ = !global.__IS_CLIENT__;
2017-08-15 15:05:32 +00:00
global.__TIMEOUT__ = (json.timeout !== undefined) ? json.timeout * 1e3 : 600 * 1e3;
2017-08-14 09:39:37 +00:00
global.__WORKERS__ = (json.workers !== undefined) ? json.workers : 0;
global.__DNS_EXPIRE__ = (json.dns_expire !== undefined) ? json.dns_expire * 1e3 : DNS_DEFAULT_EXPIRE;
global.__ALL_CONFIG__ = json;
// dns
2017-08-16 02:06:54 +00:00
if (json.dns !== undefined && json.dns.length > 0) {
global.__DNS__ = json.dns;
dns.setServers(json.dns);
}
// log_path & log_level
const absolutePath = path.resolve(process.cwd(), json.log_path || '.');
const isFile = fs.statSync(absolutePath).isFile();
global.__LOG_PATH__ = isFile ? absolutePath : path.join(absolutePath, `bs-${__IS_CLIENT__ ? 'client' : 'server'}.log`);
global.__LOG_LEVEL__ = (json.log_level !== undefined) ? json.log_level : DEFAULT_LOG_LEVEL;
Logger.init({file: __LOG_PATH__, level: __LOG_LEVEL__});
// behaviours
const behaviours = {
...DEFAULT_BEHAVIOURS,
...(json.behaviours !== undefined ? json.behaviours : {})
};
const events = Object.keys(behaviours);
global.__BEHAVIOURS__ = {};
for (const ev of events) {
const clazz = getBehaviourClassByName(behaviours[ev].name);
global.__BEHAVIOURS__[ev] = new clazz(behaviours[ev].params || {});
}
}
static initServer(server) {
this.validateServer(server);
2017-08-15 15:05:32 +00:00
global.__TRANSPORT__ = (server.transport !== undefined) ? server.transport : 'tcp';
global.__SERVER_HOST__ = server.host;
global.__SERVER_PORT__ = server.port;
global.__KEY__ = server.key;
global.__PRESETS__ = server.presets;
2016-12-09 14:54:58 +00:00
}
}