This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "IPFire 2.x development tree".
The branch, next has been updated via 25365003f603f0ad5e8800a7e01a11cc1c87a317 (commit) via b058000c23c9b0e3c81ec1bbd825dba791e13b4c (commit) via adf8a243af6ccd6dbe95e4034f5913f0dfe562a7 (commit) via c2a389e9d4fd731357b4f7400d8d7ef4f313ea4b (commit) via 1908e3d0708d6298a5424ec6402c6562a75036d8 (commit) via ae49f553597b9fe70f763cec8641dbaec5206859 (commit) via dac4464e91f5d83ae87ece96974656319f98dafa (commit) via b79c0fc4ff9aa00f3cfd7e5230b63c6c22938e5b (commit) via 8628d3e8d06ce2e4d1b8cc3aa0f3826108e555e7 (commit) via 2e4432a8f5c953092f346f0ea9ec468a4c5fd8bb (commit) via c2761068cce358a3b94e75b7e7a99677df3f0daf (commit) via 9ff53d57863e26b8dfa2bef0f189f68931d91efa (commit) via 8733b313deb490d14314ebcebb8fc2c30dbbfebb (commit) via 92e8358d46aa5316710cd832a95dfa6166ad7a54 (commit) via 0e1ae247e7c074034265d9fd0d1b8b48f02d5267 (commit) via edcea3e1c9c484097060831c99ac2445b457491c (commit) via 038c9db2bda048ff184e1fb9b4e2ef9b0ee1ff7b (commit) via 084795163e2aa51a2b33f0cb0808c793592e7181 (commit) via d9348a16f14181e351990617a6d79ad40bab35d8 (commit) via e4cc1eefd51c7b6cc9515f4fedf7c77e7ec80d7c (commit) via 7397809eb4cda1c51af68343d7d1efc414356e07 (commit) via 43f001cb5b58dca18090f32eb1eae63bb001a329 (commit) via bf352bbbcb0362bd73e8bac6408416d16cf32527 (commit) via 193638f078a398dc7fa67fef78d63eb2a16fcde8 (commit) via 716c69eee473e16ae0b9387c66f0c000e9950268 (commit) via b2787f168bcd3b58e00badb950d1ad8cf9e22b62 (commit) via 130606f039599c8906c8a59ccd516e62eca92605 (commit) via 72f4fccbe43a0b6cbc8b78f6e6a91d205d6d8687 (commit) from 8f114a01c3286dbcc28254de37bf76e7a326f2e1 (commit)
Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below.
- Log ----------------------------------------------------------------- commit 25365003f603f0ad5e8800a7e01a11cc1c87a317 Author: Michael Tremer michael.tremer@ipfire.org Date: Tue Aug 13 09:32:07 2024 +0000
core188: Ship DHCP/Unbound Bridge socket implementation
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit b058000c23c9b0e3c81ec1bbd825dba791e13b4c Merge: 8f114a01c3 adf8a243af Author: Michael Tremer michael.tremer@ipfire.org Date: Tue Aug 13 09:27:25 2024 +0000
Merge remote-tracking branch 'ms/unbound-socket' into next
commit adf8a243af6ccd6dbe95e4034f5913f0dfe562a7 Author: Michael Tremer michael.tremer@ipfire.org Date: Mon May 13 14:42:26 2024 +0000
dhcp: Explicitely compile with support for execute()
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit c2a389e9d4fd731357b4f7400d8d7ef4f313ea4b Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:53:22 2024 +0100
unbound-dhcp-leases-bridge: Remove unused functions and module imports
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 1908e3d0708d6298a5424ec6402c6562a75036d8 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:51:41 2024 +0100
unbound-dhcp-leases-bridge: Make expiry check work for stub leases
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit ae49f553597b9fe70f763cec8641dbaec5206859 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:51:26 2024 +0100
unbound-dhcp-leases-bridge: Remove unused code
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit dac4464e91f5d83ae87ece96974656319f98dafa Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:47:44 2024 +0100
unbound-dhcp-leases-bridge: Don't parse any inactive leases
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit b79c0fc4ff9aa00f3cfd7e5230b63c6c22938e5b Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:46:45 2024 +0100
unbound-dhcp-leases-bridge: Drop parsing MAC addresses
We will represent the current state in DNS and we won't filter out anything that we think might be no longer valid.
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 8628d3e8d06ce2e4d1b8cc3aa0f3826108e555e7 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:40:27 2024 +0100
unbound-dhcp-leases-bridge: Remove fixed leases cache
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 2e4432a8f5c953092f346f0ea9ec468a4c5fd8bb Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:36:40 2024 +0100
unbound-dhcp-leases-bridge: Include traceback if the worker callback fails
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit c2761068cce358a3b94e75b7e7a99677df3f0daf Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:31:25 2024 +0100
unbound-dhcp-leases-bridge: Log if a lease is not being added
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 9ff53d57863e26b8dfa2bef0f189f68931d91efa Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:28:58 2024 +0100
unbound-dhcp-leases-bridge: Remove leases to keep the store up to date
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 8733b313deb490d14314ebcebb8fc2c30dbbfebb Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:25:13 2024 +0100
unbound-dhcp-leases-bridge: Skip updates if not necessary
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 92e8358d46aa5316710cd832a95dfa6166ad7a54 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:20:30 2024 +0100
unbound-dhcp-leases-bridge: Find existing leases to remove all data
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 0e1ae247e7c074034265d9fd0d1b8b48f02d5267 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:16:13 2024 +0100
unbound-dhcp-leases-bridge: Store leases in a globally accessible set()
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit edcea3e1c9c484097060831c99ac2445b457491c Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:07:23 2024 +0100
unbound-dhcp-leases-bridge: Make Leases hashable and equal by IP address
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 038c9db2bda048ff184e1fb9b4e2ef9b0ee1ff7b Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:04:43 2024 +0100
unbound-dhcp-leases-bridge: Use IPv4Address to store IP addresses
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 084795163e2aa51a2b33f0cb0808c793592e7181 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 17:01:50 2024 +0100
unbound-dhcp-leases-bridge: Implement a worker thread to handle all events
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit d9348a16f14181e351990617a6d79ad40bab35d8 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 16:32:07 2024 +0100
unbound-dhcp-leases-bridge: Store all messages in a queue
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit e4cc1eefd51c7b6cc9515f4fedf7c77e7ec80d7c Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 16:07:05 2024 +0100
dhcp.cgi: Call the unbound-dhcp-leases-client for all events
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 7397809eb4cda1c51af68343d7d1efc414356e07 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 16:06:23 2024 +0100
unbound-dhcp-leases-client: A new script to send events to the bridge
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 43f001cb5b58dca18090f32eb1eae63bb001a329 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 14:50:30 2024 +0100
unbound-dhcp-leases-bridge: Decode any incoming messages
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit bf352bbbcb0362bd73e8bac6408416d16cf32527 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 14:31:53 2024 +0100
unbound-dhcp-leases-bridge: Remove running indicator
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 193638f078a398dc7fa67fef78d63eb2a16fcde8 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 14:29:31 2024 +0100
unbound-dhcp-leases-bridge: Reload on SIGHUP
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 716c69eee473e16ae0b9387c66f0c000e9950268 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 14:27:10 2024 +0100
unbound-dhcp-leases-bridge: No longer listen to any changed files
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit b2787f168bcd3b58e00badb950d1ad8cf9e22b62 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 14:25:53 2024 +0100
unbound-dhcp-leases-bridge: Initialize at startup
When the process starts, we will now load all static hosts and leases and reload Unbound to have a defined state to start with.
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 130606f039599c8906c8a59ccd516e62eca92605 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 14:19:05 2024 +0100
unbound-dhcp-leases-bridge: Open a socket to listen for events
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit 72f4fccbe43a0b6cbc8b78f6e6a91d205d6d8687 Author: Michael Tremer michael.tremer@ipfire.org Date: Fri May 10 14:18:12 2024 +0100
unbound-dhcp-leases-bridge: Terminate on SIGINT
Signed-off-by: Michael Tremer michael.tremer@ipfire.org
-----------------------------------------------------------------------
Summary of changes: config/rootfiles/common/unbound | 1 + .../{oldcore/111 => core/188}/filelists/dhcp | 0 config/rootfiles/core/188/filelists/files | 1 + .../{oldcore/106 => core/188}/filelists/unbound | 0 config/unbound/unbound-dhcp-leases-bridge | 452 ++++++++++++++------- .../unbound-dhcp-leases-client} | 67 ++- html/cgi-bin/dhcp.cgi | 43 ++ lfs/dhcp | 1 + lfs/unbound | 2 + 9 files changed, 401 insertions(+), 166 deletions(-) copy config/rootfiles/{oldcore/111 => core/188}/filelists/dhcp (100%) copy config/rootfiles/{oldcore/106 => core/188}/filelists/unbound (100%) copy config/{dma/dma-cleanup-spool => unbound/unbound-dhcp-leases-client} (63%)
Difference in files: diff --git a/config/rootfiles/common/unbound b/config/rootfiles/common/unbound index 79612c634..40997f96b 100644 --- a/config/rootfiles/common/unbound +++ b/config/rootfiles/common/unbound @@ -19,6 +19,7 @@ usr/sbin/unbound-checkconf usr/sbin/unbound-control usr/sbin/unbound-control-setup usr/sbin/unbound-dhcp-leases-bridge +usr/sbin/unbound-dhcp-leases-client usr/sbin/unbound-host #usr/share/man/man1/unbound-host.1 #usr/share/man/man3/libunbound.3 diff --git a/config/rootfiles/core/188/filelists/dhcp b/config/rootfiles/core/188/filelists/dhcp new file mode 120000 index 000000000..32d8da443 --- /dev/null +++ b/config/rootfiles/core/188/filelists/dhcp @@ -0,0 +1 @@ +../../../common/dhcp \ No newline at end of file diff --git a/config/rootfiles/core/188/filelists/files b/config/rootfiles/core/188/filelists/files index f8447de55..e9b52cc4f 100644 --- a/config/rootfiles/core/188/filelists/files +++ b/config/rootfiles/core/188/filelists/files @@ -3,6 +3,7 @@ etc/rc.d/init.d/functions etc/rc.d/init.d/networking/functions.network etc/rc.d/init.d/networking/red lib/udev/network-hotplug-vlan +srv/web/ipfire/cgi-bin/dhcp.cgi srv/web/ipfire/cgi-bin/services.cgi srv/web/ipfire/cgi-bin/system.cgi srv/web/ipfire/cgi-bin/vulnerabilities.cgi diff --git a/config/rootfiles/core/188/filelists/unbound b/config/rootfiles/core/188/filelists/unbound new file mode 120000 index 000000000..66adf0924 --- /dev/null +++ b/config/rootfiles/core/188/filelists/unbound @@ -0,0 +1 @@ +../../../common/unbound \ No newline at end of file diff --git a/config/unbound/unbound-dhcp-leases-bridge b/config/unbound/unbound-dhcp-leases-bridge index 7f89f620a..3972a45c6 100644 --- a/config/unbound/unbound-dhcp-leases-bridge +++ b/config/unbound/unbound-dhcp-leases-bridge @@ -28,15 +28,15 @@ import ipaddress import logging import logging.handlers import os +import queue import re import signal +import socket import stat import subprocess import sys import tempfile -import time - -import inotify.adapters +import threading
LOCAL_TTL = 60
@@ -65,121 +65,248 @@ def setup_logging(daemon=True, loglevel=logging.INFO):
return log
-def ip_address_to_reverse_pointer(address): - parts = address.split(".") - parts.reverse() - - return "%s.in-addr.arpa" % ".".join(parts) - -def reverse_pointer_to_ip_address(rr): - parts = rr.split(".") - - # Only take IP address part - parts = reversed(parts[0:4]) - - return ".".join(parts) - class UnboundDHCPLeasesBridge(object): - def __init__(self, dhcp_leases_file, fix_leases_file, unbound_leases_file, hosts_file): + def __init__(self, dhcp_leases_file, fix_leases_file, unbound_leases_file, hosts_file, socket_path): self.leases_file = dhcp_leases_file self.fix_leases_file = fix_leases_file self.hosts_file = hosts_file + self.socket_path = socket_path + + self.socket = None
- self.watches = { - self.leases_file : inotify.constants.IN_MODIFY, - self.fix_leases_file : 0, - self.hosts_file : 0, - } + # Store all known leases + self.leases = set() + + # Create a queue for all received events + self.queue = queue.Queue() + + # Initialize the worker + self.worker = Worker(self.queue, callback=self._handle_message)
self.unbound = UnboundConfigWriter(unbound_leases_file) - self.running = False + + # Load all required data + self.reload()
def run(self): log.info("Unbound DHCP Leases Bridge started on %s" % self.leases_file) - self.running = True
- i = inotify.adapters.Inotify() + # Launch the worker + self.worker.start() + + # Open the server socket + self.socket = self._open_socket(self.socket_path) + + while True: + # Accept any incoming connections + try: + conn, peer = self.socket.accept() + except OSError as e: + break + + try: + # Receive what the client is sending + data, ancillary_data, flags, address = conn.recvmsg(4096)
- # 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, - ) + # Log that we have received some data + log.debug("Received message of %s byte(s)" % len(data))
- # Enabled so that we update hosts and leases on startup - update_hosts = update_leases = True + # Decode the data + message = self._decode_message(data)
- while self.running: - log.debug("Wakeup of main loop") + # Add the message to the queue + self.queue.put(message)
- # 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 + conn.send(b"OK\n")
- # Decode the event - header, type_names, path, filename = event + # Send ERROR to the client if something went wrong + except Exception as e: + log.error("Could not handle message: %s" % e)
- file = os.path.join(path, filename) + conn.send(b"ERROR\n") + continue
- log.debug("inotify event received for %s: %s", file, " ".join(type_names)) + # Close the connection + finally: + conn.close()
- # Did the hosts file change? - if self.hosts_file == file: - update_hosts = True + # Terminate the worker + self.queue.put(None) + self.worker.join()
- # We will need to update the leases on any change - update_leases = True + log.info("Unbound DHCP Leases Bridge terminated")
- # Update hosts (if needed) - if update_hosts: - self.hosts = self.read_static_hosts() + def _open_socket(self, path): + # Allocate a new socket + s = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM)
- # Update leases (if needed) - if update_leases: - self.update_dhcp_leases() + # Unlink any old sockets + try: + os.unlink(path) + except FileNotFoundError as e: + pass
- # Reset - update_hosts = update_leases = False + # Bind the socket + try: + s.bind(self.socket_path) + except OSError as e: + log.error("Could not open socket at %s: %s" % (path, e))
- # Wait a moment before we start the next iteration - time.sleep(5) + raise SystemExit(1) from e
- log.info("Unbound DHCP Leases Bridge terminated") + # Listen + s.listen(128)
- def update_dhcp_leases(self): - leases = [] + return s
- for lease in DHCPLeases(self.leases_file): - # Don't bother with any leases that don't have a hostname - if not lease.fqdn: + def _decode_message(self, data): + message = {} + + for line in data.splitlines(): + # Skip empty lines + if not line: continue
- leases.append(lease) + # Try to decode the line + try: + line = line.decode() + except UnicodeError as e: + log.error("Could not decode %r: %s" % (line, e))
- for lease in FixLeases(self.fix_leases_file): - leases.append(lease) + raise e
- # Skip any leases that also are a static host - leases = [l for l in leases if not l.fqdn in self.hosts] + # Split the line + key, _, value = line.partition("=")
- # Remove any inactive or expired leases - leases = [l for l in leases if l.active and not l.expired] + # Skip the line if it does not have a value + if not _: + raise ValueError("No value given") + + # Store the attributes + message[key] = value + + return message + + def _handle_message(self, message): + log.debug("Handling message:") + for key in message: + log.debug(" %-20s = %s" % (key, message[key])) + + # Extract the event type + event = message.get("EVENT") + + # Check if event is set + if not event: + raise ValueError("The message does not have EVENT set") + + # COMMIT + elif event == "commit": + address = message.get("ADDRESS") + name = message.get("NAME") + + # Find the old lease + old_lease = self._find_lease(address) + + # Create a new lease + lease = Lease(address, { + "client-hostname" : name, + }) + self._add_lease(lease) + + # Can we skip the update? + if old_lease: + if lease.rrset == old_lease.rrset: + log.debug("Won't update %s as nothing has changed" % lease) + return + + # Remove the old lease first + self.unbound.remove_lease(old_lease) + self._remove_lease(old_lease) + + # Apply the lease + self.unbound.apply_lease(lease) + + # RELEASE/EXPIRY + elif event in ("release", "expiry"): + address = message.get("ADDRESS") + + # Find the lease + lease = self._find_lease(address) + + if not lease: + log.warning("Could not find lease for %s" % address) + return + + # Remove the lease + self.unbound.remove_lease(lease) + self._remove_lease(lease) + + # Raise an error if the event is not supported + else: + raise ValueError("Unsupported event: %s" % event) + + def update_dhcp_leases(self): + # Drop all known leases + self.leases.clear() + + # Add all dynamic leases + for lease in DHCPLeases(self.leases_file): + self._add_lease(lease) + + # Add all static leases + for lease in FixLeases(self.fix_leases_file): + self._add_lease(lease)
# Dump leases - if leases: + if self.leases: log.debug("DHCP Leases:") - for lease in leases: + for lease in self.leases: log.debug(" %s:" % lease.fqdn) - log.debug(" State: %s" % lease.binding_state) log.debug(" Start: %s" % lease.time_starts) log.debug(" End : %s" % lease.time_ends) - if lease.expired: + if lease.has_expired(): log.debug(" Expired")
- self.unbound.update_dhcp_leases(leases) + self.unbound.update_dhcp_leases(self.leases) + + def _add_lease(self, lease): + # Skip leases without an FQDN + if not lease.fqdn: + log.debug("Skipping lease without an FQDN: %s" % lease) + return + + # Skip any leases that also are a static host + elif lease.fqdn in self.hosts: + log.debug("Skipping lease for which a static host exists: %s" % lease) + return + + # Don't add expired leases + elif lease.has_expired(): + log.debug("Skipping expired lease: %s" % lease) + return + + # Remove any previous leases + self._remove_lease(lease) + + # Store the lease + self.leases.add(lease) + + def _find_lease(self, ipaddr): + """ + Returns the lease with the specified IP address + """ + if not isinstance(ipaddr, ipaddress.IPv4Address): + ipaddr = ipaddress.IPv4Address(ipaddr) + + for lease in self.leases: + if lease.ipaddr == ipaddr: + return lease + + def _remove_lease(self, lease): + try: + self.leases.remove(lease) + except KeyError: + pass
def read_static_hosts(self): log.info("Reading static hosts from %s" % self.hosts_file) @@ -219,8 +346,47 @@ class UnboundDHCPLeasesBridge(object):
return hosts
- def terminate(self): - self.running = False + def reload(self, *args, **kwargs): + # Read all static hosts + self.hosts = self.read_static_hosts() + + # Unconditionally update all leases and reload Unbound + self.update_dhcp_leases() + + def terminate(self, *args, **kwargs): + # Close the socket + if self.socket: + self.socket.close() + + +class Worker(threading.Thread): + """ + The worker is launched in a separate thread + which allows us to perform some tasks asynchronously. + """ + def __init__(self, queue, callback): + super().__init__() + + self.queue = queue + self.callback = callback + + def run(self): + log.debug("Worker %s launched" % self.native_id) + + while True: + message = self.queue.get() + + # If the message is None, we have to quit + if message is None: + break + + # Call the callback + try: + self.callback(message) + except Exception as e: + log.error("Callback failed: %s" % e, exc_info=True) + + log.debug("Worker %s terminated" % self.native_id)
class DHCPLeases(object): @@ -255,18 +421,12 @@ class DHCPLeases(object): if not "hardware" in properties: continue
- lease = Lease(ipaddr, properties) - - # Check if a lease for this Ethernet address already - # exists in the list of known leases. If so replace - # if with the most recent lease - for i, l in enumerate(leases): - if l.ipaddr == lease.ipaddr: - leases[i] = max(lease, l) - break + # Skip inactive leases + elif not properties.get("binding", "state active"): + continue
- else: - leases.append(lease) + lease = Lease(ipaddr, properties) + leases.append(lease)
return leases
@@ -300,12 +460,10 @@ class DHCPLeases(object):
class FixLeases(object): - cache = {} - def __init__(self, path): self.path = path
- self._leases = self.cache[self.path] = self._parse() + self._leases = self._parse()
def __iter__(self): return iter(self._leases) @@ -313,9 +471,10 @@ class FixLeases(object): def _parse(self): log.info("Reading fix leases from %s" % self.path)
- leases = [] now = datetime.datetime.utcnow()
+ leases = [] + with open(self.path) as f: for line in f.readlines(): line = line.rstrip() @@ -333,72 +492,42 @@ class FixLeases(object): l = Lease(ipaddr, { "binding" : "state active", "client-hostname" : hostname, - "hardware" : "ethernet %s" % hwaddr, "starts" : now.strftime("%w %Y/%m/%d %H:%M:%S"), "ends" : "never", }) leases.append(l)
- # Try finding any deleted leases - for lease in self.cache.get(self.path, []): - if lease in leases: - continue - - # Free the deleted lease - lease.free() - leases.append(lease) - return leases
class Lease(object): def __init__(self, ipaddr, properties): + if not isinstance(ipaddr, ipaddress.IPv4Address): + ipaddr = ipaddress.IPv4Address(ipaddr) + self.ipaddr = ipaddr self._properties = properties
def __repr__(self): - return "<%s %s for %s (%s)>" % (self.__class__.__name__, - self.ipaddr, self.hwaddr, self.hostname) + return "<%s for %s (%s)>" % (self.__class__.__name__, self.ipaddr, self.hostname)
def __eq__(self, other): - return self.ipaddr == other.ipaddr and self.hwaddr == other.hwaddr - - def __gt__(self, other): - if not self.ipaddr == other.ipaddr: - return - - if not self.hwaddr == other.hwaddr: - return - - return self.time_starts > other.time_starts - - @property - def binding_state(self): - state = self._properties.get("binding") - - if state: - state = state.split(" ", 1) - return state[1] - - def free(self): - self._properties.update({ - "binding" : "state free", - }) + if isinstance(other, self.__class__): + return self.ipaddr == other.ipaddr
- @property - def active(self): - return self.binding_state == "active" + return NotImplemented
- @property - def hwaddr(self): - hardware = self._properties.get("hardware") + def __gt__(self, other): + if isinstance(other, self.__class__): + if not self.ipaddr == other.ipaddr: + return NotImplemented
- if not hardware: - return + return self.time_starts > other.time_starts
- ethernet, address = hardware.split(" ", 1) + return NotImplemented
- return address + def __hash__(self): + return hash(self.ipaddr)
@property def hostname(self): @@ -488,8 +617,10 @@ class Lease(object):
return self._parse_time(ends)
- @property - def expired(self): + def has_expired(self): + if not self.time_starts: + return + if not self.time_ends: return self.time_starts > datetime.datetime.utcnow()
@@ -503,10 +634,10 @@ class Lease(object):
return [ # Forward record - (self.fqdn, "%s" % LOCAL_TTL, "IN A", self.ipaddr), + (self.fqdn, "%s" % LOCAL_TTL, "IN A", "%s" % self.ipaddr),
# Reverse record - (ip_address_to_reverse_pointer(self.ipaddr), "%s" % LOCAL_TTL, + (self.ipaddr.reverse_pointer, "%s" % LOCAL_TTL, "IN PTR", self.fqdn), ]
@@ -560,6 +691,9 @@ class UnboundConfigWriter(object): command = ["unbound-control"] command.extend(args)
+ # Log what we are doing + log.debug("Running %s" % " ".join(command)) + try: subprocess.check_output(command)
@@ -570,6 +704,28 @@ class UnboundConfigWriter(object):
raise e
+ def apply_lease(self, lease): + """ + This method takes a lease and updates Unbound at runtime. + """ + log.debug("Applying lease %s" % lease) + + for rr in lease.rrset: + log.debug("Adding new record %s" % " ".join(rr)) + + self._control("local_data", *rr) + + def remove_lease(self, lease): + """ + This method takes a lease and removes it from Unbound at runtime. + """ + log.debug("Removing lease %s" % lease) + + for name, ttl, type, content in lease.rrset: + log.debug("Removing records for %s" % name) + + self._control("local_data_remove", name) +
if __name__ == "__main__": parser = argparse.ArgumentParser(description="Bridge for DHCP Leases and Unbound DNS") @@ -588,6 +744,9 @@ if __name__ == "__main__": metavar="PATH", help="Path to the fix leases file") parser.add_argument("--hosts", default="/var/ipfire/main/hosts", metavar="PATH", help="Path to static hosts file") + parser.add_argument("--socket-path", default="/var/run/unbound-dhcp-leases-bridge.sock", + metavar="PATH", help="Socket Path", + )
# Parse command line arguments args = parser.parse_args() @@ -602,13 +761,14 @@ if __name__ == "__main__": loglevel = logging.DEBUG
bridge = UnboundDHCPLeasesBridge(args.dhcp_leases, args.fix_leases, - args.unbound_leases, args.hosts) + args.unbound_leases, args.hosts, socket_path=args.socket_path)
with daemon.DaemonContext( detach_process=args.daemon, stderr=None if args.daemon else sys.stderr, signal_map = { - signal.SIGHUP : bridge.update_dhcp_leases, + signal.SIGHUP : bridge.reload, + signal.SIGINT : bridge.terminate, signal.SIGTERM : bridge.terminate, }, ) as daemon: diff --git a/config/unbound/unbound-dhcp-leases-client b/config/unbound/unbound-dhcp-leases-client new file mode 100644 index 000000000..b1b6291d9 --- /dev/null +++ b/config/unbound/unbound-dhcp-leases-client @@ -0,0 +1,75 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2016 Michael Tremer # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +SOCKET="/var/run/unbound-dhcp-leases-bridge.sock" + +main() { + local event="${1}" + shift + + # Check if we have received an event + if [ -z "${event}" ]; then + echo "${0}: Missing event" >&2 + return 2 + fi + + # Check if the socket exists + if [ ! -S "${SOCKET}" ]; then + echo "${0}: ${SOCKET} does not exist" >&2 + return 1 + fi + + # Connect to the socket + coproc NC { nc -U "${SOCKET}"; } + + local arg + local response + + # Send the message + { + # Send the event + echo "EVENT=${event}" + + # Send all arguments + for arg in $@; do + echo "${arg}" + done + } >&"${NC[1]}" + + # Close the input part of the connection + exec {NC[1]}>&- + + # Capture the response + read response <&"${NC[0]}" + + case "${response}" in + OK) + return 0 + ;; + + *) + echo "${response}" >&2 + return 1 + ;; + esac +} + +main "$@" || exit $? diff --git a/html/cgi-bin/dhcp.cgi b/html/cgi-bin/dhcp.cgi index be00f199a..83cd80965 100644 --- a/html/cgi-bin/dhcp.cgi +++ b/html/cgi-bin/dhcp.cgi @@ -1374,6 +1374,49 @@ sub buildconf { } }
+ # Add event handlers + print FILE <<EOF; +on commit { + set ClientAddress = concat( + "ADDRESS=", + binary-to-ascii(10, 8, ".", leased-address) + ); + set ClientName = concat( + "NAME=", + pick-first-value(option host-name, config-option-host-name, client-name, "") + ); + + if (ClientName != "") { + execute("/usr/sbin/unbound-dhcp-leases-client", "commit", ClientAddress, ClientName); + } +} + +on release { + set ClientAddress = concat( + "ADDRESS=", + binary-to-ascii(10, 8, ".", leased-address) + ); + set ClientName = concat( + "NAME=", + pick-first-value(option host-name, config-option-host-name, client-name, "") + ); + + if (ClientName != "") { + execute("/usr/sbin/unbound-dhcp-leases-client", "release", ClientAddress, ClientName); + } +} + +on expiry { + set ClientAddress = concat( + "ADDRESS=", + binary-to-ascii(10, 8, ".", leased-address) + ); + + execute("/usr/sbin/unbound-dhcp-leases-client", "expiry", ClientAddress); +} + +EOF + #write fixed leases if any. Does not handle duplicates to write them elsewhere than the global scope. my $key = 0; foreach my $line (@current2) { diff --git a/lfs/dhcp b/lfs/dhcp index 6849e7dff..f795f9a97 100644 --- a/lfs/dhcp +++ b/lfs/dhcp @@ -84,6 +84,7 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) --sysconfdir=/etc/dhcp \ --with-srv-conf-file=/etc/dhcp/dhcpd.conf \ --with-srv-lease-file=/var/state/dhcp/dhcpd.leases \ + --enable-execute \ --enable-paranoia \ --enable-early-chroot \ --disable-dhcpv6 diff --git a/lfs/unbound b/lfs/unbound index 904744bc2..580508809 100644 --- a/lfs/unbound +++ b/lfs/unbound @@ -99,6 +99,8 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) # Install DHCP leases bridge install -v -m 755 $(DIR_SRC)/config/unbound/unbound-dhcp-leases-bridge \ /usr/sbin/unbound-dhcp-leases-bridge + install -v -m 755 $(DIR_SRC)/config/unbound/unbound-dhcp-leases-client \ + /usr/sbin/unbound-dhcp-leases-client
# Install key -mkdir -pv /var/lib/unbound
hooks/post-receive -- IPFire 2.x development tree