diff --git a/server/model/monitor.js b/server/model/monitor.js index f81f6e00e2..1319988f05 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -5,7 +5,7 @@ var timezone = require('dayjs/plugin/timezone') dayjs.extend(utc) dayjs.extend(timezone) const axios = require("axios"); -const {tcping, ping} = require("../util-server"); +const {tcping, ping, dnsLookup, isIpAddress} = require("../util-server"); const {R} = require("redbean-node"); const {BeanModel} = require("redbean-node/dist/bean-model"); const {Notification} = require("../notification") @@ -68,34 +68,68 @@ class Monitor extends BeanModel { try { if (this.type === "http" || this.type === "keyword") { - let startTime = dayjs().valueOf(); - let res = await axios.get(this.url, { - headers: { 'User-Agent':'Uptime-Kuma' } - }) - bean.msg = `${res.status} - ${res.statusText}` - bean.ping = dayjs().valueOf() - startTime; - - if (this.type === "http") { - bean.status = 1; + // Parse URL + const targetUrl = new URL(this.url); + const serversToCheck = []; + if (!isIpAddress(targetUrl.hostname)) { + // Get DNS Result + const dnsResults = await dnsLookup(targetUrl.hostname); + serversToCheck.push(...dnsResults); } else { - - let data = res.data; - - // Convert to string for object/array - if (typeof data !== "string") { - data = JSON.stringify(data) - } - - if (data.includes(this.keyword)) { - bean.msg += ", keyword is found" - bean.status = 1; - } else { - throw new Error(bean.msg + ", but keyword is not found") - } - + serversToCheck.push({ address: targetUrl.hostname }); } - - + // Check each server in parallel + await Promise.all( + serversToCheck.map(async (server) => { + let startTime = dayjs().valueOf(); + let res; + + // Catch connection errors inside + try { + res = await axios.get( + `${targetUrl.protocol}//${server.address}`, + { + headers: { + "User-Agent": "Uptime-Kuma", + host: targetUrl.hostname, + }, + timeout: 3000, + } + ); + } catch (error) { + bean.msg = `server [${server.address}]: ${error.message}`; + return; + } + + // Stop if we found a server that's up + if (bean.status == 1) { + return; + } + + bean.msg = `${res.status} - ${res.statusText}`; + bean.ping = dayjs().valueOf() - startTime; + + if (this.type === "http") { + bean.status = 1; + } else { + let data = res.data; + + // Convert to string for object/array + if (typeof data !== "string") { + data = JSON.stringify(data); + } + + if (data.includes(this.keyword)) { + bean.msg += ", keyword is found"; + bean.status = 1; + } else { + throw new Error( + bean.msg + ", but keyword is not found" + ); + } + } + }) + ); } else if (this.type === "port") { bean.ping = await tcping(this.hostname, this.port); bean.msg = "" diff --git a/server/util-server.js b/server/util-server.js index b387f4c7cc..f0bec6651b 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -1,6 +1,7 @@ const tcpp = require('tcp-ping'); const Ping = require("./ping-lite"); const {R} = require("redbean-node"); +const dns = require('dns'); exports.tcping = function (hostname, port) { return new Promise((resolve, reject) => { @@ -70,3 +71,39 @@ exports.getSettings = async function (type) { return result; } + +// Async function wrapper for dns.lookup() +// Returns list of resolved IP Addresses +exports.dnsLookup = async function (name) { + return new Promise((resolve, reject) => { + dns.lookup(name, { all: true, family: 0 }, (err, ips) => { + if (err) { + reject(err); + } else { + resolve(ips); + } + }); + }); +}; + +// check if string is a valid IP address +// is-ipv6-node by @anatoliygatt +exports.isIpAddress = function (ipAddress) { + // IPv4 + if ( + /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( + ipAddress + ) + ) { + return true; + } + // IPv6 + if ( + /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/.test( + ipAddress + ) + ) { + return true; + } + return false; +};