dns2 2.0.5 → 2.2.1

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/package.json CHANGED
@@ -1,11 +1,23 @@
1
1
  {
2
2
  "name": "dns2",
3
- "version": "2.0.5",
3
+ "version": "2.2.1",
4
4
  "description": "A DNS Server and Client Implementation in Pure JavaScript with no dependencies.",
5
5
  "main": "index.js",
6
+ "types": "ts/index.d.ts",
7
+ "files": [
8
+ "lib",
9
+ "client",
10
+ "server",
11
+ "packet.js",
12
+ "example",
13
+ "ts"
14
+ ],
6
15
  "scripts": {
7
- "test": "node test",
8
- "lint": "eslint .",
16
+ "test": "node --test",
17
+ "test:coverage": "node --test --experimental-test-coverage",
18
+ "test:coverage:lcov": "mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info",
19
+ "lint": "npx eslint .",
20
+ "lint:fix": "npx eslint . --fix",
9
21
  "example-server-udp": "node example/server/udp.js",
10
22
  "example-server-tcp": "node example/server/tcp.js",
11
23
  "example-server-doh": "node example/server/doh.js",
@@ -13,30 +25,32 @@
13
25
  "example-client-udp": "node example/client/udp.js",
14
26
  "example-client-tcp": "node example/client/tcp.js",
15
27
  "example-client-google": "node example/client/google.js",
16
- "example-client-udp-subnet": "node example/client/udp-subnet.js"
28
+ "example-client-udp-subnet": "node example/client/udp-subnet.js",
29
+ "benchmark": "node benchmark/udp.js"
17
30
  },
18
31
  "keywords": [
19
32
  "dns"
20
33
  ],
21
- "author": "Liu Song <hi@lsong.org>",
34
+ "author": "Liu Song <song940@gmail.com>",
22
35
  "contributors": [
23
36
  "Andris Reinman <andris.reinman@gmail.com>",
24
37
  "Eviltik <eviltik@gmail.com>",
25
- "Martin Heidegger <martin.heidegger@gmail.com>"
38
+ "Martin Heidegger <martin.heidegger@gmail.com>",
39
+ "Matt Simerson <matt@tnpi.net>"
26
40
  ],
27
41
  "license": "MIT",
28
42
  "repository": {
29
43
  "type": "git",
30
- "url": "git+https://github.com/song940/node-dns.git"
44
+ "url": "git+https://github.com/lsongdev/node-dns.git"
31
45
  },
32
46
  "bugs": {
33
- "url": "https://github.com/song940/node-dns/issues"
47
+ "url": "https://github.com/lsongdev/node-dns/issues"
34
48
  },
35
- "homepage": "https://github.com/song940/node-dns#readme",
49
+ "homepage": "https://github.com/lsongdev/node-dns#readme",
36
50
  "devDependencies": {
37
- "eslint": "^7.28.0",
38
- "eslint-config-semistandard": "^15.0.1",
39
- "eslint-plugin-node": "^11.1.0",
40
- "eslint-plugin-promise": "^4.2.1"
51
+ "@eslint/js": "^10.0.1",
52
+ "@stylistic/eslint-plugin": "^5.10.0",
53
+ "eslint": "^10.4.0",
54
+ "globals": "^17.6.0"
41
55
  }
42
56
  }
package/packet.js CHANGED
@@ -1,4 +1,5 @@
1
- const { debuglog } = require('util');
1
+ const { debuglog } = require('node:util');
2
+ const { randomInt } = require('node:crypto');
2
3
  const BufferReader = require('./lib/reader');
3
4
  const BufferWriter = require('./lib/writer');
4
5
 
@@ -9,6 +10,34 @@ const toIPv6 = buffer => buffer
9
10
  .join(':')
10
11
  .replace(/\b(?:0+:){1,}/, ':');
11
12
 
