tangerine 1.4.8 → 1.5.0
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 +43 -0
- package/index.js +101 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
* [`tangerine.reverse(ip[, abortController, purgeCache])`](#tangerinereverseip-abortcontroller-purgecache)
|
|
57
57
|
* [`tangerine.setDefaultResultOrder(order)`](#tangerinesetdefaultresultorderorder)
|
|
58
58
|
* [`tangerine.setServers(servers)`](#tangerinesetserversservers)
|
|
59
|
+
* [`tangerine.spoofPacket(hostname, rrtype, answers)`](#tangerinespoofpackethostname-rrtype-answers)
|
|
59
60
|
* [Options](#options)
|
|
60
61
|
* [Cache](#cache)
|
|
61
62
|
* [Compatibility](#compatibility)
|
|
@@ -331,6 +332,48 @@ This mirrors output from <https://github.com/rthalley/dnspython>.
|
|
|
331
332
|
|
|
332
333
|
### `tangerine.setServers(servers)`
|
|
333
334
|
|
|
335
|
+
### `tangerine.spoofPacket(hostname, rrtype, answers)`
|
|
336
|
+
|
|
337
|
+
This method is useful for writing tests to spoof DNS packets in-memory.
|
|
338
|
+
|
|
339
|
+
The `rrtype` must be either `"TXT"` or `"MX"`, and `answers` must be an Array of DNS resource record answers.
|
|
340
|
+
|
|
341
|
+
For example, if you want to spoof TXT and MX records:
|
|
342
|
+
|
|
343
|
+
```js
|
|
344
|
+
const Redis = require('ioredis-mock');
|
|
345
|
+
const Tangerine = require('tangerine');
|
|
346
|
+
const ip = require('ip');
|
|
347
|
+
|
|
348
|
+
const cache = new Redis();
|
|
349
|
+
const tangerine = new Tangerine({ cache });
|
|
350
|
+
|
|
351
|
+
const obj = {};
|
|
352
|
+
|
|
353
|
+
obj['txt:forwardmail.net'] = tangerine.spoofPacket('forwardmail.net', 'TXT', [
|
|
354
|
+
`v=spf1 ip4:${ip.address()} -all`
|
|
355
|
+
]);
|
|
356
|
+
|
|
357
|
+
obj['mx:forwardemail.net'] = tangerine.spoofPacket('forwardemail.net', 'MX', [
|
|
358
|
+
{ exchange: 'mx1.forwardemail.net', preference: 0 },
|
|
359
|
+
{ exchange: 'mx2.forwardemail.net', preference: 0 }
|
|
360
|
+
]);
|
|
361
|
+
|
|
362
|
+
await cache.mset(obj);
|
|
363
|
+
|
|
364
|
+
//
|
|
365
|
+
// NOTE: spoofed values are used below (this means no DNS query performed)
|
|
366
|
+
//
|
|
367
|
+
|
|
368
|
+
const txt = await tangerine.resolveTxt('forwardemail.net');
|
|
369
|
+
console.log('txt', txt);
|
|
370
|
+
|
|
371
|
+
const mx = await tangerine.resolveMx('forwardemail.net');
|
|
372
|
+
console.log('mx', mx);
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Pull requests are welcome to add support for other `rrtype` values for this method.**
|
|
376
|
+
|
|
334
377
|
|
|
335
378
|
## Options
|
|
336
379
|
|
package/index.js
CHANGED
|
@@ -1428,6 +1428,74 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
1428
1428
|
this.options.servers = new Set(servers);
|
|
1429
1429
|
}
|
|
1430
1430
|
|
|
1431
|
+
spoofPacket(name, rrtype, answers = []) {
|
|
1432
|
+
if (typeof name !== 'string') {
|
|
1433
|
+
const err = new TypeError('The "name" argument must be of type string.');
|
|
1434
|
+
err.code = 'ERR_INVALID_ARG_TYPE';
|
|
1435
|
+
throw err;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
if (typeof rrtype !== 'string') {
|
|
1439
|
+
const err = new TypeError(
|
|
1440
|
+
'The "rrtype" argument must be of type string.'
|
|
1441
|
+
);
|
|
1442
|
+
err.code = 'ERR_INVALID_ARG_TYPE';
|
|
1443
|
+
throw err;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
if (!this.constructor.TYPES.has(rrtype)) {
|
|
1447
|
+
const err = new TypeError("The argument 'rrtype' is invalid.");
|
|
1448
|
+
err.code = 'ERR_INVALID_ARG_VALUE';
|
|
1449
|
+
throw err;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
if (!Array.isArray(answers)) {
|
|
1453
|
+
const err = new TypeError("The argument 'answers' is invalid.");
|
|
1454
|
+
err.code = 'ERR_INVALID_ARG_VALUE';
|
|
1455
|
+
throw err;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
return {
|
|
1459
|
+
id: 0,
|
|
1460
|
+
type: 'response',
|
|
1461
|
+
flags: 384,
|
|
1462
|
+
flag_qr: true,
|
|
1463
|
+
opcode: 'QUERY',
|
|
1464
|
+
flag_aa: false,
|
|
1465
|
+
flag_tc: false,
|
|
1466
|
+
flag_rd: true,
|
|
1467
|
+
flag_ra: true,
|
|
1468
|
+
flag_z: false,
|
|
1469
|
+
flag_ad: false,
|
|
1470
|
+
flag_cd: false,
|
|
1471
|
+
rcode: 'NOERROR',
|
|
1472
|
+
questions: [{ name, type: rrtype, class: 'IN' }],
|
|
1473
|
+
answers: answers.map((answer) => ({
|
|
1474
|
+
name,
|
|
1475
|
+
type: rrtype,
|
|
1476
|
+
ttl: 300,
|
|
1477
|
+
class: 'IN',
|
|
1478
|
+
flush: false,
|
|
1479
|
+
data: rrtype === 'TXT' ? [answer] : answer
|
|
1480
|
+
})),
|
|
1481
|
+
authorities: [],
|
|
1482
|
+
additionals: [
|
|
1483
|
+
{
|
|
1484
|
+
name: '.',
|
|
1485
|
+
type: 'OPT',
|
|
1486
|
+
udpPayloadSize: 1232,
|
|
1487
|
+
extendedRcode: 0,
|
|
1488
|
+
ednsVersion: 0,
|
|
1489
|
+
flags: 0,
|
|
1490
|
+
flag_do: false,
|
|
1491
|
+
options: [Array]
|
|
1492
|
+
}
|
|
1493
|
+
],
|
|
1494
|
+
ttl: 300,
|
|
1495
|
+
expires: Date.now() + 10000
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1431
1499
|
// eslint-disable-next-line complexity
|
|
1432
1500
|
async resolve(name, rrtype = 'A', options = {}, abortController) {
|
|
1433
1501
|
if (typeof name !== 'string') {
|
|
@@ -1481,6 +1549,17 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
1481
1549
|
// (this saves us from duplicating the same `...sort().filter(Number.isFinite)` logic)
|
|
1482
1550
|
//
|
|
1483
1551
|
data = await this.options.cache.get(key);
|
|
1552
|
+
//
|
|
1553
|
+
// if it's not an object then assume that
|
|
1554
|
+
// the cache implementation does not have JSON.parse built-in
|
|
1555
|
+
// and so we should try to parse it on our own (user-friendly)
|
|
1556
|
+
//
|
|
1557
|
+
if (typeof data === 'string') {
|
|
1558
|
+
try {
|
|
1559
|
+
data = JSON.parse(data);
|
|
1560
|
+
} catch {}
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1484
1563
|
// safeguard in case cache pollution
|
|
1485
1564
|
if (data && typeof data === 'object') {
|
|
1486
1565
|
debug('cache retrieved', key);
|
|
@@ -1847,19 +1926,30 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
1847
1926
|
// if it returns answers with `type: TLSA` then recursively lookup
|
|
1848
1927
|
// 3 1 1 D6FEA64D4E68CAEAB7CBB2E0F905D7F3CA3308B12FD88C5B469F08AD 7E05C7C7
|
|
1849
1928
|
return result.answers.map((answer) => {
|
|
1850
|
-
|
|
1851
|
-
throw new Error('Buffer was not available');
|
|
1852
|
-
|
|
1853
|
-
// <https://www.mailhardener.com/kb/dane>
|
|
1854
|
-
return {
|
|
1929
|
+
const obj = {
|
|
1855
1930
|
name: answer.name,
|
|
1856
|
-
ttl: answer.ttl
|
|
1857
|
-
// <https://github.com/rthalley/dnspython/blob/98b12e9e43847dac615bb690355d2fabaff969d2/dns/rdtypes/tlsabase.py#L35>
|
|
1858
|
-
usage: answer.data.subarray(0, 1).readUInt8(),
|
|
1859
|
-
selector: answer.data.subarray(1, 2).readUInt8(),
|
|
1860
|
-
mtype: answer.data.subarray(2, 3).readUInt8(),
|
|
1861
|
-
cert: answer.data.subarray(3)
|
|
1931
|
+
ttl: answer.ttl
|
|
1862
1932
|
};
|
|
1933
|
+
|
|
1934
|
+
// <https://www.mailhardener.com/kb/dane>
|
|
1935
|
+
// <https://github.com/rthalley/dnspython/blob/98b12e9e43847dac615bb690355d2fabaff969d2/dns/rdtypes/tlsabase.py#L35>
|
|
1936
|
+
if (Buffer.isBuffer(answer.data)) {
|
|
1937
|
+
obj.usage = answer.data.subarray(0, 1).readUInt8();
|
|
1938
|
+
obj.selector = answer.data.subarray(1, 2).readUInt8();
|
|
1939
|
+
obj.mtype = answer.data.subarray(2, 3).readUInt8();
|
|
1940
|
+
obj.cert = answer.data.subarray(3);
|
|
1941
|
+
} else {
|
|
1942
|
+
obj.usage = answer.data.usage;
|
|
1943
|
+
obj.selector = answer.data.selector;
|
|
1944
|
+
obj.mtype = answer.data.matchingType;
|
|
1945
|
+
obj.cert = answer.data.certificate;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// aliases to match Cloudflare DNS response
|
|
1949
|
+
obj.matchingType = obj.mtype;
|
|
1950
|
+
obj.certificate = obj.cert;
|
|
1951
|
+
|
|
1952
|
+
return obj;
|
|
1863
1953
|
});
|
|
1864
1954
|
}
|
|
1865
1955
|
|
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
|
+
"version": "1.5.0",
|
|
5
5
|
"author": "Forward Email (https://forwardemail.net)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/forwardemail/tangerine/issues"
|