]> git.ipfire.org Git - ipfire-2.x.git/blobdiff - config/unbound/unbound-dhcp-leases-bridge
Core Update 168: Ship fcrontab and rebuild it from scratch
[ipfire-2.x.git] / config / unbound / unbound-dhcp-leases-bridge
index a8cd837bbfbf17b08cf03cd77eb5c11f7b56233d..1446c88dfd7f14d14cd6fa2f2b1be1d1d86533cc 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 ###############################################################################
 #                                                                             #
 # IPFire.org - A linux based firewall                                         #
 import argparse
 import datetime
 import daemon
+import functools
 import ipaddress
 import logging
 import logging.handlers
 import os
 import re
 import signal
+import stat
 import subprocess
+import sys
 import tempfile
+import time
 
 import inotify.adapters
 
 LOCAL_TTL = 60
 
-def setup_logging(loglevel=logging.INFO):
-       log = logging.getLogger("dhcp")
+log = logging.getLogger("dhcp")
+log.setLevel(logging.DEBUG)
+
+def setup_logging(daemon=True, loglevel=logging.INFO):
        log.setLevel(loglevel)
 
+       # Log to syslog by default
        handler = logging.handlers.SysLogHandler(address="/dev/log", facility="daemon")
-       handler.setLevel(loglevel)
+       log.addHandler(handler)
 
+       # Format everything
        formatter = logging.Formatter("%(name)s[%(process)d]: %(message)s")
        handler.setFormatter(formatter)
 
-       log.addHandler(handler)
+       handler.setLevel(loglevel)
 
-       return log
+       # If we are running in foreground, we should write everything to the console, too
+       if not daemon:
+               handler = logging.StreamHandler()
+               log.addHandler(handler)
 
-log = logging.getLogger("dhcp")
+               handler.setLevel(loglevel)
+
+       return log
 
 def ip_address_to_reverse_pointer(address):
        parts = address.split(".")
@@ -71,6 +84,12 @@ class UnboundDHCPLeasesBridge(object):
                self.fix_leases_file = fix_leases_file
                self.hosts_file = hosts_file
 
+               self.watches = {
+                       self.leases_file     : inotify.constants.IN_MODIFY,
+                       self.fix_leases_file : 0,
+                       self.hosts_file      : 0,
+               }
+
                self.unbound = UnboundConfigWriter(unbound_leases_file)
                self.running = False
 
@@ -78,37 +97,54 @@ class UnboundDHCPLeasesBridge(object):
                log.info("Unbound DHCP Leases Bridge started on %s" % self.leases_file)
                self.running = True
 
-               # Initial setup
-               self.hosts = self.read_static_hosts()
-               self.update_dhcp_leases()
+               i = inotify.adapters.Inotify()
 
-               i = inotify.adapters.Inotify([
-                       self.leases_file,
-                       self.fix_leases_file,
-                       self.hosts_file,
-               ])
+               # Add watches for the directories of every relevant file
+               for f, mask in self.watches.items():
+                       i.add_watch(
+                               os.path.dirname(f),
+                               mask | inotify.constants.IN_CLOSE_WRITE | inotify.constants.IN_MOVED_TO,
+                       )
 
-               for event in i.event_gen():
-                       # End if we are requested to terminate
-                       if not self.running:
-                               break
+               # Enabled so that we update hosts and leases on startup
+               update_hosts = update_leases = True
 
-                       if event is None:
-                               continue
+               while self.running:
+                       log.debug("Wakeup of main loop")
+
+                       # Process the entire inotify queue and identify what we need to do
+                       for event in i.event_gen():
+                               # Nothing to do
+                               if event is None:
+                                       break
+
+                               # Decode the event
+                               header, type_names, path, filename = event
+
+                               file = os.path.join(path, filename)
 
-                       header, type_names, watch_path, filename = event
+                               log.debug("inotify event received for %s: %s", file, " ".join(type_names))
 
