diff packages/net/common/current/src/dhcp_prot.c @ 208:e0c0827131d1 ecos

Merge from eCos master repository on 2002-05-20-20:11:54-BST
author jlarmour
date Mon, 20 May 2002 22:19:26 +0000
parents
children 6eb55882e01c
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/packages/net/common/current/src/dhcp_prot.c
@@ -0,0 +1,1263 @@
+/*==========================================================================
+//
+//      dhcp_prot.c
+//
+//      DHCP protocol implementation for DHCP client
+//
+//==========================================================================
+//####ECOSPDCOPYRIGHTBEGIN####
+//
+// Copyright (C) 2000, 2001, 2002 Red Hat, Inc.
+// All Rights Reserved.
+//
+// Permission is granted to use, copy, modify and redistribute this
+// file.
+//
+//####ECOSPDCOPYRIGHTEND####
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):   hmt
+// Contributors: gthomas
+// Date:        2000-07-01
+// Purpose:     DHCP support
+// Description: 
+//
+//####DESCRIPTIONEND####
+//
+//========================================================================*/
+
+#include <pkgconf/system.h>
+#include <pkgconf/net.h>
+
+#ifdef CYGPKG_NET_DHCP
+
+#if 0
+#define perror( txt ) // nothing
+#endif
+
+#include <network.h>
+#include <dhcp.h>
+
+#include <cyg/infra/cyg_ass.h>
+
+// ------------------------------------------------------------------------
+// Returns a pointer to the end of dhcp message (or NULL if invalid)
+// meaning the address of the byte *after* the TAG_END token in the vendor
+// data.
+
+static unsigned char *
+scan_dhcp_size( struct bootp *ppkt )
+{
+    unsigned char *op;
+    
+    op = &ppkt->bp_vend[0];
+    // First check for the cookie!
+    if ( op[0] !=  99 ||
+         op[1] != 130 ||
+         op[2] !=  83 ||
+         op[3] !=  99 ) {
+        CYG_FAIL( "Bad DHCP cookie" );
+        return NULL;
+    }
+    op += 4;
+    while (*op != TAG_END) {
+        op += *(op+1)+2;
+        if ( op > &ppkt->bp_vend[BP_VEND_LEN-1] ) {
+            CYG_FAIL( "Oversize DHCP packet in dhcp_size" );
+            return NULL;
+        }
+    }
+    // Check op has not gone wild
+    CYG_ASSERT( op > (unsigned char *)(&ppkt[0]), "op pointer underflow!" );
+    // Compare op with non-existent "next" struct bootp in the array.
+    CYG_ASSERT( op < (unsigned char *)(&ppkt[1]), "op pointer overflow!" );
+    return op + 1; // Address of first invalid byte
+}
+
+// ------------------------------------------------------------------------
+// Get the actual packet size of an initialized buffer
+
+static int
+dhcp_size( struct bootp *ppkt )
+{
+    unsigned char *op;
+
+    op = scan_dhcp_size( ppkt );
+    if ( !op ) return 0;
+    return (op - (unsigned char *)ppkt);
+}
+
+
+// ------------------------------------------------------------------------
+// Get the actual packet size of an initialized buffer
+// This will also pad the packet with 0 if length is less
+// than BP_STD_TX_MINPKTSZ.
+
+static int
+dhcp_size_for_send( struct bootp *ppkt )
+{
+    unsigned char *op;
+
+    op = scan_dhcp_size( ppkt );
+    if ( !op ) return 0; // Better not scribble!
+    // Zero extra bytes until the packet is large enough.
+    for ( ; op < (((unsigned char *)ppkt) + BP_STD_TX_MINPKTSZ); op++ )
+        *op = 0;
+    return (op - (unsigned char *)ppkt);
+}
+
+// ------------------------------------------------------------------------
+// Insert/set an option value in an initialized buffer
+
+static int
+set_fixed_tag( struct bootp *ppkt,
+               unsigned char tag,
+               cyg_uint32 value,
+               int len)
+{
+    unsigned char *op;
+
+    // Initially this will only scan the options field.
+    
+    op = &ppkt->bp_vend[4];
+    while (*op != TAG_END) {
+        if ( op > &ppkt->bp_vend[BP_VEND_LEN-1] ) {
+            CYG_FAIL( "Oversize DHCP packet in set_fixed_tag" );
+            return false;
+        }
+        if (*op == tag)                 // Found it...
+            break;
+        op += *(op+1)+2;
+    }
+    
+    if (*op == tag) { // Found it...
+        if ( *(op+1) != len ) {
+            CYG_FAIL( "Wrong size in set_fixed_tag" );
+            return false;           // wrong size
+        }
+    }
+    else { // overwrite the end tag and install a new one
+        if ( op + len + 2 > &ppkt->bp_vend[BP_VEND_LEN-1] ) {
+            CYG_FAIL( "Oversize DHCP packet in set_fixed_tag append" );
+            return false;
+        }
+        *op = tag;
+        *(op+1) = len;
+        *(op + len + 2) = TAG_END;
+    }
+    // and insert the value.  Net order is BE.
+    op += len + 2 - 1;              // point to end of value
+    while ( len-- > 0 ) {
+        *op-- = (unsigned char)(value & 255);
+        value >>= 8;
+    }
+    return true;
+}
+
+// Note that this does not permit changing the size of an extant tag.
+static int
+set_variable_tag( struct bootp *ppkt,
+               unsigned char tag,
+               cyg_uint8 *pvalue,
+               int len)
+{
+    unsigned char *op;
+
+    // Initially this will only scan the options field.
+    op = &ppkt->bp_vend[4];
+    while (*op != TAG_END) {
+        if ( op > &ppkt->bp_vend[BP_VEND_LEN-1] ) {
+            CYG_FAIL( "Oversize DHCP packet in set_variable_tag" );
+            return false;
+        }
+        if (*op == tag)                 // Found it...
+            break;
+        op += *(op+1)+2;
+    }
+    
+    if (*op == tag) { // Found it...
+        if ( *(op+1) != len ) {
+            CYG_FAIL( "Wrong size in set_variable_tag" );
+            return false;           // wrong size
+        }
+    }
+    else { // overwrite the end tag and install a new one
+        if ( op + len + 2 > &ppkt->bp_vend[BP_VEND_LEN-1] ) {
+            CYG_FAIL( "Oversize DHCP packet in set_variable_tag append" );
+            return false;
+        }
+        *op = tag;
+        *(op+1) = len;
+        *(op + len + 2) = TAG_END;
+    }
+    // and insert the value.  No order is implied.
+    op += 2;               // point to start of value
+    while ( len-- > 0 ) {
+        *op++ = *pvalue++;
+    }
+    return true;
+}
+
+static int
+unset_tag( struct bootp *ppkt,
+           unsigned char tag )
+{
+    unsigned char *op, *nextp = 0, *killp = 0;
+
+    // Initially this will only scan the options field.
+    
+    op = &ppkt->bp_vend[4];
+    while (*op != TAG_END) {
+        if ( op > &ppkt->bp_vend[BP_VEND_LEN-1] ) {
+            CYG_FAIL( "Oversize DHCP packet in unset_tag" );
+            return false;
+        }
+        if (*op == tag) {               // Found it...
+            killp = op;                 // item to kill
+            nextp = op + *(op+1)+2;     // next item address
+        }
+        op += *(op+1)+2;                // scan to the end
+    }
+
+    if ( !killp )
+        return false;
+
+    // Obliterate the found op by copying down: *op is the end.
+    while( nextp <= op )                // <= to copy the TAG_END too.
+        *killp++ = *nextp++;
+    
+    return true;
+}
+
+// ------------------------------------------------------------------------
+// Bring up an interface enough to broadcast, before we know who we are
+
+static int
+bring_half_up(const char *intf, struct ifreq *ifrp )
+{
+    int s;
+    int one = 1;
+
+    struct sockaddr_in *addrp;
+    struct ecos_rtentry route;
+
+    // Ensure clean slate
+    cyg_route_reinit();  // Force any existing routes to be forgotten
+
+    s = socket(AF_INET, SOCK_DGRAM, 0);
+    if (s < 0) {
+        perror("socket");
+        return false;
+    }
+
+    if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one))) {
+        perror("setsockopt");
+        return false;
+    }
+
+    addrp = (struct sockaddr_in *) &ifrp->ifr_addr;
+    memset(addrp, 0, sizeof(*addrp));
+    addrp->sin_family = AF_INET;
+    addrp->sin_len = sizeof(*addrp);
+    addrp->sin_port = 0;
+    addrp->sin_addr.s_addr = INADDR_ANY;
+
+    strcpy(ifrp->ifr_name, intf);
+    if (ioctl(s, SIOCSIFADDR, ifrp)) { /* set ifnet address */
+        perror("SIOCSIFADDR");
+        return false;
+    }
+
+    if (ioctl(s, SIOCSIFNETMASK, ifrp)) { /* set net addr mask */
+        perror("SIOCSIFNETMASK");
+        return false;
+    }
+
+    /* the broadcast address is 255.255.255.255 */
+    memset(&addrp->sin_addr, 255, sizeof(addrp->sin_addr));
+    if (ioctl(s, SIOCSIFBRDADDR, ifrp)) { /* set broadcast addr */
+        perror("SIOCSIFBRDADDR");
+        return false;
+    }
+
+    ifrp->ifr_flags = IFF_UP | IFF_BROADCAST | IFF_RUNNING;
+    if (ioctl(s, SIOCSIFFLAGS, ifrp)) { /* set ifnet flags */
+        perror("SIOCSIFFLAGS up");
+        return false;
+    }
+
+    if (ioctl(s, SIOCGIFHWADDR, ifrp) < 0) { /* get MAC address */
+        perror("SIOCGIFHWADDR 1");
+        return false;
+    }
+
+    // Set up routing
+    addrp->sin_family = AF_INET;
+    addrp->sin_port = 0;
+    addrp->sin_len = sizeof(*addrp);  // Size of address
+
+    /* the broadcast address is 255.255.255.255 */
+    memset(&addrp->sin_addr, 255, sizeof(addrp->sin_addr));
+    memset(&route, 0, sizeof(route));
+    memcpy(&route.rt_gateway, addrp, sizeof(*addrp));
+
+    addrp->sin_addr.s_addr = INADDR_ANY;
+    memcpy(&route.rt_dst, addrp, sizeof(*addrp));
+    memcpy(&route.rt_genmask, addrp, sizeof(*addrp));
+
+    route.rt_dev = ifrp->ifr_name;
+    route.rt_flags = RTF_UP|RTF_GATEWAY;
+    route.rt_metric = 0;
+
+    if (ioctl(s, SIOCADDRT, &route)) { /* add route */
+        if (errno != EEXIST) {
+            perror("SIOCADDRT 3");
+            return false;
+        }
+    }
+
+    close(s);
+
+    return true;
+}
+
+
+// ------------------------------------------------------------------------
+// DHCP retransmission timeouts and number of tries
+// 
+// To work better with simulated failures (or real ones!) so that the rest
+// of the system is tested, rather than DHCP renewal failures pulling
+// everything down, we try a little more zealously than the RFC suggests.
+
+static unsigned char timeout_random = 0;
+
+struct timeout_state {
+    unsigned int secs;
+    int countdown;
+};
+
+static inline void reset_timeout( struct timeval *ptv, struct timeout_state *pstate )
+{
+    timeout_random++;
+    pstate->countdown = 4; // initial fast retries
+    pstate->secs = 3 + (timeout_random & 3);
+    ptv->tv_sec = 0;
+    ptv->tv_usec = 65536 * (2 + (timeout_random & 3)); // 0.1 - 0.3S, about
+}
+
+static inline int next_timeout( struct timeval *ptv, struct timeout_state *pstate )
+{
+    if ( 0 < pstate->countdown-- )
+        return true;
+    if ( 0 == ptv->tv_sec )
+        ptv->tv_sec = pstate->secs;
+    else {
+        timeout_random++;
+        pstate->secs = ptv->tv_sec * 2 - 2 + (timeout_random & 3);
+        pstate->countdown = 2; // later fast retries
+        ptv->tv_sec = 0;
+    }
+    return pstate->secs < 100; // If longer, too many tries...
+}
+
+// ------------------------------------------------------------------------
+// Lease expiry and alarms to notify it
+
+static cyg_alarm_t alarm_function;
+
+static void alarm_function(cyg_handle_t alarm, cyg_addrword_t data)
+{
+    struct dhcp_lease *lease = (struct dhcp_lease *)data;
+    lease->which |= lease->next;
+    if ( lease->needs_attention )
+        cyg_semaphore_post( lease->needs_attention );
+
+    // Step the lease on into its next state of being alarmed ;-)
+    if ( lease->next & DHCP_LEASE_EX ) {
+        cyg_alarm_disable( alarm );
+    }
+    else if ( lease->next & DHCP_LEASE_T2 ) {
+        lease->next = DHCP_LEASE_EX;
+        cyg_alarm_initialize( lease->alarm, lease->expiry, 0 );
+        cyg_alarm_enable( lease->alarm );
+    }
+    else if ( lease->next & DHCP_LEASE_T1 ) {
+        lease->next = DHCP_LEASE_T2;
+        cyg_alarm_initialize( lease->alarm, lease->t2, 0 );
+        cyg_alarm_enable( lease->alarm );
+    }
+}
+
+static inline void no_lease( struct dhcp_lease *lease )
+{
+    if ( lease->alarm ) {
+        // Already set: delete this.
+        cyg_alarm_disable( lease->alarm );
+        cyg_alarm_delete( lease->alarm );
+        lease->alarm = 0;
+    }
+}
+
+static inline void new_lease( struct bootp *bootp, struct dhcp_lease *lease )
+{
+    cyg_tick_count_t now = cyg_current_time();
+    cyg_tick_count_t then;
+    cyg_uint32 tag = 0;
+    cyg_uint32 expiry_then;
+    cyg_resolution_t resolution = 
+        cyg_clock_get_resolution(cyg_real_time_clock());
+    cyg_handle_t h;
+    unsigned int length;
+    
+    // Silence any jabbering from past lease on this interface
+    no_lease( lease );
+    lease->which = lease->next = 0;
+    cyg_clock_to_counter(cyg_real_time_clock(), &h);
+    cyg_alarm_create( h, alarm_function, (cyg_addrword_t)lease,
+                      &lease->alarm, &lease->alarm_obj );
+
+    // extract the lease time and scale it &c to now.
+    length = sizeof(tag);
+    if(!get_bootp_option( bootp, TAG_DHCP_LEASE_TIME, &tag ,&length))
+        tag = 0xffffffff;
+
+    if ( 0xffffffff == tag ) {
+        lease->expiry = 0xffffffffffffffff;
+        lease->t2     = 0xffffffffffffffff;
+        lease->t1     = 0xffffffffffffffff;
+        return; // it's an infinite lease, hurrah!
+    }
+
+    then = (cyg_uint64)(ntohl(tag));
+    expiry_then = then;
+
+    then *= 1000000000; // into nS - we know there is room in a tick_count_t
+    then = (then / resolution.dividend) * resolution.divisor; // into system ticks
+    lease->expiry = now + then;
+    length = sizeof(tag);
+    if (get_bootp_option( bootp, TAG_DHCP_REBIND_TIME, &tag, &length ))
+        then = (cyg_uint64)(ntohl(tag));
+    else
+        then = expiry_then - expiry_then/4;
+    then *= 1000000000; // into nS - we know there is room in a tick_count_t
+    then = (then / resolution.dividend) * resolution.divisor; // into system ticks
+    lease->t2 = now + then;
+
+    length = sizeof(tag);
+    if (get_bootp_option( bootp, TAG_DHCP_RENEWAL_TIME, &tag, &length ))
+        then = (cyg_uint64)(ntohl(tag));
+    else
+        then = expiry_then/2;
+    then *= 1000000000; // into nS - we know there is room in a tick_count_t
+    then = (then / resolution.dividend) * resolution.divisor; // into system ticks
+    lease->t1 = now + then;
+
+#if 0 // for testing this mechanism
+    lease->expiry = now + 5000; // 1000 here makes for failure in the DHCP test
+    lease->t2     = now + 3500;
+    lease->t1     = now + 2500;
+#endif
+
+#ifdef CYGDBG_NET_DHCP_CHATTER
+    diag_printf("new_lease:\n");
+    diag_printf("  expiry = %d\n",lease->expiry);
+    diag_printf("      t1 = %d\n",lease->t1);
+    diag_printf("      t2 = %d\n",lease->t2);
+#endif
+
+    lease->next = DHCP_LEASE_T1;
+
+    cyg_alarm_initialize( lease->alarm, lease->t1, 0 );
+    cyg_alarm_enable( lease->alarm );
+}
+
+// ------------------------------------------------------------------------
+// Set all the tags we want to use when sending a packet.
+// This has expanded to a large, explicit set to interwork better
+// with a variety of DHCP servers.
+
+static void set_default_dhcp_tags( struct bootp *xmit )
+{
+    // Explicitly request full set of params that are default for LINUX
+    // dhcp servers, but not default for others.  This is rather arbitrary,
+    // but it preserves behaviour for people using those servers.
+    // Perhaps configury of this set will be needed in future?
+    //
+    // Here's the set:
+    static cyg_uint8 req_list[]  = {
+#ifdef CYGOPT_NET_DHCP_PARM_REQ_LIST_REPLACE
+        CYGOPT_NET_DHCP_PARM_REQ_LIST_REPLACE ,
+#else
+        TAG_DHCP_SERVER_ID    ,     //     DHCP server id: 10.16.19.66
+        TAG_DHCP_LEASE_TIME   ,     //     DHCP time 51: 60
+        TAG_DHCP_RENEWAL_TIME ,     //     DHCP time 58: 30
+        TAG_DHCP_REBIND_TIME  ,     //     DHCP time 59: 52
+        TAG_SUBNET_MASK       ,     //     subnet mask: 255.255.255.0
+        TAG_GATEWAY           ,     //     gateway: 10.16.19.66
+        TAG_DOMAIN_SERVER     ,     //     domain server: 10.16.19.66
+        TAG_DOMAIN_NAME       ,     //     domain name: hmt10.cambridge.redhat.com
+        TAG_IP_BROADCAST      ,     //     IP broadcast: 10.16.19.255
+#endif
+#ifdef CYGOPT_NET_DHCP_PARM_REQ_LIST_ADDITIONAL
+        CYGOPT_NET_DHCP_PARM_REQ_LIST_ADDITIONAL ,
+#endif
+    };
+
+    if ( req_list[0] ) // So that one may easily turn it all off by configury
+        set_variable_tag( xmit, TAG_DHCP_PARM_REQ_LIST,
+                          &req_list[0], sizeof( req_list ) );
+
+    // Explicitly specify our max message size.
+    set_fixed_tag( xmit, TAG_DHCP_MAX_MSGSZ, BP_MINPKTSZ, 2 );
+}
+
+// ------------------------------------------------------------------------
+// the DHCP state machine - this does all the work
+
+int
+do_dhcp(const char *intf, struct bootp *res,
+        cyg_uint8 *pstate, struct dhcp_lease *lease)
+{
+    struct ifreq ifr;
+    struct sockaddr_in cli_addr, broadcast_addr, server_addr, rx_addr;
+    int s, addrlen;
+    int one = 1;
+    unsigned char mincookie[] = {99,130,83,99,255} ;
+    struct timeval tv;
+    struct timeout_state timeout_scratch;
+    cyg_uint8 oldstate = *pstate;
+    cyg_uint8 msgtype = 0, seen_bootp_reply = 0;
+    unsigned int length;
+    
+    cyg_uint32 xid;
+
+#define CHECK_XID() (  /* and other details */                                  \
+    received->bp_xid   != xid            || /* not the same transaction */      \
+    received->bp_htype != xmit->bp_htype || /* not the same ESA type    */      \
+    received->bp_hlen  != xmit->bp_hlen  || /* not the same length      */      \
+    bcmp( &received->bp_chaddr, &xmit->bp_chaddr, xmit->bp_hlen )               \
+    )
+
+    // IMPORTANT: xmit is the same as res throughout this; *received is a
+    // scratch buffer for reception; its contents are always copied to res
+    // when we are happy with them.  So we always transmit from the
+    // existing state.
+    struct bootp rx_local;
+    struct bootp *received = &rx_local;
+    struct bootp *xmit = res;
+
+    // First, get a socket on the interface in question.  But Zeroth, if
+    // needs be, bring it to the half-up broadcast only state if needs be.
+    
+    if ( DHCPSTATE_INIT      == oldstate
+         || DHCPSTATE_FAILED == oldstate
+         || 0                == oldstate ) {
+        // either explicit init state or the beginning of time or retry
+        if ( ! bring_half_up( intf, &ifr ) )
+            return false;
+
+        *pstate = DHCPSTATE_INIT;
+    }
+
+    s = socket(AF_INET, SOCK_DGRAM, 0);
+    if (s < 0) {
+        perror("socket");
+        return false;
+    }
+
+    if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one))) {
+        perror("setsockopt");
+        return false;
+    }
+
+    memset((char *) &cli_addr, 0, sizeof(cli_addr));
+    cli_addr.sin_family = AF_INET;
+    cli_addr.sin_len = sizeof(cli_addr);
+    cli_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+    cli_addr.sin_port = htons(IPPORT_BOOTPC);
+    
+    memset((char *) &broadcast_addr, 0, sizeof(broadcast_addr));
+    broadcast_addr.sin_family = AF_INET;
+    broadcast_addr.sin_len = sizeof(broadcast_addr);
+    broadcast_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+    broadcast_addr.sin_port = htons(IPPORT_BOOTPS);
+
+    memset((char *) &server_addr, 0, sizeof(server_addr));
+    server_addr.sin_family = AF_INET;
+    server_addr.sin_len = sizeof(server_addr);
+    server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); // overwrite later
+    server_addr.sin_port = htons(IPPORT_BOOTPS);
+
+    if(bind(s, (struct sockaddr *) &cli_addr, sizeof(cli_addr)) < 0) {
+        perror("bind error");
+        return false;
+    }
+    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+        perror("setsockopt SO_REUSEADDR");
+        return false;
+    }
+    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one))) {
+        perror("setsockopt SO_REUSEPORT");
+        return false;
+    }
+    
+    // Now, we can launch into the DHCP state machine.  I think this will
+    // be the neatest way to do it; it returns from within the switch arms
+    // when all is well, or utterly failed.
+
+    reset_timeout( &tv, &timeout_scratch );
+
+    // Choose a new XID: first get the ESA as a basis:
+    strcpy(&ifr.ifr_name[0], intf);
+    if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) {
+        perror("SIOCGIFHWADDR 2");
+        return false;
+    }
+
+    // Choose from scratch depending on ifr_hwaddr...[]
+    xid = ifr.ifr_hwaddr.sa_data[5];
+    xid |= (ifr.ifr_hwaddr.sa_data[4]) << 8;
+    xid |= (ifr.ifr_hwaddr.sa_data[3]) << 16;
+    xid |= (ifr.ifr_hwaddr.sa_data[2]) << 24;
+    xid ^= (cyg_arc4random() & 0xffff0000);
+
+    // Avoid adjacent ESAs colliding by increment
+#define NEW_XID(_xid) CYG_MACRO_START (_xid)+= 0x10000; CYG_MACRO_END
+
+    while ( 1 ) {
+
+        // If we are active rather than in the process of shutting down,
+        // check for any lease expiry every time round, so that alarms
+        // *can* change the course of events even when already renewing,
+        // for example.
+        if ( DHCPSTATE_DO_RELEASE   != *pstate
+             && DHCPSTATE_NOTBOUND  != *pstate
+             && DHCPSTATE_FAILED    != *pstate ) {
+            cyg_uint8 lease_state;
+
+            cyg_scheduler_lock();
+            lease_state = lease->which;
+            lease->which = 0; // flag that we have noticed it
+            cyg_scheduler_unlock();
+
+            if ( lease_state & DHCP_LEASE_EX ) {
+                // then the lease has expired completely!
+                *pstate = DHCPSTATE_NOTBOUND;
+            }
+            else if ( lease_state & DHCP_LEASE_T2 ) {
+                // Time to renew
+                reset_timeout( &tv, &timeout_scratch ); // next conversation
+                *pstate = DHCPSTATE_REBINDING;
+            }
+            else if ( lease_state & DHCP_LEASE_T1 ) {
+                // Time to renew
+                reset_timeout( &tv, &timeout_scratch ); // next conversation
+                *pstate = DHCPSTATE_RENEWING;
+            }
+        }
+
+        switch ( *pstate ) {
+
+        case DHCPSTATE_INIT:
+
+            // Send the DHCPDISCOVER packet
+
+            // Fill in the BOOTP request - DHCPDISCOVER packet
+            bzero(xmit, sizeof(*xmit));
+            xmit->bp_op = BOOTREQUEST;
+            xmit->bp_htype = HTYPE_ETHERNET;
+            xmit->bp_hlen = IFHWADDRLEN;
+            xmit->bp_xid = xid;
+            xmit->bp_secs = 0;
+            xmit->bp_flags = htons(0x8000); // BROADCAST FLAG
+            bcopy(ifr.ifr_hwaddr.sa_data, &xmit->bp_chaddr, xmit->bp_hlen);
+            bcopy(mincookie, xmit->bp_vend, sizeof(mincookie));
+
+            // remove the next line to test ability to handle bootp packets.
+            set_fixed_tag( xmit, TAG_DHCP_MESS_TYPE, DHCPDISCOVER, 1 );
+            // Set all the tags we want to use when sending a packet
+            set_default_dhcp_tags( xmit );
+
+#ifdef CYGDBG_NET_DHCP_CHATTER
+            diag_printf( "---------DHCPSTATE_INIT sending:\n" );
+            show_bootp( intf, xmit );
+#endif            
+            if(sendto(s, xmit, dhcp_size_for_send(xmit), 0, 
+                      (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr)) < 0) {
+                *pstate = DHCPSTATE_FAILED;
+                break;
+            }
+
+            seen_bootp_reply = 0;
+            *pstate = DHCPSTATE_SELECTING;
+            break;
+
+        case DHCPSTATE_SELECTING:
+            // This is a separate state so that we can listen again
+            // *without* retransmitting.
+            
+            // listen for the DHCPOFFER reply
+
+            setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+
+            addrlen = sizeof(rx_addr);
+            if (recvfrom(s, received, sizeof(struct bootp), 0,
+                         (struct sockaddr *)&rx_addr, &addrlen) < 0) {
+                // No packet arrived (this time)
+                if ( seen_bootp_reply ) { // then already have a bootp reply
+                    // Save the good packet in *xmit
+                    bcopy( received, xmit, dhcp_size(received) );
+                    *pstate = DHCPSTATE_BOOTP_FALLBACK;
+                    NEW_XID( xid ); // Happy to advance, so new XID
+                    reset_timeout( &tv, &timeout_scratch );
+                    break;
+                }       
+                // go to the next larger timeout and re-send:
+                if ( ! next_timeout( &tv, &timeout_scratch ) ) {
+                    *pstate = DHCPSTATE_FAILED;
+                    break;
+                }
+                *pstate = DHCPSTATE_INIT; // to retransmit
+                break;
+            }
+
+#ifdef CYGDBG_NET_DHCP_CHATTER
+            diag_printf( "---------DHCPSTATE_SELECTING received:\n" );
+            diag_printf( "...rx_addr is family %d, addr %08x, port %d\n",
+                         rx_addr.sin_family,
+                         rx_addr.sin_addr.s_addr,
+                         rx_addr.sin_port );
+            show_bootp( intf, received );
+#endif            
+            if ( CHECK_XID() )          // XID and ESA matches?
+                break;                  // listen again...
+
+            if ( 0 == received->bp_siaddr.s_addr ) {
+                // then fill in from the options...
+                length = sizeof(received->bp_siaddr.s_addr);
+                get_bootp_option( received, TAG_DHCP_SERVER_ID,
+                                  &received->bp_siaddr.s_addr,
+                                  &length);
+            }
+
+            // see if it was a DHCP reply or a bootp reply; it could be
+            // either.
+            length = sizeof(msgtype);
+            if ( get_bootp_option( received, TAG_DHCP_MESS_TYPE, &msgtype,
+                                   &length) ) {
+                if ( DHCPOFFER == msgtype ) { // all is well
+                    // Save the good packet in *xmit
+                    bcopy( received, xmit, dhcp_size(received) );
+                    // we like the packet, so reset the timeout for next time
+                    reset_timeout( &tv, &timeout_scratch );
+                    *pstate = DHCPSTATE_REQUESTING;
+                    NEW_XID( xid ); // Happy to advance, so new XID
+                }
+            }
+            else // No TAG_DHCP_MESS_TYPE entry so it's a bootp reply
+                seen_bootp_reply = 1; // (keep the bootp packet in received)
+                
+            // If none of the above state changes occurred, we got a packet
+            // that "should not happen", OR we have a bootp reply in our
+            // hand; so listen again with the same timeout, without
+            // retrying the send, in the hope of getting a DHCP reply.
+            break;
+
+        case DHCPSTATE_REQUESTING:
+            // Just send what you got with a DHCPREQUEST in the message type.
+            // then wait for an ACK in DHCPSTATE_REQUEST_RECV.
+
+            // Fill in the BOOTP request - DHCPREQUEST packet
+            xmit->bp_xid = xid;
+            xmit->bp_op = BOOTREQUEST;
+            xmit->bp_flags = htons(0x8000); // BROADCAST FLAG
+
+            set_fixed_tag( xmit, TAG_DHCP_MESS_TYPE, DHCPREQUEST, 1 );
+            // Set all the tags we want to use when sending a packet
+            set_default_dhcp_tags( xmit );
+            // And this will be a new one:
+            set_fixed_tag( xmit, TAG_DHCP_REQ_IP, ntohl(xmit->bp_yiaddr.s_addr), 4 );
+            
+#ifdef CYGDBG_NET_DHCP_CHATTER
+            diag_printf( "---------DHCPSTATE_REQUESTING sending:\n" );
+            show_bootp( intf, xmit );
+#endif            
+            if(sendto(s, xmit, dhcp_size_for_send(xmit), 0, 
+                      (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr)) < 0) {
+                *pstate = DHCPSTATE_FAILED;
+                break;
+            }
+
+            *pstate = DHCPSTATE_REQUEST_RECV;
+            break;
+
+        case DHCPSTATE_REQUEST_RECV:
+            // wait for an ACK or a NACK - retry by going back to
+            // DHCPSTATE_REQUESTING; NACK means go back to INIT.
+
+            setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+
+            addrlen = sizeof(rx_addr);
+            if (recvfrom(s, received, sizeof(struct bootp), 0,
+                         (struct sockaddr *)&rx_addr, &addrlen) < 0) {
+                // No packet arrived
+                // go to the next larger timeout and re-send:
+                if ( ! next_timeout( &tv, &timeout_scratch ) ) {
+                    *pstate = DHCPSTATE_FAILED;
+                    break;
+                }
+                *pstate = DHCPSTATE_REQUESTING;
+                break;
+            }
+
+#ifdef CYGDBG_NET_DHCP_CHATTER
+            diag_printf( "---------DHCPSTATE_REQUEST_RECV received:\n" );
+            diag_printf( "...rx_addr is family %d, addr %08x, port %d\n",
+                         rx_addr.sin_family,
+                         rx_addr.sin_addr.s_addr,
+                         rx_addr.sin_port );
+            show_bootp( intf, received );
+#endif            
+            if ( CHECK_XID() )          // not the same transaction;
+                break;                  // listen again...
+
+            if ( 0 == received->bp_siaddr.s_addr ) {
+                // then fill in from the options...
+                length = sizeof(received->bp_siaddr.s_addr );
+                get_bootp_option( received, TAG_DHCP_SERVER_ID,
+                                  &received->bp_siaddr.s_addr,
+                                  &length);
+            }
+
+            // check it was a DHCP reply
+            length = sizeof(msgtype);
+            if ( get_bootp_option( received, TAG_DHCP_MESS_TYPE, &msgtype,
+                                   &length) ) {
+                if ( DHCPACK == msgtype // Same offer & server?
+                     && received->bp_yiaddr.s_addr == xmit->bp_yiaddr.s_addr
+                     && received->bp_siaddr.s_addr == xmit->bp_siaddr.s_addr) {
+                    // we like the packet, so reset the timeout for next time
+                    reset_timeout( &tv, &timeout_scratch );
+                    // Record the new lease and set up timers &c
+                    new_lease( received, lease );
+                    *pstate = DHCPSTATE_BOUND;
+                    break;
+                }
+                if ( DHCPNAK == msgtype // Same server?
+                     && received->bp_siaddr.s_addr == xmit->bp_siaddr.s_addr) {
+                    // we're bounced!
+                    *pstate = DHCPSTATE_INIT;  // So back the start of the rigmarole.
+                    NEW_XID( xid ); // Unhappy to advance, so new XID
+                    reset_timeout( &tv, &timeout_scratch );
+                    break;
+                }
+                // otherwise it's something else, maybe another offer, or a bogus
+                // NAK from someone we are not asking!
+                // Just listen again, which implicitly discards it.
+            }
+            break;
+
+        case DHCPSTATE_BOUND:
+
+            // We are happy now, we have our address.
+
+            // All done with socket
+            close(s);
+
+            // Re-initialize the interface with the new state
+            if ( DHCPSTATE_BOUND != oldstate ) {
+                // Then need to go down and up
+                do_dhcp_down_net( intf, res, &oldstate, lease ); // oldstate used
+                if ( 0 != oldstate ) {
+                    // Then not called from init_all_network_interfaces()
+                    // so we must initialize the interface ourselves
+                    if (!init_net(intf, res)) {
+                        do_dhcp_down_net( intf, res, pstate, lease );
+                        *pstate = DHCPSTATE_FAILED;
+                        return false;
+                    }
+                }
+            }
+
+            // Otherwise, nothing whatsoever to do...
+            return true;
+
+        case DHCPSTATE_RENEWING:
+            // Just send what you got with a DHCPREQUEST in the message
+            // type UNICAST straight to the server.  Then wait for an ACK.
+
+            // Fill in the BOOTP request - DHCPREQUEST packet
+            xmit->bp_xid = xid;
+            xmit->bp_op = BOOTREQUEST;
+            xmit->bp_flags = htons(0); // No BROADCAST FLAG
+            // Use the *client* address here:
+            xmit->bp_ciaddr.s_addr = xmit->bp_yiaddr.s_addr;
+
+            set_fixed_tag( xmit, TAG_DHCP_MESS_TYPE, DHCPREQUEST, 1 );
+            // These must not be set in this context
+            unset_tag( xmit, TAG_DHCP_REQ_IP );
+            unset_tag( xmit, TAG_DHCP_SERVER_ID );
+            // Set all the tags we want to use when sending a packet
+            set_default_dhcp_tags( xmit );
+            
+            // Set unicast address to *server*
+            server_addr.sin_addr.s_addr = res->bp_siaddr.s_addr;
+
+#ifdef CYGDBG_NET_DHCP_CHATTER
+            diag_printf( "---------DHCPSTATE_RENEWING sending:\n" );
+            diag_printf( "UNICAST to family %d, addr %08x, port %d\n",
+                         server_addr.sin_family,
+                         server_addr.sin_addr.s_addr,
+                         server_addr.sin_port );
+            show_bootp( intf, xmit );
+#endif            
+            
+            if(sendto(s, xmit, dhcp_size_for_send(xmit), 0, 
+                       // UNICAST address of the server:
+                      (struct sockaddr *)&server_addr,
+                      sizeof(server_addr)) < 0) {
+                *pstate = DHCPSTATE_FAILED;
+                break;
+            }
+
+            *pstate = DHCPSTATE_RENEW_RECV;
+            break;
+
+        case DHCPSTATE_RENEW_RECV:
+            // wait for an ACK or a NACK - retry by going back to
+            // DHCPSTATE_RENEWING; NACK means go to NOTBOUND.
+            // No answer means just wait for T2, to broadcast.
+
+            setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+
+            addrlen = sizeof(rx_addr);
+            if (recvfrom(s, received, sizeof(struct bootp), 0,
+                         (struct sockaddr *)&rx_addr, &addrlen) < 0) {
+                // No packet arrived
+                // go to the next larger timeout and re-send:
+                if ( ! next_timeout( &tv, &timeout_scratch ) ) {
+                    // If we timed out completely, just give up until T2
+                    // expires - retain the lease meanwhile.  The normal
+                    // lease mechanism will invoke REBINDING as and when
+                    // necessary.
+                    *pstate = DHCPSTATE_BOUND;
+                    break;
+                }
+                *pstate = DHCPSTATE_RENEWING;
+                break;
+            }
+
+#ifdef CYGDBG_NET_DHCP_CHATTER
+            diag_printf( "---------DHCPSTATE_RENEW_RECV received:\n" );
+            diag_printf( "...rx_addr is family %d, addr %08x, port %d\n",
+                         rx_addr.sin_family,
+                         rx_addr.sin_addr.s_addr,
+                         rx_addr.sin_port );
+            show_bootp( intf, received );
+#endif            
+            if ( CHECK_XID() )          // not the same transaction;
+                break;                  // listen again...
+
+            if ( 0 == received->bp_siaddr.s_addr ) {
+                // then fill in from the options...
+                length = sizeof(received->bp_siaddr.s_addr);
+                get_bootp_option( received, TAG_DHCP_SERVER_ID,
+                                  &received->bp_siaddr.s_addr,
+                                  &length);
+            }
+
+            // check it was a DHCP reply
+            length = sizeof(msgtype);
+            if ( get_bootp_option( received, TAG_DHCP_MESS_TYPE, &msgtype,
+                                   &length) ) {
+                if ( DHCPACK == msgtype  // Same offer?
+                     && received->bp_yiaddr.s_addr == xmit->bp_yiaddr.s_addr) {
+                    // we like the packet, so reset the timeout for next time
+                    reset_timeout( &tv, &timeout_scratch );
+                    // Record the new lease and set up timers &c
+                    new_lease( received, lease );
+                    *pstate = DHCPSTATE_BOUND;
+                    break;
+                }
+                if ( DHCPNAK == msgtype ) { // we're bounced!
+                    *pstate = DHCPSTATE_NOTBOUND;  // So quit out.
+                    break;
+                }
+                // otherwise it's something else, maybe another offer.
+                // Just listen again, which implicitly discards it.
+            }
+            break;
+
+        case DHCPSTATE_REBINDING:
+            // Just send what you got with a DHCPREQUEST in the message type.
+            // Then wait for an ACK.  This one is BROADCAST.
+
+            // Fill in the BOOTP request - DHCPREQUEST packet
+            xmit->bp_xid = xid;
+            xmit->bp_op = BOOTREQUEST;
+            xmit->bp_flags = htons(0); // no BROADCAST FLAG
+            // Use the *client* address here:
+            xmit->bp_ciaddr.s_addr = xmit->bp_yiaddr.s_addr;
+
+            set_fixed_tag( xmit, TAG_DHCP_MESS_TYPE, DHCPREQUEST, 1 );
+            // These must not be set in this context
+            unset_tag( xmit, TAG_DHCP_REQ_IP );
+            unset_tag( xmit, TAG_DHCP_SERVER_ID );
+            // Set all the tags we want to use when sending a packet
+            set_default_dhcp_tags( xmit );
+            
+#ifdef CYGDBG_NET_DHCP_CHATTER
+            diag_printf( "---------DHCPSTATE_REBINDING sending:\n" );
+            show_bootp( intf, xmit );
+#endif            
+            if(sendto(s, xmit, dhcp_size_for_send(xmit), 0, 
+                      (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr)) < 0) {
+                *pstate = DHCPSTATE_FAILED;
+                break;
+            }
+
+            *pstate = DHCPSTATE_REBIND_RECV;
+            break;
+
+        case DHCPSTATE_REBIND_RECV:
+            // wait for an ACK or a NACK - retry by going back to
+            // DHCPSTATE_REBINDING; NACK means go to NOTBOUND.
+            // No answer means just wait for expiry; we tried!
+
+            setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+
+            addrlen = sizeof(rx_addr);
+            if (recvfrom(s, received, sizeof(struct bootp), 0,
+                         (struct sockaddr *)&rx_addr, &addrlen) < 0) {
+                // No packet arrived
+                // go to the next larger timeout and re-send:
+                if ( ! next_timeout( &tv, &timeout_scratch ) ) {
+                    // If we timed out completely, just give up until EX
+                    // expires - retain the lease meanwhile.  The normal
+                    // lease mechanism will invoke NOTBOUND state as and
+                    // when necessary.
+                    *pstate = DHCPSTATE_BOUND;
+                    break;
+                }
+                *pstate = DHCPSTATE_REBINDING;
+                break;
+            }
+
+#ifdef CYGDBG_NET_DHCP_CHATTER
+            diag_printf( "---------DHCPSTATE_REBIND_RECV received:\n" );
+            diag_printf( "...rx_addr is family %d, addr %08x, port %d\n",
+                         rx_addr.sin_family,
+                         rx_addr.sin_addr.s_addr,
+                         rx_addr.sin_port );
+            show_bootp( intf, received );
+#endif            
+            if ( CHECK_XID() )          // not the same transaction;
+                break;                  // listen again...
+
+            if ( 0 == received->bp_siaddr.s_addr ) {
+                // then fill in from the options...
+                int length = sizeof(received->bp_siaddr.s_addr );
+                get_bootp_option( received, TAG_DHCP_SERVER_ID,
+                                  &received->bp_siaddr.s_addr,
+                                  &length);
+            }
+
+            // check it was a DHCP reply
+            length = sizeof(msgtype);
+            if ( get_bootp_option( received, TAG_DHCP_MESS_TYPE, &msgtype,
+                                   &length) ) {
+                if ( DHCPACK == msgtype  // Same offer?
+                     && received->bp_yiaddr.s_addr == xmit->bp_yiaddr.s_addr) {
+                    // we like the packet, so reset the timeout for next time
+                    reset_timeout( &tv, &timeout_scratch );
+                    // Record the new lease and set up timers &c
+                    new_lease( received, lease );
+                    *pstate = DHCPSTATE_BOUND;
+                    break;
+                }
+                else if ( DHCPNAK == msgtype ) { // we're bounced!
+                    *pstate = DHCPSTATE_NOTBOUND;  // So back the start of the rigmarole.
+                    break;
+                }
+                // otherwise it's something else, maybe another offer.
+                // Just listen again, which implicitly discards it.
+            }
+            break;
+
+        case DHCPSTATE_BOOTP_FALLBACK:
+            // All done with socket
+            close(s);
+            // And no lease should have become active, but JIC
+            no_lease( lease );
+            // Re-initialize the interface with the new state
+            if ( DHCPSTATE_BOOTP_FALLBACK != oldstate ) {
+                // Then need to go down and up
+                do_dhcp_down_net( intf, res, &oldstate, lease ); // oldstate used
+                if ( 0 != oldstate ) {
+                    // Then not called from init_all_network_interfaces()
+                    // so we must initialize the interface ourselves
+                    if (!init_net(intf, res)) {
+                        do_dhcp_down_net( intf, res, pstate, lease );
+                        *pstate = DHCPSTATE_FAILED;
+                        return false;
+                    }
+                }
+            }
+
+            // Otherwise, nothing whatsoever to do...
+            return true;
+
+        case DHCPSTATE_NOTBOUND:
+            // All done with socket
+            close(s);
+            // No lease active
+            no_lease( lease );
+            // Leave interface up so app can tidy.
+            return false;
+
+        case DHCPSTATE_FAILED:
+            // All done with socket
+            close(s);
+            // No lease active
+            no_lease( lease );
+            // Unconditionally down the interface.
+            do_dhcp_down_net( intf, res, &oldstate, lease );
+            return false;
+
+        case DHCPSTATE_DO_RELEASE:
+            // We have been forced here by external means, to release the
+            // lease for graceful shutdown.
+
+            // Just send what you got with a DHCPRELEASE in the message
+            // type UNICAST straight to the server.  No ACK.  Then go to
+            // NOTBOUND state.
+            NEW_XID( xid );
+            xmit->bp_xid = xid;
+            xmit->bp_op = BOOTREQUEST;
+            xmit->bp_flags = htons(0); // no BROADCAST FLAG
+            // Use the *client* address here:
+            xmit->bp_ciaddr.s_addr = xmit->bp_yiaddr.s_addr;
+
+            set_fixed_tag( xmit, TAG_DHCP_MESS_TYPE, DHCPRELEASE, 1 );
+
+            // Set unicast address to *server*
+            server_addr.sin_addr.s_addr = res->bp_siaddr.s_addr;
+
+#ifdef CYGDBG_NET_DHCP_CHATTER
+            diag_printf( "---------DHCPSTATE_DO_RELEASE sending:\n" );
+            diag_printf( "UNICAST to family %d, addr %08x, port %d\n",
+                         server_addr.sin_family,
+                         server_addr.sin_addr.s_addr,
+                         server_addr.sin_port );
+            show_bootp( intf, xmit );
+#endif            
+            if(sendto(s, xmit, dhcp_size_for_send(xmit), 0, 
+                       // UNICAST address of the server:
+                      (struct sockaddr *)&server_addr,
+                      sizeof(server_addr)) < 0) {
+                *pstate = DHCPSTATE_FAILED;
+                break;
+            }
+
+            *pstate = DHCPSTATE_NOTBOUND;
+            break;
+
+        default:
+            no_lease( lease );
+            close(s);
+            return false;
+        }
+    }
+    /* NOTREACHED */
+    return false;
+}
+
+// ------------------------------------------------------------------------
+// Bring an interface down, failed to initialize it or lease is expired
+// Also part of normal startup, bring down for proper reinitialization
+
+int
+do_dhcp_down_net(const char *intf, struct bootp *res,
+        cyg_uint8 *pstate, struct dhcp_lease *lease)
+{
+    struct sockaddr_in *addrp;
+    struct ifreq ifr;
+    int s;
+
+    // Ensure clean slate
+    cyg_route_reinit();  // Force any existing routes to be forgotten
+
+    s = socket(AF_INET, SOCK_DGRAM, 0);
+    if (s < 0) {
+        perror("socket");
+        return false;
+    }
+
+    addrp = (struct sockaddr_in *) &ifr.ifr_addr;
+
+    // Remove any existing address
+    if ( DHCPSTATE_FAILED  == *pstate
+         || DHCPSTATE_INIT == *pstate
+         || 0              == *pstate ) {
+        // it was configured for broadcast only, "half-up"
+        memset(addrp, 0, sizeof(*addrp));
+        addrp->sin_family = AF_INET;
+        addrp->sin_len = sizeof(*addrp);
+        addrp->sin_port = 0;
+        addrp->sin_addr.s_addr = INADDR_ANY;
+    }
+    else {
+        // get the specific address that was used
+        strcpy(ifr.ifr_name, intf);
+        if (ioctl(s, SIOCGIFADDR, &ifr)) {
+            perror("SIOCGIFADDR 1");
+            return false;
+        }
+    }
+
+    strcpy(ifr.ifr_name, intf);
+    if (ioctl(s, SIOCDIFADDR, &ifr)) { /* delete IF addr */
+        perror("SIOCDIFADDR1");
+    }
+
+    // Shut down interface so it can be reinitialized
+    ifr.ifr_flags &= ~(IFF_UP | IFF_RUNNING);
+    if (ioctl(s, SIOCSIFFLAGS, &ifr)) { /* set ifnet flags */
+        perror("SIOCSIFFLAGS down");
+        return false;
+    }
+
+    // All done with socket
+    close(s);
+
+    if ( 0 != *pstate ) // preserve initial state
+        *pstate = DHCPSTATE_INIT;
+
+    return true;
+}
+
+// ------------------------------------------------------------------------
+// Release (relinquish) a leased address - if we have one - and bring down
+// the interface.
+int
+do_dhcp_release(const char *intf, struct bootp *res,
+        cyg_uint8 *pstate, struct dhcp_lease *lease)
+{
+    if ( 0                           != *pstate
+         && DHCPSTATE_INIT           != *pstate
+         && DHCPSTATE_NOTBOUND       != *pstate
+         && DHCPSTATE_FAILED         != *pstate
+         && DHCPSTATE_BOOTP_FALLBACK != *pstate ) {
+        *pstate = DHCPSTATE_DO_RELEASE;
+        do_dhcp( intf, res, pstate, lease ); // to send the release packet
+        cyg_thread_delay( 100 );             // to let it leave the building
+    }
+    return true;
+}
+
+// ------------------------------------------------------------------------
+
+#endif // CYGPKG_NET_DHCP
+
+// EOF dhcp_prot.c