blinksocks/src/core/config.js

259 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';
2017-08-20 14:01:30 +00:00
import winston from 'winston';
import isPlainObject from 'lodash.isplainobject';
import {getPresetClassByName} from '../presets';
2017-08-20 14:01:30 +00:00
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';
2016-12-09 14:54:58 +00:00
export class Config {
2017-08-31 06:53:45 +00:00
static init(json) {
this._validate(json);
global.__LOCAL_HOST__ = json.host;
global.__LOCAL_PORT__ = json.port;
if (json.servers !== undefined) {
global.__SERVERS__ = json.servers.filter((server) => server.enabled);
global.__IS_CLIENT__ = true;
global.__IS_SERVER__ = false;
} else {
global.__IS_CLIENT__ = false;
global.__IS_SERVER__ = true;
this.initServer(json);
}
global.__TIMEOUT__ = (json.timeout !== undefined) ? json.timeout * 1e3 : 600 * 1e3;
global.__REDIRECT__ = (json.redirect !== '') ? json.redirect : null;
2017-08-31 06:53:45 +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
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 || '.');
let isFile = false;
if (fs.existsSync(absolutePath)) {
isFile = fs.statSync(absolutePath).isFile();
} else if (path.extname(absolutePath) !== '') {
isFile = true;
}
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.configure({
level: __LOG_LEVEL__,
transports: [
new (winston.transports.Console)({
colorize: true,
prettyPrint: true
}),
new (require('winston-daily-rotate-file'))({
filename: __LOG_PATH__,
level: __LOG_LEVEL__
2017-08-31 06:53:45 +00:00
})
]
});
}
static initServer(server) {
global.__TRANSPORT__ = (server.transport !== undefined) ? server.transport : 'tcp';
2017-09-19 06:04:40 +00:00
if (__TRANSPORT__ === 'tls') {
2017-08-31 06:53:45 +00:00
global.__TLS_CERT__ = fs.readFileSync(path.resolve(process.cwd(), server.tls_cert));
if (__IS_SERVER__) {
global.__TLS_KEY__ = fs.readFileSync(path.resolve(process.cwd(), server.tls_key));
}
}
global.__SERVER_HOST__ = server.host;
global.__SERVER_PORT__ = server.port;
global.__KEY__ = server.key;
global.__PRESETS__ = server.presets;
}
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
}
// 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
}
2017-08-31 06:53:45 +00:00
servers.forEach(this._validateServer);
} else {
2017-08-31 06:53:45 +00:00
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?`);
}
}
if (json.redirect !== undefined && json.redirect !== '') {
if (typeof json.redirect !== 'string') {
throw Error('\'redirect\' must be a string');
}
const parts = json.redirect.split(':');
if (parts.length !== 2) {
throw Error('\'redirect\' must be "<host or ip>:<port>"');
}
const [host, port] = parts;
if (!isValidHostname(host) && !net.isIP(host)) {
throw Error('\'redirect\' host is invalid');
}
if (!isValidPort(+port)) {
throw Error('\'redirect\' port is invalid');
}
}
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
}
2017-08-31 06:53:45 +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-09-19 06:04:40 +00:00
if (!['tcp', 'tls', 'websocket'].includes(server.transport)) {
throw Error('\'server.transport\' must be "tcp", "tls" or "websocket"');
2017-08-15 15:05:32 +00:00
}
2017-08-22 02:54:25 +00:00
if (server.transport === 'tls') {
if (typeof server.tls_cert !== 'string') {
throw Error('\'server.tls_key\' must be a string');
2017-08-21 13:09:39 +00:00
}
2017-08-22 02:54:25 +00:00
if (server.tls_cert === '') {
throw Error('\'server.tls_cert\' cannot be empty');
2017-08-21 13:09:39 +00:00
}
2017-08-15 15:05:32 +00:00
}
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');
}
}
2017-08-31 06:53:45 +00:00
// check for existence of the preset
const PresetClass = getPresetClassByName(preset.name);
PresetClass.checkParams(preset.params || {});
2017-08-21 13:09:39 +00:00
}
2016-12-09 14:54:58 +00:00
}
}