Created
June 8, 2023 05:12
-
-
Save big-lip-bob/611e1dfe66dc0b6e5cbb6486e05f120f to your computer and use it in GitHub Desktop.
Namespace aware DHClient script (for DNS only)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# dhclient-script for Linux. Dan Halbert, March, 1997. | |
# Updated for Linux 2.[12] by Brian J. Murrell, January 1999. | |
# Addenum by Haruspex for IP Network Namespace DNS awareness, May 2023. | |
# No guarantees about this. I'm a novice at the details of Linux networking. | |
# Notes: | |
# 0. This script is based on the netbsd script supplied with dhcp-970306. | |
# 1. ifconfig down apparently deletes all relevant routes and flushes | |
# the arp cache, so this doesn't need to be done explicitly. | |
# 2. The alias address handling here has not been tested AT ALL. | |
# I'm just going by the doc of modern Linux ip aliasing, which uses | |
# notations like eth0:0, eth0:1, for each alias. | |
# 3. I have to calculate the network address, and calculate the broadcast | |
# address if it is not supplied. This might be much more easily done | |
# by the dhclient C code, and passed on. | |
# 4. TIMEOUT not tested. ping has a flag I don't know, and I'm suspicious | |
# of the $1 in its args. | |
# 5. Script refresh in 2017. The aliasing code was too convoluted and needs | |
# to go away. Migrated DHCPv4 script to ip command from iproute2 suite. | |
# This is based on Debian script with some tweaks. ifconfig is no longer | |
# used. Everything is done using ip tool from ip-route2. | |
# 'ip' just looks too weird. Also, we now have unit-tests! Those unit-tests | |
# overwirte this line to use a fake ip-echo tool. It's also convenient | |
# if your system holds ip tool in a non-standard location. | |
ip=/sbin/ip | |
# Addenum for network namespace consideration | |
namespace=$(ip netns identify) | |
if [ -n "$namespace" ]; then | |
resolver_path="/etc/netns/$namespace/resolv.conf" | |
else | |
resolver_path="/etc/resolv.conf" | |
fi | |
resolver_path_new="$resolver_path.dhclient-new" | |
# update /etc/resolv.conf based on received values | |
# This updated version mostly follows Debian script by Andrew Pollock et al. | |
make_resolv_conf() { | |
local new_resolv_conf | |
# DHCPv4 | |
if [ -n "$new_domain_search" ] || [ -n "$new_domain_name" ] || | |
[ -n "$new_domain_name_servers" ]; then | |
new_resolv_conf=$resolver_path_new | |
rm -f $new_resolv_conf | |
if [ -n "$new_domain_name" ]; then | |
echo domain ${new_domain_name%% *} >>$new_resolv_conf | |
fi | |
if [ -n "$new_domain_search" ]; then | |
if [ -n "$new_domain_name" ]; then | |
domain_in_search_list="" | |
for domain in $new_domain_search; do | |
if [ "$domain" = "${new_domain_name}" ] || | |
[ "$domain" = "${new_domain_name}." ]; then | |
domain_in_search_list="Yes" | |
fi | |
done | |
if [ -z "$domain_in_search_list" ]; then | |
new_domain_search="$new_domain_name $new_domain_search" | |
fi | |
fi | |
echo "search ${new_domain_search}" >> $new_resolv_conf | |
elif [ -n "$new_domain_name" ]; then | |
echo "search ${new_domain_name}" >> $new_resolv_conf | |
fi | |
if [ -n "$new_domain_name_servers" ]; then | |
for nameserver in $new_domain_name_servers; do | |
echo nameserver $nameserver >>$new_resolv_conf | |
done | |
else # keep 'old' nameservers | |
sed -n /^\w*[Nn][Aa][Mm][Ee][Ss][Ee][Rr][Vv][Ee][Rr]/p $resolver_path >>$new_resolv_conf | |
fi | |
if [ -f $resolver_path ]; then | |
chown --reference=$resolver_path $new_resolv_conf | |
chmod --reference=$resolver_path $new_resolv_conf | |
fi | |
mv -f $new_resolv_conf $resolver_path | |
# DHCPv6 | |
elif [ -n "$new_dhcp6_domain_search" ] || [ -n "$new_dhcp6_name_servers" ]; then | |
new_resolv_conf=$resolver_path_new | |
rm -f $new_resolv_conf | |
if [ -n "$new_dhcp6_domain_search" ]; then | |
echo "search ${new_dhcp6_domain_search}" >> $new_resolv_conf | |
fi | |
if [ -n "$new_dhcp6_name_servers" ]; then | |
for nameserver in $new_dhcp6_name_servers; do | |
# append %interface to link-local-address nameservers | |
if [ "${nameserver##fe80::}" != "$nameserver" ] || | |
[ "${nameserver##FE80::}" != "$nameserver" ]; then | |
nameserver="${nameserver}%${interface}" | |
fi | |
echo nameserver $nameserver >>$new_resolv_conf | |
done | |
else # keep 'old' nameservers | |
sed -n /^\w*[Nn][Aa][Mm][Ee][Ss][Ee][Rr][Vv][Ee][Rr]/p $resolver_path >>$new_resolv_conf | |
fi | |
if [ -f $resolver_path ]; then | |
chown --reference=$resolver_path $new_resolv_conf | |
chmod --reference=$resolver_path $new_resolv_conf | |
fi | |
mv -f $new_resolv_conf $resolver_path | |
fi | |
} | |
# set host name | |
set_hostname() { | |
local current_hostname | |
if [ -n "$new_host_name" ]; then | |
current_hostname=$(uname -n) | |
# current host name is empty, '(none)' or 'localhost' or differs from new one from DHCP | |
if [ -z "$current_hostname" ] || | |
[ "$current_hostname" = '(none)' ] || | |
[ "$current_hostname" = 'localhost' ] || | |
[ "$current_hostname" = "$old_host_name" ]; then | |
if [ "$new_host_name" != "$old_host_name" ]; then | |
sysctl -w kernel/hostname="$new_host_name" | |
fi | |
fi | |
fi | |
} | |
# run given script | |
run_hook() { | |
local script | |
local exit_status | |
script="$1" | |
if [ -f $script ]; then | |
. $script | |
fi | |
if [ -n "$exit_status" ] && [ "$exit_status" -ne 0 ]; then | |
logger -p daemon.err "$script returned non-zero exit status $exit_status" | |
fi | |
return $exit_status | |
} | |
# run scripts in given directory | |
run_hookdir() { | |
local dir | |
local exit_status | |
dir="$1" | |
if [ -d "$dir" ]; then | |
for script in $(run-parts --list $dir); do | |
run_hook $script || true | |
exit_status=$? | |
done | |
fi | |
return $exit_status | |
} | |
# Must be used on exit. Invokes the local dhcp client exit hooks, if any. | |
exit_with_hooks() { | |
exit_status=$1 | |
# Source the documented exit-hook script, if it exists | |
if ! run_hook /etc/dhclient-exit-hooks; then | |
exit_status=$? | |
fi | |
# Now run scripts in the Debian-specific directory. | |
if ! run_hookdir /etc/dhclient-exit-hooks.d; then | |
exit_status=$? | |
fi | |
exit $exit_status | |
} | |
# This function was largely borrowed from dhclient-script that | |
# ships with Centos, authored by Jiri Popelka and David Cantrell | |
# of Redhat. Thanks guys. | |
add_ipv6_addr_with_DAD() { | |
${ip} -6 addr replace ${new_ip6_address}/${new_ip6_prefixlen} \ | |
dev ${interface} scope global valid_lft ${new_max_life} \ | |
preferred_lft ${new_preferred_life} | |
if [ ${dad_wait_time} -le 0 ] | |
then | |
# if we're not waiting for DAD, assume we're good | |
return 0 | |
fi | |
# Repeatedly test whether newly added address passed | |
# duplicate address detection (DAD) | |
for i in $(seq 1 ${dad_wait_time}); do | |
sleep 1 # give the DAD some time | |
addr=$(${ip} -6 addr show dev ${interface} \ | |
| grep ${new_ip6_address}/${new_ip6_prefixlen}) | |
# tentative flag == DAD is still not complete | |
tentative=$(echo "${addr}" | grep tentative) | |
# dadfailed flag == address is already in use somewhere else | |
dadfailed=$(echo "${addr}" | grep dadfailed) | |
if [ -n "${dadfailed}" ] ; then | |
# address was added with valid_lft/preferred_lft 'forever', | |
# remove it | |
${ip} -6 addr del ${new_ip6_address}/${new_ip6_prefixlen} \ | |
dev ${interface} | |
exit_with_hooks 3 | |
fi | |
if [ -z "${tentative}" ] ; then | |
if [ -n "${addr}" ]; then | |
# DAD is over | |
return 0 | |
else | |
# address was auto-removed (or not added at all) | |
exit_with_hooks 3 | |
fi | |
fi | |
done | |
return 0 | |
} | |
# Invoke the local dhcp client enter hooks, if they exist. | |
run_hook /etc/dhclient-enter-hooks | |
run_hookdir /etc/dhclient-enter-hooks.d | |
# Execute the operation | |
case "$reason" in | |
### DHCPv4 Handlers | |
MEDIUM|ARPCHECK|ARPSEND) | |
# Do nothing | |
;; | |
PREINIT) | |
# The DHCP client is requesting that an interface be | |
# configured as required in order to send packets prior to | |
# receiving an actual address. - dhclient-script(8) | |
# ensure interface is up | |
${ip} link set dev ${interface} up | |
if [ -n "$alias_ip_address" ]; then | |
# flush alias IP from interface | |
${ip} -4 addr flush dev ${interface} label ${interface}:0 | |
fi | |
;; | |
BOUND|RENEW|REBIND|REBOOT) | |
set_hostname | |
if [ -n "$old_ip_address" ] && [ -n "$alias_ip_address" ] && | |
[ "$alias_ip_address" != "$old_ip_address" ]; then | |
# alias IP may have changed => flush it | |
${ip} -4 addr flush dev ${interface} label ${interface}:0 | |
fi | |
if [ -n "$old_ip_address" ] && | |
[ "$old_ip_address" != "$new_ip_address" ]; then | |
# leased IP has changed => flush it | |
${ip} -4 addr flush dev ${interface} label ${interface} | |
fi | |
if [ -z "$old_ip_address" ] || | |
[ "$old_ip_address" != "$new_ip_address" ] || | |
[ "$reason" = "BOUND" ] || [ "$reason" = "REBOOT" ]; then | |
# new IP has been leased or leased IP changed => set it | |
${ip} -4 addr add ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \ | |
${new_broadcast_address:+broadcast $new_broadcast_address} \ | |
dev ${interface} label ${interface} | |
if [ -n "$new_interface_mtu" ]; then | |
# set MTU | |
${ip} link set dev ${interface} mtu ${new_interface_mtu} | |
fi | |
# if we have $new_rfc3442_classless_static_routes then we have to | |
# ignore $new_routers entirely | |
if [ ! "$new_rfc3442_classless_static_routes" ]; then | |
# set if_metric if IF_METRIC is set or there's more than one router | |
if_metric="$IF_METRIC" | |
if [ "${new_routers%% *}" != "${new_routers}" ]; then | |
if_metric=${if_metric:-1} | |
fi | |
for router in $new_routers; do | |
if [ "$new_subnet_mask" = "255.255.255.255" ]; then | |
# point-to-point connection => set explicit route | |
${ip} -4 route add ${router} dev $interface >/dev/null 2>&1 | |
fi | |
# set default route | |
${ip} -4 route add default via ${router} dev ${interface} \ | |
${if_metric:+metric $if_metric} >/dev/null 2>&1 | |
if [ -n "$if_metric" ]; then | |
if_metric=$((if_metric+1)) | |
fi | |
done | |
fi | |
fi | |
if [ -n "$alias_ip_address" ] && | |
[ "$new_ip_address" != "$alias_ip_address" ]; then | |
# separate alias IP given, which may have changed | |
# => flush it, set it & add host route to it | |
${ip} -4 addr flush dev ${interface} label ${interface}:0 | |
${ip} -4 addr add ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \ | |
dev ${interface} label ${interface}:0 | |
${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1 | |
fi | |
# update /etc/resolv.conf | |
make_resolv_conf | |
;; | |
EXPIRE|FAIL|RELEASE|STOP) | |
if [ -n "$alias_ip_address" ]; then | |
# flush alias IP | |
${ip} -4 addr flush dev ${interface} label ${interface}:0 | |
fi | |
if [ -n "$old_ip_address" ]; then | |
# flush leased IP | |
${ip} -4 addr flush dev ${interface} label ${interface} | |
fi | |
if [ -n "$alias_ip_address" ]; then | |
# alias IP given => set it & add host route to it | |
${ip} -4 addr add ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \ | |
dev ${interface} label ${interface}:0 | |
${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1 | |
fi | |
;; | |
TIMEOUT) | |
if [ -n "$alias_ip_address" ]; then | |
# flush alias IP | |
${ip} -4 addr flush dev ${interface} label ${interface}:0 | |
fi | |
# set IP from recorded lease | |
${ip} -4 addr add ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \ | |
${new_broadcast_address:+broadcast $new_broadcast_address} \ | |
dev ${interface} label ${interface} | |
if [ -n "$new_interface_mtu" ]; then | |
# set MTU | |
${ip} link set dev ${interface} mtu ${new_interface_mtu} | |
fi | |
# if there is no router recorded in the lease or the 1st router answers pings | |
if [ -z "$new_routers" ] || ping -q -c 1 "${new_routers%% *}"; then | |
# if we have $new_rfc3442_classless_static_routes then we have to | |
# ignore $new_routers entirely | |
if [ ! "$new_rfc3442_classless_static_routes" ]; then | |
if [ -n "$alias_ip_address" ] && | |
[ "$new_ip_address" != "$alias_ip_address" ]; then | |
# separate alias IP given => set up the alias IP & add host route to it | |
${ip} -4 addr add \ | |
${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \ | |
dev ${interface} label ${interface}:0 | |
${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1 | |
fi | |
# set if_metric if IF_METRIC is set or there's more than one router | |
if_metric="$IF_METRIC" | |
if [ "${new_routers%% *}" != "${new_routers}" ]; then | |
if_metric=${if_metric:-1} | |
fi | |
# set default route | |
for router in $new_routers; do | |
${ip} -4 route add default via ${router} dev ${interface} \ | |
${if_metric:+metric $if_metric} >/dev/null 2>&1 | |
if [ -n "$if_metric" ]; then | |
if_metric=$((if_metric+1)) | |
fi | |
done | |
fi | |
# update /etc/resolv.conf | |
make_resolv_conf | |
else | |
# flush all IPs from interface | |
ip -4 addr flush dev ${interface} | |
exit_with_hooks 2 | |
fi | |
;; | |
V6ONLY) | |
if [ -n "$old_ip_address" ]; then | |
# flush leased IP | |
${ip} -4 addr flush dev ${interface} label ${interface} | |
fi | |
;; | |
### DHCPv6 Handlers | |
# TODO handle prefix change: ?based on ${old_ip6_prefix} and ${new_ip6_prefix}? | |
PREINIT6) | |
# ensure interface is up | |
${ip} link set ${interface} up | |
# We need to give the kernel some time to active interface | |
interface_up_wait_time=5 | |
for i in $(seq 0 ${interface_up_wait_time}) | |
do | |
${ip} link show dev ${interface} | grep -q LOWER_UP 2>&1 | |
if [ $? -eq 0 ]; then | |
break; | |
fi | |
sleep 1 | |
done | |
# flush any stale global permanent IPs from interface | |
${ip} -6 addr flush dev ${interface} scope global permanent | |
# Wait for duplicate address detection for this interface if the | |
# --dad-wait-time parameter has been specified and is greater than | |
# zero. | |
if [ ${dad_wait_time} -gt 0 ]; then | |
# Check if any IPv6 address on this interface is marked as | |
# tentative. | |
${ip} addr show ${interface} | grep inet6 | grep tentative \ | |
&> /dev/null | |
if [ $? -eq 0 ]; then | |
# Wait for duplicate address detection to complete or for | |
# the timeout specified as --dad-wait-time. | |
for i in $(seq 0 $dad_wait_time) | |
do | |
# We're going to poll for the tentative flag every second. | |
sleep 1 | |
${ip} addr show ${interface} | grep inet6 | grep tentative \ | |
&> /dev/null | |
if [ $? -ne 0 ]; then | |
break; | |
fi | |
done | |
fi | |
fi | |
;; | |
BOUND6|RENEW6|REBIND6) | |
if [ "${new_ip6_address}" ] && [ "${new_ip6_prefixlen}" ]; then | |
# set leased IP | |
add_ipv6_addr_with_DAD | |
fi | |
# update /etc/resolv.conf | |
if [ "${reason}" = BOUND6 ] || | |
[ "${new_dhcp6_name_servers}" != "${old_dhcp6_name_servers}" ] || | |
[ "${new_dhcp6_domain_search}" != "${old_dhcp6_domain_search}" ]; then | |
make_resolv_conf | |
fi | |
;; | |
DEPREF6) | |
if [ -z "${cur_ip6_prefixlen}" ]; then | |
exit_with_hooks 2 | |
fi | |
# set preferred lifetime of leased IP to 0 | |
${ip} -6 addr change ${cur_ip6_address}/${cur_ip6_prefixlen} \ | |
dev ${interface} scope global preferred_lft 0 | |
;; | |
EXPIRE6|RELEASE6|STOP6) | |
if [ -z "${old_ip6_address}" ] || [ -z "${old_ip6_prefixlen}" ]; then | |
exit_with_hooks 2 | |
fi | |
# delete leased IP | |
${ip} -6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \ | |
dev ${interface} | |
;; | |
esac | |
exit_with_hooks 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment