dns2
Version:
A DNS Server and Client Implementation in Pure JavaScript with no dependencies.
108 lines (96 loc) • 2.88 kB
JavaScript
const http = require('node:http');
const https = require('node:https');
const http2 = require('node:http2');
const Packet = require('../packet');
const protocols = {
'http:': http.get,
'https:': https.get,
'h2:': (url, options, done) => {
const urlObj = new URL(url);
const client = http2.connect(url.replace('h2:', 'https:'));
const req = client.request({
':path': `${urlObj.pathname}${urlObj.search}`,
':method': 'GET',
...options.headers,
});
req.on('response', headers => {
client.close();
done({
headers,
statusCode: headers[':status'],
on: req.on.bind(req),
});
});
req.on('error', err => {
client.close();
throw err;
});
req.end();
},
};
const makeRequest = (url, query) =>
new Promise((resolve, reject) => {
const index = url.indexOf('://');
if (index === -1) url = `https://${url}`;
const u = new URL(url);
// The DNS query is included in a single variable named “dns” in the
// query component of the request URI. The value of the “dns” variable
// is the content of the DNS request message, encoded with base64url
// [RFC4648](https://datatracker.ietf.org/doc/html/rfc8484#section-4.1).
const searchParams = u.searchParams;
searchParams.set('dns', query);
u.search = searchParams.toString();
const get = protocols[u.protocol];
if (!get)
throw new Error(
`Unsupported protocol: ${u.protocol}, must be specified (http://, https:// or h2://)`,
);
const req = get(
u.toString(),
{ headers: { accept: 'application/dns-message' } },
resolve,
);
if (req) req.on('error', reject);
});
const readStream = res =>
new Promise((resolve, reject) => {
const chunks = [];
res
.on('error', reject)
.on('data', chunk => chunks.push(chunk))
.on('end', () => {
const data = Buffer.concat(chunks);
if (res.statusCode !== 200) {
return reject(
new Error(`HTTP ${res.statusCode}: ${data.toString()}`),
);
}
resolve(data);
});
});
const buildQuery = ({
name,
type = 'A',
cls = Packet.CLASS.IN,
clientIp,
recursive = true,
}) => {
const packet = new Packet();
packet.header.rd = recursive ? 1 : 0;
if (clientIp) {
packet.additionals.push(
Packet.Resource.EDNS([Packet.Resource.EDNS.ECS(clientIp)]),
);
}
packet.questions.push({ name, class: cls, type: Packet.TYPE[type] });
return packet.toBase64URL();
};
const DOHClient = ({ dns }) => {
return async (name, type, cls, options = {}) => {
const query = buildQuery({ name, type, cls, ...options });
const response = await makeRequest(dns, query);
const data = await readStream(response);
return Packet.parse(data);
};
};
module.exports = DOHClient;