13
+ const fromIPv6 = (address) => {
14
+ const digits = address.split(':');
15
+ // CAVEAT edge case for :: and IPs starting
16
+ // or ending by ::
17
+ if (digits[0] === '') {
18
+ digits.shift();
19
+ }
20
+ if (digits[digits.length - 1] === '') {
21
+ digits.pop();
22
+ }
23
+ // node js 10 does not support Array.prototype.flatMap
24
+ if (!Array.prototype.flatMap) {
25
+ Array.prototype.flatMap = function(f, ctx) {
26
+ return this.reduce((r, x, i, a) => r.concat(f.call(ctx, x, i, a)), []);
27
+ };
28
+ }
29
+
30
+ // CAVEAT we have to take into account
31
+ // the extra space used by the empty string
32
+ const missingFields = 8 - digits.length + 1;
33
+ return digits.flatMap((digit) => {
34
+ if (digit === '') {
35
+ return Array(missingFields).fill('0');
36
+ }
37
+ return digit.padStart(4, '0');
38
+ });
39
+ };
40
+
12
41
  /**
13
42
  * [Packet description]
14
43
  * @param {[type]} data [description]
@@ -55,31 +84,32 @@ function Packet(data) {
55
84
  * @docs https://tools.ietf.org/html/rfc1035#section-3.2.2
56
85
  */
57
86
  Packet.TYPE = {
58
- A : 0x01,
59
- NS : 0x02,
60
- MD : 0x03,
61
- MF : 0x04,
62
- CNAME : 0x05,
63
- SOA : 0x06,
64
- MB : 0x07,
65
- MG : 0x08,
66
- MR : 0x09,
67
- NULL : 0x0A,
68
- WKS : 0x0B,
69
- PTR : 0x0C,
70
- HINFO : 0x0D,
71
- MINFO : 0x0E,
72
- MX : 0x0F,
73
- TXT : 0x10,
74
- AAAA : 0x1C,
75
- SRV : 0x21,
76
- EDNS : 0x29,
77
- SPF : 0x63,
78
- AXFR : 0xFC,
79
- MAILB : 0xFD,
80
- MAILA : 0xFE,
81
- ANY : 0xFF,
82
- CAA : 0x101,
87
+ A : 0x01,
88
+ NS : 0x02,
89
+ MD : 0x03,
90
+ MF : 0x04,
91
+ CNAME : 0x05,
92
+ SOA : 0x06,
93
+ MB : 0x07,
94
+ MG : 0x08,
95
+ MR : 0x09,
96
+ NULL : 0x0A,
97
+ WKS : 0x0B,
98
+ PTR : 0x0C,
99
+ HINFO : 0x0D,
100
+ MINFO : 0x0E,
101
+ MX : 0x0F,
102
+ TXT : 0x10,
103
+ AAAA : 0x1C,
104
+ SRV : 0x21,
105
+ EDNS : 0x29,
106
+ SPF : 0x63,
107
+ AXFR : 0xFC,
108
+ MAILB : 0xFD,
109
+ MAILA : 0xFE,
110
+ ANY : 0xFF,
111
+ CAA : 0x101,
112
+ DNSKEY : 0x30,
83
113
  };
84
114
  /**
85
115
  * [QUERY_CLASS description]
@@ -93,6 +123,19 @@ Packet.CLASS = {
93
123
  HS : 0x04,
94
124
  ANY : 0xFF,
95
125
  };
126
+ /**
127
+ * DNS response codes
128
+ * @type {Object}
129
+ * @docs https://tools.ietf.org/html/rfc1035#section-4.1.1
130
+ */
131
+ Packet.RCODE = {
132
+ NOERROR : 0,
133
+ FORMERR : 1,
134
+ SERVFAIL : 2,
135
+ NXDOMAIN : 3,
136
+ NOTIMP : 4,
137
+ REFUSED : 5,
138
+ };
96
139
  /**
97
140
  * [EDNS_OPTION_CODE description]
98
141
  * @type {Object}
@@ -103,11 +146,13 @@ Packet.EDNS_OPTION_CODE = {
103
146
  };
104
147
 
105
148
  /**
106
- * [uuid description]
107
- * @return {[type]} [description]
149
+ * Generate a cryptographically random 16-bit DNS transaction ID.
150
+ * RFC 5452 §3 — the full 16-bit space must be used from a CSPRNG to make
151
+ * response forgery / cache poisoning impractical.
152
+ * @return {number} integer in [0, 0xFFFF]
108
153
  */
109
154
  Packet.uuid = function() {
110
- return Math.floor(Math.random() * 1e5);
155
+ return randomInt(0x10000);
111
156
  };
112
157
 
113
158
  /**
@@ -151,7 +196,6 @@ Object.defineProperty(Packet.prototype, 'recursive', {
151
196
  },
152
197
  set(yn) {
153
198
  this.header.rd = +yn;
154
- return this.header.rd;
155
199
  },
156
200
  });
157
201
 
@@ -358,9 +402,18 @@ Packet.Resource.encode = function(resource, writer) {
358
402
  })[0];
359
403
  if (encoder in Packet.Resource && Packet.Resource[encoder].encode) {
360
404
  return Packet.Resource[encoder].encode(resource, writer);
361
- } else {
362
- debug('node-dns > unknown encoder %s(%j)', encoder, resource.type);
363
405
  }
406
+ debug('node-dns > unknown encoder %s(%j)', encoder, resource.type);
407
+ // Fallback for unknown / decoder-only types: round-trip the raw RDATA the
408
+ // decoder preserved as `resource.data`. Without this, RDLENGTH and RDATA
409
+ // would be omitted entirely, truncating the wire format and corrupting any
410
+ // records that follow.
411
+ const data = Buffer.isBuffer(resource.data) ? resource.data : Buffer.alloc(0);
412
+ writer.write(data.length, 16);
413
+ for (const byte of data) {
414
+ writer.write(byte, 8);
415
+ }
416
+ return writer.toBuffer();
364
417
  };
365
418
  /**
366
419
  * [parse description]
@@ -524,7 +577,7 @@ Packet.Resource.AAAA = {
524
577
  },
525
578
  encode: function(record, writer) {
526
579
  writer = writer || new Packet.Writer();
527
- const parts = record.address.split(':');
580
+ const parts = fromIPv6(record.address);
528
581
  writer.write(parts.length * 2, 16);
529
582
  parts.forEach(function(part) {
530
583
  writer.write(parseInt(part, 16), 16);
@@ -576,7 +629,7 @@ Packet.Resource.SPF =
576
629
  Packet.Resource.TXT = {
577
630
  decode: function(reader, length) {
578
631
  const parts = [];
579
- let bytesRead = 0; let chunkLength = 0;
632
+ let bytesRead = 0; let chunkLength;
580
633
 
581
634
  while (bytesRead < length) {
582
635
  chunkLength = reader.read(8); // text length
@@ -764,21 +817,30 @@ Packet.Resource.EDNS.ECS.decode = function(reader, length) {
764
817
  rdata.scopePrefixLength = reader.read(8);
765
818
  length -= 4;
766
819
 
767
- if (rdata.family !== 1) {
768
- debug('node-dns > unimplemented address family');
769
- reader.read(length * 8); // Ignore data that doesn't understand
770
- return rdata;
820
+ if (rdata.family === 1) {
821
+ const ipv4Octets = [];
822
+ while (length--) {
823
+ const octet = reader.read(8);
824
+ ipv4Octets.push(octet);
825
+ }
826
+ while (ipv4Octets.length < 4) {
827
+ ipv4Octets.push(0);
828
+ }
829
+ rdata.ip = ipv4Octets.join('.');
771
830
  }
772
831
 
773
- const ipv4Octets = [];
774
- while (length--) {
775
- const octet = reader.read(8);
776
- ipv4Octets.push(octet);
777
- }
778
- while (ipv4Octets.length < 4) {
779
- ipv4Octets.push(0);
832
+ if (rdata.family === 2) {
833
+ const ipv6Segments = [];
834
+ for (; length; length -= 2) {
835
+ const segment = reader.read(16).toString(16);
836
+ ipv6Segments.push(segment);
837
+ }
838
+ while (ipv6Segments.length < 8) {
839
+ ipv6Segments.push('0');
840
+ }
841
+ rdata.ip = ipv6Segments.join(':');
780
842
  }
781
- rdata.ip = ipv4Octets.join('.');
843
+
782
844
  return rdata;
783
845
  };
784
846
 
@@ -807,15 +869,135 @@ Packet.Resource.CAA = {
807
869
  });
808
870
  return writer.toBuffer();
809
871
  },
872
+ decode: function(reader, length) {
873
+ this.flags = reader.read(8);
874
+ const tagLength = reader.read(8);
875
+ const bytes = [];
876
+ let remaining = length - 2;
877
+ while (remaining--) bytes.push(reader.read(8));
878
+ const buffer = Buffer.from(bytes);
879
+ this.tag = buffer.slice(0, tagLength).toString('utf8');
880
+ this.value = buffer.slice(tagLength).toString('utf8');
881
+ return this;
882
+ },
883
+ };
884
+
885
+ /**
886
+ * @type {{decode: (function(*, *): Packet.Resource.DNSKEY)}}
887
+ * @link https://tools.ietf.org/html/rfc4034
888
+ * @link https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml#table-dns-sec-alg-numbers-1
889
+ */
890
+ Packet.Resource.DNSKEY = {
891
+ decode: function(reader, length) {
892
+ const RData = [];
893
+ while (RData.length < length) {
894
+ RData.push(reader.read(8));
895
+ }
896
+ this.flags = RData[0] << 8 | RData[1];
897
+ this.protocol = RData[2];
898
+ this.algorithm = RData[3];
899
+ // for key tag
900
+ let ac = 0;
901
+ for (let i = 0; i < length; ++i) {
902
+ ac += (i & 1) ? RData[i] : RData[i] << 8;
903
+ }
904
+ ac += (ac >> 16) & 0xFFFF;
905
+ this.keyTag = ac & 0XFFFF;
906
+
907
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 = 16
908
+ // convert binary flags
909
+ let binFlags = this.flags.toString(2);
910
+ // add left padding until 16 chars
911
+ while (binFlags.length < 16) {
912
+ binFlags = '0' + binFlags;
913
+ }
914
+ this.zoneKey = binFlags[7] === '1';
915
+ this.zoneSep = binFlags[15] === '1';
916
+ this.key = Buffer.from(RData.slice(4)).toString('base64');
917
+ return this;
918
+ },
919
+ encode: function(record, writer) {
920
+ writer = writer || new Packet.Writer();
921
+ const buffer = Buffer.from(record.key, 'base64');
922
+ writer.write(4 + buffer.length, 16);
923
+ writer.write(record.flags, 16);
924
+ writer.write(record.protocol, 8);
925
+ writer.write(record.algorithm, 8);
926
+ buffer.forEach(function(c) {
927
+ writer.write(c, 8);
928
+ });
929
+ return writer.toBuffer();
930
+ },
931
+ };
932
+
933
+ /**
934
+ * RRSIG just support decode
935
+ * test with dns.resolveRRSIG('example.com')
936
+ *
937
+ * @type {{decode: (function(*, *): Packet.Resource.RRSIG)}}
938
+ */
939
+ Packet.Resource.RRSIG = {
940
+ decode: function(reader, length) {
941
+ function dateForSig(date) {
942
+ // javascript date is from millisecond
943
+ date = new Date(date * 1000);
944
+ const definitions = {
945
+ month : (date.getUTCMonth() + 1),
946
+ date : date.getUTCDate(),
947
+ hour : date.getUTCHours(),
948
+ minutes : date.getUTCMinutes(),
949
+ seconds : date.getUTCSeconds(),
950
+ };
951
+ let i;
952
+ for (i in definitions) {
953
+ // if less than 10 > single
954
+ if (definitions[i] < 10) {
955
+ definitions[i] = '0' + '' + definitions[i];
956
+ }
957
+ }
958
+ return date.getFullYear() + '' +
959
+ definitions.month + '' +
960
+ definitions.date + '' +
961
+ definitions.hour + '' +
962
+ definitions.minutes + '' +
963
+ definitions.seconds;
964
+ }
965
+
966
+ // calculate max-offset uint8
967
+ const maxOffset = reader.offset + (length * 8);
968
+ /*
969
+ * Stuff sign contains 18 octets
970
+ */
971
+ this.sigType = reader.read(16); // 2
972
+ this.algorithm = reader.read(8); // 1
973
+ this.labels = reader.read(8); // 1
974
+ this.originalTtl = reader.read(32); // 4
975
+ this.expiration = dateForSig(reader.read(32)); // 4
976
+ this.inception = dateForSig(reader.read(32)); // 4
977
+ this.keyTag = reader.read(16); // 2
978
+ this.signer = Packet.Name.decode(reader);
979
+ const maxLength = (maxOffset - reader.offset) / 8;
980
+ const signature = [];
981
+ while (signature.length < maxLength) {
982
+ signature.push(reader.read(8));
983
+ }
984
+ this.signature = Buffer.from(signature).toString('base64');
985
+ return this;
986
+ },
810
987
  };
