Skip to content

Commit ddcafa3

Browse files
jasnellcjihrig
authored andcommitted
quic: use promisified dns lookup
PR-URL: #34283 Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 076a86d commit ddcafa3

File tree

3 files changed

+70
-64
lines changed

3 files changed

+70
-64
lines changed

doc/api/quic.md

+40-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ added: REPLACEME
269269
* `type` {string} Can be one of `'udp4'`, `'upd6'`, or `'udp6-only'` to
270270
use IPv4, IPv6, or IPv6 with dual-stack mode disabled.
271271
**Default**: `'udp4'`.
272-
* `lookup` {Function} A custom DNS lookup function. Default `dns.lookup()`.
272+
* `lookup` {Function} A [custom DNS lookup function][].
273+
**Default**: undefined.
273274
* `maxConnections` {number} The maximum number of total active inbound
274275
connections.
275276
* `maxConnectionsPerHost` {number} The maximum number of inbound connections
@@ -1505,6 +1506,8 @@ added: REPLACEME
15051506
* `type` {string} Can be one of `'udp4'`, `'upd6'`, or `'udp6-only'` to
15061507
use IPv4, IPv6, or IPv6 with dual-stack mode disabled.
15071508
**Default**: `'udp4'`.
1509+
* `lookup` {Function} A [custom DNS lookup function][].
1510+
**Default**: undefined.
15081511
* Returns: {QuicEndpoint}
15091512

15101513
Creates and adds a new `QuicEndpoint` to the `QuicSocket` instance. An
@@ -1661,6 +1664,8 @@ added: REPLACEME
16611664
passphrase: <string>]}`. The object form can only occur in an array.
16621665
`object.passphrase` is optional. Encrypted keys will be decrypted with
16631666
`object.passphrase` if provided, or `options.passphrase` if it is not.
1667+
* `lookup` {Function} A [custom DNS lookup function][].
1668+
**Default**: undefined.
16641669
* `activeConnectionIdLimit` {number} Must be a value between `2` and `8`
16651670
(inclusive). Default: `2`.
16661671
* `congestionAlgorithm` {string} Must be either `'reno'` or `'cubic'`.
@@ -1836,6 +1841,8 @@ added: REPLACEME
18361841
passphrase: <string>]}`. The object form can only occur in an array.
18371842
`object.passphrase` is optional. Encrypted keys will be decrypted with
18381843
`object.passphrase` if provided, or `options.passphrase` if it is not.
1844+
* `lookup` {Function} A [custom DNS lookup function][].
1845+
**Default**: undefined.
18391846
* `activeConnectionIdLimit` {number}
18401847
* `congestionAlgorithm` {string} Must be either `'reno'` or `'cubic'`.
18411848
**Default**: `'reno'`.
@@ -2411,15 +2418,47 @@ added: REPLACEME
24112418

24122419
Set to `true` if the `QuicStream` is unidirectional.
24132420

