#include <machine/rtems-bsd-kernel-space.h>

/*
 *  DCHP client for RTEMS
 *  Andrew Bythell, <abythell@nortelnetworks.com>
 *  based on and uses subroutines from c/src/libnetworking/nfs/bootp_subr.c
 */

/*
 * DHCP task added.
 * Brendan Gannon, <bgannon@cybertec.com.au>
 */

/*
 * Added interface to terminate DHCP task, and removed panics.
 * Arnout Vandecappelle <arnout@mind.be>, Essensium/Mind
 * Maarten Van Es <maarten@mind.be>, Essensium/Mind
 */

/*
 * Copyright (c) 1995 Gordon Ross, Adam Glass
 * Copyright (c) 1992 Regents of the University of California.
 * All rights reserved.
 *
 * This software was developed by the Computer Systems Engineering group
 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
 * contributed to Berkeley.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Lawrence Berkeley Laboratory and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <machine/rtems-bsd-kernel-space.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <rtems.h>
#include <rtems/error.h>
#include <rtems/rtems_bsdnet.h>
#include <rtems/bsdnet/servers.h>

#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/sockio.h>
#include <sys/param.h>		/* for MAXHOSTNAMELEN */
#include <sys/systm.h>
#include <sys/socketvar.h>	/* for socreat() soclose() */
#include <sys/socket.h>

#include <net/if.h>
#include <net/if_var.h>
#include <netinet/in.h>		/* for NBO-HBO conversions */
#include <net/if_types.h>	/* for IFT_ETHER */
#include <net/if_dl.h>		/* for LLADDR */

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <rtems/mkrootfs.h>

#include "rtems/dhcp.h"
#include "rtems/bootp.h"

#ifndef EALEN
#define EALEN 6
#endif

/*
 *DHCP flags
 */
#define DHCP_BROADCAST 0x8000
#define DHCP_UNICAST   0x0000

/*
 * DHCP Op Codes
 */
#define DHCP_BOOTREQUEST 1
#define DHCP_BOOTREPLY   2

/*
 * DHCP Messages
 */
#define DHCP_DISCOVER 1
#define DHCP_OFFER    2
#define DHCP_REQUEST  3
#define DHCP_DECLINE  4
#define DHCP_ACK      5
#define DHCP_NACK     6
#define DHCP_RELEASE  7

/*
 * DHCP Options
 */
#define DHCP_OPTION_PAD    0
#define DHCP_SUBNET        1
#define DHCP_GATEWAY       3
#define DHCP_DNS           6
#define DHCP_HOST          12
#define DHCP_DOMAIN_NAME   15
#define DHCP_NETMASK       28
#define DHCP_REQUESTED_IP  50
#define DHCP_LEASE         51
#define DHCP_MESSAGE       53
#define DHCP_SERVER        54
#define DHCP_PARAMETERS    55
#define DHCP_OPTION_END    255

/*
 * Definitions from RFC
 */
struct dhcp_packet
{
  u_int8_t       op;
  u_int8_t       htype;
  u_int8_t       hlen;
  u_int8_t       hops;
  u_int32_t      xid;
  u_int16_t      secs;
  u_int16_t      flags;
  struct in_addr ciaddr;
  struct in_addr yiaddr;
  struct in_addr siaddr;
  struct in_addr giaddr;
  unsigned char  chaddr[16];
  char           sname[64];
  char           file[128];
  unsigned char  vend[312];
};

/*
 * Variables
 */
static int                dhcp_option_overload = 0;
static char               dhcp_gotgw = 0;
static char               dhcp_gotnetmask = 0;
static char               dhcp_gotserver = 0;
static char               dhcp_gotlogserver = 0;
static struct sockaddr_in dhcp_netmask;
static struct sockaddr_in dhcp_gw;
static char               *dhcp_hostname;
static int                dhcp_message_type = 0;
static unsigned long      dhcp_lease_time;
static unsigned long      dhcp_elapsed_time = 0;
static const char         dhcp_magic_cookie[4] = { 99, 130, 83, 99 };
static const char         dhcp_request_parameters[5] = { DHCP_SUBNET,
                                                         DHCP_GATEWAY,
                                                         DHCP_DNS,
                                                         DHCP_HOST,
                                                         DHCP_DOMAIN_NAME };
#define NUM_NAMESERVERS \
  (sizeof rtems_bsdnet_config.name_server / sizeof rtems_bsdnet_config.name_server[0])
static struct in_addr rtems_dhcpd_nameserver[NUM_NAMESERVERS];
static int rtems_dhcpd_nameserver_count = 0;

/*
 * Clean any DNS entries add by a DHCP request.
 */
