dns2 1.5.0 → 2.0.2
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/.eslintrc +16 -0
- package/.github/FUNDING.yml +12 -0
- package/.github/workflows/lint.js.yml +17 -0
- package/.github/workflows/node.js.yml +1 -2
- package/README.md +46 -15
- package/client/doh.js +24 -16
- package/client/google.js +4 -4
- package/client/tcp.js +10 -10
- package/client/udp.js +6 -6
- package/example/client/doh.js +5 -5
- package/example/client/google.js +1 -2
- package/example/client/tcp-custom-dns.js +4 -4
- package/example/client/tcp.js +4 -4
- package/example/client/udp-custom-dns.js +4 -4
- package/example/client/udp-default.js +2 -2
- package/example/client/udp-subnet.js +2 -2
- package/example/client/udp.js +3 -3
- package/example/server/dns.js +46 -0
- package/example/server/doh.js +18 -16
- package/example/server/tcp.js +6 -6
- package/example/server/udp.js +4 -4
- package/index.js +22 -14
- package/lib/reader.js +20 -22
- package/lib/writer.js +11 -11
- package/package.json +11 -3
- package/packet.js +217 -202
- package/server/dns.js +94 -0
- package/server/doh.js +130 -127
- package/server/index.js +10 -3
- package/server/tcp.js +14 -6
- package/server/udp.js +10 -4
- package/test/index.js +255 -104
- package/test/test.js +24 -17
- package/.travis.yml +0 -4
package/server/dns.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
const DOHServer = require('./doh');
|
|
3
|
+
const TCPServer = require('./tcp');
|
|
4
|
+
const UDPServer = require('./udp');
|
|
5
|
+
|
|
6
|
+
class DNSServer extends EventEmitter {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
super();
|
|
9
|
+
this.servers = {};
|
|
10
|
+
if (options.doh) {
|
|
11
|
+
this.servers.doh = (new DOHServer(options.doh))
|
|
12
|
+
.on('error', error => this.emit('error', error, 'doh'));
|
|
13
|
+
}
|
|
14
|
+
if (options.tcp) {
|
|
15
|
+
this.servers.tcp = (new TCPServer())
|
|
16
|
+
.on('error', error => this.emit('error', error, 'tcp'));
|
|
17
|
+
}
|
|
18
|
+
if (options.udp) {
|
|
19
|
+
this.servers.udp = (new UDPServer(typeof options.udp === 'object' ? options.udp : undefined))
|
|
20
|
+
.on('error', error => this.emit('error', error, 'udp'));
|
|
21
|
+
}
|
|
22
|
+
const servers = Object.values(this.servers);
|
|
23
|
+
this.closed = Promise.all(
|
|
24
|
+
servers.map(server => new Promise(resolve => server.once('close', resolve))),
|
|
25
|
+
).then(() => {
|
|
26
|
+
this.emit('close');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.listening = Promise.all(
|
|
30
|
+
servers.map(server => new Promise(resolve => server.once('listening', resolve))),
|
|
31
|
+
).then(() => {
|
|
32
|
+
const addresses = this.addresses();
|
|
33
|
+
this.emit('listening', addresses);
|
|
34
|
+
return addresses;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const emitRequest = (request, send, client) => this.emit('request', request, send, client);
|
|
38
|
+
const emitRequestError = (error) => this.emit('requestError', error);
|
|
39
|
+
for (const server of servers) {
|
|
40
|
+
server.on('request', emitRequest);
|
|
41
|
+
server.on('requestError', emitRequestError);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (options.handle) {
|
|
45
|
+
this.on('request', options.handle.bind(options));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
addresses() {
|
|
50
|
+
const addresses = {};
|
|
51
|
+
const { udp, tcp, doh } = this.servers;
|
|
52
|
+
if (udp) {
|
|
53
|
+
addresses.udp = udp.address();
|
|
54
|
+
}
|
|
55
|
+
if (tcp) {
|
|
56
|
+
addresses.tcp = tcp.address();
|
|
57
|
+
}
|
|
58
|
+
if (doh) {
|
|
59
|
+
addresses.doh = doh.address();
|
|
60
|
+
}
|
|
61
|
+
return addresses;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
listen(options = {}) {
|
|
65
|
+
for (const serverType of Object.keys(this.servers)) {
|
|
66
|
+
const server = this.servers[serverType];
|
|
67
|
+
const serverOptions = options[serverType]; // Port or { port, address }
|
|
68
|
+
|
|
69
|
+
if (serverOptions && serverOptions.port) {
|
|
70
|
+
server.listen(serverOptions.port, serverOptions.address);
|
|
71
|
+
} else {
|
|
72
|
+
server.listen(serverOptions);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return this.listening;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
close() {
|
|
80
|
+
const { doh, udp, tcp } = this.servers;
|
|
81
|
+
if (udp) {
|
|
82
|
+
udp.close();
|
|
83
|
+
}
|
|
84
|
+
if (tcp) {
|
|
85
|
+
tcp.close();
|
|
86
|
+
}
|
|
87
|
+
if (doh) {
|
|
88
|
+
doh.close();
|
|
89
|
+
}
|
|
90
|
+
return this.closed;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = DNSServer;
|
package/server/doh.js
CHANGED
|
@@ -1,143 +1,146 @@
|
|
|
1
1
|
const http = require('http');
|
|
2
2
|
const https = require('https');
|
|
3
|
-
const
|
|
4
|
-
const url = require('url');
|
|
5
|
-
const EventEmitter = require("events");
|
|
3
|
+
const { URL } = require('url');
|
|
6
4
|
const Packet = require('../packet');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
constructor(options) {
|
|
10
|
-
super();
|
|
5
|
+
const EventEmitter = require('events');
|
|
6
|
+
const { debuglog } = require('util');
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
var port = 80
|
|
14
|
-
var sslport = 443;
|
|
15
|
-
var cert = '';
|
|
16
|
-
var key = '';
|
|
17
|
-
if (typeof options === 'object') {
|
|
18
|
-
if (options.http) port = options.port;
|
|
19
|
-
if (options.sslport) sslport = options.sslport;
|
|
20
|
-
if (options.cert) cert = options.cert;
|
|
21
|
-
if (options.key) key = options.key;
|
|
22
|
-
}
|
|
8
|
+
const debug = debuglog('dns2-server');
|
|
23
9
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
const decodeBase64URL = str => {
|
|
11
|
+
let queryData = str
|
|
12
|
+
.replace(/-/g, '+')
|
|
13
|
+
.replace(/_/g, '/');
|
|
14
|
+
const pad = queryData.length % 4;
|
|
15
|
+
if (pad === 1) return;
|
|
16
|
+
if (pad) {
|
|
17
|
+
queryData += new Array(5 - pad).join('=');
|
|
18
|
+
}
|
|
19
|
+
return queryData;
|
|
20
|
+
};
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
httpServer.on("request", this.handleRequest.bind(this))
|
|
22
|
+
const readStream = stream => new Promise((resolve, reject) => {
|
|
23
|
+
let buffer = '';
|
|
24
|
+
stream
|
|
25
|
+
.on('error', reject)
|
|
26
|
+
.on('data', chunk => { buffer += chunk; })
|
|
27
|
+
.on('end', () => resolve(buffer));
|
|
28
|
+
});
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
30
|
+
class Server extends EventEmitter {
|
|
31
|
+
constructor(options) {
|
|
32
|
+
super();
|
|
33
|
+
const { ssl } = Object.assign(this, { cors: true }, options);
|
|
34
|
+
this.server = (ssl ? https.createServer(options) : http.createServer())
|
|
35
|
+
.on('request', this.handleRequest.bind(this))
|
|
36
|
+
.on('listening', () => this.emit('listening', this.address()))
|
|
37
|
+
.on('error', error => this.emit('error', error))
|
|
38
|
+
.on('close', () => {
|
|
39
|
+
this.server.removeAllListeners();
|
|
40
|
+
this.emit('close');
|
|
41
|
+
});
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
async handleRequest(client, res) {
|
|
46
|
+
try {
|
|
47
|
+
const { method, url, headers } = client;
|
|
48
|
+
const { pathname, searchParams: query } = new URL(url, 'http://unused/');
|
|
49
|
+
const { cors } = this;
|
|
50
|
+
if (cors === true) {
|
|
51
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
52
|
+
} else if (typeof cors === 'string') {
|
|
53
|
+
res.setHeader('Access-Control-Allow-Origin', cors);
|
|
54
|
+
res.setHeader('Vary', 'Origin');
|
|
55
|
+
} else if (typeof cors === 'function') {
|
|
56
|
+
const isAllowed = cors(headers.origin);
|
|
57
|
+
res.setHeader('Access-Control-Allow-Origin', isAllowed ? headers.origin : 'false');
|
|
58
|
+
res.setHeader('Vary', 'Origin');
|
|
59
|
+
}
|
|
60
|
+
// debug
|
|
61
|
+
debug('request', method, url);
|
|
62
|
+
// We are only handling get and post as reqired by rfc
|
|
63
|
+
if ((method !== 'GET' && method !== 'POST')) {
|
|
64
|
+
res.writeHead(405, { 'Content-Type': 'text/plain' });
|
|
65
|
+
res.write('405 Method not allowed\n');
|
|
66
|
+
res.end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Check so the uri is correct
|
|
70
|
+
if (pathname !== '/dns-query') {
|
|
71
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
72
|
+
res.write('404 Not Found\n');
|
|
73
|
+
res.end();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Make sure the requestee is requesting the correct content type
|
|
77
|
+
const contentType = headers.accept;
|
|
78
|
+
if (contentType !== 'application/dns-message') {
|
|
79
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
80
|
+
res.write('400 Bad Request: Illegal content type\n');
|
|
81
|
+
res.end();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
let queryData;
|
|
85
|
+
if (method === 'GET') {
|
|
86
|
+
// Parse query string for the request data
|
|
87
|
+
const dns = query.get('dns');
|
|
88
|
+
if (!dns) {
|
|
89
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
90
|
+
res.write('400 Bad Request: No query defined\n');
|
|
91
|
+
res.end();
|
|
92
|
+
return;
|
|
58
93
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return;
|
|
94
|
+
// Decode from Base64Url Encoding
|
|
95
|
+
const base64 = decodeBase64URL(dns);
|
|
96
|
+
if (!base64) {
|
|
97
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
98
|
+
res.write('400 Bad Request: Invalid query data\n');
|
|
99
|
+
res.end();
|
|
100
|
+
return;
|
|
67
101
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//Parse query string for the request data
|
|
82
|
-
const queryObject = url.parse(req.url,true).query;
|
|
83
|
-
if (!queryObject.dns) {
|
|
84
|
-
res.writeHead(400, {"Content-Type": "text/plain"});
|
|
85
|
-
res.write("400 Bad Request: No query defined\n");
|
|
86
|
-
res.end();
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
//Decode from Base64Url Encoding
|
|
91
|
-
var queryData = queryObject.dns.replace(/-/g, '+').replace(/_/g, '/');
|
|
92
|
-
var pad = queryData.length % 4;
|
|
93
|
-
if(pad) {
|
|
94
|
-
if(pad === 1) {
|
|
95
|
-
res.writeHead(400, {"Content-Type": "text/plain"});
|
|
96
|
-
res.write("400 Bad Request: Invalid query data\n");
|
|
97
|
-
res.end();
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
queryData += new Array(5-pad).join('=');
|
|
101
|
-
}
|
|
102
|
+
// Decode Base64 to buffer
|
|
103
|
+
queryData = Buffer.from(base64, 'base64');
|
|
104
|
+
} else if (method === 'POST') {
|
|
105
|
+
queryData = await readStream(client);
|
|
106
|
+
}
|
|
107
|
+
// Parse DNS query and Raise event.
|
|
108
|
+
const message = Packet.parse(queryData);
|
|
109
|
+
this.emit('request', message, this.response.bind(this, res), client);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
this.emit('requestError', e);
|
|
112
|
+
res.destroy();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
102
115
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const message = Packet.parse(request);
|
|
115
|
-
|
|
116
|
-
//then raise the event
|
|
117
|
-
this.emit('request', message, this.response.bind(this, res), req);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
};
|
|
116
|
+
/**
|
|
117
|
+
* Send of the response to the client
|
|
118
|
+
* @param {*} res
|
|
119
|
+
* @param {*} message
|
|
120
|
+
*/
|
|
121
|
+
response(res, message) {
|
|
122
|
+
debug('response');
|
|
123
|
+
res.setHeader('Content-Type', 'application/dns-message');
|
|
124
|
+
res.writeHead(200);
|
|
125
|
+
res.end(message.toBuffer());
|
|
126
|
+
}
|
|
122
127
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
/**
|
|
129
|
+
* listen
|
|
130
|
+
* @param {*} port
|
|
131
|
+
* @returns
|
|
132
|
+
*/
|
|
133
|
+
listen(port, address) {
|
|
134
|
+
return this.server.listen(port || this.port, address);
|
|
135
|
+
}
|
|
129
136
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
request.on('data', chunk => {
|
|
134
|
-
body += chunk;
|
|
135
|
-
});
|
|
136
|
-
request.on('end', () => {
|
|
137
|
-
callback(body);
|
|
138
|
-
});
|
|
139
|
-
};
|
|
137
|
+
address() {
|
|
138
|
+
return this.server.address();
|
|
139
|
+
}
|
|
140
140
|
|
|
141
|
+
close() {
|
|
142
|
+
return this.server.close();
|
|
143
|
+
}
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
module.exports = Server;
|
|
146
|
+
module.exports = Server;
|
package/server/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const UDPServer = require('./udp');
|
|
2
2
|
const TCPServer = require('./tcp');
|
|
3
3
|
const DOHServer = require('./doh');
|
|
4
|
+
const DNSServer = require('./dns');
|
|
4
5
|
|
|
5
6
|
const createUDPServer = options => {
|
|
6
7
|
return new UDPServer(options);
|
|
@@ -12,13 +13,19 @@ const createTCPServer = options => {
|
|
|
12
13
|
|
|
13
14
|
const createDOHServer = options => {
|
|
14
15
|
return new DOHServer(options);
|
|
15
|
-
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const createServer = options => {
|
|
19
|
+
return new DNSServer(options);
|
|
20
|
+
};
|
|
16
21
|
|
|
17
22
|
module.exports = {
|
|
18
23
|
UDPServer,
|
|
19
24
|
TCPServer,
|
|
20
25
|
DOHServer,
|
|
26
|
+
DNSServer,
|
|
21
27
|
createTCPServer,
|
|
22
28
|
createUDPServer,
|
|
23
|
-
createDOHServer
|
|
24
|
-
|
|
29
|
+
createDOHServer,
|
|
30
|
+
createServer,
|
|
31
|
+
};
|
package/server/tcp.js
CHANGED
|
@@ -9,18 +9,26 @@ class Server extends tcp.Server {
|
|
|
9
9
|
}
|
|
10
10
|
this.on('connection', this.handle.bind(this));
|
|
11
11
|
}
|
|
12
|
+
|
|
12
13
|
async handle(client) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
try {
|
|
15
|
+
const data = await Packet.readStream(client);
|
|
16
|
+
const message = Packet.parse(data);
|
|
17
|
+
this.emit('request', message, this.response.bind(this, client), client);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
this.emit('requestError', e);
|
|
20
|
+
client.destroy();
|
|
21
|
+
}
|
|
16
22
|
}
|
|
23
|
+
|
|
17
24
|
response(client, message) {
|
|
18
|
-
if (message instanceof Packet)
|
|
25
|
+
if (message instanceof Packet) {
|
|
19
26
|
message = message.toBuffer();
|
|
27
|
+
}
|
|
20
28
|
const len = Buffer.alloc(2);
|
|
21
29
|
len.writeUInt16BE(message.length);
|
|
22
|
-
client.end(Buffer.concat([len, message]));
|
|
30
|
+
client.end(Buffer.concat([ len, message ]));
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
module.exports = Server;
|
|
34
|
+
module.exports = Server;
|
package/server/udp.js
CHANGED
|
@@ -18,13 +18,18 @@ class Server extends udp.Socket {
|
|
|
18
18
|
}
|
|
19
19
|
this.on('message', this.handle.bind(this));
|
|
20
20
|
}
|
|
21
|
+
|
|
21
22
|
handle(data, rinfo) {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
try {
|
|
24
|
+
const message = Packet.parse(data);
|
|
25
|
+
this.emit('request', message, this.response.bind(this, rinfo), rinfo);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
this.emit('requestError', e);
|
|
28
|
+
}
|
|
24
29
|
}
|
|
30
|
+
|
|
25
31
|
response(rinfo, message) {
|
|
26
|
-
if (message instanceof Packet)
|
|
27
|
-
message = message.toBuffer();
|
|
32
|
+
if (message instanceof Packet) { message = message.toBuffer(); }
|
|
28
33
|
return new Promise((resolve, reject) => {
|
|
29
34
|
this.send(message, rinfo.port, rinfo.address, err => {
|
|
30
35
|
if (err) return reject(err);
|
|
@@ -32,6 +37,7 @@ class Server extends udp.Socket {
|
|
|
32
37
|
});
|
|
33
38
|
});
|
|
34
39
|
}
|
|
40
|
+
|
|
35
41
|
listen(port, address) {
|
|
36
42
|
return new Promise(resolve =>
|
|
37
43
|
this.bind(port, address, resolve));
|