@blamejs/core 0.8.52 → 0.8.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/index.js +8 -0
  3. package/lib/audit.js +4 -0
  4. package/lib/auth/fido-mds3.js +624 -0
  5. package/lib/auth/passkey.js +214 -2
  6. package/lib/auth-bot-challenge.js +1 -1
  7. package/lib/credential-hash.js +2 -2
  8. package/lib/db-collection.js +290 -0
  9. package/lib/db-query.js +245 -0
  10. package/lib/db.js +173 -67
  11. package/lib/framework-error.js +55 -0
  12. package/lib/guard-cidr.js +2 -1
  13. package/lib/guard-jwt.js +2 -2
  14. package/lib/guard-oauth.js +2 -2
  15. package/lib/http-client-cache.js +916 -0
  16. package/lib/http-client.js +242 -0
  17. package/lib/mail-arf.js +343 -0
  18. package/lib/mail-auth.js +265 -40
  19. package/lib/mail-bimi.js +948 -33
  20. package/lib/mail-bounce.js +386 -4
  21. package/lib/mail-mdn.js +424 -0
  22. package/lib/mail-unsubscribe.js +265 -25
  23. package/lib/mail.js +403 -21
  24. package/lib/middleware/bearer-auth.js +1 -1
  25. package/lib/middleware/clear-site-data.js +122 -0
  26. package/lib/middleware/dpop.js +1 -1
  27. package/lib/middleware/index.js +9 -0
  28. package/lib/middleware/nel.js +214 -0
  29. package/lib/middleware/security-headers.js +56 -4
  30. package/lib/middleware/speculation-rules.js +323 -0
  31. package/lib/mime-parse.js +198 -0
  32. package/lib/mtls-ca.js +15 -5
  33. package/lib/network-dns.js +890 -27
  34. package/lib/network-tls.js +745 -0
  35. package/lib/object-store/sigv4.js +54 -0
  36. package/lib/public-suffix.js +414 -0
  37. package/lib/safe-buffer.js +7 -0
  38. package/lib/safe-json.js +1 -1
  39. package/lib/static.js +120 -0
  40. package/lib/storage.js +11 -0
  41. package/lib/vendor/MANIFEST.json +33 -0
  42. package/lib/vendor/bimi-trust-anchors.pem +33 -0
  43. package/lib/vendor/public-suffix-list.dat +16376 -0
  44. package/package.json +1 -1
  45. package/sbom.cyclonedx.json +6 -6
