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"]);
|
|
|
|
}
|
|
|
|
|
2019-09-01 15:41:05 -04:00
|
|
|
Object.assign(window["dns"] || (window["dns"] = {} as any), _dns);
|