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
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// PROXY protocol parser (HAProxy/Nginx).
|
|
4
|
+
// Spec: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
|
5
|
+
//
|
|
6
|
+
// parse(buffer) returns:
|
|
7
|
+
// - { header, headerLength } when a complete header is at the start of buffer
|
|
8
|
+
// - null when the buffer is a valid prefix but more bytes are needed
|
|
9
|
+
// and throws when the bytes are not a valid PROXY header.
|
|
10
|
+
|
|
11
|
+
const V2_SIGNATURE = Buffer.from([
|
|
12
|
+
0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D,
|
|
13
|
+
0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
|
|
14
|
+
]);
|
|
15
|
+
const V1_PREFIX = Buffer.from('PROXY ');
|
|
16
|
+
const V1_MAX_LEN = 108;
|
|
17
|
+
|
|
18
|
+
const FAMILY = { 0x10: 'IPv4', 0x20: 'IPv6', 0x30: 'Unix' };
|
|
19
|
+
const TRANSPORT = { 0x01: 'STREAM', 0x02: 'DGRAM' };
|
|
20
|
+
|
|
21
|
+
function parse(buffer) {
|
|
22
|
+
if (buffer.length >= 12) {
|
|
23
|
+
if (buffer.slice(0, 12).equals(V2_SIGNATURE)) return parseV2(buffer);
|
|
24
|
+
} else if (V2_SIGNATURE.slice(0, buffer.length).equals(buffer)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (buffer.length >= 6) {
|
|
29
|
+
if (buffer.slice(0, 6).equals(V1_PREFIX)) return parseV1(buffer);
|
|
30
|
+
} else if (V1_PREFIX.slice(0, buffer.length).equals(buffer)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new Error('PROXY protocol: header missing or malformed');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseV1(buffer) {
|
|
38
|
+
const search = buffer.slice(0, Math.min(buffer.length, V1_MAX_LEN));
|
|
39
|
+
const newline = search.indexOf('\r\n');
|
|
40
|
+
if (newline === -1) {
|
|
41
|
+
if (buffer.length >= V1_MAX_LEN) {
|
|
42
|
+
throw new Error('PROXY v1: header exceeds maximum length');
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const line = buffer.slice(0, newline).toString('ascii');
|
|
47
|
+
const parts = line.split(' ');
|
|
48
|
+
if (parts[0] !== 'PROXY') throw new Error('PROXY v1: malformed header');
|
|
49
|
+
const headerLength = newline + 2;
|
|
50
|
+
|
|
51
|
+
if (parts[1] === 'UNKNOWN') {
|
|
52
|
+
return { header: { version: 1, command: 'UNKNOWN' }, headerLength };
|
|
53
|
+
}
|
|
54
|
+
if (parts.length !== 6) throw new Error('PROXY v1: malformed header');
|
|
55
|
+
const [ , proto, sourceAddress, destinationAddress, srcPort, dstPort ] = parts;
|
|
56
|
+
if (proto !== 'TCP4' && proto !== 'TCP6') {
|
|
57
|
+
throw new Error(`PROXY v1: unsupported protocol ${proto}`);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
header: {
|
|
61
|
+
version : 1,
|
|
62
|
+
command : 'PROXY',
|
|
63
|
+
family : proto === 'TCP4' ? 'IPv4' : 'IPv6',
|
|
64
|
+
transport : 'STREAM',
|
|
65
|
+
sourceAddress,
|
|
66
|
+
sourcePort : parseInt(srcPort, 10),
|
|
67
|
+
destinationAddress,
|
|
68
|
+
destinationPort : parseInt(dstPort, 10),
|
|
69
|
+
},
|
|
70
|
+
headerLength,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseV2(buffer) {
|
|
75
|
+
if (buffer.length < 16) return null;
|
|
76
|
+
const verCmd = buffer[12];
|
|
77
|
+
const version = verCmd >> 4;
|
|
78
|
+
const command = verCmd & 0x0F;
|
|
79
|
+
if (version !== 2) throw new Error(`PROXY v2: unsupported version ${version}`);
|
|
80
|
+
if (command !== 0 && command !== 1) {
|
|
81
|
+
throw new Error(`PROXY v2: unknown command ${command}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const famProto = buffer[13];
|
|
85
|
+
const addressLength = buffer.readUInt16BE(14);
|
|
86
|
+
const headerLength = 16 + addressLength;
|
|
87
|
+
if (buffer.length < headerLength) return null;
|
|
88
|
+
|
|
89
|
+
if (command === 0) {
|
|
90
|
+
// LOCAL — no real client info (e.g. proxy-originated health check).
|
|
91
|
+
return { header: { version: 2, command: 'LOCAL' }, headerLength };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const family = FAMILY[famProto & 0xF0];
|
|
95
|
+
const transport = TRANSPORT[famProto & 0x0F];
|
|
96
|
+
|
|
97
|
+
let sourceAddress, destinationAddress, sourcePort, destinationPort;
|
|
98
|
+
if (family === 'IPv4' && addressLength >= 12) {
|
|
99
|
+
sourceAddress = `${buffer[16]}.${buffer[17]}.${buffer[18]}.${buffer[19]}`;
|
|
100
|
+
destinationAddress = `${buffer[20]}.${buffer[21]}.${buffer[22]}.${buffer[23]}`;
|
|
101
|
+
sourcePort = buffer.readUInt16BE(24);
|
|
102
|
+
destinationPort = buffer.readUInt16BE(26);
|
|
103
|
+
} else if (family === 'IPv6' && addressLength >= 36) {
|
|
104
|
+
sourceAddress = ipv6FromBytes(buffer.slice(16, 32));
|
|
105
|
+
destinationAddress = ipv6FromBytes(buffer.slice(32, 48));
|
|
106
|
+
sourcePort = buffer.readUInt16BE(48);
|
|
107
|
+
destinationPort = buffer.readUInt16BE(50);
|
|
108
|
+
} else {
|
|
109
|
+
throw new Error(`PROXY v2: unsupported address family/protocol 0x${famProto.toString(16)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
header: {
|
|
114
|
+
version : 2,
|
|
115
|
+
command : 'PROXY',
|
|
116
|
+
family,
|
|
117
|
+
transport,
|
|
118
|
+
sourceAddress,
|
|
119
|
+
sourcePort,
|
|
120
|
+
destinationAddress,
|
|
121
|
+
destinationPort,
|
|
122
|
+
},
|
|
123
|
+
headerLength,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function ipv6FromBytes(bytes) {
|
|
128
|
+
const segments = [];
|
|
129
|
+
for (let i = 0; i < 16; i += 2) {
|
|
130
|
+
segments.push(bytes.readUInt16BE(i).toString(16));
|
|
131
|
+
}
|
|
132
|
+
return segments.join(':');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Test helpers — build wire-format headers used by tests and example code.
|
|
136
|
+
function buildV1({ family = 'TCP4', sourceAddress, destinationAddress, sourcePort, destinationPort }) {
|
|
137
|
+
return Buffer.from(`PROXY ${family} ${sourceAddress} ${destinationAddress} ${sourcePort} ${destinationPort}\r\n`, 'ascii');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildV2Ipv4({ sourceAddress, destinationAddress, sourcePort, destinationPort, transport = 'STREAM' }) {
|
|
141
|
+
const buf = Buffer.alloc(16 + 12);
|
|
142
|
+
V2_SIGNATURE.copy(buf, 0);
|
|
143
|
+
buf[12] = 0x21; // version 2 | PROXY command
|
|
144
|
+
buf[13] = 0x10 | (transport === 'DGRAM' ? 0x02 : 0x01); // IPv4 | STREAM/DGRAM
|
|
145
|
+
buf.writeUInt16BE(12, 14);
|
|
146
|
+
sourceAddress.split('.').forEach((o, i) => { buf[16 + i] = parseInt(o, 10); });
|
|
147
|
+
destinationAddress.split('.').forEach((o, i) => { buf[20 + i] = parseInt(o, 10); });
|
|
148
|
+
buf.writeUInt16BE(sourcePort, 24);
|
|
149
|
+
buf.writeUInt16BE(destinationPort, 26);
|
|
150
|
+
return buf;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { parse, parseV1, parseV2, buildV1, buildV2Ipv4 };
|
package/lib/reader.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* [Reader description]
|
|
4
3
|
* @param {[type]} buffer [description]
|
|
@@ -19,9 +18,12 @@ function BufferReader(buffer, offset) {
|
|
|
19
18
|
*/
|
|
20
19
|
BufferReader.read = function(buffer, offset, length) {
|
|
21
20
|
let a = [];
|
|
22
|
-
let c = Math.ceil(length / 8);
|
|
23
21
|
let l = Math.floor(offset / 8);
|
|
24
22
|
const m = offset % 8;
|
|
23
|
+
// Need enough bytes to cover the bit range [offset, offset+length); when the
|
|
24
|
+
// offset isn't byte-aligned, the read can straddle one more byte than
|
|
25
|
+
// ceil(length/8) alone accounts for.
|
|
26
|
+
let c = Math.ceil((length + m) / 8);
|
|
25
27
|
function t(n) {
|
|
26
28
|
const r = [ 0, 0, 0, 0, 0, 0, 0, 0 ];
|
|
27
29
|
for (let i = 7; i >= 0; i--) {
|
package/lib/writer.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dns2",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
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
|
-
"
|
|
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/
|
|
44
|
+
"url": "git+https://github.com/lsongdev/node-dns.git"
|
|
31
45
|
},
|
|
32
46
|
"bugs": {
|
|
33
|
-
"url": "https://github.com/
|
|
47
|
+
"url": "https://github.com/lsongdev/node-dns/issues"
|
|
34
48
|
},
|
|
35
|
-
"homepage": "https://github.com/
|
|
49
|
+
"homepage": "https://github.com/lsongdev/node-dns#readme",
|
|
36
50
|
"devDependencies": {
|
|
37
|
-
"eslint": "^
|
|
38
|
-
"eslint-
|
|
39
|
-
"eslint
|
|
40
|
-
"
|
|
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
|
}
|