WARNING: This is an alpha script. Please experiment in a test environment!
this is the latest and greatest Proof-of-Concept script. There are comments in the files that might help.
Here is the script. Please install this script first. It is named dhcpEvent.sh
:
EDIT: Do not use the code below. It is not complete!
#!/bin/bash
# DHCP commit/release/expiry status drives this process
############### following 7 lines for testing only ###############
# Kill the unbound-dhcp-leases-bridge process
pgrep -f unbound-dhcp-leases-bridge &&
kill -s SIGTERM $(pgrep -d',' -f unbound-dhcp-leases-bridge) &&
/usr/bin/logger --tag dhcpEvent "terminated unbound-dhcp-leases-bridge"
# chmod -v 744 /root/dhcpdconf/dhcpEvent_vN.sh
# ln -vsf /root/dhcpdconf/dhcpEvent_vN.sh /root/dhcpdconf/dhcpEvent.sh
#-------------------------------------------------------------------------
dhcpCREstatus="${1:-}"
clientIP="${2:-}"
clientHostname="${3:-}"
unboundDHCPleases="/etc/unbound/dhcp-leases.conf"
unboundStaticHosts="/etc/unbound/hosts.conf"
TTL="60" # time to live (seconds)
tagName="dhcpEv27" # to be "dhcpEvent"
############### Functions ###############
# write A & PTR records to unbound
#
write-unbound () {
local IPaddr
local clientName
local revIP
local recA
local recPTR
IPaddr=$1
clientName=$2
# create reverse IP for arpa name
revIP=$( echo "${IPaddr}" | /usr/bin/awk -F. '{print $4 "." $3 "." $2 "." $1}' )
# create "A" record and "PTR" record for unbound
recA="${clientName}.${DOMAINNAME}. ${TTL} IN A ${IPaddr}"
recPTR="${revIP}.in-addr.arpa. ${TTL} IN PTR ${clientName}.${DOMAINNAME}."
# send A & PTR records to unbound and dhcp-leases file
echo -e "${recA}\n${recPTR}" | /usr/sbin/unbound-control local_datas
echo "local-data: \"${recA}\"" >> "${unboundDHCPleases}"
echo "local-data: \"${recPTR}\"" >> "${unboundDHCPleases}"
msg_log "${dhcpCREstatus}: added A & PTR records to unbound"
}
# delete A & PTR records from unbound, by IP address
#
delete_unbound () {
local IPaddr
local revIP
local fqdn
local deleteList
local IPaddr=$1
# need IP address to get FQDN and arpa name
revIP=$( echo "${IPaddr}" | /usr/bin/awk -F. '{print $4 "." $3 "." $2 "." $1}' )
fqdn=$( /bin/grep --word-regexp --ignore-case "${IPaddr}" <<< "${ucList}" | \
/usr/bin/awk '{ print $1 }' )
# does a FQDN exist for this IP address? If yes, then remove
if [[ -n "${fqdn}" ]] ; then
# remove fqdn names from unbound AND dhcp-leases file
# look for multiple instances of fqdn (client hostname.domain)
deleteList=$( /bin/grep --word-regexp --ignore-case "${fqdn}" <<< "${ucList}" | \
/usr/bin/awk '{ print $1 }' )
# delete from unbound cache
echo "${deleteList}" | /usr/sbin/unbound-control local_datas_remove
# delete from dhcp-leases.conf file
/bin/sed --in-place "/\b${fqdn}/d" "${unboundDHCPleases}"
msg_log "${dhcpCREstatus}: references to ${fqdn} removed from unbound"
else
msg_log "${dhcpCREstatus}: Oops, no FQDN - exit"
return 1
fi
}
# send message to message log
msg_log () {
if tty --silent ; then
echo "${tagName}:" "$*"
else
/usr/bin/logger --tag "${tagName}" "$*"
fi
}
############### Main ###############
# check parameter count
if (( "$#" < 2 )) || (( $# > 3 )); then
msg_log "Illegal number of parameters - exit"
exit 1
fi
# simple check for valid IP addr ( may be too simple! )
if ! /bin/grep --quiet --extended-regexp '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' <<< "${clientIP}" ; then
msg_log "${clientIP} is an invalid IP address - exit"
exit
fi
# need DOMAINNAME value
eval "$(/usr/local/bin/readhash /var/ipfire/main/settings)"
# remove invalid characters in client hostname
#clientHostname=$( echo "${clientHostname}" | /bin/sed 's/[^A-Za-z0-9.-]/_/g' )
# end hostname at first invalid character
clientHostname=$( echo "${clientHostname}" | /bin/sed 's/[^A-Za-z0-9.-]/_/g' | /usr/bin/awk -F_ '{print $1}' )
echo "clientHostname = ${clientHostname}"
# get existing records from unbound-control list_local_data
ucList=$( /usr/sbin/unbound-control list_local_data | /bin/grep -i "${DOMAINNAME}" | expand -t1 )
# Does this Record A already exist?
UC_RecA=$( /bin/grep --ignore-case "^${clientHostname}\.${DOMAINNAME}.*${clientIP}$" <<< "${ucList}" )
# find IP addr in unbound
UC_IP=$( /bin/grep --word-regexp "${clientIP}" <<< "${ucList}" )
# find client name, at start of line, in unbound (looking for record A)
UC_FQDN=$( /bin/grep --word-regexp --ignore-case "^${clientHostname}\.${DOMAINNAME}" <<< "${ucList}" )
case "${dhcpCREstatus}" in
commit)
# if client hostname is blank then exit
if [[ -z "${clientHostname}" ]] ; then
msg_log "${dhcpCREstatus}: received IP ${clientIP} but missing client hostname - exit"
exit
fi
# if client exists in static hosts then exit
if /bin/grep --word-regexp --quiet --ignore-case -e "${clientIP}" -e "${clientHostname}" "${unboundStaticHosts}"
then
msg_log "${dhcpCREstatus}: ${clientHostname} or ${clientIP} found in static hosts - exit"
exit
fi
# (ip fqdn)
if [[ -n "${UC_RecA}" ]] ; then
# existing IP and existing FQDN # ( 1 1 )
msg_log "${dhcpCREstatus}: unbound hostname & IP already exist - exit"
exit
else
if [[ -z "${UC_IP}" ]] ; then
# New IP address! - write info to unbound & dhcp-leases.conf # ( 0 0/1 )
write-unbound "${clientIP}" "${clientHostname}"
else
# Existing IP address!
if [[ -z "${UC_FQDN}" ]] ; then
# Existing IP and new FQDN
# hostname/fqdn changed - delete A & PTR records from unbound # ( 1 0 )
delete_unbound "${clientIP}"
# add new to unbound!
write-unbound "${clientIP}" "${clientHostname}"
fi
fi
fi
;;
release|expiry)
# NOTE: expiry & release are the same since I don't understand the difference :-(
if [[ -z "${clientIP}" ]] ; then
msg_log "${dhcpCREstatus}: missing client IP address - exit"
exit 1
fi
# does IP addr appear in unbound? if yes, delete lines
if [[ -n "${UC_IP}" ]] ; then
delete_unbound "${clientIP}"
else
msg_log "${dhcpCREstatus}: ${clientIP} not found in unbound - done"
fi
;;
*)
msg_log "CRE script: case = no status - exit"
exit
;;
esac
exit
This file must be made executable.
When the script is run, the script will check and make sure the unbound-dhcp-leases-bridge
is not running. If it is running, it will terminate it.
And here is the config file. This files goes here: /var/ipfire/dhcp/dhcpd.conf.local
# This file CAN be modified by the user.
# If this file is not needed, just delete the text.
# But, DO NOT completely delete this file!
#
#
# DHCP will fail with an odd message such as:
#
# [root@ipfire ~] # /etc/rc.d/init.d/dhcp start
# Starting DHCP Server... [ FAIL ]
# Starting Unbound DHCP Leases Bridge... [ OK ]
# [root@ipfire ~] # chmod: cannot access '/var/run/dhcpd.pid': No such file or directory
#
#
# commit
#
on commit {
set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
#set NoName = concat("dhcp-", binary-to-ascii(10, 8, "-", leased-address));
# if preferred, NoName can be changed to blank. and then ignored in execute script
set NoName = "";
set ClientName = pick-first-value(option host-name, config-option-host-name, client-name, NoName);
# a MAC address can easily be added for other uses. MAC is not needed for DNS
log(concat("CRE-Commit: ClientIP: ", ClientIP, " Name: ", ClientName));
execute("/root/dhcpdconf/dhcpEvent.sh", "commit", ClientIP, ClientName);
}
#
# release
#
on release {
set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
#set NoName = concat("dhcp-", binary-to-ascii(10, 8, "-", leased-address));
set NoName = "";
set ClientName = pick-first-value(option host-name, config-option-host-name, client-name, NoName);
log(concat("CRE-Release: ClientIP: ", ClientIP, " Name: ", ClientName));
execute("/root/dhcpdconf/dhcpEvent.sh", "release", ClientIP, ClientName);
}
#
# expiry
#
on expiry {
set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
set ClientName = "";
log(concat("CRE-Expiry: ClientIP: ", ClientIP, " Name: ", ClientName));
execute("/root/dhcpdconf/dhcpEvent.sh", "expiry", ClientIP, "");
}
Make sure this line in the config file is correct:
execute("/root/dhcpdconf/dhcpEvent.sh", . . .
My path to dhcpEvent.sh
is probably different than your path. There are three (3) execute lines in the config file.
If something goes wrong, just remove the text from dhcpd.conf.local
and save a empty file. Then do a /etc/init.d/unbound restart
. Everything will return to default.
After installing the above I get no unbound restarts. Except for the manual ones I cause with rpz testing.
WARNING: This is an alpha script. Please experiment in a test environment!