tangerine 1.0.2 → 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 +41 -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,6 +283,31 @@ 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 (?)
@@ -702,7 +727,7 @@ class Resolver extends dns.promises.Resolver {
702
727
  //
703
728
  async #request(
704
729
  pkt,
705
- ip,
730
+ server,
706
731
  abortController,
707
732
  requestTimeout = this.options.timeout
708
733
  ) {
@@ -711,7 +736,8 @@ class Resolver extends dns.promises.Resolver {
711
736
 
712
737
  let localAddress;
713
738
  let localPort;
714
- if (isIPv4(ip)) {
739
+ let url = `${this.options.protocol}://${server}/dns-query`;
740
+ if (isIPv4(new URL(url).hostname)) {
715
741
  localAddress = this.options.ipv4;
716
742
  if (this.options.ipv4LocalPort) localPort = this.options.ipv4LocalPort;
717
743
  } else {
@@ -728,7 +754,6 @@ class Resolver extends dns.promises.Resolver {
728
754
  if (localPort) options.localPort = localPort;
729
755
 
730
756
  // <https://github.com/hildjj/dohdec/blob/43564118c40f2127af871bdb4d40f615409d4b9c/pkg/dohdec/lib/doh.js#L117-L120>
731
- let url = `${this.options.protocol}://${ip}/dns-query`;
732
757
  if (this.options.undici.method === 'GET') {
733
758
  if (!dohdec) await pWaitFor(() => Boolean(dohdec));
734
759
  url += `?dns=${dohdec.DNSoverHTTPS.base64urlEncode(pkt)}`;
@@ -765,7 +790,8 @@ class Resolver extends dns.promises.Resolver {
765
790
  let buffer;
766
791
  const errors = [];
767
792
  // NOTE: we would have used `p-map-series` but it did not support abort/break
768
- for (const ip of this.options.servers) {
793
+ const servers = [...this.options.servers];
794
+ for (const server of servers) {
769
795
  const ipErrors = [];
770
796
  for (let i = 0; i < this.options.tries; i++) {
771
797
  try {
@@ -773,7 +799,7 @@ class Resolver extends dns.promises.Resolver {
773
799
  // eslint-disable-next-line no-await-in-loop
774
800
  const response = await this.#request(
775
801
  pkt,
776
- ip,
802
+ server,
777
803
  abortController,
778
804
  this.options.timeout * 2 ** i
779
805
  );
@@ -828,7 +854,7 @@ class Resolver extends dns.promises.Resolver {
828
854
  // break out if we had a response
829
855
  if (buffer) break;
830
856
  if (ipErrors.length > 0) {
831
- // 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
832
858
  // (this ensures we don't keep retrying servers that keep timing out)
833
859
  // (which improves upon default c-ares behavior)
834
860
  if (this.options.servers.size > 1 && this.options.smartRotate) {
@@ -836,9 +862,9 @@ class Resolver extends dns.promises.Resolver {
836
862
  new Error('Rotating DNS servers due to issues'),
837
863
  ...ipErrors
838
864
  ]);
839
- this.options.logger.error(err, { ip });
840
- this.options.servers.delete(ip);
841
- this.options.servers.add(ip);
865
+ this.options.logger.error(err, { server });
866
+ this.options.servers.delete(server);
867
+ this.options.servers.add(server);
842
868
  }
843
869
 
844
870
  errors.push(...ipErrors);
@@ -1028,17 +1054,18 @@ class Resolver extends dns.promises.Resolver {
1028
1054
  }
1029
1055
 
1030
1056
  setServers(servers) {
1031
- if (
1032
- !Array.isArray(servers) ||
1033
- servers.length === 0 ||
1034
- servers.some((s) => typeof s !== 'string' || s.trim() === '' || !isIP(s))
1035
- ) {
1057
+ if (!Array.isArray(servers) || servers.length === 0) {
1036
1058
  const err = new TypeError(
1037
1059
  'The "name" argument must be an instance of Array.'
1038
1060
  );
1039
1061
  err.code = 'ERR_INVALID_ARG_TYPE';
1040
1062
  }
1041
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
+
1042
1069
  // <https://github.com/nodejs/node/blob/9bbde3d7baef584f14569ef79f116e9d288c7aaa/lib/internal/dns/utils.js#L87-L95>
1043
1070
  this.options.servers = new Set(servers);
1044
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.2",
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"