Skip to content

Instantly share code, notes, and snippets.

@JadedDragoon
Last active February 10, 2022 18:53
Show Gist options
  • Save JadedDragoon/d7f01852ba7b8570a4adc7d7e95b69c1 to your computer and use it in GitHub Desktop.
Save JadedDragoon/d7f01852ba7b8570a4adc7d7e95b69c1 to your computer and use it in GitHub Desktop.
Robust scripts for managing DHCP6-PD templating for configurations which must have the actual IPv6 address. Requires jq, tsp, and dhcpcd.
#!/bin/bash
# we need seperate logging because dhcpcd-run-hooks eats stdout and stderr
# rather doing something sane like passing it back to dhcpcd (and then systemd)
. /usr/local/lib/logging.bash
################################################################################
# Configuration begins here.
################################################################################
. /usr/local/etc/dhcp-template.conf
################################################################################
# Script begins here.
################################################################################
. /usr/local/lib/dhcp-template.bash
if [[ "${1}" == '-exec' ]]; then
bashlog 'info' "Executing task for ${reason}."
get_vals
write_configs
if [[ ${log_verbosity} -ge 8 ]]; then
bashlog 'debug' "Environment:\n $(printenv | sort)"
fi
else
bashlog 'debug' "Hook reason is ${reason}."
case "${reason}" in
DELEGATED6 | BOUND | BOUND6 | REBIND | REBIND6)
for util in "${required_commands[@]}"; do
if ! command -v "${util}" >/dev/null 2>&1; then printf '%s' "Utility \'${util}\' unavailable. Aborting."; exit 1; fi
done
# Because we do not control execution and cannot afford to block
# execution at any point, flocks are not a viable approach. Here we use
# task-spooler to queue execution of the important parts of this script
# instead.
bashlog 'info' "Queueing task for ${reason}."
bashlog 'debug' "Running: tsp -f \"${script_name[0]}\" -exec"
tsp -f "${script_name[0]}" -exec
;;
esac
fi
# vim: ts=4:sw=4
# dhcpd6.template
default-lease-time 2592000;
preferred-lifetime 604800;
option dhcp-renewal-time 3600;
option dhcp-rebinding-time 7200;
allow leasequery;
option dhcp6.preference 255;
# Server side command to enable rapid-commit (2 packet exchange)
#option dhcp6.rapid-commit;
option dhcp6.info-refresh-time 21600;
authoritative;
log-facility local7;
subnet6 $[PREFIX]::/$[PFSUBNET] {
pool6 {
option dhcp6.name-servers 2606:4700:4700::1111, 2606:4700:4700::1001;
max-lease-time 300;
range6 $[PREFIX]::2:1 $[PREFIX]::2:FFFF;
allow unknown-clients;
}
pool6 {
option dhcp6.name-servers fe80::FFFF:18ff:fea5:6c28, 2606:4700:4700::1111, 2606:4700:4700::1001;
option dhcp6.domain-search "subdomain.example.net", "example.net";
deny unknown-clients;
range6 $[PREFIX]::1:101 $[PREFIX]::1:FFFF;
host destynovamk4 {
host-identifier option client-id 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD;
fixed-address6 $[PREFIX]::1:1;
}
host drido {
host-identifier option client-id 11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:FF;
fixed-address6 $[PREFIX]::1:2;
}
host zappan {
host-identifier option client-id 22:33:44:55:66:77:88:99:AA:BB:CC:DD:FF:00;
fixed-address6 $[PREFIX]::1:3;
}
}
}
# vim: ts=4:sw=4
################################################################################
# Configuration begins here.
################################################################################
wan_interface='wan'
lan_interface='lan'
log_identifier='dhcpcd-exit-hook'
declare -A templates=(
['/etc/dhcpd6.template.conf']="/etc/dhcpd6-${lan_interface}.conf"
)
services=(
dhcpd6@${lan_interface}
)
json_conf='/var/lib/dhcpcd/exit-hook-conf.json'
#log_verbosity=7 #enable debugging
# vim: ts=4:sw=4
declare -r script_name=$(readlink -f ${BASH_SOURCE[0]})
declare -r conf_ver='0.4.0'
declare -r required_commands=(tsp jq)
IFS=' '
read -r -d '' header <<'EOF'
#####################################\
# #\
# DO NOT EDIT THIS FILE #\
# #\
# This file is periodicly replaced. #\
# Edit the template instead. #\
#####################################
EOF
upsert_transform() {
if [[ ! -f "${json_conf}~" ]]; then printf '%s' '{}' > "${json_conf}~"; fi
if ! jq \
--arg match "${1}" \
--arg value "${2}" \
--arg ver "${conf_ver}" \
--arg lu "$(date -Ins)" \
'.transforms[$match] = $value | .version = $ver | .lastupdate = $lu' \
"${json_conf}" \
1>"${json_conf}~" 2>&1
then
printf '%s' 'Failed assigning values to temporary JSON.'
return 1
fi
mv "${json_conf}~" "${json_conf}" 2>&1 || printf '%s' 'Failed overwriting existing JSON' && return 2
}
delegated_prefix() {
bashlog 'info' "Delegated IPv6 LAN Prefix. Updating services configuration..."
prefixes=(${new_delegated_dhcp6_prefix//[^a-fA-F0-9:\/ ]/})
prefix=$(printf '%s' "${prefixes[0]}" | sed -E 's/::[a-fA-F0-9]{1,4}\/[0-9]{1,3}$//g')
subnet=$(printf '%s' "${prefixes[0]}" | sed -E 's/^[a-fA-F0-9:]+\///g')
bashlog 'debug' " Prefix resolution: ${prefixes[*]} -> ${prefix} ${subnet}"
bashlog 'info' " Found Prefix: ${prefix}::/${subnet}"
bashlog 'info' ' Updating json.'
bashlog 'err' "$(upsert_transform 'lan6prefix' ${prefix})"
bashlog 'err' "$(upsert_transform 'lan6subnet' ${subnet})"
bashlog 'info' ' Done.'
}
bound_addr() {
bashlog 'info' "Bound new IP to WAN interface. Updating services configuration..."
case "${reason}" in
BOUND | REBIND)
address=$(printf '%s' "${new_ip_address}" | grep -P '((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])')
;;
BOUND6 | REBIND6)
address=$(printf '%s' "${new_dhcp6_ia_na1_ia_addr1}" | grep -P '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$')
;;
esac
if [[ -z ${address} ]]; then bashlog 'err' ' Failed to determine new ip address.'; exit 1; fi
bashlog 'info' " Found IP Address: ${address}"
bashlog 'info' ' Updating json.'
case "${reason}" in
BOUND | REBIND)
bashlog 'err' "$(upsert_transform 'wan4addr' ${address})"
;;
BOUND6 | REBIND6)
bashlog 'err' "$(upsert_transform 'wan6addr' ${address})"
;;
esac
bashlog 'info' ' Done.'
}
inplace_dance() {
read -r -d '' data
printf '%s' "${data}" 1> "${1}~"
mv "${1}~" "${1}"
}
get_vals() {
case "${reason}" in
DELEGATED6)
bashlog 'debug' "${interface} must match ${lan_interface}"
if [[ "${interface}" == "${lan_interface}" ]]; then delegated_prefix; fi
;;
BOUND | BOUND6 | REBIND | REBIND6)
bashlog 'debug' "${interface} must match ${wan_interface}"
if [[ "${interface}" == "${wan_interface}" ]]; then bound_addr; fi
;;
esac
}
write_configs() {
declare -A transforms="$(\
jq --join-output '.transforms | [ to_entries[] | "["+(.key|@sh)+"]="+(.value|@sh) ] | "("+join(" ")+")"' ${json_conf} \
)"
bashlog 'info' 'Generating config files from templates...'
bashlog 'debug' " Templates: ${!templates[@]}"
for template in "${!templates[@]}"; do
bashlog 'debug' " Processing - ${template} into ${templates[${template}]}"
bashlog 'debug' ' Striping comments and empty lines.'
bashlog 'err' $(\
sed -E 's/#.*$//g' "${template}" |\
sed -E '/^\s*$/d' |\
inplace_dance "${templates[$template]}" \
2>&1)
for match in "${!transforms[@]}"; do
bashlog 'debug' " Transforming \$[${match^^}] into ${transforms[$match]}."
bashlog 'err' $(\
sed "s/\$\[${match^^}\]/${transforms[$match]}/g" "${templates[${template}]}" |\
inplace_dance "${templates[$template]}" \
2>&1)
done
bashlog 'debug' ' Adding header.'
bashlog 'err' $(\
sed "1 i # TEMPLATE FILE: ${template}" "${templates[${template}]}" |\
sed "1 i ${header}" |\
inplace_dance "${templates[$template]}" \
2>&1)
bashlog 'err' $(chmod 644 "${templates[${template}]}" 2>&1)
done
bashlog 'info' " Done."
bashlog 'info' "(Re)Starting services..."
for service in "${services[@]}"; do
bashlog 'debug' "Restarting ${service}."
bashlog 'err' "$(systemctl restart ${service} 2>&1)"
done
bashlog 'info' " Done."
}
# vim: ts=4:sw=4
log_verbosity=6 #info
stdout_verbosity=4 #warning
stderr_verbosity=3 #error
log_identifier='bash-logging'
log_console='off'
bashlog() {
if [ -n "$2" ]; then
string="${@:2}"
errlvl=8
case "$1" in
emerg | emergency | panic)
errlvl=0
;;
alert)
errlvl=1
string="!!!ALERT!!! $string"
;;
crit | critical)
errlvl=2
string="CRITICAL: $string"
;;
err | error)
errlvl=3
string="ERROR: $string"
;;
warn | warning)
errlvl=4
string="Warning: $string"
;;
note | notice | notify)
errlvl=5
string="notice: $string"
;;
info | inform | information)
errlvl=6
;;
debug)
errlvl=7
string="debug: $string"
;;
esac
# quick and dirty test to see if logging is working
if [[ $log_verbosity -ge 8 ]]; then echo "testing ${@:2}" | logger -p 7 -t ${log_identifier}; fi
# log to syslog/journald
if [[ $log_verbosity -ge $errlvl ]]; then
printf "${string}" | logger -p $errlvl -t "${log_identifier}"
fi
# log to console as well
if [[ ${log_console} == 'on' ]]; then
if [[ $stdout_verbosity -ge $errlvl && $stderr_verbosity -lt $errlvl ]]; then
printf '%s' "${string}"
fi
if [[ $stderr_verbosity -ge $errlvl ]]; then
printf '%s' "${string}" >&2
fi
fi
fi
}
# vim: ts=4:sw=4
#!/bin/bash
# script to force update dhcp6 templates on command
. /usr/local/lib/logging.bash
. /usr/local/etc/dhcp-template.conf
. /usr/local/lib/dhcp-template.bash
IFS=' '
log_console='on'
log_verbosity=7
write_configs
# vim: ts=4:sw=4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment