dns2 2.1.0 → 2.3.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 +95 -7
- package/client/doh.js +80 -40
- package/client/google.js +1 -1
- package/client/tcp.js +43 -28
- package/client/udp.js +81 -13
- package/example/client/doh.js +18 -8
- package/index.js +27 -17
- package/lib/proxy-protocol.js +153 -0
- package/lib/reader.js +4 -2
- package/lib/writer.js +0 -1
- package/package.json +26 -12
- package/packet.js +314 -65
- package/server/dns.js +19 -2
- package/server/doh.js +9 -8
- package/server/tcp.js +72 -1
- package/server/udp.js +28 -4
- package/ts/index.d.ts +371 -0
- package/ts/tsconfig.json +13 -0
- package/ts/typings-check.ts +124 -0
- package/.eslintrc +0 -16
- package/.github/FUNDING.yml +0 -12
- package/.github/workflows/lint.js.yml +0 -17
- package/.github/workflows/node.js.yml +0 -29
- package/SECURITY.md +0 -21
- package/test/index.js +0 -413
- package/test/test.js +0 -34
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# dns2
|
|
2
2
|
|
|
3
|
-
![NPM version]
|
|
4
|
-
[](https://github.com/song940/node-dns/actions/workflows/node.js.yml)
|
|
3
|
+
![NPM version][npm-img] [![Build Status][ci-img]][ci-url]
|
|
5
4
|
|
|
6
5
|
> A DNS Server and Client Implementation in Pure JavaScript with no dependencies.
|
|
7
6
|
|
|
@@ -27,10 +26,9 @@ DNS client will use UDP by default.
|
|
|
27
26
|
const dns2 = require('dns2');
|
|
28
27
|
|
|
29
28
|
const options = {
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// recursive: Recursion Desired flag (boolean, default true, since > v1.4.2)
|
|
29
|
+
// nameServers: ['8.8.8.8'] — array of DNS server IPs (default: Google + 114dns)
|
|
30
|
+
// port: 53 — DNS server port (number)
|
|
31
|
+
// recursive: true — Recursion Desired flag (boolean, default true)
|
|
34
32
|
};
|
|
35
33
|
const dns = new dns2(options);
|
|
36
34
|
|
|
@@ -40,6 +38,47 @@ const dns = new dns2(options);
|
|
|
40
38
|
})();
|
|
41
39
|
```
|
|
42
40
|
|
|
41
|
+
The high-level `DNS` class exposes convenience methods for common record types:
|
|
42
|
+
|
|
43
|
+
| Method | Record type | Key answer fields |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `resolveA(domain)` | A | `address` |
|
|
46
|
+
| `resolveAAAA(domain)` | AAAA | `address` |
|
|
47
|
+
| `resolveMX(domain)` | MX | `exchange`, `priority` |
|
|
48
|
+
| `resolveCNAME(domain)` | CNAME | `domain` |
|
|
49
|
+
| `resolveSOA(domain)` | SOA | `primary`, `admin`, `serial`, `refresh`, `retry`, `expiration`, `minimum` |
|
|
50
|
+
| `resolvePTR(domain)` | PTR | `domain` |
|
|
51
|
+
| `resolveDNSKEY(domain)` | DNSKEY | `publicKey`, `algorithm` |
|
|
52
|
+
| `resolveRRSIG(domain)` | RRSIG | varies |
|
|
53
|
+
|
|
54
|
+
For any record type not listed above, use `dns.resolve(domain, 'TYPE')` directly.
|
|
55
|
+
|
|
56
|
+
#### Example: SOA record lookup
|
|
57
|
+
|
|
58
|
+
SOA (Start of Authority) records contain the authoritative zone information for a domain.
|
|
59
|
+
If the authoritative nameserver returns the SOA record in the answer section, it will appear
|
|
60
|
+
in `result.answers`. Otherwise check `result.authorities`.
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
const dns2 = require('dns2');
|
|
64
|
+
|
|
65
|
+
const dns = new dns2({ nameServers: ['8.8.8.8'] });
|
|
66
|
+
|
|
67
|
+
(async () => {
|
|
68
|
+
const result = await dns.resolveSOA('google.com');
|
|
69
|
+
const soa = result.answers[0] || result.authorities[0];
|
|
70
|
+
if (soa) {
|
|
71
|
+
console.log(soa.primary); // ns1.google.com
|
|
72
|
+
console.log(soa.admin); // dns-admin.google.com
|
|
73
|
+
console.log(soa.serial); // zone serial number
|
|
74
|
+
console.log(soa.refresh); // refresh interval (seconds)
|
|
75
|
+
console.log(soa.retry); // retry interval (seconds)
|
|
76
|
+
console.log(soa.expiration); // expiry (seconds)
|
|
77
|
+
console.log(soa.minimum); // minimum TTL (seconds)
|
|
78
|
+
}
|
|
79
|
+
})();
|
|
80
|
+
```
|
|
81
|
+
|
|
43
82
|
Another way to instanciate dns2 UDP Client:
|
|
44
83
|
|
|
45
84
|
```js
|
|
@@ -162,7 +201,6 @@ server.listen({
|
|
|
162
201
|
udp: {
|
|
163
202
|
port: 5333,
|
|
164
203
|
address: "127.0.0.1",
|
|
165
|
-
type: "udp4", // IPv4 or IPv6 (Must be either "udp4" or "udp6")
|
|
166
204
|
},
|
|
167
205
|
|
|
168
206
|
// Optionally specify port and/or address for tcp server:
|
|
@@ -185,6 +223,52 @@ $ dig @127.0.0.1 -p5333 lsong.org
|
|
|
185
223
|
Note that when implementing your own lookups, the contents of the query
|
|
186
224
|
will be found in `request.questions[0].name`.
|
|
187
225
|
|
|
226
|
+
### Responding with DNS Error Codes
|
|
227
|
+
|
|
228
|
+
Use `Packet.RCODE` to send standard DNS error responses from your handler:
|
|
229
|
+
|
|
230
|
+
| Constant | Value | Meaning |
|
|
231
|
+
|---|---|---|
|
|
232
|
+
| `Packet.RCODE.NOERROR` | 0 | No error |
|
|
233
|
+
| `Packet.RCODE.FORMERR` | 1 | Format error |
|
|
234
|
+
| `Packet.RCODE.SERVFAIL` | 2 | Server failure |
|
|
235
|
+
| `Packet.RCODE.NXDOMAIN` | 3 | Non-existent domain |
|
|
236
|
+
| `Packet.RCODE.NOTIMP` | 4 | Not implemented |
|
|
237
|
+
| `Packet.RCODE.REFUSED` | 5 | Query refused |
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
const dns2 = require('dns2');
|
|
241
|
+
const { Packet } = dns2;
|
|
242
|
+
|
|
243
|
+
const server = dns2.createServer({
|
|
244
|
+
udp: true,
|
|
245
|
+
handle: (request, send) => {
|
|
246
|
+
const response = Packet.createResponseFromRequest(request);
|
|
247
|
+
const [ question ] = request.questions;
|
|
248
|
+
|
|
249
|
+
if (question.name.endsWith('.internal')) {
|
|
250
|
+
// Refuse queries for internal names
|
|
251
|
+
response.header.rcode = Packet.RCODE.REFUSED;
|
|
252
|
+
return send(response);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!isKnownDomain(question.name)) {
|
|
256
|
+
// Domain does not exist
|
|
257
|
+
response.header.rcode = Packet.RCODE.NXDOMAIN;
|
|
258
|
+
return send(response);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Normal answer ...
|
|
262
|
+
response.answers.push({ name: question.name, type: Packet.TYPE.A, class: Packet.CLASS.IN, ttl: 300, address: '1.2.3.4' });
|
|
263
|
+
send(response);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Performance and Benchmarking
|
|
269
|
+
|
|
270
|
+
see [benchmark/README.md](benchmark/README.md)
|
|
271
|
+
|
|
188
272
|
### Relevant Specifications
|
|
189
273
|
|
|
190
274
|
+ [RFC-1034 - Domain Names - Concepts and Facilities](https://tools.ietf.org/html/rfc1034)
|
|
@@ -225,3 +309,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
225
309
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
226
310
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
227
311
|
THE SOFTWARE.
|
|
312
|
+
|
|
313
|
+
[npm-img]: https://img.shields.io/npm/v/dns2.svg?style=flat
|
|
314
|
+
[ci-img]: https://github.com/song940/node-dns/actions/workflows/node.js.yml/badge.svg
|
|
315
|
+
[ci-url]: https://github.com/song940/node-dns/actions/workflows/node.js.yml
|
package/client/doh.js
CHANGED
|
@@ -1,49 +1,89 @@
|
|
|
1
|
+
const http = require('node:http');
|
|
2
|
+
const https = require('node:https');
|
|
3
|
+
const http2 = require('node:http2');
|
|
1
4
|
const Packet = require('../packet');
|
|
2
5
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
6
|
+
const protocols = {
|
|
7
|
+
'http:' : http.get,
|
|
8
|
+
'https:' : https.get,
|
|
9
|
+
'h2:' : (url, options, done) => {
|
|
10
|
+
const urlObj = new URL(url);
|
|
11
|
+
const client = http2.connect(url.replace('h2:', 'https:'));
|
|
12
|
+
const req = client.request({
|
|
13
|
+
':path' : `${urlObj.pathname}${urlObj.search}`,
|
|
14
|
+
':method' : 'GET',
|
|
15
|
+
...options.headers,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
req.on('response', headers => {
|
|
19
|
+
client.close();
|
|
20
|
+
done({
|
|
21
|
+
headers,
|
|
22
|
+
statusCode : headers[':status'],
|
|
23
|
+
on : req.on.bind(req),
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
req.on('error', err => {
|
|
28
|
+
client.close();
|
|
29
|
+
throw err;
|
|
30
|
+
});
|
|
11
31
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
stream
|
|
16
|
-
.on('error', reject)
|
|
17
|
-
.on('data', chunk => buffer.push(chunk))
|
|
18
|
-
.on('end', () => resolve(Buffer.concat(buffer)));
|
|
19
|
-
});
|
|
32
|
+
req.end();
|
|
33
|
+
},
|
|
20
34
|
};
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
const makeRequest = (url, query) => new Promise((resolve, reject) => {
|
|
37
|
+
const index = url.indexOf('://');
|
|
38
|
+
if (index === -1) url = `https://${url}`;
|
|
39
|
+
const u = new URL(url);
|
|
40
|
+
// The DNS query is included in a single variable named “dns” in the
|
|
41
|
+
// query component of the request URI. The value of the “dns” variable
|
|
42
|
+
// is the content of the DNS request message, encoded with base64url
|
|
43
|
+
// [RFC4648](https://datatracker.ietf.org/doc/html/rfc8484#section-4.1).
|
|
44
|
+
const searchParams = u.searchParams;
|
|
45
|
+
searchParams.set('dns', query);
|
|
46
|
+
u.search = searchParams.toString();
|
|
47
|
+
const get = protocols[u.protocol];
|
|
48
|
+
if (!get) throw new Error(`Unsupported protocol: ${u.protocol}, must be specified (http://, https:// or h2://)`);
|
|
49
|
+
const req = get(u.toString(), { headers: { accept: 'application/dns-message' } }, resolve);
|
|
50
|
+
if (req) req.on('error', reject);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const readStream = res => new Promise((resolve, reject) => {
|
|
54
|
+
const chunks = [];
|
|
55
|
+
res
|
|
56
|
+
.on('error', reject)
|
|
57
|
+
.on('data', chunk => chunks.push(chunk))
|
|
58
|
+
.on('end', () => {
|
|
59
|
+
const data = Buffer.concat(chunks);
|
|
60
|
+
if (res.statusCode !== 200) {
|
|
61
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data.toString()}`));
|
|
62
|
+
}
|
|
63
|
+
resolve(data);
|
|
42
64
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const buildQuery = ({ name, type = 'A', cls = Packet.CLASS.IN, clientIp, recursive = true }) => {
|
|
68
|
+
const packet = new Packet();
|
|
69
|
+
packet.header.rd = recursive ? 1 : 0;
|
|
70
|
+
|
|
71
|
+
if (clientIp) {
|
|
72
|
+
packet.additionals.push(Packet.Resource.EDNS([
|
|
73
|
+
Packet.Resource.EDNS.ECS(clientIp),
|
|
74
|
+
]));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
packet.questions.push({ name, class: cls, type: Packet.TYPE[type] });
|
|
78
|
+
return packet.toBase64URL();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const DOHClient = ({ dns }) => {
|
|
82
|
+
return async(name, type, cls, options = {}) => {
|
|
83
|
+
const query = buildQuery({ name, type, cls, ...options });
|
|
84
|
+
const response = await makeRequest(dns, query);
|
|
85
|
+
const data = await readStream(response);
|
|
86
|
+
return Packet.parse(data);
|
|
47
87
|
};
|
|
48
88
|
};
|
|
49
89
|
|
package/client/google.js
CHANGED
package/client/tcp.js
CHANGED
|
@@ -1,34 +1,49 @@
|
|
|
1
|
-
const
|
|
1
|
+
const tls = require('node:tls');
|
|
2
|
+
const tcp = require('node:net');
|
|
2
3
|
const Packet = require('../packet');
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
5
|
+
const makeQuery = ({ name, type = 'A', cls = Packet.CLASS.IN, clientIp, recursive = true }) => {
|
|
6
|
+
const packet = new Packet();
|
|
7
|
+
packet.header.rd = recursive ? 1 : 0;
|
|
8
|
+
|
|
9
|
+
if (clientIp) {
|
|
10
|
+
packet.additionals.push(Packet.Resource.EDNS([
|
|
11
|
+
Packet.Resource.EDNS.ECS(clientIp),
|
|
12
|
+
]));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
packet.questions.push({ name, class: cls, type: Packet.TYPE[type] });
|
|
16
|
+
return packet.toBuffer();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const sendQuery = (client, message) => {
|
|
20
|
+
const len = Buffer.alloc(2);
|
|
21
|
+
len.writeUInt16BE(message.length);
|
|
22
|
+
client.write(Buffer.concat([ len, message ]));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const protocols = {
|
|
26
|
+
'tcp:' : (host, port) => tcp.connect({ host, port }),
|
|
27
|
+
'tls:' : (host, port) => tls.connect({ host, port, servername: host }),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const TCPClient = ({ dns, protocol = 'tcp:', port = protocol === 'tls:' ? 853 : 53 } = {}) => {
|
|
31
|
+
if (!protocols[protocol]) {
|
|
32
|
+
throw new Error('Protocol must be tcp: or tls:');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return async(name, type, cls, options = {}) => {
|
|
36
|
+
const message = makeQuery({ name, type, cls, ...options });
|
|
37
|
+
const [ host ] = dns.split(':');
|
|
38
|
+
const client = protocols[protocol](host, port);
|
|
39
|
+
|
|
40
|
+
sendQuery(client, message);
|
|
28
41
|
const data = await Packet.readStream(client);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
client.end();
|
|
43
|
+
|
|
44
|
+
if (!data.length) throw new Error('Empty response');
|
|
32
45
|
return Packet.parse(data);
|
|
33
46
|
};
|
|
34
47
|
};
|
|
48
|
+
|
|
49
|
+
module.exports = TCPClient;
|
package/client/udp.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
const udp = require('dgram');
|
|
1
|
+
const udp = require('node:dgram');
|
|
2
|
+
const net = require('node:net');
|
|
3
|
+
const crypto = require('node:crypto');
|
|
2
4
|
const Packet = require('../packet');
|
|
3
|
-
const {
|
|
4
|
-
const { debuglog } = require('util');
|
|
5
|
+
const { debuglog } = require('node:util');
|
|
5
6
|
|
|
6
7
|
const debug = debuglog('dns2');
|
|
7
8
|
|
|
8
|
-
module.exports = ({
|
|
9
|
-
|
|
9
|
+
module.exports = ({
|
|
10
|
+
dns = '8.8.8.8',
|
|
11
|
+
port = 53,
|
|
12
|
+
socketType = 'udp4',
|
|
13
|
+
timeout = 10000,
|
|
14
|
+
retryOverTCP = true,
|
|
15
|
+
} = {}) => {
|
|
16
|
+
return (name, type = 'A', cls = Packet.CLASS.IN, options = {}) => {
|
|
17
|
+
const { clientIp, recursive = true } = options;
|
|
10
18
|
const query = new Packet();
|
|
11
|
-
query.header.id =
|
|
12
|
-
|
|
19
|
+
query.header.id = crypto.randomInt(0x10000);
|
|
13
20
|
// see https://github.com/song940/node-dns/issues/29
|
|
14
21
|
if (recursive) {
|
|
15
22
|
query.header.rd = 1;
|
|
@@ -25,15 +32,76 @@ module.exports = ({ dns = '8.8.8.8', port = 53, socketType = 'udp4' } = {}) => {
|
|
|
25
32
|
type : Packet.TYPE[type],
|
|
26
33
|
});
|
|
27
34
|
const client = new udp.Socket(socketType);
|
|
35
|
+
// Only enforce a strict source-address check when `dns` is an IP literal;
|
|
36
|
+
// hostnames would require an extra resolve to compare against.
|
|
37
|
+
const expectedAddress = net.isIP(dns) ? dns : null;
|
|
28
38
|
return new Promise((resolve, reject) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
let settled = false;
|
|
40
|
+
let timer;
|
|
41
|
+
const cleanup = () => {
|
|
42
|
+
if (settled) return;
|
|
43
|
+
settled = true;
|
|
44
|
+
if (timer) clearTimeout(timer);
|
|
45
|
+
client.removeListener('message', onMessage);
|
|
46
|
+
client.removeListener('error', onError);
|
|
47
|
+
try { client.close(); } catch (_) { /* already closed */ }
|
|
48
|
+
};
|
|
49
|
+
function onMessage(message, rinfo) {
|
|
50
|
+
// Drop packets that didn't come from the configured resolver.
|
|
51
|
+
if (rinfo.port !== port || (expectedAddress && rinfo.address !== expectedAddress)) {
|
|
52
|
+
debug('udp: dropping packet from unexpected sender %s:%d', rinfo.address, rinfo.port);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let response;
|
|
56
|
+
try {
|
|
57
|
+
response = Packet.parse(message);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
debug('udp: dropping unparseable packet: %s', e.message);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Stray / late reply from a reused ephemeral port — keep listening.
|
|
63
|
+
if (response.header.id !== query.header.id) {
|
|
64
|
+
debug('udp: dropping response with mismatched id %d (expected %d)',
|
|
65
|
+
response.header.id, query.header.id);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// RFC 1035 §4.2.1: if the TC (truncated) bit is set the upstream had
|
|
69
|
+
// more data than fits in a 512-byte UDP datagram. Retry over TCP so
|
|
70
|
+
// callers always receive a complete answer.
|
|
71
|
+
if (response.header.tc && retryOverTCP) {
|
|
72
|
+
debug('udp: TC bit set — retrying query over TCP');
|
|
73
|
+
cleanup();
|
|
74
|
+
const TCPClient = require('./tcp');
|
|
75
|
+
resolve(TCPClient({ dns, port })(name, type, cls, options));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
cleanup();
|
|
33
79
|
resolve(response);
|
|
34
|
-
}
|
|
80
|
+
}
|
|
81
|
+
function onError(err) {
|
|
82
|
+
cleanup();
|
|
83
|
+
reject(err);
|
|
84
|
+
}
|
|
85
|
+
client.on('message', onMessage);
|
|
86
|
+
client.on('error', onError);
|
|
87
|
+
|
|
88
|
+
if (timeout > 0) {
|
|
89
|
+
timer = setTimeout(() => {
|
|
90
|
+
cleanup();
|
|
91
|
+
const err = new Error(`DNS query timed out after ${timeout}ms`);
|
|
92
|
+
err.code = 'ETIMEDOUT';
|
|
93
|
+
reject(err);
|
|
94
|
+
}, timeout);
|
|
95
|
+
timer.unref();
|
|
96
|
+
}
|
|
97
|
+
|
|
35
98
|
debug('send', dns, query.toBuffer());
|
|
36
|
-
client.send(query.toBuffer(), port, dns, err =>
|
|
99
|
+
client.send(query.toBuffer(), port, dns, err => {
|
|
100
|
+
if (err) {
|
|
101
|
+
cleanup();
|
|
102
|
+
reject(err);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
37
105
|
});
|
|
38
106
|
};
|
|
39
107
|
};
|
package/example/client/doh.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
const { DOHClient } = require('../..');
|
|
2
2
|
|
|
3
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
3
|
+
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
4
4
|
|
|
5
|
-
const resolve = DOHClient({
|
|
6
|
-
|
|
7
|
-
});
|
|
5
|
+
// const resolve = DOHClient({
|
|
6
|
+
// dns: '1.1.1.1',
|
|
7
|
+
// });
|
|
8
8
|
|
|
9
|
-
(async() => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
})();
|
|
9
|
+
// (async() => {
|
|
10
|
+
// const response = await resolve('google.com');
|
|
11
|
+
// console.log(response.answers);
|
|
12
|
+
// })();
|
|
13
|
+
|
|
14
|
+
// import DNS2 from 'dns2';
|
|
15
|
+
|
|
16
|
+
// DOHClient({
|
|
17
|
+
// dns: 'h2://ada.openbld.net',
|
|
18
|
+
// })('cdnjs.com', 'NS').then(console.log);
|
|
19
|
+
|
|
20
|
+
DOHClient({
|
|
21
|
+
dns: 'https://1.0.0.1/dns-query',
|
|
22
|
+
})('cdnjs.com', 'NS').then(console.log);
|
package/index.js
CHANGED
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
createDOHServer,
|
|
8
8
|
createServer,
|
|
9
9
|
} = require('./server');
|
|
10
|
-
const EventEmitter = require('events');
|
|
10
|
+
const EventEmitter = require('node:events');
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* [DNS description]
|
|
@@ -15,13 +15,19 @@ const EventEmitter = require('events');
|
|
|
15
15
|
* @docs https://tools.ietf.org/html/rfc1035
|
|
16
16
|
*/
|
|
17
17
|
class DNS extends EventEmitter {
|
|
18
|
-
constructor(options) {
|
|
18
|
+
constructor(options = {}) {
|
|
19
19
|
super();
|
|
20
|
+
// Accept `dns` as a shorthand alias for `nameServers` so that
|
|
21
|
+
// `new DNS({ dns: '8.8.8.8' })` works as documented and intuited.
|
|
22
|
+
if (options.dns != null && options.nameServers == null) {
|
|
23
|
+
options = Object.assign({}, options, { nameServers: [].concat(options.dns) });
|
|
24
|
+
}
|
|
20
25
|
Object.assign(this, {
|
|
21
26
|
port : 53,
|
|
22
27
|
retries : 3,
|
|
23
28
|
timeout : 3,
|
|
24
29
|
recursive : true,
|
|
30
|
+
retryOverTCP : true,
|
|
25
31
|
resolverProtocol : 'UDP',
|
|
26
32
|
nameServers : [
|
|
27
33
|
'8.8.8.8',
|
|
@@ -34,27 +40,19 @@ class DNS extends EventEmitter {
|
|
|
34
40
|
}, options);
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
/**
|
|
38
|
-
* query
|
|
39
|
-
* @param {*} questions
|
|
40
|
-
*/
|
|
41
|
-
query(name, type, cls, clientIp) {
|
|
42
|
-
const { port, nameServers, recursive, resolverProtocol = 'UDP' } = this;
|
|
43
|
-
const createResolver = DNS[resolverProtocol + 'Client'];
|
|
44
|
-
return Promise.race(nameServers.map(address => {
|
|
45
|
-
const resolve = createResolver({ dns: address, port, recursive });
|
|
46
|
-
return resolve(name, type, cls, clientIp);
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
43
|
/**
|
|
51
44
|
* resolve
|
|
52
45
|
* @param {*} domain
|
|
53
46
|
* @param {*} type
|
|
54
47
|
* @param {*} cls
|
|
55
48
|
*/
|
|
56
|
-
resolve(domain, type = 'ANY', cls = DNS.Packet.CLASS.IN,
|
|
57
|
-
|
|
49
|
+
resolve(domain, type = 'ANY', cls = DNS.Packet.CLASS.IN, options = {}) {
|
|
50
|
+
const { port, nameServers, resolverProtocol = 'UDP', retryOverTCP } = this;
|
|
51
|
+
const createResolver = DNS[resolverProtocol + 'Client'];
|
|
52
|
+
return Promise.race(nameServers.map(address => {
|
|
53
|
+
const resolve = createResolver({ dns: address, port, retryOverTCP });
|
|
54
|
+
return resolve(domain, type, cls, options);
|
|
55
|
+
}));
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
resolveA(domain, clientIp) {
|
|
@@ -76,6 +74,18 @@ class DNS extends EventEmitter {
|
|
|
76
74
|
resolvePTR(domain) {
|
|
77
75
|
return this.resolve(domain, 'PTR');
|
|
78
76
|
}
|
|
77
|
+
|
|
78
|
+
resolveDNSKEY(domain) {
|
|
79
|
+
return this.resolve(domain, 'DNSKEY');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
resolveRRSIG(domain) {
|
|
83
|
+
return this.resolve(domain, 'RRSIG');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
resolveSOA(domain) {
|
|
87
|
+
return this.resolve(domain, 'SOA');
|
|
88
|
+
}
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
DNS.TCPServer = TCPServer;
|