Used by dhcpcd
to run hook scripts
found in
/lib/dhcpcd/dhcpcd-hooks
and /etc/dhcpcd.enter-hook
and
/etc/dhcpcd.exit-hook.
(may be empty)
The default scripts configure /etc/resolv.conf
and the hostname.
May include scripts to configure ntp
or ypbind
.
A test
hook echos the dhcp variables from DISCOVER message.
Set $interface
and $reason
before calling dhcpcd-run-hooks
DHCP information to be configured is in variables starting with new_
and old DHCP
information (to be removed) is in variables starting with old_
.
dhcpcd
displays the list of variables using -V, --variables
Reasons dhcpcd-run-hooks invoked:
PREINIT |
dhcpcd
will clear the environment variables aside from $PATH
and $RC_SVCNAME.
$interface_order |
/etc/dhcpcd.enter-hook
, /lib/dhcpcd/dhcpcd-hooks
in a lexical order and then finally /etc/dhcpcd.exit-hook
/lib/dhcpcd/dhcpcd-hooks > wc * 8 37 283 01-test 8 23 120 02-dump 120 367 2930 10-wpa_supplicant 204 718 5588 20-resolv.conf 155 454 3503 30-hostname 141 491 3472 50-ntp.conf
01-test if [ "$reason" = "TEST" ]; then set | grep "^\(interface\|pid\|reason\|profile\|skip_hooks\)=" | sort set | grep "^if\(carrier\|flags\|mtu\|wireless\|ssid\)=" | sort set | grep "^\(new_\|old_\|nd[0-9]*_\)" | sort exit 0 fi
02-dump # Just echo our DHCP options we have case "$reason" in DUMP|DUMP6) set | sed -ne 's/^new_//p' | sort exit 0 ;; esac
10-wpa_supplicant (edited ed) # Start, reconfigure and stop wpa_supplicant per wireless interface. # needed because wpa_supplicant lacks hotplugging if [ -z "$wpa_supplicant_conf" ]; then for x in \ /etc/wpa_supplicant/wpa_supplicant-"$interface".conf \ # ex: /etc/wpa_supplicant/wpa_supplicant-wlan0.conf /etc/wpa_supplicant/wpa_supplicant.conf \ /etc/wpa_supplicant-"$interface".conf \ # ex: /etc/wpa_supplicant-wlan0.conf /etc/wpa_supplicant.conf \ ; do if [ -s "$x" ]; then wpa_supplicant_conf="$x" break fi done fi : ${wpa_supplicant_conf:=/etc/wpa_supplicant.conf} wpa_supplicant_ctrldir() { local dir dir=$(key_get_value "[[:space:]]*ctrl_interface=" \ "$wpa_supplicant_conf") dir=$(trim "$dir") case "$dir" in DIR=*) dir=${dir##DIR=} dir=${dir%%[[:space:]]GROUP=*} dir=$(trim "$dir") ;; esac printf %s "$dir" } wpa_supplicant_start() { local dir err errn [ "$ifcarrier" = "up" ] && return 0 # If the carrier is up, don't bother checking anything # Pre flight checks if [ ! -s "$wpa_supplicant_conf" ]; then syslog warn \ "$wpa_supplicant_conf does not exist" syslog warn "not interacting with wpa_supplicant(8)" return 1 fi dir=$(wpa_supplicant_ctrldir) if [ -z "$dir" ]; then syslog warn \ "ctrl_interface not defined in $wpa_supplicant_conf" syslog warn "not interacting with wpa_supplicant(8)" return 1 fi wpa_cli -p "$dir" -i "$interface" status >/dev/null 2>&1 && return 0 syslog info "starting wpa_supplicant" wpa_supplicant_driver="${wpa_supplicant_driver:-nl80211,wext}" driver=${wpa_supplicant_driver:+-D}$wpa_supplicant_driver err=$(wpa_supplicant -B -c"$wpa_supplicant_conf" -i"$interface" "$driver" 2>&1) errn=$? if [ $errn != 0 ]; then syslog err "failed to start wpa_supplicant" syslog err "$err" fi return $errn } wpa_supplicant_reconfigure() { local dir err errn dir=$(wpa_supplicant_ctrldir) [ -z "$dir" ] && return 1 if ! wpa_cli -p "$dir" -i "$interface" status >/dev/null 2>&1; then wpa_supplicant_start return $? fi syslog info "reconfiguring wpa_supplicant" err=$(wpa_cli -p "$dir" -i "$interface" reconfigure 2>&1) errn=$? if [ $errn != 0 ]; then syslog err "failed to reconfigure wpa_supplicant" syslog err "$err" fi return $errn } wpa_supplicant_stop() { local dir err errn dir=$(wpa_supplicant_ctrldir) [ -z "$dir" ] && return 1 wpa_cli -p "$dir" -i "$interface" status >/dev/null 2>&1 || return 0 syslog info "stopping wpa_supplicant" err=$(wpa_cli -i"$interface" terminate 2>&1) errn=$? if [ $errn != 0 ]; then syslog err "failed to start wpa_supplicant" syslog err "$err" fi return $errn } if [ "$ifwireless" = "1" ] && \ type wpa_supplicant >/dev/null 2>&1 && \ type wpa_cli >/dev/null 2>&1 then case "$reason" in PREINIT) wpa_supplicant_start;; RECONFIGURE) wpa_supplicant_reconfigure;; DEPARTED) wpa_supplicant_stop;; esac fi
/lib/dhcpcd/dhcpcd-hooks > cat 20-resolv.conf # Generate /etc/resolv.conf # Support resolvconf(8) if available # merge dhcpcd resolv.conf files into one like resolvconf, # but resolvconf is preferred as other applications like VPN clients can readily hook into it. # Also, resolvconf can configure local nameservers such as bind or dnsmasq. resolv_conf_dir="$state_dir/resolv.conf" NL=" " : ${resolvconf:=resolvconf} build_resolv_conf() { local cf="$state_dir/resolv.conf.$ifname" local interfaces= header= search= srvs= servers= x= interfaces=$(list_interfaces "$resolv_conf_dir") # list of interfaces if [ -n "$interfaces" ]; then # Build the resolv.conf for x in ${interfaces}; do header="$header${header:+, }$x" done ## Build the header domain=$(cd "$resolv_conf_dir"; \ key_get_value "domain " ${interfaces}) ## Build the search list search=$(cd "$resolv_conf_dir"; \ key_get_value "search " ${interfaces}) set -- ${domain} domain="$1" [ -n "$2" ] && search="$search $*" [ -n "$search" ] && search="$(uniqify $search)" [ "$domain" = "$search" ] && search= [ -n "$domain" ] && domain="domain $domain$NL" [ -n "$search" ] && search="search $search$NL" srvs=$(cd "$resolv_conf_dir"; \ key_get_value "nameserver " ${interfaces}) ## Build the nameserver list for x in $(uniqify ${srvs}); do servers="${servers}nameserver $x$NL" done fi header="$signature_base${header:+ $from }$header" # Assemble resolv.conf using our head and tail files [ -f "$cf" ] && rm -f "$cf" [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir" echo "$header" > "$cf" if [ -f /etc/resolv.conf.head ]; then cat /etc/resolv.conf.head >> "$cf" else echo "# /etc/resolv.conf.head can replace this line" >> "$cf" fi printf %s "$domain$search$servers" >> "$cf" if [ -f /etc/resolv.conf.tail ]; then cat /etc/resolv.conf.tail >> "$cf" else echo "# /etc/resolv.conf.tail can replace this line" >> "$cf" fi if change_file /etc/resolv.conf "$cf"; then chmod 644 /etc/resolv.conf fi rm -f "$cf" } # Extract ND DNS options from the RA # ignore the lifetime of the DNS options unless they are absent or zero. # See draft-gont-6man-slaac-dns-config-issues-01 for issues regarding DNS option lifetime in ND messages. eval_nd_dns() { eval ltime=\$nd${i}_rdnss${j}_lifetime if [ -z "$ltime" -o "$ltime" = 0 ]; then rdnss= else eval rdnss=\$nd${i}_rdnss${j}_servers fi eval ltime=\$nd${i}_dnssl${j}_lifetime if [ -z "$ltime" -o "$ltime" = 0 ]; then dnssl= else eval dnssl=\$nd${i}_dnssl${j}_search fi [ -z "$rdnss" -a -z "$dnssl" ] && return 1 [ -n "$rdnss" ] && new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss" [ -n "$dnssl" ] && new_dnssl="$new_dnssl${new_dnssl:+ }$dnssl" j=$(($j + 1)) return 0 } add_resolv_conf() { local x= conf="$signature$NL" warn=true local i j ltime rdnss dnssl new_rdnss new_dnssl i=1 j=1 while true; do # Loop to extract the ND DNS options using our indexed shell values while true; do eval_nd_dns || break done i=$(($i + 1)) j=1 eval_nd_dns || break done [ -n "$new_rdnss" ] && new_domain_name_servers="$new_domain_name_servers${new_domain_name_servers:+ }$new_rdnss" [ -n "$new_dnssl" ] && new_domain_search="$new_domain_search${new_domain_search:+ }$new_dnssl" # Derive a new domain from our various hostname options if [ -z "$new_domain_name" ]; then if [ "$new_dhcp6_fqdn" != "${new_dhcp6_fqdn#*.}" ]; then new_domain_name="${new_dhcp6_fqdn#*.}" elif [ "$new_fqdn" != "${new_fqdn#*.}" ]; then new_domain_name="${new_fqdn#*.}" elif [ "$new_host_name" != "${new_host_name#*.}" ]; then new_domain_name="${new_host_name#*.}" fi fi # If we don't have any configuration, remove it if [ -z "$new_domain_name_servers" -a \ -z "$new_domain_name" -a \ -z "$new_domain_search" ]; then remove_resolv_conf return $? fi if [ -n "$new_domain_name" ]; then set -- $new_domain_name if valid_domainname "$1"; then conf="${conf}domain $1$NL" else syslog err "Invalid domain name: $1" fi # If there is no search this, make this one if [ -z "$new_domain_search" ]; then new_domain_search="$new_domain_name" [ "$new_domain_name" = "$1" ] && warn=true fi fi if [ -n "$new_domain_search" ]; then if valid_domainname_list $new_domain_search; then conf="${conf}search $new_domain_search$NL" elif ! $warn; then syslog err "Invalid domain name in list:" \ "$new_domain_search" fi fi for x in ${new_domain_name_servers}; do conf="${conf}nameserver $x$NL" done if type "$resolvconf" >/dev/null 2>&1; then [ -n "$ifmetric" ] && export IF_METRIC="$ifmetric" printf %s "$conf" | "$resolvconf" -a "$ifname" return $? fi if [ -e "$resolv_conf_dir/$ifname" ]; then rm -f "$resolv_conf_dir/$ifname" fi [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir" printf %s "$conf" > "$resolv_conf_dir/$ifname" build_resolv_conf } remove_resolv_conf() { if type "$resolvconf" >/dev/null 2>&1; then "$resolvconf" -d "$ifname" -f else if [ -e "$resolv_conf_dir/$ifname" ]; then rm -f "$resolv_conf_dir/$ifname" fi build_resolv_conf fi } # For ease of use, map DHCP6 names onto our DHCP4 names case "$reason" in BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) new_domain_name_servers="$new_dhcp6_name_servers" new_domain_search="$new_dhcp6_domain_search" ;; esac if $if_up || [ "$reason" = ROUTERADVERT ]; then add_resolv_conf elif $if_down; then remove_resolv_conf fi
/lib/dhcpcd/dhcpcd-hooks > cat 30-hostname # Set the hostname from DHCP. Can be a short hostname or a FQDN. # hostname_fqdn=true # hostname_fqdn=false # hostname_fqdn=server # 'server' means what the server says, don't manipulate it. # RFC4702 section 3.1 says FQDN should be prefered over hostname. # # the default is hostname_fqdn=true so that a consistent hostname is always assigned. : ${hostname_fqdn:=true} # Some systems don't have hostname(1) _hostname() { local name= if [ -z "$1" ]; then if type hostname >/dev/null 2>&1; then hostname elif [ -r /proc/sys/kernel/hostname ]; then read name /dev/null 2>&1; then sysctl -n kern.hostname elif sysctl kernel.hostname >/dev/null 2>&1; then sysctl -n kernel.hostname else return 1 fi return $? fi # prefer hostname(1) if type hostname >/dev/null 2>&1; then hostname "$1" elif [ -w /proc/sys/kernel/hostname ]; then echo "$1" >/proc/sys/kernel/hostname elif sysctl kern.hostname >/dev/null 2>&1; then sysctl -w "kern.hostname=$1" elif sysctl kernel.hostname >/dev/null 2>&1; then sysctl -w "kernel.hostname=$1" else hostname "$1" # this will fail, with an error to stdout fi } need_hostname() { local hostname hfqdn=false hshort=false case "$force_hostname" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) return 0;; esac hostname="$(_hostname)" case "$hostname" in ""|"(none)"|localhost|localhost.localdomain) return 0;; esac case "$hostname_fqdn" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;; #yestrue ""|[Ss][Ee][Rr][Vv][Ee][Rr]) ;; #server *) hshort=true;; esac if [ -n "$old_fqdn" ]; then if ${hfqdn} || ! ${hsort}; then [ "$hostname" = "$old_fqdn" ] else [ "$hostname" = "${old_fqdn%%.*}" ] fi elif [ -n "$old_host_name" ]; then if ${hfqdn}; then if [ -n "$old_domain_name" -a \ "$old_host_name" = "${old_host_name#*.}" ] then [ "$hostname" = \ "$old_host_name.$old_domain_name" ] else [ "$hostname" = "$old_host_name" ] fi elif ${hshort}; then [ "$hostname" = "${old_host_name%%.*}" ] else [ "$hostname" = "$old_host_name" ] fi else false # No old hostname fi } try_hostname() { if valid_domainname "$1"; then _hostname "$1" else syslog err "Invalid hostname: $1" fi } set_hostname() { local hfqdn=false hshort=false need_hostname || return case "$hostname_fqdn" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;; #yestrue ""|[Ss][Ee][Rr][Vv][Ee][Rr]) ;; #server *) hshort=true;; esac if [ -n "$new_fqdn" ]; then if ${hfqdn} || ! ${hshort}; then try_hostname "$new_fqdn" else try_hostname "${new_fqdn%%.*}" fi elif [ -n "$new_host_name" ]; then if ${hfqdn}; then if [ -n "$new_domain_name" -a \ "$new_host_name" = "${new_host_name#*.}" ] then try_hostname "$new_host_name.$new_domain_name" else try_hostname "$new_host_name" fi elif ${hshort}; then try_hostname "${new_host_name%%.*}" else try_hostname "$new_host_name" fi fi } # For ease of use, map DHCP6 names onto our DHCP4 names case "$reason" in BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) new_fqdn="$new_dhcp6_fqdn" old_fqdn="$old_dhcp6_fqdn" ;; esac if $if_up; then set_hostname fi
/lib/dhcpcd/dhcpcd-hooks > cat 50-ntp.conf # Sample dhcpcd hook script for NTP # configures one of NTP, OpenNTP or Chrony (in that order) # and will default to NTP if no default config is found. # Like resolv.conf hook , store a database of ntp.conf files and merge into /etc/ntp.conf # set $NTP_CONF to override the derived default on systems with >1 NTP client installed. # example for OpenNTP # dhcpcd -e NTP_CONF=/usr/pkg/etc/ntpd.conf # or by adding this to /etc/dhcpcd.conf # env NTP_CONF=/usr/pkg/etc/ntpd.conf # or by adding this to /etc/dhcpcd.enter-hook # NTP_CONF=/usr/pkg/etc/ntpd.conf # To use Chrony change ntpd.conf to chrony.conf in the above . : ${ntp_confs:=ntp.conf ntpd.conf chrony.conf} : ${ntp_conf_dirs=/etc /usr/pkg/etc /usr/local/etc} ntp_conf_dir="$state_dir/ntp.conf" # If NTP_CONF is not set, work out a good default if [ -z "$NTP_CONF" ]; then for d in ${ntp_conf_dirs}; do for f in ${ntp_confs}; do if [ -e "$d/$f" ]; then NTP_CONF="$d/$f" break 2 fi done done [ -e "$NTP_CONF" ] || NTP_CONF=/etc/ntp.conf fi if [ -z "$ntp_service" ]; then # Derive service name from configuration case "$NTP_CONF" in *chrony.conf) ntp_service=chronyd;; *) ntp_service=ntp;; esac fi # Debian has a seperate file for DHCP config to avoid stamping on the master. if [ "$ntp_service" = ntp ] && type invoke-rc.d >/dev/null 2>&1; then [ -e /var/lib/ntp ] || mkdir /var/lib/ntp : ${ntp_service:=ntp} : ${NTP_DHCP_CONF:=/run/ntp.conf.dhcp} fi : ${ntp_restart_cmd:=service_condcommand $ntp_service restart} ntp_conf=${NTP_CONF} NL=" " build_ntp_conf() { local cf="$state_dir/ntp.conf.$ifname" local interfaces= header= srvs= servers= x= # Build a list of interfaces interfaces=$(list_interfaces "$ntp_conf_dir") if [ -n "$interfaces" ]; then for x in ${interfaces}; do header="$header${header:+, }$x" done # Build the header # Build a server list srvs=$(cd "$ntp_conf_dir"; key_get_value "server " $interfaces) if [ -n "$srvs" ]; then for x in $(uniqify $srvs); do servers="${servers}server $x$NL" done fi fi # Merge our config into ntp.conf [ -e "$cf" ] && rm -f "$cf" [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir" if [ -n "$NTP_DHCP_CONF" ]; then [ -e "$ntp_conf" ] && cp "$ntp_conf" "$cf" ntp_conf="$NTP_DHCP_CONF" elif [ -e "$ntp_conf" ]; then remove_markers "$signature_base" "$signature_base_end" \ "$ntp_conf" > "$cf" fi if [ -n "$servers" ]; then echo "$signature_base${header:+ $from }$header" >> "$cf" printf %s "$servers" >> "$cf" echo "$signature_base_end${header:+ $from }$header" >> "$cf" else [ -e "$ntp_conf" -a -e "$cf" ] || return fi # If we changed anything, restart ntpd if change_file "$ntp_conf" "$cf"; then [ -n "$ntp_restart_cmd" ] && eval $ntp_restart_cmd fi } add_ntp_conf() { local cf="$ntp_conf_dir/$ifname" x= [ -e "$cf" ] && rm "$cf" [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir" if [ -n "$new_ntp_servers" ]; then for x in $new_ntp_servers; do echo "server $x" >> "$cf" done fi build_ntp_conf } remove_ntp_conf() { if [ -e "$ntp_conf_dir/$ifname" ]; then rm "$ntp_conf_dir/$ifname" fi build_ntp_conf } # For ease of use, map DHCP6 names onto our DHCP4 names case "$reason" in BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) new_ntp_servers="$new_dhcp6_sntp_servers" ;; esac if $if_up; then add_ntp_conf elif $if_down; then remove_ntp_conf fi
See /etc/dhcp/dhclient-exit-hooks.d
debug -> /etc/dhcp/debug rfc3442-classless-routes timesyncd
cat /etc/dhcp/dhclient-exit-hooks.d/timesyncd TIMESYNCD_CONF=/run/systemd/timesyncd.conf.d/01-dhclient.conf timesyncd_servers_setup_remove() { if [ -e $TIMESYNCD_CONF ]; then rm -f $TIMESYNCD_CONF systemctl try-restart systemd-timesyncd.service || true fi } timesyncd_servers_setup_add() { if [ ! -d /run/systemd/system ]; then return fi if [ -e $TIMESYNCD_CONF ] && [ "$new_ntp_servers" = "$old_ntp_servers" ]; then return fi if [ -z "$new_ntp_servers" ]; then timesyncd_servers_setup_remove return fi mkdir -p $(dirname $TIMESYNCD_CONF) cat <${TIMESYNCD_CONF}.new # NTP server entries received from DHCP server [Time] NTP=$new_ntp_servers EOF mv ${TIMESYNCD_CONF}.new ${TIMESYNCD_CONF} systemctl try-restart systemd-timesyncd.service || true } case $reason in BOUND|RENEW|REBIND|REBOOT) timesyncd_servers_setup_add ;; EXPIRE|FAIL|RELEASE|STOP) timesyncd_servers_setup_remove ;; esac