static void
clean_dns_entries (void)
{
  int e;
  for (e = 0; e < rtems_dhcpd_nameserver_count; ++e)
  {
    int n;
    for (n = 0; n < rtems_bsdnet_nameserver_count; ++ n)
    {
      if (memcmp (&rtems_dhcpd_nameserver[e], &rtems_bsdnet_nameserver[n], 4) == 0)
      {
        if (n < (NUM_NAMESERVERS - 1))
          memmove (&rtems_bsdnet_nameserver[n],
                   &rtems_bsdnet_nameserver[n + 1],
                   (NUM_NAMESERVERS - n - 1) * 4);
        --rtems_bsdnet_nameserver_count;
      }
    }
  }
  rtems_dhcpd_nameserver_count = 0;
}

/*
 * Format an IP address in dotted decimal.
 */
static void
format_ip (unsigned long ip, char* buffer)
{
  sprintf (buffer,
           "%lu.%lu.%lu.%lu",
          (ip >> 24),
          (ip >> 16) & 0xff,
          (ip >>  8) & 0xff,
          (ip        & 0xff));

  return;
}

/*
 * Print the IP setup
 */
static void
printsetup (const char     *iface,
            struct in_addr ip_addr,
            struct in_addr mask_addr,
            struct in_addr srv_addr,
            struct in_addr gw_addr)
{
  unsigned long ip;
  char ip_str[15];

  printf ("dhcpc: %s: ", iface);

  ip = ntohl (ip_addr.s_addr);
  format_ip (ip, ip_str);
  printf ("inet: %-15s ", ip_str);

  ip = ntohl (mask_addr.s_addr);
  format_ip (ip, ip_str);
  printf ("mask: %-15s\n", ip_str);

  ip = ntohl (srv_addr.s_addr);
  format_ip (ip, ip_str);
  printf   ("             srv: %-15s ", ip_str);

  ip = ntohl (gw_addr.s_addr);
  format_ip (ip, ip_str);
  printf ("  gw: %-15s\n", ip_str);

  return;
}

/*
 * Process options from the DHCP packet.
 * Based on BOOTP routine.
 */
