mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-07-19 07:44:02 +02:00
DNSSEC support and fixed multiple conditions for DNS monitor
This commit is contained in:
parent
9c344ad371
commit
3ed4a2a2cb
10 changed files with 241 additions and 55 deletions
|
@ -88,6 +88,7 @@ async function createTables() {
|
||||||
table.string("dns_resolve_server", 255);
|
table.string("dns_resolve_server", 255);
|
||||||
table.string("dns_transport", 3);
|
table.string("dns_transport", 3);
|
||||||
table.string("doh_query_path", 255);
|
table.string("doh_query_path", 255);
|
||||||
|
table.boolean("skip_remote_dnssec").defaultTo(false);
|
||||||
table.string("dns_last_result", 255);
|
table.string("dns_last_result", 255);
|
||||||
table.integer("retry_interval").notNullable().defaultTo(0);
|
table.integer("retry_interval").notNullable().defaultTo(0);
|
||||||
table.string("push_token", 20).defaultTo(null);
|
table.string("push_token", 20).defaultTo(null);
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return knex.schema
|
return knex.schema
|
||||||
.alterTable("monitor", function (table) {
|
.alterTable("monitor", function (table) {
|
||||||
table.string("dns_transport");
|
table.string("dns_transport").notNullable().defaultTo("UDP");
|
||||||
table.string("doh_query_path");
|
table.string("doh_query_path");
|
||||||
|
table.boolean("skip_remote_dnssec").defaultTo(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,5 +11,6 @@ exports.down = function (knex) {
|
||||||
return knex.schema.alterTable("monitor", function (table) {
|
return knex.schema.alterTable("monitor", function (table) {
|
||||||
table.dropColumn("dns_transport");
|
table.dropColumn("dns_transport");
|
||||||
table.dropColumn("doh_query_path");
|
table.dropColumn("doh_query_path");
|
||||||
|
table.dropColumn("skip_remote_dnssec");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
43
package-lock.json
generated
43
package-lock.json
generated
|
@ -27,6 +27,7 @@
|
||||||
"croner": "~8.1.0",
|
"croner": "~8.1.0",
|
||||||
"dayjs": "~1.11.5",
|
"dayjs": "~1.11.5",
|
||||||
"dev-null": "^0.1.1",
|
"dev-null": "^0.1.1",
|
||||||
|
"dns-packet": "~5.6.1",
|
||||||
"dotenv": "~16.0.3",
|
"dotenv": "~16.0.3",
|
||||||
"express": "~4.21.0",
|
"express": "~4.21.0",
|
||||||
"express-basic-auth": "~1.2.1",
|
"express-basic-auth": "~1.2.1",
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
"http-proxy-agent": "~7.0.2",
|
"http-proxy-agent": "~7.0.2",
|
||||||
"https-proxy-agent": "~7.0.6",
|
"https-proxy-agent": "~7.0.6",
|
||||||
"iconv-lite": "~0.6.3",
|
"iconv-lite": "~0.6.3",
|
||||||
|
"ip-address": "~10.0.1",
|
||||||
"isomorphic-ws": "^5.0.0",
|
"isomorphic-ws": "^5.0.0",
|
||||||
"jsesc": "~3.0.2",
|
"jsesc": "~3.0.2",
|
||||||
"jsonata": "^2.0.3",
|
"jsonata": "^2.0.3",
|
||||||
|
@ -2745,6 +2747,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@leichtgewicht/ip-codec": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@louislam/ping": {
|
"node_modules/@louislam/ping": {
|
||||||
"version": "0.4.4-mod.1",
|
"version": "0.4.4-mod.1",
|
||||||
"resolved": "https://registry.npmjs.org/@louislam/ping/-/ping-0.4.4-mod.1.tgz",
|
"resolved": "https://registry.npmjs.org/@louislam/ping/-/ping-0.4.4-mod.1.tgz",
|
||||||
|
@ -7671,6 +7679,18 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dns-packet": {
|
||||||
|
"version": "5.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
|
||||||
|
"integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@leichtgewicht/ip-codec": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dns2": {
|
"node_modules/dns2": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/dns2/-/dns2-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/dns2/-/dns2-2.0.5.tgz",
|
||||||
|
@ -10318,14 +10338,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ip-address": {
|
"node_modules/ip-address": {
|
||||||
"version": "9.0.5",
|
"version": "10.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
|
||||||
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
|
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"jsbn": "1.1.0",
|
|
||||||
"sprintf-js": "^1.1.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
|
@ -15261,6 +15277,19 @@
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/socks/node_modules/ip-address": {
|
||||||
|
"version": "9.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
||||||
|
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jsbn": "1.1.0",
|
||||||
|
"sprintf-js": "^1.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sortablejs": {
|
"node_modules/sortablejs": {
|
||||||
"version": "1.14.0",
|
"version": "1.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
|
||||||
|
|
|
@ -120,6 +120,7 @@ class Monitor extends BeanModel {
|
||||||
dns_resolve_server: this.dns_resolve_server,
|
dns_resolve_server: this.dns_resolve_server,
|
||||||
dns_transport: this.dns_transport,
|
dns_transport: this.dns_transport,
|
||||||
doh_query_path: this.doh_query_path,
|
doh_query_path: this.doh_query_path,
|
||||||
|
skip_remote_dnssec: this.skip_remote_dnssec,
|
||||||
dns_last_result: this.dns_last_result,
|
dns_last_result: this.dns_last_result,
|
||||||
docker_container: this.docker_container,
|
docker_container: this.docker_container,
|
||||||
docker_host: this.docker_host,
|
docker_host: this.docker_host,
|
||||||
|
|
|
@ -296,6 +296,11 @@ const defaultNumberOperators = [
|
||||||
operatorMap.get(OP_GTE)
|
operatorMap.get(OP_GTE)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const defaultArrayOperators = [
|
||||||
|
operatorMap.get(OP_CONTAINS),
|
||||||
|
operatorMap.get(OP_NOT_CONTAINS)
|
||||||
|
];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
OP_STR_EQUALS,
|
OP_STR_EQUALS,
|
||||||
OP_STR_NOT_EQUALS,
|
OP_STR_NOT_EQUALS,
|
||||||
|
@ -314,5 +319,6 @@ module.exports = {
|
||||||
operatorMap,
|
operatorMap,
|
||||||
defaultStringOperators,
|
defaultStringOperators,
|
||||||
defaultNumberOperators,
|
defaultNumberOperators,
|
||||||
|
defaultArrayOperators,
|
||||||
ConditionOperator,
|
ConditionOperator,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ const dayjs = require("dayjs");
|
||||||
const { dnsResolve } = require("../util-server");
|
const { dnsResolve } = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { ConditionVariable } = require("../monitor-conditions/variables");
|
const { ConditionVariable } = require("../monitor-conditions/variables");
|
||||||
const { defaultStringOperators } = require("../monitor-conditions/operators");
|
const { defaultStringOperators, defaultNumberOperators, defaultArrayOperators } = require("../monitor-conditions/operators");
|
||||||
const { ConditionExpressionGroup } = require("../monitor-conditions/expression");
|
const { ConditionExpressionGroup } = require("../monitor-conditions/expression");
|
||||||
const { evaluateExpressionGroup } = require("../monitor-conditions/evaluator");
|
const { evaluateExpressionGroup } = require("../monitor-conditions/evaluator");
|
||||||
|
|
||||||
|
@ -14,7 +14,25 @@ class DnsMonitorType extends MonitorType {
|
||||||
supportsConditions = true;
|
supportsConditions = true;
|
||||||
|
|
||||||
conditionVariables = [
|
conditionVariables = [
|
||||||
new ConditionVariable("record", defaultStringOperators ),
|
// A, AAAA, TXT, PTR, NS
|
||||||
|
new ConditionVariable("records", defaultArrayOperators),
|
||||||
|
// CNAME
|
||||||
|
new ConditionVariable("hostname", defaultStringOperators),
|
||||||
|
// CAA
|
||||||
|
new ConditionVariable("flags", defaultStringOperators),
|
||||||
|
new ConditionVariable("tag", defaultStringOperators),
|
||||||
|
new ConditionVariable("value", defaultStringOperators),
|
||||||
|
// MX
|
||||||
|
new ConditionVariable("hostnames", defaultArrayOperators),
|
||||||
|
// SOA
|
||||||
|
new ConditionVariable("mname", defaultStringOperators),
|
||||||
|
new ConditionVariable("rname", defaultStringOperators),
|
||||||
|
new ConditionVariable("serial", defaultStringOperators),
|
||||||
|
new ConditionVariable("refresh", defaultNumberOperators),
|
||||||
|
new ConditionVariable("retry", defaultNumberOperators),
|
||||||
|
new ConditionVariable("minimum", defaultNumberOperators),
|
||||||
|
// SRV
|
||||||
|
new ConditionVariable("targets", defaultArrayOperators),
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +42,13 @@ class DnsMonitorType extends MonitorType {
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
let dnsMessage = "";
|
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 requestData = {
|
||||||
|
name: monitor.hostname,
|
||||||
|
rrtype: monitor.dns_resolve_type,
|
||||||
|
dnssec: true, // Request DNSSEC information in the response
|
||||||
|
dnssecCheckingDisabled: monitor.skip_remote_dnssec,
|
||||||
|
};
|
||||||
|
let dnsRes = await dnsResolve(requestData, monitor.dns_resolve_server, monitor.port, monitor.dns_transport, monitor.doh_query_path);
|
||||||
const records = dnsRes.answers.map(record => {
|
const records = dnsRes.answers.map(record => {
|
||||||
return Buffer.isBuffer(record.data) ? record.data.toString() : record.data;
|
return Buffer.isBuffer(record.data) ? record.data.toString() : record.data;
|
||||||
});
|
});
|
||||||
|
@ -41,22 +65,22 @@ class DnsMonitorType extends MonitorType {
|
||||||
case "PTR":
|
case "PTR":
|
||||||
case "NS":
|
case "NS":
|
||||||
dnsMessage = records.join(" | ");
|
dnsMessage = records.join(" | ");
|
||||||
conditionsResult = records.some(record => handleConditions({ record }));
|
conditionsResult = handleConditions({ records: records });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "CNAME":
|
case "CNAME":
|
||||||
dnsMessage = records[0];
|
dnsMessage = records[0];
|
||||||
conditionsResult = handleConditions({ record: records[0] });
|
conditionsResult = handleConditions({ hostname: records[0].value });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "CAA":
|
case "CAA":
|
||||||
dnsMessage = records.map(record => `${record.flags} ${record.tag} "${record.value}"`).join(" | ");
|
dnsMessage = records.map(record => `${record.flags} ${record.tag} "${record.value}"`).join(" | ");
|
||||||
conditionsResult = handleConditions({ record: records[0] });
|
conditionsResult = handleConditions(records[0]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "MX":
|
case "MX":
|
||||||
dnsMessage = records.map(record => `Hostname: ${record.exchange}; Priority: ${record.priority}`).join(" | ");
|
dnsMessage = records.map(record => `Hostname: ${record.exchange}; Priority: ${record.priority}`).join(" | ");
|
||||||
conditionsResult = records.some(record => handleConditions({ record }));
|
conditionsResult = handleConditions({ hostnames: records.map(record => record.exchange) });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "SOA": {
|
case "SOA": {
|
||||||
|
@ -71,7 +95,7 @@ class DnsMonitorType extends MonitorType {
|
||||||
}).map(([ name, value ]) => {
|
}).map(([ name, value ]) => {
|
||||||
return `${name}: ${value}`;
|
return `${name}: ${value}`;
|
||||||
}).join("; ");
|
}).join("; ");
|
||||||
conditionsResult = handleConditions({ record: records[0] });
|
conditionsResult = handleConditions(records[0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +110,7 @@ class DnsMonitorType extends MonitorType {
|
||||||
return `${name}: ${value}`;
|
return `${name}: ${value}`;
|
||||||
}).join("; ");
|
}).join("; ");
|
||||||
}).join(" | ");
|
}).join(" | ");
|
||||||
conditionsResult = records.some(record => handleConditions({ record }));
|
conditionsResult = handleConditions({ targets: records.map(record => record.target) });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -828,6 +828,7 @@ let needSetup = false;
|
||||||
bean.dns_resolve_server = monitor.dns_resolve_server;
|
bean.dns_resolve_server = monitor.dns_resolve_server;
|
||||||
bean.dns_transport = monitor.dns_transport;
|
bean.dns_transport = monitor.dns_transport;
|
||||||
bean.doh_query_path = monitor.doh_query_path;
|
bean.doh_query_path = monitor.doh_query_path;
|
||||||
|
bean.skip_remote_dnssec = monitor.skip_remote_dnssec;
|
||||||
bean.pushToken = monitor.pushToken;
|
bean.pushToken = monitor.pushToken;
|
||||||
bean.docker_container = monitor.docker_container;
|
bean.docker_container = monitor.docker_container;
|
||||||
bean.docker_host = monitor.docker_host;
|
bean.docker_host = monitor.docker_host;
|
||||||
|
|
|
@ -4,6 +4,7 @@ const { R } = require("redbean-node");
|
||||||
const { log, genSecret, badgeConstants } = require("../src/util");
|
const { log, genSecret, badgeConstants } = require("../src/util");
|
||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
const dnsPacket = require("dns-packet");
|
const dnsPacket = require("dns-packet");
|
||||||
|
const optioncodes = require("dns-packet/optioncodes.js");
|
||||||
const dgram = require("dgram");
|
const dgram = require("dgram");
|
||||||
const { Socket, isIP, isIPv4, isIPv6 } = require("net");
|
const { Socket, isIP, isIPv4, isIPv6 } = require("net");
|
||||||
const { Address6 } = require("ip-address");
|
const { Address6 } = require("ip-address");
|
||||||
|
@ -285,17 +286,99 @@ exports.httpNtlm = function (options, ntlmOptions) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from https://github.com/hildjj/dohdec/blob/v7.0.2/pkg/dohdec/lib/dnsUtils.js
|
||||||
|
* Encode a DNS query packet to a buffer.
|
||||||
|
* @param {object} opts Options for the query.
|
||||||
|
* @param {string} opts.name The name to look up.
|
||||||
|
* @param {number} [opts.id=0] ID for the query. SHOULD be 0 for DOH.
|
||||||
|
* @param {packet.RecordType} [opts.rrtype="A"] The record type to look up.
|
||||||
|
* @param {boolean} [opts.dnssec=false] Request DNSSec information?
|
||||||
|
* @param {boolean} [opts.dnssecCheckingDisabled=false] Disable DNSSec
|
||||||
|
* validation?
|
||||||
|
* @param {string} [opts.ecsSubnet] Subnet to use for ECS.
|
||||||
|
* @param {number} [opts.ecs] Number of ECS bits. Defaults to 24 or 56
|
||||||
|
* (IPv4/IPv6).
|
||||||
|
* @param {boolean} [opts.stream=false] Encode for streaming, with the packet
|
||||||
|
* prefixed by a 2-byte big-endian integer of the number of bytes in the
|
||||||
|
* packet.
|
||||||
|
* @returns {Buffer} The encoded packet.
|
||||||
|
* @throws {TypeError} opts does not contain a name attribute.
|
||||||
|
*/
|
||||||
|
exports.makePacket = function (opts) {
|
||||||
|
const PAD_SIZE = 128;
|
||||||
|
|
||||||
|
if (!opts?.name) {
|
||||||
|
throw new TypeError("Name is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {dnsPacket.OptAnswer} */
|
||||||
|
const opt = {
|
||||||
|
name: ".",
|
||||||
|
type: "OPT",
|
||||||
|
udpPayloadSize: 4096,
|
||||||
|
extendedRcode: 0,
|
||||||
|
flags: 0,
|
||||||
|
flag_do: false, // Setting here has no effect
|
||||||
|
ednsVersion: 0,
|
||||||
|
options: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {dnsPacket.Packet} */
|
||||||
|
const dns = {
|
||||||
|
type: "query",
|
||||||
|
id: opts.id || 0,
|
||||||
|
flags: dnsPacket.RECURSION_DESIRED,
|
||||||
|
questions: [{
|
||||||
|
type: opts.rrtype || "A",
|
||||||
|
class: "IN",
|
||||||
|
name: opts.name,
|
||||||
|
}],
|
||||||
|
additionals: [ opt ],
|
||||||
|
};
|
||||||
|
//assert(dns.flags !== undefined);
|
||||||
|
if (opts.dnssec) {
|
||||||
|
dns.flags |= dnsPacket.AUTHENTIC_DATA;
|
||||||
|
opt.flags |= dnsPacket.DNSSEC_OK;
|
||||||
|
}
|
||||||
|
if (opts.dnssecCheckingDisabled) {
|
||||||
|
dns.flags |= dnsPacket.CHECKING_DISABLED;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(opts.ecs != null) ||
|
||||||
|
(opts.ecsSubnet && (isIP(opts.ecsSubnet) !== 0))
|
||||||
|
) {
|
||||||
|
// https://tools.ietf.org/html/rfc7871#section-11.1
|
||||||
|
const prefix = (opts.ecsSubnet && isIPv4(opts.ecsSubnet)) ? 24 : 56;
|
||||||
|
opt.options.push({
|
||||||
|
code: optioncodes.toCode("CLIENT_SUBNET"),
|
||||||
|
ip: opts.ecsSubnet || "0.0.0.0",
|
||||||
|
sourcePrefixLength: (opts.ecs == null) ? prefix : opts.ecs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const unpadded = dnsPacket.encodingLength(dns);
|
||||||
|
opt.options.push({
|
||||||
|
code: optioncodes.toCode("PADDING"),
|
||||||
|
// Next pad size, minus what we already have, minus another 4 bytes for
|
||||||
|
// the option header
|
||||||
|
length: (Math.ceil(unpadded / PAD_SIZE) * PAD_SIZE) - unpadded - 4,
|
||||||
|
});
|
||||||
|
if (opts.stream) {
|
||||||
|
return dnsPacket.streamEncode(dns);
|
||||||
|
}
|
||||||
|
return dnsPacket.encode(dns);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a given record using the specified DNS server
|
* Resolves a given record using the specified DNS server
|
||||||
* @param {string} hostname The hostname of the record to lookup
|
* @param {string} opts Options for the query
|
||||||
* @param {string} resolverServer The DNS server to use
|
* @param {string} resolverServer The DNS server to use
|
||||||
* @param {string} resolverPort Port the DNS server is listening on
|
* @param {string} resolverPort Port the DNS server is listening on
|
||||||
* @param {string} rrtype The type of record to request
|
|
||||||
* @param {string} transport The transport method to use
|
* @param {string} transport The transport method to use
|
||||||
* @param {string} dohQuery The query path used only for DoH
|
* @param {string} dohQuery The query path used only for DoH
|
||||||
* @returns {Promise<(string[] | object[] | object)>} DNS response
|
* @returns {Promise<(string[] | object[] | object)>} DNS response
|
||||||
*/
|
*/
|
||||||
exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, transport, dohQuery) {
|
exports.dnsResolve = function (opts, resolverServer, resolverPort, transport, dohQuery) {
|
||||||
// Parse IPv4 and IPv6 addresses to determine address family and
|
// Parse IPv4 and IPv6 addresses to determine address family and
|
||||||
// add square brackets to IPv6 addresses, following RFC 3986 syntax
|
// add square brackets to IPv6 addresses, following RFC 3986 syntax
|
||||||
resolverServer = resolverServer.replace("[", "").replace("]", "");
|
resolverServer = resolverServer.replace("[", "").replace("]", "");
|
||||||
|
@ -306,27 +389,23 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, t
|
||||||
|
|
||||||
// If performing reverse (PTR) record lookup, ensure hostname
|
// If performing reverse (PTR) record lookup, ensure hostname
|
||||||
// syntax follows RFC 1034 / RFC 3596
|
// syntax follows RFC 1034 / RFC 3596
|
||||||
if (rrtype === "PTR") {
|
if (opts.rrtype === "PTR") {
|
||||||
if (isIPv4(hostname)) {
|
if (isIPv4(opts.name)) {
|
||||||
let octets = hostname.split(".");
|
let octets = opts.name.split(".");
|
||||||
octets.reverse();
|
octets.reverse();
|
||||||
hostname = octets.join(".") + ".in-addr.arpa";
|
opts.name = octets.join(".") + ".in-addr.arpa";
|
||||||
} else if (isIPv6(hostname)) {
|
} else if (isIPv6(opts.name)) {
|
||||||
let address = new Address6(hostname);
|
let address = new Address6(opts.name);
|
||||||
hostname = address.reverseForm();
|
opts.name = address.reverseForm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the DNS request data that will get encoded later
|
// Set request ID
|
||||||
const requestData = {
|
if (opts.id == null) {
|
||||||
type: "query",
|
// Set query ID to "0" for HTTP cache friendlyness on DoH requests.
|
||||||
id: Math.floor(Math.random() * 65534) + 1,
|
// See https://github.com/mafintosh/dns-packet/issues/77
|
||||||
flags: dnsPacket.RECURSION_DESIRED,
|
opts.id = (transport.toUpperCase() === "DOH") ? 0 : Math.floor(Math.random() * 65534) + 1;
|
||||||
questions: [{
|
}
|
||||||
type: rrtype,
|
|
||||||
name: hostname,
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let client;
|
let client;
|
||||||
let resolver = null;
|
let resolver = null;
|
||||||
|
@ -336,7 +415,8 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, t
|
||||||
|
|
||||||
case "TCP":
|
case "TCP":
|
||||||
case "DOT": {
|
case "DOT": {
|
||||||
const buf = dnsPacket.streamEncode(requestData);
|
opts.stream = true;
|
||||||
|
const buf = exports.makePacket(opts);
|
||||||
if (isSecure) {
|
if (isSecure) {
|
||||||
const options = {
|
const options = {
|
||||||
port: resolverPort,
|
port: resolverPort,
|
||||||
|
@ -387,10 +467,7 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, t
|
||||||
}
|
}
|
||||||
|
|
||||||
case "DOH": {
|
case "DOH": {
|
||||||
// Set query ID to "0" for HTTP cache friendlyness. See
|
const buf = exports.makePacket(opts);
|
||||||
// https://github.com/mafintosh/dns-packet/issues/77
|
|
||||||
requestData.id = 0;
|
|
||||||
const buf = dnsPacket.encode(requestData);
|
|
||||||
// TODO: implement POST requests for wireformat and JSON
|
// TODO: implement POST requests for wireformat and JSON
|
||||||
dohQuery = dohQuery || "dns-query?dns={query}";
|
dohQuery = dohQuery || "dns-query?dns={query}";
|
||||||
dohQuery = dohQuery.replace("{query}", buf.toString("base64url"));
|
dohQuery = dohQuery.replace("{query}", buf.toString("base64url"));
|
||||||
|
@ -436,7 +513,7 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, t
|
||||||
|
|
||||||
//case "UDP":
|
//case "UDP":
|
||||||
default: {
|
default: {
|
||||||
const buf = dnsPacket.encode(requestData);
|
const buf = exports.makePacket(opts);
|
||||||
client = dgram.createSocket("udp" + String(addressFamily));
|
client = dgram.createSocket("udp" + String(addressFamily));
|
||||||
client.on("connect", () => {
|
client.on("connect", () => {
|
||||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
||||||
|
|
|
@ -143,7 +143,10 @@
|
||||||
"Test": "Test",
|
"Test": "Test",
|
||||||
"Certificate Info": "Certificate Info",
|
"Certificate Info": "Certificate Info",
|
||||||
"Resolver Server": "Resolver Server",
|
"Resolver Server": "Resolver Server",
|
||||||
|
"Transport Method": "Transport Method",
|
||||||
|
"Query Path": "Query Path",
|
||||||
"Resource Record Type": "Resource Record Type",
|
"Resource Record Type": "Resource Record Type",
|
||||||
|
"Skip Remote DNSSEC Verification": "Skip Remote DNSSEC Verification",
|
||||||
"Last Result": "Last Result",
|
"Last Result": "Last Result",
|
||||||
"Create your admin account": "Create your admin account",
|
"Create your admin account": "Create your admin account",
|
||||||
"Repeat Password": "Repeat Password",
|
"Repeat Password": "Repeat Password",
|
||||||
|
@ -269,6 +272,7 @@
|
||||||
"Shrink Database": "Shrink Database",
|
"Shrink Database": "Shrink Database",
|
||||||
"shrinkDatabaseDescriptionSqlite": "Trigger database {vacuum} for SQLite. {auto_vacuum} is already enabled but this does not defragment the database nor repack individual database pages the way that the {vacuum} command does.",
|
"shrinkDatabaseDescriptionSqlite": "Trigger database {vacuum} for SQLite. {auto_vacuum} is already enabled but this does not defragment the database nor repack individual database pages the way that the {vacuum} command does.",
|
||||||
"Pick a RR-Type...": "Pick a RR-Type…",
|
"Pick a RR-Type...": "Pick a RR-Type…",
|
||||||
|
"Select the transport method...": "Select the transport method…",
|
||||||
"Pick Accepted Status Codes...": "Pick Accepted Status Codes…",
|
"Pick Accepted Status Codes...": "Pick Accepted Status Codes…",
|
||||||
"Default": "Default",
|
"Default": "Default",
|
||||||
"HTTP Options": "HTTP Options",
|
"HTTP Options": "HTTP Options",
|
||||||
|
@ -576,6 +580,7 @@
|
||||||
"rrtypeDescription": "Select the RR type you want to monitor",
|
"rrtypeDescription": "Select the RR type you want to monitor",
|
||||||
"dnsTransportDescription": "Select the transport method for querying the DNS server.",
|
"dnsTransportDescription": "Select the transport method for querying the DNS server.",
|
||||||
"dohQueryPathDescription": "Set the query path to use for DNS wireformat. Must contain",
|
"dohQueryPathDescription": "Set the query path to use for DNS wireformat. Must contain",
|
||||||
|
"skipRemoteDnssecDescription": "Requests the resolver server not to perform DNSSEC verification against queried records.",
|
||||||
"pauseMonitorMsg": "Are you sure want to pause?",
|
"pauseMonitorMsg": "Are you sure want to pause?",
|
||||||
"enableDefaultNotificationDescription": "This notification will be enabled by default for new monitors. You can still disable the notification separately for each monitor.",
|
"enableDefaultNotificationDescription": "This notification will be enabled by default for new monitors. You can still disable the notification separately for each monitor.",
|
||||||
"clearEventsMsg": "Are you sure want to delete all events for this monitor?",
|
"clearEventsMsg": "Are you sure want to delete all events for this monitor?",
|
||||||
|
|
|
@ -377,17 +377,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="dohSelected" class="my-3">
|
|
||||||
<label for="doh_query_path" class="form-label">{{ $t("Query Path") }}</label>
|
|
||||||
<div class="d-flex">
|
|
||||||
<label for="doh_query_path" class="px-2 fs-5">/</label>
|
|
||||||
<input id="doh_query_path" v-model="monitor.doh_query_path" type="text" class="form-control" :pattern="urlQueryRegex" placeholder="dns-query?dns={query}">
|
|
||||||
</div>
|
|
||||||
<div class="form-text">
|
|
||||||
{{ $t("dohQueryPathDescription") + " " }}{{ '"{query}".' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="dns_transport" class="form-label">{{ $t("Transport Method") }}</label>
|
<label for="dns_transport" class="form-label">{{ $t("Transport Method") }}</label>
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
|
@ -398,7 +387,7 @@
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
:clear-on-select="false"
|
:clear-on-select="false"
|
||||||
:preserve-search="false"
|
:preserve-search="false"
|
||||||
:placeholder="$t('Select the transport method')"
|
:placeholder="$t('Select the transport method...')"
|
||||||
:preselect-first="false"
|
:preselect-first="false"
|
||||||
:max-height="500"
|
:max-height="500"
|
||||||
:taggable="false"
|
:taggable="false"
|
||||||
|
@ -676,6 +665,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Advanced DNS monitor settings -->
|
||||||
|
<div v-if="monitor.type === 'dns'" class="my-3">
|
||||||
|
<div v-if="dohSelected">
|
||||||
|
<label for="doh_query_path" class="form-label">{{ $t("Query Path") }}</label>
|
||||||
|
<div class="d-flex">
|
||||||
|
<label for="doh_query_path" class="px-2 fs-5">/</label>
|
||||||
|
<input id="doh_query_path" v-model="monitor.doh_query_path" type="text" class="form-control" :pattern="urlQueryRegex" placeholder="dns-query?dns={query}">
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("dohQueryPathDescription") + ' "{query}".' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input id="skip_remote_dnssec" v-model="monitor.skip_remote_dnssec" class="form-check-input" type="checkbox" value="">
|
||||||
|
<label class="form-check-label" for="skip_remote_dnssec">
|
||||||
|
{{ $t("Skip Remote DNSSEC Verification") }}
|
||||||
|
</label>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("skipRemoteDnssecDescription") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="my-3 form-check">
|
<div class="my-3 form-check">
|
||||||
<input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox">
|
<input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox">
|
||||||
<label class="form-check-label" for="upside-down">
|
<label class="form-check-label" for="upside-down">
|
||||||
|
@ -1125,6 +1138,7 @@ const monitorDefaults = {
|
||||||
dns_resolve_type: "A",
|
dns_resolve_type: "A",
|
||||||
dns_resolve_server: "1.1.1.1",
|
dns_resolve_server: "1.1.1.1",
|
||||||
dns_transport: "UDP",
|
dns_transport: "UDP",
|
||||||
|
skip_remote_dnssec: false,
|
||||||
docker_container: "",
|
docker_container: "",
|
||||||
docker_host: null,
|
docker_host: null,
|
||||||
proxyId: null,
|
proxyId: null,
|
||||||
|
@ -1464,6 +1478,32 @@ message HealthCheckResponse {
|
||||||
},
|
},
|
||||||
|
|
||||||
conditionVariables() {
|
conditionVariables() {
|
||||||
|
if (this.monitor.type === "dns") {
|
||||||
|
// When the monitor type is DNS, the conditions depend on
|
||||||
|
// record type. Condition variables are all added to a single
|
||||||
|
// array defined in server\monitor-types\dns.js in order to
|
||||||
|
// pass to Vue, then sliced below based on index.
|
||||||
|
const dnsConditionVariables = this.$root.monitorTypeList["dns"]?.conditionVariables;
|
||||||
|
switch (this.monitor.dns_resolve_type) {
|
||||||
|
case "A":
|
||||||
|
case "AAAA":
|
||||||
|
case "TXT":
|
||||||
|
case "PTR":
|
||||||
|
case "NS":
|
||||||
|
return dnsConditionVariables.slice(0, 1);
|
||||||
|
case "CNAME":
|
||||||
|
return dnsConditionVariables.slice(1, 2);
|
||||||
|
case "CAA":
|
||||||
|
return dnsConditionVariables.slice(2, 5);
|
||||||
|
case "MX":
|
||||||
|
return dnsConditionVariables.slice(5, 6);
|
||||||
|
case "SOA":
|
||||||
|
return dnsConditionVariables.slice(6, 12);
|
||||||
|
case "SRV":
|
||||||
|
return dnsConditionVariables.slice(12, 13);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return this.$root.monitorTypeList[this.monitor.type]?.conditionVariables || [];
|
return this.$root.monitorTypeList[this.monitor.type]?.conditionVariables || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue