support selecting resolver via env

This commit is contained in:
George Kampitakis 2024-06-19 09:43:35 +01:00 committed by gkampitakis
parent c3d073b0cc
commit 6278da6aec
No known key found for this signature in database
1 changed files with 38 additions and 20 deletions

View File

@ -20,7 +20,6 @@ import {
registerResolver, registerResolver,
registerDefaultScheme, registerDefaultScheme,
} from './resolver'; } from './resolver';
import { AnyRecord } from 'dns';
import * as dns from 'dns/promises'; import * as dns from 'dns/promises';
import { extractAndSelectServiceConfig, ServiceConfig } from './service-config'; import { extractAndSelectServiceConfig, ServiceConfig } from './service-config';
import { Status } from './constants'; import { Status } from './constants';
@ -45,7 +44,8 @@ function trace(text: string): void {
*/ */
export const DEFAULT_PORT = 443; export const DEFAULT_PORT = 443;
const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30_000; const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30_000,
DNS_RESOLUTION_ENV = 'GRPC_DNS_RESOLVER_TODO';
/** /**
* Resolver implementation that handles DNS names and IP addresses. * Resolver implementation that handles DNS names and IP addresses.
@ -60,7 +60,7 @@ class DnsResolver implements Resolver {
* Failures are handled by the backoff timer. * Failures are handled by the backoff timer.
*/ */
private readonly minTimeBetweenResolutionsMs: number; private readonly minTimeBetweenResolutionsMs: number;
private pendingLookupPromise: Promise<AnyRecord[]> | null = null; private pendingLookupPromise: Promise<TcpSubchannelAddress[]> | null = null;
private pendingTxtPromise: Promise<string[][]> | null = null; private pendingTxtPromise: Promise<string[][]> | null = null;
private latestLookupResult: Endpoint[] | null = null; private latestLookupResult: Endpoint[] | null = null;
private latestServiceConfig: ServiceConfig | null = null; private latestServiceConfig: ServiceConfig | null = null;
@ -73,7 +73,7 @@ class DnsResolver implements Resolver {
private isNextResolutionTimerRunning = false; private isNextResolutionTimerRunning = false;
private isServiceConfigEnabled = true; private isServiceConfigEnabled = true;
private returnedIpResult = false; private returnedIpResult = false;
private resolver = new dns.Resolver(); private independentResolver = new dns.Resolver();
constructor( constructor(
private target: GrpcUri, private target: GrpcUri,
@ -82,7 +82,7 @@ class DnsResolver implements Resolver {
) { ) {
trace('Resolver constructed for target ' + uriToString(target)); trace('Resolver constructed for target ' + uriToString(target));
if (target.authority) { if (target.authority) {
this.resolver.setServers([target.authority]); this.independentResolver.setServers([target.authority]);
} }
const hostPort = splitHostPort(target.path); const hostPort = splitHostPort(target.path);
if (hostPort === null) { if (hostPort === null) {
@ -187,11 +187,7 @@ class DnsResolver implements Resolver {
* revert to an effectively blank one. */ * revert to an effectively blank one. */
this.latestLookupResult = null; this.latestLookupResult = null;
const hostname: string = this.dnsHostname; const hostname: string = this.dnsHostname;
/* We lookup both address families here and then split them up later this.pendingLookupPromise = this.lookup(hostname);
* because when looking up a single family, dns.lookup outputs an error
* if the name exists but there are no records for that family, and that
* error is indistinguishable from other kinds of errors */
this.pendingLookupPromise = this.resolver.resolveAny(hostname);
this.pendingLookupPromise.then( this.pendingLookupPromise.then(
addressList => { addressList => {
if (this.pendingLookupPromise === null) { if (this.pendingLookupPromise === null) {
@ -200,19 +196,12 @@ class DnsResolver implements Resolver {
this.pendingLookupPromise = null; this.pendingLookupPromise = null;
this.backoff.reset(); this.backoff.reset();
this.backoff.stop(); this.backoff.stop();
const subchannelAddresses: TcpSubchannelAddress[] = addressList this.latestLookupResult = addressList.map(address => ({
.filter(addr => {
addr.type === 'A' || addr.type === 'AAAA';
})
.map(addr => ({ host: addr.address, port: +this.port! }));
this.latestLookupResult = subchannelAddresses.map(address => ({
addresses: [address], addresses: [address],
})); }));
const allAddressesString: string = const allAddressesString: string =
'[' + '[' +
subchannelAddresses addressList.map(addr => addr.host + ':' + addr.port).join(',') +
.map(addr => addr.host + ':' + addr.port)
.join(',') +
']'; ']';
trace( trace(
'Resolved addresses for target ' + 'Resolved addresses for target ' +
@ -257,7 +246,7 @@ class DnsResolver implements Resolver {
/* We handle the TXT query promise differently than the others because /* We handle the TXT query promise differently than the others because
* the name resolution attempt as a whole is a success even if the TXT * the name resolution attempt as a whole is a success even if the TXT
* lookup fails */ * lookup fails */
this.pendingTxtPromise = this.resolver.resolveTxt(hostname); this.pendingTxtPromise = this.resolveTxt(hostname);
this.pendingTxtPromise.then( this.pendingTxtPromise.then(
txtRecord => { txtRecord => {
if (this.pendingTxtPromise === null) { if (this.pendingTxtPromise === null) {
@ -306,6 +295,35 @@ class DnsResolver implements Resolver {
} }
} }
private async lookup(hostname: string): Promise<TcpSubchannelAddress[]> {
if (process.env[DNS_RESOLUTION_ENV] === 'true') {
const records = await this.independentResolver.resolveAny(hostname);
const addressList = records.filter(addr => {
addr.type === 'A' || addr.type === 'AAAA';
}) as unknown as { address: string }[];
return addressList.map(addr => ({
host: addr.address,
port: +this.port!,
}));
}
/* We lookup both address families here and then split them up later
* because when looking up a single family, dns.lookup outputs an error
* if the name exists but there are no records for that family, and that
* error is indistinguishable from other kinds of errors */
const addressList = await dns.lookup(hostname, { all: true });
return addressList.map(addr => ({ host: addr.address, port: +this.port! }));
}
private async resolveTxt(hostname: string): Promise<string[][]> {
if (process.env[DNS_RESOLUTION_ENV] === 'true') {
return this.independentResolver.resolveTxt(hostname);
}
return dns.resolveTxt(hostname);
}
private startNextResolutionTimer() { private startNextResolutionTimer() {
clearTimeout(this.nextResolutionTimer); clearTimeout(this.nextResolutionTimer);
this.nextResolutionTimer = setTimeout(() => { this.nextResolutionTimer = setTimeout(() => {