@@ -20,8 +20,7 @@ import {
20
20
registerResolver ,
21
21
registerDefaultScheme ,
22
22
} from './resolver' ;
23
- import * as dns from 'dns' ;
24
- import * as util from 'util' ;
23
+ import { promises as dns } from 'node:dns' ;
25
24
import { extractAndSelectServiceConfig , ServiceConfig } from './service-config' ;
26
25
import { Status } from './constants' ;
27
26
import { StatusObject } from './call-interface' ;
@@ -33,6 +32,7 @@ import { GrpcUri, uriToString, splitHostPort } from './uri-parser';
33
32
import { isIPv6 , isIPv4 } from 'net' ;
34
33
import { ChannelOptions } from './channel-options' ;
35
34
import { BackoffOptions , BackoffTimeout } from './backoff-timeout' ;
35
+ import { GRPC_NODE_USE_ALTERNATIVE_RESOLVER } from './environment' ;
36
36
37
37
const TRACER_NAME = 'dns_resolver' ;
38
38
@@ -47,9 +47,6 @@ export const DEFAULT_PORT = 443;
47
47
48
48
const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30_000 ;
49
49
50
- const resolveTxtPromise = util . promisify ( dns . resolveTxt ) ;
51
- const dnsLookupPromise = util . promisify ( dns . lookup ) ;
52
-
53
50
/**
54
51
* Resolver implementation that handles DNS names and IP addresses.
55
52
*/
@@ -63,7 +60,7 @@ class DnsResolver implements Resolver {
63
60
* Failures are handled by the backoff timer.
64
61
*/
65
62
private readonly minTimeBetweenResolutionsMs : number ;
66
- private pendingLookupPromise : Promise < dns . LookupAddress [ ] > | null = null ;
63
+ private pendingLookupPromise : Promise < TcpSubchannelAddress [ ] > | null = null ;
67
64
private pendingTxtPromise : Promise < string [ ] [ ] > | null = null ;
68
65
private latestLookupResult : Endpoint [ ] | null = null ;
69
66
private latestServiceConfig : ServiceConfig | null = null ;
@@ -76,12 +73,17 @@ class DnsResolver implements Resolver {
76
73
private isNextResolutionTimerRunning = false ;
77
74
private isServiceConfigEnabled = true ;
78
75
private returnedIpResult = false ;
76
+ private alternativeResolver = new dns . Resolver ( ) ;
77
+
79
78
constructor (
80
79
private target : GrpcUri ,
81
80
private listener : ResolverListener ,
82
81
channelOptions : ChannelOptions
83
82
) {
84
83
trace ( 'Resolver constructed for target ' + uriToString ( target ) ) ;
84
+ if ( target . authority ) {
85
+ this . alternativeResolver . setServers ( [ target . authority ] ) ;
86
+ }
85
87
const hostPort = splitHostPort ( target . path ) ;
86
88
if ( hostPort === null ) {
87
89
this . ipResult = null ;
@@ -185,11 +187,7 @@ class DnsResolver implements Resolver {
185
187
* revert to an effectively blank one. */
186
188
this . latestLookupResult = null ;
187
189
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 ) ;
193
191
this . pendingLookupPromise . then (
194
192
addressList => {
195
193
if ( this . pendingLookupPromise === null ) {
@@ -198,17 +196,12 @@ class DnsResolver implements Resolver {
198
196
this . pendingLookupPromise = null ;
199
197
this . backoff . reset ( ) ;
200
198
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 => ( {
205
200
addresses : [ address ] ,
206
201
} ) ) ;
207
202
const allAddressesString : string =
208
203
'[' +
209
- subchannelAddresses
210
- . map ( addr => addr . host + ':' + addr . port )
211
- . join ( ',' ) +
204
+ addressList . map ( addr => addr . host + ':' + addr . port ) . join ( ',' ) +
212
205
']' ;
213
206
trace (
214
207
'Resolved addresses for target ' +
@@ -253,7 +246,7 @@ class DnsResolver implements Resolver {
253
246
/* We handle the TXT query promise differently than the others because
254
247
* the name resolution attempt as a whole is a success even if the TXT
255
248
* lookup fails */
256
- this . pendingTxtPromise = resolveTxtPromise ( hostname ) ;
249
+ this . pendingTxtPromise = this . resolveTxt ( hostname ) ;
257
250
this . pendingTxtPromise . then (
258
251
txtRecord => {
259
252
if ( this . pendingTxtPromise === null ) {
@@ -302,6 +295,48 @@ class DnsResolver implements Resolver {
302
295
}
303
296
}
304
297
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
+
305
340
private startNextResolutionTimer ( ) {
306
341
clearTimeout ( this . nextResolutionTimer ) ;
307
342
this . nextResolutionTimer = setTimeout ( ( ) => {
0 commit comments