From 05619469b7b8d84866445712f32bb672bdf6e3d8 Mon Sep 17 00:00:00 2001 From: mmorrison Date: Sun, 20 Jan 2019 03:45:57 -0600 Subject: [PATCH] Add punycode support (2.0.6) --- README.md | 3 ++ lib/DnsResolver.js | 71 ++++++++++++++++++++++++++++++++++++++++++++ lib/Logger.js | 40 +++++++++++++++++++++++++ package-lock.json | 15 +++++++--- package.json | 3 +- protocols/core.js | 73 +++++++++------------------------------------- 6 files changed, 141 insertions(+), 64 deletions(-) create mode 100644 lib/DnsResolver.js create mode 100644 lib/Logger.js diff --git a/README.md b/README.md index 87519ab..8b7170e 100644 --- a/README.md +++ b/README.md @@ -429,6 +429,9 @@ as well: `--debug`, `--pretty`, `--socketTimeout 5000`, etc. Changelog --- +### 2.0.6 +* Added support for host domains requiring Punycode encoding (special characters) + ### 2.0.5 * Added support for Counter-Strike: 2D diff --git a/lib/DnsResolver.js b/lib/DnsResolver.js new file mode 100644 index 0000000..7cfdd22 --- /dev/null +++ b/lib/DnsResolver.js @@ -0,0 +1,71 @@ +const dns = require('dns'), + Logger = require('./Logger'), + util = require('util'), + dnsLookupAsync = util.promisify(dns.lookup), + dnsResolveAsync = util.promisify(dns.resolve), + punycode = require('punycode'); + +class DnsResolver { + /** + * @param {Logger} logger + */ + constructor(logger) { + this.logger = logger; + } + + isIp(host) { + return !!host.match(/\d+\.\d+\.\d+\.\d+/); + } + + /** + * Response port will only be present if srv record was involved. + * @param {string} host + * @param {string=} srvRecordPrefix + * @returns {Promise<{address:string, port:number=}>} + */ + async resolve(host, srvRecordPrefix) { + this.logger.debug("DNS Lookup: " + host); + + if(this.isIp(host)) { + this.logger.debug("Raw IP Address: " + host); + return {address: host}; + } + + const asciiForm = punycode.toASCII(host); + if (asciiForm !== host) { + this.logger.debug("Encoded punycode: " + host + " -> " + asciiForm); + host = asciiForm; + } + + if (srvRecordPrefix) { + this.logger.debug("SRV Resolve: " + srvRecordPrefix + '.' + host); + let records; + try { + records = await dnsResolveAsync(srvRecordPrefix + '.' + host, 'SRV'); + if (records.length >= 1) { + this.logger.debug("Found SRV Records: ", records); + const record = records[0]; + const srvPort = record.port; + const srvHost = record.name; + if (srvHost === host) { + throw new Error('Loop in DNS SRV records'); + } + return { + port: srvPort, + ...await this.resolve(srvHost, srvRecordPrefix) + }; + } + this.logger.debug("No SRV Record"); + } catch (e) { + this.logger.debug(e); + } + } + + this.logger.debug("Standard Resolve: " + host); + const {address,family} = await dnsLookupAsync(host); + this.logger.debug("Found address: " + address); + return {address: address}; + } +} + +module.exports = DnsResolver; diff --git a/lib/Logger.js b/lib/Logger.js new file mode 100644 index 0000000..03bdea2 --- /dev/null +++ b/lib/Logger.js @@ -0,0 +1,40 @@ +const HexUtil = require('./HexUtil'); + +class Logger { + constructor() { + this.debugEnabled = false; + } + + debug(...args) { + if (!this.debugEnabled) return; + this._print(...args); + } + + _print(...args) { + try { + const strings = this._convertArgsToStrings(...args); + console.log(...strings); + } catch(e) { + console.log("Error while logging: " + e); + } + } + + _convertArgsToStrings(...args) { + const out = []; + for (const arg of args) { + if (arg instanceof Error) { + out.push(arg.stack); + } else if (arg instanceof Buffer) { + out.push("\n" + HexUtil.debugDump(arg) + "\n"); + } else if (typeof arg == 'function') { + const result = arg.call(undefined, (...args) => out.push(...this._convertArgsToStrings(...args))); + if (result !== undefined) out.push(...this._convertArgsToStrings(result)); + } else { + out.push(arg); + } + } + return out; + } +} + +module.exports = Logger; diff --git a/package-lock.json b/package-lock.json index ad5c650..9a53515 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gamedig", - "version": "1.0.49", + "version": "2.0.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -449,9 +449,9 @@ "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.2", @@ -503,6 +503,13 @@ "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } } } } diff --git a/package.json b/package.json index f4e5767..11dd5fd 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ ], "main": "lib/index.js", "author": "Michael Morrison", - "version": "2.0.5", + "version": "2.0.6", "repository": { "type": "git", "url": "https://github.com/sonicsnes/node-gamedig.git" @@ -31,6 +31,7 @@ "long": "^2.4.0", "minimist": "^1.2.0", "moment": "^2.21.0", + "punycode": "^2.1.1", "request": "^2.88.0", "request-promise": "^4.2.2", "varint": "^4.0.1" diff --git a/protocols/core.js b/protocols/core.js index 4cc22be..e301296 100644 --- a/protocols/core.js +++ b/protocols/core.js @@ -1,13 +1,11 @@ const EventEmitter = require('events').EventEmitter, - dns = require('dns'), net = require('net'), Reader = require('../lib/reader'), HexUtil = require('../lib/HexUtil'), - util = require('util'), - dnsLookupAsync = util.promisify(dns.lookup), - dnsResolveAsync = util.promisify(dns.resolve), requestAsync = require('request-promise'), - Promises = require('../lib/Promises'); + Promises = require('../lib/Promises'), + Logger = require('../lib/Logger'), + DnsResolver = require('../lib/DnsResolver'); class Core extends EventEmitter { constructor() { @@ -17,6 +15,8 @@ class Core extends EventEmitter { this.delimiter = '\0'; this.srvRecord = null; this.abortedPromise = null; + this.logger = new Logger(); + this.dnsResolver = new DnsResolver(this.logger); // Sent to us by QueryRunner this.options = null; @@ -26,6 +26,10 @@ class Core extends EventEmitter { } async runAllAttempts() { + if (this.options.debug) { + this.logger.debugEnabled = true; + } + let result = null; let lastError = null; for (let attempt = 1; attempt <= this.options.maxAttempts; attempt++) { @@ -72,7 +76,9 @@ class Core extends EventEmitter { async runOnce() { const options = this.options; if (('host' in options) && !('address' in options)) { - options.address = await this.parseDns(options.host); + const resolved = await this.dnsResolver.resolve(options.host, this.srvRecord); + options.address = resolved.address; + if (resolved.port) options.port = resolved.port; } const state = { @@ -108,44 +114,6 @@ class Core extends EventEmitter { async run(state) {} - /** - * @param {string} host - * @returns {Promise} - */ - async parseDns(host) { - const isIp = (host) => { - return !!host.match(/\d+\.\d+\.\d+\.\d+/); - }; - const resolveStandard = async (host) => { - if(isIp(host)) return host; - this.debugLog("Standard DNS Lookup: " + host); - const {address,family} = await dnsLookupAsync(host); - this.debugLog(address); - return address; - }; - const resolveSrv = async (srv,host) => { - if(isIp(host)) return host; - this.debugLog("SRV DNS Lookup: " + srv+'.'+host); - let records; - try { - records = await dnsResolveAsync(srv + '.' + host, 'SRV'); - this.debugLog(records); - if(records.length >= 1) { - const record = records[0]; - this.options.port = record.port; - const srvhost = record.name; - return await resolveStandard(srvhost); - } - } catch(e) { - this.debugLog(e.toString()); - } - return await resolveStandard(host); - }; - - if(this.srvRecord) return await resolveSrv(this.srvRecord, host); - else return await resolveStandard(host); - } - /** Param can be a time in ms, or a promise (which will be timed) */ registerRtt(param) { if (param.then) { @@ -383,22 +351,9 @@ class Core extends EventEmitter { } } + /** @deprecated */ debugLog(...args) { - if (!this.options.debug) return; - try { - if(args[0] instanceof Buffer) { - this.debugLog(HexUtil.debugDump(args[0])); - } else if (typeof args[0] == 'function') { - const result = args[0].call(undefined, this.debugLog.bind(this)); - if (result !== undefined) { - this.debugLog(result); - } - } else { - console.log(...args); - } - } catch(e) { - console.log("Error while debug logging: " + e); - } + this.logger.debug(...args); } }