static void
process_options (unsigned char *optbuf, int optbufSize)
{
  int j = 0;
  int len;
  int code, ncode;
  char *p;

  dhcp_message_type = 0;

  ncode = optbuf[0];
  while (j < optbufSize)
  {
    code = optbuf[j] = ncode;
    if (code == 255)
      return;
    if (code == 0)
    {
      j++;
      continue;
    }
    len = optbuf[j + 1];
    j += 2;

    if ((len + j) >= optbufSize)
    {
      printf ("Truncated field for code %d", code);
      return;
    }

    ncode = optbuf[j + len];
    optbuf[j + len] = '\0';
    p = (char*) &optbuf[j];
    j += len;

    /*
     * Process the option
     */
    switch (code)
    {
      case 1:
        /* Subnet mask */
        if (len != 4) {
          printf ("dhcpc: subnet mask len is %d\n", len);
          continue;
        }
        memcpy (&dhcp_netmask.sin_addr, p, 4);
        dhcp_gotnetmask = 1;
        break;

      case 2:
        /* Time offset */
        if (len != 4) {
          printf ("dhcpc: time offset len is %d\n", len);
          continue;
        }
        memcpy (&rtems_bsdnet_timeoffset, p, 4);
        rtems_bsdnet_timeoffset = ntohl (rtems_bsdnet_timeoffset);
        break;

      case 3:
        /* Routers */
        if (len % 4) {
          printf ("dhcpc: Router Len is %d\n", len);
          continue;
        }
        if (len > 0)
        {
          memcpy (&dhcp_gw.sin_addr, p, 4);
          dhcp_gotgw = 1;
        }
        break;

      case 42:
        /* NTP servers */
        if (len % 4) {
          printf ("dhcpc: time server Len is %d\n", len);
          continue;
        }
        {
          int tlen = 0;
          while ((tlen < len) &&
                 (rtems_bsdnet_ntpserver_count <
                  sizeof rtems_bsdnet_config.ntp_server /
                  sizeof rtems_bsdnet_config.ntp_server[0]))
          {
            memcpy (&rtems_bsdnet_ntpserver[rtems_bsdnet_ntpserver_count],
                    p + tlen, 4);
            rtems_bsdnet_ntpserver_count++;
            tlen += 4;
          }
        }
        break;

      case 6:
        /* Domain Name servers */
        if (len % 4) {
          printf ("dhcpc: DNS Len is %d\n", len);
          continue;
        }
        {
          int dlen = 0;
          while ((dlen < len) &&
                 (rtems_dhcpd_nameserver_count < NUM_NAMESERVERS) &&
                 (rtems_bsdnet_nameserver_count < NUM_NAMESERVERS))
          {
            memcpy (&rtems_dhcpd_nameserver
                    [rtems_dhcpd_nameserver_count], p + dlen, 4);
            rtems_dhcpd_nameserver_count++;
            memcpy (&rtems_bsdnet_nameserver
                    [rtems_bsdnet_nameserver_count], p + dlen, 4);
            rtems_bsdnet_nameserver_count++;
            dlen += 4;
          }
        }
        break;

      case 12:
        /* Host name */
        if (len >= MAXHOSTNAMELEN) {
          printf ("dhcpc: hostname >= %d bytes\n", MAXHOSTNAMELEN);
          len = MAXHOSTNAMELEN-1;
        }
        if (sethostname (p, len) < 0) {
          printf ("dhcpc: can't set host name");
        }
        if (dhcp_hostname != NULL)
        {
          char *tmp = realloc (dhcp_hostname, len);
          if (tmp != NULL) {
            dhcp_hostname = tmp;
            strncpy (dhcp_hostname, p, len);
          } else {  /* realloc failed */
            printf ("dhcpc: realloc failed (%s:%d)", __FILE__, __LINE__);
            free (dhcp_hostname, 0);
            dhcp_hostname = NULL;
          }
        } else { /* dhcp_hostname == NULL */
          dhcp_hostname = strndup (p, len);
        }
        break;

      case 7:
        /* Log servers */
        if (len % 4) {
          printf ("dhcpc: Log server Len is %d\n", len);
          continue;
        }
        if (len > 0)
        {
          memcpy (&rtems_bsdnet_log_host_address, p, 4);
          dhcp_gotlogserver = 1;
        }
        break;

      case 15:
        /* Domain name */
        if (p[0])
        {
          rtems_bsdnet_domain_name = strdup (p);
        }
        break;

      case 16:		/* Swap server IP address. unused */
        break;

      case 50:
        /* DHCP Requested IP Address */
        if (len != 4)
          printf ("dhcpc: DHCP option requested IP len is %d", len);
        /*
         * although nothing happens here, this case keeps the client
         * from complaining about unknown options.  The Requested IP
         * is necessary to return to the server for a DHCP REQUEST
         */
        break;

      case 51:
        /* DHCP Lease Length */
        if (len != 4) {
          printf ("dhcpc: DHCP option lease-length len is %d", len);
          continue;
        }
        memcpy (&dhcp_lease_time, &p[0], 4);
        dhcp_lease_time = ntohl (dhcp_lease_time);
        break;

      case 52:
        /* DHCP option override */
        if (len != 1) {
          printf ("dhcpc: DHCP option overload len is %d", len);
          continue;
        }
        dhcp_option_overload = p[0];
        break;

      case 53:
        /* DHCP message */
        if (len != 1) {
          printf ("dhcpc: DHCP message len is %d", len);
          continue;
        }
        dhcp_message_type = p[0];
        break;

      case 128:		/* Site-specific option for DHCP servers that
                   *   a) don't supply tag 54
                   * and
                   *   b) don't supply the server address in siaddr
                   * For example, on Solaris 2.6 in.dhcpd, include in the dhcptab:
                   *    Bootsrv s Site,128,IP,1,1
                   * and use that symbol in the macro that defines the client:
                   *    Bootsrv=<tftp-server-ip-address>
                   */
      case 54:
        /* DHCP server */
        if (len != 4) {
          printf ("dhcpc: DHCP server len is %d", len);
          continue;
        }
        memcpy (&rtems_bsdnet_bootp_server_address, p, 4);
        dhcp_gotserver = 1;
        break;

      case 66:
        /* DHCP server name option */
        if (p[0])
          rtems_bsdnet_bootp_server_name = strdup (p);
        break;

      case 67:
        /* DHCP bootfile option */
        if (p[0])
          rtems_bsdnet_bootp_boot_file_name = strdup (p);
        break;

      default:
        break;
    }
  }
}

/*
 * Generate the packet for a DHCP DISCOVER.
 */
