Home | History | Annotate | Line # | Download | only in scripts
linux revision 1.1.1.1
      1 #!/bin/bash
      2 # dhclient-script for Linux. Dan Halbert, March, 1997.
      3 # Updated for Linux 2.[12] by Brian J. Murrell, January 1999.
      4 # No guarantees about this. I'm a novice at the details of Linux
      5 # networking.
      6 
      7 # Notes:
      8 
      9 # 0. This script is based on the netbsd script supplied with dhcp-970306.
     10 
     11 # 1. ifconfig down apparently deletes all relevant routes and flushes
     12 # the arp cache, so this doesn't need to be done explicitly.
     13 
     14 # 2. The alias address handling here has not been tested AT ALL.
     15 # I'm just going by the doc of modern Linux ip aliasing, which uses
     16 # notations like eth0:0, eth0:1, for each alias.
     17 
     18 # 3. I have to calculate the network address, and calculate the broadcast
     19 # address if it is not supplied. This might be much more easily done
     20 # by the dhclient C code, and passed on.
     21 
     22 # 4. TIMEOUT not tested. ping has a flag I don't know, and I'm suspicious
     23 # of the $1 in its args.
     24 
     25 # 5. Script refresh in 2017. The aliasing code was too convoluted and needs
     26 # to go away. Migrated DHCPv4 script to ip command from iproute2 suite.
     27 # This is based on Debian script with some tweaks. ifconfig is no longer
     28 # used. Everything is done using ip tool from ip-route2.
     29 
     30 # 'ip' just looks too weird. Also, we now have unit-tests! Those unit-tests
     31 # overwirte this line to use a fake ip-echo tool. It's also convenient
     32 # if your system holds ip tool in a non-standard location.
     33 ip=/sbin/ip
     34 
     35 # update /etc/resolv.conf based on received values
     36 # This updated version mostly follows Debian script by Andrew Pollock et al.
     37 make_resolv_conf() {
     38     local new_resolv_conf
     39 
     40     # DHCPv4
     41     if [ -n "$new_domain_search" ] || [ -n "$new_domain_name" ] ||
     42        [ -n "$new_domain_name_servers" ]; then
     43         new_resolv_conf=/etc/resolv.conf.dhclient-new
     44         rm -f $new_resolv_conf
     45 
     46         if [ -n "$new_domain_name" ]; then
     47             echo domain ${new_domain_name%% *} >>$new_resolv_conf
     48         fi
     49 
     50         if [ -n "$new_domain_search" ]; then
     51             if [ -n "$new_domain_name" ]; then
     52                 domain_in_search_list=""
     53                 for domain in $new_domain_search; do
     54                     if [ "$domain" = "${new_domain_name}" ] ||
     55                        [ "$domain" = "${new_domain_name}." ]; then
     56                         domain_in_search_list="Yes"
     57                     fi
     58                 done
     59                 if [ -z "$domain_in_search_list" ]; then
     60                     new_domain_search="$new_domain_name $new_domain_search"
     61                 fi
     62             fi
     63             echo "search ${new_domain_search}" >> $new_resolv_conf
     64         elif [ -n "$new_domain_name" ]; then
     65             echo "search ${new_domain_name}" >> $new_resolv_conf
     66         fi
     67 
     68         if [ -n "$new_domain_name_servers" ]; then
     69             for nameserver in $new_domain_name_servers; do
     70                 echo nameserver $nameserver >>$new_resolv_conf
     71             done
     72         else # keep 'old' nameservers
     73             sed -n /^\w*[Nn][Aa][Mm][Ee][Ss][Ee][Rr][Vv][Ee][Rr]/p /etc/resolv.conf >>$new_resolv_conf
     74         fi
     75 
     76 	if [ -f /etc/resolv.conf ]; then
     77 	    chown --reference=/etc/resolv.conf $new_resolv_conf
     78 	    chmod --reference=/etc/resolv.conf $new_resolv_conf
     79 	fi
     80         mv -f $new_resolv_conf /etc/resolv.conf
     81     # DHCPv6
     82     elif [ -n "$new_dhcp6_domain_search" ] || [ -n "$new_dhcp6_name_servers" ]; then
     83         new_resolv_conf=/etc/resolv.conf.dhclient-new
     84         rm -f $new_resolv_conf
     85 
     86         if [ -n "$new_dhcp6_domain_search" ]; then
     87             echo "search ${new_dhcp6_domain_search}" >> $new_resolv_conf
     88         fi
     89 
     90         if [ -n "$new_dhcp6_name_servers" ]; then
     91             for nameserver in $new_dhcp6_name_servers; do
     92                 # append %interface to link-local-address nameservers
     93                 if [ "${nameserver##fe80::}" != "$nameserver" ] ||
     94                    [ "${nameserver##FE80::}" != "$nameserver" ]; then
     95                     nameserver="${nameserver}%${interface}"
     96                 fi
     97                 echo nameserver $nameserver >>$new_resolv_conf
     98             done
     99         else # keep 'old' nameservers
    100             sed -n /^\w*[Nn][Aa][Mm][Ee][Ss][Ee][Rr][Vv][Ee][Rr]/p /etc/resolv.conf >>$new_resolv_conf
    101         fi
    102 
    103 	if [ -f /etc/resolv.conf ]; then
    104             chown --reference=/etc/resolv.conf $new_resolv_conf
    105             chmod --reference=/etc/resolv.conf $new_resolv_conf
    106 	fi
    107         mv -f $new_resolv_conf /etc/resolv.conf
    108     fi
    109 }
    110 
    111 # set host name
    112 set_hostname() {
    113     local current_hostname
    114 
    115     if [ -n "$new_host_name" ]; then
    116         current_hostname=$(hostname)
    117 
    118         # current host name is empty, '(none)' or 'localhost' or differs from new one from DHCP
    119         if [ -z "$current_hostname" ] ||
    120            [ "$current_hostname" = '(none)' ] ||
    121            [ "$current_hostname" = 'localhost' ] ||
    122            [ "$current_hostname" = "$old_host_name" ]; then
    123            if [ "$new_host_name" != "$old_host_name" ]; then
    124                hostname "$new_host_name"
    125            fi
    126         fi
    127     fi
    128 }
    129 
    130 # run given script
    131 run_hook() {
    132     local script
    133     local exit_status
    134     script="$1"
    135 
    136     if [ -f $script ]; then
    137         . $script
    138     fi
    139 
    140     if [ -n "$exit_status" ] && [ "$exit_status" -ne 0 ]; then
    141         logger -p daemon.err "$script returned non-zero exit status $exit_status"
    142     fi
    143 
    144     return $exit_status
    145 }
    146 
    147 # run scripts in given directory
    148 run_hookdir() {
    149     local dir
    150     local exit_status
    151     dir="$1"
    152 
    153     if [ -d "$dir" ]; then
    154         for script in $(run-parts --list $dir); do
    155             run_hook $script || true
    156             exit_status=$?
    157         done
    158     fi
    159 
    160     return $exit_status
    161 }
    162 
    163 # Must be used on exit.   Invokes the local dhcp client exit hooks, if any.
    164 exit_with_hooks() {
    165     exit_status=$1
    166 
    167     # Source the documented exit-hook script, if it exists
    168     if ! run_hook /etc/dhclient-exit-hooks; then
    169         exit_status=$?
    170     fi
    171 
    172     # Now run scripts in the Debian-specific directory.
    173     if ! run_hookdir /etc/dhclient-exit-hooks.d; then
    174         exit_status=$?
    175     fi
    176 
    177     exit $exit_status
    178 }
    179 
    180 # This function was largely borrowed from dhclient-script that
    181 # ships with Centos, authored by Jiri Popelka and David Cantrell
    182 # of Redhat. Thanks guys.
    183 add_ipv6_addr_with_DAD() {
    184     ${ip} -6 addr replace ${new_ip6_address}/${new_ip6_prefixlen} \
    185     dev ${interface} scope global valid_lft ${new_max_life} \
    186         preferred_lft ${new_preferred_life}
    187 
    188     if [ ${dad_wait_time} -le 0 ]
    189     then
    190         # if we're not waiting for DAD, assume we're good
    191         return 0
    192     fi
    193 
    194     # Repeatedly test whether newly added address passed
    195     # duplicate address detection (DAD)
    196     for i in $(seq 1 ${dad_wait_time}); do
    197         sleep 1 # give the DAD some time
    198 
    199         addr=$(${ip} -6 addr show dev ${interface} \
    200             | grep ${new_ip6_address}/${new_ip6_prefixlen})
    201 
    202         # tentative flag == DAD is still not complete
    203         tentative=$(echo "${addr}" | grep tentative)
    204         # dadfailed flag == address is already in use somewhere else
    205         dadfailed=$(echo "${addr}" | grep dadfailed)
    206 
    207         if [ -n "${dadfailed}" ] ; then
    208             # address was added with valid_lft/preferred_lft 'forever',
    209             # remove it
    210             ${ip} -6 addr del ${new_ip6_address}/${new_ip6_prefixlen} \
    211                 dev ${interface}
    212 
    213             exit_with_hooks 3
    214         fi
    215 
    216         if [ -z "${tentative}" ] ; then
    217             if [ -n "${addr}" ]; then
    218                 # DAD is over
    219                     return 0
    220             else
    221                 # address was auto-removed (or not added at all)
    222                 exit_with_hooks 3
    223             fi
    224         fi
    225     done
    226 
    227     return 0
    228 }
    229 
    230 # Invoke the local dhcp client enter hooks, if they exist.
    231 run_hook /etc/dhclient-enter-hooks
    232 run_hookdir /etc/dhclient-enter-hooks.d
    233 
    234 # Execute the operation
    235 case "$reason" in
    236 
    237     ### DHCPv4 Handlers
    238 
    239     MEDIUM|ARPCHECK|ARPSEND)
    240         # Do nothing
    241         ;;
    242     PREINIT)
    243         # The DHCP client is requesting that an interface be
    244         # configured as required in order to send packets prior to
    245         # receiving an actual address. - dhclient-script(8)
    246 
    247         # ensure interface is up
    248         ${ip} link set dev ${interface} up
    249 
    250         if [ -n "$alias_ip_address" ]; then
    251             # flush alias IP from interface
    252             ${ip} -4 addr flush dev ${interface} label ${interface}:0
    253         fi
    254 
    255         ;;
    256 
    257     BOUND|RENEW|REBIND|REBOOT)
    258         set_hostname
    259 
    260         if [ -n "$old_ip_address" ] && [ -n "$alias_ip_address" ] &&
    261            [ "$alias_ip_address" != "$old_ip_address" ]; then
    262             # alias IP may have changed => flush it
    263             ${ip} -4 addr flush dev ${interface} label ${interface}:0
    264         fi
    265 
    266         if [ -n "$old_ip_address" ] &&
    267            [ "$old_ip_address" != "$new_ip_address" ]; then
    268             # leased IP has changed => flush it
    269             ${ip} -4 addr flush dev ${interface} label ${interface}
    270         fi
    271 
    272         if [ -z "$old_ip_address" ] ||
    273            [ "$old_ip_address" != "$new_ip_address" ] ||
    274            [ "$reason" = "BOUND" ] || [ "$reason" = "REBOOT" ]; then
    275             # new IP has been leased or leased IP changed => set it
    276             ${ip} -4 addr add ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \
    277                 ${new_broadcast_address:+broadcast $new_broadcast_address} \
    278                 dev ${interface} label ${interface}
    279 
    280             if [ -n "$new_interface_mtu" ]; then
    281                 # set MTU
    282                 ${ip} link set dev ${interface} mtu ${new_interface_mtu}
    283             fi
    284 
    285 	    # if we have $new_rfc3442_classless_static_routes then we have to
    286 	    # ignore $new_routers entirely
    287 	    if [ ! "$new_rfc3442_classless_static_routes" ]; then
    288 		    # set if_metric if IF_METRIC is set or there's more than one router
    289 		    if_metric="$IF_METRIC"
    290 		    if [ "${new_routers%% *}" != "${new_routers}" ]; then
    291 			if_metric=${if_metric:-1}
    292 		    fi
    293 
    294 		    for router in $new_routers; do
    295 			if [ "$new_subnet_mask" = "255.255.255.255" ]; then
    296 			    # point-to-point connection => set explicit route
    297 			    ${ip} -4 route add ${router} dev $interface >/dev/null 2>&1
    298 			fi
    299 
    300 			# set default route
    301 			${ip} -4 route add default via ${router} dev ${interface} \
    302 			    ${if_metric:+metric $if_metric} >/dev/null 2>&1
    303 
    304 			if [ -n "$if_metric" ]; then
    305 			    if_metric=$((if_metric+1))
    306 			fi
    307 		    done
    308 	    fi
    309         fi
    310 
    311         if [ -n "$alias_ip_address" ] &&
    312            [ "$new_ip_address" != "$alias_ip_address" ]; then
    313             # separate alias IP given, which may have changed
    314             # => flush it, set it & add host route to it
    315             ${ip} -4 addr flush dev ${interface} label ${interface}:0
    316             ${ip} -4 addr add ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \
    317                 dev ${interface} label ${interface}:0
    318             ${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1
    319         fi
    320 
    321         # update /etc/resolv.conf
    322         make_resolv_conf
    323 
    324         ;;
    325 
    326     EXPIRE|FAIL|RELEASE|STOP)
    327         if [ -n "$alias_ip_address" ]; then
    328             # flush alias IP
    329             ${ip} -4 addr flush dev ${interface} label ${interface}:0
    330         fi
    331 
    332         if [ -n "$old_ip_address" ]; then
    333             # flush leased IP
    334             ${ip} -4 addr flush dev ${interface} label ${interface}
    335         fi
    336 
    337         if [ -n "$alias_ip_address" ]; then
    338             # alias IP given => set it & add host route to it
    339             ${ip} -4 addr add ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \
    340                 dev ${interface} label ${interface}:0
    341             ${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1
    342         fi
    343 
    344         ;;
    345 
    346     TIMEOUT)
    347         if [ -n "$alias_ip_address" ]; then
    348             # flush alias IP
    349             ${ip} -4 addr flush dev ${interface} label ${interface}:0
    350         fi
    351 
    352         # set IP from recorded lease
    353         ${ip} -4 addr add ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \
    354             ${new_broadcast_address:+broadcast $new_broadcast_address} \
    355             dev ${interface} label ${interface}
    356 
    357         if [ -n "$new_interface_mtu" ]; then
    358             # set MTU
    359             ${ip} link set dev ${interface} mtu ${new_interface_mtu}
    360         fi
    361 
    362         # if there is no router recorded in the lease or the 1st router answers pings
    363         if [ -z "$new_routers" ] || ping -q -c 1 "${new_routers%% *}"; then
    364 	    # if we have $new_rfc3442_classless_static_routes then we have to
    365 	    # ignore $new_routers entirely
    366 	    if [ ! "$new_rfc3442_classless_static_routes" ]; then
    367 		    if [ -n "$alias_ip_address" ] &&
    368 		       [ "$new_ip_address" != "$alias_ip_address" ]; then
    369 			# separate alias IP given => set up the alias IP & add host route to it
    370 			${ip} -4 addr add \
    371                               ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \
    372 			      dev ${interface} label ${interface}:0
    373 			${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1
    374 		    fi
    375 
    376 		    # set if_metric if IF_METRIC is set or there's more than one router
    377 		    if_metric="$IF_METRIC"
    378 		    if [ "${new_routers%% *}" != "${new_routers}" ]; then
    379 			if_metric=${if_metric:-1}
    380 		    fi
    381 
    382 		    # set default route
    383 		    for router in $new_routers; do
    384 			${ip} -4 route add default via ${router} dev ${interface} \
    385 			    ${if_metric:+metric $if_metric} >/dev/null 2>&1
    386 
    387 			if [ -n "$if_metric" ]; then
    388 			    if_metric=$((if_metric+1))
    389 			fi
    390 		    done
    391 	    fi
    392 
    393             # update /etc/resolv.conf
    394             make_resolv_conf
    395         else
    396             # flush all IPs from interface
    397             ip -4 addr flush dev ${interface}
    398             exit_with_hooks 2
    399         fi
    400 
    401         ;;
    402 
    403     ### DHCPv6 Handlers
    404     # TODO handle prefix change: ?based on ${old_ip6_prefix} and ${new_ip6_prefix}?
    405 
    406     PREINIT6)
    407         # ensure interface is up
    408         ${ip} link set ${interface} up
    409 
    410         # We need to give the kernel some time to active interface
    411         interface_up_wait_time=5
    412         for i in $(seq 0 ${interface_up_wait_time})
    413         do
    414             ifconfig ${interface} | grep RUNNING >/dev/null 2>&1
    415             if [ $? -eq 0 ]; then
    416                 break;
    417             fi
    418             sleep 1
    419         done
    420 
    421         # flush any stale global permanent IPs from interface
    422         ${ip} -6 addr flush dev ${interface} scope global permanent
    423 
    424         # Wait for duplicate address detection for this interface if the
    425         # --dad-wait-time parameter has been specified and is greater than
    426         # zero.
    427         if [ ${dad_wait_time} -gt 0 ]; then
    428             # Check if any IPv6 address on this interface is marked as
    429             # tentative.
    430             ${ip} addr show ${interface} | grep inet6 | grep tentative \
    431                 &> /dev/null
    432             if [ $? -eq 0 ]; then
    433                 # Wait for duplicate address detection to complete or for
    434                 # the timeout specified as --dad-wait-time.
    435                 for i in $(seq 0 $dad_wait_time)
    436                 do
    437                     # We're going to poll for the tentative flag every second.
    438                     sleep 1
    439                     ${ip} addr show ${interface} | grep inet6 | grep tentative \
    440                         &> /dev/null
    441                     if [ $? -ne 0 ]; then
    442                         break;
    443                     fi
    444                 done
    445             fi
    446         fi
    447 
    448         ;;
    449 
    450     BOUND6|RENEW6|REBIND6)
    451         if [ "${new_ip6_address}" ] && [ "${new_ip6_prefixlen}" ]; then
    452             # set leased IP
    453             add_ipv6_addr_with_DAD
    454         fi
    455 
    456         # update /etc/resolv.conf
    457         if [ "${reason}" = BOUND6 ] ||
    458            [ "${new_dhcp6_name_servers}" != "${old_dhcp6_name_servers}" ] ||
    459            [ "${new_dhcp6_domain_search}" != "${old_dhcp6_domain_search}" ]; then
    460             make_resolv_conf
    461         fi
    462 
    463         ;;
    464 
    465     DEPREF6)
    466         if [ -z "${cur_ip6_prefixlen}" ]; then
    467             exit_with_hooks 2
    468         fi
    469 
    470         # set preferred lifetime of leased IP to 0
    471         ${ip} -6 addr change ${cur_ip6_address}/${cur_ip6_prefixlen} \
    472             dev ${interface} scope global preferred_lft 0
    473 
    474         ;;
    475 
    476     EXPIRE6|RELEASE6|STOP6)
    477         if [ -z "${old_ip6_address}" ] || [ -z "${old_ip6_prefixlen}" ]; then
    478             exit_with_hooks 2
    479         fi
    480 
    481         # delete leased IP
    482         ${ip} -6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \
    483             dev ${interface}
    484 
    485         ;;
    486 esac
    487 
    488 exit_with_hooks 0
    489