#!/usr/bin/env python3 """Asynchronous network monitoring script, reports current state every 5 seconds.""" from ipaddress import ( IPv4Address as IPAddress, IPv4Network as IPNetwork, IPv4Interface as IPInterface, AddressValueError, ) from dbus_fast.aio import MessageBus from dbus_fast import Variant, BusType import re import asyncio from subprocess import PIPE import functools MONITOR_IPS = {"ezrinet": IPAddress("10.242.3.1"), "internet": IPAddress("1.1.1.1")} INTERFACE_IGNORE_PATTERNS = [ re.compile(r"^vb-"), re.compile(r"^vz-"), re.compile(r"^virbr[0-9]+"), re.compile(r"^lo$"), ] def timer(interval: float, initial_delay: float = 0): """Decorate a function or coroutine to run it on the given interval.""" def decorator(func): async def do_interval(): try: await asyncio.sleep(initial_delay) while True: await func() await asyncio.sleep(interval) except asyncio.CancelledError: return def start(): loop = asyncio.get_running_loop() if "_timer_task" in func.__dict__: raise Exception("Timer is already running.") func.__dict__["_timer_task"] = loop.create_task(do_interval()) func.__dict__["start"] = start return func return decorator async def ping(address: IPAddress) -> tuple[bool, float | None]: """Ping a host and return a tuple of [success, ping time].""" proc = await asyncio.create_subprocess_exec( "ping", "-c1", "-w1", "-n", str(address), stdout=PIPE ) stdout, stderr = await proc.communicate() _match = re.search( r"icmp_seq=1 ttl=[0-9]+ time=([0-9]+\.?[0-9]*) ms", stdout.decode("utf-8") ) if _match is None: return (False, None) else: return (True, float(_match.group(1))) @timer(5) async def ping_checks(): """Recurring ping checks for configured IP addresses."""