Using a provider for the dns resolver

master
WolverinDEV 2021-03-18 12:55:35 +01:00
parent 126b836cbd
commit 38ea11725f
10 changed files with 193 additions and 102 deletions

View File

@ -0,0 +1,3 @@
window.__native_client_init_shared(__webpack_require__);
import "tc-shared/ui/react-elements/external-modal/PopoutEntrypoint";

View File

@ -1,6 +0,0 @@
import {AddressTarget, ResolveOptions} from "tc-shared/dns";
import {ServerAddress} from "tc-shared/tree/Server";
export function supported();
export function resolve_address(address: ServerAddress, options?: ResolveOptions) : Promise<AddressTarget>;
export function resolve_address_ipv4(address: string) : Promise<string>;

View File

@ -15,7 +15,6 @@ import {defaultRecorder, RecorderProfile} from "./voice/RecorderProfile";
import {Regex} from "./ui/modal/ModalConnect";
import {formatMessage} from "./ui/frames/chat";
import {spawnAvatarUpload} from "./ui/modal/ModalAvatar";
import * as dns from "tc-backend/dns";
import {EventHandler, Registry} from "./events";
import {FileManager} from "./file/FileManager";
import {FileTransferState, TransferProvider} from "./file/Transfer";
@ -42,6 +41,7 @@ import {PlaylistManager} from "tc-shared/music/PlaylistManager";
import {connectionHistory} from "tc-shared/connectionlog/History";
import {ConnectParameters} from "tc-shared/ui/modal/connect/Controller";
import {assertMainApplication} from "tc-shared/ui/utils";
import {getDNSProvider} from "tc-shared/dns";
assertMainApplication();
@ -318,20 +318,25 @@ export class ConnectionHandler {
if(resolvedAddress.host.match(Regex.IP_V4) || resolvedAddress.host.match(Regex.IP_V6)) {
/* We don't have to resolve the target host */
} else if(dns.supported()) {
} else {
this.log.log("connection.hostname.resolve", {});
try {
const resolved = await dns.resolve_address(parsedAddress, { timeout: 5000 });
const resolved = await getDNSProvider().resolveAddress({ hostname: parsedAddress.host, port: parsedAddress.port }, { timeout: 5000 });
if(this.connectAttemptId !== localConnectionAttemptId) {
/* Our attempt has been aborted */
return;
}
if(resolved?.target_ip) {
resolvedAddress.host = resolved.target_ip;
resolvedAddress.port = typeof resolved.target_port === "number" ? resolved.target_port : resolvedAddress.port;
} else {
throw tr("address resolve result id empty");
if(resolved.status === "empty-result") {
throw tr("address resolve result empty");
} else if(resolved.status === "error") {
throw resolved.message;
}
resolvedAddress.host = resolved.resolvedAddress.hostname;
resolvedAddress.port = resolved.resolvedAddress.port;
if(typeof resolvedAddress.port !== "number") {
resolvedAddress.port = parsedAddress.port;
}
this.log.log("connection.hostname.resolved", {
@ -349,8 +354,6 @@ export class ConnectionHandler {
this.handleDisconnect(DisconnectReason.DNS_FAILED, error);
return;
}
} else {
this.handleDisconnect(DisconnectReason.DNS_FAILED, tr("Unable to resolve hostname"));
}
if(this.autoReconnectAttempt) {
@ -495,7 +498,7 @@ export class ConnectionHandler {
return this.serverConnection && this.serverConnection.connected();
}
private generate_ssl_certificate_accept() : JQuery {
private generate_ssl_certificate_accept() : HTMLAnchorElement {
const properties = {
connect_default: true,
connect_profile: this.serverConnection.handshake_handler().parameters.profile.id,
@ -504,26 +507,29 @@ export class ConnectionHandler {
const build_url = (base: string, search: string, props: any) => {
const parameters: string[] = [];
for(const key of Object.keys(props))
for(const key of Object.keys(props)) {
parameters.push(key + "=" + encodeURIComponent(props[key]));
}
let callback = base + search; /* don't use document.URL because it may contains a #! */
if(!search)
if(!search) {
callback += "?" + parameters.join("&");
else
} else {
callback += "&" + parameters.join("&");
}
return "https://" + this.serverConnection.remote_address().host + ":" + this.serverConnection.remote_address().port + "/?forward_url=" + encodeURIComponent(callback);
};
/* generate the tag */
const tag = $.spawn("a").text(tr("here"));
const tag = document.createElement("a");
tag.text = tr("here");
let pathname = document.location.pathname;
if(pathname.endsWith(".php"))
pathname = pathname.substring(0, pathname.lastIndexOf("/"));
tag.attr('href', build_url(document.location.origin + pathname, document.location.search, properties));
tag.href = build_url(document.location.origin + pathname, document.location.search, properties);
return tag;
}
@ -561,7 +567,7 @@ export class ConnectionHandler {
logError(LogCategory.CLIENT, tr("Could not connect to remote host!"), data);
}
if(__build.target === "client" || !dns.resolve_address_ipv4) {
if(__build.target === "client") {
createErrorModal(
tr("Could not connect"),
tr("Could not connect to remote host (Connection refused)")
@ -569,9 +575,19 @@ export class ConnectionHandler {
} else {
const generateAddressPart = () => Math.floor(Math.random() * 256);
const addressParts = [generateAddressPart(), generateAddressPart(), generateAddressPart(), generateAddressPart()];
dns.resolve_address_ipv4(addressParts.join("-") + ".con-gate.work").then(async result => {
if(result !== addressParts.join("."))
getDNSProvider().resolveAddressIPv4({
hostname: addressParts.join("-") + ".con-gate.work",
port: 9987
}, { timeout: 5000 }).then(async result => {
if(result.status === "empty-result") {
throw tr("empty result");
} else if(result.status === "error") {
throw result.message;
}
if(result.resolvedAddress.hostname !== addressParts.join(".")) {
throw "miss matching address";
}
createErrorModal(
tr("Could not connect"),

View File

@ -5,18 +5,60 @@ export interface AddressTarget {
export interface ResolveOptions {
timeout?: number;
allow_cache?: boolean;
max_depth?: number;
allowCache?: boolean;
maxDepth?: number;
allow_srv?: boolean;
allow_cname?: boolean;
allow_any?: boolean;
allow_a?: boolean;
allow_aaaa?: boolean;
allowSrv?: boolean;
allowCName?: boolean;
allowAny?: boolean;
allowA?: boolean;
allowAAAA?: boolean;
}
export const default_options: ResolveOptions = {
timeout: 5000,
allow_cache: true,
max_depth: 5
};
allowCache: true,
maxDepth: 5
};
export interface DNSResolveOptions {
timeout?: number;
allowCache?: boolean;
maxDepth?: number;
allowSrv?: boolean;
allowCName?: boolean;
allowAny?: boolean;
allowA?: boolean;
allowAAAA?: boolean;
}
export interface DNSAddress {
hostname: string,
port: number
}
export type DNSResolveResult = {
status: "success",
originalAddress: DNSAddress,
resolvedAddress: DNSAddress
} | {
status: "error",
message: string
} | {
status: "empty-result"
};
export interface DNSProvider {
resolveAddress(address: DNSAddress, options: DNSResolveOptions) : Promise<DNSResolveResult>;
resolveAddressIPv4(address: DNSAddress, options: DNSResolveOptions) : Promise<DNSResolveResult>;
}
let provider: DNSProvider;
export function getDNSProvider() : DNSProvider {
return provider;
}
export function setDNSProvider(newProvider: DNSProvider) {
provider = newProvider;
}

View File

@ -1,21 +1,16 @@
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import * as ipc from "../../../ipc/BrowserIPC";
import * as i18n from "../../../i18n/localize";
import {Stage} from "tc-loader";
import {AbstractModal} from "../../../ui/react-elements/ModalDefinitions";
import {AppParameters} from "../../../settings";
import {getPopoutController} from "./PopoutController";
import {setupJSRender} from "../../../ui/jsrender";
import "../../../file/RemoteAvatars";
import "../../../file/RemoteIcons";
import {findRegisteredModal} from "tc-shared/ui/react-elements/modal/Registry";
import {ModalRenderer} from "tc-shared/ui/react-elements/external-modal/ModalRenderer";
import {constructAbstractModalClass} from "tc-shared/ui/react-elements/modal/Definitions";
if("__native_client_init_shared" in window) {
(window as any).__native_client_init_shared(__webpack_require__);
}
import "../../../file/RemoteAvatars";
import "../../../file/RemoteIcons";
let modalRenderer: ModalRenderer;
let modalInstance: AbstractModal;

View File

@ -1,13 +0,0 @@
import {AddressTarget, ResolveOptions} from "tc-shared/dns";
import {ServerAddress} from "tc-shared/tree/Server";
import {resolveAddressIpV4, resolveTeaSpeakServerAddress} from "./dns/resolver";
export function supported() { return true; }
export async function resolve_address(address: ServerAddress, options?: ResolveOptions) : Promise<AddressTarget> {
return await resolveTeaSpeakServerAddress(address, options);
}
export async function resolve_address_ipv4(address: string) : Promise<string> {
return await resolveAddressIpV4(address);
}

View File

@ -1,15 +1,11 @@
import * as log from "tc-shared/log";
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
import {LogCategory, logError, logTrace, logWarn} from "tc-shared/log";
import {tr} from "tc-shared/i18n/localize";
import {ServerAddress} from "tc-shared/tree/Server";
import {AddressTarget, default_options, ResolveOptions} from "tc-shared/dns";
import {executeDnsRequest, RRType} from "tc-backend/web/dns/api";
type Address = { host: string, port: number };
import {default_options, DNSAddress, DNSResolveResult, ResolveOptions} from "tc-shared/dns";
import {executeDnsRequest, RRType} from "./api";
interface DNSResolveMethod {
name() : string;
resolve(address: Address) : Promise<Address | undefined>;
resolve(address: DNSAddress) : Promise<DNSAddress | undefined>;
}
class LocalhostResolver implements DNSResolveMethod {
@ -17,10 +13,10 @@ class LocalhostResolver implements DNSResolveMethod {
return "localhost";
}
async resolve(address: Address): Promise<Address | undefined> {
if(address.host === "localhost") {
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
if(address.hostname === "localhost") {
return {
host: "127.0.0.1",
hostname: "127.0.0.1",
port: address.port
}
}
@ -42,14 +38,14 @@ class IPResolveMethod implements DNSResolveMethod {
return "ip v" + (this.v6 ? "6" : "4") + " resolver";
}
async resolve(address: Address): Promise<Address | undefined> {
const answer = await executeDnsRequest(address.host, this.v6 ? RRType.AAAA : RRType.A);
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
const answer = await executeDnsRequest(address.hostname, this.v6 ? RRType.AAAA : RRType.A);
if(!answer.length) {
return undefined;
}
return {
host: answer[0].data,
hostname: answer[0].data,
port: address.port
}
}
@ -73,8 +69,8 @@ class SRVResolveMethod implements DNSResolveMethod {
return "srv resolve [" + this.application + "]";
}
async resolve(address: Address): Promise<Address | undefined> {
const answer = await executeDnsRequest((this.application ? this.application + "." : "") + address.host, RRType.SRV);
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
const answer = await executeDnsRequest((this.application ? this.application + "." : "") + address.hostname, RRType.SRV);
const records: {[key: number]: ParsedSVRRecord[]} = {};
for(const record of answer) {
@ -146,7 +142,7 @@ class SRVResolveMethod implements DNSResolveMethod {
}
return {
host: record.target,
hostname: record.target,
port: record.port == 0 ? address.port : record.port
};
}
@ -167,7 +163,7 @@ class SRV_IPResolveMethod implements DNSResolveMethod {
return "srv ip resolver [" + this.srvResolver.name() + "; " + this.ipv4Resolver.name() + "; " + this.ipv6Resolver.name() + "]";
}
async resolve(address: Address): Promise<Address | undefined> {
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
const srvAddress = await this.srvResolver.resolve(address);
if(!srvAddress) {
return undefined;
@ -192,21 +188,21 @@ class DomainRootResolveMethod implements DNSResolveMethod {
return "domain-root [" + this.resolver.name() + "]";
}
async resolve(address: Address): Promise<Address | undefined> {
const parts = address.host.split(".");
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
const parts = address.hostname.split(".");
if(parts.length < 3) {
return undefined;
}
return await this.resolver.resolve({
host: parts.slice(-2).join("."),
hostname: parts.slice(1).join("."),
port: address.port
});
}
}
class TeaSpeakDNSResolve {
readonly address: Address;
readonly address: DNSAddress;
private resolvers: {[key: string]:{ resolver: DNSResolveMethod, after: string[] }} = {};
private resolving = false;
private timeout;
@ -217,7 +213,7 @@ class TeaSpeakDNSResolve {
private finished_resolvers: string[];
private resolving_resolvers: string[];
constructor(addr: Address) {
constructor(addr: DNSAddress) {
this.address = addr;
}
@ -229,7 +225,7 @@ class TeaSpeakDNSResolve {
this.resolvers[resolver.name()] = { resolver: resolver, after: after.map(e => typeof e === "string" ? e : e.name()) };
}
resolve(timeout: number) : Promise<Address> {
resolve(timeout: number) : Promise<DNSAddress> {
if(this.resolving) {
throw tr("already resolving");
}
@ -246,9 +242,9 @@ class TeaSpeakDNSResolve {
this.timeout = setTimeout(() => {
this.callback_fail(tr("timeout"));
}, timeout);
logTrace(LogCategory.DNS, tr("Start resolving %s:%d"), this.address.host, this.address.port);
logTrace(LogCategory.DNS, tr("Start resolving %s:%d"), this.address.hostname, this.address.port);
return new Promise<Address>((resolve, reject) => {
return new Promise<DNSAddress>((resolve, reject) => {
this.callback_success = data => {
cleanup();
resolve(data);
@ -290,8 +286,8 @@ class TeaSpeakDNSResolve {
}
logTrace(LogCategory.DNS, tr(" Successfully resolved address %s:%d to %s:%d via resolver %s"),
this.address.host, this.address.port,
result.host, result.port,
this.address.hostname, this.address.port,
result.hostname, result.port,
resolver_name);
this.callback_success(result);
}).catch(error => {
@ -324,29 +320,48 @@ const resolverSrvTS3 = new SRV_IPResolveMethod(new SRVResolveMethod("_ts3._udp")
const resolverDrSrvTS = new DomainRootResolveMethod(resolverSrvTS);
const resolverDrSrvTS3 = new DomainRootResolveMethod(resolverSrvTS3);
export async function resolveTeaSpeakServerAddress(address: ServerAddress, _options?: ResolveOptions) : Promise<AddressTarget> {
const options = Object.assign({}, default_options);
Object.assign(options, _options);
export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options?: ResolveOptions) : Promise<DNSResolveResult> {
try {
const options = Object.assign({}, default_options);
Object.assign(options, _options);
const resolver = new TeaSpeakDNSResolve(address);
const resolver = new TeaSpeakDNSResolve(address);
resolver.registerResolver(kResolverLocalhost);
resolver.registerResolver(kResolverLocalhost);
resolver.registerResolver(resolverSrvTS, kResolverLocalhost);
resolver.registerResolver(resolverSrvTS3, kResolverLocalhost);
//TODO: TSDNS somehow?
resolver.registerResolver(resolverSrvTS, kResolverLocalhost);
resolver.registerResolver(resolverSrvTS3, kResolverLocalhost);
//TODO: TSDNS somehow?
resolver.registerResolver(resolverDrSrvTS, resolverSrvTS);
resolver.registerResolver(resolverDrSrvTS3, resolverSrvTS3);
resolver.registerResolver(resolverDrSrvTS, resolverSrvTS);
resolver.registerResolver(resolverDrSrvTS3, resolverSrvTS3);
resolver.registerResolver(kResolverIpV4, resolverSrvTS, resolverSrvTS3);
resolver.registerResolver(kResolverIpV6, kResolverIpV4);
resolver.registerResolver(kResolverIpV4, resolverSrvTS, resolverSrvTS3);
resolver.registerResolver(kResolverIpV6, kResolverIpV4);
const response = await resolver.resolve(options.timeout || 5000);
return {
target_ip: response.host,
target_port: response.port
};
const response = await resolver.resolve(options.timeout || 5000);
if(!response) {
return {
status: "empty-result"
};
}
return {
status: "success",
originalAddress: address,
resolvedAddress: response
};
} catch (error) {
if(typeof error !== "string") {
logError(LogCategory.DNS, tr("Failed to resolve %o: %o"), address, error);
error = tr("lookup the console");
}
return {
status: "error",
message: error
};
}
}
export async function resolveAddressIpV4(address: string) : Promise<string> {

37
web/app/hooks/Dns.ts Normal file
View File

@ -0,0 +1,37 @@
import {DNSAddress, DNSProvider, DNSResolveOptions, DNSResolveResult, setDNSProvider} from "tc-shared/dns";
import {resolveAddressIpV4, resolveTeaSpeakServerAddress} from "tc-backend/web/dns/resolver";
import {LogCategory, logError} from "tc-shared/log";
import {tr} from "tc-shared/i18n/localize";
setDNSProvider(new class implements DNSProvider {
resolveAddress(address: DNSAddress, options: DNSResolveOptions): Promise<DNSResolveResult> {
return resolveTeaSpeakServerAddress(address, options);
}
async resolveAddressIPv4(address: DNSAddress, options: DNSResolveOptions): Promise<DNSResolveResult> {
try {
const result = await resolveAddressIpV4(address.hostname);
if(!result) {
return { status: "empty-result" };
}
return {
status: "success",
originalAddress: address,
resolvedAddress: {
hostname: result,
port: address.port
}
};
} catch (error) {
if(typeof error !== "string") {
logError(LogCategory.DNS, tr("Failed to resolve %o: %o"), address, error);
error = tr("lookup the console");
}
return {
status: "error",
message: error
};
}
}
});

View File

@ -9,6 +9,7 @@ import "./hooks/ExternalModal";
import "./hooks/AudioRecorder";
import "./hooks/MenuBar";
import "./hooks/Video";
import "./hooks/Dns";
import "./UnloadHandler";

View File

@ -3,7 +3,8 @@ import * as config_base from "./webpack.config";
export = env => config_base.config(env, "client").then(config => {
Object.assign(config.entry, {
"client-app": ["./client/app/index.ts"]
"client-app": ["./client/app/index.ts"],
"modal-external": ["./client/app/EntryPointPopoutModal.ts"]
});
Object.assign(config.resolve.alias, {