dns2 2.1.0 → 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.1.0",
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,7 +25,8 @@
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"
@@ -22,21 +35,22 @@
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
 
@@ -19,6 +20,13 @@ const fromIPv6 = (address) => {
19
20
  if (digits[digits.length - 1] === '') {
20
21
  digits.pop();
21
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
+
22
30
  // CAVEAT we have to take into account
23
31
  // the extra space used by the empty string
24
32
  const missingFields = 8 - digits.length + 1;
@@ -76,31 +84,32 @@ function Packet(data) {
76
84
  * @docs https://tools.ietf.org/html/rfc1035#section-3.2.2
77
85
  */
78
86
  Packet.TYPE = {
79
- A : 0x01,
80
- NS : 0x02,
81
- MD : 0x03,
82
- MF : 0x04,
83
- CNAME : 0x05,
84
- SOA : 0x06,
85
- MB : 0x07,
86
- MG : 0x08,
87
- MR : 0x09,
88
- NULL : 0x0A,
89
- WKS : 0x0B,
90
- PTR : 0x0C,
91
- HINFO : 0x0D,
92
- MINFO : 0x0E,
93
- MX : 0x0F,
94
- TXT : 0x10,
95
- AAAA : 0x1C,
96
- SRV : 0x21,
97
- EDNS : 0x29,
98
- SPF : 0x63,
99
- AXFR : 0xFC,
100
- MAILB : 0xFD,
101
- MAILA : 0xFE,
102
- ANY : 0xFF,
103
- 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,
104
113
  };
105
114
  /**
106
115
  * [QUERY_CLASS description]
@@ -114,6 +123,19 @@ Packet.CLASS = {
114
123
  HS : 0x04,
115
124
  ANY : 0xFF,
116
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
+ };
117
139
  /**
118
140
  * [EDNS_OPTION_CODE description]
119
141
  * @type {Object}
@@ -124,11 +146,13 @@ Packet.EDNS_OPTION_CODE = {
124
146
  };
125
147
 
126
148
  /**
127
- * [uuid description]
128
- * @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]
129
153
  */
130
154
  Packet.uuid = function() {
131
- return Math.floor(Math.random() * 1e5);
155
+ return randomInt(0x10000);
132
156
  };
133
157
 
134
158
  /**
@@ -172,7 +196,6 @@ Object.defineProperty(Packet.prototype, 'recursive', {
172
196
  },
173
197
  set(yn) {
174
198
  this.header.rd = +yn;
175
- return this.header.rd;
176
199
  },
177
200
  });
178
201
 
@@ -379,9 +402,18 @@ Packet.Resource.encode = function(resource, writer) {
379
402
  })[0];
380
403
  if (encoder in Packet.Resource && Packet.Resource[encoder].encode) {
381
404
  return Packet.Resource[encoder].encode(resource, writer);
382
- } else {
383
- debug('node-dns > unknown encoder %s(%j)', encoder, resource.type);
384
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();
385
417
  };
386
418
  /**
387
419
  * [parse description]
@@ -597,7 +629,7 @@ Packet.Resource.SPF =
597
629
  Packet.Resource.TXT = {
598
630
  decode: function(reader, length) {
599
631
  const parts = [];
600
- let bytesRead = 0; let chunkLength = 0;
632
+ let bytesRead = 0; let chunkLength;
601
633
 
602
634
  while (bytesRead < length) {
603
635
  chunkLength = reader.read(8); // text length
@@ -785,21 +817,30 @@ Packet.Resource.EDNS.ECS.decode = function(reader, length) {
785
817
  rdata.scopePrefixLength = reader.read(8);
786
818
  length -= 4;
787
819
 
788
- if (rdata.family !== 1) {
789
- debug('node-dns > unimplemented address family');
790
- reader.read(length * 8); // Ignore data that doesn't understand
791
- 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('.');
792
830
  }
793
831
 
794
- const ipv4Octets = [];
795
- while (length--) {
796
- const octet = reader.read(8);
797
- ipv4Octets.push(octet);
798
- }
799
- while (ipv4Octets.length < 4) {
800
- 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(':');
801
842
  }
802
- rdata.ip = ipv4Octets.join('.');
843
+
803
844
  return rdata;
804
845
  };
805
846
 
@@ -828,15 +869,135 @@ Packet.Resource.CAA = {
828
869
  });
829
870
  return writer.toBuffer();
830
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
+ },
831
987
  };
832
988
 
833
989
  Packet.Reader = BufferReader;
834
990
  Packet.Writer = BufferWriter;
835
991
 
836
992
  Packet.createResponseFromRequest = function(request) {
837
- const response = new Packet(request);
838
- response.header.qr = 1;
839
- 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();
840
1001
  return response;
841
1002
  };
842
1003
 
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') {