1
0
mirror of https://github.com/louislam/uptime-kuma.git synced 2025-09-16 09:26:42 +02:00

Enhanced ping monitor with advanced options (count, timeout, numeric) (#5588)
Some checks failed
Automatically close stale issues / stale (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
validate / json-yaml-validate (push) Has been cancelled
validate / validate (push) Has been cancelled
Auto Test / armv7-simple-test (18, ARMv7) (push) Has been cancelled
Auto Test / armv7-simple-test (20, ARMv7) (push) Has been cancelled
Auto Test / check-linters (push) Has been cancelled
Auto Test / e2e-test (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Auto Test / auto-test (18, ARM64) (push) Has been cancelled
Auto Test / auto-test (18, macos-latest) (push) Has been cancelled
Auto Test / auto-test (18, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (18, windows-latest) (push) Has been cancelled
Auto Test / auto-test (20, ARM64) (push) Has been cancelled
Auto Test / auto-test (20, macos-latest) (push) Has been cancelled
Auto Test / auto-test (20, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (20, windows-latest) (push) Has been cancelled

Co-authored-by: Frank Elsinga <frank@elsinga.de>
This commit is contained in:
Filippo
2025-05-24 02:57:39 +02:00
committed by GitHub
parent c69fcd4093
commit 839ead80cc
8 changed files with 332 additions and 41 deletions

View File

@@ -2,7 +2,11 @@ const dayjs = require("dayjs");
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
SQL_DATETIME_FORMAT, evaluateJsonQuery
SQL_DATETIME_FORMAT, evaluateJsonQuery,
PING_PACKET_SIZE_MIN, PING_PACKET_SIZE_MAX, PING_PACKET_SIZE_DEFAULT,
PING_GLOBAL_TIMEOUT_MIN, PING_GLOBAL_TIMEOUT_MAX, PING_GLOBAL_TIMEOUT_DEFAULT,
PING_COUNT_MIN, PING_COUNT_MAX, PING_COUNT_DEFAULT,
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
} = require("../../src/util");
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
@@ -156,6 +160,11 @@ class Monitor extends BeanModel {
smtpSecurity: this.smtpSecurity,
rabbitmqNodes: JSON.parse(this.rabbitmqNodes),
conditions: JSON.parse(this.conditions),
// ping advanced options
ping_numeric: this.isPingNumeric(),
ping_count: this.ping_count,
ping_per_request_timeout: this.ping_per_request_timeout,
};
if (includeSensitiveData) {
@@ -248,6 +257,14 @@ class Monitor extends BeanModel {
return Boolean(this.expiryNotification);
}
/**
* Check if ping should use numeric output only
* @returns {boolean} True if IP addresses will be output instead of symbolic hostnames
*/
isPingNumeric() {
return Boolean(this.ping_numeric);
}
/**
* Parse to boolean
* @returns {boolean} Should TLS errors be ignored?
@@ -585,7 +602,7 @@ class Monitor extends BeanModel {
bean.status = UP;
} else if (this.type === "ping") {
bean.ping = await ping(this.hostname, this.packetSize);
bean.ping = await ping(this.hostname, this.ping_count, "", this.ping_numeric, this.packetSize, this.timeout, this.ping_per_request_timeout);
bean.msg = "";
bean.status = UP;
} else if (this.type === "push") { // Type: Push
@@ -657,7 +674,7 @@ class Monitor extends BeanModel {
bean.msg = res.data.response.servers[0].name;
try {
bean.ping = await ping(this.hostname, this.packetSize);
bean.ping = await ping(this.hostname, PING_COUNT_DEFAULT, "", true, this.packetSize, PING_GLOBAL_TIMEOUT_DEFAULT, PING_PER_REQUEST_TIMEOUT_DEFAULT);
} catch (_) { }
} else {
throw new Error("Server not found on Steam");
@@ -1469,6 +1486,31 @@ class Monitor extends BeanModel {
if (this.interval < MIN_INTERVAL_SECOND) {
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
}
if (this.type === "ping") {
// ping parameters validation
if (this.packetSize && (this.packetSize < PING_PACKET_SIZE_MIN || this.packetSize > PING_PACKET_SIZE_MAX)) {
throw new Error(`Packet size must be between ${PING_PACKET_SIZE_MIN} and ${PING_PACKET_SIZE_MAX} (default: ${PING_PACKET_SIZE_DEFAULT})`);
}
if (this.ping_per_request_timeout && (this.ping_per_request_timeout < PING_PER_REQUEST_TIMEOUT_MIN || this.ping_per_request_timeout > PING_PER_REQUEST_TIMEOUT_MAX)) {
throw new Error(`Per-ping timeout must be between ${PING_PER_REQUEST_TIMEOUT_MIN} and ${PING_PER_REQUEST_TIMEOUT_MAX} seconds (default: ${PING_PER_REQUEST_TIMEOUT_DEFAULT})`);
}
if (this.ping_count && (this.ping_count < PING_COUNT_MIN || this.ping_count > PING_COUNT_MAX)) {
throw new Error(`Echo requests count must be between ${PING_COUNT_MIN} and ${PING_COUNT_MAX} (default: ${PING_COUNT_DEFAULT})`);
}
if (this.timeout) {
const pingGlobalTimeout = Math.round(Number(this.timeout));
if (pingGlobalTimeout < this.ping_per_request_timeout || pingGlobalTimeout < PING_GLOBAL_TIMEOUT_MIN || pingGlobalTimeout > PING_GLOBAL_TIMEOUT_MAX) {
throw new Error(`Timeout must be between ${PING_GLOBAL_TIMEOUT_MIN} and ${PING_GLOBAL_TIMEOUT_MAX} seconds (default: ${PING_GLOBAL_TIMEOUT_DEFAULT})`);
}
this.timeout = pingGlobalTimeout;
}
}
}
/**

View File

@@ -876,6 +876,11 @@ let needSetup = false;
bean.rabbitmqPassword = monitor.rabbitmqPassword;
bean.conditions = JSON.stringify(monitor.conditions);
// ping advanced options
bean.ping_numeric = monitor.ping_numeric;
bean.ping_count = monitor.ping_count;
bean.ping_per_request_timeout = monitor.ping_per_request_timeout;
bean.validate();
await R.store(bean);

View File

@@ -1,7 +1,11 @@
const tcpp = require("tcp-ping");
const ping = require("@louislam/ping");
const { R } = require("redbean-node");
const { log, genSecret, badgeConstants } = require("../src/util");
const {
log, genSecret, badgeConstants,
PING_PACKET_SIZE_DEFAULT, PING_GLOBAL_TIMEOUT_DEFAULT,
PING_COUNT_DEFAULT, PING_PER_REQUEST_TIMEOUT_DEFAULT
} = require("../src/util");
const passwordHash = require("./password-hash");
const { Resolver } = require("dns");
const iconv = require("iconv-lite");
@@ -118,20 +122,33 @@ exports.tcping = function (hostname, port) {
/**
* Ping the specified machine
* @param {string} hostname Hostname / address of machine
* @param {number} size Size of packet to send
* @param {string} destAddr Hostname / IP address of machine to ping
* @param {number} count Number of packets to send before stopping
* @param {string} sourceAddr Source address for sending/receiving echo requests
* @param {boolean} numeric If true, IP addresses will be output instead of symbolic hostnames
* @param {number} size Size (in bytes) of echo request to send
* @param {number} deadline Maximum time in seconds before ping stops, regardless of packets sent
* @param {number} timeout Maximum time in seconds to wait for each response
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
*/
exports.ping = async (hostname, size = 56) => {
exports.ping = async (
destAddr,
count = PING_COUNT_DEFAULT,
sourceAddr = "",
numeric = true,
size = PING_PACKET_SIZE_DEFAULT,
deadline = PING_GLOBAL_TIMEOUT_DEFAULT,
timeout = PING_PER_REQUEST_TIMEOUT_DEFAULT,
) => {
try {
return await exports.pingAsync(hostname, false, size);
return await exports.pingAsync(destAddr, false, count, sourceAddr, numeric, size, deadline, timeout);
} catch (e) {
// If the host cannot be resolved, try again with ipv6
log.debug("ping", "IPv6 error message: " + e.message);
// As node-ping does not report a specific error for this, try again if it is an empty message with ipv6 no matter what.
if (!e.message) {
return await exports.pingAsync(hostname, true, size);
return await exports.pingAsync(destAddr, true, count, sourceAddr, numeric, size, deadline, timeout);
} else {
throw e;
}
@@ -140,18 +157,35 @@ exports.ping = async (hostname, size = 56) => {
/**
* Ping the specified machine
* @param {string} hostname Hostname / address of machine to ping
* @param {string} destAddr Hostname / IP address of machine to ping
* @param {boolean} ipv6 Should IPv6 be used?
* @param {number} size Size of ping packet to send
* @param {number} count Number of packets to send before stopping
* @param {string} sourceAddr Source address for sending/receiving echo requests
* @param {boolean} numeric If true, IP addresses will be output instead of symbolic hostnames
* @param {number} size Size (in bytes) of echo request to send
* @param {number} deadline Maximum time in seconds before ping stops, regardless of packets sent
* @param {number} timeout Maximum time in seconds to wait for each response
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
*/
exports.pingAsync = function (hostname, ipv6 = false, size = 56) {
exports.pingAsync = function (
destAddr,
ipv6 = false,
count = PING_COUNT_DEFAULT,
sourceAddr = "",
numeric = true,
size = PING_PACKET_SIZE_DEFAULT,
deadline = PING_GLOBAL_TIMEOUT_DEFAULT,
timeout = PING_PER_REQUEST_TIMEOUT_DEFAULT,
) {
return new Promise((resolve, reject) => {
ping.promise.probe(hostname, {
ping.promise.probe(destAddr, {
v6: ipv6,
min_reply: 1,
deadline: 10,
min_reply: count,
sourceAddr: sourceAddr,
numeric: numeric,
packetSize: size,
deadline: deadline,
timeout: timeout
}).then((res) => {
// If ping failed, it will set field to unknown
if (res.alive) {