@@ -20,8 +20,7 @@ import {
2020 registerResolver ,
2121 registerDefaultScheme ,
2222} from './resolver' ;
23- import * as dns from 'dns' ;
24- import * as util from 'util' ;
23+ import { promises as dns } from 'node:dns' ;
2524import { extractAndSelectServiceConfig , ServiceConfig } from './service-config' ;
2625import { Status } from './constants' ;
2726import { StatusObject } from './call-interface' ;
@@ -33,6 +32,7 @@ import { GrpcUri, uriToString, splitHostPort } from './uri-parser';
3332import { isIPv6 , isIPv4 } from 'net' ;
3433import { ChannelOptions } from './channel-options' ;
3534import { BackoffOptions , BackoffTimeout } from './backoff-timeout' ;
35+ import { GRPC_NODE_USE_ALTERNATIVE_RESOLVER } from './environment' ;
3636
3737const TRACER_NAME = 'dns_resolver' ;
3838
@@ -47,9 +47,6 @@ export const DEFAULT_PORT = 443;
4747
4848const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30_000 ;
4949
50- const resolveTxtPromise = util . promisify ( dns . resolveTxt ) ;
51- const dnsLookupPromise = util . promisify ( dns . lookup ) ;
52-
5350/**
5451 * Resolver implementation that handles DNS names and IP addresses.
5552 */
@@ -63,7 +60,7 @@ class DnsResolver implements Resolver {
6360 * Failures are handled by the backoff timer.
6461 */
6562 private readonly minTimeBetweenResolutionsMs : number ;
66- private pendingLookupPromise : Promise < dns . LookupAddress [ ] > | null = null ;
63+ private pendingLookupPromise : Promise < TcpSubchannelAddress [ ] > | null = null ;
6764 private pendingTxtPromise : Promise < string [ ] [ ] > | null = null ;
6865 private latestLookupResult : Endpoint [ ] | null = null ;
6966 private latestServiceConfig : ServiceConfig | null = null ;
@@ -76,12 +73,17 @@ class DnsResolver implements Resolver {
7673 private isNextResolutionTimerRunning = false ;
7774 private isServiceConfigEnabled = true ;
7875 private returnedIpResult = false ;
76+ private alternativeResolver = new dns . Resolver ( ) ;
77+
7978 constructor (
8079 private target : GrpcUri ,
8180 private listener : ResolverListener ,
8281 channelOptions : ChannelOptions
8382 ) {
8483 trace ( 'Resolver constructed for target ' + uriToString ( target ) ) ;
84+ if ( target . authority ) {
85+ this . alternativeResolver . setServers ( [ target . authority ] ) ;
86+ }
8587 const hostPort = splitHostPort ( target . path ) ;
8688 if ( hostPort === null ) {
8789 this . ipResult = null ;
@@ -185,11 +187,7 @@ class DnsResolver implements Resolver {
185187 * revert to an effectively blank one. */
186188 this . latestLookupResult = null ;
187189 const hostname : string = this . dnsHostname ;
188- /* We lookup both address families here and then split them up later
189- * because when looking up a single family, dns.lookup outputs an error
190- * if the name exists but there are no records for that family, and that
191- * error is indistinguishable from other kinds of errors */
192- this . pendingLookupPromise = dnsLookupPromise ( hostname , { all : true } ) ;
190+ this . pendingLookupPromise = this . lookup ( hostname ) ;
193191 this . pendingLookupPromise . then (
194192 addressList => {
195193 if ( this . pendingLookupPromise === null ) {
@@ -198,17 +196,12 @@ class DnsResolver implements Resolver {
198196 this . pendingLookupPromise = null ;
199197 this . backoff . reset ( ) ;
200198 this . backoff . stop ( ) ;
201- const subchannelAddresses : TcpSubchannelAddress [ ] = addressList . map (
202- addr => ( { host : addr . address , port : + this . port ! } )
203- ) ;
204- this . latestLookupResult = subchannelAddresses . map ( address => ( {
199+ this . latestLookupResult = addressList . map ( address => ( {
205200 addresses : [ address ] ,
206201 } ) ) ;
207202 const allAddressesString : string =
208203 '[' +
209- subchannelAddresses
210- . map ( addr => addr . host + ':' + addr . port )
211- . join ( ',' ) +
204+ addressList . map ( addr => addr . host + ':' + addr . port ) . join ( ',' ) +
212205 ']' ;
213206 trace (
214207 'Resolved addresses for target ' +
@@ -253,7 +246,7 @@ class DnsResolver implements Resolver {
253246 /* We handle the TXT query promise differently than the others because
254247 * the name resolution attempt as a whole is a success even if the TXT
255248 * lookup fails */
256- this . pendingTxtPromise = resolveTxtPromise ( hostname ) ;
249+ this . pendingTxtPromise = this . resolveTxt ( hostname ) ;
257250 this . pendingTxtPromise . then (
258251 txtRecord => {
259252 if ( this . pendingTxtPromise === null ) {
@@ -302,6 +295,48 @@ class DnsResolver implements Resolver {
302295 }
303296 }
304297
298+ private async lookup ( hostname : string ) : Promise < TcpSubchannelAddress [ ] > {
299+ if ( GRPC_NODE_USE_ALTERNATIVE_RESOLVER ) {
300+ trace ( 'Using alternative DNS resolver.' ) ;
301+
302+ const records = await Promise . allSettled ( [
303+ this . alternativeResolver . resolve4 ( hostname ) ,
304+ this . alternativeResolver . resolve6 ( hostname ) ,
305+ ] ) ;
306+
307+ if ( records . every ( result => result . status === 'rejected' ) ) {
308+ throw new Error ( ( records [ 0 ] as PromiseRejectedResult ) . reason ) ;
309+ }
310+
311+ return records
312+ . reduce < string [ ] > ( ( acc , result ) => {
313+ return result . status === 'fulfilled'
314+ ? [ ...acc , ...result . value ]
315+ : acc ;
316+ } , [ ] )
317+ . map ( addr => ( {
318+ host : addr ,
319+ port : + this . port ! ,
320+ } ) ) ;
321+ }
322+
323+ /* We lookup both address families here and then split them up later
324+ * because when looking up a single family, dns.lookup outputs an error
325+ * if the name exists but there are no records for that family, and that
326+ * error is indistinguishable from other kinds of errors */
327+ const addressList = await dns . lookup ( hostname , { all : true } ) ;
328+ return addressList . map ( addr => ( { host : addr . address , port : + this . port ! } ) ) ;
329+ }
330+
331+ private async resolveTxt ( hostname : string ) : Promise < string [ ] [ ] > {
332+ if ( GRPC_NODE_USE_ALTERNATIVE_RESOLVER ) {
333+ trace ( 'Using alternative DNS resolver.' ) ;
334+ return this . alternativeResolver . resolveTxt ( hostname ) ;
335+ }
336+
337+ return dns . resolveTxt ( hostname ) ;
338+ }
339+
305340 private startNextResolutionTimer ( ) {
306341 clearTimeout ( this . nextResolutionTimer ) ;
307342 this . nextResolutionTimer = setTimeout ( ( ) => {
0 commit comments