diff options
author | patacongo <patacongo@42af7a65-404d-4744-a932-0658087f49c3> | 2011-03-19 21:04:13 +0000 |
---|---|---|
committer | patacongo <patacongo@42af7a65-404d-4744-a932-0658087f49c3> | 2011-03-19 21:04:13 +0000 |
commit | 7cc856ea2f1808e98387ea66537ecbc6c3de2f88 (patch) | |
tree | 673b6eef191373f0607e5b9a9f79ad390e9bb970 /apps/netutils/dhcpd | |
parent | 7267882ebd0a6aa79cf88b7f42675804eaff1bcf (diff) | |
download | nuttx-7cc856ea2f1808e98387ea66537ecbc6c3de2f88.tar.gz nuttx-7cc856ea2f1808e98387ea66537ecbc6c3de2f88.tar.bz2 nuttx-7cc856ea2f1808e98387ea66537ecbc6c3de2f88.zip |
Move nuttx/netutils to apps/netutils
git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@3401 42af7a65-404d-4744-a932-0658087f49c3
Diffstat (limited to 'apps/netutils/dhcpd')
-rw-r--r-- | apps/netutils/dhcpd/Makefile | 94 | ||||
-rw-r--r-- | apps/netutils/dhcpd/dhcpd.c | 1393 |
2 files changed, 1487 insertions, 0 deletions
diff --git a/apps/netutils/dhcpd/Makefile b/apps/netutils/dhcpd/Makefile new file mode 100644 index 000000000..bcfb0330f --- /dev/null +++ b/apps/netutils/dhcpd/Makefile @@ -0,0 +1,94 @@ +############################################################################ +# apps/netutils/dhcpd/Makefile +# +# Copyright (C) 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# 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. Neither the name NuttX 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 COPYRIGHT HOLDERS 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 +# COPYRIGHT OWNER 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 $(TOPDIR)/.config +-include $(TOPDIR)/Make.defs +include $(APPDIR)/Make.defs + +# DHCP Daemn Library + +ASRCS = +CSRCS = + +ifeq ($(CONFIG_NET_UDP),y) +CSRCS += dhcpd.c +endif + +AOBJS = $(ASRCS:.S=$(OBJEXT)) +COBJS = $(CSRCS:.c=$(OBJEXT)) + +SRCS = $(ASRCS) $(CSRCS) +OBJS = $(AOBJS) $(COBJS) + +BIN = ../../libapps$(LIBEXT) + +ROOTDEPPATH = --dep-path . + +# Common build + +VPATH = + +all: .built + +$(AOBJS): %$(OBJEXT): %.S + $(call ASSEMBLE, $<, $@) + +$(COBJS): %$(OBJEXT): %.c + $(call COMPILE, $<, $@) + +$(BIN): $(OBJS) + @( for obj in $(OBJS) ; do \ + $(call ARCHIVE, $@, $${obj}); \ + done ; ) + @touch .built + +.built: $(BIN) + +.depend: Makefile $(SRCS) + @$(MKDEP) $(ROOTDEPPATH) \ + $(CC) -- $(CFLAGS) -- $(SRCS) >Make.dep + @touch $@ + +# Register application +depend: .depend + +clean: + @rm -f $(BIN) *.o *~ .*.swp .built + $(call CLEAN) + +distclean: clean + @rm -f Make.dep .depend + +-include Make.dep diff --git a/apps/netutils/dhcpd/dhcpd.c b/apps/netutils/dhcpd/dhcpd.c new file mode 100644 index 000000000..39050bd62 --- /dev/null +++ b/apps/netutils/dhcpd/dhcpd.c @@ -0,0 +1,1393 @@ +/**************************************************************************** + * netutils/dhcpd/dhcpd.c + * + * Copyright (C) 2007-2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * 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. Neither the name NuttX 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 COPYRIGHT HOLDERS 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 + * COPYRIGHT OWNER 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#ifdef CONFIG_NETUTILS_DHCPD_HOST +# include <stdio.h> + +# define HTONS(a) htons(a) +# define HTONL(a) htonl(a) + +# define CONFIG_CPP_HAVE_WARNING 1 +# define FAR + +# define ndbg(...) printf(__VA_ARGS__) +# define nvdbg(...) printf(__VA_ARGS__) + +# define ERROR (-1) +# define OK (0) +#else +# include <nuttx/config.h> /* NuttX configuration */ +# include <debug.h> /* For ndbg, vdbg */ +# include <nuttx/compiler.h> /* For CONFIG_CPP_HAVE_WARNING */ +# include <arch/irq.h> /* For irqstore() and friends -- REVISIT */ +# include <net/uip/uip-arp.h> /* For low-level ARP interfaces -- REVISIT */ +# include <apps/netutils/dhcpd.h> /* Advertised DHCPD APIs */ +#endif + +#include <sys/socket.h> +#include <sys/ioctl.h> + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <time.h> +#include <errno.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#define DHCP_SERVER_PORT 67 +#define DHCP_CLIENT_PORT 68 + +/* Option codes understood in this file */ +/* Code Data Description */ +/* Length */ +#define DHCP_OPTION_PAD 1 /* 1 Pad */ +#define DHCP_OPTION_REQ_IPADDR 50 /* 4 Requested IP Address */ +#define DHCP_OPTION_LEASE_TIME 51 /* 4 IP address lease time */ +#define DHCP_OPTION_OVERLOAD 52 /* 1 Option overload */ +#define DHCP_OPTION_MSG_TYPE 53 /* 1 DHCP message type */ +#define DHCP_OPTION_SERVER_ID 54 /* 4 Server identifier */ +#define DHCP_OPTION_END 255 /* 0 End */ + +/* Values for the dhcp msg 'op' field */ + +#define DHCP_REQUEST 1 +#define DHCP_REPLY 2 + +/* DHCP message types understood in this file */ + +#define DHCPDISCOVER 1 /* Received from client only */ +#define DHCPOFFER 2 /* Sent from server only */ +#define DHCPREQUEST 3 /* Received from client only */ +#define DHCPDECLINE 4 /* Received from client only */ +#define DHCPACK 5 /* Sent from server only */ +#define DHCPNAK 6 /* Sent from server only */ +#define DHCPRELEASE 7 /* Received from client only */ +#define DHCPINFORM 8 /* Not used */ + +/* The form of an option is: + * code - 1 byte + * length - 1 byte + * data - variable number of bytes + */ + +#define DHCPD_OPTION_CODE 0 +#define DHCPD_OPTION_LENGTH 1 +#define DHCPD_OPTION_DATA 2 + +/* Size of options in DHCP message */ + +#define DHCPD_OPTIONS_SIZE 312 + +/* Values for htype and hlen field */ + +#define DHCP_HTYPE_ETHERNET 1 +#define DHCP_HLEN_ETHERNET 6 + +/* Values for flags field */ + +#define BOOTP_BROADCAST 0x8000 + +/* Legal values for this option are: + * + * 1 the 'file' field is used to hold options + * 2 the 'sname' field is used to hold options + * 3 both fields are used to hold options + */ + +#define DHCPD_OPTION_FIELD 0 +#define DHCPD_FILE_FIELD 1 +#define DHCPD_SNAME_FIELD 2 + +#ifndef CONFIG_NETUTILS_DHCPD_LEASETIME +# define CONFIG_NETUTILS_DHCPD_LEASETIME (60*60*24*10) /* 10 days */ +# undef CONFIG_NETUTILS_DHCPD_MINLEASETIME +# undef CONFIG_NETUTILS_DHCPD_MAXLEASETIME +#endif + +#ifndef CONFIG_NETUTILS_DHCPD_MINLEASETIME +# define CONFIG_NETUTILS_DHCPD_MINLEASETIME (60*60*24*1) /* 1 days */ +#endif + +#ifndef CONFIG_NETUTILS_DHCPD_MAXLEASETIME +# define CONFIG_NETUTILS_DHCPD_MAXLEASETIME (60*60*24*30) /* 30 days */ +#endif + +#ifndef CONFIG_NETUTILS_DHCPD_INTERFACE +# define CONFIG_NETUTILS_DHCPD_INTERFACE "eth0" +#endif + +#ifndef CONFIG_NETUTILS_DHCPD_MAXLEASES +# define CONFIG_NETUTILS_DHCPD_MAXLEASES 16 +#endif + +#ifndef CONFIG_NETUTILS_DHCPD_STARTIP +# define CONFIG_NETUTILS_DHCPD_STARTIP (10L<<24|0L<<16|0L<<16|2L) +#endif + +#undef CONFIG_NETUTILS_DHCP_OPTION_ENDIP +#define CONFIG_NETUTILS_DHCP_OPTION_ENDIP \ + (CONFIG_NETUTILS_DHCPD_STARTIP + CONFIG_NETUTILS_DHCPD_MAXLEASES - 1) + +#ifndef CONFIG_NETUTILS_DHCPD_OFFERTIME +# define CONFIG_NETUTILS_DHCPD_OFFERTIME (60*60) /* 1 hour */ +#endif + +#ifndef CONFIG_NETUTILS_DHCPD_DECLINETIME +# define CONFIG_NETUTILS_DHCPD_DECLINETIME (60*60) /* 1 hour */ +#endif + +#undef HAVE_LEASE_TIME +#if defined(CONFIG_NETUTILS_DHCPD_HOST) || !defined(CONFIG_DISABLE_POSIX_TIMERS) +# define HAVE_LEASE_TIME 1 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure describes one element in the lease table. There is one slot + * in the lease table for each assign-able IP address (hence, the IP address + * itself does not have to be in the table. + */ + +struct lease_s +{ + uint8_t mac[DHCP_HLEN_ETHERNET]; /* MAC address (network order) -- could be larger! */ + bool allocated; /* true: IP address is allocated */ +#ifdef HAVE_LEASE_TIME + time_t expiry; /* Lease expiration time (seconds past Epoch) */ +#endif +}; + +struct dhcpmsg_s +{ + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint8_t xid[4]; + uint16_t secs; + uint16_t flags; + uint8_t ciaddr[4]; + uint8_t yiaddr[4]; + uint8_t siaddr[4]; + uint8_t giaddr[4]; + uint8_t chaddr[16]; +#ifndef CONFIG_NET_DHCP_LIGHT + uint8_t sname[64]; + uint8_t file[128]; +#endif + uint8_t options[312]; +}; + +struct dhcpd_state_s +{ + /* Server configuration */ + + in_addr_t ds_serverip; /* The server IP address */ + + /* Message buffers */ + + struct dhcpmsg_s ds_inpacket; /* Holds the incoming DHCP client message */ + struct dhcpmsg_s ds_outpacket; /* Holds the outgoing DHCP server message */ + + /* Parsed options from the incoming DHCP client message */ + + uint8_t ds_optmsgtype; /* Incoming DHCP message type */ + in_addr_t ds_optreqip; /* Requested IP address (host order) */ + in_addr_t ds_optserverip; /* Serverip IP address (host order) */ + time_t ds_optleasetime; /* Requested lease time (host order) */ + + /* End option pointer for outgoing DHCP server message */ + + uint8_t *ds_optend; + + /* Leases */ + + struct lease_s ds_leases[CONFIG_NETUTILS_DHCPD_MAXLEASES]; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const uint8_t g_magiccookie[4] = {99, 130, 83, 99}; +static const uint8_t g_anyipaddr[4] = {0, 0, 0, 0}; +static struct dhcpd_state_s g_state; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: dhcpd_arpupdate + ****************************************************************************/ + +#ifndef CONFIG_NETUTILS_DHCPD_HOST +static inline void dhcpd_arpupdate(uint16_t *pipaddr, uint8_t *phwaddr) +{ + uip_lock_t flags; + + /* Disable interrupts and update the ARP table -- very non-portable hack. + * REVISIT -- switch to the SIOCSARP ioctl call if/when it is implemented. + */ + + flags = uip_lock(); + uip_arp_update(pipaddr, phwaddr); + uip_unlock(flags); +} +#else +# define dhcpd_arpupdate(pipaddr,phwaddr) +#endif + +/**************************************************************************** + * Name: dhcpd_time + ****************************************************************************/ + +#ifdef CONFIG_NETUTILS_DHCPD_HOST +# define dhcpd_time() time(0) +#elif defined(HAVE_LEASE_TIME) +static time_t dhcpd_time(void) +{ + struct timespec time; + time_t ret = 0; + + if (clock_gettime(CLOCK_REALTIME, &time) == OK) + { + ret = time.tv_sec; + } + return ret; +} +#else +# define dhcpd_time() (0) +#endif + +/**************************************************************************** + * Name: dhcpd_leaseexpired + ****************************************************************************/ + +#ifdef HAVE_LEASE_TIME +static inline bool dhcpd_leaseexpired(struct lease_s *lease) +{ + if (lease->expiry < dhcpd_time()) + { + return false; + } + else + { + memset(lease, 0, sizeof(struct lease_s)); + return true; + } +} +#else +# define dhcpd_leaseexpired(lease) (false) +#endif + +/**************************************************************************** + * Name: dhcpd_setlease + ****************************************************************************/ + +struct lease_s *dhcpd_setlease(const uint8_t *mac, in_addr_t ipaddr, time_t expiry) +{ + int ndx = ntohl(ipaddr) - CONFIG_NETUTILS_DHCPD_STARTIP; + struct lease_s *ret = NULL; + + if (ndx >= 0 && ndx < CONFIG_NETUTILS_DHCPD_MAXLEASES) + { + ret = &g_state.ds_leases[ndx]; + memcpy(ret->mac, mac, DHCP_HLEN_ETHERNET); + ret->allocated = true; +#ifdef HAVE_LEASE_TIME + ret->expiry = dhcpd_time() + expiry; +#endif + } + return ret; +} + +/**************************************************************************** + * Name: dhcp_leaseipaddr + ****************************************************************************/ + +static inline in_addr_t dhcp_leaseipaddr( struct lease_s *lease) +{ + return htonl((g_state.ds_leases - lease)/sizeof(struct lease_s) + CONFIG_NETUTILS_DHCPD_STARTIP); +} + +/**************************************************************************** + * Name: dhcpd_findbymac + ****************************************************************************/ + +static struct lease_s *dhcpd_findbymac(const uint8_t *mac) +{ + int i; + + for (i = 0; i < CONFIG_NETUTILS_DHCPD_MAXLEASES; i++) + { + if (memcmp(g_state.ds_leases[i].mac, mac, DHCP_HLEN_ETHERNET) == 0) + { + return &(g_state.ds_leases[i]); + } + } + + return NULL; +} + +/**************************************************************************** + * Name: dhcpd_findbyipaddr + ****************************************************************************/ + +static struct lease_s *dhcpd_findbyipaddr(in_addr_t ipaddr) +{ + if (ipaddr >= CONFIG_NETUTILS_DHCPD_STARTIP && + ipaddr <= CONFIG_NETUTILS_DHCP_OPTION_ENDIP) + { + struct lease_s *lease = &g_state.ds_leases[ipaddr - CONFIG_NETUTILS_DHCPD_STARTIP]; + if (lease->allocated > 0) + { + return lease; + } + } + return NULL; +} + +/**************************************************************************** + * Name: dhcpd_allocipaddr + ****************************************************************************/ + +in_addr_t dhcpd_allocipaddr(void) +{ + struct lease_s *lease = NULL; + in_addr_t ipaddr; + + ipaddr = CONFIG_NETUTILS_DHCPD_STARTIP; + for (; ipaddr <= CONFIG_NETUTILS_DHCP_OPTION_ENDIP; ipaddr++) + { + if ((ipaddr & 0xff) == 0 || (ipaddr & 0xff) == 0xff) + { + continue; + } + + lease = dhcpd_findbyipaddr(ipaddr); + if ((!lease || dhcpd_leaseexpired(lease))) + { +#ifdef CONFIG_CPP_HAVE_WARNING +# warning "FIXME: Should check if anything responds to an ARP request or ping" +# warning " to verify that there is no other user of this IP address" +#endif + memset(g_state.ds_leases[ipaddr - CONFIG_NETUTILS_DHCPD_STARTIP].mac, 0, DHCP_HLEN_ETHERNET); + g_state.ds_leases[ipaddr - CONFIG_NETUTILS_DHCPD_STARTIP].allocated = true; +#ifdef HAVE_LEASE_TIME + g_state.ds_leases[ipaddr - CONFIG_NETUTILS_DHCPD_STARTIP].expiry = dhcpd_time() + CONFIG_NETUTILS_DHCPD_OFFERTIME; +#endif + return ntohl(ipaddr); + } + } + return 0; +} + +/**************************************************************************** + * Name: dhcpd_parseoptions + ****************************************************************************/ + +static inline bool dhcpd_parseoptions(void) +{ + uint32_t tmp; + uint8_t *ptr; + uint8_t overloaded; + uint8_t currfield; + int optlen = 0; + int remaining; + + /* Verify that the option field starts with a valid magic number */ + + ptr = g_state.ds_inpacket.options; + if (memcmp(ptr, g_magiccookie, 4) != 0) + { + /* Bad magic number... skip g_state.ds_outpacket */ + + ndbg("Bad magic: %d,%d,%d,%d\n", ptr[0], ptr[1], ptr[2], ptr[3]); + return false; + } + + /* Set up to parse the options */ + + ptr += 4; + remaining = DHCPD_OPTIONS_SIZE - 4; + overloaded = DHCPD_OPTION_FIELD; + currfield = DHCPD_OPTION_FIELD; + + /* Set all options to the default value */ + + g_state.ds_optmsgtype = 0; /* Incoming DHCP message type */ + g_state.ds_optreqip = 0; /* Requested IP address (host order) */ + g_state.ds_optserverip = 0; /* Serverip IP address (host order) */ + g_state.ds_optleasetime = 0; /* Requested lease time (host order) */ + g_state.ds_optend = NULL; + + do + { + /* The form of an option is: + * code - 1 byte + * length - 1 byte + * data - variable number of bytes + */ + + switch (ptr[DHCPD_OPTION_CODE]) + { + /* Skip over any padding bytes */ + + case DHCP_OPTION_PAD: + optlen = 1; + break; + + /* the Overload option is used to indicate that the DHCP 'sname' or 'file' + * fields are being overloaded by using them to carry DHCP options. A DHCP + * server inserts this option if the returned parameters will exceed the + * usual space allotted for options. + * + * If this option is present, the client interprets the specified additional + * fields after it concludes interpretation of the standard option fields. + * + * Legal values for this option are: + * + * 1 the 'file' field is used to hold options + * 2 the 'sname' field is used to hold options + * 3 both fields are used to hold options + */ + +#ifndef CONFIG_NET_DHCP_LIGHT + case DHCP_OPTION_OVERLOAD: + optlen = ptr[DHCPD_OPTION_LENGTH] + 2; + if (optlen >= 1 && optlen < remaining) + { + overloaded = ptr[DHCPD_OPTION_DATA]; + } + break; +#endif + + case DHCP_OPTION_END: +#ifndef CONFIG_NET_DHCP_LIGHT + if (currfield == DHCPD_OPTION_FIELD && + (overloaded & DHCPD_FILE_FIELD) != 0) + { + ptr = g_state.ds_inpacket.file; + remaining = sizeof(g_state.ds_inpacket.file); + currfield = DHCPD_FILE_FIELD; + } + else if (currfield == DHCPD_FILE_FIELD && + (overloaded & DHCPD_SNAME_FIELD) != 0) + { + ptr = g_state.ds_inpacket.sname; + remaining = sizeof(g_state.ds_inpacket.sname); + currfield = DHCPD_SNAME_FIELD; + } + else + { + return true; + } + break; +#else + return true; +#endif + + case DHCP_OPTION_REQ_IPADDR: /* Requested IP Address */ + optlen = ptr[DHCPD_OPTION_LENGTH] + 2; + if (optlen >= 4 && optlen < remaining) + { + memcpy(&tmp, &ptr[DHCPD_OPTION_DATA], 4); + g_state.ds_optreqip = (in_addr_t)ntohl(tmp); + } + break; + + case DHCP_OPTION_LEASE_TIME: /* IP address lease time */ + optlen = ptr[DHCPD_OPTION_LENGTH] + 2; + if (optlen >= 4 && optlen < remaining) + { + memcpy(&tmp, &ptr[DHCPD_OPTION_DATA], 4); + g_state.ds_optleasetime = (time_t)ntohl(tmp); + } + break; + + case DHCP_OPTION_MSG_TYPE: /* DHCP message type */ + optlen = ptr[DHCPD_OPTION_LENGTH] + 2; + if (optlen >= 1 && optlen < remaining) + { + g_state.ds_optmsgtype = ptr[DHCPD_OPTION_DATA]; + } + break; + + case DHCP_OPTION_SERVER_ID: /* Server identifier */ + optlen = ptr[DHCPD_OPTION_LENGTH] + 2; + if (optlen >= 4 && optlen < remaining) + { + memcpy(&tmp, &ptr[DHCPD_OPTION_DATA], 4); + g_state.ds_optserverip = (in_addr_t)ntohl(tmp); + } + break; + + default: + /* Skip over unsupported options */ + + optlen = ptr[DHCPD_OPTION_LENGTH] + 2; + break; + } + + /* Advance to the next option */ + + ptr += optlen; + remaining -= optlen; + } + while (remaining > 0); + return false; +} + +/**************************************************************************** + * Name: dhcpd_verifyreqip + ****************************************************************************/ + +static inline bool dhcpd_verifyreqip(void) +{ + struct lease_s *lease; + + /* Verify that the requested IP address is within the supported lease range */ + + if (g_state.ds_optreqip > 0 && + g_state.ds_optreqip >= CONFIG_NETUTILS_DHCPD_STARTIP && + g_state.ds_optreqip <= CONFIG_NETUTILS_DHCP_OPTION_ENDIP) + { + /* And verify that the lease has not already been taken or offered + * (unless the lease/offer is expired, then the address is free game). + */ + + lease = dhcpd_findbyipaddr(g_state.ds_optreqip); + if (!lease || dhcpd_leaseexpired(lease)) + { + return true; + } + } + return false; +} + +/**************************************************************************** + * Name: dhcpd_verifyreqleasetime + ****************************************************************************/ + +static inline bool dhcpd_verifyreqleasetime(uint32_t *leasetime) +{ + uint32_t tmp = g_state.ds_optleasetime; + + /* Did the client request a specific lease time? */ + + if (tmp != 0) + { + /* Yes.. Verify that the requested lease time is within a valid range */ + + if (tmp > CONFIG_NETUTILS_DHCPD_MAXLEASETIME) + { + tmp = CONFIG_NETUTILS_DHCPD_MAXLEASETIME; + } + else if (tmp < CONFIG_NETUTILS_DHCPD_MINLEASETIME) + { + tmp = CONFIG_NETUTILS_DHCPD_MINLEASETIME; + } + + /* Return the clipped lease time */ + + *leasetime = tmp; + return true; + } + return false; +} + +/**************************************************************************** + * Name: dhcpd_addoption + ****************************************************************************/ + +static int dhcpd_addoption(uint8_t *option) +{ + int offset; + int len = 4; + + if (g_state.ds_optend) + { + offset = g_state.ds_outpacket.options - g_state.ds_optend; + len = option[DHCPD_OPTION_LENGTH] + 2; + + /* Check if the option will fit into the options array */ + + if (offset + len + 1 < DHCPD_OPTIONS_SIZE) + { + /* Copy the option into the option array */ + + memcpy(g_state.ds_optend, option, len); + g_state.ds_optend += len; + *g_state.ds_optend = DHCP_OPTION_END; + } + } + return len; +} + +/**************************************************************************** + * Name: dhcpd_addoption8 + ****************************************************************************/ + +static int dhcpd_addoption8(uint8_t code, uint8_t value) +{ + uint8_t option[3]; + + /* Construct the option sequence */ + + option[DHCPD_OPTION_CODE] = code; + option[DHCPD_OPTION_LENGTH] = 1; + option[DHCPD_OPTION_DATA] = value; + + /* Add the option sequence to the response */ + + return dhcpd_addoption(option); +} + +/**************************************************************************** + * Name: dhcpd_addoption32 + ****************************************************************************/ + +static int dhcpd_addoption32(uint8_t code, uint32_t value) +{ + uint8_t option[6]; + + /* Construct the option sequence */ + + option[DHCPD_OPTION_CODE] = code; + option[DHCPD_OPTION_LENGTH] = 4; + memcpy(&option[DHCPD_OPTION_DATA], &value, 4); + + /* Add the option sequence to the response */ + + return dhcpd_addoption(option); +} + +/**************************************************************************** + * Name: dhcpd_soclet + ****************************************************************************/ + +static inline int dhcpd_socket(void) +{ + int sockfd; +#if defined(HAVE_SO_REUSEADDR) || defined(HAVE_SO_BROADCAST) + int optval; + int ret; +#endif + + /* Create a socket to listen for requests from DHCP clients */ + + sockfd = socket(PF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + ndbg("socket failed: %d\n", errno); + return ERROR; + } + + /* Configure the socket */ + +#ifdef HAVE_SO_REUSEADDR + optval = 1; + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&optval, sizeof(int)); + if (ret < 0) + { + ndbg("setsockopt SO_REUSEADDR failed: %d\n", errno); + close(sockfd); + return ERROR; + } +#endif + +#ifdef HAVE_SO_BROADCAST + optval = 1; + ret = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (void*)&optval, sizeof(int)); + if (ret < 0) + { + ndbg("setsockopt SO_BROADCAST failed: %d\n", errno); + close(sockfd); + return ERROR; + } +#endif + + return sockfd; + +} + +/**************************************************************************** + * Name: dhcpd_openresponder + ****************************************************************************/ + +static inline int dhcpd_openresponder(void) +{ + struct sockaddr_in addr; + int sockfd; + int ret; + + nvdbg("Responder: %08lx\n", (long)g_state.ds_serverip); + + /* Create a socket to listen for requests from DHCP clients */ + + sockfd = dhcpd_socket(); + if (sockfd < 0) + { + ndbg("socket failed: %d\n", errno); + return ERROR; + } + + /* Bind the socket to a local port.*/ + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = g_state.ds_serverip; + + ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); + if (ret < 0) + { + ndbg("bind failed, port=%d addr=%08lx: %d\n", + addr.sin_port, (long)addr.sin_addr.s_addr, errno); + close(sockfd); + return ERROR; + } + + return sockfd; +} + +/**************************************************************************** + * Name: dhcpd_initpacket + ****************************************************************************/ + +static void dhcpd_initpacket(uint8_t mtype) +{ + uint32_t nulladdr = 0; + + /* Set up the generic parts of the DHCP server message */ + + memset(&g_state.ds_outpacket, 0, sizeof(struct dhcpmsg_s)); + + g_state.ds_outpacket.op = DHCP_REPLY; + g_state.ds_outpacket.htype = g_state.ds_inpacket.htype; + g_state.ds_outpacket.hlen = g_state.ds_inpacket.hlen; + + memcpy(&g_state.ds_outpacket.xid, &g_state.ds_inpacket.xid, 4); + memcpy(g_state.ds_outpacket.chaddr, g_state.ds_inpacket.chaddr, 16); + + if (memcmp(g_state.ds_outpacket.giaddr, &nulladdr, 4) != 0) + { + g_state.ds_outpacket.flags = g_state.ds_inpacket.flags; + } + else + { + g_state.ds_outpacket.flags = 0; + } + memset(g_state.ds_outpacket.giaddr, 0, 4); + + /* Add the generic options */ + + memcpy(g_state.ds_outpacket.options, g_magiccookie, 4); + g_state.ds_optend = &g_state.ds_outpacket.options[4]; + *g_state.ds_optend = DHCP_OPTION_END; + dhcpd_addoption8(DHCP_OPTION_MSG_TYPE, mtype); + dhcpd_addoption32(DHCP_OPTION_SERVER_ID, g_state.ds_serverip); +} + +/**************************************************************************** + * Name: dhcpd_sendpacket + ****************************************************************************/ + +static int dhcpd_sendpacket(int bbroadcast) +{ + struct sockaddr_in addr; + in_addr_t ipaddr; + int sockfd; + int len; + int ret = ERROR; + +#ifdef CONFIG_NETUTILS_DHCPD_IGNOREBROADCAST + /* This is a hack. I've had problems with Windows machines responding + * to unicast. I think this is associated with a Windows registry key in + * HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DHCPServer\Parameters: + * The IgnoreBroadcastFlag value controls this behavior: A value of 1 will + * cause the server to ignore the client broadcast flag and always respond + * with multicast; the value 0 to allows clients to request unicast. + */ + + ipaddr = INADDR_BROADCAST; +#else + /* Determine which address to respond to (or if we need to broadcast the response) + * + * (1) If he caller know that it needs to multicast the response, it will set bbroadcast. + * (2) Otherwise, if the client already has and address (ciaddr), then use that for uni-cast + * (3) Broadcast if the client says it can't handle uni-cast (BOOTP_BROADCAST set) + * (4) Otherwise, the client claims it can handle the uni-casst response and we + * will uni-cast to the offered address (yiaddr). + * + * NOTE: We really should also check the giaddr field. If no zero, the server should + * send any return messages to the 'DHCP server' port on the BOOTP relay agent whose + * address appears in 'giaddr'. + */ + + if (bbroadcast) + { + ipaddr = INADDR_BROADCAST; + } + else if (memcmp(g_state.ds_outpacket.ciaddr, g_anyipaddr, 4) != 0) + { + dhcpd_arpupdate((uint16_t*)g_state.ds_outpacket.ciaddr, g_state.ds_outpacket.chaddr); + memcpy(&ipaddr, g_state.ds_outpacket.ciaddr, 4); + } + else if (g_state.ds_outpacket.flags & HTONS(BOOTP_BROADCAST)) + { + ipaddr = INADDR_BROADCAST; + } + else + { + dhcpd_arpupdate((uint16_t*)g_state.ds_outpacket.yiaddr, g_state.ds_outpacket.chaddr); + memcpy(&ipaddr, g_state.ds_outpacket.yiaddr, 4); + } +#endif + + /* Create a socket to respond with a packet to the client. We + * cannot re-use the listener socket because it is not bound correctly + */ + + sockfd = dhcpd_openresponder(); + if (sockfd >= 0) + { + /* Then send the reponse to the DHCP client port at that address */ + + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = HTONS(DHCP_CLIENT_PORT); + addr.sin_addr.s_addr = ipaddr; + + /* Send the minimum sized packet that includes the END option */ + + len = (g_state.ds_optend - (uint8_t*)&g_state.ds_outpacket) + 1; + nvdbg("sendto %08lx:%04x len=%d\n", + (long)addr.sin_addr.s_addr, addr.sin_port, len); + + ret = sendto(sockfd, &g_state.ds_outpacket, len, 0, + (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); + close(sockfd); + } + return ret; +} + +/**************************************************************************** + * Name: dhcpd_sendoffer + ****************************************************************************/ + +static inline int dhcpd_sendoffer(in_addr_t ipaddr, uint32_t leasetime) +{ + nvdbg("Sending offer: %08lx\n", (long)ipaddr); + + /* Initialize the outgoing packet */ + + dhcpd_initpacket(DHCPOFFER); + + /* Add the address offered to the client */ + + memcpy(g_state.ds_outpacket.yiaddr, &ipaddr, 4); + + /* Add the leasetime to the response options */ + + dhcpd_addoption32(DHCP_OPTION_LEASE_TIME, htonl(leasetime)); + + /* Send the offer response */ + +#ifdef CONFIG_NETUTILS_DHCPD_IGNOREBROADCAST + return dhcpd_sendpacket(true); +#else + return dhcpd_sendpacket(false); +#endif +} + +/**************************************************************************** + * Name: dhcpd_sendnak + ****************************************************************************/ + +static int dhcpd_sendnak(void) +{ + /* Initialize and send the NAK response */ + + dhcpd_initpacket(DHCPNAK); + memcpy(g_state.ds_outpacket.ciaddr, g_state.ds_inpacket.ciaddr, 4); + return dhcpd_sendpacket(true); +} + +/**************************************************************************** + * Name: dhcpd_sendack + ****************************************************************************/ + +int dhcpd_sendack(in_addr_t ipaddr) +{ + uint32_t leasetime = CONFIG_NETUTILS_DHCPD_LEASETIME; + + /* Initialize the ACK response */ + + dhcpd_initpacket(DHCPACK); + memcpy(g_state.ds_outpacket.ciaddr, g_state.ds_inpacket.ciaddr, 4); + + /* Add the IP address assigned to the client */ + + memcpy(g_state.ds_outpacket.yiaddr, &ipaddr, 4); + + /* Did the client request a specific lease time? */ + + (void)dhcpd_verifyreqleasetime(&leasetime); + + /* Add the lease time to the response */ + + dhcpd_addoption32(DHCP_OPTION_LEASE_TIME, htonl(leasetime)); + +#ifdef CONFIG_NETUTILS_DHCPD_IGNOREBROADCAST + if (dhcpd_sendpacket(true) < 0) +#else + if (dhcpd_sendpacket(false) < 0) +#endif + { + return ERROR; + } + + dhcpd_setlease(g_state.ds_inpacket.chaddr, ipaddr, leasetime); + return OK; +} + +/**************************************************************************** + * Name: dhcpd_discover + ****************************************************************************/ + +static inline int dhcpd_discover(void) +{ + struct lease_s *lease; + in_addr_t ipaddr; + uint32_t leasetime = CONFIG_NETUTILS_DHCPD_LEASETIME; + + /* Check if the client is aleady in the lease table */ + + lease = dhcpd_findbymac(g_state.ds_inpacket.chaddr); + if (lease) + { + /* Yes... get the remaining time on the lease */ + +#ifdef HAVE_LEASE_TIME + if (!dhcpd_leaseexpired(lease)) + { + leasetime = lease->expiry - dhcpd_time(); + if (leasetime < CONFIG_NETUTILS_DHCPD_MINLEASETIME) + { + leasetime = CONFIG_NETUTILS_DHCPD_MINLEASETIME; + } + } +#endif + /* Get the IP address associated with the lease */ + + ipaddr = dhcp_leaseipaddr(lease); + nvdbg("Already have lease for IP %08lx\n", (long)ipaddr); + } + + /* Check if the client has requested a specific IP address */ + + else if (dhcpd_verifyreqip()) + { + /* Use the requested IP address */ + + ipaddr = g_state.ds_optreqip; + nvdbg("Use requested IP %08lx\n", (long)ipaddr); + } + else + { + /* No... allocate a new IP address */ + + ipaddr = dhcpd_allocipaddr(); + nvdbg("Allocated IP %08lx\n", (long)ipaddr); + } + + /* Did we get any IP address? */ + + if (!ipaddr) + { + /* Nope... return failure */ + + ndbg("Failed to get IP address\n"); + return ERROR; + } + + /* Reserve the leased IP for a shorter time for the offer */ + + if (!dhcpd_setlease(g_state.ds_inpacket.chaddr, ipaddr, CONFIG_NETUTILS_DHCPD_OFFERTIME)) + { + ndbg("Failed to set lease\n"); + return ERROR; + } + + /* Check if the client has requested a specific lease time */ + + (void)dhcpd_verifyreqleasetime(&leasetime); + + /* Send the offer response */ + + return dhcpd_sendoffer(ipaddr, leasetime); +} + +/**************************************************************************** + * Name: dhcpd_request + ****************************************************************************/ + +static inline int dhcpd_request(void) +{ + struct lease_s *lease; + in_addr_t ipaddr; + uint8_t response = 0; + + /* Check if this client already holds a lease. This can happen when the client (1) + * the IP is reserved for the client from a previous offer, or (2) the client is + * re-initializing or rebooting while the lease is still valid. + */ + + lease = dhcpd_findbymac(g_state.ds_inpacket.chaddr); + if (lease) + { + /* Yes.. the client already holds a lease. Verify that the request is consistent + * with the existing lease. + */ + + ipaddr = dhcp_leaseipaddr(lease); + if (g_state.ds_optserverip) + { + /* ACK if the serverip is correct and the requested IP address is the one + * already offered to the client. + */ + + if (g_state.ds_optserverip == ntohl(g_state.ds_serverip) && + (g_state.ds_optreqip != 0 || g_state.ds_optreqip == ipaddr)) + { + response = DHCPACK; + } + else + { + response = DHCPNAK; + } + } + + /* We have the lease and no server IP was requested. Was as specific IP address + * requested? + */ + + else if (g_state.ds_optreqip) + { + /* Yes..ACK if the requested IP address is the one already leased */ + + if (ipaddr == g_state.ds_optreqip) + { + response = DHCPACK; + } + else + { + response = DHCPNAK; + } + } + + /* The client has specified neither a server IP nor requested IP address */ + + else + { + /* ACK if the IP used by the client is the one already assigned to it */ + + if (memcmp(&ipaddr, g_state.ds_inpacket.ciaddr, 4) == 0) + { + response = DHCPACK; + } + else + { + response = DHCPNAK; + } + } + } + + /* The client does not hold a lease (referenced by its MAC address) and is + * requesting a specific IP address that was, apparently, never offered to + * to the client. Perform some sanity checks before sending the NAK. + */ + + else if (g_state.ds_optreqip && !g_state.ds_optserverip) + { + /* Is this IP address already assigned? */ + + lease = dhcpd_findbyipaddr(g_state.ds_optreqip); + if (lease) + { + /* Yes.. Send NAK unless the lease has expired */ + + if (!dhcpd_leaseexpired(lease)) + { + response = DHCPNAK; + } + } + + /* No.. is the requested IP address in range? NAK if not */ + + else if (g_state.ds_optreqip < CONFIG_NETUTILS_DHCPD_STARTIP || + g_state.ds_optreqip > CONFIG_NETUTILS_DHCP_OPTION_ENDIP) + { + response = DHCPNAK; + } + } + + /* Finally, either (1) send the ACK, (2) send a NAK, or (3) remain silent + * based on the checks above. + */ + + if (response == DHCPACK) + { + nvdbg("ACK IP %08lx\n", (long)ipaddr); + dhcpd_sendack(ipaddr); + } + else if (response == DHCPNAK) + { + nvdbg("NAK IP %08lx\n", (long)ipaddr); + dhcpd_sendnak(); + } + + return OK; +} + +/**************************************************************************** + * Name: dhcpd_decline + ****************************************************************************/ + +static inline int dhcpd_decline(void) +{ + struct lease_s *lease; + + /* Find the lease associated with this hardware address */ + + lease = dhcpd_findbymac(g_state.ds_inpacket.chaddr); + if (lease) + { + /* Disassociate the IP from the MAC, but prevent re-used of this + * address for a period of time. + */ + + memset(lease->mac, 0, DHCP_HLEN_ETHERNET); +#ifdef HAVE_LEASE_TIME + lease->expiry = dhcpd_time() + CONFIG_NETUTILS_DHCPD_DECLINETIME; +#endif + } + return OK; +} + +static inline int dhcpd_release(void) +{ + struct lease_s *lease; + + /* Find the lease associated with this hardware address */ + + lease = dhcpd_findbymac(g_state.ds_inpacket.chaddr); + if (lease) + { + /* Release the IP address now */ + + memset(lease, 0, sizeof(struct lease_s)); + } + return OK; +} + +/**************************************************************************** + * Name: dhcpd_openlistener + ****************************************************************************/ + +static inline int dhcpd_openlistener(void) +{ + struct sockaddr_in addr; + struct ifreq req; + int sockfd; + int ret; + + /* Create a socket to listen for requests from DHCP clients */ + + sockfd = dhcpd_socket(); + if (sockfd < 0) + { + ndbg("socket failed: %d\n", errno); + return ERROR; + } + + /* Get the IP address of the selected device */ + + strncpy(req.ifr_name, CONFIG_NETUTILS_DHCPD_INTERFACE, IFNAMSIZ); + ret = ioctl(sockfd, SIOCGIFADDR, (unsigned long)&req); + if (ret < 0) + { + ndbg("setsockopt SIOCGIFADDR failed: %d\n", errno); + close(sockfd); + return ERROR; + } + g_state.ds_serverip = ((struct sockaddr_in*)&req.ifr_addr)->sin_addr.s_addr; + nvdbg("serverip: %08lx\n", ntohl(g_state.ds_serverip)); + + /* Bind the socket to a local port. We have to bind to INADDRY_ANY to + * receive broadcast messages. + */ + + addr.sin_family = AF_INET; + addr.sin_port = htons(DHCP_SERVER_PORT); + addr.sin_addr.s_addr = INADDR_ANY; + + ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); + if (ret < 0) + { + ndbg("bind failed, port=%d addr=%08lx: %d\n", + addr.sin_port, (long)addr.sin_addr.s_addr, errno); + close(sockfd); + return ERROR; + } + + return sockfd; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: dhcpd_run + ****************************************************************************/ + +int dhcpd_run(void) +{ + int sockfd; + int nbytes; + + nvdbg("Started\n"); + + /* Initialize everything to zero */ + + memset(&g_state, 0, sizeof(struct dhcpd_state_s)); + + /* Now loop indefinitely, reading packets from the DHCP server socket */ + + sockfd = -1; + for (;;) + { + /* Create a socket to listen for requests from DHCP clients */ + + if (sockfd < 0) + { + sockfd = dhcpd_openlistener(); + if (sockfd < 0) + { + ndbg("Failed to create socket\n"); + break; + } + } + + /* Read the next g_state.ds_outpacket */ + + nbytes = recv(sockfd, &g_state.ds_inpacket, sizeof(struct dhcpmsg_s), 0); + if (nbytes < 0) + { + /* On errors (other EINTR), close the socket and try again */ + + ndbg("recv failed: %d\n", errno); + if (errno != EINTR) + { + close(sockfd); + sockfd = -1; + } + continue; + } + + /* Parse the incoming message options */ + + if (!dhcpd_parseoptions()) + { + /* Failed to parse the message options */ + + ndbg("No msg type\n"); + continue; + } + +#ifdef CONFIG_NETUTILS_DHCPD_HOST + /* Get the poor little uC a change to get its recvfrom in place */ + + usleep(500*1000); +#endif + + /* Now process the incoming DHCP message by its message type */ + + switch (g_state.ds_optmsgtype) + { + case DHCPDISCOVER: + nvdbg("DHCPDISCOVER\n"); + dhcpd_discover(); + break; + + case DHCPREQUEST: + nvdbg("DHCPREQUEST\n"); + dhcpd_request(); + break; + + case DHCPDECLINE: + nvdbg("DHCPDECLINE\n"); + dhcpd_decline(); + break; + + case DHCPRELEASE: + nvdbg("DHCPRELEASE\n"); + dhcpd_release(); + break; + + case DHCPINFORM: /* Not supported */ + default: + ndbg("Unsupported message type: %d\n", g_state.ds_optmsgtype); + break; + } + } + return OK; +} |