| /**************************************************************************** |
| * apps/netutils/dhcpc/dhcpc.c |
| * |
| * Copyright (C) 2007, 2009, 2011-2012 Gregory Nutt. All rights reserved. |
| * Author: Gregory Nutt <gnutt@nuttx.org> |
| * |
| * Based heavily on portions of uIP: |
| * |
| * Author: Adam Dunkels <adam@dunkels.com> |
| * Copyright (c) 2005, Swedish Institute of Computer Science |
| * All rights reserved. |
| * |
| * 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 of the Institute 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 INSTITUTE 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 INSTITUTE 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 |
| ****************************************************************************/ |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <sys/random.h> |
| |
| #include <inttypes.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <debug.h> |
| #include <pthread.h> |
| |
| #include <arpa/inet.h> |
| #include <netinet/udp.h> |
| #include <nuttx/net/ip.h> |
| |
| #include "netutils/dhcpc.h" |
| #include "netutils/netlib.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Configuration */ |
| |
| /* DHCP Definitions */ |
| |
| #define STATE_INITIAL 0 |
| #define STATE_HAVE_OFFER 1 |
| #define STATE_HAVE_LEASE 2 |
| |
| #define DHCP_REQUEST 1 |
| #define DHCP_REPLY 2 |
| #define DHCP_HTYPE_ETHERNET 1 |
| #define DHCP_HLEN_ETHERNET 6 |
| #define DHCP_MSG_LEN 236 |
| |
| #define DHCPC_SERVER_PORT 67 |
| #define DHCPC_CLIENT_PORT 68 |
| |
| #define DHCPDISCOVER 1 |
| #define DHCPOFFER 2 |
| #define DHCPREQUEST 3 |
| #define DHCPDECLINE 4 |
| #define DHCPACK 5 |
| #define DHCPNAK 6 |
| #define DHCPRELEASE 7 |
| |
| #define DHCP_OPTION_SUBNET_MASK 1 |
| #define DHCP_OPTION_ROUTER 3 |
| #define DHCP_OPTION_DNS_SERVER 6 |
| #define DHCP_OPTION_HOST_NAME 12 |
| #define DHCP_OPTION_REQ_IPADDR 50 |
| #define DHCP_OPTION_LEASE_TIME 51 |
| #define DHCP_OPTION_MSG_TYPE 53 |
| #define DHCP_OPTION_SERVER_ID 54 |
| #define DHCP_OPTION_REQ_LIST 55 |
| #define DHCP_OPTION_CLIENT_ID 61 |
| #define DHCP_OPTION_END 255 |
| |
| #define BUFFER_SIZE 256 |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct dhcp_msg |
| { |
| 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 dhcpc_state_s |
| { |
| FAR const char *interface; |
| int sockfd; |
| uint8_t xid[4]; |
| struct in_addr ipaddr; |
| struct in_addr serverid; |
| struct dhcp_msg packet; |
| bool cancel; |
| pthread_t thread; /* Thread ID of the DHCPC thread */ |
| dhcpc_callback_t callback; /* Thread callback of the DHCPC thread */ |
| int maclen; |
| uint8_t macaddr[1]; |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static const uint8_t magic_cookie[4] = |
| { |
| 99, 130, 83, 99 |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: dhcpc_add<option> |
| ****************************************************************************/ |
| |
| static FAR uint8_t *dhcpc_addhostname(FAR const char *hostname, |
| FAR uint8_t *optptr) |
| { |
| int len = strlen(hostname); |
| *optptr++ = DHCP_OPTION_HOST_NAME; |
| *optptr++ = len; |
| memcpy(optptr, hostname, len); |
| return optptr + len; |
| } |
| |
| static FAR uint8_t *dhcpc_addmsgtype(FAR uint8_t *optptr, uint8_t type) |
| { |
| *optptr++ = DHCP_OPTION_MSG_TYPE; |
| *optptr++ = 1; |
| *optptr++ = type; |
| return optptr; |
| } |
| |
| static FAR uint8_t *dhcpc_addserverid(FAR struct in_addr *serverid, |
| FAR uint8_t *optptr) |
| { |
| *optptr++ = DHCP_OPTION_SERVER_ID; |
| *optptr++ = 4; |
| memcpy(optptr, &serverid->s_addr, 4); |
| return optptr + 4; |
| } |
| |
| static FAR uint8_t *dhcpc_addreqipaddr(FAR struct in_addr *ipaddr, |
| FAR uint8_t *optptr) |
| { |
| *optptr++ = DHCP_OPTION_REQ_IPADDR; |
| *optptr++ = 4; |
| memcpy(optptr, &ipaddr->s_addr, 4); |
| return optptr + 4; |
| } |
| |
| static FAR uint8_t *dhcpc_addclientid(FAR uint8_t *clientid, |
| FAR uint8_t len, |
| FAR uint8_t *optptr) |
| { |
| *optptr++ = DHCP_OPTION_CLIENT_ID; |
| *optptr++ = 1 + len; |
| *optptr++ = 0x1; |
| memcpy(optptr, clientid, len); |
| return optptr + len; |
| } |
| |
| static FAR uint8_t *dhcpc_addreqoptions(FAR uint8_t *optptr) |
| { |
| *optptr++ = DHCP_OPTION_REQ_LIST; |
| *optptr++ = 3; |
| *optptr++ = DHCP_OPTION_SUBNET_MASK; |
| *optptr++ = DHCP_OPTION_ROUTER; |
| *optptr++ = DHCP_OPTION_DNS_SERVER; |
| return optptr; |
| } |
| |
| static FAR uint8_t *dhcpc_addend(FAR uint8_t *optptr) |
| { |
| *optptr++ = DHCP_OPTION_END; |
| return optptr; |
| } |
| |
| /**************************************************************************** |
| * Name: dhcpc_sendmsg |
| ****************************************************************************/ |
| |
| static int dhcpc_sendmsg(FAR struct dhcpc_state_s *pdhcpc, |
| FAR struct dhcpc_state *presult, int msgtype) |
| { |
| char hostname[HOST_NAME_MAX + 1]; |
| struct sockaddr_in addr; |
| FAR uint8_t *pend; |
| in_addr_t serverid = INADDR_BROADCAST; |
| int len; |
| |
| /* Create the common message header settings */ |
| |
| memset(&pdhcpc->packet, 0, sizeof(struct dhcp_msg)); |
| pdhcpc->packet.op = DHCP_REQUEST; |
| pdhcpc->packet.htype = DHCP_HTYPE_ETHERNET; |
| pdhcpc->packet.hlen = pdhcpc->maclen; |
| memcpy(pdhcpc->packet.xid, pdhcpc->xid, 4); |
| memcpy(pdhcpc->packet.chaddr, pdhcpc->macaddr, pdhcpc->maclen); |
| memset(&pdhcpc->packet.chaddr[pdhcpc->maclen], 0, 16 - pdhcpc->maclen); |
| memcpy(pdhcpc->packet.options, magic_cookie, sizeof(magic_cookie)); |
| |
| /* Add the common header options */ |
| |
| pend = &pdhcpc->packet.options[4]; |
| pend = dhcpc_addmsgtype(pend, msgtype); |
| |
| /* Get the current host name */ |
| |
| if (gethostname(hostname, sizeof(hostname)) || (0 == strlen(hostname))) |
| { |
| strlcpy(hostname, CONFIG_NETUTILS_DHCPC_HOST_NAME, sizeof(hostname)); |
| } |
| |
| /* Handle the message specific settings */ |
| |
| switch (msgtype) |
| { |
| /* Broadcast DISCOVER message to all servers */ |
| |
| case DHCPDISCOVER: |
| /* Socket binded to INADDR_ANY is not intended to receive unicast |
| * traffic before being fully configured, at least dhclient |
| * configured with socket-only won't do so on Linux and BSDs. |
| * We can sometimes receive unicast traffic before being fully |
| * configured, it's good, but not always, so we need to set the |
| * broadcast flag under some situations. |
| */ |
| |
| /* Broadcast bit. */ |
| |
| pdhcpc->packet.flags = HTONS(CONFIG_NETUTILS_DHCPC_BOOTP_FLAGS); |
| |
| pend = dhcpc_addhostname(hostname, pend); |
| pend = dhcpc_addreqoptions(pend); |
| pend = dhcpc_addclientid(pdhcpc->macaddr, pdhcpc->maclen, pend); |
| break; |
| |
| /* Send REQUEST message to the server that sent the *first* OFFER */ |
| |
| case DHCPREQUEST: |
| |
| /* Broadcast bit. */ |
| |
| pdhcpc->packet.flags = HTONS(CONFIG_NETUTILS_DHCPC_BOOTP_FLAGS); |
| |
| pend = dhcpc_addhostname(hostname, pend); |
| pend = dhcpc_addserverid(&pdhcpc->serverid, pend); |
| pend = dhcpc_addreqipaddr(&pdhcpc->ipaddr, pend); |
| pend = dhcpc_addclientid(pdhcpc->macaddr, pdhcpc->maclen, pend); |
| break; |
| |
| /* Send DECLINE message to the server that sent the *last* OFFER */ |
| |
| case DHCPDECLINE: |
| memcpy(pdhcpc->packet.ciaddr, &presult->ipaddr.s_addr, 4); |
| pend = dhcpc_addserverid(&presult->serverid, pend); |
| serverid = presult->serverid.s_addr; |
| break; |
| |
| default: |
| errno = EINVAL; |
| return ERROR; |
| } |
| |
| pend = dhcpc_addend(pend); |
| len = pend - (uint8_t *)&pdhcpc->packet; |
| |
| /* Send the request */ |
| |
| addr.sin_family = AF_INET; |
| addr.sin_port = HTONS(DHCPC_SERVER_PORT); |
| addr.sin_addr.s_addr = serverid; |
| |
| return sendto(pdhcpc->sockfd, &pdhcpc->packet, len, 0, |
| (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); |
| } |
| |
| /**************************************************************************** |
| * Name: dhcpc_parseoptions |
| ****************************************************************************/ |
| |
| static uint8_t dhcpc_parseoptions(FAR struct dhcpc_state *presult, |
| FAR uint8_t *optptr, int len) |
| { |
| FAR uint8_t *end = optptr + len; |
| uint8_t type = 0; |
| |
| while (optptr < end) |
| { |
| switch (*optptr) |
| { |
| case DHCP_OPTION_SUBNET_MASK: |
| |
| /* Get subnet mask in network order */ |
| |
| if (optptr + 6 <= end) |
| { |
| memcpy(&presult->netmask.s_addr, optptr + 2, 4); |
| } |
| else |
| { |
| nerr("Packet too short (netmask missing)\n"); |
| } |
| break; |
| |
| case DHCP_OPTION_ROUTER: |
| |
| /* Get the default router address in network order */ |
| |
| if (optptr + 6 <= end) |
| { |
| memcpy(&presult->default_router.s_addr, optptr + 2, 4); |
| } |
| else |
| { |
| nerr("Packet too short (router address missing)\n"); |
| } |
| break; |
| |
| case DHCP_OPTION_DNS_SERVER: |
| |
| /* Get the DNS server address in network order */ |
| |
| if (optptr + 6 <= end) |
| { |
| memcpy(&presult->dnsaddr.s_addr, optptr + 2, 4); |
| } |
| else |
| { |
| nerr("Packet too short (DNS address missing)\n"); |
| } |
| break; |
| |
| case DHCP_OPTION_MSG_TYPE: |
| |
| /* Get message type */ |
| |
| if (optptr + 3 <= end) |
| { |
| type = *(optptr + 2); |
| } |
| else |
| { |
| nerr("Packet too short (type missing)\n"); |
| } |
| break; |
| |
| case DHCP_OPTION_SERVER_ID: |
| |
| /* Get server address in network order */ |
| |
| if (optptr + 6 <= end) |
| { |
| memcpy(&presult->serverid.s_addr, optptr + 2, 4); |
| } |
| else |
| { |
| nerr("Packet too short (server address missing)\n"); |
| } |
| break; |
| |
| case DHCP_OPTION_LEASE_TIME: |
| |
| /* Get lease time (in seconds) in host order */ |
| |
| if (optptr + 6 <= end) |
| { |
| uint16_t tmp[2]; |
| memcpy(tmp, optptr + 2, 4); |
| presult->lease_time = ((uint32_t)ntohs(tmp[0])) << 16 | |
| (uint32_t)ntohs(tmp[1]); |
| } |
| else |
| { |
| nerr("Packet too short (lease time missing)\n"); |
| } |
| break; |
| |
| case DHCP_OPTION_END: |
| return type; |
| } |
| |
| if (optptr + 1 >= end) |
| { |
| break; |
| } |
| |
| optptr += optptr[1] + 2; |
| } |
| |
| return type; |
| } |
| |
| /**************************************************************************** |
| * Name: dhcpc_parsemsg |
| ****************************************************************************/ |
| |
| static uint8_t dhcpc_parsemsg(FAR struct dhcpc_state_s *pdhcpc, int buflen, |
| FAR struct dhcpc_state *presult) |
| { |
| if (buflen >= 44 && pdhcpc->packet.op == DHCP_REPLY && |
| memcmp(pdhcpc->packet.xid, pdhcpc->xid, 4) == 0 && |
| memcmp(pdhcpc->packet.chaddr, |
| pdhcpc->macaddr, pdhcpc->maclen) == 0) |
| { |
| memcpy(&presult->ipaddr.s_addr, pdhcpc->packet.yiaddr, 4); |
| return dhcpc_parseoptions(presult, &pdhcpc->packet.options[4], |
| buflen - |
| (offsetof(struct dhcp_msg, options) + 4)); |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: dhcpc_run |
| ****************************************************************************/ |
| |
| static void *dhcpc_run(void *args) |
| { |
| FAR struct dhcpc_state_s *pdhcpc = (FAR struct dhcpc_state_s *)args; |
| struct dhcpc_state result; |
| int ret; |
| |
| while (1) |
| { |
| ret = dhcpc_request(pdhcpc, &result); |
| if (ret == OK) |
| { |
| pdhcpc->callback(&result); |
| } |
| else |
| { |
| pdhcpc->callback(NULL); |
| memset(&result, 0, sizeof(result)); |
| nerr("dhcpc_request error\n"); |
| } |
| |
| if (pdhcpc->cancel) |
| { |
| return NULL; |
| } |
| |
| result.lease_time /= 2; |
| while (result.lease_time) |
| { |
| result.lease_time = sleep(result.lease_time); |
| if (pdhcpc->cancel) |
| { |
| return NULL; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: dhcpc_open |
| ****************************************************************************/ |
| |
| FAR void *dhcpc_open(FAR const char *interface, FAR const void *macaddr, |
| int maclen) |
| { |
| FAR struct dhcpc_state_s *pdhcpc; |
| struct sockaddr_in addr; |
| struct timeval tv; |
| int ret; |
| const uint8_t default_xid[4] = |
| { |
| 0xad, 0xde, 0x12, 0x23 |
| }; |
| |
| ninfo("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", |
| ((uint8_t *)macaddr)[0], ((uint8_t *)macaddr)[1], |
| ((uint8_t *)macaddr)[2], ((uint8_t *)macaddr)[3], |
| ((uint8_t *)macaddr)[4], ((uint8_t *)macaddr)[5]); |
| |
| /* Allocate an internal DHCP structure */ |
| |
| pdhcpc = malloc(sizeof(struct dhcpc_state_s) + maclen - 1); |
| if (pdhcpc) |
| { |
| /* Initialize the allocated structure */ |
| |
| memset(pdhcpc, 0, sizeof(struct dhcpc_state_s)); |
| |
| /* RFC2131: A DHCP client MUST choose 'xid's in such a |
| * way as to minimize the chance of using an 'xid' identical to one |
| * used by another client. |
| */ |
| |
| #if defined(CONFIG_DEV_URANDOM) || defined(CONFIG_DEV_RANDOM) |
| ret = getrandom(pdhcpc->xid, 4, 0); |
| if (ret != 4) |
| { |
| ret = getrandom(pdhcpc->xid, 4, GRND_RANDOM); |
| if (ret != 4) |
| { |
| memcpy(pdhcpc->xid, default_xid, 4); |
| } |
| } |
| #else |
| memcpy(pdhcpc->xid, default_xid, 4); |
| #endif |
| |
| pdhcpc->interface = interface; |
| pdhcpc->maclen = maclen; |
| memcpy(pdhcpc->macaddr, macaddr, pdhcpc->maclen); |
| |
| /* Create a UDP socket */ |
| |
| pdhcpc->sockfd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); |
| if (pdhcpc->sockfd < 0) |
| { |
| ninfo("socket handle %d\n", pdhcpc->sockfd); |
| free(pdhcpc); |
| return NULL; |
| } |
| |
| /* Bind the socket */ |
| |
| addr.sin_family = AF_INET; |
| addr.sin_port = HTONS(DHCPC_CLIENT_PORT); |
| addr.sin_addr.s_addr = INADDR_ANY; |
| |
| ret = bind(pdhcpc->sockfd, (struct sockaddr *)&addr, |
| sizeof(struct sockaddr_in)); |
| if (ret < 0) |
| { |
| ninfo("bind status %d\n", ret); |
| close(pdhcpc->sockfd); |
| free(pdhcpc); |
| return NULL; |
| } |
| |
| /* Configure for read timeouts */ |
| |
| tv.tv_sec = CONFIG_NETUTILS_DHCPC_RECV_TIMEOUT_MS / 1000; |
| tv.tv_usec = (CONFIG_NETUTILS_DHCPC_RECV_TIMEOUT_MS % 1000) * 1000; |
| |
| ret = setsockopt(pdhcpc->sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, |
| sizeof(struct timeval)); |
| if (ret < 0) |
| { |
| ninfo("setsockopt(RCVTIMEO) status %d\n", ret); |
| close(pdhcpc->sockfd); |
| free(pdhcpc); |
| return NULL; |
| } |
| |
| #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). |
| */ |
| |
| ret = setsockopt(pdhcpc->sockfd, SOL_SOCKET, SO_BINDTODEVICE, |
| pdhcpc->interface, strlen(pdhcpc->interface)); |
| if (ret < 0) |
| { |
| ninfo("setsockopt(BINDTODEVICE) status %d\n", ret); |
| close(pdhcpc->sockfd); |
| free(pdhcpc); |
| return NULL; |
| } |
| #endif |
| } |
| |
| return (FAR void *)pdhcpc; |
| } |
| |
| /**************************************************************************** |
| * Name: dhcpc_close |
| ****************************************************************************/ |
| |
| void dhcpc_close(FAR void *handle) |
| { |
| struct dhcpc_state_s *pdhcpc = (struct dhcpc_state_s *)handle; |
| |
| if (pdhcpc) |
| { |
| if (pdhcpc->thread) |
| { |
| dhcpc_cancel(pdhcpc); |
| } |
| |
| if (pdhcpc->sockfd) |
| { |
| close(pdhcpc->sockfd); |
| } |
| |
| free(pdhcpc); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: dhcpc_cancel |
| ****************************************************************************/ |
| |
| void dhcpc_cancel(FAR void *handle) |
| { |
| struct dhcpc_state_s *pdhcpc = (struct dhcpc_state_s *)handle; |
| sighandler_t old; |
| int ret; |
| |
| if (pdhcpc) |
| { |
| pdhcpc->cancel = true; |
| |
| if (pdhcpc->thread) |
| { |
| old = signal(SIGQUIT, SIG_IGN); |
| |
| /* Signal the dhcpc_run */ |
| |
| ret = pthread_kill(pdhcpc->thread, SIGQUIT); |
| if (ret != 0) |
| { |
| nerr("ERROR: pthread_kill DHCPC thread\n"); |
| } |
| |
| /* Wait for the end of dhcpc_run */ |
| |
| ret = pthread_join(pdhcpc->thread, NULL); |
| if (ret != 0) |
| { |
| nerr("ERROR: pthread_join DHCPC thread\n"); |
| } |
| |
| pdhcpc->thread = 0; |
| signal(SIGQUIT, old); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: dhcpc_request |
| ****************************************************************************/ |
| |
| int dhcpc_request(FAR void *handle, FAR struct dhcpc_state *presult) |
| { |
| FAR struct dhcpc_state_s *pdhcpc = (FAR struct dhcpc_state_s *)handle; |
| struct in_addr oldaddr; |
| ssize_t result; |
| uint8_t msgtype; |
| int retries; |
| int state; |
| clock_t start; |
| |
| memset(presult, 0, sizeof(*presult)); |
| |
| /* RFC2131: For example, a client may choose a different, |
| * random initial 'xid' each time the client is rebooted, and |
| * subsequently use sequential 'xid's until the next reboot. |
| */ |
| |
| pdhcpc->xid[3]++; |
| |
| /* Save the currently assigned IP address. It should be INADDR_ANY |
| * if this is the initial request, or a valid IP if this is a renewal. |
| */ |
| |
| oldaddr.s_addr = 0; |
| netlib_get_ipv4addr(pdhcpc->interface, &oldaddr); |
| |
| /* Loop sending the DISCOVER up to CONFIG_NETUTILS_DHCPC_RETRIES |
| * times |
| */ |
| |
| retries = 0; |
| |
| /* Loop sending DISCOVER until we receive an OFFER from a DHCP |
| * server. We will lock on to the first OFFER and decline any |
| * subsequent offers (which will happen if there are more than one |
| * DHCP servers on the network. |
| */ |
| |
| state = STATE_INITIAL; |
| do |
| { |
| if (pdhcpc->cancel) |
| { |
| errno = EINTR; |
| return ERROR; |
| } |
| |
| /* Send the DISCOVER command */ |
| |
| ninfo("Broadcast DISCOVER\n"); |
| if (dhcpc_sendmsg(pdhcpc, presult, DHCPDISCOVER) < 0) |
| { |
| return ERROR; |
| } |
| |
| retries++; |
| |
| /* Get the DHCPOFFER response */ |
| |
| start = clock(); |
| do |
| { |
| result = recv(pdhcpc->sockfd, &pdhcpc->packet, |
| sizeof(struct dhcp_msg), 0); |
| if (result >= 0) |
| { |
| msgtype = dhcpc_parsemsg(pdhcpc, result, presult); |
| if (msgtype == DHCPOFFER) |
| { |
| /* Save the servid from the presult so that it is not |
| * clobbered by a new OFFER. |
| */ |
| |
| ninfo("Received OFFER from %08" PRIx32 "\n", |
| (uint32_t)ntohl(presult->serverid.s_addr)); |
| pdhcpc->ipaddr.s_addr = presult->ipaddr.s_addr; |
| pdhcpc->serverid.s_addr = presult->serverid.s_addr; |
| |
| /* Temporarily use the address offered by the server |
| * and break out of the loop. |
| */ |
| |
| netlib_set_ipv4addr(pdhcpc->interface, |
| &presult->ipaddr); |
| state = STATE_HAVE_OFFER; |
| } |
| } |
| |
| /* An error has occurred. If this was a timeout error (meaning |
| * that nothing was received on this socket for a long period |
| * of time). Then loop and send the DISCOVER command again. |
| */ |
| |
| else if (errno != EAGAIN && errno != EINTR) |
| { |
| /* An error other than a timeout was received -- error out */ |
| |
| return ERROR; |
| } |
| } |
| while (state == STATE_INITIAL && TICK2MSEC(clock() - start) < |
| CONFIG_NETUTILS_DHCPC_RECV_TIMEOUT_MS); |
| } |
| while (state == STATE_INITIAL && |
| retries < CONFIG_NETUTILS_DHCPC_RETRIES); |
| |
| /* If no DHCPOFFER received here, error out */ |
| |
| if (state == STATE_INITIAL) |
| { |
| return ERROR; |
| } |
| |
| /* Loop sending the REQUEST up to CONFIG_NETUTILS_DHCPC_RETRIES times |
| * (if there is no response) |
| */ |
| |
| retries = 0; |
| do |
| { |
| if (pdhcpc->cancel) |
| { |
| errno = EINTR; |
| return ERROR; |
| } |
| |
| /* Send the REQUEST message to obtain the lease that was offered to |
| * us. |
| */ |
| |
| ninfo("Send REQUEST\n"); |
| if (dhcpc_sendmsg(pdhcpc, presult, DHCPREQUEST) < 0) |
| { |
| return ERROR; |
| } |
| |
| retries++; |
| |
| /* Get the ACK/NAK response to the REQUEST (or timeout) */ |
| |
| start = clock(); |
| do |
| { |
| result = recv(pdhcpc->sockfd, &pdhcpc->packet, |
| sizeof(struct dhcp_msg), 0); |
| if (result >= 0) |
| { |
| /* Parse the response */ |
| |
| msgtype = dhcpc_parsemsg(pdhcpc, result, presult); |
| |
| /* The ACK response means that the server has accepted |
| * our request and we have the lease. |
| */ |
| |
| if (msgtype == DHCPACK) |
| { |
| ninfo("Received ACK\n"); |
| state = STATE_HAVE_LEASE; |
| } |
| |
| /* NAK means the server has refused our request */ |
| |
| else if (msgtype == DHCPNAK) |
| { |
| ninfo("Received NAK\n"); |
| oldaddr.s_addr = INADDR_ANY; |
| netlib_set_ipv4addr(pdhcpc->interface, &oldaddr); |
| errno = ECONNREFUSED; |
| return ERROR; |
| } |
| |
| /* If we get any OFFERs from other servers, then decline |
| * them now and continue waiting for the ACK from the server |
| * that we requested from. |
| */ |
| |
| else if (msgtype == DHCPOFFER && |
| pdhcpc->serverid.s_addr != presult->serverid.s_addr) |
| { |
| ninfo("Received another OFFER, send DECLINE\n"); |
| dhcpc_sendmsg(pdhcpc, presult, DHCPDECLINE); |
| } |
| |
| /* Otherwise, it is something that we do not recognize */ |
| |
| else |
| { |
| ninfo("Ignoring msgtype=%d\n", msgtype); |
| } |
| } |
| |
| /* An error has occurred. If this was a timeout error (meaning |
| * that nothing was received on this socket for a long period of |
| * time). Then break out and send the DISCOVER command again |
| * (at most 3 times). |
| */ |
| |
| else if (errno != EAGAIN && errno != EINTR) |
| { |
| /* An error other than a timeout was received */ |
| |
| netlib_set_ipv4addr(pdhcpc->interface, &oldaddr); |
| return ERROR; |
| } |
| } |
| while (state == STATE_HAVE_OFFER && TICK2MSEC(clock() - start) < |
| CONFIG_NETUTILS_DHCPC_RECV_TIMEOUT_MS); |
| } |
| while (state == STATE_HAVE_OFFER && |
| retries < CONFIG_NETUTILS_DHCPC_RETRIES); |
| |
| /* If no DHCPLEASE received here, error out */ |
| |
| if (state != STATE_HAVE_LEASE) |
| { |
| return ERROR; |
| } |
| |
| ninfo("Got IP address %u.%u.%u.%u\n", |
| ip4_addr1(presult->ipaddr.s_addr), |
| ip4_addr2(presult->ipaddr.s_addr), |
| ip4_addr3(presult->ipaddr.s_addr), |
| ip4_addr4(presult->ipaddr.s_addr)); |
| ninfo("Got netmask %u.%u.%u.%u\n", |
| ip4_addr1(presult->netmask.s_addr), |
| ip4_addr2(presult->netmask.s_addr), |
| ip4_addr3(presult->netmask.s_addr), |
| ip4_addr4(presult->netmask.s_addr)); |
| ninfo("Got DNS server %u.%u.%u.%u\n", |
| ip4_addr1(presult->dnsaddr.s_addr), |
| ip4_addr2(presult->dnsaddr.s_addr), |
| ip4_addr3(presult->dnsaddr.s_addr), |
| ip4_addr4(presult->dnsaddr.s_addr)); |
| ninfo("Got default router %u.%u.%u.%u\n", |
| ip4_addr1(presult->default_router.s_addr), |
| ip4_addr2(presult->default_router.s_addr), |
| ip4_addr3(presult->default_router.s_addr), |
| ip4_addr4(presult->default_router.s_addr)); |
| ninfo("Lease expires in %" PRId32 " seconds\n", presult->lease_time); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: dhcpc_request_async |
| ****************************************************************************/ |
| |
| int dhcpc_request_async(FAR void *handle, dhcpc_callback_t callback) |
| { |
| FAR struct dhcpc_state_s *pdhcpc = (FAR struct dhcpc_state_s *)handle; |
| int ret; |
| |
| if (!handle || !callback) |
| { |
| errno = EINVAL; |
| return ERROR; |
| } |
| |
| if (pdhcpc->thread) |
| { |
| nerr("ERROR: DHCPC thread already running\n"); |
| errno = EALREADY; |
| return ERROR; |
| } |
| |
| pdhcpc->callback = callback; |
| pdhcpc->cancel = 0; |
| ret = pthread_create(&pdhcpc->thread, NULL, dhcpc_run, pdhcpc); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to start the DHCPC thread\n"); |
| errno = ret; |
| return ERROR; |
| } |
| |
| return OK; |
| } |