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/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 fs = require('fs');
4
- const url = require('url');
5
- const EventEmitter = require("events");
3
+ const { URL } = require('url');
6
4
  const Packet = require('../packet');
7
-
8
- class Server extends EventEmitter {
9
- constructor(options) {
10
- super();
5
+ const EventEmitter = require('events');
6
+ const { debuglog } = require('util');
11
7
 
12
- // Set defaults and then check what the cat brought in
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
- //If no cert or key specified make a futile atempt of reading default names
25
- if (cert=='') cert = fs.readFileSync(path.join(__dirname,"server.crt"));
26
- if (key=='') key = fs.readFileSync(path.join(__dirname,"secret.key"));
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
- // if http port is specified create http server. However DoH needs SSL so a proxy must be infront.
29
- if (port!=0)
30
- var httpOptions = {
31
- };
32
- var httpServer = http.createServer(httpOptions).listen(port, function(){
33
- this.emit('listening', "http", port);
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
- // if SSL-port, key and cert is specified then create an SSL endpoint
38
- if (sslport!=0&&key!=''&&cert!='')
39
- var httpsOptions = {
40
- key: key,
41
- cert: cert
42
- };
43
- var httpsServer = https.createServer(httpsOptions).listen(sslport, function(){
44
- this.emit('listening', "https", sslport);
45
- });
46
- httpsServer.on("request", this.handleRequest.bind(this))
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
- handleRequest(req,res) {
50
-
51
- // We are only handling get and post as reqired by rfc
52
- const method = req.method;
53
- if ((method!="GET" && method!="POST")) {
54
- res.writeHead(405, {"Content-Type": "text/plain"});
55
- res.write("405 Method not allowed\n");
56
- res.end();
57
- return;
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
- // Check so the uri is correct
61
- const uri = url.parse(req.url).pathname;
62
- if (uri!="/dns-query") {
63
- res.writeHead(404, {"Content-Type": "text/plain"});
64
- res.write("404 Not Found\n");
65
- res.end();
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
- // Make sure the requestee is requesting the correct content type
70
- const contentType = req.headers['accept'];
71
- if (contentType!="application/dns-message") {
72
- res.writeHead(400, {"Content-Type": "text/plain"});
73
- res.write("400 Bad Request: Illegal content type\n");
74
- res.end();
75
- return;
76
- }
77
-
78
- var queryData = {};
79
-
80
- if (method == 'GET') {
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
- //Decode Base64 to buffer
104
- queryData = Buffer.from(queryData, 'base64');
105
- const message = Packet.parse(queryData);
106
-
107
- //Raise event
108
- this.emit('request', message, this.response.bind(this, res), req);
109
-
110
- } else {
111
-
112
- // Collect request from client and..
113
- collectRequestData.then((request)=>{
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
- // Send of the response to the client
124
- response(res, message) {
125
- res.setHeader('Content-Type', 'application/dns-message');
126
- res.writeHead(200);
127
- res.end(message.toBuffer());
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
- // Wait for the request body to be received
131
- async collectRequestData(request) {
132
- let body = '';
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
- const data = await Packet.readStream(client);
14
- const message = Packet.parse(data);
15
- this.emit('request', message, this.response.bind(this, client), client);
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
- const message = Packet.parse(data);
23
- this.emit('request', message, this.response.bind(this, rinfo), rinfo);
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));