Subject: Re: Dhclient weirdness
To: None <netbsd-users@netbsd.org>
From: Christos Zoulas <christos@zoulas.com>
List: netbsd-users
Date: 04/16/2003 23:37:53
In article <Pine.NEB.4.53.0304161657390.16358@frear.com>,
John <from_netbsd@frear.com> wrote:

Send-pr please.

christos

>Hello!
>
>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
>/sbin/dhclient-script.
>
>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??
>Thanks!
>
>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
>
>----------------------------------------------------------------------------
>
>#!/bin/sh
>
># 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
>fi
>
>
># Function to check for a valid IP address...
>#
># Inputs like " 1.2.3.4" and "1.2.3.4 " will fail, so will "255.255.300.255".
># Of course, "255.255.255.255" and "0.0.0.0" and everything in between work.
>#
># Returns 0 on valid input, 1 for a failure
>#
>Verify_IP_Addr() {
>	local IP_NUM_REGEX IP_REGEX REGEX_RESULT
>	if [ $# -ne 1 ]; then
>		return 1
>	fi
>	IP_NUM_REGEX='([0-9]|[0-9][0-9]|[0,1][0-9][0-9]|2[0-4][0-9]|25[0-5])'
>	IP_REGEX="^$IP_NUM_REGEX\.$IP_NUM_REGEX\.$IP_NUM_REGEX\.$IP_NUM_REGEX$"
>	#
>	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
>	else
>		return 0 # The argument is indeed strictly an IP
>	fi
>}
>
>
># 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
>	old_ip_address="$real_old_ip_address"
>	export old_ip_address
>fi
>
># 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
>	old_routers="$real_old_routers"
>	export old_routers
>fi
>
>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
>#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