]> git.ipfire.org Git - ipfire-2.x.git/commitdiff
unbound-dhcp-leases-bridge: Watch unbound
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 21 Aug 2024 09:10:33 +0000 (10:10 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 21 Aug 2024 09:10:33 +0000 (10:10 +0100)
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 <michael.tremer@ipfire.org>
config/unbound/unbound-dhcp-leases-bridge

index 3972a45c62a31dc848f03f8d2970fbb6641537c3..986fae2d247151be775ce7816aa3712efb271048 100644 (file)
@@ -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")