From 77a085d44ae74ed99644359af683b1803ea592a5 Mon Sep 17 00:00:00 2001 From: Micooz Date: Sun, 11 Jun 2017 18:44:19 +0800 Subject: [PATCH] feat(core): allow to custom DNS servers close: #66 --- README.md | 1 + bin/cli-init.js | 6 ++++++ docs/config/README.md | 22 ++++++++++++++++++++++ docs/development/architecture/README.md | 1 + lib/core/config.js | 2 +- src/core/__tests__/config.test.js | 17 +++++++++++++++++ src/core/config.js | 18 ++++++++++++++++++ 7 files changed, 66 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 52d5cdd..33f8d7b 100755 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ module.exports = { {name: "ss-aead-cipher", params: {method: "aes-256-gcm", info: "ss-subkey"}} ] }], + dns: ['8.8.8.8'], timeout: 600, profile: false, watch: true, diff --git a/bin/cli-init.js b/bin/cli-init.js index 4e99bcf..2616d9e 100755 --- a/bin/cli-init.js +++ b/bin/cli-init.js @@ -68,6 +68,9 @@ const clientJs = `module.exports = { } ], + // an ip list of DNS server + dns: [], + // close inactive connection after timeout seconds timeout: 600, @@ -119,6 +122,9 @@ const serverJs = `module.exports = { } ], + // an ip list of DNS server + dns: [], + // redirect data to here once preset fail to process(server side only) // Should be formed with "host:port". redirect: "", diff --git a/docs/config/README.md b/docs/config/README.md index bda06be..95de51c 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -61,6 +61,9 @@ module.exports = { } ], + // an ip list of DNS server + dns: [], + // close inactive connection after timeout seconds timeout: 600, @@ -115,6 +118,9 @@ module.exports = { } ], + // an ip list of DNS server + dns: [], + // redirect data to here once preset fail to process(server side only) // Should be formed with "host:port". redirect: "", @@ -203,3 +209,19 @@ from client. ``` If `redirect` is not provided, connection will be closed after random seconds when server fail to process. + +## Custom DNS servers + +If you encounter **ENOTFOUND** every now and then, you would better custom dns servers via `dns` options: + +``` +{ + ... + "dns": ["8.8.8.8"] + ... +} +``` + +If no `dns` option or no ip provided in `dns`, blinksocks use system dns settings as usual. + +See: https://github.com/blinksocks/blinksocks/issues/66 diff --git a/docs/development/architecture/README.md b/docs/development/architecture/README.md index 9fd2803..f8cfa31 100755 --- a/docs/development/architecture/README.md +++ b/docs/development/architecture/README.md @@ -267,6 +267,7 @@ export default class CustomPreset extends IPreset { | \_\_SERVERS\_\_ | | \_\_KEY\_\_ | | \_\_PRESETS\_\_ | +| \_\_DNS\_\_ | | \_\_REDIRECT\_\_ | | \_\_TIMEOUT\_\_ | | \_\_LOG_LEVEL\_\_ | diff --git a/lib/core/config.js b/lib/core/config.js index b2033fc..d179eb2 100755 --- a/lib/core/config.js +++ b/lib/core/config.js @@ -1 +1 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.Config=undefined;var _fs=require('fs');var _fs2=_interopRequireDefault(_fs);var _blinksocksUtils=require('blinksocks-utils');var _constants=require('./constants');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function mkdir(dir){try{_fs2.default.lstatSync(dir)}catch(err){if(err.code==='ENOENT'){_fs2.default.mkdirSync(dir)}}}mkdir(_constants.BLINKSOCKS_DIR);mkdir(_constants.LOG_DIR);class Config{static validate(json){if(typeof json!=='object'||Array.isArray(json)){throw Error('Invalid configuration file')}if(typeof json.host!=='string'||json.host===''){throw Error('\'host\' must be provided and is not empty')}if(!(0,_blinksocksUtils.isValidPort)(json.port)){throw Error('\'port\' is invalid')}if(typeof json.servers!=='undefined'){if(!Array.isArray(json.servers)){throw Error('\'servers\' must be provided as an array')}const servers=json.servers.filter(server=>server.enabled===true);if(servers.length<1){throw Error('\'servers\' must have at least one enabled item')}servers.forEach(this.validateServer)}else{this.validateServer(json)}if(typeof json.redirect==='string'&&json.redirect!==''){const address=json.redirect.split(':');if(address.length!==2||!(0,_blinksocksUtils.isValidPort)(+address[1])){throw Error('\'redirect\' is an invalid address')}}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?`)}}static validateServer(server){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"')}if(typeof server.host!=='string'||server.host===''){throw Error('\'server.host\' must be provided and is not empty')}if(!(0,_blinksocksUtils.isValidPort)(server.port)){throw Error('\'server.port\' is invalid')}if(typeof server.key!=='string'){throw Error('\'server.key\' must be a string')}if(server.key===''){throw Error('\'server.key\' cannot be empty')}if(!Array.isArray(server.presets)){throw Error('\'server.presets\' must be an array')}if(server.presets.length<1){throw Error('\'server.presets\' must contain at least one preset')}var _iteratorNormalCompletion=true;var _didIteratorError=false;var _iteratorError=undefined;try{for(var _iterator=server.presets[Symbol.iterator](),_step;!(_iteratorNormalCompletion=(_step=_iterator.next()).done);_iteratorNormalCompletion=true){const preset=_step.value;const name=preset.name,params=preset.params;if(typeof name==='undefined'){throw Error('\'server.presets[].name\' must be a string')}if(name===''){throw Error('\'server.presets[].name\' cannot be empty')}if(typeof params!=='object'||params===null){throw Error('\'server.presets[].params\' must be an object and not null')}const ps=require(`../presets/${preset.name}`).default;if(name!==server.presets[0].name){delete new ps(params||{})}}}catch(err){_didIteratorError=true;_iteratorError=err}finally{try{if(!_iteratorNormalCompletion&&_iterator.return){_iterator.return()}}finally{if(_didIteratorError){throw _iteratorError}}}}static init(json){this.validate(json);global.__LOCAL_HOST__=json.host;global.__LOCAL_PORT__=json.port;if(typeof 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__;global.__REDIRECT__=json.redirect;global.__TIMEOUT__=json.timeout;global.__PROFILE__=!!json.profile;global.__IS_WATCH__=!!json.watch;global.__LOG_LEVEL__=json.log_level||_constants.DEFAULT_LOG_LEVEL;global.__ALL_CONFIG__=json}static initServer(server){this.validateServer(server);global.__TRANSPORT__=server.transport;global.__SERVER_HOST__=server.host;global.__SERVER_PORT__=server.port;global.__KEY__=server.key;global.__PRESETS__=server.presets}}exports.Config=Config; \ No newline at end of file +'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.Config=undefined;var _dns=require('dns');var _dns2=_interopRequireDefault(_dns);var _fs=require('fs');var _fs2=_interopRequireDefault(_fs);var _net=require('net');var _net2=_interopRequireDefault(_net);var _blinksocksUtils=require('blinksocks-utils');var _constants=require('./constants');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function mkdir(dir){try{_fs2.default.lstatSync(dir)}catch(err){if(err.code==='ENOENT'){_fs2.default.mkdirSync(dir)}}}mkdir(_constants.BLINKSOCKS_DIR);mkdir(_constants.LOG_DIR);class Config{static validate(json){if(typeof json!=='object'||Array.isArray(json)){throw Error('Invalid configuration file')}if(typeof json.host!=='string'||json.host===''){throw Error('\'host\' must be provided and is not empty')}if(!(0,_blinksocksUtils.isValidPort)(json.port)){throw Error('\'port\' is invalid')}if(typeof json.servers!=='undefined'){if(!Array.isArray(json.servers)){throw Error('\'servers\' must be provided as an array')}const servers=json.servers.filter(server=>server.enabled===true);if(servers.length<1){throw Error('\'servers\' must have at least one enabled item')}servers.forEach(this.validateServer)}else{this.validateServer(json)}if(typeof json.dns!=='undefined'){if(!Array.isArray(json.dns)){throw Error('\'dns\' must be an array')}var _iteratorNormalCompletion=true;var _didIteratorError=false;var _iteratorError=undefined;try{for(var _iterator=json.dns[Symbol.iterator](),_step;!(_iteratorNormalCompletion=(_step=_iterator.next()).done);_iteratorNormalCompletion=true){const ip=_step.value;if(!_net2.default.isIP(ip)){throw Error(`"${ip}" is not an ip address`)}}}catch(err){_didIteratorError=true;_iteratorError=err}finally{try{if(!_iteratorNormalCompletion&&_iterator.return){_iterator.return()}}finally{if(_didIteratorError){throw _iteratorError}}}}if(typeof json.redirect==='string'&&json.redirect!==''){const address=json.redirect.split(':');if(address.length!==2||!(0,_blinksocksUtils.isValidPort)(+address[1])){throw Error('\'redirect\' is an invalid address')}}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?`)}}static validateServer(server){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"')}if(typeof server.host!=='string'||server.host===''){throw Error('\'server.host\' must be provided and is not empty')}if(!(0,_blinksocksUtils.isValidPort)(server.port)){throw Error('\'server.port\' is invalid')}if(typeof server.key!=='string'){throw Error('\'server.key\' must be a string')}if(server.key===''){throw Error('\'server.key\' cannot be empty')}if(!Array.isArray(server.presets)){throw Error('\'server.presets\' must be an array')}if(server.presets.length<1){throw Error('\'server.presets\' must contain at least one preset')}var _iteratorNormalCompletion2=true;var _didIteratorError2=false;var _iteratorError2=undefined;try{for(var _iterator2=server.presets[Symbol.iterator](),_step2;!(_iteratorNormalCompletion2=(_step2=_iterator2.next()).done);_iteratorNormalCompletion2=true){const preset=_step2.value;const name=preset.name,params=preset.params;if(typeof name==='undefined'){throw Error('\'server.presets[].name\' must be a string')}if(name===''){throw Error('\'server.presets[].name\' cannot be empty')}if(typeof params!=='object'||params===null){throw Error('\'server.presets[].params\' must be an object and not null')}const ps=require(`../presets/${preset.name}`).default;if(name!==server.presets[0].name){delete new ps(params||{})}}}catch(err){_didIteratorError2=true;_iteratorError2=err}finally{try{if(!_iteratorNormalCompletion2&&_iterator2.return){_iterator2.return()}}finally{if(_didIteratorError2){throw _iteratorError2}}}}static init(json){this.validate(json);global.__LOCAL_HOST__=json.host;global.__LOCAL_PORT__=json.port;if(typeof 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__;global.__REDIRECT__=json.redirect;global.__TIMEOUT__=json.timeout;global.__PROFILE__=!!json.profile;global.__IS_WATCH__=!!json.watch;global.__LOG_LEVEL__=json.log_level||_constants.DEFAULT_LOG_LEVEL;global.__ALL_CONFIG__=json;if(typeof json.dns!=='undefined'&&json.dns.length>0){global.__DNS__=json.dns;_dns2.default.setServers(json.dns)}}static initServer(server){this.validateServer(server);global.__TRANSPORT__=server.transport;global.__SERVER_HOST__=server.host;global.__SERVER_PORT__=server.port;global.__KEY__=server.key;global.__PRESETS__=server.presets}}exports.Config=Config; \ No newline at end of file diff --git a/src/core/__tests__/config.test.js b/src/core/__tests__/config.test.js index 73d8478..084d8fc 100644 --- a/src/core/__tests__/config.test.js +++ b/src/core/__tests__/config.test.js @@ -85,6 +85,23 @@ describe('Config#init', function () { }).not.toThrow(); }); + it('should throw when dns is provided but invalid', function () { + const conf = { + transport: 'tcp', + host: 'localhost', + port: 1080, + key: 'abc', + presets: [{name: 'ss-base', params: {}}], + timeout: 600 + }; + expect(() => Config.init({...conf, dns: null})).toThrow(); + expect(() => Config.init({...conf, dns: ['']})).toThrow(); + expect(() => Config.init({...conf, dns: ['localhost']})).toThrow(); + + expect(() => Config.init({...conf, dns: []})).not.toThrow(); + expect(() => Config.init({...conf, dns: ['8.8.8.8']})).not.toThrow(); + }); + // others it('should __IS_SERVER__ set to true, if no servers provided', function () { diff --git a/src/core/config.js b/src/core/config.js index c05d3ed..62ed751 100755 --- a/src/core/config.js +++ b/src/core/config.js @@ -1,4 +1,6 @@ +import dns from 'dns'; import fs from 'fs'; +import net from 'net'; import {isValidPort} from 'blinksocks-utils'; import {BLINKSOCKS_DIR, LOG_DIR, DEFAULT_LOG_LEVEL} from './constants'; @@ -58,6 +60,17 @@ export class Config { this.validateServer(json); } + if (typeof 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`); + } + } + } + // redirect if (typeof json.redirect === 'string' && json.redirect !== '') { const address = json.redirect.split(':'); @@ -165,6 +178,11 @@ export class Config { global.__IS_WATCH__ = !!json.watch; global.__LOG_LEVEL__ = json.log_level || DEFAULT_LOG_LEVEL; global.__ALL_CONFIG__ = json; + + if (typeof json.dns !== 'undefined' && json.dns.length > 0) { + global.__DNS__ = json.dns; + dns.setServers(json.dns); + } } static initServer(server) {