/**************************************************************************** * 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 # 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 configuration */ # include /* For nerr, info */ # include /* For CONFIG_CPP_HAVE_WARNING */ # include "netutils/dhcpd.h" /* Advertised DHCPD APIs */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "netutils/netlib.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 0 /* 1 Pad */ #define DHCP_OPTION_SUBNET_MASK 1 /* 1 Subnet Mask */ #define DHCP_OPTION_ROUTER 3 /* 4 Router */ #define DHCP_OPTION_DNS_SERVER 6 /* 4N DNS */ #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_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_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 ****************************************************************************/ /* 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]; }; /* This enumeration describes the state of the DHCPD daemon */ enum dhcpd_daemon_e { DHCPD_NOT_RUNNING = 0, DHCPD_STARTED, DHCPD_RUNNING, DHCPD_STOP_REQUESTED, DHCPD_STOPPED }; 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]; }; /* This type describes the state of the DHCPD client daemon. Only one * instance of the DHCPD daemon is permitted in this implementation. */ struct dhcpd_daemon_s { uint8_t ds_state; /* See enum dhcpd_daemon_e */ sem_t ds_lock; /* Used to protect the whole structure */ sem_t ds_sync; /* Used to synchronize start and stop events */ pid_t ds_pid; /* Task ID of the DHCPD daemon */ FAR struct dhcpd_state_s *ds_data; /* DHCPD daemon 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 }; /* This type describes the state of the DHCPD client daemon. Only one * 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, SEM_INITIALIZER(1), SEM_INITIALIZER(0), -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; /* Put the protocol address in a standard form. ipaddr is assumed to be in * 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)); /* Update the ARP table */ 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) { /* Calculate the offset from the first IP address managed by DHCPD. * 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); /* Verify that the address offset is within the supported range */ 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 IP address in host order */ 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++) { /* Skip over address ending in 0 or 255 */ if ((ipaddr & 0xff) == 0 || (ipaddr & 0xff) == 0xff) { continue; } /* Is there already a lease on this address? If so, has it expired? */ 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 the address in host order */ 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; /* 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 */ nerr("ERROR: 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; #ifndef CONFIG_NET_DHCP_LIGHT overloaded = DHCPD_OPTION_FIELD; currfield = DHCPD_OPTION_FIELD; #endif /* 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 >= 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: /* Requested IP Address */ 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: /* IP address 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: /* DHCP message 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: /* Server identifier */ 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: /* 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 >= g_dhcpd_config.ds_startip && g_state.ds_optreqip <= g_dhcpd_config.ds_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_optend - g_state.ds_outpacket.options; 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: dhcp_addoption32p ****************************************************************************/ #ifdef HAVE_DNSIP static int dhcp_addoption32p(uint8_t code, FAR uint8_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); } #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 /* Create a socket to listen for requests from DHCP clients */ sockfd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (sockfd < 0) { nerr("ERROR: socket failed: %d\n", errno); return ERROR; } /* Configure the socket */ #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 /* Bind socket to interface, because UDP packets have to be sent to the * 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; /* 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; } /* 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 sockfd, int bbroadcast) { struct sockaddr_in addr; in_addr_t ipaddr; int len; #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 const uint8_t anyipaddr[4] = { }; /* 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 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 /* Create a socket to respond with a packet to the client. We * cannot re-use the listener socket because it is not bound correctly */ /* Then send the response 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 - (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 /* IP address is in host order */ ninfo("Sending offer: %08lx\n", (long)ipaddr); /* Initialize the outgoing packet */ dhcpd_initpacket(DHCPOFFER); /* Add the address offered to the client (converting to network order) */ netaddr = htonl(ipaddr); memcpy(g_state.ds_outpacket.yiaddr, &netaddr, 4); /* Add the leasetime to the response options */ 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 /* Send the offer response */ #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) { /* Initialize and send the NAK response */ 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 /* 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 */ netaddr = htonl(ipaddr); memcpy(g_state.ds_outpacket.yiaddr, &netaddr, 4); /* Did the client request a specific lease time? */ dhcpd_verifyreqleasetime(&leasetime); /* Add the lease time to the response */ 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; /* Check if the client is already 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 (host order) */ ipaddr = dhcp_leaseipaddr(lease); ninfo("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 (host order) */ ipaddr = g_state.ds_optreqip; ninfo("User requested IP %08lx\n", (long)ipaddr); } else { /* No... allocate a new IP address (host order) */ ipaddr = dhcpd_allocipaddr(); ninfo("Allocated IP %08lx\n", (long)ipaddr); } /* Did we get any IP address? */ if (!ipaddr) { /* Nope... return failure */ nerr("ERROR: 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)) { nerr("ERROR: Failed to set lease\n"); return ERROR; } /* Check if the client has requested a specific lease time */ dhcpd_verifyreqleasetime(&leasetime); /* Send the offer response */ 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; /* 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 (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) { /* 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 a specific IP * address requested? (host order) */ else if (g_state.ds_optreqip) { /* Yes..ACK if the requested IP address is the one already leased. * Both addresses are in host order. */ 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. 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; } } } /* 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) { ninfo("Server IP: %08" PRIx32 " Requested IP: %08" PRIx32 "\n", (uint32_t)g_state.ds_optserverip, (uint32_t)g_state.ds_optreqip); /* 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; } } /* DHCPREQUEST without DHCPDISCOVER: * 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; } /* The requested IP address out of range, negative */ else { response = DHCPNAK; } } /* Otherwise, the client does not hold a lease and is not requesting any * specific IP address. */ /* Finally, either (1) send the ACK, (2) send a NAK, or (3) remain silent * 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; /* 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(FAR const char *interface) { struct sockaddr_in addr; struct ifreq req; int sockfd; int ret; /* Create a socket to listen for requests from DHCP clients */ sockfd = dhcpd_socket(interface); if (sockfd < 0) { nerr("ERROR: socket failed: %d\n", errno); return ERROR; } /* Get the IP address of the selected device */ 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)); /* 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) { 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 ****************************************************************************/ static int dhcpd_task_run(int argc, char **argv) { return dhcpd_run(argv[1]); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: dhcpd_run ****************************************************************************/ int dhcpd_run(FAR const char *interface) { int sockfd; int nbytes; ninfo("Started\n"); /* Exit if the dhcpd is already executed */ if (g_dhcpd_daemon.ds_state == DHCPD_RUNNING) { return OK; } /* Initialize everything to zero */ 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)); /* Update the pid if running in daemon mode */ g_dhcpd_daemon.ds_pid = getpid(); /* Indicate that we have started */ g_dhcpd_daemon.ds_state = DHCPD_RUNNING; sem_post(&g_dhcpd_daemon.ds_sync); /* Now loop indefinitely, reading packets from the DHCP server socket */ sockfd = -1; while (g_dhcpd_daemon.ds_state != DHCPD_STOP_REQUESTED) { /* Create a socket to listen for requests from DHCP clients */ if (sockfd < 0) { sockfd = dhcpd_openlistener(interface); if (sockfd < 0) { nerr("ERROR: 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 */ nerr("ERROR: recv failed: %d\n", errno); if (errno != EINTR && errno != EAGAIN) { close(sockfd); sockfd = -1; } continue; } /* Parse the incoming message options */ if (!dhcpd_parseoptions()) { /* Failed to parse the message options */ nerr("ERROR: 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: 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: /* Not supported */ 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; sem_post(&g_dhcpd_daemon.ds_sync); 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. * ****************************************************************************/ int dhcpd_start(FAR const char *interface) { FAR char *argv[2]; int pid; argv[0] = (char *)interface; argv[1] = NULL; /* Is the DHCPD in a non-running state? */ sem_wait(&g_dhcpd_daemon.ds_lock); if (g_dhcpd_daemon.ds_state == DHCPD_NOT_RUNNING || g_dhcpd_daemon.ds_state == DHCPD_STOPPED) { /* Start the DHCPD daemon */ g_dhcpd_daemon.ds_state = DHCPD_STARTED; pid = task_create("DHCPD daemon", CONFIG_NETUTILS_DHCPD_PRIORITY, CONFIG_NETUTILS_DHCPD_STACKSIZE, dhcpd_task_run, argv); /* Handle failures to start the DHCPD daemon */ if (pid < 0) { int errval = errno; g_dhcpd_daemon.ds_state = DHCPD_STOPPED; nerr("ERROR: Failed to start the DHCPD daemon: %d\n", errval); sem_post(&g_dhcpd_daemon.ds_lock); return -errval; } /* Wait for any daemon state change */ do { sem_wait(&g_dhcpd_daemon.ds_sync); } while (g_dhcpd_daemon.ds_state == DHCPD_STARTED); } sem_post(&g_dhcpd_daemon.ds_lock); return OK; } /**************************************************************************** * 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. * ****************************************************************************/ int dhcpd_stop(void) { int ret; /* Is the DHCPD in a running state? */ sem_wait(&g_dhcpd_daemon.ds_lock); if (g_dhcpd_daemon.ds_state == DHCPD_STARTED || g_dhcpd_daemon.ds_state == DHCPD_RUNNING) { /* Yes.. request that the daemon stop. */ g_dhcpd_daemon.ds_state = DHCPD_STOP_REQUESTED; /* Wait for any daemon state change */ do { /* Signal the DHCPD client */ ret = kill(g_dhcpd_daemon.ds_pid, CONFIG_NETUTILS_DHCPD_SIGWAKEUP); if (ret < 0) { nerr("ERROR: kill pid %d failed: %d\n", g_dhcpd_daemon.ds_pid, errno); break; } /* Wait for the DHCPD client to respond to the stop request */ sem_wait(&g_dhcpd_daemon.ds_sync); } while (g_dhcpd_daemon.ds_state == DHCPD_STOP_REQUESTED); } sem_post(&g_dhcpd_daemon.ds_lock); return OK; } /**************************************************************************** * 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