[pkg-dhcp-devel] Bug#849100: isc-dhcp-client: fails to work properly on dhcp request timeout

Ori Berger debian-bug-reports at orib.net
Thu Dec 22 16:55:26 UTC 2016


Package: isc-dhcp-client
Version: 4.2.4-7ubuntu12.8
Severity: important
Tags: upstream

Dear isc-dhcp-client Maintainer,

There is a small omission in dhclient-script, which may cause a IP address to
be lost, with time to get an IP address again typically taking a 1-7 days (but
possibly a year or more), unless action is taken to reset it, e.g. by
ifdown/ifup on the interface.

* Evidence from syslog

sh[987]: DHCPDISCOVER on enp2s0 to 255.255.255.255 port 67 interval 9
(xid=0x93c8b31e)
dhclient[1001]: No DHCPOFFERS received.
sh[987]: No DHCPOFFERS received.
sh[987]: Trying recorded lease 172.50.55.101
dhclient[1001]: Trying recorded lease 172.50.55.101
sh[987]: PING 172.50.55.1 (172.50.55.1) 56(84) bytes of data.
sh[987]: --- 172.50.55.1 ping statistics ---
sh[987]: 1 packets transmitted, 0 received, +1 errors, 100% packet loss, time
0ms
root: /etc/dhcp/dhclient-exit-hooks returned non-zero exit status 2
dhclient[1001]: bound: renewal in 477651 seconds.
sh[987]: bound: renewal in 477651 seconds.

* How it happens

basically, "if/then/else" in bash eats return codes: The following script is
instructive

----------
#!/bin/sh
return_1() {
    return 1
}

return_1
echo "RC is $?"

if ! return_1; then
  echo "then RC is $?"
else
  echo "else RC is $?"
fi
----------
RC is 1
then RC is 0
----------

Let's assume a machine has an active lease for $interface (whether or not the
interface is already configured), and does not have a file called "/etc/dhcp
/dhcp-exit-hooks". Then, dhclient restarts (e.g., because of a reboot or an
apt-get upgrade)  tries to renew the still valid lease, but times out because
the dhcp server is not responding for whatever reason. What we expect to happen
is a retry until an answer is received, with the interface maintaining its
address (if it has one) until the original lease expires, or continuing to re-
request the address until one is obtained. What actually happens is as follows:

1. dhclient (c side) calls dhclient-script with the TIMEOUT reason and all
parameters reflecting the existing lease.
2. dhclient-script TIMEOUT logic tries to ping the 1st router in the lease and
gets no reply
3. dhclient-script therefore flushes all IPs from interface using "ip -4 addr
flush dev ${interface}", and runs "exit_with_hooks 2"
4. exit_with_hooks initializes exit_status=2, and calls "run_hook /etc/dhcp
/dhclient-exit-hooks"
5. run_hook declares a local exit_status variable but does not initialize it,
so it retains the value 2 from the calling scope.
6. there is no /etc/dhcp/dhcp-exit-hooks script to run, so exit_status=2
remains.
7. run_hook wrongly logs an error in the (nonexistant) /etc/dhcp/dhclient-exit-
hooks using "logger -p"
9. run_hook still returns exit_status=2
10. back in exit_with_hooks, "run_hook" returned a non zero status 2. However,
the "if" test kills the $? return code, and as a result, the "then" part sets
exit_status=0
11. Let's assume that run_hookdir succeeds.
12. exit_with_hooks now exits with exit_status=0, indicating success
13. dhclient (c side) notes the success, assuming that the interface has been
set up (it is not, and even if it were - step 3 above flushes it) and sleeps
until the expiration of the original lease.

Result: we have an interface with no IP address, no attempt to renew it until
the original lease expires. Leases are typically 1-14 days, but some routers
give leases for a year.

* Temporary solution for an affected system (if you can issue commands to it,
e.g. through a console or another interface)

ifdown $interface && ifup $interface

Worked well for the system in which this was discovered.

* Potential Solution

1. In dhclient-script.linux,  intiialize the local exit_status variable to 0,
as shown in the diff below.

----------
 # run given script
 run_hook() {
     local script
-    local exit_status
+    local exit_status=0
     script="$1"

     if [ -f $script ]; then
         . $script
         exit_status=$?
     fi

     if [ -n "$exit_status" ] && [ "$exit_status" -ne 0 ]; then
         logger -p daemon.err "$script returned non-zero exit status
$exit_status"
     fi

     return $exit_status
 }
--------

2. In exit_with_hooks, replace both occurences of the construct

--------
if ! run_hook .... ; then
   exit_status=$?
fi
--------

with something like

--------
$rc = run_hook ....
if [ $rc ne 0 ] then ; exit_status=$rc; fi
--------

* Note: Have not tried it yet in production. My bash/sh/dash is rusty, and I
wanted to report while I test my setting.



-- System Information:
Debian Release: jessie/sid
  APT prefers trusty-updates
  APT policy: (500, 'trusty-updates'), (500, 'trusty-security'), (500, 'trusty'), (100, 'trusty-backports')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 4.4.0-47-generic (SMP w/2 CPU cores)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash

Versions of packages isc-dhcp-client depends on:
ii  debianutils      4.4
ii  iproute2         3.12.0-2ubuntu1
ii  isc-dhcp-common  4.2.4-7ubuntu12.8
ii  libc6            2.19-0ubuntu6.9

isc-dhcp-client recommends no packages.

Versions of packages isc-dhcp-client suggests:
ii  apparmor       2.8.95~2430-0ubuntu5.3
ii  avahi-autoipd  0.6.31-4ubuntu1.1
ii  resolvconf     1.69ubuntu1.1

-- no debconf information



More information about the pkg-dhcp-devel mailing list