This commit is contained in:
rouja 2025-03-26 16:31:48 +00:00 committed by GitHub
commit 2a09009b15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 164 additions and 6 deletions
server
src/pages
test/backend-test

View file

@ -24,6 +24,7 @@ const { CookieJar } = require("tough-cookie");
const { HttpsCookieAgent } = require("http-cookie-agent/http");
const https = require("https");
const http = require("http");
const tls = require("tls");
const rootCertificates = rootCertificatesFingerprints();
@ -611,11 +612,6 @@ class Monitor extends BeanModel {
}
} else if (this.type === "port") {
bean.ping = await tcping(this.hostname, this.port);
bean.msg = "";
bean.status = UP;
} else if (this.type === "ping") {
bean.ping = await ping(this.hostname, this.packetSize);
bean.msg = "";

View file

@ -0,0 +1,73 @@
const { MonitorType } = require("./monitor-type");
const { UP, DOWN, log, evaluateJsonQuery } = require("../../src/util");
const { tcping, checkCertificate } = require("../util-server");
const tls = require("tls");
class TCPMonitorType extends MonitorType {
name = "port";
/**
* @inheritdoc
*/
async check(monitor, heartbeat, _server) {
try {
heartbeat.ping = await tcping(monitor.hostname, monitor.port);
heartbeat.msg = "";
heartbeat.status = UP;
} catch (error) {
heartbeat.status = DOWN;
heartbeat.msg = "Connection failed";
return;
}
if (monitor.isEnabledExpiryNotification()) {
let socket = null;
try {
const options = {
host: monitor.hostname,
port: monitor.port,
servername: monitor.hostname,
};
const tlsInfoObject = await new Promise((resolve, reject) => {
socket = tls.connect(options);
socket.on("secureConnect", () => {
try {
const info = checkCertificate(socket);
resolve(info);
} catch (error) {
reject(error);
}
});
socket.on("error", (error) => {
reject(error);
});
socket.setTimeout(10000, () => {
reject(new Error("Connection timed out"));
});
});
await monitor.handleTlsInfo(tlsInfoObject);
if (!tlsInfoObject.valid) {
heartbeat.status = DOWN;
heartbeat.msg = "Certificate is invalid";
}
} catch (error) {
heartbeat.status = DOWN;
heartbeat.msg = "Connection failed";
} finally {
if (socket && !socket.destroyed) {
socket.end();
}
}
}
}
}
module.exports = {
TCPMonitorType,
};

View file

@ -116,6 +116,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType();
UptimeKumaServer.monitorTypeList["port"] = new TCPMonitorType();
// Allow all CORS origins (polling) in development
let cors = undefined;
@ -554,4 +555,5 @@ const { MqttMonitorType } = require("./monitor-types/mqtt");
const { SNMPMonitorType } = require("./monitor-types/snmp");
const { MongodbMonitorType } = require("./monitor-types/mongodb");
const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
const { TCPMonitorType } = require("./monitor-types/tcp.js");
const Monitor = require("./model/monitor");

View file

@ -612,7 +612,7 @@
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' " class="my-3 form-check" :title="monitor.ignoreTls ? $t('ignoredTLSError') : ''">
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'port' " class="my-3 form-check" :title="monitor.ignoreTls ? $t('ignoredTLSError') : ''">
<input id="expiry-notification" v-model="monitor.expiryNotification" class="form-check-input" type="checkbox" :disabled="monitor.ignoreTls">
<label class="form-check-label" for="expiry-notification">
{{ $t("Certificate Expiry Notification") }}

View file

@ -0,0 +1,87 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { TCPMonitorType } = require("../../server/monitor-types/tcp");
const { UP, DOWN, PENDING } = require("../../src/util");
const net = require("net");
describe("TCP Monitor", () => {
async function createTCPServer(port) {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.listen(port, () => {
resolve(server);
});
server.on("error", (err) => {
reject(err);
});
});
}
test("TCP server is running", async () => {
const port = 12345;
const server = await createTCPServer(port);
try {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "localhost",
port: port,
isEnabledExpiryNotification: () => false
};
const heartbeat = {
msg: "",
status: PENDING,
};
await tcpMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "");
} finally {
server.close();
}
});
test("TCP server is not running", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "localhost",
port: 54321,
isEnabledExpiryNotification: () => false
};
const heartbeat = {
msg: "",
status: PENDING,
};
await tcpMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, DOWN);
});
test("TCP server with expired or invalid TLS certificate", async (t) => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "expired.badssl.com",
port: 443,
isEnabledExpiryNotification: () => true,
handleTlsInfo: async (tlsInfo) => {
return tlsInfo;
}
};
const heartbeat = {
msg: "",
status: PENDING,
};
await tcpMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, DOWN);
assert(["Certificate is invalid", "Connection failed"].includes(heartbeat.msg));
});
});