Skip to content

Commit a9d3400

Browse files
committed
add h2(http2) supports, and fix #89
1 parent 0fed203 commit a9d3400

File tree

2 files changed

+94
-49
lines changed

2 files changed

+94
-49
lines changed

client/doh.js

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,85 @@
1+
const http = require('http');
2+
const https = require('https');
3+
const http2 = require('http2');
14
const Packet = require('../packet');
25

3-
const defaultGet = url => new Promise((resolve, reject) => {
4-
const headers = {
5-
accept: 'application/dns-message',
6-
};
7-
const base = url.startsWith('https') ? require('https') : require('http');
8-
const req = base.get(url, { headers }, resolve);
9-
req.on('error', reject);
10-
});
6+
const protocols = {
7+
'http:': http.get,
8+
'https:': https.get,
9+
'h2:': (url, options, cb) => {
10+
const urlObj = new URL(url);
11+
const client = http2.connect(url.replace('h2:', 'https:'));
12+
const req = client.request({
13+
':path': `${urlObj.pathname}${urlObj.search}`,
14+
':method': 'GET',
15+
...options.headers
16+
});
17+
18+
req.on('response', headers => {
19+
client.close();
20+
cb({
21+
headers,
22+
statusCode: headers[':status'],
23+
on: req.on.bind(req)
24+
});
25+
});
26+
27+
req.on('error', err => {
28+
client.close();
29+
throw err;
30+
});
1131

12-
const readStream = stream => {
13-
const buffer = [];
14-
return new Promise((resolve, reject) => {
15-
stream
16-
.on('error', reject)
17-
.on('data', chunk => buffer.push(chunk))
18-
.on('end', () => resolve(Buffer.concat(buffer)));
19-
});
32+
req.end();
33+
}
2034
};
2135

22-
/**
23-
* @docs https://tools.ietf.org/html/rfc8484
24-
* @param {*} param0
25-
*/
26-
const DOHClient = ({ dns, http, get = defaultGet } = {}) => {
27-
return (name, type = 'A', cls = Packet.CLASS.IN, { clientIp, recursive = true } = {}) => {
28-
const packet = new Packet();
29-
// see https://github.com/song940/node-dns/issues/29
30-
if (recursive) {
31-
packet.header.rd = 1;
32-
}
33-
if (clientIp) {
34-
packet.additionals.push(Packet.Resource.EDNS([
35-
Packet.Resource.EDNS.ECS(clientIp),
36-
]));
37-
}
38-
packet.questions.push({
39-
name,
40-
class : cls,
41-
type : Packet.TYPE[type],
36+
const makeRequest = (url, query) => new Promise((resolve, reject) => {
37+
const index = url.indexOf('://');
38+
if (index === -1) url = `https://${url}`;
39+
const u = new URL(url);
40+
const get = protocols[u.protocol];
41+
if (!get) throw new Error(`Unsupported protocol: ${u.protocol}, must be specified (http://, https:// or h2://)`);
42+
if (!u.pathname) url += '/dns-query?dns={query}';
43+
url = url.replace('{query}', query);
44+
const req = get(url, { headers: { accept: 'application/dns-message' } }, resolve);
45+
if (req) req.on('error', reject);
46+
});
47+
48+
const readStream = res => new Promise((resolve, reject) => {
49+
const chunks = [];
50+
res
51+
.on('error', reject)
52+
.on('data', chunk => chunks.push(chunk))
53+
.on('end', () => {
54+
const data = Buffer.concat(chunks);
55+
if (res.statusCode !== 200) {
56+
reject(new Error(`HTTP ${res.statusCode}: ${data.toString()}`));
57+
}
58+
resolve(data);
4259
});
43-
const query = packet.toBase64URL();
44-
return Promise.resolve(get(`http${http ? '' : 's'}://${dns}/dns-query?dns=${query}`))
45-
.then(readStream)
46-
.then(Packet.parse);
60+
});
61+
62+
const buildQuery = ({ name, type = 'A', cls = Packet.CLASS.IN, clientIp, recursive = true }) => {
63+
const packet = new Packet();
64+
packet.header.rd = recursive ? 1 : 0;
65+
66+
if (clientIp) {
67+
packet.additionals.push(Packet.Resource.EDNS([
68+
Packet.Resource.EDNS.ECS(clientIp)
69+
]));
70+
}
71+
72+
packet.questions.push({ name, class: cls, type: Packet.TYPE[type] });
73+
return packet.toBase64URL();
74+
};
75+
76+
const DOHClient = ({ dns }) => {
77+
return async (name, type, cls, options = {}) => {
78+
const query = buildQuery({ name, type, cls, ...options });
79+
const response = await makeRequest(dns, query);
80+
const data = await readStream(response);
81+
return Packet.parse(data);
4782
};
4883
};
4984

50-
module.exports = DOHClient;
85+
module.exports = DOHClient;

example/client/doh.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
const { DOHClient } = require('../..');
22

3-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
3+
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
44

5-
const resolve = DOHClient({
6-
dns: '1.1.1.1',
7-
});
5+
// const resolve = DOHClient({
6+
// dns: '1.1.1.1',
7+
// });
88

9-
(async() => {
10-
const response = await resolve('google.com');
11-
console.log(response.answers);
12-
})();
9+
// (async() => {
10+
// const response = await resolve('google.com');
11+
// console.log(response.answers);
12+
// })();
13+
14+
// import DNS2 from 'dns2';
15+
16+
// DOHClient({
17+
// dns: 'h2://ada.openbld.net',
18+
// })('cdnjs.com', 'NS').then(console.log);
19+
20+
DOHClient({
21+
dns: 'h2://ada.openbld.net/dns-query?dns={query}',
22+
})('cdnjs.com', 'NS').then(console.log);

0 commit comments

Comments
 (0)