DNSServer.js

const dns = require('native-dns');
const { UDPServer, Request } = dns;
const Promise = require('bluebird');

/**
 * A DNS proxy server that will route requests to instances in the TorPool provided.
 * @extends UDPServer
 */
class DNSServer extends UDPServer {
	/**
	 * Binds the server to a port and IP Address.
	 * 
	 * @async
	 * @param {number} port - The port to bind to.
	 * @param {string} [host="::"] - Address to bind to. Will default to :: or 0.0.0.0 if not specified.
	 * @returns {Promise}
	 * 
	*/
	async listen() {
		let args = Array.from(arguments);
		let inner_func = super.serve;

		if (!args[1])
			args[1] = null;

		return await new Promise((resolve, reject) => {
			args.push(() => {
				let args = Array.from(arguments);
				resolve.apply(args);
			});

			try {
				inner_func.apply(this, args);
			} catch (error) {
				reject(error);
			}
		});
	}

	/**
	 * Creates an instance of `DNSServer`.
	 * @param {TorPool} tor_pool - The pool of instances that will be used for requests.
	 * @param {Object} [dns_options] - Options that will be passed to the parent constructor.
	 * @param {number} dns_timeout - How long to wait before each outbound DNS request before timing out.
	 * @param {Logger} [logger] - Winston logger that will be used for logging. If not specified will disable logging.
	 */
	constructor(tor_pool, dns_options, dns_timeout, logger) {
		/**
		 * Handles an incoming DNS request.
		 * 
		 * @function handle_request
		 * @param {Request} req - Incoming DNS request.
		 * @param {Response} res - Outgoing DNS response.
		 * @private
		 */
		const handle_request = (req, res) => {
			let connect = (tor_instance) => {
				for (let question of req.question) {
					let dns_port = (tor_instance.dns_port);
					let outbound_req = Request({
						question,
						server: { address: '127.0.0.1', port: dns_port, type: 'udp' },
						timeout: this.dns_timeout
					});

					outbound_req.on('message', (err, answer) => {
						if (!err && answer) {
							for (let a of answer.answer){
								res.answer.push(a);
							}
						}
					});	

					outbound_req.on('error', (err) => {
						this.logger.error(`[dns]: an error occured while handling the request: ${err.message}`);
					});


					outbound_req.on('end', () => {
						let source = { hostname: req.address.address, port: req.address.port, proto: 'dns' };
						/**
						 * Fires when the proxy has made a connection through an instance.
						 * 
						 * @event DNSServer#instance-connection
						 * @param {TorProcess} instance - Instance that has been connected to.
						 * @param {InstanceConnectionSource} source - Details on the source of the connection.
						 */
						this.emit('instance_connection', tor_instance, source);
						this.logger.verbose(`[dns]: ${source.hostname}:${source.port} → 127.0.0.1:${dns_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }`);
						res.send();
					});	

					outbound_req.send();
				};
			};
			if (this.tor_pool.instances.length) {
				connect(this.tor_pool.next());
			}
			else {
				this.logger.debug(`[dns]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
				this.tor_pool.once('instance_created', connect);
			}
		};

		super(dns_options);
		/**
		 * Winston logger
		 * @type {Logger}
		 * @public
		 */
		this.logger = logger || require('./winston_silent_logger');

		/**
		 * The pool of instances that will be used for requests.
		 * @type {TorPool}
		 * @public
		 */
		this.tor_pool = tor_pool;

		/**
		 * Timeout for each outbound DNS request
		 * @type {number}
		 * @public
		 */
		this.dns_timeout = dns_timeout;

		this.on('request', handle_request);
	}
};

/**
 * Module that contains the {@link DNSSErver} class.
 * @module tor-router/DNSServer
 * @see DNSServer
 */
module.exports = DNSServer;