Skip to content

Instantly share code, notes, and snippets.

@akpoff
Last active July 2, 2021 06:37
Show Gist options
  • Save akpoff/1c1b994a7b51dab34d1413ca7eab0628 to your computer and use it in GitHub Desktop.
Save akpoff/1c1b994a7b51dab34d1413ca7eab0628 to your computer and use it in GitHub Desktop.
dhcpd_to_unbound -- add dhcpd entries as local_data to unbound
#!/bin/sh
#
# Copyright (c) 2017, 2019 Aaron Poffenberger <akp@hypernote.com>
#
# 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.
#
#
# Add dhcpd(8) entries as local_data to unbound(8).
#
# v1.0.0
#
# Add host assignments from:
# /etc/dhcpd.conf
# /var/db/dhcpd.leases
#
# Clears existing entries for DOMAIN_NAME.
#
# Adds one entry per `host` declaration in `/etc/dhcpd.conf`
#
# Adds one unique entry per `hardware ethernet` (MAC address)
# in `/var/db/dhcpd.leases` (with hostname)
#
# Adds reverse lookup records.
#
# This script should be run regularly from cron(8). E.g.
# */3 * * * * dhcpd_to_unbound
# @reboot dhcpd_to_unbound
#
# BUGS:
# Probably doesn't work with IPv6.
#
# Does not validate unbound(8) or dhcpd(8) are running.
#
# Only works with the first `option domain-name` found
# in /etc/dhcpd.conf, meaning it assumes all clients
# found in `/var/db/dhcpd.leases` belong to first the
# `option domain-name` found.
__progname="${0##*/}"
CONF='/etc/dhcpd.conf'
LEASES='/var/db/dhcpd.leases'
TMP_FILE='/tmp/dhcpd_to_unbound'
CUR_DATA='/tmp/dhcpd_to_unbound_cur_data'
UC_FLAGS='-q'
[ -f "$TMP_FILE" ] && exit
err() { log "$__progname: $*"; }
log() { printf "%s\\n" "$*" > /dev/stderr; }
[ ! -f "$CONF" ] && err "No $CONF file" && exit 1
[ ! -f "$LEASES" ] && err "No $LEASES file" && exit 1
cleanup() {
trap "" EXIT
rm -f "$TMP_FILE" "$CUR_DATA"
}
trap cleanup EXIT INT QUIT
touch "$TMP_FILE"
while getopts :c:hl:sv opt ; do
case $opt in
c)
CONF="$OPTARG"
;;
h)
CMD="usage"
;;
l)
LEASES="$OPTARG"
;;
s)
CMD=show
;;
v)
unset UC_FLAGS
;;
:)
echo "$__progname: option requires an argument" \
"-- $OPTARG"
CMD=abort
;;
\?)
echo "$__progname: invalid option -- $OPTARG"
CMD=abort
;;
esac
done
shift $(( OPTIND - 1 ))
DOMAIN_NAME=$(grep 'domain-name ' "$CONF" | \
head -n1 | cut -d ' ' -f 3 | tr -d ';"')
case ${CMD:-run} in
abort)
exit;
;;
usage)
log "usage: $__progname [-c file] [-h] [-l file] [-s] [-v]"
log " -c path to conf to use instead of ${CONF}."
log " -h this message."
log " -l path to leases file to use instead of ${LEASES}."
log " -s show unbound entries for the specified domain."
log " -v verbose."
exit
;;
show)
unbound-control list_local_data | grep "$DOMAIN_NAME"
exit
;;
esac
# /etc/dhcpd.conf
grep -E 'host|fixed-address|{|}' "$CONF" \
| grep -v subnet \
| tr -d '\t' \
| sed 's#host ##g' \
| tr -d '\n' \
| tr '{' ';' \
| tr '}' '\n' \
| tr -s ' ' \
| tr -d '";' \
| sort -k3 \
| awk 'NF == 3 { print $3, $1 }' \
> "$TMP_FILE"
# /var/db/dhcpd.leases
grep -E 'lease|starts|ends|hardware ethernet|abandoned|client-hostname|}' \
"$LEASES" \
| tr -d '\t' \
| sed 's#starts [0-9]##g ; s#ends [0-9]##g ; s# UTC##g ; s#lease ##g ; s#{#;#g' \
| tr -d '\n' \
| tr '}' '\n' \
| sed 's# *; *#;#g' \
| awk -v cur_ts="$(date +%s)" '
BEGIN { FS=";" }
$0 !~ /abandon/ {
end_ts = strptime($3)
if ( length(end_ts) == 0 || cur_ts > end_ts ) { exit }
split($4, hardwares, " ")
mac = hardwares[3]
if ( split($5, clients, " ") == 2) {
hostname = clients[2]
gsub("\"", "", hostname)
} else {
# use the MAC address as the hostname if none found
hostname = mac
gsub(":", "_", hostname)
}
print $1, hostname
}
# a static implementation .. only knows one format
function strptime(str, elems, times, dt) {
split(str, elems, " ")
gsub("\/", "", elems[1])
split(elems[2], times, ":")
cmd = sprintf("date -uj \"+%s\" %s%s%s.%s\n",
"%s", elems[1], times[1], times[2], times[3])
cmd | getline dt
return dt
}
' | sort -k5 -k4r \
| uniq -f1 \
>> "$TMP_FILE"
[ -z "$UC_FLAGS" ] && log "Add host entries for $DOMAIN_NAME to" \
"unbound"
# remove all addresses for $DOMAIN_NAME
unbound-control $UC_FLAGS list_local_data | grep "$DOMAIN_NAME" \
| cut -f 1 \
| sed 's#.$##g' \
> "$CUR_DATA"
unbound-control $UC_FLAGS local_datas_remove < "$CUR_DATA"
# load new data
awk -v domain_name="$DOMAIN_NAME" '
{
rev_ip = reverse_ip($1)
printf("%s.%s IN A %s\n", $2, domain_name, $1)
printf("%s.in-addr.arpa. IN PTR %s.%s\n",
rev_ip, $2,domain_name)
}
function reverse_ip(ip, elems) {
split(ip, elems, ".")
rev_ip = sprintf("%s.%s.%s.%s",
elems[4], elems[3], elems[2], elems[1])
return rev_ip
}
' "$TMP_FILE" \
| unbound-control $UC_FLAGS local_datas
@akpoff
Copy link
Author

akpoff commented Dec 9, 2020 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment