tangerine 1.4.6 → 1.4.7
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.
- package/README.md +10 -0
- package/index.js +67 -46
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
* [`tangerine.setServers(servers)`](#tangerinesetserversservers)
|
|
59
59
|
* [Options](#options)
|
|
60
60
|
* [Cache](#cache)
|
|
61
|
+
* [Compatibility](#compatibility)
|
|
61
62
|
* [Debugging](#debugging)
|
|
62
63
|
* [Benchmarks](#benchmarks)
|
|
63
64
|
* [Tangerine Benchmarks](#tangerine-benchmarks)
|
|
@@ -430,6 +431,15 @@ await tangerine.resolve('forwardemail.net'); // uses cached value
|
|
|
430
431
|
This purge cache feature is useful for DNS records that have recently changed and have had their caches purged at the relevant DNS provider (e.g. [Cloudflare's Purge Cache tool](https://1.1.1.1/purge-cache/)).
|
|
431
432
|
|
|
432
433
|
|
|
434
|
+
## Compatibility
|
|
435
|
+
|
|
436
|
+
The only known compatibility issue is for locally running DNS servers that have wildcard DNS matching.
|
|
437
|
+
|
|
438
|
+
If you are using `dnsmasq` with a wildcard match on "localhost" to "127.0.0.1", then the results may vary. For example, if your `dnsmasq` configuration has `address=/localhost/127.0.0.1`, then any match of `localhost` will resolve to `127.0.0.1`. This means that `dns.promises.lookup('foo.localhost')` will return `127.0.0.1` – however with :tangerine: Tangerine it will not return a value.
|
|
439
|
+
|
|
440
|
+
The reason is because :tangerine: Tangerine only looks at either `/etc/hosts` (macOS/Linux) and `C:/Windows/System32/drivers/etc/hosts` (Windows). It does not lookup BIND, dnsmasq, or other configurations running locally. We would welcome a PR to resolve this (see `isCI` usage in test folder) – however it is a non-issue, as the workaround is to simply append a new line to the hostfile of `127.0.0.1 foo.localhost`.
|
|
441
|
+
|
|
442
|
+
|
|
433
443
|
## Debugging
|
|
434
444
|
|
|
435
445
|
If you run into issues while using :tangerine: Tangerine, then these recommendations may help:
|
package/index.js
CHANGED
|
@@ -20,20 +20,12 @@ const pWaitFor = require('p-wait-for');
|
|
|
20
20
|
const packet = require('dns-packet');
|
|
21
21
|
const semver = require('semver');
|
|
22
22
|
const structuredClone = require('@ungap/structured-clone').default;
|
|
23
|
-
const { Hosts } = require('hosts-parser');
|
|
24
23
|
const { getService } = require('port-numbers');
|
|
25
24
|
|
|
26
25
|
const pkg = require('./package.json');
|
|
27
26
|
|
|
28
27
|
const debug = debuglog('tangerine');
|
|
29
28
|
|
|
30
|
-
const hosts = new Hosts(
|
|
31
|
-
hostile
|
|
32
|
-
.get()
|
|
33
|
-
.map((arr) => arr.join(' '))
|
|
34
|
-
.join('\n')
|
|
35
|
-
);
|
|
36
|
-
|
|
37
29
|
// dynamically import dohdec
|
|
38
30
|
let dohdec;
|
|
39
31
|
// eslint-disable-next-line unicorn/prefer-top-level-await
|
|
@@ -41,8 +33,32 @@ import('dohdec').then((obj) => {
|
|
|
41
33
|
dohdec = obj;
|
|
42
34
|
});
|
|
43
35
|
|
|
36
|
+
// dynamically import private-ip
|
|
37
|
+
let isPrivateIP;
|
|
38
|
+
// eslint-disable-next-line unicorn/prefer-top-level-await
|
|
39
|
+
import('private-ip').then((obj) => {
|
|
40
|
+
isPrivateIP = obj.default;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const HOSTFILE = hostile
|
|
44
|
+
.get(true)
|
|
45
|
+
.map((s) => (Array.isArray(s) ? s.join(' ') : s))
|
|
46
|
+
.join('\n');
|
|
47
|
+
|
|
48
|
+
const HOSTS = [];
|
|
49
|
+
const hosts = hostile.get();
|
|
50
|
+
for (const line of hosts) {
|
|
51
|
+
const [ip, str] = line;
|
|
52
|
+
const hosts = str.split(' ');
|
|
53
|
+
HOSTS.push({ ip, hosts });
|
|
54
|
+
}
|
|
55
|
+
|
|
44
56
|
// <https://github.com/szmarczak/cacheable-lookup/pull/76>
|
|
45
57
|
class Tangerine extends dns.promises.Resolver {
|
|
58
|
+
static HOSTFILE = HOSTFILE;
|
|
59
|
+
|
|
60
|
+
static HOSTS = HOSTS;
|
|
61
|
+
|
|
46
62
|
static isValidPort(port) {
|
|
47
63
|
return Number.isSafeInteger(port) && port >= 0 && port <= 65535;
|
|
48
64
|
}
|
|
@@ -616,6 +632,16 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
616
632
|
throw err;
|
|
617
633
|
}
|
|
618
634
|
|
|
635
|
+
if (name === '.') {
|
|
636
|
+
const err = this.constructor.createError(name, '', dns.NOTFOUND);
|
|
637
|
+
// remap and perform syscall
|
|
638
|
+
err.syscall = 'getaddrinfo';
|
|
639
|
+
err.message = err.message.replace('query', 'getaddrinfo');
|
|
640
|
+
err.errno = -3008; // <-- ?
|
|
641
|
+
// err.errno = -3007;
|
|
642
|
+
throw err;
|
|
643
|
+
}
|
|
644
|
+
|
|
619
645
|
// purge cache support
|
|
620
646
|
let purgeCache;
|
|
621
647
|
if (options?.purgeCache) {
|
|
@@ -662,25 +688,22 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
662
688
|
let resolve4;
|
|
663
689
|
let resolve6;
|
|
664
690
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
rule.ip !== name
|
|
670
|
-
)
|
|
671
|
-
continue;
|
|
691
|
+
const lower = name.toLowerCase();
|
|
692
|
+
|
|
693
|
+
for (const rule of this.constructor.HOSTS) {
|
|
694
|
+
if (rule.hosts.every((h) => h.toLowerCase() !== lower)) continue;
|
|
672
695
|
const type = isIP(rule.ip);
|
|
673
|
-
if (!resolve4 && type === 4)
|
|
674
|
-
|
|
675
|
-
|
|
696
|
+
if (!resolve4 && type === 4) {
|
|
697
|
+
if (!Array.isArray(resolve4)) resolve4 = [rule.ip];
|
|
698
|
+
else if (!resolve4.includes(rule.ip)) resolve4.push([rule.ip]);
|
|
699
|
+
} else if (!resolve6 && type === 6) {
|
|
700
|
+
if (!Array.isArray(resolve6)) resolve6 = [rule.ip];
|
|
701
|
+
else if (!resolve6.includes(rule.ip)) resolve6.push(rule.ip);
|
|
702
|
+
}
|
|
676
703
|
}
|
|
677
704
|
|
|
678
|
-
//
|
|
679
|
-
|
|
680
|
-
if (
|
|
681
|
-
name.toLowerCase() === 'localhost' ||
|
|
682
|
-
name.toLowerCase() === 'localhost.'
|
|
683
|
-
) {
|
|
705
|
+
// safeguard (matches c-ares)
|
|
706
|
+
if (lower === 'localhost' || lower === 'localhost.') {
|
|
684
707
|
if (!resolve4) resolve4 = ['127.0.0.1'];
|
|
685
708
|
if (!resolve6) resolve6 = ['::1'];
|
|
686
709
|
}
|
|
@@ -733,25 +756,6 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
733
756
|
throw err;
|
|
734
757
|
}
|
|
735
758
|
|
|
736
|
-
/*
|
|
737
|
-
//
|
|
738
|
-
// NOTE: we probably should handle this differently (?)
|
|
739
|
-
// (not sure what native nodejs dns module does for different errors - haven't checked yet)
|
|
740
|
-
//
|
|
741
|
-
if (errors.every((e) => e.code !== 'ENODATA')) {
|
|
742
|
-
const err = this.constructor.combineErrors(errors);
|
|
743
|
-
err.hostname = name;
|
|
744
|
-
// remap and perform syscall
|
|
745
|
-
err.syscall = 'getaddrinfo';
|
|
746
|
-
err.message = err.message.replace('query', 'getaddrinfo');
|
|
747
|
-
if (!err.code)
|
|
748
|
-
err.code = errors.find((e) => e.code)?.code || dns.BADRESP;
|
|
749
|
-
if (!err.errno)
|
|
750
|
-
err.errno = errors.find((e) => e.errno)?.errno || undefined;
|
|
751
|
-
throw err;
|
|
752
|
-
}
|
|
753
|
-
*/
|
|
754
|
-
|
|
755
759
|
// default node behavior seems to return IPv4 by default always regardless
|
|
756
760
|
if (answers.length > 0)
|
|
757
761
|
answers =
|
|
@@ -927,8 +931,25 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
927
931
|
throw err;
|
|
928
932
|
}
|
|
929
933
|
|
|
930
|
-
// edge case where localhost IP returns
|
|
931
|
-
if (
|
|
934
|
+
// edge case where localhost IP returns matches
|
|
935
|
+
if (!isPrivateIP) await pWaitFor(() => Boolean(isPrivateIP));
|
|
936
|
+
|
|
937
|
+
const answers = new Set();
|
|
938
|
+
let match = false;
|
|
939
|
+
|
|
940
|
+
for (const rule of this.constructor.HOSTS) {
|
|
941
|
+
if (rule.ip === ip) {
|
|
942
|
+
match = true;
|
|
943
|
+
for (const host of rule.hosts.slice(1)) {
|
|
944
|
+
answers.add(host);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (answers.size > 0 || match) return [...answers];
|
|
950
|
+
|
|
951
|
+
// NOTE: we can prob remove this (?)
|
|
952
|
+
// if (ip === '::1' || ip === '127.0.0.1') return [];
|
|
932
953
|
|
|
933
954
|
// reverse the IP address
|
|
934
955
|
if (!dohdec) await pWaitFor(() => Boolean(dohdec));
|
|
@@ -1430,7 +1451,7 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
1430
1451
|
|
|
1431
1452
|
// edge case where c-ares detects "." as start of string
|
|
1432
1453
|
// <https://github.com/c-ares/c-ares/blob/38b30bc922c21faa156939bde15ea35332c30e08/src/lib/ares_getaddrinfo.c#L829>
|
|
1433
|
-
if (name.startsWith('.') || name.includes('..'))
|
|
1454
|
+
if (name !== '.' && (name.startsWith('.') || name.includes('..')))
|
|
1434
1455
|
throw this.constructor.createError(name, rrtype, dns.BADNAME);
|
|
1435
1456
|
|
|
1436
1457
|
// purge cache support
|
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 (with TTL and purge support).",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.7",
|
|
5
5
|
"author": "Forward Email (https://forwardemail.net)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/forwardemail/tangerine/issues"
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
"dohdec": "^5.0.3",
|
|
17
17
|
"get-stream": "6",
|
|
18
18
|
"hostile": "^1.3.3",
|
|
19
|
-
"hosts-parser": "^0.3.2",
|
|
20
19
|
"ipaddr.js": "^2.0.1",
|
|
21
20
|
"merge-options": "3.0.4",
|
|
22
21
|
"p-map": "4",
|
|
23
22
|
"p-timeout": "4",
|
|
24
23
|
"p-wait-for": "3",
|
|
25
24
|
"port-numbers": "^6.0.1",
|
|
25
|
+
"private-ip": "^3.0.0",
|
|
26
26
|
"punycode": "^2.3.0",
|
|
27
27
|
"semver": "^7.3.8"
|
|
28
28
|
},
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"husky": "^8.0.3",
|
|
42
42
|
"ioredis": "^5.3.1",
|
|
43
43
|
"ioredis-mock": "^8.2.6",
|
|
44
|
+
"is-ci": "^3.0.1",
|
|
44
45
|
"lint-staged": "^13.1.2",
|
|
45
46
|
"lodash": "^4.17.21",
|
|
46
47
|
"nock": "^13.3.0",
|