static int
dhcp_discover_req (struct dhcp_packet* call,
                   struct sockaddr_dl *sdl,
                   unsigned long *xid)
{
  int len = 0;

  memset (call, 0, sizeof (struct dhcp_packet));

  /*
   * Send a DHCP DISCOVER Message
   */
  call->op = DHCP_BOOTREQUEST;
  call->htype = 1;		/* 10mb ethernet */
  call->hlen = sdl->sdl_alen;	/* Hardware address length */
  call->hops = 0;
  (*xid)++;
  call->xid = htonl (*xid);
  call->flags = htons (DHCP_BROADCAST);

  memcpy (&call->chaddr, LLADDR (sdl), sdl->sdl_alen);

  /*
   * Magic cookie.
   */
  memcpy (&call->vend[len], dhcp_magic_cookie, sizeof (dhcp_magic_cookie));
  len += sizeof (dhcp_magic_cookie);

  /*
   * DHCP Message type.
   */
  call->vend[len++] = DHCP_MESSAGE;
  call->vend[len++] = 1;
  call->vend[len++] = DHCP_DISCOVER;

  /*
   * If a host name is set add it to the request.
   */
  if (rtems_bsdnet_config.hostname && \
      (strlen (rtems_bsdnet_config.hostname) > 1) &&
      (strlen (rtems_bsdnet_config.hostname) < 32)) {
    call->vend[len++] = DHCP_HOST;
    call->vend[len++] = strlen (rtems_bsdnet_config.hostname);
    memcpy (&call->vend[len],
            rtems_bsdnet_config.hostname,
            strlen (rtems_bsdnet_config.hostname));
    len += strlen (rtems_bsdnet_config.hostname);
  }

  /*
   * DHCP Parameter request list
   */
  call->vend[len++] = DHCP_PARAMETERS;
  call->vend[len++] = sizeof (dhcp_request_parameters);
  memcpy (&call->vend[len], &dhcp_request_parameters, sizeof (dhcp_request_parameters));
  len += sizeof (dhcp_request_parameters);

  /*
   * Lease time.
   */
  call->vend[len++] = DHCP_LEASE;
  call->vend[len++] = 4;
  memset (&call->vend[len], 0xFF, 4);	/* request infinite lease time */
  len += 4;

  /*
   * End.
   */
  call->vend[len++] = DHCP_OPTION_END;
  call->secs = 0;

  return len;
}

/*
 * Generate the packet for a DHCP REQUEST.
 */
static int
dhcp_request_req (struct dhcp_packet* call,
                  struct dhcp_packet* reply,
                  struct sockaddr_dl *sdl,
                  int broadcast)
{
  int           len = 0;
  unsigned long temp;
  char          *hostname;

  memset (call, 0, sizeof (struct dhcp_packet));

  /*
   * Send a DHCP REQUEST Message
   */
  call->op = DHCP_BOOTREQUEST;
  call->htype = 1;		/* 10mb ethernet */
  call->hlen = sdl->sdl_alen;	/* Hardware address length */
  call->hops = 0;
  call->xid = reply->xid;
  if (broadcast)
    call->flags = htons (DHCP_BROADCAST);
  else
  {
    call->flags = htons (DHCP_UNICAST);
    call->ciaddr = reply->yiaddr;
  }
  memcpy (&call->chaddr, LLADDR (sdl), sdl->sdl_alen);

  /*
   * Magic cookie.
   */
  memcpy (&call->vend[len], dhcp_magic_cookie, sizeof (dhcp_magic_cookie));
  len += sizeof (dhcp_magic_cookie);

  /*
   * DHCP Message type.
   */
  call->vend[len++] = DHCP_MESSAGE;
  call->vend[len++] = 1;
  call->vend[len++] = DHCP_REQUEST;

  /*
   * DHCP server
   */
  if (broadcast)
  {
    call->vend[len++] = DHCP_SERVER;
    call->vend[len++] = sizeof (rtems_bsdnet_bootp_server_address);
    memcpy (&call->vend[len], &rtems_bsdnet_bootp_server_address,
            sizeof (rtems_bsdnet_bootp_server_address));
    len += sizeof (rtems_bsdnet_bootp_server_address);
  }

  /*
   * Requested IP
   */
  call->vend[len++] = DHCP_REQUESTED_IP;
  call->vend[len++] = sizeof (reply->yiaddr);
  memcpy (&call->vend[len], &reply->yiaddr, sizeof (reply->yiaddr));
  len += sizeof (reply->yiaddr);

  /*
   * DHCP Parameter request list
   */
  call->vend[len++] = DHCP_PARAMETERS;
  call->vend[len++] = sizeof (dhcp_request_parameters);
  memcpy (&call->vend[len], &dhcp_request_parameters, sizeof (dhcp_request_parameters));
  len += sizeof (dhcp_request_parameters);

  /*
   * Lease time.
   * For the REQUEST, return the lease time the server offered.
   */
  call->vend[len++] = DHCP_LEASE;
  call->vend[len++] = 4;
  temp = htonl (dhcp_lease_time);
  memcpy (&call->vend[len], &temp, sizeof (unsigned long));
  len += 4;

  /*
   * Host name.
   */
  hostname = malloc (MAXHOSTNAMELEN, 0, M_NOWAIT);
  if (hostname != NULL)
  {
    if (gethostname (hostname, MAXHOSTNAMELEN) == 0)
    {
      call->vend[len++] = DHCP_HOST;
      call->vend[len++] = strlen (hostname);
      strcpy ((char*) &call->vend[len], hostname);
      len += strlen (hostname);
    }
    free (hostname, 0);
  }

  /*
   * End.
   */
  call->vend[len++] = DHCP_OPTION_END;
  call->secs = 0;

  return len;
}

