dhcpd-run-hooks

Used by dhcpcd to run system and user defined hook scripts.
System hook scripts are found in /lib/dhcpcd/dhcpcd-hooks and
user defined hooks are /etc/dhcpcd.enter-hook and /etc/dhcpcd.exit-hook. (maybe empty)

The default supplies hook scripts for configuring /etc/resolv.conf and the hostname.
May include scripts to configure ntp or ypbind.
A test hook is also supplied that simply echos the dhcp variables to the console 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 held in variables starting with the word old_.

dhcpcd displays the list of variables using -V, --variables

Reasons dhcpcd-run-hooks invoked:

PREINIT starting up
CARRIER detected the carrier is up. generally a notification and no action need be taken.
NOCARRIER lost the carrier. The cable may have been unplugged or association to the wireless point lost.
INFORM
INFORM6
informed a DHCP server about it's address and obtained other configuration details.
BOUND
BOUND6
obtained a new lease from a DHCP server.
RENEW
RENEW6
renewed it's lease.
REBIND
REBIND6
rebound to a new DHCP server.
REBOOT
REBOOT6
successfully requested a lease from a DHCP server.
DELEGATED6 assigned a delegated prefix to the interface.
IPV4LL obtaind an IPV4LL address, or one was removed.
STATIC configured with a static configuration which has not been obtained from a DHCP server.
3RDPARTY monitoring the interface for a 3rd party to give it an IP address.
TIMEOUT failed to contact any DHCP servers but was able to use an old lease.
EXPIRE
EXPIRE6
lease or state expired and it failed to obtain a new one.
NAK received a NAK from the DHCP server. This should be treated as EXPIRE.
RECONFIGURE instructed to reconfigure an interface.
ROUTERADVERT received an IPv6 Router Advertisement, or one has expired.
STOP
STOP6
stopped running on the interface.
STOPPED stopped entirely.
DEPARTED interface has been removed.
FAIL failed to operate on the interface. dhcpcd does not support the raw interface, which means it cannot work as a DHCP or ZeroConf client. Static configuration and DHCP INFORM is still allowed.
DUMP the last lease for the interface.
TEST received an OFFER from a DHCP server but will not configure the interface.
test that variables are filled correctly for the script to process them.

ENVIRONMENT

dhcpcd will clear the environment variables aside from $PATH and $RC_SVCNAME. The following variables will then be set, along with any protocol supplied ones.
$interface the name of the interface.
$reason as described above.
$pid the pid of dhcpcd.
$ifcarrier the link status of $interface: unknown, up or down.
$ifmetric $interface preference, lower is better.
$ifwireless 1 if $interface is wireless, otherwise 0.
$ifflags $interface flags.
$ifmtu $interface MTU.
$ifssid the name of the SSID the interface is connected to.
$interface_order A list of interfaces, in order of preference.
$if_up true if the interface is up, otherwise false.
$if_down true if the interface is down, otherwise false.
$af_waiting Address family waiting for, as defined in dhcpcd.conf(5).
$profile the name of the profile selected from dhcpcd.conf(5).
$new_delegated_dhcp6_prefix space separated list of delegated prefixes.

FILES

Loads
/etc/dhcpcd.enter-hook and any scripts found in
/lib/dhcpcd/dhcpcd-hooks in a lexical order and then finally
/etc/dhcpcd.exit-hook

See

dhcpcd(8)

SECURITY CONSIDERATIONS

dhcpcd will validate the content of each option against its encoding. For string, ascii, raw or binhex encoding it's up to the user to validate it for the intended purpose. When used in a shell script, each variable must be quoted correctly.


/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. # This is needed because wpa_supplicant lacks hotplugging of any kind # and the user should not be expected to have to wire it into their system # if the base system doesn't do this itself. 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 # We can merge other 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. This is important as the libc resolver isn't that powerful. 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= # Build a list of interfaces interfaces=$(list_interfaces "$resolv_conf_dir") # Build the resolv.conf if [ -n "$interfaces" ]; then # Build the header for x in ${interfaces}; do header="$header${header:+, }$x" done # Build the search list domain=$(cd "$resolv_conf_dir"; \ key_get_value "domain " ${interfaces}) 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" # Build the nameserver list srvs=$(cd "$resolv_conf_dir"; \ key_get_value "nameserver " ${interfaces}) 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 any ND DNS options from the RA # For now, we ignore the lifetime of the DNS options unless they are absent or zero. # In this case they are removed from consideration. # 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 # Loop to extract the ND DNS options using our indexed shell values i=1 j=1 while true; do 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 data if required # A hostname can either be a short hostname or a FQDN. # hostname_fqdn=true # hostname_fqdn=false # hostname_fqdn=server # A value of server means just what the server says, don't manipulate it. # This could lead to an inconsistent hostname on a DHCPv4 and DHCPv6 network # where the DHCPv4 hostname is short and the DHCPv6 has an FQDN. # DHCPv6 has no hostname option. # RFC4702 section 3.1 says FQDN should be prefered over hostname. # # As such, 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 # Always prefer hostname(1) if we have it 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" # We know this will fail, but it will now 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 # It will configure either one of NTP, OpenNTP or Chrony (in that order) # and will default to NTP if no default config is found. # Like our resolv.conf hook script, we store a database of ntp.conf files and merge into /etc/ntp.conf # You can set the env var NTP_CONF to override the derived default on systems with >1 NTP client installed. # Here is an 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 instead, simply change ntpd.conf to chrony.conf in the # above examples. : ${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 # Derive service name from configuration if [ -z "$ntp_service" ]; then 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