TeaSpeak-Client/modules/renderer/dns/dns_resolver.ts

245 lines
10 KiB
TypeScript
Raw Normal View History

2019-06-26 16:09:01 -04:00
/// <reference path="../imports/imports_shared.d.ts" />
window["require_setup"](module);
import * as dns_handler from "dns";
namespace _dns {
type Lookup<R> = (hostname: string, callback: (err: NodeJS.ErrnoException, result: R) => void) => void;
type AsyncLookup<R> = (hostname: string, timeout: number) => Promise<R>;
function make_async<R>(fun: Lookup<R>) : AsyncLookup<R> {
return (hostname, timeout) => {
return new Promise<R>((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<AddressTarget> {
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);
2019-07-03 07:16:38 -04:00
}) as any;
2019-06-26 16:09:01 -04:00
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);
2019-07-03 07:16:38 -04:00
}) as any;
2019-06-26 16:09:01 -04:00
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);