/*
 * Variables for the DHCP task.
 */
static struct dhcp_packet dhcp_req;
static rtems_id           dhcp_task_id;

/*
 * The DHCP task counts until half the lease time has expired.
 * When this period is up, it sends a DHCP REQUEST packet to the
 * server again to renew the lease.
 * If the lease is renewed, the task starts counting again.
 * If the lease is not renewed, the task retries until it is.
 *
 * The task will not rebind if the lease is not renewed.
 */
static void
dhcp_task (rtems_task_argument _sdl)
{
  unsigned long       count;
  struct dhcp_packet  call;
  struct sockaddr_dl  *sdl;
  rtems_event_set     event_out;
  unsigned int        timeout = 0;
  int                 error;
  struct proc *procp = NULL;
  rtems_status_code   ev_st;

  sdl = (struct sockaddr_dl *) _sdl;

  count = dhcp_elapsed_time;

  while (true)
  {
    /*
     * Sleep until the next poll
     */
    timeout = RTEMS_MILLISECONDS_TO_TICKS (1000);
    ev_st = rtems_event_receive (RTEMS_EVENT_0,
                                 RTEMS_WAIT | RTEMS_EVENT_ANY,
                                 timeout, &event_out);

    /*
     * Check if not a poll timeout. So when ANY event received, exit task.
     * Actually, only event RTEMS_EVENT_0 sent from rtem_dhcp_failsafe.c
     * if "failsafe" dhcp enabled when interface down.  Otherwise, no
     * event should occur, just timeout.
     */
    if(ev_st != RTEMS_TIMEOUT)
        break;

    count++;

    if (count >= (dhcp_lease_time / 2))
    {
      rtems_bsdnet_semaphore_obtain ();

      dhcp_request_req (&call, &dhcp_req, sdl, true);

      /*
       * Send the Request.
       */
      error = bootpc_call ((struct bootp_packet *)&call,
                           (struct bootp_packet *)&dhcp_req, procp, NULL, 0);
      if (error) {
        rtems_bsdnet_semaphore_release ();
        printf ("DHCP call failed -- error %d", error);
        continue;
      }

      /*
       * Check for DHCP ACK/NACK
       */
      if (memcmp (&dhcp_req.vend[0],
                  dhcp_magic_cookie,
                  sizeof (dhcp_magic_cookie)) != 0)
      {
        rtems_bsdnet_semaphore_release ();
        printf ("DHCP server did not send Magic Cookie.\n");
        continue;
      }

      /*
       * We have an ack. Clear the DNS entries that have been assigned by a previous
       * DHCP request.
       */
      clean_dns_entries ();

      /*
       * Process this requests options.
       */
      process_options (&dhcp_req.vend[4], sizeof (dhcp_req.vend) - 4);

      if (dhcp_message_type != DHCP_ACK)
      {
        rtems_bsdnet_semaphore_release ();
        printf ("DHCP server did not accept the DHCP request");
        continue;
      }

      rtems_bsdnet_semaphore_release ();

      count = 0;
    }
  }


  dhcp_task_id = 0;
  printf ("dhcpc: exiting lease renewal task.\n");
  rtems_task_exit();

}

/*
 * Start the DHCP task.
 */
static rtems_status_code
dhcp_start_task (struct sockaddr_dl *sdl,
                 struct dhcp_packet *reply,
                 int priority)
{
  rtems_status_code sc;

  memcpy (&dhcp_req, reply, sizeof (struct dhcp_packet));

  sc = rtems_task_create (rtems_build_name ('d','h','c','p'),
                          priority,
                          2048,
                          RTEMS_PREEMPT |
                          RTEMS_NO_TIMESLICE |
                          RTEMS_NO_ASR |
                          RTEMS_INTERRUPT_LEVEL (0),
                          RTEMS_LOCAL,
                          &dhcp_task_id);

  if (sc != RTEMS_SUCCESSFUL)
    return sc;

  sc = rtems_task_start (dhcp_task_id,
                         dhcp_task,
                         (rtems_task_argument) sdl);

  if (sc != RTEMS_SUCCESSFUL)
    return sc;

  return RTEMS_SUCCESSFUL;
}

/*
 * Check if the chosen interface already has an IP.
 */
static int
dhcp_interface_has_ip (struct ifreq *ireq, struct socket *so, struct proc *procp)
{
  struct sockaddr_in* sin;
  int error;

  /*
   * Check if the interface is already up.
   */
  error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)ireq, procp);
  if (error)
    return 0;

  if ((ireq->ifr_flags & IFF_UP) == 0)
    return 0;

  sin = (struct sockaddr_in *)&ireq->ifr_addr;
  bzero ((caddr_t)sin, sizeof (struct sockaddr_in));
  sin->sin_len = sizeof (struct sockaddr_in);
  sin->sin_family = AF_INET;
  error = ifioctl (so, SIOCGIFADDR, (caddr_t)ireq, procp);
  if (error)
    return 0;

  if (sin->sin_addr.s_addr != 0)
    return 1;

  return 0;
}

