From 9e38cd1bb61c10aee484ac7374b0a4f75f8857ba Mon Sep 17 00:00:00 2001 From: Ingmar Van Glabbeek Date: Tue, 27 Oct 2026 20:00:42 -0700 Subject: [PATCH] dns: add resolveSvcb() and resolveHttps() Add support for the SVCB and HTTPS service binding records defined in RFC 9460 through dns.resolveSvcb(), dns.resolveHttps(), the matching Resolver and dns.promises methods, the 'SVCB' and 'HTTPS' rrtypes for dns.resolve(), and dns.resolveAny(). Known SvcParams (mandatory, alpn, no-default-alpn, port, ipv4hint, ech, ipv6hint, dohpath and ohttp) are decoded. All other keys, including private-use keys, are returned as keyNNNNN with the raw value as an ArrayBuffer. SvcParams on AliasMode records are ignored. Records with malformed SvcParams fail resolveSvcb() and resolveHttps() with EBADRESP and are dropped from resolveAny() results. Refs: https://github.com/nodejs/node/issues/38173 Refs: https://github.com/nodejs/node/issues/64280 Signed-off-by: Ingmar Van Glabbeek --- doc/api/dns.md | 158 +++++++++ lib/internal/dns/utils.js | 4 + src/cares_wrap.cc | 323 +++++++++++++++++++ src/cares_wrap.h | 2 + src/env_properties.h | 3 + test/common/dns.js | 22 ++ test/common/internet.js | 2 + test/internet/test-dns-any.js | 12 + test/internet/test-dns-cares-domains.js | 2 + test/internet/test-dns.js | 29 ++ test/internet/test-trace-events-dns.js | 2 + test/parallel/test-dns-resolve-null-bytes.js | 4 + test/parallel/test-dns-resolvesvcb.js | 216 +++++++++++++ 13 files changed, 779 insertions(+) create mode 100644 test/parallel/test-dns-resolvesvcb.js diff --git a/doc/api/dns.md b/doc/api/dns.md index d8b1839d9736..fd7e29c34499 100644 --- a/doc/api/dns.md +++ b/doc/api/dns.md @@ -123,12 +123,14 @@ The following methods from the `node:dns` module are available: * [`resolver.resolveAny()`][`dns.resolveAny()`] * [`resolver.resolveCaa()`][`dns.resolveCaa()`] * [`resolver.resolveCname()`][`dns.resolveCname()`] +* [`resolver.resolveHttps()`][`dns.resolveHttps()`] * [`resolver.resolveMx()`][`dns.resolveMx()`] * [`resolver.resolveNaptr()`][`dns.resolveNaptr()`] * [`resolver.resolveNs()`][`dns.resolveNs()`] * [`resolver.resolvePtr()`][`dns.resolvePtr()`] * [`resolver.resolveSoa()`][`dns.resolveSoa()`] * [`resolver.resolveSrv()`][`dns.resolveSrv()`] +* [`resolver.resolveSvcb()`][`dns.resolveSvcb()`] * [`resolver.resolveTlsa()`][`dns.resolveTlsa()`] * [`resolver.resolveTxt()`][`dns.resolveTxt()`] * [`resolver.reverse()`][`dns.reverse()`] @@ -445,12 +447,14 @@ records. The type and structure of individual results varies based on `rrtype`: | `'ANY'` | any records | {Object} | [`dns.resolveAny()`][] | | `'CAA'` | CA authorization records | {Object} | [`dns.resolveCaa()`][] | | `'CNAME'` | canonical name records | {string} | [`dns.resolveCname()`][] | +| `'HTTPS'` | HTTPS service bindings | {Object} | [`dns.resolveHttps()`][] | | `'MX'` | mail exchange records | {Object} | [`dns.resolveMx()`][] | | `'NAPTR'` | name authority pointer records | {Object} | [`dns.resolveNaptr()`][] | | `'NS'` | name server records | {string} | [`dns.resolveNs()`][] | | `'PTR'` | pointer records | {string} | [`dns.resolvePtr()`][] | | `'SOA'` | start of authority records | {Object} | [`dns.resolveSoa()`][] | | `'SRV'` | service records | {Object} | [`dns.resolveSrv()`][] | +| `'SVCB'` | service bindings | {Object} | [`dns.resolveSvcb()`][] | | `'TLSA'` | certificate associations | {Object} | [`dns.resolveTlsa()`][] | | `'TXT'` | text records | {string\[]} | [`dns.resolveTxt()`][] | @@ -546,12 +550,14 @@ will be present on the object: | `'AAAA'` | `address`/`ttl` | | `'CAA'` | Refer to [`dns.resolveCaa()`][] | | `'CNAME'` | `value` | +| `'HTTPS'` | Refer to [`dns.resolveHttps()`][] | | `'MX'` | Refer to [`dns.resolveMx()`][] | | `'NAPTR'` | Refer to [`dns.resolveNaptr()`][] | | `'NS'` | `value` | | `'PTR'` | `value` | | `'SOA'` | Refer to [`dns.resolveSoa()`][] | | `'SRV'` | Refer to [`dns.resolveSrv()`][] | +| `'SVCB'` | Refer to [`dns.resolveSvcb()`][] | | `'TLSA'` | Refer to [`dns.resolveTlsa()`][] | | `'TXT'` | This type of record contains an array property called `entries` which refers to [`dns.resolveTxt()`][], e.g. `{ entries: ['...'], type: 'TXT' }` | @@ -624,6 +630,26 @@ will contain an array of certification authority authorization records available for the `hostname` (e.g. `[{critical: 0, iodef: 'mailto:pki@example.com'}, {critical: 128, issue: 'pki.example.com'}]`). +## `dns.resolveHttps(hostname, callback)` + + + + + +* `hostname` {string} +* `callback` {Function} + * `err` {Error} + * `records` {Object\[]} + + + +Uses the DNS protocol to resolve `HTTPS` records ([RFC 9460][]) for the +`hostname`. `HTTPS` records use the same format as `SVCB` records, so the +`records` argument passed to the `callback` function has the same structure as +in [`dns.resolveSvcb()`][]. + ## `dns.resolveMx(hostname, callback)` + + + +* `hostname` {string} +* `callback` {Function} + * `err` {Error} + * `records` {Object\[]} + + + +Uses the DNS protocol to resolve service binding records (`SVCB` records, as +defined in [RFC 9460][]) for the `hostname`. The `records` argument passed to +the `callback` function is an array of objects with these properties: + +* `priority` {integer} The `SvcPriority`. `0` indicates AliasMode, in which + case `target` is an alias and `params` is empty. +* `target` {string} The `TargetName`. An empty string represents the root + name (`.`), which in ServiceMode means the owner name of the record itself. +* `params` {Object} The `SvcParams`, keyed by their presentation name. Known + keys are decoded as follows: + + * `mandatory` {string\[]} Names of the keys the client must understand. + * `alpn` {string\[]} ALPN protocol identifiers, e.g. `['h2', 'h3']`. + * `no-default-alpn` {boolean} Always `true` when present. + * `port` {integer} + * `ipv4hint` {string\[]} IPv4 addresses. + * `ech` {ArrayBuffer} The `ECHConfigList`. + * `ipv6hint` {string\[]} IPv6 addresses. + * `dohpath` {string} The DoH URI template ([RFC 9461][]). + * `ohttp` {boolean} Always `true` when present ([RFC 9540][]). + + Any other key is reported as `keyNNNNN`, where `NNNNN` is the numeric key, + with the raw value as an {ArrayBuffer}. This includes keys in the + private-use range (65280-65534). + +```js +({ + priority: 1, + target: 'svc.example.com', + params: { + alpn: ['h2', 'h3'], + port: 8443, + ipv4hint: ['192.0.2.1'], + key65400: [ArrayBuffer], + }, +}); +``` + +If any record in the response has a malformed `SvcParams` field, the query +fails with `EBADRESP`. In [`dns.resolveAny()`][] results, a malformed set of +`SVCB` or `HTTPS` records is omitted instead. + ## `dns.resolveTlsa(hostname, callback)` + +* `hostname` {string} + +Uses the DNS protocol to resolve `HTTPS` records ([RFC 9460][]) for the +`hostname`. On success, the `Promise` is resolved with an array of objects with +the same structure as in [`dnsPromises.resolveSvcb()`][]. + ### `dnsPromises.resolveMx(hostname)` + +* `hostname` {string} + +Uses the DNS protocol to resolve service binding records (`SVCB` records, as +defined in [RFC 9460][]) for the `hostname`. On success, the `Promise` is +resolved with an array of objects with these properties: + +* `priority` {integer} The `SvcPriority`. `0` indicates AliasMode, in which + case `target` is an alias and `params` is empty. +* `target` {string} The `TargetName`. An empty string represents the root + name (`.`), which in ServiceMode means the owner name of the record itself. +* `params` {Object} The `SvcParams`, keyed by their presentation name. Known + keys are decoded as follows: + + * `mandatory` {string\[]} Names of the keys the client must understand. + * `alpn` {string\[]} ALPN protocol identifiers, e.g. `['h2', 'h3']`. + * `no-default-alpn` {boolean} Always `true` when present. + * `port` {integer} + * `ipv4hint` {string\[]} IPv4 addresses. + * `ech` {ArrayBuffer} The `ECHConfigList`. + * `ipv6hint` {string\[]} IPv6 addresses. + * `dohpath` {string} The DoH URI template ([RFC 9461][]). + * `ohttp` {boolean} Always `true` when present ([RFC 9540][]). + + Any other key is reported as `keyNNNNN`, where `NNNNN` is the numeric key, + with the raw value as an {ArrayBuffer}. This includes keys in the + private-use range (65280-65534). + +```js +({ + priority: 1, + target: 'svc.example.com', + params: { + alpn: ['h2', 'h3'], + port: 8443, + ipv4hint: ['192.0.2.1'], + key65400: [ArrayBuffer], + }, +}); +``` + +If any record in the response has a malformed `SvcParams` field, the query +fails with `EBADRESP`. In [`dnsPromises.resolveAny()`][] results, a malformed +set of `SVCB` or `HTTPS` records is omitted instead. + ### `dnsPromises.resolveTlsa(hostname)`