2421+
## Additional Notes
2422+
2423+
### Custom DNS Lookup Functions
2424+
2425+
By default, the QUIC implementation uses the `dns` module's
2426+
[promisified version of `lookup()`][] to resolve domains names
2427+
into IP addresses. For most typical use cases, this will be
2428+
sufficient. However, it is possible to pass a custom `lookup`
2429+
function as an option in several places throughout the QUIC API:
2430+
2431+
* `net.createQuicSocket()`
2432+
* `quicsocket.addEndpoint()`
2433+
* `quicsocket.connect()`
2434+
* `quicsocket.listen()`
2435+
2436+
The custom `lookup` function must teturn a `Promise` that is
2437+
resolved once the lookup is complete. It will be invoked with
2438+
two arguments:
2439+
2440+
* `address` {string|undefined} The host name to resolve, or
2441+
`undefined` if no host name was provided.
2442+
* `family` {number} One of `4` or `6`, identifying either
2443+
IPv4 or IPv6.
2444+
2445+
```js
2446+
async function myCustomLookup(address, type) {
2447+
return resolveTheAddressSomehow(address, type);
2448+
}
2449+
```
2450+
24142451
[`crypto.getCurves()`]: crypto.html#crypto_crypto_getcurves
24152452
[`stream.Readable`]: #stream_class_stream_readable
24162453
[`tls.DEFAULT_ECDH_CURVE`]: #tls_tls_default_ecdh_curve
24172454
[`tls.getCiphers()`]: tls.html#tls_tls_getciphers
24182455
[ALPN]: https://tools.ietf.org/html/rfc7301
24192456
[RFC 4007]: https://tools.ietf.org/html/rfc4007
24202457
[Certificate Object]: https://nodejs.org/dist/latest-v12.x/docs/api/tls.html#tls_certificate_object
2458+
[custom DNS lookup function]: #custom_dns_lookups
24212459
[modifying the default cipher suite]: tls.html#tls_modifying_the_default_tls_cipher_suite
24222460
[OpenSSL Options]: crypto.html#crypto_openssl_options
24232461
[Perfect Forward Secrecy]: #tls_perfect_forward_secrecy
2462+
[promisified version of `lookup()`]: dns.html#dns_dnspromises_lookup_hostname_options
24242463
['qlog']: #quic_quicsession_qlog
24252464
[qlog standard]: https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-00.html

lib/internal/quic/core.js

