import argparse
import datetime
import daemon
+import ipaddress
import logging
import logging.handlers
import re
log = logging.getLogger("dhcp")
+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, unbound_leases_file):
self.leases_file = dhcp_leases_file
def hostname(self):
hostname = self._properties.get("client-hostname")
+ if hostname is None:
+ return
+
# Remove any ""
- if hostname:
- hostname = hostname.replace("\"", "")
+ hostname = hostname.replace("\"", "")
- return hostname
+ # Only return valid hostnames
+ m = re.match(r"^[A-Z0-9\-]{1,63}$", hostname, re.I)
+ if m:
+ return hostname
@property
def domain(self):
- return "local" # XXX
+ # Load ethernet settings
+ ethernet_settings = self.read_settings("/var/ipfire/ethernet/settings")
+
+ # Load DHCP settings
+ dhcp_settings = self.read_settings("/var/ipfire/dhcp/settings")
+
+ subnets = {}
+ for zone in ("GREEN", "BLUE"):
+ if not dhcp_settings.get("ENABLE_%s" % zone) == "on":
+ continue
+
+ netaddr = ethernet_settings.get("%s_NETADDRESS" % zone)
+ submask = ethernet_settings.get("%s_NETMASK" % zone)
+
+ subnet = ipaddress.ip_network("%s/%s" % (netaddr, submask))
+ domain = dhcp_settings.get("DOMAIN_NAME_%s" % zone)
+
+ subnets[subnet] = domain
+
+ address = ipaddress.ip_address(self.ipaddr)
+
+ for subnet, domain in subnets.items():
+ if address in subnet:
+ return domain
+
+ # Fall back to localdomain if no match could be found
+ return "localdomain"
+
+ @staticmethod
+ def read_settings(filename):
+ settings = {}
+
+ with open(filename) as f:
+ for line in f.readlines():
+ # Remove line-breaks
+ line = line.rstrip()
+
+ k, v = line.split("=", 1)
+ settings[k] = v
+
+ return settings
@property
def fqdn(self):
- return "%s.%s" % (self.hostname, self.domain)
+ if self.hostname:
+ return "%s.%s" % (self.hostname, self.domain)
@staticmethod
def _parse_time(s):
@property
def rrset(self):
+ # If the lease does not have a valid FQDN, we cannot create any RRs
+ if self.fqdn is None:
+ return []
+
return [
# Forward record
(self.fqdn, "%s" % LOCAL_TTL, "IN A", self.ipaddr),
# Reverse record
- (self.ipaddr, "%s" % LOCAL_TTL, "IN PTR", self.fqdn),
+ (ip_address_to_reverse_pointer(self.ipaddr), "%s" % LOCAL_TTL,
+ "IN PTR", self.fqdn),
]
if record_type == "A":
ret[hostname] = content
elif record_type == "PTR":
- ret[content] = hostname
+ ret[content] = reverse_pointer_to_ip_address(hostname)
return ret
def update_dhcp_leases(self, leases):
- # Strip all non-active or expired leases
- leases = [l for l in leases if l.active and not l.expired]
+ # Cache all expired or inactive leases
+ expired_leases = [l for l in leases if l.expired or not l.active]
- # Find any leases that have expired or do not exist any more
+ # Find any leases that have expired or do not exist any more
+ # but are still in the unbound local data
removed_leases = []
for fqdn, address in self.existing_leases.items():
- if not fqdn in (l.fqdn for l in leases):
+ if fqdn in (l.fqdn for l in expired_leases):
removed_leases += [fqdn, address]
+ # Strip all non-active or expired leases
+ leases = [l for l in leases if l.active and not l.expired]
+
# Find any leases that have been added
new_leases = [l for l in leases
if l.fqdn not in self.existing_leases]