-                       # Update leases after leases file has been modified
-                       if "IN_MODIFY" in type_names:
-                               # Reload hosts
-                               if watch_path == self.hosts_file:
-                                       self.hosts = self.read_static_hosts()
+                               # Did the hosts file change?
+                               if self.hosts_file == file:
+                                       update_hosts = True
 
+                               # We will need to update the leases on any change
+                               update_leases = True
+
+                       # Update hosts (if needed)
+                       if update_hosts:
+                               self.hosts = self.read_static_hosts()
+
+                       # Update leases (if needed)
+                       if update_leases:
                                self.update_dhcp_leases()
 
-                       # If the file is deleted, we re-add the watcher
-                       if "IN_IGNORED" in type_names:
-                               i.add_watch(watch_path)
+                       # Reset
+                       update_hosts = update_leases = False
+
+                       # Wait a moment before we start the next iteration
+                       time.sleep(5)
 
                log.info("Unbound DHCP Leases Bridge terminated")
 
@@ -153,7 +189,7 @@ class UnboundDHCPLeasesBridge(object):
                                line = line.rstrip()
 
                                try:
-                                       enabled, ipaddr, hostname, domainname = line.split(",")
+                                       enabled, ipaddr, hostname, domainname, generateptr = line.split(",")
                                except:
                                        log.warning("Could not parse line: %s" % line)
                                        continue
@@ -177,8 +213,8 @@ class UnboundDHCPLeasesBridge(object):
 
                # Dump everything in the logs
                log.debug("Static hosts:")
-               for hostname, addresses in hosts.items():
-                       log.debug("  %-20s : %s" % (hostname, ", ".join(addresses)))
+               for name in hosts:
+                       log.debug("  %-20s : %s" % (name, ", ".join(hosts[name])))
 
                return hosts
 
@@ -401,14 +437,15 @@ class Lease(object):
 
                address = ipaddress.ip_address(self.ipaddr)
 
-               for subnet, domain in subnets.items():
+               for subnet in subnets:
                        if address in subnet:
-                               return domain
+                               return subnets[subnet]
 
                # Fall back to localdomain if no match could be found
                return "localdomain"
 
        @staticmethod
+       @functools.cache
        def read_settings(filename):
                settings = {}
 
@@ -528,6 +565,9 @@ class UnboundConfigWriter(object):
                                for rr in l.rrset:
                                        f.write("local-data: \"%s\"\n" % " ".join(rr))
 
+                       # Make file readable for everyone
+                       os.fchmod(f.fileno(), stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)
+
                os.rename(filename, self.path)
 
        def _control(self, *args):
@@ -567,23 +607,25 @@ if __name__ == "__main__":
        args = parser.parse_args()
 
        # Setup logging
-       if args.verbose == 1:
-               loglevel = logging.INFO
-       elif args.verbose >= 2:
-               loglevel = logging.DEBUG
-       else:
-               loglevel = logging.WARN
+       loglevel = logging.WARN
 
-       setup_logging(loglevel)
+       if args.verbose:
+               if args.verbose == 1:
+                       loglevel = logging.INFO
+               elif args.verbose >= 2:
+                       loglevel = logging.DEBUG
 
        bridge = UnboundDHCPLeasesBridge(args.dhcp_leases, args.fix_leases,
                args.unbound_leases, args.hosts)
 
-       ctx = daemon.DaemonContext(detach_process=args.daemon)
-       ctx.signal_map = {
-               signal.SIGHUP  : bridge.update_dhcp_leases,
-               signal.SIGTERM : bridge.terminate,
-       }
+       with daemon.DaemonContext(
+               detach_process=args.daemon,
+               stderr=None if args.daemon else sys.stderr,
+               signal_map = {
+                       signal.SIGHUP  : bridge.update_dhcp_leases,
+                       signal.SIGTERM : bridge.terminate,
+               },
+       ) as daemon:
+               setup_logging(daemon=args.daemon, loglevel=loglevel)
 
-       with ctx:
                bridge.run()