* apps/netutils/dhcpd/dhcpd.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* 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 nerr(...) printf(__VA_ARGS__)
# define ninfo(...) printf(__VA_ARGS__)
# define ERROR (-1)
# define OK (0)
#else
# include <nuttx/config.h>
# include <debug.h>
# include <nuttx/compiler.h>
# include "netutils/dhcpd.h"
#endif
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <inttypes.h>
#include <sched.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 <netinet/udp.h>
#include <arpa/inet.h>
#include "netutils/netlib.h"
* Private Data
****************************************************************************/
#define DHCP_SERVER_PORT 67
#define DHCP_CLIENT_PORT 68
* Code Data Description
* Length
*/
#define DHCP_OPTION_PAD 0
#define DHCP_OPTION_SUBNET_MASK 1
#define DHCP_OPTION_ROUTER 3
#define DHCP_OPTION_DNS_SERVER 6
#define DHCP_OPTION_REQ_IPADDR 50
#define DHCP_OPTION_LEASE_TIME 51
#define DHCP_OPTION_OVERLOAD 52
#define DHCP_OPTION_MSG_TYPE 53
#define DHCP_OPTION_SERVER_ID 54
#define DHCP_OPTION_END 255
#define DHCP_REQUEST 1
#define DHCP_REPLY 2
#define DHCPDISCOVER 1
#define DHCPOFFER 2
#define DHCPREQUEST 3
#define DHCPDECLINE 4
#define DHCPACK 5
#define DHCPNAK 6
#define DHCPRELEASE 7
#define DHCPINFORM 8
* 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
#define DHCPD_OPTIONS_SIZE 312
#define DHCP_HTYPE_ETHERNET 1
#define DHCP_HLEN_ETHERNET 6
#define BOOTP_BROADCAST 0x8000
*
* 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)
# 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)
#endif
#ifndef CONFIG_NETUTILS_DHCPD_MAXLEASETIME
# define CONFIG_NETUTILS_DHCPD_MAXLEASETIME (60*60*24*30)
#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)
#endif
#ifndef CONFIG_NETUTILS_DHCPD_DECLINETIME
# define CONFIG_NETUTILS_DHCPD_DECLINETIME (60*60)
#endif
#undef HAVE_ROUTERIP
#if defined(CONFIG_NETUTILS_DHCPD_ROUTERIP) && CONFIG_NETUTILS_DHCPD_ROUTERIP
# define HAVE_ROUTERIP 1
#endif
#undef HAVE_NETMASK
#if defined(CONFIG_NETUTILS_DHCPD_NETMASK) && CONFIG_NETUTILS_DHCPD_NETMASK
# define HAVE_NETMASK 1
#endif
#undef HAVE_DNSIP
#if defined(CONFIG_NETUTILS_DHCPD_DNSIP) && CONFIG_NETUTILS_DHCPD_DNSIP
# define HAVE_DNSIP 1
#endif
#undef HAVE_LEASE_TIME
#if defined(CONFIG_NETUTILS_DHCPD_HOST) || !defined(CONFIG_DISABLE_POSIX_TIMERS)
# define HAVE_LEASE_TIME 1
#endif
#define g_state (*g_dhcpd_daemon.ds_data)
* Private Types
****************************************************************************/
* 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];
bool allocated;
#ifdef HAVE_LEASE_TIME
time_t expiry;
#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];
};
enum dhcpd_daemon_e
{
DHCPD_NOT_RUNNING = 0,
DHCPD_STARTED,
DHCPD_RUNNING,
DHCPD_STOP_REQUESTED,
DHCPD_STOPPED
};
struct dhcpd_state_s
{
in_addr_t ds_serverip;
struct dhcpmsg_s ds_inpacket;
struct dhcpmsg_s ds_outpacket;
uint8_t ds_optmsgtype;
in_addr_t ds_optreqip;
in_addr_t ds_optserverip;
time_t ds_optleasetime;
uint8_t *ds_optend;
struct lease_s ds_leases[CONFIG_NETUTILS_DHCPD_MAXLEASES];
};
* instance of the DHCPD daemon is permitted in this implementation.
*/
struct dhcpd_daemon_s
{
uint8_t ds_state;
pid_t ds_pid;
FAR struct dhcpd_state_s *ds_data;
};
struct dhcpd_config_s
{
in_addr_t ds_startip;
in_addr_t ds_endip;
#ifdef HAVE_ROUTERIP
in_addr_t ds_routerip;
#endif
#ifdef HAVE_NETMASK
in_addr_t ds_netmask;
#endif
#ifdef HAVE_DNSIP
in_addr_t ds_dnsip;
#endif
};
* Private Data
****************************************************************************/
static const uint8_t g_magiccookie[4] =
{
99, 130, 83, 99
};
* instance of the DHCPD daemon is permitted in this implementation. This
* limitation is due only to this global data structure.
*/
static struct dhcpd_daemon_s g_dhcpd_daemon =
{
DHCPD_NOT_RUNNING,
-1,
NULL
};
static struct dhcpd_config_s g_dhcpd_config =
{
CONFIG_NETUTILS_DHCPD_STARTIP,
CONFIG_NETUTILS_DHCP_OPTION_ENDIP,
#ifdef HAVE_ROUTERIP
CONFIG_NETUTILS_DHCPD_ROUTERIP,
#endif
#ifdef HAVE_NETMASK
CONFIG_NETUTILS_DHCPD_NETMASK,
#endif
#ifdef HAVE_DNSIP
CONFIG_NETUTILS_DHCPD_DNSIP
#endif
};
* Private Functions
****************************************************************************/
* Name: dhcpd_arpupdate
****************************************************************************/
#ifndef CONFIG_NETUTILS_DHCPD_IGNOREBROADCAST
# ifndef CONFIG_NETUTILS_DHCPD_HOST
static inline void dhcpd_arpupdate(FAR uint8_t *ipaddr, FAR uint8_t *hwaddr)
{
struct sockaddr_in inaddr;
* network order by the memcpy.
*/
inaddr.sin_family = AF_INET;
inaddr.sin_port = 0;
memcpy(&inaddr.sin_addr.s_addr, ipaddr, sizeof(in_addr_t));
netlib_set_arpmapping(&inaddr, hwaddr, NULL);
}
# else
# define dhcpd_arpupdate(ipaddr,hwaddr)
# endif
#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 ts;
time_t ret = 0;
if (clock_gettime(CLOCK_REALTIME, &ts) == OK)
{
ret = ts.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)
{
* ipaddr must be in host order!
*/
int ndx = ipaddr - g_dhcpd_config.ds_startip;
struct lease_s *ret = NULL;
ninfo("ipaddr: %08" PRIx32 " ipaddr: %08" PRIx32 " ndx: %d MAX: %d\n",
(uint32_t)ipaddr, (uint32_t)g_dhcpd_config.ds_startip, ndx,
CONFIG_NETUTILS_DHCPD_MAXLEASES);
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(FAR struct lease_s *lease)
{
return (in_addr_t)(lease - g_state.ds_leases) +
g_dhcpd_config.ds_startip;
}
* Name: dhcpd_findbymac
****************************************************************************/
static FAR struct lease_s *dhcpd_findbymac(FAR 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 FAR struct lease_s *dhcpd_findbyipaddr(in_addr_t ipaddr)
{
if (ipaddr >= g_dhcpd_config.ds_startip &&
ipaddr <= g_dhcpd_config.ds_endip)
{
FAR struct lease_s *lease =
&g_state.ds_leases[ipaddr - g_dhcpd_config.ds_startip];
if (lease->allocated > 0)
{
return lease;
}
}
return NULL;
}
* Name: dhcpd_allocipaddr
****************************************************************************/
static in_addr_t dhcpd_allocipaddr(void)
{
struct lease_s *lease = NULL;
in_addr_t ipaddr, startaddr;
ipaddr = startaddr = g_dhcpd_config.ds_startip;
for (; ipaddr <= g_dhcpd_config.ds_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 - startaddr].mac,
0, DHCP_HLEN_ETHERNET);
g_state.ds_leases[ipaddr - startaddr].allocated = true;
#ifdef HAVE_LEASE_TIME
g_state.ds_leases[ipaddr - startaddr].expiry =
dhcpd_time() + CONFIG_NETUTILS_DHCPD_OFFERTIME;
#endif
return ipaddr;
}
}
return 0;
}
* Name: dhcpd_parseoptions
****************************************************************************/
static inline bool dhcpd_parseoptions(void)
{
uint32_t tmp;
uint8_t *ptr;
#ifndef CONFIG_NET_DHCP_LIGHT
uint8_t overloaded;
uint8_t currfield;
#endif
int optlen = 0;
int remaining;
ptr = g_state.ds_inpacket.options;
if (memcmp(ptr, g_magiccookie, 4) != 0)
{
nerr("ERROR: Bad magic: %d,%d,%d,%d\n",
ptr[0], ptr[1], ptr[2], ptr[3]);
return false;
}
ptr += 4;
remaining = DHCPD_OPTIONS_SIZE - 4;
#ifndef CONFIG_NET_DHCP_LIGHT
overloaded = DHCPD_OPTION_FIELD;
currfield = DHCPD_OPTION_FIELD;
#endif
g_state.ds_optmsgtype = 0;
g_state.ds_optreqip = 0;
g_state.ds_optserverip = 0;
g_state.ds_optleasetime = 0;
g_state.ds_optend = NULL;
do
{
* code - 1 byte
* length - 1 byte
* data - variable number of bytes
*/
switch (ptr[DHCPD_OPTION_CODE])
{
case DHCP_OPTION_PAD:
optlen = 1;
break;
* 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 >= 3 && 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:
optlen = ptr[DHCPD_OPTION_LENGTH] + 2;
if (optlen >= 6 && optlen < remaining)
{
memcpy(&tmp, &ptr[DHCPD_OPTION_DATA], 4);
g_state.ds_optreqip = (in_addr_t)ntohl(tmp);
}
break;
case DHCP_OPTION_LEASE_TIME:
optlen = ptr[DHCPD_OPTION_LENGTH] + 2;
if (optlen >= 6 && optlen < remaining)
{
memcpy(&tmp, &ptr[DHCPD_OPTION_DATA], 4);
g_state.ds_optleasetime = (time_t)ntohl(tmp);
}
break;
case DHCP_OPTION_MSG_TYPE:
optlen = ptr[DHCPD_OPTION_LENGTH] + 2;
if (optlen >= 3 && optlen < remaining)
{
g_state.ds_optmsgtype = ptr[DHCPD_OPTION_DATA];
}
break;
case DHCP_OPTION_SERVER_ID:
optlen = ptr[DHCPD_OPTION_LENGTH] + 2;
if (optlen >= 6 && optlen < remaining)
{
memcpy(&tmp, &ptr[DHCPD_OPTION_DATA], 4);
g_state.ds_optserverip = (in_addr_t)ntohl(tmp);
}
break;
default:
optlen = ptr[DHCPD_OPTION_LENGTH] + 2;
break;
}
ptr += optlen;
remaining -= optlen;
}
while (remaining > 0);
return false;
}
* Name: dhcpd_verifyreqip
****************************************************************************/
static inline bool dhcpd_verifyreqip(void)
{
struct lease_s *lease;
* range
*/
if (g_state.ds_optreqip >= g_dhcpd_config.ds_startip &&
g_state.ds_optreqip <= g_dhcpd_config.ds_endip)
{
* (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;
if (tmp != 0)
{
* 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;
}
*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_optend - g_state.ds_outpacket.options;
len = option[DHCPD_OPTION_LENGTH] + 2;
if (offset + len + 1 < DHCPD_OPTIONS_SIZE)
{
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];
option[DHCPD_OPTION_CODE] = code;
option[DHCPD_OPTION_LENGTH] = 1;
option[DHCPD_OPTION_DATA] = value;
return dhcpd_addoption(option);
}
* Name: dhcpd_addoption32
****************************************************************************/
static int dhcpd_addoption32(uint8_t code, uint32_t value)
{
uint8_t option[6];
option[DHCPD_OPTION_CODE] = code;
option[DHCPD_OPTION_LENGTH] = 4;
memcpy(&option[DHCPD_OPTION_DATA], &value, 4);
return dhcpd_addoption(option);
}
* Name: dhcp_addoption32p
****************************************************************************/
#ifdef HAVE_DNSIP
static int dhcp_addoption32p(uint8_t code, FAR uint8_t *value)
{
uint8_t option[6];
option[DHCPD_OPTION_CODE] = code;
option[DHCPD_OPTION_LENGTH] = 4;
memcpy(&option[DHCPD_OPTION_DATA], value, 4);
return dhcpd_addoption(option);
}
#endif
* Name: dhcpd_socket
****************************************************************************/
static inline int dhcpd_socket(FAR const char *interface)
{
int sockfd;
#if defined(HAVE_SO_REUSEADDR) || defined(HAVE_SO_BROADCAST)
int optval;
int ret;
#endif
sockfd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (sockfd < 0)
{
nerr("ERROR: socket failed: %d\n", errno);
return ERROR;
}
#ifdef HAVE_SO_REUSEADDR
optval = 1;
ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (FAR void *)&optval,
sizeof(int));
if (ret < 0)
{
nerr("ERROR: 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, (FAR void *)&optval,
sizeof(int));
if (ret < 0)
{
nerr("ERROR: setsockopt SO_BROADCAST failed: %d\n", errno);
close(sockfd);
return ERROR;
}
#endif
#ifdef CONFIG_NET_BINDTODEVICE
* broadcast address at a moment when it is not possible to decide the
* target network device using the local or remote address (which is,
* by definition and purpose of DHCP, undefined yet).
*/
if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
interface, strlen(interface)) < 0)
{
ninfo("ERROR: setsockopt SO_BINDTODEVICE failed: %d\n", errno);
close(sockfd);
return ERROR;
}
#endif
return sockfd;
}
* Name: dhcpd_initpacket
****************************************************************************/
static void dhcpd_initpacket(uint8_t mtype)
{
uint32_t nulladdr = 0;
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;
}
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 sockfd, int bbroadcast)
{
struct sockaddr_in addr;
in_addr_t ipaddr;
int len;
#ifdef CONFIG_NETUTILS_DHCPD_IGNOREBROADCAST
* 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
const uint8_t anyipaddr[4] =
{
};
* response)
*
* (1) If he caller know that it needs to multicast the response, it will
* set broadcast.
* (2) Otherwise, if the client already has and address (ciaddr), then use
* that for unicast
* (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, anyipaddr, 4) != 0)
{
dhcpd_arpupdate(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(g_state.ds_outpacket.yiaddr,
g_state.ds_outpacket.chaddr);
memcpy(&ipaddr, g_state.ds_outpacket.yiaddr, 4);
}
#endif
* cannot re-use the listener socket because it is not bound correctly
*/
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;
len = (g_state.ds_optend - (FAR uint8_t *)&g_state.ds_outpacket) + 1;
ninfo("sendto %08lx:%04x len=%d\n",
(long)ntohl(addr.sin_addr.s_addr), ntohs(addr.sin_port), len);
return sendto(sockfd, &g_state.ds_outpacket, len, 0,
(struct sockaddr *)&addr, sizeof(struct sockaddr_in));
}
* Name: dhcpd_sendoffer
****************************************************************************/
static inline int dhcpd_sendoffer(int sockfd, in_addr_t ipaddr,
uint32_t leasetime)
{
in_addr_t netaddr;
#ifdef HAVE_DNSIP
uint32_t dnsaddr;
dnsaddr = htonl(g_dhcpd_config.ds_dnsip);
#endif
ninfo("Sending offer: %08lx\n", (long)ipaddr);
dhcpd_initpacket(DHCPOFFER);
netaddr = htonl(ipaddr);
memcpy(g_state.ds_outpacket.yiaddr, &netaddr, 4);
dhcpd_addoption32(DHCP_OPTION_LEASE_TIME, htonl(leasetime));
#ifdef HAVE_NETMASK
dhcpd_addoption32(DHCP_OPTION_SUBNET_MASK,
htonl(g_dhcpd_config.ds_netmask));
#endif
#ifdef HAVE_ROUTERIP
dhcpd_addoption32(DHCP_OPTION_ROUTER,
htonl(g_dhcpd_config.ds_routerip));
#endif
#ifdef HAVE_DNSIP
dhcp_addoption32p(DHCP_OPTION_DNS_SERVER, (FAR uint8_t *)&dnsaddr);
#endif
#ifdef CONFIG_NETUTILS_DHCPD_IGNOREBROADCAST
return dhcpd_sendpacket(sockfd, true);
#else
return dhcpd_sendpacket(sockfd, false);
#endif
}
* Name: dhcpd_sendnak
****************************************************************************/
static int dhcpd_sendnak(int sockfd)
{
dhcpd_initpacket(DHCPNAK);
memcpy(g_state.ds_outpacket.ciaddr, g_state.ds_inpacket.ciaddr, 4);
return dhcpd_sendpacket(sockfd, true);
}
* Name: dhcpd_sendack
****************************************************************************/
int dhcpd_sendack(int sockfd, in_addr_t ipaddr)
{
uint32_t leasetime = CONFIG_NETUTILS_DHCPD_LEASETIME;
in_addr_t netaddr;
#ifdef HAVE_DNSIP
uint32_t dnsaddr;
dnsaddr = htonl(g_dhcpd_config.ds_dnsip);
#endif
dhcpd_initpacket(DHCPACK);
memcpy(g_state.ds_outpacket.ciaddr, g_state.ds_inpacket.ciaddr, 4);
netaddr = htonl(ipaddr);
memcpy(g_state.ds_outpacket.yiaddr, &netaddr, 4);
dhcpd_verifyreqleasetime(&leasetime);
dhcpd_addoption32(DHCP_OPTION_LEASE_TIME, htonl(leasetime));
#ifdef HAVE_NETMASK
dhcpd_addoption32(DHCP_OPTION_SUBNET_MASK,
htonl(g_dhcpd_config.ds_netmask));
#endif
#ifdef HAVE_ROUTERIP
dhcpd_addoption32(DHCP_OPTION_ROUTER,
htonl(g_dhcpd_config.ds_routerip));
#endif
#ifdef HAVE_DNSIP
dhcp_addoption32p(DHCP_OPTION_DNS_SERVER, (FAR uint8_t *)&dnsaddr);
#endif
#ifdef CONFIG_NETUTILS_DHCPD_IGNOREBROADCAST
if (dhcpd_sendpacket(sockfd, true) < 0)
#else
if (dhcpd_sendpacket(sockfd, false) < 0)
#endif
{
return ERROR;
}
dhcpd_setlease(g_state.ds_inpacket.chaddr, ipaddr, leasetime);
return OK;
}
* Name: dhcpd_discover
****************************************************************************/
static inline int dhcpd_discover(int sockfd)
{
struct lease_s *lease;
in_addr_t ipaddr;
uint32_t leasetime = CONFIG_NETUTILS_DHCPD_LEASETIME;
lease = dhcpd_findbymac(g_state.ds_inpacket.chaddr);
if (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
ipaddr = dhcp_leaseipaddr(lease);
ninfo("Already have lease for IP %08lx\n", (long)ipaddr);
}
else if (dhcpd_verifyreqip())
{
ipaddr = g_state.ds_optreqip;
ninfo("User requested IP %08lx\n", (long)ipaddr);
}
else
{
ipaddr = dhcpd_allocipaddr();
ninfo("Allocated IP %08lx\n", (long)ipaddr);
}
if (!ipaddr)
{
nerr("ERROR: Failed to get IP address\n");
return ERROR;
}
if (!dhcpd_setlease(g_state.ds_inpacket.chaddr, ipaddr,
CONFIG_NETUTILS_DHCPD_OFFERTIME))
{
nerr("ERROR: Failed to set lease\n");
return ERROR;
}
dhcpd_verifyreqleasetime(&leasetime);
return dhcpd_sendoffer(sockfd, ipaddr, leasetime);
}
* Name: dhcpd_request
****************************************************************************/
static inline int dhcpd_request(int sockfd)
{
struct lease_s *lease;
in_addr_t ipaddr = 0;
uint8_t response = 0;
* 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)
{
* consistent* with the existing lease (host order).
*/
ipaddr = dhcp_leaseipaddr(lease);
ninfo("Lease ipaddr: %08" PRIx32 " Server IP: %08" PRIx32
" Requested IP: %08" PRIx32 "\n",
(uint32_t)ipaddr,
(uint32_t)g_state.ds_optserverip,
(uint32_t)g_state.ds_optreqip);
if (g_state.ds_optserverip)
{
* 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;
}
}
* address requested? (host order)
*/
else if (g_state.ds_optreqip)
{
* Both addresses are in host order.
*/
if (ipaddr == g_state.ds_optreqip)
{
response = DHCPACK;
}
else
{
response = DHCPNAK;
}
}
* IP address
*/
else
{
* to it. NOTE ipaddr is in host order; ciaddr is network order!
*/
uint32_t tmp = htonl(ipaddr);
if (memcmp(&tmp, g_state.ds_inpacket.ciaddr, 4) == 0)
{
response = DHCPACK;
}
else
{
response = DHCPNAK;
}
}
}
* 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)
{
ninfo("Server IP: %08" PRIx32 " Requested IP: %08" PRIx32 "\n",
(uint32_t)g_state.ds_optserverip,
(uint32_t)g_state.ds_optreqip);
lease = dhcpd_findbyipaddr(g_state.ds_optreqip);
if (lease)
{
if (!dhcpd_leaseexpired(lease))
{
response = DHCPNAK;
}
}
* The request IP address is in the range but has not been leased,
* maybe requested before the last shutdown, lease again.
*/
else if (g_state.ds_optreqip >= g_dhcpd_config.ds_startip &&
g_state.ds_optreqip <= g_dhcpd_config.ds_endip)
{
ipaddr = g_state.ds_optreqip;
response = DHCPACK;
}
else
{
response = DHCPNAK;
}
}
* specific IP address.
*/
* based on the checks above.
*/
if (response == DHCPACK)
{
ninfo("ACK IP %08lx\n", (long)ipaddr);
dhcpd_sendack(sockfd, ipaddr);
}
else if (response == DHCPNAK)
{
ninfo("NAK IP %08lx\n", (long)ipaddr);
dhcpd_sendnak(sockfd);
}
else
{
ninfo("Remaining silent IP %08lx\n", (long)ipaddr);
}
return OK;
}
* Name: dhcpd_decline
****************************************************************************/
static inline int dhcpd_decline(void)
{
struct lease_s *lease;
lease = dhcpd_findbymac(g_state.ds_inpacket.chaddr);
if (lease)
{
* 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;
lease = dhcpd_findbymac(g_state.ds_inpacket.chaddr);
if (lease)
{
memset(lease, 0, sizeof(struct lease_s));
}
return OK;
}
* Name: dhcpd_openlistener
****************************************************************************/
static inline int dhcpd_openlistener(FAR const char *interface)
{
struct sockaddr_in addr;
struct ifreq req;
int sockfd;
int ret;
sockfd = dhcpd_socket(interface);
if (sockfd < 0)
{
nerr("ERROR: socket failed: %d\n", errno);
return ERROR;
}
strlcpy(req.ifr_name, interface, IFNAMSIZ);
ret = ioctl(sockfd, SIOCGIFADDR, (unsigned long)&req);
if (ret < 0)
{
nerr("ERROR: setsockopt SIOCGIFADDR failed: %d\n", errno);
close(sockfd);
return ERROR;
}
g_state.ds_serverip = ((FAR struct sockaddr_in *)
&req.ifr_addr)->sin_addr.s_addr;
ninfo("serverip: %08" PRIx32 "\n", ntohl(g_state.ds_serverip));
* 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)
{
nerr("ERROR: 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_task_run
****************************************************************************/
#ifndef CONFIG_BUILD_KERNEL
static int dhcpd_task_run(int argc, char **argv)
{
return dhcpd_run(argv[1]);
}
#endif
* Name: dhcpd_get_pid
*
* Description:
* Use /proc filesystem to get dhcpd process PID
*
* Returned Value:
* PID of dhcpd process on success, -1 on failure
*
****************************************************************************/
#if !defined(CONFIG_BUILD_KERNEL) || defined(CONFIG_SCHED_WAITPID)
static pid_t dhcpd_get_pid(void)
{
pid_t pid = -1;
#if !defined(CONFIG_BUILD_KERNEL)
pid = g_dhcpd_daemon.ds_pid;
#elif defined(CONFIG_SYSTEM_POPEN)
FAR FILE *fp;
char line[32];
fp = popen(("pidof dhcpd"), "r");
if (fp == NULL)
{
return -errno;
}
if (fgets(line, sizeof(line), fp) != NULL)
{
pid = atoi(line);
}
pclose(fp);
#endif
return pid;
}
#endif
#ifndef CONFIG_DISABLE_SIGNALS
* Name: dhcpd_signal_handler
*
* Description:
* Signal handler for CONFIG_NETUTILS_DHCPD_SIGWAKEUP
* This function allows dhcpd_stop to terminate dhcpd daemon
*
* Input Parameters:
* signo - Signal number
* info - Signal information
* ctx - Signal context
*
****************************************************************************/
static void
dhcpd_signal_handler(int signo, FAR siginfo_t *info, FAR void *ctx)
{
g_dhcpd_daemon.ds_state = DHCPD_STOP_REQUESTED;
}
#endif
* Public Functions
****************************************************************************/
* Name: dhcpd_run
****************************************************************************/
int dhcpd_run(FAR const char *interface)
{
#ifndef CONFIG_DISABLE_SIGNALS
struct sigaction act;
#endif
int sockfd = -1;
int nbytes;
ninfo("Started\n");
if (g_dhcpd_daemon.ds_state == DHCPD_RUNNING)
{
return OK;
}
g_dhcpd_daemon.ds_data = malloc(sizeof(struct dhcpd_state_s));
if (g_dhcpd_daemon.ds_data == NULL)
{
return -ENOMEM;
}
memset(g_dhcpd_daemon.ds_data, 0, sizeof(struct dhcpd_state_s));
g_dhcpd_daemon.ds_pid = getpid();
#ifndef CONFIG_DISABLE_SIGNALS
memset(&act, 0, sizeof(act));
act.sa_sigaction = dhcpd_signal_handler;
act.sa_flags = SA_SIGINFO;
sigaction(CONFIG_NETUTILS_DHCPD_SIGWAKEUP, &act, NULL);
#endif
g_dhcpd_daemon.ds_state = DHCPD_RUNNING;
while (g_dhcpd_daemon.ds_state != DHCPD_STOP_REQUESTED)
{
if (sockfd < 0)
{
sockfd = dhcpd_openlistener(interface);
if (sockfd < 0)
{
nerr("ERROR: Failed to create socket\n");
break;
}
}
nbytes = recv(sockfd, &g_state.ds_inpacket,
sizeof(struct dhcpmsg_s), 0);
if (nbytes < 0)
{
nerr("ERROR: recv failed: %d\n", errno);
if (errno != EINTR && errno != EAGAIN)
{
close(sockfd);
sockfd = -1;
}
continue;
}
if (!dhcpd_parseoptions())
{
nerr("ERROR: No msg type\n");
continue;
}
#ifdef CONFIG_NETUTILS_DHCPD_HOST
usleep(500 * 1000);
#endif
switch (g_state.ds_optmsgtype)
{
case DHCPDISCOVER:
ninfo("DHCPDISCOVER\n");
dhcpd_discover(sockfd);
break;
case DHCPREQUEST:
ninfo("DHCPREQUEST\n");
dhcpd_request(sockfd);
break;
case DHCPDECLINE:
ninfo("DHCPDECLINE\n");
dhcpd_decline();
break;
case DHCPRELEASE:
ninfo("DHCPRELEASE\n");
dhcpd_release();
break;
case DHCPINFORM:
default:
nerr("ERROR: Unsupported message type: %d\n",
g_state.ds_optmsgtype);
break;
}
}
free(g_dhcpd_daemon.ds_data);
g_dhcpd_daemon.ds_data = NULL;
g_dhcpd_daemon.ds_pid = -1;
g_dhcpd_daemon.ds_state = DHCPD_STOPPED;
return OK;
}
* Name: dhcpd_start
*
* Description:
* Start the DHCPD daemon
*
* Returned Value:
* On success, the non-negative task ID of the DHCPDC daemon is returned;
* On failure, a negated errno value is returned.
*
****************************************************************************/
#ifndef CONFIG_BUILD_KERNEL
int dhcpd_start(FAR const char *interface)
{
FAR char *argv[2];
int pid;
argv[0] = (char *)interface;
argv[1] = NULL;
if (dhcpd_get_pid() < 0)
{
pid = task_create("dhcpd", CONFIG_NETUTILS_DHCPD_PRIORITY,
CONFIG_NETUTILS_DHCPD_STACKSIZE, dhcpd_task_run,
argv);
if (pid < 0)
{
int errval = errno;
nerr("ERROR: Failed to start the DHCPD daemon: %d\n", errval);
return -errval;
}
}
return OK;
}
#endif
* Name: dhcpd_stop
*
* Description:
* Stop the DHCPD daemon
*
* Returned Value:
* Zero on success; a negated errno value on failure. The current
* implementation only returns success.
*
****************************************************************************/
#ifdef CONFIG_SCHED_WAITPID
int dhcpd_stop(void)
{
pid_t pid;
pid = dhcpd_get_pid();
if (pid >= 0)
{
if (kill(pid, CONFIG_NETUTILS_DHCPD_SIGWAKEUP) < 0)
{
nerr("ERROR: kill pid %d failed: %d\n", pid, errno);
return -errno;
}
if (waitpid(pid, NULL, 0) < 0)
{
nerr("ERROR: waitpid failed: %d\n", errno);
return -errno;
}
}
return OK;
}
#endif
* Name: dhcpd_set_startip
*
* Description:
* Set start IP for DHCPD
*
* Returned Value:
* OK
*
****************************************************************************/
int dhcpd_set_startip(in_addr_t startip)
{
g_dhcpd_config.ds_startip = startip;
g_dhcpd_config.ds_endip = startip + CONFIG_NETUTILS_DHCPD_MAXLEASES - 1;
return OK;
}
#ifdef HAVE_ROUTERIP
* Name: dhcpd_set_routerip
*
* Description:
* Set Router IP for DHCPD
*
* Returned Value:
* OK
*
****************************************************************************/
int dhcpd_set_routerip(in_addr_t routerip)
{
g_dhcpd_config.ds_routerip = routerip;
return OK;
}
#endif
#ifdef HAVE_NETMASK
* Name: dhcpd_set_netmask
*
* Description:
* Set Netmask for DHCPD
*
* Returned Value:
* OK
*
****************************************************************************/
int dhcpd_set_netmask(in_addr_t netmask)
{
g_dhcpd_config.ds_netmask = netmask;
return OK;
}
#endif
#ifdef HAVE_DNSIP
* Name: dhcpd_set_dnsip
*
* Description:
* Set DNS for DHCPD
*
* Returned Value:
* OK
*
****************************************************************************/
int dhcpd_set_dnsip(in_addr_t dnsip)
{
g_dhcpd_config.ds_dnsip = dnsip;
return OK;
}
#endif