@@ -594,7 +594,7 @@ async function _dotLookup(host, family) {
594
594
  ready: new Promise(function (res, rej) {
595
595
  sock.once("secureConnect", function () { res(); });
596
596
  sock.once("error", function (e) {
597
- rej(new DnsError("dns/dot-handshake",
597
+ rej(new DnsError("dns/dot-handshake-failed",
598
598
  "DoT TLS handshake to " + STATE.dot.host + ":" + STATE.dot.port +
599
599
  " failed: " + ((e && e.message) || String(e))));
600
600
  });
@@ -671,6 +671,793 @@ function _resetDotPool() {
671
671
  for (var i = 0; i < keys.length; i++) _dotEvict(keys[i]);
672
672
  }
673
673
 
674
+ // ---- Generic DNS query (arbitrary QTYPE) -----------------------------
675
+ //
676
+ // The pre-v0.8.53 DNS module only handled A (1) and AAAA (28) lookups.
677
+ // SVCB (64) / HTTPS (65) and the DDR / DNR discovery primitives need a
678
+ // path that sends an arbitrary QTYPE and returns the raw rdata buffers
679
+ // for downstream parsing. These helpers reuse the existing encode +
680
+ // transport infrastructure (DoH / DoT / system) and add a small
681
+ // rdata-aware decoder that walks the answer section preserving the
682
+ // rdata bytes and answer offsets (needed because SVCB rdata contains
683
+ // compressed names that point back into the message).
684
+ //
685
+ // NOT IN SCOPE — DoQ (DNS-over-QUIC, RFC 9250). Node's QUIC support is
686
+ // experimental as of Node 24.x (tracking issue
687
+ // https://github.com/nodejs/node/issues/38478) and the framework's
688
+ // "no flag-gated experimental APIs in defaults" policy keeps it
689
+ // deferred. Operators wanting DoQ today wire it in their own agent
690
+ // and feed the returned IP set back through the existing transport
691
+ // abstractions (DDR-discovered DoH / DoT). Re-evaluate when Node
692
+ // flips QUIC stable.
693
+
694
+ // RFC 1035 §4.1.4 name compression — read a possibly-compressed name
695
+ // starting at `start`. Returns { name, nextOff } where nextOff is the
696
+ // byte immediately after the name's length-prefixed encoding (NOT
697
+ // chasing the pointer). `name` is a dot-joined string (without the
698
+ // trailing root label). Hardened against pointer loops with an
699
+ // iteration cap.
700
+ function _readDnsName(buf, start) {
701
+ var labels = [];
702
+ var off = start;
703
+ var nextOff = -1;
704
+ var iterations = 0;
705
+ var ITER_CAP = 256; // allow:raw-byte-literal — DNS name pointer-loop safeguard
706
+ while (off < buf.length && iterations < ITER_CAP) {
707
+ iterations += 1;
708
+ var len = buf[off];
709
+ if (len === 0) {
710
+ if (nextOff === -1) nextOff = off + 1;
711
+ break;
712
+ }
713
+ if ((len & 0xc0) === 0xc0) { // allow:raw-byte-literal — RFC 1035 name-compression pointer mask
714
+ if (off + 1 >= buf.length) {
715
+ throw new DnsError("dns/svcb-malformed",
716
+ "DNS name truncated at compression pointer");
717
+ }
718
+ if (nextOff === -1) nextOff = off + 2;
719
+ var ptr = ((len & 0x3f) << 8) | buf[off + 1]; // allow:raw-byte-literal — RFC 1035 pointer offset mask
720
+ if (ptr >= buf.length || ptr === off) {
721
+ throw new DnsError("dns/svcb-malformed",
722
+ "DNS name pointer out of bounds or self-referential");
723
+ }
724
+ off = ptr;
725
+ continue;
726
+ }
727
+ if ((len & 0xc0) !== 0) { // allow:raw-byte-literal — RFC 1035 reserved label-type bits
728
+ throw new DnsError("dns/svcb-malformed",
729
+ "DNS name has reserved label type 0x" + len.toString(HEX_RADIX));
730
+ }
731
+ if (off + 1 + len > buf.length) {
732
+ throw new DnsError("dns/svcb-malformed",
733
+ "DNS name label exceeds message length");
734
+ }
735
+ labels.push(buf.toString("ascii", off + 1, off + 1 + len));
736
+ off += 1 + len;
737
+ }
738
+ if (iterations >= ITER_CAP) {
739
+ throw new DnsError("dns/svcb-malformed",
740
+ "DNS name compression loop (>" + ITER_CAP + " hops)");
741
+ }
742
+ if (nextOff === -1) {
743
+ throw new DnsError("dns/svcb-malformed",
744
+ "DNS name not terminated");
745
+ }
746
+ return { name: labels.join("."), nextOff: nextOff };
747
+ }
748
+
749
+ // Walk the answer section preserving rdata offsets so SVCB rdata can
750
+ // resolve compressed names against the full message buffer.
751
+ function _decodeDnsAnswerRaw(buf) {
752
+ if (!Buffer.isBuffer(buf) || buf.length < 12) {
753
+ throw new DnsError("dns/bad-reply", "dns reply truncated");
754
+ }
755
+ var rcode = buf.readUInt8(3) & 0x0f; // allow:raw-byte-literal — RFC 1035 RCODE nibble mask
756
+ if (rcode !== 0) {
757
+ throw new DnsError("dns/no-result", "dns reply rcode " + rcode);
758
+ }
759
+ var qdcount = buf.readUInt16BE(4);
760
+ var ancount = buf.readUInt16BE(6);
761
+ var state = { off: 12 };
762
+ for (var q = 0; q < qdcount; q++) {
763
+ _skipDnsName(buf, state);
764
+ state.off += 4;
765
+ }
766
+ var answers = [];
767
+ for (var a = 0; a < ancount; a++) {
768
+ _skipDnsName(buf, state);
769
+ var off = state.off;
770
+ if (off + 10 > buf.length) {
771
+ throw new DnsError("dns/bad-reply", "answer record truncated");
772
+ }
773
+ var rtype = buf.readUInt16BE(off); off += 2;
774
+ var rclass = buf.readUInt16BE(off); off += 2;
775
+ var ttl = buf.readUInt32BE(off); off += 4;
776
+ var rdlen = buf.readUInt16BE(off); off += 2;
777
+ if (off + rdlen > buf.length) {
778
+ throw new DnsError("dns/bad-reply", "answer rdata truncated");
779
+ }
780
+ answers.push({
781
+ rtype: rtype,
782
+ rclass: rclass,
783
+ ttl: ttl,
784
+ rdataOff: off,
785
+ rdlen: rdlen,
786
+ });
787
+ off += rdlen;
788
+ state.off = off;
789
+ }
790
+ return { msg: buf, answers: answers, ad: _readAdBit(buf) };
791
+ }
792
+
793
+ async function _dohRawQuery(host, qtype) {
794
+ var enc = _encodeDnsQuery(host, qtype);
795
+ var b64 = enc.buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
796
+ var getUrl = STATE.doh.url + (STATE.doh.url.indexOf("?") === -1 ? "?" : "&") + "dns=" + b64;
797
+ var forcedMethod = STATE.doh.method;
798
+ var usePost = forcedMethod === "POST" || (!forcedMethod && getUrl.length > DOH_GET_URL_MAX_BYTES);
799
+ var u = safeUrl.parse(STATE.doh.url, { allowedProtocols: safeUrl.ALLOW_HTTP_TLS });
800
+ return new Promise(function (resolve, reject) {
801
+ var reqOpts = {
802
+ hostname: u.hostname,
803
+ port: u.port || 443, // allow:raw-byte-literal — HTTPS default port
804
+ path: u.pathname + u.search,
805
+ method: usePost ? "POST" : "GET",
806
+ headers: { "accept": "application/dns-message" },
807
+ minVersion: "TLSv1.3",
808
+ ecdhCurve: C.TLS_GROUP_CURVE_STR,
809
+ };
810
+ if (STATE.doh.ca) reqOpts.ca = STATE.doh.ca;
811
+ if (usePost) {
812
+ reqOpts.headers["content-type"] = "application/dns-message";
813
+ reqOpts.headers["content-length"] = enc.buf.length;
814
+ } else {
815
+ var parsedGet = safeUrl.parse(getUrl, { allowedProtocols: safeUrl.ALLOW_HTTP_TLS });
816
+ reqOpts.path = parsedGet.pathname + parsedGet.search;
817
+ }
818
+ var req = https.request(reqOpts, function (res) {
819
+ var collector = safeBuffer.boundedChunkCollector({
820
+ maxBytes: C.BYTES.kib(256),
821
+ errorClass: DnsError,
822
+ sizeCode: "dns/doh-too-large",
823
+ sizeMessage: "DoH response exceeds 256 KiB",
824
+ });
825
+ var pushFailed = null;
826
+ res.on("data", function (c) {
827
+ if (pushFailed) return;
828
+ try { collector.push(c); }
829
+ catch (e) { pushFailed = e; }
830
+ });
831
+ res.on("end", function () {
832
+ try {
833
+ if (pushFailed) { reject(pushFailed); return; }
834
+ if (res.statusCode !== 200) { // allow:raw-byte-literal — HTTP 200 OK
835
+ reject(new DnsError("dns/doh-http", "DoH HTTP " + res.statusCode + " for " + host));
836
+ return;
837
+ }
838
+ resolve(collector.result());
839
+ } catch (e) { reject(e); }
840
+ });
841
+ });
842
+ req.on("error", function (e) { reject(new DnsError("dns/doh-failed", "DoH request failed: " + e.message)); });
843
+ if (usePost) req.write(enc.buf);
844
+ req.end();
845
+ });
846
+ }
847
+
848
+ async function _dotRawQuery(host, qtype) {
849
+ var enc = _encodeDnsQuery(host, qtype);
850
+ var key = _dotPoolKey();
851
+ var entry = _dotPool.get(key);
852
+ if (entry && (Date.now() - entry.lastUsedAt > DOT_IDLE_TIMEOUT_MS)) {
853
+ _dotEvict(key);
854
+ entry = null;
855
+ }
856
+ if (!entry) {
857
+ var sock = _dotConnect();
858
+ entry = {
859
+ sock: sock,
860
+ lastUsedAt: Date.now(),
861
+ idle: true,
862
+ ready: new Promise(function (res, rej) {
863
+ sock.once("secureConnect", function () { res(); });
864
+ sock.once("error", function (e) {
865
+ rej(new DnsError("dns/dot-handshake-failed",
866
+ "DoT TLS handshake to " + STATE.dot.host + ":" + STATE.dot.port +
867
+ " failed: " + ((e && e.message) || String(e))));
868
+ });
869
+ }),
870
+ };
871
+ entry.ready.catch(function () { /* observed via per-lookup handler below */ });
872
+ _dotPool.set(key, entry);
873
+ sock.on("error", function () { _dotEvict(key); });
874
+ sock.on("close", function () { if (_dotPool.get(key) === entry) _dotPool.delete(key); });
875
+ }
876
+ var waitTicket = entry._tail || Promise.resolve();
877
+ entry._tail = waitTicket.then(function () {
878
+ return new Promise(function (resolve, reject) {
879
+ entry.idle = false;
880
+ try { entry.sock.ref(); } catch (_e) { /* best-effort event-loop hold */ }
881
+ Promise.resolve(entry.ready).then(function () {
882
+ var lenBuf = Buffer.alloc(2);
883
+ lenBuf.writeUInt16BE(enc.buf.length, 0);
884
+ var got = [];
885
+ var expectLen = -1;
886
+ var done = false;
887
+ function settle(err, val) {
888
+ if (done) return;
889
+ done = true;
890
+ entry.sock.removeListener("data", onData);
891
+ entry.sock.removeListener("error", onErr);
892
+ entry.idle = true;
893
+ entry.lastUsedAt = Date.now();
894
+ try { entry.sock.unref(); } catch (_e) { /* best-effort event-loop release */ }
895
+ if (err) reject(err); else resolve(val);
896
+ }
897
+ function onData(chunk) {
898
+ got.push(chunk);
899
+ var all = Buffer.concat(got);
900
+ if (expectLen === -1 && all.length >= 2) expectLen = all.readUInt16BE(0);
901
+ if (expectLen >= 0 && all.length >= expectLen + 2) {
902
+ settle(null, all.slice(2, 2 + expectLen));
903
+ }
904
+ }
905
+ function onErr(e) {
906
+ _dotEvict(key);
907
+ settle(new DnsError("dns/dot-failed", "DoT failed: " + e.message));
908
+ }
909
+ entry.sock.on("data", onData);
910
+ entry.sock.on("error", onErr);
911
+ entry.sock.write(lenBuf);
912
+ entry.sock.write(enc.buf);
913
+ }, function (handshakeErr) {
914
+ entry.idle = true;
915
+ try { entry.sock.unref(); } catch (_e) { /* best-effort event-loop release */ }
916
+ reject(handshakeErr);
917
+ });
918
+ });
919
+ });
920
+ return entry._tail;
921
+ }
922
+
923
+ async function _systemRawQuery(host, qtype) {
924
+ // node:dns doesn't expose arbitrary-QTYPE wire-format queries; fall
925
+ // back to TCP framed query against the configured system resolvers
926
+ // (port 53). Used only when the operator has explicitly opted out
927
+ // of DoH/DoT via useSystemResolver().
928
+ var servers = getServers();
929
+ if (servers.length === 0) {
930
+ throw new DnsError("dns/no-system-resolvers",
931
+ "system resolver has no configured servers; cannot send raw QTYPE query");
932
+ }
933
+ var serverEntry = servers[0];
934
+ var serverHost = serverEntry;
935
+ var serverPort = 53; // allow:raw-byte-literal — IANA-assigned DNS port
936
+ var bracketEnd = serverEntry.lastIndexOf("]:");
937
+ if (bracketEnd !== -1) {
938
+ serverHost = serverEntry.slice(1, bracketEnd);
939
+ serverPort = parseInt(serverEntry.slice(bracketEnd + 2), 10) || 53; // allow:raw-byte-literal — IANA-assigned DNS port
940
+ } else if (serverEntry.indexOf(":") !== -1 && net.isIP(serverEntry) === 0) {
941
+ var colonIdx = serverEntry.lastIndexOf(":");
942
+ serverHost = serverEntry.slice(0, colonIdx);
943
+ serverPort = parseInt(serverEntry.slice(colonIdx + 1), 10) || 53; // allow:raw-byte-literal — IANA-assigned DNS port
944
+ }
945
+ var enc = _encodeDnsQuery(host, qtype);
946
+ return new Promise(function (resolve, reject) {
947
+ var sock = net.connect({ host: serverHost, port: serverPort });
948
+ var got = [];
949
+ var expectLen = -1;
950
+ var done = false;
951
+ function settle(err, val) {
952
+ if (done) return;
953
+ done = true;
954
+ try { sock.destroy(); } catch (_e) { /* best-effort socket teardown */ }
955
+ if (err) reject(err); else resolve(val);
956
+ }
957
+ sock.on("connect", function () {
958
+ var lenBuf = Buffer.alloc(2);
959
+ lenBuf.writeUInt16BE(enc.buf.length, 0);
960
+ sock.write(lenBuf);
961
+ sock.write(enc.buf);
962
+ });
963
+ sock.on("data", function (chunk) {
964
+ got.push(chunk);
965
+ var all = Buffer.concat(got);
966
+ if (expectLen === -1 && all.length >= 2) expectLen = all.readUInt16BE(0);
967
+ if (expectLen >= 0 && all.length >= expectLen + 2) {
968
+ settle(null, all.slice(2, 2 + expectLen));
969
+ }
970
+ });
971
+ sock.on("error", function (e) {
972
+ settle(new DnsError("dns/system-failed", "system DNS TCP query failed: " + e.message));
973
+ });
974
+ sock.on("close", function () {
975
+ if (!done) settle(new DnsError("dns/system-failed", "system DNS TCP closed before reply"));
976
+ });
977
+ });
978
+ }
979
+
980
+ // Pick a transport for raw-QTYPE queries based on operator config.
981
+ // `forceTransport` (used by DDR) overrides; "system" routes through
982
+ // the OS resolver.
983
+ async function _rawQuery(host, qtype, forceTransport) {
984
+ _ensureSecureDefault();
985
+ var transport = forceTransport;
986
+ if (!transport) {
987
+ if (STATE.doh) transport = "doh";
988
+ else if (STATE.dot) transport = "dot";
989
+ else transport = "system";
990
+ }
991
+ if (transport === "doh") {
992
+ if (!STATE.doh) {
993
+ throw new DnsError("dns/transport-unavailable",
994
+ "raw query requested DoH transport but useDnsOverHttps() not configured");
995
+ }
996
+ return _withTimeout(_dohRawQuery(host, qtype), STATE.lookupTimeoutMs, host);
997
+ }
998
+ if (transport === "dot") {
999
+ if (!STATE.dot) {
1000
+ throw new DnsError("dns/transport-unavailable",
1001
+ "raw query requested DoT transport but useDnsOverTls() not configured");
1002
+ }
1003
+ return _withTimeout(_dotRawQuery(host, qtype), STATE.lookupTimeoutMs, host);
1004
+ }
1005
+ if (transport === "system") {
1006
+ return _withTimeout(_systemRawQuery(host, qtype), STATE.lookupTimeoutMs, host);
1007
+ }
1008
+ throw new DnsError("dns/bad-transport",
1009
+ "raw query: unknown transport '" + transport + "' (expected 'doh' | 'dot' | 'system')");
1010
+ }
1011
+
1012
+ // ---- SVCB / HTTPS RR (RFC 9460) --------------------------------------
1013
+
1014
+ var DNS_QTYPE_SVCB = 64; // allow:raw-byte-literal — RFC 9460 §14.1 SVCB record type code
1015
+ var DNS_QTYPE_HTTPS = 65; // allow:raw-byte-literal — RFC 9460 §14.1 HTTPS record type code
1016
+
1017
+ // SvcParamKey assignments (RFC 9460 §14.3.2 + IANA registry). Keys
1018
+ // past 7 are operator-extensible; we recognize the IETF-blessed set
1019
+ // and surface the rest as opaque buffers under params.unknown[<key>].
1020
+ var SVCB_KEY_MANDATORY = 0;
1021
+ var SVCB_KEY_ALPN = 1;
1022
+ var SVCB_KEY_NO_DEF_ALPN = 2;
1023
+ var SVCB_KEY_PORT = 3;
1024
+ var SVCB_KEY_IPV4HINT = 4;
1025
+ var SVCB_KEY_ECH = 5;
1026
+ var SVCB_KEY_IPV6HINT = 6;
1027
+ var SVCB_KEY_DOHPATH = 7; // allow:raw-byte-literal — RFC 9461 SvcParamKey
1028
+
1029
+ function _readCharString(buf, off, end) {
1030
+ if (off >= end) {
1031
+ throw new DnsError("dns/svcb-malformed", "alpn list truncated at char-string length");
1032
+ }
1033
+ var len = buf[off];
1034
+ if (off + 1 + len > end) {
1035
+ throw new DnsError("dns/svcb-malformed", "alpn char-string overflows alpn value");
1036
+ }
1037
+ return { value: buf.toString("utf8", off + 1, off + 1 + len), nextOff: off + 1 + len };
1038
+ }
1039
+
1040
+ function _parseSvcbRdata(msg, rdataOff, rdlen) {
1041
+ var end = rdataOff + rdlen;
1042
+ if (rdataOff + 2 > end) {
1043
+ throw new DnsError("dns/svcb-malformed", "SVCB rdata truncated before priority");
1044
+ }
1045
+ var priority = msg.readUInt16BE(rdataOff);
1046
+ var nameRes = _readDnsName(msg, rdataOff + 2);
1047
+ var target = nameRes.name === "" ? "." : nameRes.name;
1048
+ var off = nameRes.nextOff;
1049
+ var params = {};
1050
+ var prevKey = -1;
1051
+ while (off < end) {
1052
+ if (off + 4 > end) {
1053
+ throw new DnsError("dns/svcb-malformed", "SvcParam header truncated");
1054
+ }
1055
+ var key = msg.readUInt16BE(off); off += 2;
1056
+ var paramLen = msg.readUInt16BE(off); off += 2;
1057
+ if (off + paramLen > end) {
1058
+ throw new DnsError("dns/svcb-malformed", "SvcParam value overflows rdata");
1059
+ }
1060
+ if (key <= prevKey) {
1061
+ throw new DnsError("dns/svcb-malformed",
1062
+ "SvcParams not in ascending key order (key " + key + " after " + prevKey + ")");
1063
+ }
1064
+ prevKey = key;
1065
+ var paramEnd = off + paramLen;
1066
+ if (key === SVCB_KEY_MANDATORY) {
1067
+ if (paramLen % 2 !== 0) {
1068
+ throw new DnsError("dns/svcb-malformed", "mandatory SvcParam length not multiple of 2");
1069
+ }
1070
+ var mand = [];
1071
+ for (var mo = off; mo < paramEnd; mo += 2) {
1072
+ mand.push(msg.readUInt16BE(mo));
1073
+ }
1074
+ params.mandatory = mand;
1075
+ } else if (key === SVCB_KEY_ALPN) {
1076
+ var alpns = [];
1077
+ var ao = off;
1078
+ while (ao < paramEnd) {
1079
+ var cs = _readCharString(msg, ao, paramEnd);
1080
+ alpns.push(cs.value);
1081
+ ao = cs.nextOff;
1082
+ }
1083
+ params.alpn = alpns;
1084
+ } else if (key === SVCB_KEY_NO_DEF_ALPN) {
1085
+ if (paramLen !== 0) {
1086
+ throw new DnsError("dns/svcb-malformed", "no-default-alpn must have zero-length value");
1087
+ }
1088
+ params.noDefaultAlpn = true;
1089
+ } else if (key === SVCB_KEY_PORT) {
1090
+ if (paramLen !== 2) {
1091
+ throw new DnsError("dns/svcb-malformed", "port SvcParam must be 2 bytes");
1092
+ }
1093
+ params.port = msg.readUInt16BE(off);
1094
+ } else if (key === SVCB_KEY_IPV4HINT) {
1095
+ if (paramLen % 4 !== 0) {
1096
+ throw new DnsError("dns/svcb-malformed", "ipv4hint length not multiple of 4");
1097
+ }
1098
+ var v4 = [];
1099
+ for (var v4o = off; v4o < paramEnd; v4o += 4) {
1100
+ v4.push(msg[v4o] + "." + msg[v4o + 1] + "." + msg[v4o + 2] + "." + msg[v4o + 3]);
1101
+ }
1102
+ params.ipv4hint = v4;
1103
+ } else if (key === SVCB_KEY_ECH) {
1104
+ // ECHConfigList — opaque to the caller; surface as raw buffer.
1105
+ params.ech = Buffer.from(msg.slice(off, paramEnd));
1106
+ } else if (key === SVCB_KEY_IPV6HINT) {
1107
+ if (paramLen % IPV6_ADDR_BYTES !== 0) {
1108
+ throw new DnsError("dns/svcb-malformed", "ipv6hint length not multiple of 16");
1109
+ }
1110
+ var v6 = [];
1111
+ for (var v6o = off; v6o < paramEnd; v6o += IPV6_ADDR_BYTES) {
1112
+ var groups = [];
1113
+ for (var g = 0; g < IPV6_HEX_GROUPS; g++) {
1114
+ groups.push(msg.readUInt16BE(v6o + g * 2).toString(HEX_RADIX));
1115
+ }
1116
+ v6.push(groups.join(":"));
1117
+ }
1118
+ params.ipv6hint = v6;
1119
+ } else if (key === SVCB_KEY_DOHPATH) {
1120
+ params.dohpath = msg.toString("utf8", off, paramEnd);
1121
+ } else {
1122
+ // Unknown / future SvcParamKey — surface as opaque bytes so the
1123
+ // operator can still read it without us silently dropping the
1124
+ // record.
1125
+ if (!params.unknown) params.unknown = {};
1126
+ params.unknown[key] = Buffer.from(msg.slice(off, paramEnd));
1127
+ }
1128
+ off = paramEnd;
1129
+ }
1130
+ return { priority: priority, target: target, params: params };
1131
+ }
1132
+
1133
+ function _validateLdh(host, primitive) {
1134
+ if (typeof host !== "string" || host.length === 0 || host.length > 253) { // allow:raw-byte-literal — RFC 1035 hostname octet ceiling
1135
+ throw new DnsError("dns/bad-host",
1136
+ primitive + ": host must be a non-empty RFC 1035 LDH name (length 1..253)");
1137
+ }
1138
+ // Allow leading underscore on labels (SVCB / HTTPS query targets like
1139
+ // "_dns.resolver.arpa" require it).
1140
+ var labels = host.split(".");
1141
+ for (var li = 0; li < labels.length; li += 1) {
1142
+ var label = labels[li];
1143
+ if (label.length === 0 || label.length > 63) { // allow:raw-byte-literal — RFC 1035 max label length
1144
+ throw new DnsError("dns/bad-host",
1145
+ primitive + ": host label length must be 1..63");
1146
+ }
1147
+ if (!/^[A-Za-z0-9_](?:[A-Za-z0-9_-]*[A-Za-z0-9_])?$/.test(label)) {
1148
+ throw new DnsError("dns/bad-host",
1149
+ primitive + ": host label '" + label + "' violates LDH (allowed: letters/digits/underscore/hyphen, no leading/trailing hyphen)");
1150
+ }
1151
+ }
1152
+ }
1153
+
1154
+ async function _querySvcbLike(host, qtype, opts) {
1155
+ opts = opts || {};
1156
+ validateOpts(opts, ["transport"], "dns.querySvcb");
1157
+ _validateLdh(host, "dns.querySvcb");
1158
+ if (opts.transport !== undefined && opts.transport !== "doh" &&
1159
+ opts.transport !== "dot" && opts.transport !== "system") {
1160
+ throw new DnsError("dns/bad-transport",
1161
+ "dns.querySvcb: transport must be 'doh' | 'dot' | 'system' | undefined");
1162
+ }
1163
+ _emitObs("network.dns.svcb.requested", { qtype: qtype, transport: opts.transport || "auto" });
1164
+ var startMs = _now();
1165
+ var reply;
1166
+ try {
1167
+ reply = await _rawQuery(host, qtype, opts.transport);
1168
+ } catch (e) {
1169
+ _emitObs("network.dns.svcb.failure", {
1170
+ latencyMs: _now() - startMs,
1171
+ code: e.code || "unknown",
1172
+ });
1173
+ throw e;
1174
+ }
1175
+ var decoded = _decodeDnsAnswerRaw(reply);
1176
+ var records = [];
1177
+ for (var i = 0; i < decoded.answers.length; i++) {
1178
+ var ans = decoded.answers[i];
1179
+ if (ans.rtype !== qtype) continue;
1180
+ records.push(_parseSvcbRdata(decoded.msg, ans.rdataOff, ans.rdlen));
1181
+ }
1182
+ records.sort(function (a, b) { return a.priority - b.priority; });
1183
+ _emitObs("network.dns.svcb.success", {
1184
+ latencyMs: _now() - startMs,
1185
+ count: records.length,
1186
+ qtype: qtype,
1187
+ });
1188
+ return records;
1189
+ }
1190
+
1191
+ /**
1192
+ * @primitive b.network.dns.querySvcb
1193
+ * @signature b.network.dns.querySvcb(name, opts?)
1194
+ * @since 0.8.53
1195
+ * @status stable
1196
+ * @related b.network.dns.queryHttps, b.network.dns.discoverEncrypted
1197
+ *
1198
+ * Query SVCB records (RFC 9460 §2) for `name`. Returns an array of
1199
+ * `{ priority, target, params }` records sorted by priority. AliasMode
1200
+ * records (priority === 0) carry a `target` and empty `params` —
1201
+ * the caller chases the alias by re-querying the target. ServiceMode
1202
+ * records (priority > 0) carry SvcParams: `alpn` / `port` / `ipv4hint` /
1203
+ * `ipv6hint` / `ech` / `mandatory` / `dohpath`. Unknown SvcParamKeys
1204
+ * surface under `params.unknown[key]` as raw bytes — operators
1205
+ * implementing forward-compat can still read them. Malformed rdata
1206
+ * throws `DnsError` with code `dns/svcb-malformed`.
1207
+ *
1208
+ * @opts
1209
+ * {
1210
+ * transport: "doh" | "dot" | "system",
1211
+ * }
1212
+ *
1213
+ * @example
1214
+ * var b = require("@blamejs/core");
1215
+ * var rrs = await b.network.dns.querySvcb("_443._wss.example.com");
1216
+ */
1217
+ async function querySvcb(name, opts) {
1218
+ return _querySvcbLike(name, DNS_QTYPE_SVCB, opts);
1219
+ }
1220
+
1221
+ /**
1222
+ * @primitive b.network.dns.queryHttps
1223
+ * @signature b.network.dns.queryHttps(name, opts?)
1224
+ * @since 0.8.53
1225
+ * @status stable
1226
+ * @related b.network.dns.querySvcb
1227
+ *
1228
+ * Query HTTPS records (RFC 9460 §9). Identical to `querySvcb` except
1229
+ * the QTYPE is HTTPS (65) — the user-agent-facing variant of SVCB
1230
+ * for `https://` origins. Browsers query this for ECH discovery and
1231
+ * h3 advertisement; servers can call it to validate their own
1232
+ * published HTTPS RRset. Returns the same shape as `querySvcb`.
1233
+ *
1234
+ * @opts
1235
+ * {
1236
+ * transport: "doh" | "dot" | "system",
1237
+ * }
1238
+ *
1239
+ * @example
1240
+ * var b = require("@blamejs/core");
1241
+ * var rrs = await b.network.dns.queryHttps("example.com");
1242
+ */
1243
+ async function queryHttps(name, opts) {
1244
+ return _querySvcbLike(name, DNS_QTYPE_HTTPS, opts);
1245
+ }
1246
+
1247
+ // ---- DDR / DNR (RFC 9462 + RFC 9463) ---------------------------------
1248
+
1249
+ // Default DDR query target — RFC 9462 §3.
1250
+ var DDR_QUERY_NAME = "_dns.resolver.arpa";
1251
+
1252
+ // Operator-supplied designated resolver list. When set, the framework
1253
+ // prefers these over its own configured transport (subject to
1254
+ // `useDesignatedResolvers` having been called explicitly — never
1255
+ // silently overriding operator config).
1256
+ var _designatedResolvers = null;
1257
+
1258
+ /**
1259
+ * @primitive b.network.dns.discoverEncrypted
1260
+ * @signature b.network.dns.discoverEncrypted(opts?)
1261
+ * @since 0.8.53
1262
+ * @status stable
1263
+ * @related b.network.dns.useDesignatedResolvers, b.network.dns.querySvcb
1264
+ *
1265
+ * RFC 9462 Discovery of Designated Resolvers. Queries
1266
+ * `_dns.resolver.arpa` for SVCB records that advertise encrypted DNS
1267
+ * alternatives (DoH / DoT) hosted by the network's currently-configured
1268
+ * Do53 resolver. Returns a list of resolver descriptors with
1269
+ * `{ transport, alpn, target, port, dohpath, ipv4hint, ipv6hint, priority }`.
1270
+ *
1271
+ * The discovery query goes through the system resolver by default
1272
+ * (RFC 9462 §4 — DDR validation requires the response to come from
1273
+ * the Do53 resolver whose IP we compare). Callers that already have
1274
+ * a trusted DoH / DoT transport configured can pass
1275
+ * `{ insecureSystemResolverOnly: false }` to allow DDR via the
1276
+ * encrypted transport too.
1277
+ *
1278
+ * Throws `DnsError` with code `dns/ddr-not-discovered` when the
1279
+ * resolver does not publish DDR records.
1280
+ *
1281
+ * @opts
1282
+ * {
1283
+ * name: string,
1284
+ * insecureSystemResolverOnly: boolean,
1285
+ * }
1286
+ *
1287
+ * @example
1288
+ * var b = require("@blamejs/core");
1289
+ * var resolvers = await b.network.dns.discoverEncrypted();
1290
+ */
1291
+ async function discoverEncrypted(opts) {
1292
+ opts = opts || {};
1293
+ validateOpts(opts, ["name", "insecureSystemResolverOnly"], "dns.discoverEncrypted");
1294
+ var name = opts.name || DDR_QUERY_NAME;
1295
+ if (typeof name !== "string" || name.length === 0) {
1296
+ throw new DnsError("dns/bad-host",
1297
+ "dns.discoverEncrypted: name must be a non-empty string");
1298
+ }
1299
+ var insecureOnly = opts.insecureSystemResolverOnly !== false;
1300
+ var transport = insecureOnly ? "system" : undefined;
1301
+ _validateLdh(name, "dns.discoverEncrypted");
1302
+ var startMs = _now();
1303
+ var records;
1304
+ try {
1305
+ records = await _querySvcbLike(name, DNS_QTYPE_SVCB, { transport: transport });
1306
+ } catch (e) {
1307
+ _emitObs("network.dns.ddr.failure", {
1308
+ latencyMs: _now() - startMs,
1309
+ code: e.code || "unknown",
1310
+ });
1311
+ if (e.code === "dns/no-result") {
1312
+ throw new DnsError("dns/ddr-not-discovered",
1313
+ "dns.discoverEncrypted: resolver did not publish DDR records at " + name);
1314
+ }
1315
+ throw e;
1316
+ }
1317
+ if (records.length === 0) {
1318
+ _emitObs("network.dns.ddr.empty", { latencyMs: _now() - startMs });
1319
+ throw new DnsError("dns/ddr-not-discovered",
1320
+ "dns.discoverEncrypted: resolver returned empty DDR record set at " + name);
1321
+ }
1322
+ var resolvers = [];
1323
+ for (var i = 0; i < records.length; i++) {
1324
+ var rec = records[i];
1325
+ if (rec.priority === 0) continue; // AliasMode — caller chases
1326
+ var alpn = (rec.params && rec.params.alpn) || [];
1327
+ var isDot = alpn.indexOf("dot") !== -1;
1328
+ var isDoh = alpn.indexOf("h2") !== -1 || alpn.indexOf("h3") !== -1 ||
1329
+ (rec.params && typeof rec.params.dohpath === "string");
1330
+ var transportKind = isDot ? "dot" : (isDoh ? "doh" : null);
1331
+ if (!transportKind) continue;
1332
+ resolvers.push({
1333
+ transport: transportKind,
1334
+ alpn: alpn,
1335
+ target: rec.target,
1336
+ port: (rec.params && rec.params.port) ||
1337
+ (transportKind === "dot" ? 853 : 443), // allow:raw-byte-literal — IANA-assigned DoT/HTTPS ports
1338
+ dohpath: (rec.params && rec.params.dohpath) || null,
1339
+ ipv4hint: (rec.params && rec.params.ipv4hint) || [],
1340
+ ipv6hint: (rec.params && rec.params.ipv6hint) || [],
1341
+ priority: rec.priority,
1342
+ });
1343
+ }
1344
+ resolvers.sort(function (a, b) { return a.priority - b.priority; });
1345
+ if (resolvers.length === 0) {
1346
+ throw new DnsError("dns/ddr-not-discovered",
1347
+ "dns.discoverEncrypted: DDR records present but none advertised a recognized transport (alpn=dot/h2/h3)");
1348
+ }
1349
+ _emitObs("network.dns.ddr.success", {
1350
+ latencyMs: _now() - startMs,
1351
+ count: resolvers.length,
1352
+ });
1353
+ return resolvers;
1354
+ }
1355
+
1356
+ /**
1357
+ * @primitive b.network.dns.useDesignatedResolvers
1358
+ * @signature b.network.dns.useDesignatedResolvers(list)
1359
+ * @since 0.8.53
1360
+ * @status stable
1361
+ * @related b.network.dns.discoverEncrypted, b.network.dns.querySvcb
1362
+ *
1363
+ * RFC 9463 Discovery of Network-designated Resolvers. The framework
1364
+ * doesn't run a DHCP / IPv6 RA client itself; an operator-side agent
1365
+ * (or the output of `discoverEncrypted()`) supplies the resolver list
1366
+ * and the framework swaps its transport over to the lowest-priority
1367
+ * entry. Items are tried in order: the first one that successfully
1368
+ * configures (DoH `useDnsOverHttps`, DoT `useDnsOverTls`) wins.
1369
+ *
1370
+ * Each entry shape:
1371
+ *
1372
+ * {
1373
+ * transport: "doh" | "dot",
1374
+ * url: string,
1375
+ * host: string,
1376
+ * port: number,
1377
+ * servername: string,
1378
+ * alpn: Array<string>,
1379
+ * ca: string|Buffer|Array,
1380
+ * }
1381
+ *
1382
+ * Throws `DnsError` with code `dns/dnr-no-resolvers` if `list` is
1383
+ * empty, and `dns/dnr-malformed` if an entry is missing its required
1384
+ * transport-specific fields.
1385
+ *
1386
+ * @example
1387
+ * var b = require("@blamejs/core");
1388
+ * var found = await b.network.dns.discoverEncrypted();
1389
+ * b.network.dns.useDesignatedResolvers(found.map(function (r) {
1390
+ * return r.transport === "doh"
1391
+ * ? { transport: "doh", url: "https://" + r.target + (r.dohpath || "/dns-query") }
1392
+ * : { transport: "dot", host: r.target, port: r.port, servername: r.target };
1393
+ * }));
1394
+ */
1395
+ function useDesignatedResolvers(list) {
1396
+ if (!Array.isArray(list) || list.length === 0) {
1397
+ throw new DnsError("dns/dnr-no-resolvers",
1398
+ "dns.useDesignatedResolvers: expected non-empty array of resolver descriptors");
1399
+ }
1400
+ var validated = [];
1401
+ for (var i = 0; i < list.length; i++) {
1402
+ var entry = list[i];
1403
+ if (!entry || typeof entry !== "object") {
1404
+ throw new DnsError("dns/dnr-malformed",
1405
+ "dns.useDesignatedResolvers[" + i + "]: entry must be an object");
1406
+ }
1407
+ if (entry.transport !== "doh" && entry.transport !== "dot") {
1408
+ throw new DnsError("dns/dnr-malformed",
1409
+ "dns.useDesignatedResolvers[" + i + "]: transport must be 'doh' or 'dot'");
1410
+ }
1411
+ if (entry.transport === "doh") {
1412
+ if (typeof entry.url !== "string" || entry.url.indexOf("https://") !== 0) {
1413
+ throw new DnsError("dns/dnr-malformed",
1414
+ "dns.useDesignatedResolvers[" + i + "]: doh entry requires url starting with https://");
1415
+ }
1416
+ } else {
1417
+ if (typeof entry.host !== "string" || entry.host.length === 0) {
1418
+ throw new DnsError("dns/dnr-malformed",
1419
+ "dns.useDesignatedResolvers[" + i + "]: dot entry requires host");
1420
+ }
1421
+ }
1422
+ validated.push(entry);
1423
+ }
1424
+ var lastErr = null;
1425
+ for (var j = 0; j < validated.length; j++) {
1426
+ var v = validated[j];
1427
+ try {
1428
+ if (v.transport === "doh") {
1429
+ useDnsOverHttps({ url: v.url, ca: v.ca || null, method: v.method });
1430
+ } else {
1431
+ useDnsOverTls({
1432
+ host: v.host,
1433
+ port: v.port || 853, // allow:raw-byte-literal — IANA-assigned DoT port
1434
+ servername: v.servername || v.host,
1435
+ ca: v.ca || null,
1436
+ });
1437
+ }
1438
+ _designatedResolvers = validated.slice();
1439
+ _emitObs("network.dns.dnr.set", {
1440
+ count: validated.length,
1441
+ active: j,
1442
+ transport: v.transport,
1443
+ });
1444
+ return { active: j, count: validated.length };
1445
+ } catch (e) {
1446
+ lastErr = e;
1447
+ _emitObs("network.dns.dnr.entry_failed", {
1448
+ index: j,
1449
+ transport: v.transport,
1450
+ code: e.code || "unknown",
1451
+ });
1452
+ }
1453
+ }
1454
+ throw new DnsError("dns/dnr-no-resolvers",
1455
+ "dns.useDesignatedResolvers: no entry could be configured. Last error: " +
1456
+ ((lastErr && lastErr.message) || "unknown"));
1457
+ }
1458
+
1459
+ function _designatedResolversForTest() { return _designatedResolvers; }
1460
+
674
1461
  function _orderAddrs(addrs) {
675
1462
  if (STATE.resultOrder === "ipv6first") {
676
1463
  addrs.sort(function (a, b) { return (b.family || 0) - (a.family || 0); });
@@ -750,7 +1537,8 @@ async function lookup(host, opts) {
750
1537
  }
751
1538
  }
752
1539
 
753
- async function _resolveProtocol(host, family) {
1540
+ async function _resolveProtocol(host, family, opts) {
1541
+ opts = opts || {};
754
1542
  if (typeof host !== "string" || host.length === 0) {
755
1543
  throw new DnsError("dns/bad-host", "dns.resolve" + family + ": host required");
756
1544
  }
@@ -760,13 +1548,27 @@ async function _resolveProtocol(host, family) {
760
1548
  }
761
1549
  return [host];
762
1550
  }
763
- _emitObs("network.dns.resolve.requested", { family: family });
1551
+ if (opts.transport !== undefined && opts.transport !== "doh" &&
1552
+ opts.transport !== "dot" && opts.transport !== "system") {
1553
+ throw new DnsError("dns/bad-transport",
1554
+ "dns.resolve" + family + ": transport must be 'doh' | 'dot' | 'system' | undefined");
1555
+ }
1556
+ _emitObs("network.dns.resolve.requested", { family: family, transport: opts.transport || "auto" });
764
1557
  var startMs = _now();
765
1558
  try {
766
1559
  var addrs;
767
- if (STATE.doh) {
1560
+ var forced = opts.transport;
1561
+ if (forced === "doh" || (!forced && STATE.doh)) {
1562
+ if (!STATE.doh) {
1563
+ throw new DnsError("dns/transport-unavailable",
1564
+ "dns.resolve" + family + ": transport 'doh' requested but useDnsOverHttps() not configured");
1565
+ }
768
1566
  addrs = await _withTimeout(_dohLookup(host, family), STATE.lookupTimeoutMs, host);
769
- } else if (STATE.dot) {
1567
+ } else if (forced === "dot" || (!forced && STATE.dot)) {
1568
+ if (!STATE.dot) {
1569
+ throw new DnsError("dns/transport-unavailable",
1570
+ "dns.resolve" + family + ": transport 'dot' requested but useDnsOverTls() not configured");
1571
+ }
770
1572
  addrs = await _withTimeout(_dotLookup(host, family), STATE.lookupTimeoutMs, host);
771
1573
  } else {
772
1574
  var resolver = family === 6 ? dnsPromises.resolve6 : dnsPromises.resolve4;
@@ -787,9 +1589,59 @@ async function _resolveProtocol(host, family) {
787
1589
  }
788
1590
  }
789
1591
 
790
- async function resolve4(host) { return _resolveProtocol(host, 4); }
791
- async function resolve6(host) { return _resolveProtocol(host, 6); }
792
- async function resolveAaaa(host) { return _resolveProtocol(host, 6); }
1592
+ async function resolve4(host, opts) { return _resolveProtocol(host, 4, opts); }
1593
+ async function resolve6(host, opts) { return _resolveProtocol(host, 6, opts); }
1594
+ async function resolveAaaa(host, opts) { return _resolveProtocol(host, 6, opts); }
1595
+
1596
+ // Generic resolve API surfacing the transport opt + record type.
1597
+ // `type` defaults to "A"; "AAAA" routes through resolve6; SVCB / HTTPS
1598
+ // types route through the new querySvcb / queryHttps primitives.
1599
+ async function resolve(host, type, opts) {
1600
+ type = (type || "A").toUpperCase();
1601
+ if (type === "A") return _resolveProtocol(host, 4, opts);
1602
+ if (type === "AAAA") return _resolveProtocol(host, 6, opts);
1603
+ if (type === "SVCB") return querySvcb(host, opts);
1604
+ if (type === "HTTPS") return queryHttps(host, opts);
1605
+ throw new DnsError("dns/unsupported-type",
1606
+ "dns.resolve: type must be 'A' | 'AAAA' | 'SVCB' | 'HTTPS' (got " + JSON.stringify(type) + ")");
1607
+ }
1608
+
1609
+ // PTR lookup — given a v4 or v6 IP literal, return the list of names
1610
+ // the in-addr.arpa / ip6.arpa zones map back to. Building block for
1611
+ // FCrDNS (forward-confirmed reverse DNS, RFC 8601 §3 lite) callers
1612
+ // and the outbound-mail iprev surface — the PTR query plus the
1613
+ // matching forward A/AAAA query share this DnsError class.
1614
+ //
1615
+ // dnsPromises.reverse() doesn't honor the DoH/DoT transports (those
1616
+ // transports query A/AAAA/TXT via wire format; PTR queries take a
1617
+ // separate code path). For now this routes through the system
1618
+ // resolver — operators who require ALL DNS over secure transport
1619
+ // wrap the surface with their own resolver.
1620
+ async function reverse(ip) {
1621
+ if (typeof ip !== "string" || ip.length === 0) {
1622
+ throw new DnsError("dns/bad-ip", "dns.reverse: ip must be a non-empty string");
1623
+ }
1624
+ if (!net.isIP(ip)) {
1625
+ throw new DnsError("dns/bad-ip",
1626
+ "dns.reverse: '" + ip + "' is not a valid IPv4 or IPv6 address");
1627
+ }
1628
+ _emitObs("network.dns.reverse.requested", { family: net.isIPv6(ip) ? 6 : 4 });
1629
+ var startMs = _now();
1630
+ try {
1631
+ var ptrs = await _withTimeout(dnsPromises.reverse(ip), STATE.lookupTimeoutMs, ip);
1632
+ _emitObs("network.dns.reverse.success", {
1633
+ latencyMs: _now() - startMs, count: Array.isArray(ptrs) ? ptrs.length : 0,
1634
+ });
1635
+ return Array.isArray(ptrs) ? ptrs : [];
1636
+ } catch (e) {
1637
+ _emitObs("network.dns.reverse.failure", {
1638
+ latencyMs: _now() - startMs, code: e.code || "unknown",
1639
+ });
1640
+ if (e instanceof DnsError) throw e;
1641
+ throw new DnsError("dns/reverse-failed",
1642
+ "dns.reverse of '" + ip + "' failed: " + (e.message || String(e)));
1643
+ }
1644
+ }
793
1645
 
794
1646
  function nodeLookup(host, options, callback) {
795
1647
  if (typeof options === "function") { callback = options; options = {}; }
@@ -813,28 +1665,39 @@ function _resetForTest() {
813
1665
  STATE.servers = null; STATE.resultOrder = null; STATE.family = 0;
814
1666
  STATE.lookupTimeoutMs = 0; STATE.cacheTtlMs = 0; STATE.cacheNegativeTtlMs = 0;
815
1667
  STATE.doh = null; STATE.dot = null; STATE.systemResolver = false;
1668
+ _designatedResolvers = null;
816
1669
  _clearCache();
817
1670
  _resetDotPool();
818
1671
  }
819
1672
 
820
1673
  module.exports = {
821
- setServers: setServers,
822
- getServers: getServers,
823
- setResultOrder: setResultOrder,
824
- setFamily: setFamily,
825
- setLookupTimeoutMs: setLookupTimeoutMs,
826
- setCacheTtlMs: setCacheTtlMs,
827
- useDnsOverHttps: useDnsOverHttps,
828
- useDnsOverTls: useDnsOverTls,
829
- useSystemResolver: useSystemResolver,
830
- lookup: lookup,
831
- resolve4: resolve4,
832
- resolve6: resolve6,
833
- resolveAaaa: resolveAaaa,
834
- resolveSecure: resolveSecure,
835
- nodeLookup: nodeLookup,
836
- clearCache: _clearCache,
837
- DnsError: DnsError,
838
- _stateForTest: _stateForTest,
839
- _resetForTest: _resetForTest,
1674
+ setServers: setServers,
1675
+ getServers: getServers,
1676
+ setResultOrder: setResultOrder,
1677
+ setFamily: setFamily,
1678
+ setLookupTimeoutMs: setLookupTimeoutMs,
1679
+ setCacheTtlMs: setCacheTtlMs,
1680
+ useDnsOverHttps: useDnsOverHttps,
1681
+ useDnsOverTls: useDnsOverTls,
1682
+ useSystemResolver: useSystemResolver,
1683
+ useDesignatedResolvers: useDesignatedResolvers,
1684
+ discoverEncrypted: discoverEncrypted,
1685
+ lookup: lookup,
1686
+ resolve: resolve,
1687
+ resolve4: resolve4,
1688
+ resolve6: resolve6,
1689
+ resolveAaaa: resolveAaaa,
1690
+ resolveSecure: resolveSecure,
1691
+ reverse: reverse,
1692
+ querySvcb: querySvcb,
1693
+ queryHttps: queryHttps,
1694
+ nodeLookup: nodeLookup,
1695
+ clearCache: _clearCache,
1696
+ DnsError: DnsError,
1697
+ _parseSvcbRdata: _parseSvcbRdata,
1698
+ _decodeDnsAnswerRaw: _decodeDnsAnswerRaw,
1699
+ _readDnsName: _readDnsName,
1700
+ _stateForTest: _stateForTest,
1701
+ _resetForTest: _resetForTest,
1702
+ _designatedResolversForTest: _designatedResolversForTest,
840
1703
  };