I've been told new OpenBSD stuff should use this license: /* * Copyright (c) 2025 Tomasz Kramkowski * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ What follows is the output of the following command: $ head -n10000 /etc/rc.d/zoneregend /usr/local/sbin/zoneregend /usr/local/sbin/zoneregen /usr/local/sbin/leases2zones /etc/zoneregen ==> /etc/rc.d/zoneregend <== #!/bin/ksh daemon="/usr/local/sbin/zoneregend" daemon_logger="daemon.info" . /etc/rc.d/rc.subr pexp="bash $pexp" rc_bg=YES rc_cmd $1 ==> /usr/local/sbin/zoneregend <== #!/usr/bin/env bash shopt -s lastpipe LEASES=/var/db/dhcpd.leases [[ -r /etc/zoneregen ]] && . /etc/zoneregen do_regen() { printf 'Running zoneregen\n' /usr/local/sbin/zoneregen } trap 'do_regen' HUP trap 'kill 0' EXIT inotifywait -e modify -m "$LEASES" --format '' 2>&1 | while read -r; do case $REPLY in 'Watches established.'|'') do_regen ;; esac done ==> /usr/local/sbin/zoneregen <== #!/usr/bin/env bash LEASES=/var/db/dhcpd.leases FWD_PRE=/dev/null FWD_POST=/dev/null REV_PRE=/dev/null REV_POST=/dev/null . /etc/zoneregen || exit trap 'rm -f -- "$fwd_part" "$rev_part" "$full"' EXIT fwd_part=$(mktemp) || exit rev_part=$(mktemp) || exit full=$(mktemp) || exit leases2zones "$FWD_DOMAIN" "$fwd_part" "$rev_part" <"$LEASES" || exit zone_serial() { ldns-read-zone -c -E SOA -- "$1" | \ awk '{ serial = $7; exit } END { printf "%d\n", serial }' } regen_zone() { local output=$1 domain=$2 pre=$3 post=$4 part=$5 local serial=$(zone_serial "$output") ldns-read-zone -zS "$serial" -- \ <(cat "$pre" "$part" "$post") >"$full" || exit wait "$!" || exit cmp -s -- "$output" "$full" && return ldns-read-zone -zS "$((serial + 1))" -- "$full" >"$output" || exit nsd-control reload "$domain" || exit } regen_zone "$FWD" "$FWD_DOMAIN" "$FWD_PRE" "$FWD_POST" "$fwd_part" regen_zone "$REV" "$REV_DOMAIN" "$REV_PRE" "$REV_POST" "$rev_part" ==> /usr/local/sbin/leases2zones <== #!/usr/bin/env python3 from dataclasses import dataclass from datetime import datetime from ipaddress import IPv4Address, IPv6Address, ip_address from sys import argv, stdin, stderr from typing import Union, Optional, TextIO IPAddress = Union[IPv4Address, IPv6Address] @dataclass class Lease: ip: IPAddress mac: str hostname: Optional[str] @dataclass class Record: name: str type: str target: str def __str__(self): return f'{self.name} {self.type} {self.target}' def parse_leases(f: TextIO) -> list[Lease]: now = datetime.now() leases: dict[IPAddress, Lease] = {} lease = None for line in f: line = line.strip() if line.startswith('lease'): lease = dict() _, lease['ip'], _ = line.split(' ') if not lease: continue if line.startswith('starts'): _, dt = line[:-1].split(' ', maxsplit=1) lease['starts'] = datetime.strptime(dt, '%w %Y/%m/%d %H:%M:%S %Z') if line.startswith('ends'): _, dt = line[:-1].split(' ', maxsplit=1) lease['ends'] = datetime.strptime(dt, '%w %Y/%m/%d %H:%M:%S %Z') if line.startswith('hardware ethernet'): _, _, lease['mac'] = line[:-1].split(' ') if line.startswith('client-hostname'): _, lease['hostname'], _ = line.split('"') if line.startswith('abandoned'): lease['abandoned'] = True if line == '}': if lease and not lease.get('abandoned') and \ lease.get('mac') and \ lease.get('starts', now) <= now and \ lease.get('ends', now) > now: ip = ip_address(lease['ip']) leases[ip] = Lease(ip, lease['mac'], lease.get('hostname')) lease = None return list(leases.values()) def to_zones(leases: list[Lease], domain: str) -> tuple[list[Record], list[Record]]: forward: list[Record] = list() reverse: list[Record] = list() for l in leases: typ = 'A' if isinstance(l.ip, IPv4Address) else 'AAAA' rev = l.ip.reverse_pointer + '.' # 3.5 if l.hostname: base, *_ = l.hostname.split('.') hostname_fqdn = base + '.' + domain forward.append(Record(hostname_fqdn, typ, str(l.ip))) reverse.append(Record(rev, 'PTR', hostname_fqdn)) mac_fqdn = l.mac.replace(':', '_') + '.' + domain forward.append(Record(mac_fqdn, typ, str(l.ip))) if not l.hostname: reverse.append(Record(rev, 'PTR', mac_fqdn)) return forward, reverse def main() -> None: if len(argv) != 4: argv0 = argv[0] if len(argv) >= 1 else 'leases2zones' print(f'Usage: {argv0} domain forward-filename reverse-filename', file=stderr) exit(1) domain, forward_filename, reverse_filename = argv[1:] domain = '.'.join(domain.split('.')) + '.' forward, reverse = to_zones(parse_leases(stdin), domain) with open(forward_filename, 'w') as f: for record in forward: print(str(record), file=f) with open(reverse_filename, 'w') as f: for record in reverse: print(str(record), file=f) if __name__ == '__main__': main() ==> /etc/zoneregen <== ZONE_DIR=/var/nsd/zones/master FWD_DOMAIN=your.local.domain FWD=$ZONE_DIR/$FWD_DOMAIN.zone FWD_PRE=$FWD.pre REV_DOMAIN=168.192.in-addr.arpa REV=$ZONE_DIR/$REV_DOMAIN.zone REV_PRE=$REV.pre