/*
 *  DCHP Client Routine
 *    - The first DHCP offer is always accepted
 *    - No DHCP DECLINE message is sent if ARPing fails
 *
 *    return value:
 *    0: ok
 *    < 0: failed to startup or configure interface
 */
static int
dhcp_init (int update_files)
{
  struct dhcp_packet   call;
  struct dhcp_packet   reply;
  static unsigned long xid = ~0xFF;
  struct ifreq         ireq;
  struct ifnet         *ifp;
  struct socket        *so;
  int                  error;
  struct sockaddr_in   myaddr;
  struct ifaddr        *ifa;
  struct sockaddr_dl   *sdl = NULL;
  struct proc          *procp = NULL;
  char expected_dhcp_payload[7];

  clean_dns_entries();

  /*
   * If we are to update the files create the root
   * file structure.
   */
  if (update_files)
    if (rtems_create_root_fs () < 0) {
      printf("Error creating the root filesystem.\nFile not created.\n");
      update_files = 0;
    }

  /*
   * Find a network interface.
   */
  for (ifp = ifnet; ifp != 0; ifp = ifp->if_next)
    if ((ifp->if_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) == 0)
      break;
  if (ifp == NULL){
    printf ("dhcpc_init: no suitable interface\n");
    return -1;
  }

  memset (&ireq, 0, sizeof (ireq));
  sprintf (ireq.ifr_name, "%s%d", ifp->if_name, ifp->if_unit);

  if ((error = socreate (AF_INET, &so, SOCK_DGRAM, 0, procp)) != 0) {
    printf ("dhcpc_init: socreate, error: %s\n", strerror(error));
    return -1;
  }

  if (!dhcp_interface_has_ip (&ireq, so, procp))
    bootpc_fakeup_interface (&ireq, so, procp);

  /*
   * Get HW address
   */
  for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next)
    if (ifa->ifa_addr->sa_family == AF_LINK &&
        (sdl = ((struct sockaddr_dl *) ifa->ifa_addr)) &&
        sdl->sdl_type == IFT_ETHER)
      break;

  if (!sdl){
    printf ("dhcpc_init: Unable to find HW address\n");
    soclose (so);
    return -1;
  }
  if (sdl->sdl_alen != EALEN) {
    printf ("dhcpc_init: HW address len is %d, expected value is %d\n",
           sdl->sdl_alen, EALEN);
    soclose (so);
    return -1;
  }


  /*
   * Build the DHCP Discover
   */
  dhcp_discover_req (&call, sdl, &xid);

  /*
   * Expect a DHCP offer as response to DHCP discover
   */
  memcpy(expected_dhcp_payload, dhcp_magic_cookie, sizeof(dhcp_magic_cookie));
  expected_dhcp_payload[sizeof(dhcp_magic_cookie)  ]=0x35; /* DHCP */
  expected_dhcp_payload[sizeof(dhcp_magic_cookie)+1]=0x01; /* Length : 1 */
  expected_dhcp_payload[sizeof(dhcp_magic_cookie)+2]=0x02; /* DHCP_OFFER */

  /*
   * Send the Discover.
   */
  error = bootpc_call ((struct bootp_packet *)&call,
                       (struct bootp_packet *)&reply, procp,
                       expected_dhcp_payload, sizeof(expected_dhcp_payload));
  if (error) {
    printf ("BOOTP call failed -- %s\n", strerror(error));
    soclose (so);
    return -1;
  }

  /*
   * Check for DHCP OFFER
   */
  if (memcmp (&reply.vend[0], dhcp_magic_cookie, sizeof (dhcp_magic_cookie)) != 0) {
    printf ("DHCP server did not send Magic Cookie.\n");
    soclose (so);
    return -1;
  }

  process_options (&reply.vend[4], sizeof (reply.vend) - 4);

  if (dhcp_message_type != DHCP_OFFER) {
    printf ("DHCP server did not send a DHCP Offer.\n");
    soclose (so);
    return -1;
  }

  /*
   * Expect a DHCP_ACK as response to the DHCP REQUEST
   * No need to reinitialize the whole expected_dhcp_payload variable,
   * header and first two bytes of the payload are filled from DHCP offer
   */
  expected_dhcp_payload[sizeof(dhcp_magic_cookie)+2]=0x05; /* DHCP_ACK */

  /*
   * Send a DHCP REQUEST
   */
  dhcp_request_req (&call, &reply, sdl, true);

  error = bootpc_call ((struct bootp_packet *)&call,
                       (struct bootp_packet *)&reply, procp,
                       expected_dhcp_payload, sizeof(expected_dhcp_payload));
  if (error) {
    printf ("BOOTP call failed -- %s\n", strerror(error));
    soclose (so);
    return -1;
  }

  /*
   * Check for DHCP ACK/NACK
   */
  if (memcmp (&reply.vend[0], dhcp_magic_cookie, sizeof (dhcp_magic_cookie)) != 0) {
    printf ("DHCP server did not send Magic Cookie.\n");
    soclose (so);
    return -1;
  }

  process_options (&reply.vend[4], sizeof (reply.vend) - 4);

  if (dhcp_message_type != DHCP_ACK) {
    printf ("DHCP server did not accept the DHCP request\n");
    soclose (so);
    return -1;
  }

  /*
   * Initialize network address structures
   */
  memset (&myaddr, 0, sizeof (myaddr));
  memset (&dhcp_netmask, 0, sizeof (dhcp_netmask));
  memset (&dhcp_gw, 0, sizeof (dhcp_gw));
  myaddr.sin_len = sizeof (myaddr);
  myaddr.sin_family = AF_INET;
  dhcp_netmask.sin_len = sizeof (dhcp_netmask);
  dhcp_netmask.sin_family = AF_INET;
  dhcp_gw.sin_len = sizeof (dhcp_gw);
  dhcp_gw.sin_family = AF_INET;

  /*
   * Set our address
   */
  myaddr.sin_addr = reply.yiaddr;

  /*
   * Process BOOTP/DHCP options
   */
  if (memcmp (&reply.vend[0], dhcp_magic_cookie, sizeof (dhcp_magic_cookie)) == 0)
    process_options (&reply.vend[4], sizeof (reply.vend) - 4);

  if (dhcp_option_overload & 1)
    process_options ((unsigned char*) reply.file, sizeof reply.file);
  else
    if (reply.file[0])
      rtems_bsdnet_bootp_boot_file_name = strdup (reply.file);

  if (dhcp_option_overload & 2)
    process_options ((unsigned char*) reply.sname, sizeof reply.sname);
  else
    if (reply.sname[0])
      rtems_bsdnet_bootp_server_name = strdup (reply.sname);

  /*
   * Use defaults if values were not supplied by BOOTP/DHCP options
   */
  if (!dhcp_gotnetmask)
  {
    if (IN_CLASSA (ntohl (myaddr.sin_addr.s_addr)))
      dhcp_netmask.sin_addr.s_addr = htonl (IN_CLASSA_NET);
    else if (IN_CLASSB (ntohl (myaddr.sin_addr.s_addr)))
      dhcp_netmask.sin_addr.s_addr = htonl (IN_CLASSB_NET);
    else
      dhcp_netmask.sin_addr.s_addr = htonl (IN_CLASSC_NET);
  }

  if (!dhcp_gotserver)
    rtems_bsdnet_bootp_server_address = reply.siaddr;

  if (!dhcp_gotgw)
    dhcp_gw.sin_addr = reply.giaddr;

  if (!dhcp_gotlogserver)
    rtems_bsdnet_log_host_address = rtems_bsdnet_bootp_server_address;

  printsetup (ifp->if_name, myaddr.sin_addr, dhcp_netmask.sin_addr,
              rtems_bsdnet_bootp_server_address, dhcp_gw.sin_addr);

  /*
   * Update the files if we are asked too.
   */
  if (update_files) {
    char *dn = rtems_bsdnet_domain_name;
    char *hn = dhcp_hostname;
    if (!dn)
      dn = "mydomain";
    if (!hn)
    {
      hn = "me";
      sethostname (hn, strlen (hn));
    }
    rtems_rootfs_append_host_rec(myaddr.sin_addr.s_addr, hn, dn);

    /*
     * Should the given domainname be used here ?
     */
    if (dhcp_gotserver) {
      if (rtems_bsdnet_bootp_server_name)
        hn = rtems_bsdnet_bootp_server_name;
      else
        hn = "bootps";
      rtems_rootfs_append_host_rec(rtems_bsdnet_bootp_server_address.s_addr,
                                   hn, dn);
    }

    if (dhcp_gotlogserver) {
      rtems_rootfs_append_host_rec(rtems_bsdnet_log_host_address.s_addr,
                                   "logs", dn);
    }

    /*
     * Setup the DNS configuration file /etc/resolv.conf.
     */
    if (rtems_bsdnet_nameserver_count) {
      int        i;
      char       buf[64];
      const char *bufl[1];

      bufl[0] = buf;

#define MKFILE_MODE (S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH)

      if (rtems_bsdnet_domain_name &&
          (strlen(rtems_bsdnet_domain_name) < (sizeof(buf) - 1))) {
        strcpy(buf, "search ");
        strcat(buf, rtems_bsdnet_domain_name);
        strcat(buf, "\n");
        rtems_rootfs_file_append ("/etc/resolv.conf", MKFILE_MODE, 1, bufl);
      }

      for (i = 0; i < rtems_bsdnet_nameserver_count; i++) {
        char addrbuf[INET_ADDRSTRLEN];
        strcpy(buf, "nameserver ");
        strcat(buf, inet_ntoa_r(rtems_bsdnet_ntpserver[i], addrbuf));
        strcat(buf, "\n");
        if (rtems_rootfs_file_append ("/etc/resolv.conf", MKFILE_MODE, 1, bufl))
          break;
      }
    }
  }

  /*
   * Configure the interface with the new settings
   */
  error = bootpc_adjust_interface (&ireq, so,
                                   &myaddr, &dhcp_netmask, &dhcp_gw, procp);

  /*
   * Start the DHCP task if the lease isn't infinite.
   */
  if (dhcp_lease_time != 0xffffffff)
    dhcp_start_task (sdl, &reply, 150);

  soclose (so);

  return 0;
}