+16-47
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ const {
2929
customInspect,
3030
getAllowUnauthorized,
3131
getSocketType,
32-
lookup4,
33-
lookup6,
3432
setTransportParams,
3533
toggleListeners,
3634
validateNumber,
@@ -202,17 +200,12 @@ const {
202200

203201
const emit = EventEmitter.prototype.emit;
204202

205-
// TODO(@jasnell): Temporary while converting to Promises-based API
206-
const { lookup } = require('dns').promises;
207-
208-
const kAfterPreferredAddressLookup = Symbol('kAfterPreferredAddressLookup');
209203
const kAddSession = Symbol('kAddSession');
210204
const kAddStream = Symbol('kAddStream');
211205
const kBind = Symbol('kBind');
212206
const kClose = Symbol('kClose');
213207
const kCert = Symbol('kCert');
214208
const kClientHello = Symbol('kClientHello');
215-
const kCompleteListen = Symbol('kCompleteListen');
216209
const kDestroy = Symbol('kDestroy');
217210
const kEndpointBound = Symbol('kEndpointBound');
218211
const kEndpointClose = Symbol('kEndpointClose');
@@ -579,10 +572,6 @@ function addressOrLocalhost(address, type) {
579572
return address || (type === AF_INET6 ? '::' : '0.0.0.0');
580573
}
581574

582-
function lookupOrDefault(lookup, type) {
583-
return lookup || (type === AF_INET6 ? lookup6 : lookup4);
584-
}
585-
586575
function deferredClosePromise(state) {
587576
return state.closePromise = new Promise((resolve, reject) => {
588577
state.closePromiseResolve = resolve;
@@ -594,7 +583,7 @@ function deferredClosePromise(state) {
594583
});
595584
}
596585

597-
async function resolvePreferredAddress(state, preferredAddress) {
586+
async function resolvePreferredAddress(lookup, preferredAddress) {
598587
if (preferredAddress === undefined)
599588
return {};
600589
const {
@@ -603,7 +592,9 @@ async function resolvePreferredAddress(state, preferredAddress) {
603592
type = 'udp4'
604593
} = { ...preferredAddress };
605594
const [typeVal] = getSocketType(type);
606-
const { address: ip } = await lookup(address, typeVal === AF_INET6 ? 6 : 4);
595+
const {
596+
address: ip
597+
} = await lookup(address, typeVal === AF_INET6 ? 6 : 4);
607598
return { ip, port, type };
608599
}
609600

@@ -641,7 +632,7 @@ class QuicEndpoint {
641632
const state = this[kInternalState];
642633
state.socket = socket;
643634
state.address = addressOrLocalhost(address, type);
644-
state.lookup = lookupOrDefault(lookup, type);
635+
state.lookup = lookup;
645636
state.ipv6Only = ipv6Only;
646637
state.port = port;
647638
state.reuseAddr = reuseAddr;
@@ -711,11 +702,9 @@ class QuicEndpoint {
711702

712703
state.state = kSocketPending;
713704

714-
// TODO(@jasnell): Use passed in lookup function once everything
715-
// has been converted to Promises-based API
716705
const {
717706
address: ip
718-
} = await lookup(state.address, state.type === AF_INET6 ? 6 : 4);
707+
} = await state.lookup(state.address, state.type === AF_INET6 ? 6 : 4);
719708

720709
// It's possible for the QuicEndpoint to have been destroyed while
721710
// we were waiting for the DNS lookup to complete. If so, reject
@@ -977,9 +966,6 @@ class QuicSocket extends EventEmitter {
977966
// Default configuration for QuicServerSessions
978967
server,
979968

980-
// UDP type
981-
type,
982-
983969
// True if address verification should be used.
984970
validateAddress,
985971

@@ -1001,7 +987,7 @@ class QuicSocket extends EventEmitter {
1001987

1002988
state.client = client;
1003989
state.server = server;
1004-
state.lookup = lookupOrDefault(lookup, type);
990+
state.lookup = lookup;
1005991

1006992
let socketOptions = 0;
1007993
if (validateAddress)
@@ -1020,14 +1006,7 @@ class QuicSocket extends EventEmitter {
10201006
statelessResetSecret,
10211007
disableStatelessReset));
10221008

1023-
this.addEndpoint({
1024-
lookup: state.lookup,
1025-
// Keep the lookup and ...endpoint in this order
1026-
// to allow the passed in endpoint options to
1027-
// override the lookup specifically for that endpoint
1028-
...endpoint,
1029-
preferred: true
1030-
});
1009+
this.addEndpoint({ ...endpoint, preferred: true });
10311010
}
10321011

10331012
[kRejections](err, eventname, ...args) {
@@ -1205,28 +1184,16 @@ class QuicSocket extends EventEmitter {
12051184
if (state.state !== kSocketUnbound)
12061185
throw new ERR_INVALID_STATE('QuicSocket is already being bound');
12071186

1187+
options = {
1188+
lookup: state.lookup,
1189+
...options
1190+
};
1191+
12081192
const endpoint = new QuicEndpoint(this, options);
12091193
state.endpoints.add(endpoint);
12101194
return endpoint;
12111195
}
12121196

1213-
// Used only from within the [kContinueListen] function. When a preferred
1214-
// address has been provided, the hostname given must be resolved into an
1215-
// IP address, which must be passed on to #completeListen or the QuicSocket
1216-
// needs to be destroyed.
1217-
static [kAfterPreferredAddressLookup](
1218-
transportParams,
1219-
port,
1220-
type,
1221-
err,
1222-
address) {
1223-
if (err) {
1224-
this.destroy(err);
1225-
return;
1226-
}
1227-
this[kCompleteListen](transportParams, { address, port, type });
1228-
}
1229-
12301197
listen(options) {
12311198
const state = this[kInternalState];
12321199
if (state.listenPromise !== undefined)
@@ -1254,6 +1221,7 @@ class QuicSocket extends EventEmitter {
12541221
// The ALPN protocol identifier is strictly required.
12551222
const {
12561223
alpn,
1224+
lookup = state.lookup,
12571225
defaultEncoding,
12581226
highWaterMark,
12591227
transportParams,
@@ -1285,7 +1253,7 @@ class QuicSocket extends EventEmitter {
12851253
ip,
12861254
port,
12871255
type
1288-
} = await resolvePreferredAddress(state, transportParams.preferredAddress);
1256+
} = await resolvePreferredAddress(lookup, transportParams.preferredAddress);
12891257

12901258
// It's possible that the QuicSocket was destroyed or closed while
12911259
// the preferred address resolution was pending. Check for that and handle
@@ -1348,6 +1316,7 @@ class QuicSocket extends EventEmitter {
13481316
const {
13491317
type,
13501318
address,
1319+
lookup = state.lookup
13511320
} = validateQuicSocketConnectOptions(options);
13521321

13531322
await this[kMaybeBind]();

lib/internal/quic/util.js

+14-16
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ let dns;
123123

124124
function lazyDNS() {
125125
if (!dns)
126-
dns = require('dns');
126+
dns = require('dns').promises;
127127
return dns;
128128
}
129129

@@ -147,14 +147,9 @@ function getSocketType(type = 'udp4') {
147147
throw new ERR_INVALID_ARG_VALUE('options.type', type);
148148
}
149149

150-
function lookup4(address, callback) {
150+
function defaultLookup(address, type) {
151151
const { lookup } = lazyDNS();
152-
lookup(address || '127.0.0.1', 4, callback);
153-
}
154-
155-
function lookup6(address, callback) {
156-
const { lookup } = lazyDNS();
157-
lookup(address || '::1', 6, callback);
152+
return lookup(address || (type === 6 ? '::1' : '127.0.0.1'), type);
158153
}
159154

160155
function validateCloseCode(code) {
@@ -174,7 +169,7 @@ function validateCloseCode(code) {
174169

175170
function validateLookup(lookup) {
176171
if (lookup && typeof lookup !== 'function')
177-
throw new ERR_INVALID_ARG_TYPE('options.lookup', 'Function', lookup);
172+
throw new ERR_INVALID_ARG_TYPE('options.lookup', 'function', lookup);
178173
}
179174

180175
function validatePreferredAddress(address) {
@@ -360,8 +355,6 @@ function validateTransportParams(params) {
360355
}
361356

362357
function validateQuicClientSessionOptions(options = {}) {
363-
if (options !== null && typeof options !== 'object')
364-
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
365358
const {
366359
autoStart = true,
367360
address = 'localhost',
@@ -525,7 +518,7 @@ function validateQuicSocketOptions(options = {}) {
525518
client = {},
526519
disableStatelessReset = false,
527520
endpoint = { port: 0, type: 'udp4' },
528-
lookup,
521+
lookup = defaultLookup,
529522
maxConnections = DEFAULT_MAX_CONNECTIONS,
530523
maxConnectionsPerHost = DEFAULT_MAX_CONNECTIONS_PER_HOST,
531524
maxStatelessResetsPerHost = DEFAULT_MAX_STATELESS_RESETS_PER_HOST,
@@ -607,13 +600,15 @@ function validateQuicSocketListenOptions(options = {}) {
607600
highWaterMark,
608601
requestCert,
609602
rejectUnauthorized,
603+
lookup,
610604
} = options;
611605
validateString(alpn, 'options.alpn');
612606
if (rejectUnauthorized !== undefined)
613607
validateBoolean(rejectUnauthorized, 'options.rejectUnauthorized');
614608
if (requestCert !== undefined)
615609
validateBoolean(requestCert, 'options.requestCert');
616-
610+
if (lookup !== undefined)
611+
validateLookup(lookup);
617612
const transportParams =
618613
validateTransportParams(
619614
options,
@@ -622,6 +617,7 @@ function validateQuicSocketListenOptions(options = {}) {
622617

623618
return {
624619
alpn,
620+
lookup,
625621
rejectUnauthorized,
626622
requestCert,
627623
transportParams,
@@ -634,11 +630,14 @@ function validateQuicSocketConnectOptions(options = {}) {
634630
const {
635631
type = 'udp4',
636632
address,
633+
lookup,
637634
} = options;
638635
if (address !== undefined)
639636
validateString(address, 'options.address');
637+
if (lookup !== undefined)
638+
validateLookup(lookup);
640639
const [typeVal] = getSocketType(type);
641-
return { type: typeVal, address };
640+
return { type: typeVal, address, lookup };
642641
}
643642

644643
function validateCreateSecureContextOptions(options = {}) {
@@ -974,8 +973,7 @@ module.exports = {
974973
customInspect,
975974
getAllowUnauthorized,
976975
getSocketType,
977-
lookup4,
978-
lookup6,
976+
defaultLookup,
979977
setTransportParams,
980978
toggleListeners,
981979
validateNumber,

0 commit comments

Comments
 (0)