Fix DoH URL path validation, more error handling for connections

This commit is contained in:
ekrekeler 2025-06-16 00:54:31 -05:00
parent 47cf7f9d13
commit a670386281
No known key found for this signature in database
GPG key ID: 4C66C864B6B00854
3 changed files with 34 additions and 22 deletions

View file

@ -480,6 +480,7 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
let client;
let resolver;
const socketName = `${resolverServer}:${resolverPort}`;
// Transport method determines which client type to use
switch (method) {
@ -500,13 +501,13 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
options.servername = resolverServer;
}
client = tls.connect(options, () => {
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
log.debug("dns", `Connected to ${socketName}`);
client.write(buf);
});
} else {
client = new Socket();
client.connect(resolverPort, resolverServer, () => {
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
log.debug("dns", `Connected to ${socketName}`);
client.write(buf);
});
}
@ -518,11 +519,14 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
let expectedLength = 0;
let isValidLength = false;
client.on("error", (err) => {
if (err.code === "ETIMEDOUT") {
err.message = `Connection to ${socketName} timed out`;
}
reject(err);
});
client.setTimeout(timeout, () => {
client.destroy();
reject({ message: "Connection timed out" });
reject({ message: `Request to ${socketName} timed out` });
});
client.on("data", (chunk) => {
if (data.length === 0) {
@ -542,7 +546,7 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
}
});
client.on("close", () => {
log.debug("dns", "Connection closed");
log.debug("dns", `Connection to ${socketName} closed`);
if (!isValidLength) {
reject({ message: lenErrMsg });
}
@ -553,7 +557,7 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
case "DOH": {
const queryPath = dohUsePost ? dohQuery : `${dohQuery}?dns=${buf.toString("base64url")}`;
const requestURL = url.parse(`https://${resolverServer}:${resolverPort}/${queryPath}`, true);
const requestURL = url.parse(`https://${socketName}/${queryPath}`, true);
const mimeType = "application/dns-message";
const options = {
hostname: requestURL.hostname,
@ -596,10 +600,10 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
}
// Validate response from resolver
if (statusCode !== 200) {
reject({ message: `Request failed with status code ${statusCode}` });
reject({ message: `Request to ${socketName} failed with status code ${statusCode}` });
return;
} else if (contentType !== mimeType) {
reject({ message: `Content-Type was "${contentType}", expected ${mimeType}` });
reject({ message: `Response from ${socketName} Content-Type was "${contentType}", expected ${mimeType}` });
return;
}
// Read the response body into a buffer
@ -627,10 +631,10 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
});
client.setTimeout(timeout, () => {
client.destroy();
reject({ message: "Request timed out" });
reject({ message: `Request to ${socketName} timed out` });
});
client.on("connect", () => {
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
log.debug("dns", `Connected to ${socketName}`);
});
const req = client.request(headers);
req.on("error", (err) => {
@ -653,11 +657,11 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
});
client.setTimeout(timeout, () => {
client.destroy();
reject({ message: "Request timed out" });
reject({ message: `Request to ${socketName} timed out` });
});
client.on("socket", (socket) => {
socket.on("secureConnect", () => {
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
log.debug("dns", `Connected to ${socketName}`);
});
});
if (dohUsePost) {
@ -669,7 +673,7 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
reject(err);
});
client.on("close", () => {
log.debug("dns", "Connection closed");
log.debug("dns", `Connection to ${socketName} closed`);
});
});
break;
@ -695,15 +699,15 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
reject(err);
});
client.on("listening", () => {
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
log.debug("dns", `Connected to ${socketName}`);
timer = setTimeout(() => {
reject({ message: "Query timed out" });
reject({ message: `Query to ${socketName} timed out` });
client.close();
}, timeout);
});
client.on("close", () => {
clearTimeout(timer);
log.debug("dns", "Connection closed");
log.debug("dns", `Connection to ${socketName} closed`);
});
});
client.send(buf, 0, buf.length, resolverPort, resolverServer);

View file

@ -1279,6 +1279,7 @@ export default {
case "DoH":
return this.hostnameRegexPattern.source;
case "DoT":
case "DoQ":
return this.ipOrHostnameRegexPattern.source;
}
}

View file

@ -162,21 +162,28 @@ export function dnsNameRegexPattern() {
/**
* Regex patterns for validating URL paths
* @param {boolean} qstr whether or not the url follows query string format
* @param {boolean} qstr True if the url should contain a query string
* @param {boolean} tmpl True if the url should contain templating, `{query}`
* Takes precedence over qstr if both are true.
* @returns {RegExp} The requested regex
*/
export function urlPathRegexPattern(qstr = true) {
export function urlPathRegexPattern(qstr = false, tmpl = false) {
// Matches any URL path, including empty string
const pathRegexPattern = /^\/?(([a-zA-Z0-9\-_%])+\/)*[a-zA-Z0-9\-_%]*(\?([a-zA-Z0-9\-_%]+=[a-zA-Z0-9\-_%]*&?)+)?$/;
// Ensures a URL path follows query string format
const queryStringRegexPattern = /^\/?(([a-zA-Z0-9\-_%])+\/)*[a-zA-Z0-9\-_%]*\?([a-zA-Z0-9\-_%]+=[a-zA-Z0-9\-_%]*&?)+$/;
const queryRegexPattern = /^\/?(([a-zA-Z0-9\-_%])+\/)*[a-zA-Z0-9\-_%]*\?([a-zA-Z0-9\-_%]+=[a-zA-Z0-9\-_%]*&?)+$/;
// Only checks for valid URL path containing "{query}"
/* eslint-disable-next-line no-useless-escape */
const queryRegexPattern = /^[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*\{query\}[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*$/;
const queryTemplateRegexPattern = /^[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*\{query\}[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*$/;
if (qstr) {
return queryStringRegexPattern;
if (tmpl) {
return queryTemplateRegexPattern;
}
if (qstr) {
return queryRegexPattern;
}
return pathRegexPattern;
}
/**
* Get the tag color options