/// window["require_setup"](module); import * as dns_handler from "dns"; namespace _dns { type Lookup = (hostname: string, callback: (err: NodeJS.ErrnoException, result: R) => void) => void; type AsyncLookup = (hostname: string, timeout: number) => Promise; function make_async(fun: Lookup) : AsyncLookup { return (hostname, timeout) => { return new Promise((resolve, reject) => { const timeout_id = setTimeout(() => { reject("timeout"); }, timeout); fun(hostname, (err, result) => { clearTimeout(timeout_id); if(err) { if(err.errno as any == "ENOTFOUND" || err.errno as any == "ENODATA") { resolve(null); return; } reject(err); } else resolve(result); }) }); }; } const async_resolve_srv = make_async(dns_handler.resolveSrv); const async_resolve_cname = make_async(dns_handler.resolveCname); const async_resolve_a = make_async(dns_handler.resolve4); const async_resolve_aaaa = make_async(dns_handler.resolve6); const async_resolve_any = make_async(dns_handler.resolveAny); export interface AddressTarget { target_ip: string; target_port?: number; } export interface ResolveOptions extends dns.ResolveOptions { log?: (message: string, ...args: any[]) => void; override_port?: number; } export function supported() { return true; } export async function resolve_address(address: string, _options?: ResolveOptions) : Promise { if(address === "localhost") { return { target_ip: "localhost" }; } const options: ResolveOptions = {}; Object.assign(options, dns.default_options); Object.assign(options, _options || {}); if(options.max_depth <= 0) throw "max depth exceeded"; if(typeof(options.log) !== "function") options.log = (message, ...args) => console.debug("[DNS] " + message, ...args); const mod_options: ResolveOptions = {}; Object.assign(mod_options, options); mod_options.max_depth = mod_options.max_depth - 1; mod_options.allow_srv = false; mod_options.log = (message, ...args) => options.log(" " + message, ...args); options.log("Resolving %s", address); let response: AddressTarget; if(typeof(options.allow_aaaa) !== "boolean" || options.allow_aaaa) { const aaaa_response: string[] | undefined | null = await async_resolve_aaaa(address, options.timeout).catch(error => { options.log("AAAA record resolved unsuccessfully (%o)", error); return Promise.resolve(undefined); }) as any; if(typeof(aaaa_response) !== "undefined") { if(!aaaa_response || aaaa_response.length == 0) options.log("No AAAA records found"); else { options.log("Resolved AAAA records: %o. Returning: %s", aaaa_response, aaaa_response[0]); response = { target_ip: aaaa_response[0], target_port: options.override_port }; } } } if(!response && (typeof(options.allow_a) !== "boolean" || options.allow_a)) { const a_response: string[] | undefined | null = await async_resolve_a(address, options.timeout).catch(error => { options.log("A record resolved unsuccessfully (%o)", error); return Promise.resolve(undefined); }) as any; if(typeof(a_response) !== "undefined") { if(!a_response || a_response.length == 0) options.log("No A records found"); else { options.log("Resolved A records: %o. Returning: %s", a_response, a_response[0]); response = { target_ip: a_response[0], target_port: options.override_port }; } } } if(!response && (typeof(options.allow_any) !== "boolean" || options.allow_any)) { const any_response: dns_handler.AnyRecord[] = await async_resolve_any(address, options.timeout).catch(error => { options.log("ANY record resolved unsuccessfully (%o)", error); return Promise.resolve(undefined); }); if(typeof(any_response) !== "undefined") { if(!any_response || any_response.length == 0) options.log("No ANY records found"); else { options.log("Resolved ANY records: %o.", any_response); for(const record of any_response) { if(record.type === "A") { const a_record = record as dns_handler.AnyARecord; options.log("Returning A record from ANY query: %s", a_record.address); return { target_ip: a_record.address, target_port: options.override_port }; } else if(record.type === "AAAA") { const aaaa_record = record as dns_handler.AnyAaaaRecord; options.log("Returning AAAA record from ANY query: %s", aaaa_record.address); return { target_ip: aaaa_record.address, target_port: options.override_port }; } } } } } if(typeof(options.allow_srv) !== "boolean" || options.allow_srv) { const response: dns_handler.SrvRecord[] = await async_resolve_srv("_ts3._udp." + address, options.timeout).catch(error => { options.log("SRV resolve unsuccessfully (%o)", error); return Promise.resolve(undefined); }); if(typeof(response) !== "undefined") { if(!response || response.length == 0) options.log("No SRV records found"); else { const sorted = response.sort((a, b) => b.weight - a.weight); const original_port = mod_options.override_port; options.log("Resolved SRV records: %o", sorted); for(const entry of sorted) { options.log("Resolving SRV record: %o", entry); mod_options.override_port = entry.port || mod_options.override_port; const resp = await resolve_address(entry.name, mod_options).catch(error => { options.log("SRV entry resolved unsuccessfully (%o)", error); return Promise.resolve(undefined); }); if(resp) { options.log("SRV entry resolved to %o. Found result", resp); return resp; } else { options.log("No response for SRV record"); } } mod_options.override_port = original_port; } } } /* resolve CNAME in the last step, else may the A records will be empty! */ if(typeof(options.allow_cname) !== "boolean" || options.allow_cname) { const cname_response: string[] = await async_resolve_cname(address, options.timeout).catch(error => { options.log("CName resolved unsuccessfully (%o)", error); return Promise.resolve(undefined); }); if(typeof(cname_response) !== "undefined") { if(!cname_response || cname_response.length == 0) options.log("No CNAME records found"); else { options.log("Resolved %d CNAME records", cname_response.length); for(const entry of cname_response) { options.log("Resolving CNAME record: %o", entry); const resp = await resolve_address(entry, mod_options).catch(error => { options.log("Failed to resolve resolved CName (%o)", error); return Promise.resolve(undefined); }); if(resp) { options.log("CName entry resolved to %o. Found result", resp); return resp; } else { options.log("No response for CName record"); } } response = undefined; /* overridden by a CNAME */ } } } /* const lookup_result = await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject("timeout"); }, options.timeout); dns_handler.lookup(address, { hints: dns_handler.ADDRCONFIG | dns_handler.V4MAPPED, all: true, family: 0 }, (error, result, family) => { clearTimeout(timeout); console.log(result); if(error) { if(error.errno as any == "ENOTFOUND" || error.errno as any == "ENODATA") { resolve(null); return; } reject(error); } else resolve(result); }); }).catch(error => { options.log("General lookup failed: %o", error); return Promise.resolve(undefined); }); console.log(lookup_result); */ if(response) return response; options.log("No records found, no result."); return undefined; } dns_handler.setServers(["8.8.8.8", "8.8.8.4", "1.1.1.1"]); } Object.assign(window["dns"] || (window["dns"] = {}), _dns);