mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-07-19 07:44:02 +02:00
HTTP/2 support, error handling, rename variables, and various bugfixes
This commit is contained in:
parent
3ed4a2a2cb
commit
47cf7f9d13
11 changed files with 1436 additions and 1181 deletions
|
@ -87,8 +87,9 @@ async function createTables() {
|
||||||
table.string("dns_resolve_type", 5);
|
table.string("dns_resolve_type", 5);
|
||||||
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.boolean("doh_query_path", 255).defaultTo("dns-query");
|
||||||
table.boolean("skip_remote_dnssec").defaultTo(false);
|
table.boolean("force_http2").notNullable().defaultTo(false);
|
||||||
|
table.boolean("skip_remote_dnssec").notNullable().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,9 +1,10 @@
|
||||||
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").notNullable().defaultTo("UDP");
|
table.string("dns_transport", 3).notNullable().defaultTo("UDP");
|
||||||
table.string("doh_query_path");
|
table.string("doh_query_path", 255).defaultTo("dns-query");
|
||||||
table.boolean("skip_remote_dnssec").defaultTo(false);
|
table.boolean("force_http2").notNullable().defaultTo(false);
|
||||||
|
table.boolean("skip_remote_dnssec").notNullable().defaultTo(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ 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("force_http2");
|
||||||
table.dropColumn("skip_remote_dnssec");
|
table.dropColumn("skip_remote_dnssec");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -116,12 +116,13 @@ class Monitor extends BeanModel {
|
||||||
packetSize: this.packetSize,
|
packetSize: this.packetSize,
|
||||||
maxredirects: this.maxredirects,
|
maxredirects: this.maxredirects,
|
||||||
accepted_statuscodes: this.getAcceptedStatuscodes(),
|
accepted_statuscodes: this.getAcceptedStatuscodes(),
|
||||||
dns_resolve_type: this.dns_resolve_type,
|
dnsResolveType: this.dnsResolveType,
|
||||||
dns_resolve_server: this.dns_resolve_server,
|
dnsResolveServer: this.dnsResolveServer,
|
||||||
dns_transport: this.dns_transport,
|
dnsTransport: this.dnsTransport,
|
||||||
doh_query_path: this.doh_query_path,
|
dohQueryPath: this.dohQueryPath,
|
||||||
skip_remote_dnssec: this.skip_remote_dnssec,
|
forceHttp2: Boolean(this.forceHttp2),
|
||||||
dns_last_result: this.dns_last_result,
|
skipRemoteDnssec: Boolean(this.skipRemoteDnssec),
|
||||||
|
dnsLastResult: this.dnsLastResult,
|
||||||
docker_container: this.docker_container,
|
docker_container: this.docker_container,
|
||||||
docker_host: this.docker_host,
|
docker_host: this.docker_host,
|
||||||
proxyId: this.proxy_id,
|
proxyId: this.proxy_id,
|
||||||
|
|
|
@ -39,26 +39,46 @@ class DnsMonitorType extends MonitorType {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async check(monitor, heartbeat, _server) {
|
async check(monitor, heartbeat, _server) {
|
||||||
let startTime = dayjs().valueOf();
|
|
||||||
let dnsMessage = "";
|
|
||||||
|
|
||||||
const requestData = {
|
const requestData = {
|
||||||
name: monitor.hostname,
|
name: monitor.hostname,
|
||||||
rrtype: monitor.dns_resolve_type,
|
rrtype: monitor.dnsResolveType,
|
||||||
dnssec: true, // Request DNSSEC information in the response
|
dnssec: true, // Request DNSSEC information in the response
|
||||||
dnssecCheckingDisabled: monitor.skip_remote_dnssec,
|
dnssecCheckingDisabled: monitor.skip_remote_dnssec,
|
||||||
};
|
};
|
||||||
let dnsRes = await dnsResolve(requestData, monitor.dns_resolve_server, monitor.port, monitor.dns_transport, monitor.doh_query_path);
|
const transportData = {
|
||||||
const records = dnsRes.answers.map(record => {
|
type: monitor.dnsTransport,
|
||||||
return Buffer.isBuffer(record.data) ? record.data.toString() : record.data;
|
ignoreCertErrors: monitor.ignoreTls,
|
||||||
});
|
dohQueryPath: monitor.dohQueryPath,
|
||||||
|
dohUsePost: monitor.method === "POST",
|
||||||
|
dohUseHttp2: monitor.forceHttp2,
|
||||||
|
};
|
||||||
|
|
||||||
|
let startTime = dayjs().valueOf();
|
||||||
|
let dnsRes = await dnsResolve(requestData, monitor.dnsResolveServer, monitor.port, transportData);
|
||||||
heartbeat.ping = dayjs().valueOf() - startTime;
|
heartbeat.ping = dayjs().valueOf() - startTime;
|
||||||
|
|
||||||
|
let dnsMessage = "";
|
||||||
|
let rrtype = monitor.dnsResolveType;
|
||||||
const conditions = ConditionExpressionGroup.fromMonitor(monitor);
|
const conditions = ConditionExpressionGroup.fromMonitor(monitor);
|
||||||
let conditionsResult = true;
|
let conditionsResult = true;
|
||||||
const handleConditions = (data) => conditions ? evaluateExpressionGroup(conditions, data) : true;
|
const handleConditions = (data) => conditions ? evaluateExpressionGroup(conditions, data) : true;
|
||||||
|
|
||||||
switch (monitor.dns_resolve_type) {
|
const records = dnsRes.answers.reduce((results, record) => {
|
||||||
|
// Omit records that are not the same as the requested rrtype
|
||||||
|
if (record.type === monitor.dnsResolveType) {
|
||||||
|
results.push(Buffer.isBuffer(record.data) ? record.data.toString() : record.data);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}, []);
|
||||||
|
// Return down status if no records are provided
|
||||||
|
if (records.length === 0) {
|
||||||
|
rrtype = null;
|
||||||
|
dnsMessage = "No records found";
|
||||||
|
conditionsResult = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (rrtype) {
|
||||||
case "A":
|
case "A":
|
||||||
case "AAAA":
|
case "AAAA":
|
||||||
case "TXT":
|
case "TXT":
|
||||||
|
@ -114,7 +134,7 @@ class DnsMonitorType extends MonitorType {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monitor.dns_last_result !== dnsMessage && dnsMessage !== undefined) {
|
if (monitor.dnsLastResult !== dnsMessage && dnsMessage !== undefined) {
|
||||||
await R.exec("UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ", [ dnsMessage, monitor.id ]);
|
await R.exec("UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ", [ dnsMessage, monitor.id ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -824,11 +824,12 @@ let needSetup = false;
|
||||||
bean.packetSize = monitor.packetSize;
|
bean.packetSize = monitor.packetSize;
|
||||||
bean.maxredirects = monitor.maxredirects;
|
bean.maxredirects = monitor.maxredirects;
|
||||||
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
|
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
|
||||||
bean.dns_resolve_type = monitor.dns_resolve_type;
|
bean.dnsResolveType = monitor.dnsResolveType;
|
||||||
bean.dns_resolve_server = monitor.dns_resolve_server;
|
bean.dnsResolveServer = monitor.dnsResolveServer;
|
||||||
bean.dns_transport = monitor.dns_transport;
|
bean.dnsTransport = monitor.dnsTransport;
|
||||||
bean.doh_query_path = monitor.doh_query_path;
|
bean.dohQueryPath = monitor.dohQueryPath;
|
||||||
bean.skip_remote_dnssec = monitor.skip_remote_dnssec;
|
bean.forceHttp2 = monitor.forceHttp2;
|
||||||
|
bean.skipRemoteDnssec = monitor.skipRemoteDnssec;
|
||||||
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;
|
||||||
|
|
|
@ -24,8 +24,19 @@ const redis = require("redis");
|
||||||
const oidc = require("openid-client");
|
const oidc = require("openid-client");
|
||||||
const tls = require("tls");
|
const tls = require("tls");
|
||||||
const https = require("https");
|
const https = require("https");
|
||||||
|
const http2 = require("http2");
|
||||||
const url = require("url");
|
const url = require("url");
|
||||||
|
|
||||||
|
const {
|
||||||
|
HTTP2_HEADER_PATH,
|
||||||
|
HTTP2_HEADER_METHOD,
|
||||||
|
HTTP2_HEADER_AUTHORITY,
|
||||||
|
HTTP2_HEADER_ACCEPT,
|
||||||
|
HTTP2_HEADER_CONTENT_LENGTH,
|
||||||
|
HTTP2_HEADER_CONTENT_TYPE,
|
||||||
|
HTTP2_HEADER_STATUS,
|
||||||
|
} = http2.constants;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dictionaries: {
|
dictionaries: {
|
||||||
rfc2865: { file, attributes },
|
rfc2865: { file, attributes },
|
||||||
|
@ -287,25 +298,24 @@ exports.httpNtlm = function (options, ntlmOptions) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapted from https://github.com/hildjj/dohdec/blob/v7.0.2/pkg/dohdec/lib/dnsUtils.js
|
* Encode DNS query packet data to a buffer. Adapted from
|
||||||
* Encode a DNS query packet to a buffer.
|
* https://github.com/hildjj/dohdec/blob/v7.0.2/pkg/dohdec/lib/dnsUtils.js
|
||||||
* @param {object} opts Options for the query.
|
* @param {object} opts Options for the query.
|
||||||
* @param {string} opts.name The name to look up.
|
* @param {string} opts.name The name to look up.
|
||||||
* @param {number} [opts.id=0] ID for the query. SHOULD be 0 for DOH.
|
* @param {number} opts.id ID for the query. SHOULD be 0 for DOH.
|
||||||
* @param {packet.RecordType} [opts.rrtype="A"] The record type to look up.
|
* @param {packet.RecordType} opts.rrtype The record type to look up.
|
||||||
* @param {boolean} [opts.dnssec=false] Request DNSSec information?
|
* @param {boolean} opts.dnssec Request DNSSec information?
|
||||||
* @param {boolean} [opts.dnssecCheckingDisabled=false] Disable DNSSec
|
* @param {boolean} opts.dnssecCheckingDisabled Disable DNSSec validation?
|
||||||
* validation?
|
* @param {string} opts.ecsSubnet Subnet to use for ECS.
|
||||||
* @param {string} [opts.ecsSubnet] Subnet to use for ECS.
|
* @param {number} opts.ecs Number of ECS bits. Defaults to 24 (IPv4) or 56
|
||||||
* @param {number} [opts.ecs] Number of ECS bits. Defaults to 24 or 56
|
* (IPv6).
|
||||||
* (IPv4/IPv6).
|
* @param {boolean} opts.stream Encode for streaming, with the packet prefixed
|
||||||
* @param {boolean} [opts.stream=false] Encode for streaming, with the packet
|
* by a 2-byte big-endian integer of the number of bytes in the packet.
|
||||||
* prefixed by a 2-byte big-endian integer of the number of bytes in the
|
* @param {number} opts.udpPayloadSize Set a custom UDP payload size (EDNS).
|
||||||
* packet.
|
|
||||||
* @returns {Buffer} The encoded packet.
|
* @returns {Buffer} The encoded packet.
|
||||||
* @throws {TypeError} opts does not contain a name attribute.
|
* @throws {TypeError} opts does not contain a name attribute.
|
||||||
*/
|
*/
|
||||||
exports.makePacket = function (opts) {
|
exports.makeDnsPacket = function (opts) {
|
||||||
const PAD_SIZE = 128;
|
const PAD_SIZE = 128;
|
||||||
|
|
||||||
if (!opts?.name) {
|
if (!opts?.name) {
|
||||||
|
@ -316,7 +326,7 @@ exports.makePacket = function (opts) {
|
||||||
const opt = {
|
const opt = {
|
||||||
name: ".",
|
name: ".",
|
||||||
type: "OPT",
|
type: "OPT",
|
||||||
udpPayloadSize: 4096,
|
udpPayloadSize: opts.udpPayloadSize || 4096,
|
||||||
extendedRcode: 0,
|
extendedRcode: 0,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
flag_do: false, // Setting here has no effect
|
flag_do: false, // Setting here has no effect
|
||||||
|
@ -369,16 +379,65 @@ exports.makePacket = function (opts) {
|
||||||
return dnsPacket.encode(dns);
|
return dnsPacket.encode(dns);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes DNS packet response data with error handling
|
||||||
|
* @param {Buffer} data DNS packet data to decode
|
||||||
|
* @param {boolean} isStream If the data is encoded as a stream
|
||||||
|
* @param {Function} callback function to call if error is encountered
|
||||||
|
* Passes error object as a parameter to the function
|
||||||
|
* @returns {dnsPacket.Packet} DNS packet data in an object
|
||||||
|
*/
|
||||||
|
exports.decodeDnsPacket = function (data, isStream = false, callback) {
|
||||||
|
let decodedData;
|
||||||
|
try {
|
||||||
|
decodedData = isStream ? dnsPacket.streamDecode(data) : dnsPacket.decode(data);
|
||||||
|
log.debug("dns", "Response decoded");
|
||||||
|
// If the truncated bit is set, the answers section was too large
|
||||||
|
if (decodedData.flag_tc) {
|
||||||
|
callback({ message: "Response is truncated." });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
err.message = `Error decoding DNS response data: ${err.message}`;
|
||||||
|
log.warn("dns", err.message);
|
||||||
|
if (callback) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decodedData;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a given record using the specified DNS server
|
* Resolves a given record using the specified DNS server
|
||||||
* @param {string} opts Options for the query
|
* @param {object} opts Options for the query, used to generate DNS packet
|
||||||
|
* @param {string} opts.name The name of the record to query
|
||||||
|
* @param {string} opts.rrtype The resource record type
|
||||||
|
* @param {number} opts.id Set a specific ID number to use on the query
|
||||||
|
* @param {number} opts.udpPayloadSize Set a custom UDP payload size (EDNS).
|
||||||
|
* Defaults to safe values, 1432 bytes (IPv4) or 1232 bytes (IPv6).
|
||||||
* @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 {number} resolverPort Port the DNS server is listening on
|
||||||
* @param {string} transport The transport method to use
|
* @param {object} transport The transport method and options
|
||||||
* @param {string} dohQuery The query path used only for DoH
|
* @param {string} transport.type Transport method, default is UDP
|
||||||
|
* @param {number} transport.timeout Timeout to use for queries
|
||||||
|
* @param {boolean} transport.ignoreCertErrors Proceed with secure connections
|
||||||
|
* even if the server presents an untrusted or expired certificate
|
||||||
|
* @param {string} transport.dohQueryPath Query path to use for DoH requests
|
||||||
|
* @param {boolean} transport.dohUsePost If true, DNS query will be sent using
|
||||||
|
* HTTP POST method for DoH requests, otherwise use HTTP GET method
|
||||||
|
* @param {boolean} transport.dohUseHttp2 If true, DNS query will be made with
|
||||||
|
* HTTP/2 session for DOH requests, otherwise use HTTP/1.1
|
||||||
* @returns {Promise<(string[] | object[] | object)>} DNS response
|
* @returns {Promise<(string[] | object[] | object)>} DNS response
|
||||||
*/
|
*/
|
||||||
exports.dnsResolve = function (opts, resolverServer, resolverPort, transport, dohQuery) {
|
exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
|
// Set transport variables to defaults if not defined
|
||||||
|
const method = ("type" in transport) ? transport.type.toUpperCase() : "UDP";
|
||||||
|
const isSecure = [ "DOH", "DOT", "DOQ" ].includes(method);
|
||||||
|
const timeout = transport.timeout ?? 30000; // 30 seconds
|
||||||
|
const skipCertCheck = transport.ignoreCertErrors ?? false;
|
||||||
|
const dohQuery = transport.dohQueryPath ?? "dns-query";
|
||||||
|
const dohUsePost = transport.dohUsePost ?? false;
|
||||||
|
const dohUseHttp2 = transport.dohUseHttp2 ?? false;
|
||||||
|
|
||||||
// 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("]", "");
|
||||||
|
@ -404,29 +463,42 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport, do
|
||||||
if (opts.id == null) {
|
if (opts.id == null) {
|
||||||
// Set query ID to "0" for HTTP cache friendlyness on DoH requests.
|
// Set query ID to "0" for HTTP cache friendlyness on DoH requests.
|
||||||
// See https://github.com/mafintosh/dns-packet/issues/77
|
// See https://github.com/mafintosh/dns-packet/issues/77
|
||||||
opts.id = (transport.toUpperCase() === "DOH") ? 0 : Math.floor(Math.random() * 65534) + 1;
|
opts.id = (method === "DOH") ? 0 : Math.floor(Math.random() * 65534) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set UDP payload size to safe levels for transmission over 1500 MTU
|
||||||
|
if (!opts.udpPayloadSize) {
|
||||||
|
opts.udpPayloadSize = (addressFamily === 4) ? 1432 : 1232;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable stream encoding for TCP and DOT transport methods
|
||||||
|
if ([ "TCP", "DOT" ].includes(method)) {
|
||||||
|
opts.stream = true;
|
||||||
|
}
|
||||||
|
// Generate buffer with encoded DNS query
|
||||||
|
const buf = exports.makeDnsPacket(opts);
|
||||||
|
|
||||||
let client;
|
let client;
|
||||||
let resolver = null;
|
let resolver;
|
||||||
// Transport method determines which client type to use
|
// Transport method determines which client type to use
|
||||||
const isSecure = [ "DOH", "DOT" ].includes(transport.toUpperCase());
|
switch (method) {
|
||||||
switch (transport.toUpperCase()) {
|
|
||||||
|
|
||||||
case "TCP":
|
case "TCP":
|
||||||
case "DOT": {
|
case "DOT": {
|
||||||
opts.stream = true;
|
|
||||||
const buf = exports.makePacket(opts);
|
|
||||||
if (isSecure) {
|
if (isSecure) {
|
||||||
const options = {
|
const options = {
|
||||||
port: resolverPort,
|
port: resolverPort,
|
||||||
host: resolverServer,
|
host: resolverServer,
|
||||||
// TODO: Option for relaxing certificate validation
|
rejectUnauthorized: !skipCertCheck,
|
||||||
secureContext: tls.createSecureContext({
|
secureContext: tls.createSecureContext({
|
||||||
secureProtocol: "TLSv1_2_method",
|
minVersion: "TLSv1.2",
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
// TODO: Error handling for untrusted or expired cert
|
// Set TLS ServerName only if server is not an IP address per
|
||||||
|
// Section 3 of RFC 6066
|
||||||
|
if (addressFamily === 0) {
|
||||||
|
options.servername = resolverServer;
|
||||||
|
}
|
||||||
client = tls.connect(options, () => {
|
client = tls.connect(options, () => {
|
||||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
||||||
client.write(buf);
|
client.write(buf);
|
||||||
|
@ -438,99 +510,201 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport, do
|
||||||
client.write(buf);
|
client.write(buf);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
client.on("close", () => {
|
|
||||||
log.debug("dns", "Connection closed");
|
|
||||||
});
|
|
||||||
resolver = new Promise((resolve, reject) => {
|
resolver = new Promise((resolve, reject) => {
|
||||||
|
// The below message is used when the response received does
|
||||||
|
// not follow Section 4.2.2 of RFC 1035
|
||||||
|
const lenErrMsg = "Resolver returned invalid DNS response";
|
||||||
let data = Buffer.alloc(0);
|
let data = Buffer.alloc(0);
|
||||||
let expectedLength = 0;
|
let expectedLength = 0;
|
||||||
|
let isValidLength = false;
|
||||||
|
client.on("error", (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
client.setTimeout(timeout, () => {
|
||||||
|
client.destroy();
|
||||||
|
reject({ message: "Connection timed out" });
|
||||||
|
});
|
||||||
client.on("data", (chunk) => {
|
client.on("data", (chunk) => {
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
if (chunk.byteLength > 1) {
|
if (chunk.byteLength > 1) {
|
||||||
const plen = chunk.readUInt16BE(0);
|
expectedLength = chunk.readUInt16BE(0);
|
||||||
expectedLength = plen;
|
if (expectedLength < 12) {
|
||||||
if (plen < 12) {
|
reject({ message: lenErrMsg });
|
||||||
reject("Response received is below DNS minimum packet length");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data = Buffer.concat([ data, chunk ]);
|
data = Buffer.concat([ data, chunk ]);
|
||||||
if (data.byteLength >= expectedLength) {
|
if (data.byteLength - 2 === expectedLength) {
|
||||||
|
isValidLength = true;
|
||||||
client.destroy();
|
client.destroy();
|
||||||
const response = dnsPacket.streamDecode(data);
|
const response = exports.decodeDnsPacket(data, true, reject);
|
||||||
log.debug("dns", "Response decoded");
|
|
||||||
resolve(response);
|
resolve(response);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
client.on("close", () => {
|
||||||
|
log.debug("dns", "Connection closed");
|
||||||
|
if (!isValidLength) {
|
||||||
|
reject({ message: lenErrMsg });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "DOH": {
|
case "DOH": {
|
||||||
const buf = exports.makePacket(opts);
|
const queryPath = dohUsePost ? dohQuery : `${dohQuery}?dns=${buf.toString("base64url")}`;
|
||||||
// TODO: implement POST requests for wireformat and JSON
|
const requestURL = url.parse(`https://${resolverServer}:${resolverPort}/${queryPath}`, true);
|
||||||
dohQuery = dohQuery || "dns-query?dns={query}";
|
const mimeType = "application/dns-message";
|
||||||
dohQuery = dohQuery.replace("{query}", buf.toString("base64url"));
|
|
||||||
const requestURL = url.parse(`https://${resolverServer}:${resolverPort}/${dohQuery}`, true);
|
|
||||||
const options = {
|
const options = {
|
||||||
hostname: requestURL.hostname,
|
hostname: requestURL.hostname,
|
||||||
port: requestURL.port,
|
port: requestURL.port,
|
||||||
path: requestURL.path,
|
path: requestURL.path,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/dns-message",
|
"accept": mimeType,
|
||||||
},
|
},
|
||||||
// TODO: Option for relaxing certificate validation
|
rejectUnauthorized: !skipCertCheck,
|
||||||
};
|
};
|
||||||
|
if (dohUsePost) {
|
||||||
|
options.method = "POST";
|
||||||
|
// Setting Content-Length header is required for some resolvers
|
||||||
|
options.headers["content-length"] = buf.byteLength;
|
||||||
|
options.headers["content-type"] = mimeType;
|
||||||
|
}
|
||||||
resolver = new Promise((resolve, reject) => {
|
resolver = new Promise((resolve, reject) => {
|
||||||
client = https.request(options, (response) => {
|
/**
|
||||||
|
* Helper function to validate HTTP response
|
||||||
|
* @param {IncomingMessage|ClientHttp2Stream} httpResponse
|
||||||
|
* The response from https or http2 client
|
||||||
|
* @param {object} http2Headers Response headers from http2
|
||||||
|
* @returns {void}
|
||||||
|
* @throws missing one or more headers for HTTP/2 response
|
||||||
|
*/
|
||||||
|
const handleResponse = (httpResponse, http2Headers) => {
|
||||||
|
// Determine status code and content type
|
||||||
|
let statusCode;
|
||||||
|
let contentType;
|
||||||
|
if (dohUseHttp2) {
|
||||||
|
if (!http2Headers) {
|
||||||
|
throw new Error("No headers passed for HTTP/2 response");
|
||||||
|
}
|
||||||
|
statusCode = http2Headers[HTTP2_HEADER_STATUS];
|
||||||
|
contentType = http2Headers[HTTP2_HEADER_CONTENT_TYPE];
|
||||||
|
} else {
|
||||||
|
statusCode = httpResponse.statusCode;
|
||||||
|
contentType = httpResponse.headers["content-type"];
|
||||||
|
}
|
||||||
|
// Validate response from resolver
|
||||||
|
if (statusCode !== 200) {
|
||||||
|
reject({ message: `Request failed with status code ${statusCode}` });
|
||||||
|
return;
|
||||||
|
} else if (contentType !== mimeType) {
|
||||||
|
reject({ message: `Content-Type was "${contentType}", expected ${mimeType}` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Read the response body into a buffer
|
||||||
let data = Buffer.alloc(0);
|
let data = Buffer.alloc(0);
|
||||||
response.on("data", (chunk) => {
|
httpResponse.on("data", (chunk) => {
|
||||||
data = Buffer.concat([ data, chunk ]);
|
data = Buffer.concat([ data, chunk ]);
|
||||||
});
|
});
|
||||||
response.on("end", () => {
|
httpResponse.on("end", () => {
|
||||||
const response = dnsPacket.decode(data);
|
const response = exports.decodeDnsPacket(data, false, reject);
|
||||||
log.debug("dns", "Response decoded");
|
|
||||||
resolve(response);
|
resolve(response);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
if (dohUseHttp2) {
|
||||||
|
const headers = {};
|
||||||
|
headers[HTTP2_HEADER_AUTHORITY] = options.hostname;
|
||||||
|
headers[HTTP2_HEADER_PATH] = options.path;
|
||||||
|
headers[HTTP2_HEADER_METHOD] = options.method;
|
||||||
|
headers[HTTP2_HEADER_ACCEPT] = options.headers["accept"];
|
||||||
|
if (dohUsePost) {
|
||||||
|
headers[HTTP2_HEADER_CONTENT_LENGTH] = options.headers["content-length"];
|
||||||
|
headers[HTTP2_HEADER_CONTENT_TYPE] = options.headers["content-type"];
|
||||||
|
}
|
||||||
|
client = http2.connect(`https://${options.hostname}:${options.port}`, {
|
||||||
|
rejectUnauthorized: options.rejectUnauthorized,
|
||||||
|
});
|
||||||
|
client.setTimeout(timeout, () => {
|
||||||
|
client.destroy();
|
||||||
|
reject({ message: "Request timed out" });
|
||||||
|
});
|
||||||
|
client.on("connect", () => {
|
||||||
|
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
||||||
|
});
|
||||||
|
const req = client.request(headers);
|
||||||
|
req.on("error", (err) => {
|
||||||
|
err.message = "HTTP/2: " + err.message;
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
req.on("response", (resHeaders) => {
|
||||||
|
handleResponse(req, resHeaders);
|
||||||
|
});
|
||||||
|
req.on("end", () => {
|
||||||
|
client.close();
|
||||||
|
});
|
||||||
|
if (dohUsePost) {
|
||||||
|
req.write(buf);
|
||||||
|
}
|
||||||
|
req.end();
|
||||||
|
} else {
|
||||||
|
client = https.request(options, (httpResponse) => {
|
||||||
|
handleResponse(httpResponse);
|
||||||
|
});
|
||||||
|
client.setTimeout(timeout, () => {
|
||||||
|
client.destroy();
|
||||||
|
reject({ message: "Request timed out" });
|
||||||
});
|
});
|
||||||
client.on("socket", (socket) => {
|
client.on("socket", (socket) => {
|
||||||
socket.on("secureConnect", () => {
|
socket.on("secureConnect", () => {
|
||||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (dohUsePost) {
|
||||||
|
client.write(buf);
|
||||||
|
}
|
||||||
|
client.end();
|
||||||
|
}
|
||||||
client.on("error", (err) => {
|
client.on("error", (err) => {
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
client.on("close", () => {
|
client.on("close", () => {
|
||||||
log.debug("dns", "Connection closed");
|
log.debug("dns", "Connection closed");
|
||||||
});
|
});
|
||||||
client.write(buf);
|
|
||||||
client.end();
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//case "UDP":
|
case "UDP":
|
||||||
default: {
|
default: {
|
||||||
const buf = exports.makePacket(opts);
|
if (addressFamily === 0) {
|
||||||
client = dgram.createSocket("udp" + String(addressFamily));
|
return new Promise((resolve, reject) => {
|
||||||
client.on("connect", () => {
|
reject({ message: "Resolver server must be IP address for UDP transport method" });
|
||||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
|
||||||
});
|
|
||||||
client.on("close", () => {
|
|
||||||
log.debug("dns", "Connection closed");
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
client = dgram.createSocket(`udp${addressFamily}`);
|
||||||
resolver = new Promise((resolve, reject) => {
|
resolver = new Promise((resolve, reject) => {
|
||||||
|
let timer;
|
||||||
client.on("message", (rdata, rinfo) => {
|
client.on("message", (rdata, rinfo) => {
|
||||||
client.close();
|
client.close();
|
||||||
const response = dnsPacket.decode(rdata);
|
const response = exports.decodeDnsPacket(rdata, false, reject);
|
||||||
log.debug("dns", "Response decoded");
|
|
||||||
resolve(response);
|
resolve(response);
|
||||||
});
|
});
|
||||||
client.on("error", (err) => {
|
client.on("error", (err) => {
|
||||||
|
clearTimeout(timer);
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
|
client.on("listening", () => {
|
||||||
|
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
reject({ message: "Query timed out" });
|
||||||
|
client.close();
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
client.on("close", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
log.debug("dns", "Connection closed");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
client.send(buf, 0, buf.length, resolverPort, resolverServer);
|
client.send(buf, 0, buf.length, resolverPort, resolverServer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
.multiselect__single {
|
.multiselect__single {
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
"retriesDescription": "Maximum retries before the service is marked as down and a notification is sent",
|
"retriesDescription": "Maximum retries before the service is marked as down and a notification is sent",
|
||||||
"ignoredTLSError": "TLS/SSL errors have been ignored",
|
"ignoredTLSError": "TLS/SSL errors have been ignored",
|
||||||
"ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites",
|
"ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites",
|
||||||
"ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection",
|
"ignoreTLSErrorGeneral": "Ignore TLS/SSL errors for secure connections",
|
||||||
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
|
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
|
||||||
"maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.",
|
"maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.",
|
||||||
"Upside Down Mode": "Upside Down Mode",
|
"Upside Down Mode": "Upside Down Mode",
|
||||||
|
@ -145,6 +145,7 @@
|
||||||
"Resolver Server": "Resolver Server",
|
"Resolver Server": "Resolver Server",
|
||||||
"Transport Method": "Transport Method",
|
"Transport Method": "Transport Method",
|
||||||
"Query Path": "Query Path",
|
"Query Path": "Query Path",
|
||||||
|
"Force HTTP2": "HTTP/2",
|
||||||
"Resource Record Type": "Resource Record Type",
|
"Resource Record Type": "Resource Record Type",
|
||||||
"Skip Remote DNSSEC Verification": "Skip Remote DNSSEC Verification",
|
"Skip Remote DNSSEC Verification": "Skip Remote DNSSEC Verification",
|
||||||
"Last Result": "Last Result",
|
"Last Result": "Last Result",
|
||||||
|
@ -579,7 +580,9 @@
|
||||||
"resolverserverDescription": "Cloudflare is the default server. Use IP address for UDP/TCP/DoT, and domain name for DoH/DoT.",
|
"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",
|
"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.",
|
||||||
|
"dohHttpMethodDescription": "Set the HTTP method to use for DoH query.",
|
||||||
|
"forceHttp2": "Send the request using HTTP/2. Fails if the server does not support HTTP/2.",
|
||||||
"skipRemoteDnssecDescription": "Requests the resolver server not to perform DNSSEC verification against queried records.",
|
"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.",
|
||||||
|
|
|
@ -31,9 +31,9 @@
|
||||||
<br>
|
<br>
|
||||||
<span>{{ $t("Expected Value") }}:</span> <span class="keyword">{{ monitor.expectedValue }}</span>
|
<span>{{ $t("Expected Value") }}:</span> <span class="keyword">{{ monitor.expectedValue }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="monitor.type === 'dns'">[{{ monitor.dns_resolve_type }}] {{ monitor.hostname }}
|
<span v-if="monitor.type === 'dns'">[{{ monitor.dnsResolveType }}] {{ monitor.hostname }}
|
||||||
<br>
|
<br>
|
||||||
<span>{{ $t("Last Result") }}:</span> <span class="keyword">{{ monitor.dns_last_result }}</span>
|
<span>{{ $t("Last Result") }}:</span> <span class="keyword">{{ monitor.dnsLastResult }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="monitor.type === 'docker'">Docker container: {{ monitor.docker_container }}</span>
|
<span v-if="monitor.type === 'docker'">Docker container: {{ monitor.docker_container }}</span>
|
||||||
<span v-if="monitor.type === 'gamedig'">Gamedig - {{ monitor.game }}: {{ monitor.hostname }}:{{ monitor.port }}</span>
|
<span v-if="monitor.type === 'gamedig'">Gamedig - {{ monitor.game }}: {{ monitor.hostname }}:{{ monitor.port }}</span>
|
||||||
|
|
|
@ -370,18 +370,19 @@
|
||||||
<!-- For DNS Type -->
|
<!-- For DNS Type -->
|
||||||
<template v-if="monitor.type === 'dns'">
|
<template v-if="monitor.type === 'dns'">
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="dns_resolve_server" class="form-label">{{ $t("Resolver Server") }}</label>
|
<label for="dns-resolve-server" class="form-label">{{ $t("Resolver Server") }}</label>
|
||||||
<input id="dns_resolve_server" v-model="monitor.dns_resolve_server" type="text" class="form-control" :pattern="dnsResolverRegex" required>
|
<input id="dns-resolve-server" ref="dns-resolve-server" v-model="monitor.dnsResolveServer" type="text" class="form-control" :pattern="dnsResolverRegex" required>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
{{ $t("resolverserverDescription") }}
|
{{ $t("resolverserverDescription") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- TODO center selected option text -->
|
||||||
<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
|
||||||
id="dns_transport"
|
id="dns-transport"
|
||||||
v-model="monitor.dns_transport"
|
v-model="monitor.dnsTransport"
|
||||||
:options="dnsTransportOptions"
|
:options="dnsTransportOptions"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
|
@ -408,12 +409,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="dns_resolve_type" class="form-label">{{ $t("Resource Record Type") }}</label>
|
<label for="dns-resolve-type" class="form-label">{{ $t("Resource Record Type") }}</label>
|
||||||
|
|
||||||
<!-- :allow-empty="false" is not working, set a default value instead https://github.com/shentao/vue-multiselect/issues/336 -->
|
<!-- :allow-empty="false" is not working, set a default value instead https://github.com/shentao/vue-multiselect/issues/336 -->
|
||||||
|
<!-- TODO center selected option text -->
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
id="dns_resolve_type"
|
id="dns-resolve-type"
|
||||||
v-model="monitor.dns_resolve_type"
|
v-model="monitor.dnsResolveType"
|
||||||
:options="dnsresolvetypeOptions"
|
:options="dnsresolvetypeOptions"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
|
@ -646,10 +648,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'redis' " class="my-3 form-check">
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'redis' || (monitor.type === 'dns' && isSecureDnsTransport)" class="my-3 form-check">
|
||||||
<input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
|
<input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
|
||||||
<label class="form-check-label" for="ignore-tls">
|
<label class="form-check-label" for="ignore-tls">
|
||||||
{{ monitor.type === "redis" ? $t("ignoreTLSErrorGeneral") : $t("ignoreTLSError") }}
|
{{ monitor.type === "redis" || monitor.type === "dns" ? $t("ignoreTLSErrorGeneral") : $t("ignoreTLSError") }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -668,18 +670,51 @@
|
||||||
<!-- Advanced DNS monitor settings -->
|
<!-- Advanced DNS monitor settings -->
|
||||||
<div v-if="monitor.type === 'dns'" class="my-3">
|
<div v-if="monitor.type === 'dns'" class="my-3">
|
||||||
<div v-if="dohSelected">
|
<div v-if="dohSelected">
|
||||||
<label for="doh_query_path" class="form-label">{{ $t("Query Path") }}</label>
|
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<label for="doh_query_path" class="px-2 fs-5">/</label>
|
<div class="my-3 flex-column flex-fill">
|
||||||
<input id="doh_query_path" v-model="monitor.doh_query_path" type="text" class="form-control" :pattern="urlQueryRegex" placeholder="dns-query?dns={query}">
|
<div>
|
||||||
|
<label for="method" class="form-label">{{ $t("Method") }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-row">
|
||||||
|
<div class="d-inline-flex">
|
||||||
|
<select id="method" v-model="monitor.method" class="form-select">
|
||||||
|
<option value="GET">
|
||||||
|
GET
|
||||||
|
</option>
|
||||||
|
<option value="POST">
|
||||||
|
POST
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mx-3 flex-fill">
|
||||||
|
<input :value="dohDisplayUrl" type="button" class="form-control text-center" @click="focusElement('dns-resolve-server')">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
{{ $t("dohQueryPathDescription") + ' "{query}".' }}
|
{{ $t("dohHttpMethodDescription") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-3 flex-column flex-fill">
|
||||||
|
<label for="doh-query-path" class="form-label">{{ $t("Query Path") }}</label>
|
||||||
|
<input id="doh-query-path" v-model="monitor.dohQueryPath" type="text" class="form-control" :pattern="urlQueryRegex" placeholder="dns-query">
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("dohQueryPathDescription") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-3 form-check">
|
||||||
|
<input id="force-http2" v-model="monitor.forceHttp2" class="form-check-input" type="checkbox">
|
||||||
|
<label class="form-check-label" for="force-http2">
|
||||||
|
{{ $t("Force HTTP2") }}
|
||||||
|
</label>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("forceHttp2") }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check">
|
<div class="my-3 form-check">
|
||||||
<input id="skip_remote_dnssec" v-model="monitor.skip_remote_dnssec" class="form-check-input" type="checkbox" value="">
|
<input id="skip_remote_dnssec" v-model="monitor.skip_remote_dnssec" class="form-check-input" type="checkbox">
|
||||||
<label class="form-check-label" for="skip_remote_dnssec">
|
<label class="form-check-label" for="skip_remote_dnssec">
|
||||||
{{ $t("Skip Remote DNSSEC Verification") }}
|
{{ $t("Skip Remote DNSSEC Verification") }}
|
||||||
</label>
|
</label>
|
||||||
|
@ -1135,9 +1170,11 @@ const monitorDefaults = {
|
||||||
expiryNotification: false,
|
expiryNotification: false,
|
||||||
maxredirects: 10,
|
maxredirects: 10,
|
||||||
accepted_statuscodes: [ "200-299" ],
|
accepted_statuscodes: [ "200-299" ],
|
||||||
dns_resolve_type: "A",
|
dnsResolveType: "A",
|
||||||
dns_resolve_server: "1.1.1.1",
|
dnsResolveServer: "1.1.1.1",
|
||||||
dns_transport: "UDP",
|
dnsTransport: "UDP",
|
||||||
|
dohQueryPath: "dns-query",
|
||||||
|
forceHttp2: false,
|
||||||
skip_remote_dnssec: false,
|
skip_remote_dnssec: false,
|
||||||
docker_container: "",
|
docker_container: "",
|
||||||
docker_host: null,
|
docker_host: null,
|
||||||
|
@ -1235,7 +1272,7 @@ export default {
|
||||||
|
|
||||||
// Permit IP address for TCP/UDP resolvers, hostname for DoH/DoT
|
// Permit IP address for TCP/UDP resolvers, hostname for DoH/DoT
|
||||||
if (! isDev) {
|
if (! isDev) {
|
||||||
switch (this.monitor.dns_transport) {
|
switch (this.monitor.dnsTransport) {
|
||||||
case "UDP":
|
case "UDP":
|
||||||
case "TCP":
|
case "TCP":
|
||||||
return this.ipRegexPattern.source;
|
return this.ipRegexPattern.source;
|
||||||
|
@ -1484,7 +1521,7 @@ message HealthCheckResponse {
|
||||||
// array defined in server\monitor-types\dns.js in order to
|
// array defined in server\monitor-types\dns.js in order to
|
||||||
// pass to Vue, then sliced below based on index.
|
// pass to Vue, then sliced below based on index.
|
||||||
const dnsConditionVariables = this.$root.monitorTypeList["dns"]?.conditionVariables;
|
const dnsConditionVariables = this.$root.monitorTypeList["dns"]?.conditionVariables;
|
||||||
switch (this.monitor.dns_resolve_type) {
|
switch (this.monitor.dnsResolveType) {
|
||||||
case "A":
|
case "A":
|
||||||
case "AAAA":
|
case "AAAA":
|
||||||
case "TXT":
|
case "TXT":
|
||||||
|
@ -1508,8 +1545,18 @@ message HealthCheckResponse {
|
||||||
},
|
},
|
||||||
|
|
||||||
dohSelected() {
|
dohSelected() {
|
||||||
return this.monitor.dns_transport === "DoH";
|
return this.monitor.dnsTransport === "DoH";
|
||||||
}
|
},
|
||||||
|
|
||||||
|
dohDisplayUrl() {
|
||||||
|
const port = (this.monitor.port !== 443) ? `:${this.monitor.port}` : "";
|
||||||
|
return `https://${this.monitor.dnsResolveServer}${port}/`;
|
||||||
|
},
|
||||||
|
|
||||||
|
isSecureDnsTransport() {
|
||||||
|
return [ "DoH", "DoT", "DoQ" ].includes(this.monitor.dnsTransport);
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
"$root.proxyList"() {
|
"$root.proxyList"() {
|
||||||
|
@ -1977,6 +2024,11 @@ message HealthCheckResponse {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
focusElement(refId) {
|
||||||
|
// Focus the element that has a defined reference
|
||||||
|
this.$refs[refId].focus();
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -165,7 +165,7 @@ export function dnsNameRegexPattern() {
|
||||||
* @param {boolean} qstr whether or not the url follows query string format
|
* @param {boolean} qstr whether or not the url follows query string format
|
||||||
* @returns {RegExp} The requested regex
|
* @returns {RegExp} The requested regex
|
||||||
*/
|
*/
|
||||||
export function urlPathRegexPattern(qstr = false) {
|
export function urlPathRegexPattern(qstr = true) {
|
||||||
// Ensures a URL path follows query string format
|
// 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 queryStringRegexPattern = /^\/?(([a-zA-Z0-9\-_%])+\/)*[a-zA-Z0-9\-_%]*\?([a-zA-Z0-9\-_%]+=[a-zA-Z0-9\-_%]*&?)+$/;
|
||||||
// Only checks for valid URL path containing "{query}"
|
// Only checks for valid URL path containing "{query}"
|
||||||
|
|
Loading…
Add table
Reference in a new issue