mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-07-18 23:34:04 +02:00
Replace dns2 module with dns-packet
This commit is contained in:
parent
21f629e055
commit
9c344ad371
5 changed files with 158 additions and 72 deletions
|
@ -85,7 +85,7 @@
|
|||
"croner": "~8.1.0",
|
||||
"dayjs": "~1.11.5",
|
||||
"dev-null": "^0.1.1",
|
||||
"dns2": "song940/node-dns",
|
||||
"dns-packet": "~5.6.1",
|
||||
"dotenv": "~16.0.3",
|
||||
"express": "~4.21.0",
|
||||
"express-basic-auth": "~1.2.1",
|
||||
|
@ -170,6 +170,7 @@
|
|||
"cronstrue": "~2.24.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"delay": "^5.0.0",
|
||||
"dns2": "~2.0.1",
|
||||
"dompurify": "~3.1.7",
|
||||
"eslint": "~8.14.0",
|
||||
"eslint-plugin-jsdoc": "~46.4.6",
|
||||
|
|
|
@ -25,77 +25,49 @@ class DnsMonitorType extends MonitorType {
|
|||
let dnsMessage = "";
|
||||
|
||||
let dnsRes = await dnsResolve(monitor.hostname, monitor.dns_resolve_server, monitor.port, monitor.dns_resolve_type, monitor.dns_transport, monitor.doh_query_path);
|
||||
const records = dnsRes.answers.map(record => {
|
||||
return Buffer.isBuffer(record.data) ? record.data.toString() : record.data;
|
||||
});
|
||||
heartbeat.ping = dayjs().valueOf() - startTime;
|
||||
|
||||
const conditions = ConditionExpressionGroup.fromMonitor(monitor);
|
||||
let conditionsResult = true;
|
||||
const handleConditions = (data) => conditions ? evaluateExpressionGroup(conditions, data) : true;
|
||||
|
||||
let records = [];
|
||||
switch (monitor.dns_resolve_type) {
|
||||
case "A":
|
||||
case "AAAA":
|
||||
records = dnsRes.answers.map(record => {
|
||||
switch (record.type) {
|
||||
case 1: // A
|
||||
case 28: // AAAA
|
||||
return record.address;
|
||||
case 5: // CNAME
|
||||
return record.domain;
|
||||
}
|
||||
});
|
||||
dnsMessage = `Records: ${records.join(" | ")}`;
|
||||
conditionsResult = records.some(record => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "PTR":
|
||||
records = dnsRes.answers.map(record => record.domain);
|
||||
dnsMessage = `Records: ${records.join(" | ")}`;
|
||||
conditionsResult = records.some(record => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "TXT":
|
||||
records = dnsRes.answers.map(record => record.data);
|
||||
dnsMessage = `Records: ${records.join(" | ")}`;
|
||||
case "PTR":
|
||||
case "NS":
|
||||
dnsMessage = records.join(" | ");
|
||||
conditionsResult = records.some(record => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "CNAME":
|
||||
records.push(dnsRes.answers[0].domain);
|
||||
dnsMessage = records[0];
|
||||
conditionsResult = handleConditions({ record: records[0] });
|
||||
break;
|
||||
|
||||
case "CAA":
|
||||
// dns2 library currently has not implemented decoding CAA response
|
||||
//records.push(dnsRes.answers[0].issue);
|
||||
records.push("CAA issue placeholder");
|
||||
dnsMessage = records[0];
|
||||
dnsMessage = records.map(record => `${record.flags} ${record.tag} "${record.value}"`).join(" | ");
|
||||
conditionsResult = handleConditions({ record: records[0] });
|
||||
break;
|
||||
|
||||
case "MX":
|
||||
records = dnsRes.answers.map(record => record.exchange);
|
||||
dnsMessage = dnsRes.answers.map(record => `Hostname: ${record.exchange} ; Priority: ${record.priority}`).join(" | ");
|
||||
conditionsResult = records.some(record => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "NS":
|
||||
records = dnsRes.answers.map(record => record.ns);
|
||||
dnsMessage = `Servers: ${records.join(" | ")}`;
|
||||
dnsMessage = records.map(record => `Hostname: ${record.exchange}; Priority: ${record.priority}`).join(" | ");
|
||||
conditionsResult = records.some(record => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "SOA": {
|
||||
records.push(dnsRes.answers[0].primary);
|
||||
dnsMessage = Object.entries({
|
||||
"Primary-NS": dnsRes.answers[0].primary,
|
||||
"Hostmaster": dnsRes.answers[0].admin,
|
||||
"Serial": dnsRes.answers[0].serial,
|
||||
"Refresh": dnsRes.answers[0].refresh,
|
||||
"Retry": dnsRes.answers[0].retry,
|
||||
"Expire": dnsRes.answers[0].expiration,
|
||||
"MinTTL": dnsRes.answers[0].minimum,
|
||||
"Primary-NS": records[0].mname,
|
||||
"Hostmaster": records[0].rname,
|
||||
"Serial": records[0].serial,
|
||||
"Refresh": records[0].refresh,
|
||||
"Retry": records[0].retry,
|
||||
"Expire": records[0].expire,
|
||||
"MinTTL": records[0].minimum,
|
||||
}).map(([ name, value ]) => {
|
||||
return `${name}: ${value}`;
|
||||
}).join("; ");
|
||||
|
@ -104,8 +76,7 @@ class DnsMonitorType extends MonitorType {
|
|||
}
|
||||
|
||||
case "SRV":
|
||||
records = dnsRes.answers.map(record => record.target);
|
||||
dnsMessage = dnsRes.answers.map((record) => {
|
||||
dnsMessage = records.map((record) => {
|
||||
return Object.entries({
|
||||
"Target": record.target,
|
||||
"Port": record.port,
|
||||
|
|
|
@ -3,8 +3,9 @@ const ping = require("@louislam/ping");
|
|||
const { R } = require("redbean-node");
|
||||
const { log, genSecret, badgeConstants } = require("../src/util");
|
||||
const passwordHash = require("./password-hash");
|
||||
const { UDPClient, TCPClient, DOHClient } = require("dns2");
|
||||
const { isIP, isIPv4, isIPv6 } = require("node:net");
|
||||
const dnsPacket = require("dns-packet");
|
||||
const dgram = require("dgram");
|
||||
const { Socket, isIP, isIPv4, isIPv6 } = require("net");
|
||||
const { Address6 } = require("ip-address");
|
||||
const iconv = require("iconv-lite");
|
||||
const chardet = require("chardet");
|
||||
|
@ -21,6 +22,8 @@ const radiusClient = require("node-radius-client");
|
|||
const redis = require("redis");
|
||||
const oidc = require("openid-client");
|
||||
const tls = require("tls");
|
||||
const https = require("https");
|
||||
const url = require("url");
|
||||
|
||||
const {
|
||||
dictionaries: {
|
||||
|
@ -314,38 +317,149 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, t
|
|||
}
|
||||
}
|
||||
|
||||
// This is the DNS request data that will get encoded later
|
||||
const requestData = {
|
||||
type: "query",
|
||||
id: Math.floor(Math.random() * 65534) + 1,
|
||||
flags: dnsPacket.RECURSION_DESIRED,
|
||||
questions: [{
|
||||
type: rrtype,
|
||||
name: hostname,
|
||||
}],
|
||||
};
|
||||
|
||||
let client;
|
||||
let resolver = null;
|
||||
// Transport method determines which client type to use
|
||||
let resolver;
|
||||
const isSecure = [ "DOH", "DOT" ].includes(transport.toUpperCase());
|
||||
switch (transport.toUpperCase()) {
|
||||
|
||||
case "TCP":
|
||||
resolver = TCPClient({
|
||||
dns: resolverServer,
|
||||
protocol: "tcp:",
|
||||
port: resolverPort,
|
||||
case "DOT": {
|
||||
const buf = dnsPacket.streamEncode(requestData);
|
||||
if (isSecure) {
|
||||
const options = {
|
||||
port: resolverPort,
|
||||
host: resolverServer,
|
||||
// TODO: Option for relaxing certificate validation
|
||||
secureContext: tls.createSecureContext({
|
||||
secureProtocol: "TLSv1_2_method",
|
||||
}),
|
||||
};
|
||||
// TODO: Error handling for untrusted or expired cert
|
||||
client = tls.connect(options, () => {
|
||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
||||
client.write(buf);
|
||||
});
|
||||
} else {
|
||||
client = new Socket();
|
||||
client.connect(resolverPort, resolverServer, () => {
|
||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
||||
client.write(buf);
|
||||
});
|
||||
}
|
||||
client.on("close", () => {
|
||||
log.debug("dns", "Connection closed");
|
||||
});
|
||||
resolver = new Promise((resolve, reject) => {
|
||||
let data = Buffer.alloc(0);
|
||||
let expectedLength = 0;
|
||||
client.on("data", (chunk) => {
|
||||
if (data.length === 0) {
|
||||
if (chunk.byteLength > 1) {
|
||||
const plen = chunk.readUInt16BE(0);
|
||||
expectedLength = plen;
|
||||
if (plen < 12) {
|
||||
reject("Response received is below DNS minimum packet length");
|
||||
}
|
||||
}
|
||||
}
|
||||
data = Buffer.concat([ data, chunk ]);
|
||||
if (data.byteLength >= expectedLength) {
|
||||
client.destroy();
|
||||
const response = dnsPacket.streamDecode(data);
|
||||
log.debug("dns", "Response decoded");
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
case "DOT":
|
||||
resolver = TCPClient({
|
||||
dns: resolverServer,
|
||||
protocol: "tls:",
|
||||
port: resolverPort,
|
||||
});
|
||||
break;
|
||||
case "DOH":
|
||||
}
|
||||
|
||||
case "DOH": {
|
||||
// Set query ID to "0" for HTTP cache friendlyness. See
|
||||
// https://github.com/mafintosh/dns-packet/issues/77
|
||||
requestData.id = 0;
|
||||
const buf = dnsPacket.encode(requestData);
|
||||
// TODO: implement POST requests for wireformat and JSON
|
||||
dohQuery = dohQuery || "dns-query?dns={query}";
|
||||
resolver = DOHClient({
|
||||
dns: `https://${resolverServer}:${resolverPort}/${dohQuery}`,
|
||||
dohQuery = dohQuery.replace("{query}", buf.toString("base64url"));
|
||||
const requestURL = url.parse(`https://${resolverServer}:${resolverPort}/${dohQuery}`, true);
|
||||
const options = {
|
||||
hostname: requestURL.hostname,
|
||||
port: requestURL.port,
|
||||
path: requestURL.path,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/dns-message",
|
||||
},
|
||||
// TODO: Option for relaxing certificate validation
|
||||
};
|
||||
resolver = new Promise((resolve, reject) => {
|
||||
client = https.request(options, (response) => {
|
||||
let data = Buffer.alloc(0);
|
||||
response.on("data", (chunk) => {
|
||||
data = Buffer.concat([ data, chunk ]);
|
||||
});
|
||||
response.on("end", () => {
|
||||
const response = dnsPacket.decode(data);
|
||||
log.debug("dns", "Response decoded");
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
client.on("socket", (socket) => {
|
||||
socket.on("secureConnect", () => {
|
||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
||||
});
|
||||
});
|
||||
client.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
client.on("close", () => {
|
||||
log.debug("dns", "Connection closed");
|
||||
});
|
||||
client.write(buf);
|
||||
client.end();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
resolver = UDPClient({
|
||||
dns: resolverServer,
|
||||
port: resolverPort,
|
||||
socketType: "udp" + String(addressFamily),
|
||||
}
|
||||
|
||||
//case "UDP":
|
||||
default: {
|
||||
const buf = dnsPacket.encode(requestData);
|
||||
client = dgram.createSocket("udp" + String(addressFamily));
|
||||
client.on("connect", () => {
|
||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
||||
});
|
||||
client.on("close", () => {
|
||||
log.debug("dns", "Connection closed");
|
||||
});
|
||||
resolver = new Promise((resolve, reject) => {
|
||||
client.on("message", (rdata, rinfo) => {
|
||||
client.close();
|
||||
const response = dnsPacket.decode(rdata);
|
||||
log.debug("dns", "Response decoded");
|
||||
resolve(response);
|
||||
});
|
||||
client.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
client.send(buf, 0, buf.length, resolverPort, resolverServer);
|
||||
}
|
||||
}
|
||||
|
||||
return resolver(hostname, rrtype);
|
||||
return resolver;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -572,7 +572,7 @@
|
|||
"deleteMaintenanceMsg": "Are you sure want to delete this maintenance?",
|
||||
"deleteNotificationMsg": "Are you sure want to delete this notification for all monitors?",
|
||||
"dnsPortDescription": "DNS server port. Defaults to 53. Alternative ports are 443 for DoH and 853 for DoT.",
|
||||
"resolverserverDescription": "Cloudflare is the default server. You can change the resolver server anytime.",
|
||||
"resolverserverDescription": "Cloudflare is the default server. Use IP address for UDP/TCP/DoT, and domain name for DoH/DoT.",
|
||||
"rrtypeDescription": "Select the RR type you want to monitor",
|
||||
"dnsTransportDescription": "Select the transport method for querying the DNS server.",
|
||||
"dohQueryPathDescription": "Set the query path to use for DNS wireformat. Must contain",
|
||||
|
|
|
@ -1225,10 +1225,10 @@ export default {
|
|||
case "UDP":
|
||||
case "TCP":
|
||||
return this.ipRegexPattern.source;
|
||||
|
||||
case "DoH":
|
||||
case "DoT":
|
||||
return this.hostnameRegexPattern.source;
|
||||
case "DoT":
|
||||
return this.ipOrHostnameRegexPattern.source;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
Loading…
Add table
Reference in a new issue