811
988
 
812
989
  Packet.Reader = BufferReader;
813
990
  Packet.Writer = BufferWriter;
814
991
 
815
992
  Packet.createResponseFromRequest = function(request) {
816
- const response = new Packet(request);
817
- response.header.qr = 1;
818
- response.additionals = [];
993
+ const response = new Packet();
994
+ response.header = new Packet.Header({
995
+ id : request.header.id,
996
+ opcode : request.header.opcode,
997
+ rd : request.header.rd,
998
+ qr : 1,
999
+ });
1000
+ response.questions = request.questions.slice();
819
1001
  return response;
820
1002
  };
821
1003
 
@@ -874,3 +1056,4 @@ Packet.prototype.toBase64URL = function() {
874
1056
 
875
1057
  module.exports = Packet;
876
1058
  module.exports.toIPv6 = toIPv6;
1059
+ module.exports.fromIPv6 = fromIPv6;
package/server/dns.js CHANGED
@@ -1,4 +1,5 @@
1
- const EventEmitter = require('events');
1
+ const EventEmitter = require('node:events');
2
+ const Packet = require('../packet');
2
3
  const DOHServer = require('./doh');
3
4
  const TCPServer = require('./tcp');
4
5
  const UDPServer = require('./udp');
@@ -34,7 +35,23 @@ class DNSServer extends EventEmitter {
34
35
  return addresses;
35
36
  });
