From 7df13b440a386389c85a9441fa4f368fa7309293 Mon Sep 17 00:00:00 2001 From: Nelson Chan Date: Fri, 16 Jul 2021 12:10:12 +0800 Subject: [PATCH 1/3] Feat: Handle DNS multiple A Records --- server/model/monitor.js | 87 ++++++++++++++++++++++++++++------------- server/util-server.js | 37 ++++++++++++++++++ 2 files changed, 97 insertions(+), 27 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 162772875b..fa17262e39 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") @@ -70,34 +70,67 @@ 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, + }, + } + ); + } 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 6904a65a4b..81115efce8 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) => { @@ -58,3 +59,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) { + // v4 + 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; + } + // v6 + 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; +}; From 274314c5e54d35f2a21234b1c05ddc9349f6d6b9 Mon Sep 17 00:00:00 2001 From: Nelson Chan Date: Mon, 19 Jul 2021 18:04:44 +0800 Subject: [PATCH 2/3] Fix: Add axios timeout --- server/model/monitor.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/model/monitor.js b/server/model/monitor.js index fa17262e39..f770cfebce 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -95,6 +95,7 @@ class Monitor extends BeanModel { "User-Agent": "Uptime-Kuma", host: targetUrl.hostname, }, + timeout: 3000, } ); } catch (error) { From 274efd8bacf3e21edb457fc087cb9d3236968200 Mon Sep 17 00:00:00 2001 From: Nelson Chan Date: Wed, 21 Jul 2021 10:23:17 +0800 Subject: [PATCH 3/3] Chore: Improve code comments Co-authored-by: Adam Stachowicz --- server/util-server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/util-server.js b/server/util-server.js index 81115efce8..c91b827602 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -77,7 +77,7 @@ exports.dnsLookup = async function (name) { // check if string is a valid IP address // is-ipv6-node by @anatoliygatt exports.isIpAddress = function (ipAddress) { - // v4 + // 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 @@ -85,7 +85,7 @@ exports.isIpAddress = function (ipAddress) { ) { return true; } - // v6 + // 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