From a0a69e96dca2bd4cfa54da7c45489663b2d7603c Mon Sep 17 00:00:00 2001 From: Kierre Date: Sat, 8 Nov 2025 17:48:08 -0500 Subject: [PATCH] Add TCP support --- README.md | 20 ------ config-example.toml | 16 ++++- index.py | 168 ++++++++++++++++++++++++++++++++------------ templates/index.xht | 6 +- 4 files changed, 141 insertions(+), 69 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index f7acc11..0000000 --- a/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# velping -a pinger. it pings. this is made specifically for my needs, so you can open an issue for a feature request, but i likely won't accept it unless it's something i want. - -## configuration options -### frontend -- `name`: what will show as the header on the landing page. -- `port`: what port the frontend will run on. -- `pings`: how many pings to show on the frontend - -### pinging -- `allow_empty_responses`: this does NOT mean HTTP 204, but rather if the connection being suddenly closed after a request should be accepted as online. this option only exists because i run a minecraft server. -- `seconds_between_ping`: how often services should be pinged, in seconds. - -### ntfy -- `enabled`: whether or not to enable ntfy. -- `server`: the server to send notifications to. -- `topic`: the ntfy topic. - -### services -you can add services here, with the name of the key being the name of the service and the value being the URL to ping. diff --git a/config-example.toml b/config-example.toml index 43e3cdf..5404c55 100644 --- a/config-example.toml +++ b/config-example.toml @@ -4,7 +4,6 @@ name = "velping" pings = 25 [pinging] -allow_empty_responses = false seconds_between_ping = 60 [ntfy] @@ -12,5 +11,16 @@ enabled = false server = "ntfy.sh" topic = "my_velping_instance" -[services] -Google = "https://google.com/" +[services.google] +name = "Google" +type = "http" +address = "https://google.com/" +timeout = 5 + +[services.iana-whois] +name = "IANA WHOIS" +type = "tcp" +port = 43 +hostname = "whois.iana.org" +timeout = 5 +ip_version = 4 diff --git a/index.py b/index.py index dc00a18..f587a04 100644 --- a/index.py +++ b/index.py @@ -3,6 +3,7 @@ import threading import requests import tomllib import logging +import socket import time with open("config.toml", "rb") as f: @@ -14,59 +15,140 @@ logging.disable(logging.CRITICAL) uptime = {} -def http_ping(service): +def handle_service_status( + service_name: str, + down: bool = False, + error: str | None = None, +): + if ( + uptime[service_name][config["frontend"]["pings"] - 1] != "O" + and config["ntfy"]["enabled"] + and down + ): + requests.post( + url=f"https://{config['ntfy']['server']}/{config['ntfy']['topic']}", + data=f"Service {service_name} is offline", + ) + elif ( + uptime[service_name][config["frontend"]["pings"] - 1] != "I" + and config["ntfy"]["enabled"] + and not down + ): + requests.post( + url=f"https://{config['ntfy']['server']}/{config['ntfy']['topic']}", + data=f"Service {service_name} is online", + ) + + uptime[service_name].pop(0) + if down: + uptime[service_name].append("O") + print(f"[E] An error happened while pinging for {service_name}: {error}") + else: + uptime[service_name].append("I") + print(f"[I] Pinging {service_name} succeeded!") + + +def tcp_ping(service): + ip = service["ip_version"] + name = service["name"] + host = service["hostname"] + port = service["port"] + timeout = service["timeout"] + + if ip == 4: + socket_type = socket.AF_INET + elif ip == 6: + socket_type = socket.AF_INET6 + else: + print(f"[E] IP version for {name} is not 4 or 6") + return + while True: - print(f"[I] Pinging {service}") + print(f"[I] Pinging {name}") + try: - resp = requests.get(config["services"][service]) - resp.raise_for_status() + with socket.socket(family=socket_type, type=socket.SOCK_STREAM) as sock: + sock.settimeout(timeout) + sock.connect((host, port)) - if ( - uptime[service][config["frontend"]["pings"] - 1] != "I" - and config["ntfy"]["enabled"] - ): - requests.post( - f"https://{config['ntfy']['server']}/{config['ntfy']['topic']}", - data=f"Service {service} is online", - ) + handle_service_status( + service_name=name, + down=False, + ) - uptime[service].pop(0) - uptime[service].append("I") - print(f"[I] Pinging {service} worked!") - except Exception as e: - if ( - "Remote end closed connection without response" in str(e) - and config["pinging"]["allow_empty_responses"] - ): - uptime[service].pop(0) - uptime[service].append("I") - print(f"[I] Pinging {service} worked!") - else: - if ( - uptime[service][config["frontend"]["pings"] - 1] != "O" - and config["ntfy"]["enabled"] - ): - requests.post( - f"https://{config['ntfy']['server']}/{config['ntfy']['topic']}", - data=f"Service {service} is offline", - ) + except socket.timeout: + handle_service_status( + service_name=name, + down=True, + error=f"[E] Connection to {host}:{port} timed out", + ) - uptime[service].pop(0) - uptime[service].append("O") - print(f"[E] An error happened while pinging for {service}: {e}") + except Exception as err: + handle_service_status( + service_name=name, + down=True, + error=f"[E] Connection error: {err}", + ) time.sleep(config["pinging"]["seconds_between_ping"]) -for service in config["services"]: - uptime[service] = [] - for _ in range(config["frontend"]["pings"]): - uptime[service].append("?") +def http_ping(service): + name = service["name"] + address = service["address"] + timeout = service["timeout"] - threading.Thread( - target=http_ping, - args=(service,), - ).start() + while True: + print(f"[I] Pinging {service['name']}") + try: + resp = requests.get( + url=address, + timeout=timeout, + ) + resp.raise_for_status() + + handle_service_status( + service_name=name, + down=False, + ) + except Exception as e: + handle_service_status( + service_name=name, + down=True, + error=str(e), + ) + + time.sleep(config["pinging"]["seconds_between_ping"]) + + +for service_name in config["services"]: + service = config["services"][service_name] + if "type" in service and "name" in service and "timeout" in service: + uptime[service["name"]] = [] + for _ in range(config["frontend"]["pings"]): + uptime[service["name"]].append("?") + + if service["type"] == "http" and "address" in service: + threading.Thread( + target=http_ping, + args=(service,), + ).start() + elif ( + service["type"] == "tcp" + and "port" in service + and "hostname" in service + and "ip_version" in service + ): + threading.Thread( + target=tcp_ping, + args=(service,), + ).start() + else: + print( + f"[W] Service {service['name']} is missing required fields, ignoring." + ) + else: + print("[W] Invalid service entry found, ignoring.") @app.errorhandler(404) diff --git a/templates/index.xht b/templates/index.xht index 596d4e6..9ffcb38 100644 --- a/templates/index.xht +++ b/templates/index.xht @@ -12,11 +12,11 @@

{{ config['frontend']['name'] }}

- {% for service in services %} + {% for service_name in services %}
-

{{ service }}

+

{{ services[service_name]['name'] }}

- {% for ping in uptime[service] %} + {% for ping in uptime[services[service_name]['name']] %} {% if ping == "I" %}

{% elif ping == "O" %}