From: Michael Tremer Date: Wed, 21 Aug 2024 09:10:33 +0000 (+0100) Subject: unbound-dhcp-leases-bridge: Watch unbound X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8ead2ddf3d0f1521c2a99b509373615f77a754a0;p=people%2Fmfischer%2Fipfire-2.x.git unbound-dhcp-leases-bridge: Watch unbound This patch adds a watcher thread which monitors if Unbound is still alive. If not, it will wait until Unbound comes back, rewrite the leases file and reload Unbound to get it back into sync. Afterwards Unbound will receive updates as usual. Signed-off-by: Michael Tremer --- diff --git a/config/unbound/unbound-dhcp-leases-bridge b/config/unbound/unbound-dhcp-leases-bridge index 3972a45c6..986fae2d2 100644 --- a/config/unbound/unbound-dhcp-leases-bridge +++ b/config/unbound/unbound-dhcp-leases-bridge @@ -83,10 +83,10 @@ class UnboundDHCPLeasesBridge(object): # Initialize the worker self.worker = Worker(self.queue, callback=self._handle_message) - self.unbound = UnboundConfigWriter(unbound_leases_file) + # Initialize the watcher + self.watcher = Watcher(reload=self.reload) - # Load all required data - self.reload() + self.unbound = UnboundConfigWriter(unbound_leases_file) def run(self): log.info("Unbound DHCP Leases Bridge started on %s" % self.leases_file) @@ -94,6 +94,9 @@ class UnboundDHCPLeasesBridge(object): # Launch the worker self.worker.start() + # Launch the watcher + self.watcher.start() + # Open the server socket self.socket = self._open_socket(self.socket_path) @@ -132,7 +135,13 @@ class UnboundDHCPLeasesBridge(object): # Terminate the worker self.queue.put(None) + + # Terminate the watcher + self.watcher.terminate() + + # Wait for the worker and watcher to finish self.worker.join() + self.watcher.join() log.info("Unbound DHCP Leases Bridge terminated") @@ -359,6 +368,84 @@ class UnboundDHCPLeasesBridge(object): self.socket.close() +class Watcher(threading.Thread): + """ + Watches if Unbound is still running. + """ + def __init__(self, reload, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.reload = reload + + # Set to true if this thread should be terminated + self._terminated = threading.Event() + + def run(self): + log.debug("Watcher launched") + + pidfd = None + + while True: + # One iteration takes 30 seconds unless we don't know the process + # when we try to find it once a second. + if self._terminated.wait(30 if pidfd else 1): + break + + # Fetch a PIDFD for Unbound + if pidfd is None: + pidfd = self._get_pidfd() + + # If we could not acquire a PIDFD, we will try again soon... + if not pidfd: + log.warning("Cannot find Unbound...") + continue + + # Since Unbound has been restarted, we need to reload it all... + self.reload() + + log.debug("Checking if Unbound is still alive...") + + # Send the process a signal + try: + signal.pidfd_send_signal(pidfd, signal.SIG_DFL) + + # If the process has died, we land here and will have to wait until Unbound + # has come back and reload it... + except ProcessLookupError as e: + log.error("Unbound has died") + + # Reset the PIDFD + pidfd = None + + else: + log.debug("Unbound is alive") + + log.debug("Watcher terminated") + + def terminate(self): + """ + Called to signal this thread to terminate + """ + self._terminated.set() + + def _get_pidfd(self): + """ + Returns a PIDFD for unbound if it is running, otherwise None. + """ + # Try to find the PID + pid = pidof("unbound") + + if pid: + log.debug("Unbound is running as PID %s" % pid) + + # Open a PIDFD + pidfd = os.pidfd_open(pid) + + log.debug("Acquired PIDFD %s for PID %s" % (pidfd, pid)) + + return pidfd + + class Worker(threading.Thread): """ The worker is launched in a separate thread @@ -727,6 +814,28 @@ class UnboundConfigWriter(object): self._control("local_data_remove", name) +def pidof(program): + """ + Returns the first PID of the given program. + """ + try: + output = subprocess.check_output(["pidof", program]) + except subprocess.CalledProcessError as e: + return + + # Convert to string + output = output.decode() + + # Return the first PID + for pid in output.split(): + try: + pid = int(pid) + except ValueError: + continue + + return pid + + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Bridge for DHCP Leases and Unbound DNS")