dhcpd-run-hooks

Used by dhcpcd to run hook scripts found in

  • /lib/dhcpcd/dhcpcd-hooks and
    user defined ones in
  • /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 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.
    These will be set, along with any protocol supplied ones.
    $interface_order list of interfaces, in order of preference.
     
    $interface
    $reason
    $profile name of the profile selected from dhcpcd.conf(5).
    $ifcarrier link status : unknown, up or down.
    $ifmetric $interface preference, lower is better.
    $ifwireless 1 if wireless, otherwise 0.
    $ifflags
    $ifmtu
    $ifssid
    $if_up true | false.
    $if_down true | false.
    $af_waiting Address family waiting for, as defined in dhcpcd.conf(5).
    $new_delegated_dhcp6_prefix of delegated prefixes.
    $pid pid of dhcpcd.

    FILES

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

    See

    dhcpcd(8)


    /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