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. 33ip=/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. 37make_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 112set_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 131run_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 148run_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. 164exit_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. 183add_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. 231run_hook /etc/dhclient-enter-hooks 232run_hookdir /etc/dhclient-enter-hooks.d 233 234# Execute the operation 235case "$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 V6ONLY) 404 if [ -n "$old_ip_address" ]; then 405 # flush leased IP 406 ${ip} -4 addr flush dev ${interface} label ${interface} 407 fi 408 409 ;; 410 411 ### DHCPv6 Handlers 412 # TODO handle prefix change: ?based on ${old_ip6_prefix} and ${new_ip6_prefix}? 413 414 PREINIT6) 415 # ensure interface is up 416 ${ip} link set ${interface} up 417 418 # We need to give the kernel some time to active interface 419 interface_up_wait_time=5 420 for i in $(seq 0 ${interface_up_wait_time}) 421 do 422 ${ip} link show dev ${interface} | grep -q LOWER_UP 2>&1 423 if [ $? -eq 0 ]; then 424 break; 425 fi 426 sleep 1 427 done 428 429 # flush any stale global permanent IPs from interface 430 ${ip} -6 addr flush dev ${interface} scope global permanent 431 432 # Wait for duplicate address detection for this interface if the 433 # --dad-wait-time parameter has been specified and is greater than 434 # zero. 435 if [ ${dad_wait_time} -gt 0 ]; then 436 # Check if any IPv6 address on this interface is marked as 437 # tentative. 438 ${ip} addr show ${interface} | grep inet6 | grep tentative \ 439 &> /dev/null 440 if [ $? -eq 0 ]; then 441 # Wait for duplicate address detection to complete or for 442 # the timeout specified as --dad-wait-time. 443 for i in $(seq 0 $dad_wait_time) 444 do 445 # We're going to poll for the tentative flag every second. 446 sleep 1 447 ${ip} addr show ${interface} | grep inet6 | grep tentative \ 448 &> /dev/null 449 if [ $? -ne 0 ]; then 450 break; 451 fi 452 done 453 fi 454 fi 455 456 ;; 457 458 BOUND6|RENEW6|REBIND6) 459 if [ "${new_ip6_address}" ] && [ "${new_ip6_prefixlen}" ]; then 460 # set leased IP 461 add_ipv6_addr_with_DAD 462 fi 463 464 # update /etc/resolv.conf 465 if [ "${reason}" = BOUND6 ] || 466 [ "${new_dhcp6_name_servers}" != "${old_dhcp6_name_servers}" ] || 467 [ "${new_dhcp6_domain_search}" != "${old_dhcp6_domain_search}" ]; then 468 make_resolv_conf 469 fi 470 471 ;; 472 473 DEPREF6) 474 if [ -z "${cur_ip6_prefixlen}" ]; then 475 exit_with_hooks 2 476 fi 477 478 # set preferred lifetime of leased IP to 0 479 ${ip} -6 addr change ${cur_ip6_address}/${cur_ip6_prefixlen} \ 480 dev ${interface} scope global preferred_lft 0 481 482 ;; 483 484 EXPIRE6|RELEASE6|STOP6) 485 if [ -z "${old_ip6_address}" ] || [ -z "${old_ip6_prefixlen}" ]; then 486 exit_with_hooks 2 487 fi 488 489 # delete leased IP 490 ${ip} -6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \ 491 dev ${interface} 492 493 ;; 494esac 495 496exit_with_hooks 0 497