tangerine 1.0.1 → 1.0.3

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 (3) hide show
  1. package/README.md +1 -1
  2. package/index.js +43 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -255,7 +255,7 @@ Similar to the `options` argument from `new dns.promises.Resolver(options)` invo
255
255
  | ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
256
256
  | `timeout` | `Number` | `5000` | Number of milliseconds for requests to timeout. |
257
257
  | `tries` | `Number` | `4` | Number of tries per `server` in `servers` to attempt. |
258
- | `servers` | `Set` | `new Set(['1.1.1.1', '1.0.0.1'])` | A set containing IP addresses for DNS queries. Defaults to Cloudflare's of `1.1.1.1` and `1.0.0.1`. |
258
+ | `servers` | `Set` or `Array` | `new Set(['1.1.1.1', '1.0.0.1'])` | A Set or Array of [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6) formatted addresses for DNS queries (matches default Node.js dns module behavior). Duplicates will be removed as this is converted to a `Set` internally. Defaults to Cloudflare's of `1.1.1.1` and `1.0.0.1`. If an `Array` is passed, then it will be converted to a `Set`. |
259
259
  | `undici` | `Object` | Defaults to an Object with `undici.method` and `undici.headers` properties and values below | Default options to pass to [undici](https://github.com/nodejs/undici). |
260
260
  | `undici.method` | `String` | Defaults to `"GET"` (must be either `"GET"` or `"POST"`). | Default HTTP method to use for DNS over HTTP ("DoH") requests. |
261
261
  | `undici.headers` | `Object` | Defaults to `{ 'content-type': 'application/dns-message', 'user-agent': pkg.name + "/" + pkg.version, accept: 'application/dns-message' }`. | Default HTTP headers to use for DNS over HTTP ("DoH") requests. |
package/index.js CHANGED
@@ -283,9 +283,36 @@ class Resolver extends dns.promises.Resolver {
283
283
  options
284
284
  );
285
285
 
286
+ // timeout must be >= 0
287
+ if (!Number.isFinite(this.options.timeout) || this.options.timeout < 0)
288
+ throw new Error('Timeout must be >= 0');
289
+
290
+ // tries must be >= 1
291
+ if (!Number.isFinite(this.options.tries) || this.options.tries < 1)
292
+ throw new Error('Tries must be >= 1');
293
+
294
+ // perform validation by re-using `setServers` method
295
+ this.setServers([...this.options.servers]);
296
+
297
+ if (
298
+ !(this.options.servers instanceof Set) ||
299
+ this.options.servers.size === 0
300
+ )
301
+ throw new Error(
302
+ 'Servers must be an Array or Set with at least one server'
303
+ );
304
+
305
+ if (!['http', 'https'].includes(this.options.protocol))
306
+ throw new Error('Protocol must be http or https');
307
+
308
+ if (!['verbatim', 'ipv4first'].includes(this.options.dnsOrder))
309
+ throw new Error('DNS order must be either verbatim or ipv4first');
310
+
286
311
  // if `cache: false` then caching is disabled
287
312
  // but note that this doesn't disable `got` dnsCache which is separate
288
313
  // so to turn that off, you need to supply `dnsCache: undefined` in `got` object (?)
314
+ if (this.options.cache === true) this.options.cache = new Map();
315
+
289
316
  if (this.options.cache instanceof Map) {
290
317
  // each of the types have their own Keyv with prefix
291
318
  for (const type of this.constructor.TYPES) {
@@ -700,7 +727,7 @@ class Resolver extends dns.promises.Resolver {
700
727
  //
701
728
  async #request(
702
729
  pkt,
703
- ip,
730
+ server,
704
731
  abortController,
705
732
  requestTimeout = this.options.timeout
706
733
  ) {
@@ -709,7 +736,8 @@ class Resolver extends dns.promises.Resolver {
709
736
 
710
737
  let localAddress;
711
738
  let localPort;
712
- if (isIPv4(ip)) {
739
+ let url = `${this.options.protocol}://${server}/dns-query`;
740
+ if (isIPv4(new URL(url).hostname)) {
713
741
  localAddress = this.options.ipv4;
714
742
  if (this.options.ipv4LocalPort) localPort = this.options.ipv4LocalPort;
715
743
  } else {
@@ -726,7 +754,6 @@ class Resolver extends dns.promises.Resolver {
726
754
  if (localPort) options.localPort = localPort;
727
755
 
728
756
  // <https://github.com/hildjj/dohdec/blob/43564118c40f2127af871bdb4d40f615409d4b9c/pkg/dohdec/lib/doh.js#L117-L120>
729
- let url = `${this.options.protocol}://${ip}/dns-query`;
730
757
  if (this.options.undici.method === 'GET') {
731
758
  if (!dohdec) await pWaitFor(() => Boolean(dohdec));
732
759
  url += `?dns=${dohdec.DNSoverHTTPS.base64urlEncode(pkt)}`;
@@ -763,7 +790,8 @@ class Resolver extends dns.promises.Resolver {
763
790
  let buffer;
764
791
  const errors = [];
765
792
  // NOTE: we would have used `p-map-series` but it did not support abort/break
766
- for (const ip of this.options.servers) {
793
+ const servers = [...this.options.servers];
794
+ for (const server of servers) {
767
795
  const ipErrors = [];
768
796
  for (let i = 0; i < this.options.tries; i++) {
769
797
  try {
@@ -771,7 +799,7 @@ class Resolver extends dns.promises.Resolver {
771
799
  // eslint-disable-next-line no-await-in-loop
772
800
  const response = await this.#request(
773
801
  pkt,
774
- ip,
802
+ server,
775
803
  abortController,
776
804
  this.options.timeout * 2 ** i
777
805
  );
@@ -826,7 +854,7 @@ class Resolver extends dns.promises.Resolver {
826
854
  // break out if we had a response
827
855
  if (buffer) break;
828
856
  if (ipErrors.length > 0) {
829
- // if the `ip` had all errors, then remove it and add to end
857
+ // if the `server` had all errors, then remove it and add to end
830
858
  // (this ensures we don't keep retrying servers that keep timing out)
831
859
  // (which improves upon default c-ares behavior)
832
860
  if (this.options.servers.size > 1 && this.options.smartRotate) {
@@ -834,9 +862,9 @@ class Resolver extends dns.promises.Resolver {
834
862
  new Error('Rotating DNS servers due to issues'),
835
863
  ...ipErrors
836
864
  ]);
837
- this.options.logger.error(err, { ip });
838
- this.options.servers.delete(ip);
839
- this.options.servers.add(ip);
865
+ this.options.logger.error(err, { server });
866
+ this.options.servers.delete(server);
867
+ this.options.servers.add(server);
840
868
  }
841
869
 
842
870
  errors.push(...ipErrors);
@@ -1026,17 +1054,18 @@ class Resolver extends dns.promises.Resolver {
1026
1054
  }
1027
1055
 
1028
1056
  setServers(servers) {
1029
- if (
1030
- !Array.isArray(servers) ||
1031
- servers.length === 0 ||
1032
- servers.some((s) => typeof s !== 'string' || s.trim() === '' || !isIP(s))
1033
- ) {
1057
+ if (!Array.isArray(servers) || servers.length === 0) {
1034
1058
  const err = new TypeError(
1035
1059
  'The "name" argument must be an instance of Array.'
1036
1060
  );
1037
1061
  err.code = 'ERR_INVALID_ARG_TYPE';
1038
1062
  }
1039
1063
 
1064
+ //
1065
+ // TODO: every address must be ipv4 or ipv6 (use `new URL` to parse and check)
1066
+ // servers [ string ] - array of RFC 5952 formatted addresses
1067
+ //
1068
+
1040
1069
  // <https://github.com/nodejs/node/blob/9bbde3d7baef584f14569ef79f116e9d288c7aaa/lib/internal/dns/utils.js#L87-L95>
1041
1070
  this.options.servers = new Set(servers);
1042
1071
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tangerine",
3
3
  "description": "Tangerine is the best Node.js drop-in replacement for dns.promises.Resolver using DNS over HTTPS (\"DoH\") via undici with built-in retries, timeouts, smart server rotation, AbortControllers, and caching support for multiple backends via Keyv.",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "author": "Forward Email (https://forwardemail.net)",
6
6
  "bugs": {
7
7
  "url": "https://github.com/forwardemail/tangerine/issues"