/*
 *
 *  RTEMS Entry point to DHCP client
 *
 */
void rtems_bsdnet_do_dhcp (void)
{
  bool update = true;
  rtems_bsdnet_semaphore_obtain ();
  while( dhcp_init (update) < 0 ) {
    update = false;
    rtems_bsdnet_semaphore_release();
    rtems_task_wake_after(RTEMS_MILLISECONDS_TO_TICKS(1000));
    rtems_bsdnet_semaphore_obtain ();
  }
  rtems_bsdnet_semaphore_release ();
}

int rtems_bsdnet_do_dhcp_timeout( void )
{
  int return_value;

  rtems_bsdnet_semaphore_obtain ();
  return_value = dhcp_init (false);
  rtems_bsdnet_semaphore_release ();

  return return_value;
}

void rtems_bsdnet_dhcp_down (void)
{
   if(dhcp_task_id != 0) {
     rtems_event_send (dhcp_task_id, RTEMS_EVENT_0);
   }
}

void
rtems_bsdnet_do_dhcp_refresh_only (unsigned long xid,
                                   unsigned long lease_time,
                                   unsigned long elapsed_time,
                                   unsigned long ip_address,
                                   unsigned long srv_address,
                                   const char*   hostname)
{
  struct dhcp_packet reply;
  struct ifnet       *ifp = NULL;
  struct ifaddr      *ifa = NULL;
  struct sockaddr_dl *sdl = NULL;
  struct sockaddr_in *sin = NULL;
  int                match = 0;
  struct ifnet       *mtif = NULL;

  /*
   * If an infinite lease has been granted, no task is needed.
   */
  if (lease_time == 0xffffffff)
    return;

  /*
   * Find a network interface.
   */
  for (ifp = ifnet; (ifp != NULL) && !match; ifp = ifp->if_next)
    if ((ifp->if_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) == 0)
      for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next)
        if (ifa->ifa_addr->sa_family == AF_INET)
        {
          sin = (struct sockaddr_in *) ifa->ifa_addr;
          if (sin->sin_addr.s_addr == htonl (ip_address))
          {
            mtif = ifp;
            match = 1;
            break;
          }
        }

  if (!match) {
    printf ("dhcpc: no matching interface\n");
    return;
  }

  for (ifa = mtif->if_addrlist; ifa != NULL; ifa = ifa->ifa_next)
    if (ifa->ifa_addr->sa_family == AF_LINK &&
        (sdl = ((struct sockaddr_dl *) ifa->ifa_addr)) &&
        sdl->sdl_type == IFT_ETHER)
      break;

  if (!match) {
    printf ("dhcpc: no matching interface address\n");
    return;
  }

  /*
   * Set up given values in a simulated DHCP reply.
   */
  memset (&reply, 0x00, sizeof (reply));
  reply.xid = htonl (xid);
  reply.yiaddr.s_addr = htonl (ip_address);
  reply.siaddr.s_addr = htonl (srv_address);
  if (reply.siaddr.s_addr != rtems_bsdnet_bootp_server_address.s_addr)
  {
    memcpy (&rtems_bsdnet_bootp_server_address, &reply.siaddr,
            sizeof (reply.siaddr));
  }

  dhcp_lease_time = lease_time;
  dhcp_elapsed_time = elapsed_time;

  if (hostname)
  {
    sethostname ((char *) hostname, strlen (hostname));
    dhcp_hostname = bootp_strdup_realloc (dhcp_hostname, hostname);
  }

  dhcp_start_task (sdl, &reply, 150);
}