36
37
 
37
- const emitRequest = (request, send, client) => this.emit('request', request, send, client);
38
+ const maxConcurrent = options.maxConcurrent > 0 ? options.maxConcurrent : 0;
39
+ let active = 0;
40
+
41
+ const emitRequest = (request, send, client) => {
42
+ if (maxConcurrent && active >= maxConcurrent) {
43
+ const response = Packet.createResponseFromRequest(request);
44
+ response.header.rcode = Packet.RCODE.SERVFAIL;
45
+ send(response);
46
+ return;
47
+ }
48
+ active++;
49
+ const wrappedSend = (...args) => {
50
+ active--;
51
+ return send(...args);
52
+ };
53
+ this.emit('request', request, wrappedSend, client);
54
+ };
38
55
  const emitRequestError = (error) => this.emit('requestError', error);
39
56
  for (const server of servers) {
40
57
  server.on('request', emitRequest);
package/server/doh.js CHANGED
@@ -1,9 +1,9 @@
1
- const http = require('http');
2
- const https = require('https');
3
- const { URL } = require('url');
1
+ const http = require('node:http');
2
+ const https = require('node:https');
3
+ const { URL } = require('node:url');
4
4
  const Packet = require('../packet');
5
- const EventEmitter = require('events');
6
- const { debuglog } = require('util');
5
+ const EventEmitter = require('node:events');
6
+ const { debuglog } = require('node:util');
7
7
 
8
8
  const debug = debuglog('dns2-server');
9
9
 
@@ -20,11 +20,11 @@ const decodeBase64URL = str => {
20
20
  };
21
21
 
22
22
  const readStream = stream => new Promise((resolve, reject) => {
23
- let buffer = '';
23
+ const chunks = [];
24
24
  stream
25
25
  .on('error', reject)
26
- .on('data', chunk => { buffer += chunk; })
27
- .on('end', () => resolve(buffer));
26
+ .on('data', chunk => chunks.push(chunk))
27
+ .on('end', () => resolve(Buffer.concat(chunks)));
28
28
  });
29
29
 
30
30
  class Server extends EventEmitter {
@@ -139,6 +139,7 @@ class Server extends EventEmitter {
139
139
  }
140
140
 
141
141
  close() {
142
+ this.server.closeIdleConnections();
142
143
  return this.server.close();
143
144
  }
144
145
  }
package/server/tcp.js CHANGED
@@ -1,4 +1,4 @@
1
- const tcp = require('net');
1
+ const tcp = require('node:net');
2
2
  const Packet = require('../packet');
3
3
 
4
4
  class Server extends tcp.Server {
package/server/udp.js CHANGED
@@ -1,4 +1,4 @@
1
- const udp = require('dgram');
1
+ const udp = require('node:dgram');
2
2
  const Packet = require('../packet');
3
3
 
4
4
  /**
@@ -10,7 +10,7 @@ class Server extends udp.Socket {
10
10
  constructor(options) {
11
11
  let type = 'udp4';
12
12
  if (typeof options === 'object') {
13
- type = options.type;
13
+ type = options.type ?? type;
14
14
  }
15
15
  super(type);
16
16
  if (typeof options === 'function') {