DHCP error in BOOTP.LIB

We’ve discovered a bug in the bootp.lib which is supplied with Dynamic C. Whenever a DHCP server sends a NAK to the client when renewing / rebinding it’s ip adress, it stops functioning correctly. We discovered this problem because of problems we have with rabbit modules receiving duplicate ip addresses.

This problem was already communicated with the rabbit semiconductor support team, but other might have the same problem, so I’m posting it here.

The problem (and solution) are described below.

Module used: Rabbit Core Module 3700
Dynamic C version 9.50

used BOOTP_VERBOSE for DHCP tracking

First we set a IP range of 192.168.0.170-…180 for the rabbit module. The module receives an IP adres:

DHCP: Sending DISCOVER request on i/f 0
DHCP: incoming on i/f 0
DHCP: offered C0A800AF
DHCP: Sending REQUEST on i/f 0
DHCP: incoming on i/f 0
DHCP: Setting results for i/f 0…
lease=60 sec
IP=C0A800AF mask=FFFFFF00

The adres 192.168.0.175 was received.

And the updates for the DHCP lease also went correctly:

DHCP: time to renew i/f 0
DHCP: Sending RENEW request on i/f 0
DHCP: incoming on i/f 0
DHCP: Setting results for i/f 0…
lease=60 sec
IP=C0A800AF mask=FFFFFF00

When the ip range of the dhcp server was changed to …181-…190, a DHCP_TY_NAK was sent from the server

DHCP: time to renew i/f 0
DHCP: Sending RENEW request on i/f 0
DHCP: incoming on i/f 0
DHCP: RENEW/REBIND/INFORM denied

This stopped the dhcp client from working.

After some tracing / debugging, we found out that the DHCP_ST_EXPIRED state isn’t handled correctly when set. This state is used twice in the BOOTP.LIB. Once when the DHCP_TY_NAK is received, and once when checking dhcp lease.
When checking the dhcp lease, the DHCP_ST_EXPIRED state is handled correctly. The state is set and a call is made to dhcp_tick to handle the new state:

            case DHCP_ST_REBIND:
            	if ((long)(SEC_TIMER - di->lease) > 0) {
	#ifdef BOOTP_VERBOSE
						printf("DHCP: expired i/f %d
", i);
	#endif
               	di->state = DHCP_ST_EXPIRED;
                  return dhcp_tick(i);
               }
               dhcp_tick(i);
               break;

When setting the DHCP_ST_EXPIRED state when the DHCP_TY_NAK, no call to dhcp_tick is made and because this state isn’t handled in the dhcp_check_lease function, the dhcp_tick is never called again:

      // else fall thru
   case DHCP_ST_INFORM:
   case DHCP_ST_REBIND:
      if (dhcptype == DHCP_TY_NAK) {
   #ifdef BOOTP_VERBOSE
   		printf("DHCP: RENEW/REBIND/INFORM denied
");
   #endif
			di->state = DHCP_ST_EXPIRED;
      }

We made a quick and dirty fix, which fixed the problem, but introduced a lot of extra calls to the dhcp_tick function. Checking for the DHCP_ST_EXPIRED state was added to the dhcp_check_lease function. Now the changing of the ip range was resolved correctly.

            case DHCP_ST_EXPIRED:
            case DHCP_ST_REBIND:
            	if ((long)(SEC_TIMER - di->lease) > 0) {
	#ifdef BOOTP_VERBOSE
						printf("DHCP: expired i/f %d
", i);
	#endif
               	di->state = DHCP_ST_EXPIRED;
                  return dhcp_tick(i);
               }
               dhcp_tick(i);
               break;

With the desired (and expected) result:

DHCP: time to renew i/f 0
DHCP: Sending RENEW request on i/f 0
DHCP: incoming on i/f 0
DHCP: RENEW/REBIND/INFORM denied
DHCP: Sending DISCOVER request on i/f 0
DHCP: incoming on i/f 0
DHCP: offered C0A800AA
DHCP: Sending REQUEST on i/f 0
DHCP: incoming on i/f 0
DHCP: Setting results for i/f 0…
lease=60 sec
IP=C0A800AA mask=FFFFFF00

Note that normally a the ip range shouldn’t be changed (or at least not often), but we used this to simulate/stress the dhcp library due to problems we are experiencing.

Reply from rabbit semiconductor:

The problem occurs when the DHCP server changes the IP range after the rabbit DHCP client has already received an IP. What happens is the rabbit DHCP client will try to renew the IP after the range has been changed and the lease time expires. When this occurs the dhcp_handler enters DCHP_ST_REBIND state then the renewal fails and the state becomes DHCP_ST_EXPIRED. Then the function state machine completes by returning 0. Now, the next time dhcp_check_lease is called by tcp.lib the dhcp_check_lease function checks the state and does not have a case for DHCP_ST_EXPIRED. We came up with a fix by adding the following to the dhcp_check_lease function’s switch statement:

case DHCP_ST_EXPIRED: 
   return  dhcp_tick(i); 
break; 

Have them check this and let us know the outcome.

Which, as our fix, solved the problem.