Subject: Dhclient weirdness
To: None <>
From: John <>
List: netbsd-users
Date: 04/16/2003 17:30:34

I ran across some a very strange dhclient problem and went to some effort
to fix it -- I want to share my fix with the public.
I haven't decided if this is a bug in dhclient or not.

My netbsd 1.6 i386 machine has two interfaces, an rtk on a private network
with a static ip and an fxp on the internet with a dynamic ip.  Naturally,
I want my routing table's default route to be the one assigned via dhcp to
the fxp interface.

The problem was, my IP changed and dhclient automatically reconfigured the
fxp to the new IP, but it did not alter the default route.  (Which caused
loss of inet connectivity, needless to say.)

I determined this is due to the environment that /sbin/dhclient passes to

I have control of the dhcp server (its a dsl router-gadget) and can
instruct it to assign the fxp a public IP or a private network IP.  When
transitioning from a public IP to a private one, the environment passed to
dhclient-script does not include the variables old_ip_address or
old_routers, and so it decides not to adjust the default route.  During
transition from a private IP to a public one, the environment does include
those two variables, but they are wrong.  old_ip_address incorrectly
equals new_ip_address and the same goes for old_routers.  So, it seems the
problem is influenced by the dhcp server device, although /sbin/dhclient
should still set the correct old_ values regardless.

I fixed the problem by writing a /etc/dhclient-enter-hooks script. The
hook script runs before dhclient-script makes any changes to the active
config, and if it is run during a BOUND event it uses ifconfig to
determine the fxp's current IP address, and it uses "netstat -nr" to
determine the current default route.  It then assigns these determined
values to the variables old_ip_address and old_routers. This has caused
dhclient-script to work correctly in all my testing.

If someone would be kind enough to let me know if I ougth to submit a PR,
I will be happy to do so!  Should I also forward this to the ISC??

Oh yeah.. I might have hit upon a second unrelated bug.. In my
dhclient-enter-hooks script, I used a command like:

old_ip_address=`ifconfig fxp0 | grep inet | head -1 | cut -d\  -f 2`

and this command works great during interactive testing, but results in an
empty old_ip_address variable when the script is run via dhclient-script.
I fixed the problem with:

old_ip_address1=`ifconfig fxp0 | grep inet | head -1`
old_ip_address=`echo $old_ip_address1 | cut -d\  -f 2`
unset old_ip_address1

Very strange..

Thanks again netbsd community!!

Fyi, my dhclient-enter-hooks script is attached.

- John



# It seems that if the IP we had been assigned is changed, there is a problem.
# NetBSD 1.6's /sbin/dhclient-script doesn't want to set the default route
# for a primary interface change in some circumstances.
# This script detects that situation and corrects it.
# For the detection part, the env during a failure looks like this:
#	reason=BOUND
#	old_ip_address != our current ip
#	old_routers != the real default route
# For the correction part, we need to correctly set the variables
#	old_ip_address
#	old_routers

# First things first, the only dhclient case we need to fix is the BOUND case
# (ip addr chg).  We'll do this at the VERY START because 99% of the times
# we are run will be reason=RENEW.
if [ \( "x$reason" != "xBOUND" \) ]; then
	return 0

# Function to check for a valid IP address...
# Inputs like "" and " " will fail, so will "255.255.300.255".
# Of course, "" and "" and everything in between work.
# Returns 0 on valid input, 1 for a failure
Verify_IP_Addr() {
	if [ $# -ne 1 ]; then
		return 1
	REGEX_RESULT="`echo "$1" | egrep "$IP_REGEX" > /dev/null 2>&1 ;echo $?`"
	if [ "$REGEX_RESULT" -ne 0 ]; then
		# echo Fatal: Couldn\'t determine IP\! >&2
		# exit 1
		return 1 # Signal a failure
		return 0 # The argument is indeed strictly an IP

# Load our existing IP address... Store it as $old_ip_address

# The line imm. below is buggy, so we use two vars and it works.. Humm??
#real_old_ip_address="`ifconfig $interface |grep inet| head -1 | cut -d\  -f 2`"
real_old_ip_address1="`ifconfig $interface |grep inet| head -1`"
real_old_ip_address="`echo $real_old_ip_address1 | cut -d\  -f 2`"
unset real_old_ip_address1
if Verify_IP_Addr "$real_old_ip_address"; then
	export old_ip_address

# Load our default route's IP addres...  Store it as $old_routers
# Similarly, the line below is buggy, so the two var approach fixes it..
#real_old_routers="`netstat -nr | grep default | sed 's/^ *default *//' | \
#	cut -d\  -f 1`"
real_old_routers1="`netstat -nr | grep default | head -1`"
real_old_routers="`echo $real_old_routers1 | sed 's/^ *default *//' | cut -d\  -f 1`"
unset real_old_routers1
if Verify_IP_Addr "$real_old_routers"; then
	export old_routers

unset real_old_ip_address real_old_routers

# This is debugging stuff -- just ignore all of this.
# (Yes, really debugging - I think we hit a bug in /bin/sh or /usr/bin/head)
#echo Date is `date`
#real_old_ip_address1="`ifconfig $interface |grep inet| head -1`"
#real_old_ip_address="`echo $real_old_ip_address1 | cut -d\  -f 2`"
#unset real_old_ip_address1
#real_old_routers1="`netstat -nr | grep default | head -1`"
#real_old_routers="`echo $real_old_routers1 | sed 's/^ *default *//' | cut -d\  -f 1`"
#unset real_old_routers1
#echo Got \[$real_old_ip_address\] and \[$real_old_routers\]
#echo Was given \[$old_ip_address\] and \[$old_routers\]
#unset real_old_ip_address real_old_routers
#) >> /tmp/out 2>&1