| /* |
| * Copyright (c) 2001-2003 Leon Woestenberg <leon.woestenberg@axon.tv> |
| * Copyright (c) 2001-2003 Axon Digital Design B.V., The Netherlands. |
| * 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. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. |
| * |
| * This file is a contribution to the lwIP TCP/IP stack. |
| * The Swedish Institute of Computer Science and Adam Dunkels |
| * are specifically granted permission to redistribute this |
| * source code. |
| * |
| * Author: Leon Woestenberg <leon.woestenberg@axon.tv> |
| * |
| * This is a DHCP client for the lwIP TCP/IP stack, for releases newer than |
| * lwIP 0.5.3 which has the new "etharp" module. It aims to conform with |
| * RFC 2131 and RFC 2132. |
| * |
| * KNOWN BUG: |
| * - Parsing of DHCP messages which use file/sname field overloading will |
| * probably fail. Additional support for this must go into dhcp_unfold(). |
| * TODO: |
| * - Add JavaDoc style documentation (API, internals). |
| * - Make the unfold routine smarter to handle this |
| * - Support for interfaces other than Ethernet (SLIP, PPP, ...) |
| * - ... |
| * |
| * Please coordinate changes and requests with Leon Woestenberg |
| * <leon.woestenberg@axon.tv> |
| * |
| * Integration with your code: |
| * |
| * In lwip/dhcp.h |
| * #define DHCP_COARSE_TIMER_SECS (recommended 60 which is a minute) |
| * #define DHCP_FINE_TIMER_MSECS (recommended 500 which equals TCP coarse timer) |
| * |
| * Then have your application call dhcp_coarse_tmr() and |
| * dhcp_fine_tmr() on the defined intervals. |
| * |
| * How to boot the DHCP client |
| * |
| * First, call dhcp_init() to initialize the DHCP client. |
| * |
| * Then, use |
| * struct dhcp_state *client = dhcp_start(struct netif *netif) |
| * starts a DHCP client instance which configures the interface by |
| * obtaining an IP address lease and maintaining it. |
| * |
| * Use dhcp_release(client) to end the lease and use dhcp_stop(client) |
| * to remove the DHCP client. |
| * |
| */ |
| #include "lwip/debug.h" |
| #include "lwip/stats.h" |
| #include "lwip/mem.h" |
| #include "lwip/udp.h" |
| #include "netif/etharp.h" |
| |
| #include "lwip/sys.h" |
| #include "lwipopts.h" |
| #include "lwip/dhcp.h" |
| |
| /** find the active DHCP attached to the given network interface */ |
| struct dhcp_state *dhcp_find_client(struct netif *netif); |
| |
| /** transaction identifier, unique over all DHCP requests */ |
| static u32_t xid = 0xABCD0000; |
| /** singly-linked list of DHCP clients that are active */ |
| static struct dhcp_state *client_list = NULL; |
| |
| /** DHCP client state machine functions */ |
| static void dhcp_handle_ack(struct dhcp_state *state); |
| static void dhcp_handle_nak(struct dhcp_state *state); |
| static void dhcp_handle_offer(struct dhcp_state *state); |
| static err_t dhcp_discover(struct dhcp_state *state); |
| static err_t dhcp_select(struct dhcp_state *state); |
| static void dhcp_check(struct dhcp_state *state); |
| static err_t dhcp_decline(struct dhcp_state *state); |
| static void dhcp_bind(struct dhcp_state *state); |
| static err_t dhcp_rebind(struct dhcp_state *state); |
| static err_t dhcp_release(struct dhcp_state *state); |
| static void dhcp_set_state(struct dhcp_state *state, unsigned char new_state); |
| |
| /** receive, unfold, parse and free incoming messages */ |
| static void dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, u16_t port); |
| static err_t dhcp_unfold_reply(struct dhcp_state *state); |
| static u8_t *dhcp_get_option_ptr(struct dhcp_state *state, u8_t option_type); |
| static u8_t dhcp_get_option_byte(u8_t *ptr); |
| static u16_t dhcp_get_option_short(u8_t *ptr); |
| static u32_t dhcp_get_option_long(u8_t *ptr); |
| static void dhcp_free_reply(struct dhcp_state *state); |
| |
| /** set the DHCP timers */ |
| static void dhcp_timeout(struct dhcp_state *state); |
| static void dhcp_t1_timeout(struct dhcp_state *state); |
| static void dhcp_t2_timeout(struct dhcp_state *state); |
| |
| #if DHCP_DOES_ARP_CHECK |
| /** called by ARP to notify us of ARP replies */ |
| void dhcp_arp_reply(struct ip_addr *addr); |
| #endif |
| |
| /** build outgoing messages */ |
| static err_t dhcp_create_request(struct dhcp_state *state); |
| static void dhcp_delete_request(struct dhcp_state *state); |
| static void dhcp_option(struct dhcp_state *state, u8_t option_type, u8_t option_len); |
| static void dhcp_option_byte(struct dhcp_state *state, u8_t value); |
| static void dhcp_option_short(struct dhcp_state *state, u16_t value); |
| static void dhcp_option_long(struct dhcp_state *state, u32_t value); |
| static void dhcp_option_trailer(struct dhcp_state *state); |
| |
| /** |
| * Back-off the DHCP client because of a received NAK. Receiving a |
| * NAK means the client asked for something non-sensible, for |
| * example when it tries to renew a lease obtained on another network. |
| * |
| * We back-off and will end up restarting a fresh DHCP negotiation later. |
| */ |
| static void dhcp_handle_nak(struct dhcp_state *state) { |
| u16_t msecs = 10 * 1000; |
| DEBUGF(DHCP_DEBUG, ("dhcp_handle_nak()")); |
| state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; |
| DEBUGF(DHCP_DEBUG, ("dhcp_handle_nak(): set request timeout %u msecs", msecs)); |
| dhcp_set_state(state, DHCP_BACKING_OFF); |
| } |
| |
| /** |
| * Checks if the offered address from the server is already in use. |
| * |
| * It does so by sending an ARP request for the offered address and |
| * entering CHECKING state. If no ARP reply is received within a small |
| * interval, the address is assumed to be free for use by us. |
| */ |
| static void dhcp_check(struct dhcp_state *state) |
| { |
| struct pbuf *p; |
| err_t result; |
| u16_t msecs; |
| DEBUGF(DHCP_DEBUG, ("dhcp_check()")); |
| p = etharp_query(state->netif, &state->offered_ip_addr, NULL); |
| if(p != NULL) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_check(): sending ARP request len %u", p->tot_len)); |
| result = state->netif->linkoutput(state->netif, p); |
| pbuf_free(p); |
| } |
| state->tries++; |
| msecs = 500; |
| state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; |
| DEBUGF(DHCP_DEBUG, ("dhcp_check(): set request timeout %u msecs", msecs)); |
| dhcp_set_state(state, DHCP_CHECKING); |
| } |
| |
| /** |
| * Remember the configuration offered by a DHCP server. |
| * |
| */ |
| static void dhcp_handle_offer(struct dhcp_state *state) |
| { |
| u8_t *option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_SERVER_ID); |
| if (option_ptr != NULL) |
| { |
| state->server_ip_addr.addr = htonl(dhcp_get_option_long(&option_ptr[2])); |
| DEBUGF(DHCP_DEBUG, ("dhcp_handle_offer(): server 0x%08lx", state->server_ip_addr.addr)); |
| /* remember offered address */ |
| ip_addr_set(&state->offered_ip_addr, (struct ip_addr *)&state->msg_in->yiaddr); |
| DEBUGF(DHCP_DEBUG, ("dhcp_handle_offer(): offer for 0x%08lx", state->offered_ip_addr.addr)); |
| dhcp_select(state); |
| } |
| else |
| { |
| //dhcp_start(restart); |
| } |
| } |
| |
| /** |
| * Select a DHCP server offer out of all offers. |
| * |
| * Simply select the first offer received. |
| */ |
| static err_t dhcp_select(struct dhcp_state *state) |
| { |
| err_t result; |
| u32_t msecs; |
| DEBUGF(DHCP_DEBUG, ("dhcp_select()")); |
| |
| // create and initialize the DHCP message header |
| result = dhcp_create_request(state); |
| if (result == ERR_OK) |
| { |
| dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); |
| dhcp_option_byte(state, DHCP_REQUEST); |
| |
| dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); |
| dhcp_option_short(state, 576); |
| |
| /* MUST request the offered IP address */ |
| dhcp_option(state, DHCP_OPTION_REQUESTED_IP, 4); |
| dhcp_option_long(state, ntohl(state->offered_ip_addr.addr)); |
| |
| dhcp_option(state, DHCP_OPTION_SERVER_ID, 4); |
| dhcp_option_long(state, ntohl(state->server_ip_addr.addr)); |
| |
| dhcp_option(state, DHCP_OPTION_PARAMETER_REQUEST_LIST, 3); |
| dhcp_option_byte(state, DHCP_OPTION_SUBNET_MASK); |
| dhcp_option_byte(state, DHCP_OPTION_ROUTER); |
| dhcp_option_byte(state, DHCP_OPTION_BROADCAST); |
| |
| dhcp_option_trailer(state); |
| |
| pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); |
| |
| udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); |
| udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT); |
| udp_send(state->pcb, state->p_out); |
| // reconnect to any (or to server here?!) |
| udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT); |
| dhcp_delete_request(state); |
| } |
| state->tries++; |
| msecs = state->tries < 4 ? state->tries * 1000 : 4 * 1000; |
| state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; |
| DEBUGF(DHCP_DEBUG, ("dhcp_select(): set request timeout %u msecs", msecs)); |
| dhcp_set_state(state, DHCP_REQUESTING); |
| return result; |
| } |
| |
| /** |
| * The DHCP timer that checks for lease renewal/rebind timeouts. |
| * |
| */ |
| void dhcp_coarse_tmr() |
| { |
| struct dhcp_state *list_state = client_list; |
| DEBUGF(DHCP_DEBUG, ("dhcp_coarse_tmr():")); |
| // loop through clients |
| while (list_state != NULL) |
| { |
| // timer is active (non zero), and triggers (zeroes) now |
| if (list_state->t2_timeout-- == 1) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_coarse_tmr(): t2 timeout")); |
| // this clients' rebind timeout triggered |
| dhcp_t2_timeout(list_state); |
| } |
| // timer is active (non zero), and triggers (zeroes) now |
| else if (list_state->t1_timeout-- == 1) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_coarse_tmr(): t1 timeout")); |
| // this clients' renewal timeout triggered |
| dhcp_t1_timeout(list_state); |
| } |
| // proceed to next timer |
| list_state = list_state->next; |
| } |
| } |
| |
| /** |
| * The DHCP timer that handles DHCP negotiotion transaction timeouts. |
| * |
| */ |
| void dhcp_fine_tmr() |
| { |
| struct dhcp_state *list_state = client_list; |
| // DEBUGF(DHCP_DEBUG, ("dhcp_fine_tmr():")); |
| // loop through clients |
| while (list_state != NULL) |
| { |
| // timer is active (non zero), and triggers (zeroes) now |
| if (list_state->request_timeout-- == 1) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_fine_tmr(): request timeout")); |
| // this clients' request timeout triggered |
| dhcp_timeout(list_state); |
| } |
| // proceed to next client |
| list_state = list_state->next; |
| } |
| } |
| |
| /** |
| * A DHCP negotiation transaction, or ARP request, has timed out. |
| * |
| * |
| */ |
| static void dhcp_timeout(struct dhcp_state *state) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_timeout()")); |
| if ((state->state == DHCP_BACKING_OFF) || (state->state == DHCP_SELECTING)) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_timeout(): restarting discovery")); |
| dhcp_discover(state); |
| } |
| else if (state->state == DHCP_REQUESTING) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_timeout(): REQUESTING, DHCP request timed out")); |
| if (state->tries <= 5) |
| { |
| dhcp_select(state); |
| } |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_timeout(): REQUESTING, releasing, restarting")); |
| dhcp_release(state); |
| dhcp_discover(state); |
| } |
| } |
| else if (state->state == DHCP_CHECKING) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_timeout(): CHECKING, ARP request timed out")); |
| if (state->tries <= 1) |
| { |
| dhcp_check(state); |
| } |
| // no ARP replies on the offered address, |
| // looks like the IP address is indeed free |
| else |
| { |
| dhcp_bind(state); |
| } |
| } |
| else if (state->state == DHCP_RENEWING) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_timeout(): RENEWING, DHCP request timed out")); |
| dhcp_renew(state); |
| } |
| else if (state->state == DHCP_REBINDING) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_timeout(): REBINDING, DHCP request timed out")); |
| if (state->tries <= 8) |
| { |
| dhcp_rebind(state); |
| } |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_timeout(): RELEASING, DISCOVERING")); |
| dhcp_release(state); |
| dhcp_discover(state); |
| } |
| } |
| } |
| |
| /** |
| * The renewal period has timed out. |
| * |
| */ |
| static void dhcp_t1_timeout(struct dhcp_state *state) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_t1_timeout()")); |
| if ((state->state == DHCP_REQUESTING) || (state->state == DHCP_BOUND) || (state->state == DHCP_RENEWING)) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_t1_timeout(): must renew")); |
| dhcp_renew(state); |
| } |
| } |
| |
| /** |
| * The rebind period has timed out. |
| * |
| */ |
| static void dhcp_t2_timeout(struct dhcp_state *state) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_t2_timeout()")); |
| if ((state->state == DHCP_REQUESTING) || (state->state == DHCP_BOUND) || (state->state == DHCP_RENEWING)) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_t2_timeout(): must rebind")); |
| dhcp_rebind(state); |
| } |
| } |
| |
| /** |
| * Extract options from the server ACK message. |
| * |
| */ |
| static void dhcp_handle_ack(struct dhcp_state *state) |
| { |
| u8_t *option_ptr; |
| /* clear options we might not get from the ACK */ |
| state->offered_sn_mask.addr = 0; |
| state->offered_gw_addr.addr = 0; |
| state->offered_bc_addr.addr = 0; |
| |
| option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_LEASE_TIME); |
| if (option_ptr != NULL) |
| { |
| state->offered_t0_lease = dhcp_get_option_long(option_ptr + 2); |
| state->offered_t1_renew = state->offered_t0_lease / 2; |
| state->offered_t2_rebind = state->offered_t0_lease; |
| } |
| /* renewal period */ |
| option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_T1); |
| if (option_ptr != NULL) |
| { |
| state->offered_t1_renew = dhcp_get_option_long(option_ptr + 2); |
| } |
| /* rebind period */ |
| option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_T2); |
| if (option_ptr != NULL) |
| { |
| state->offered_t2_rebind = dhcp_get_option_long(option_ptr + 2); |
| } |
| /* (y)our internet address */ |
| ip_addr_set(&state->offered_ip_addr, &state->msg_in->yiaddr); |
| |
| /* subnet mask */ |
| option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_SUBNET_MASK); |
| if (option_ptr != NULL) |
| { |
| state->offered_sn_mask.addr = htonl(dhcp_get_option_long(&option_ptr[2])); |
| } |
| |
| /* gateway router */ |
| option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_ROUTER); |
| if (option_ptr != NULL) |
| { |
| state->offered_gw_addr.addr = htonl(dhcp_get_option_long(&option_ptr[2])); |
| } |
| |
| /* broadcast address */ |
| option_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_BROADCAST); |
| if (option_ptr != NULL) |
| { |
| state->offered_bc_addr.addr = htonl(dhcp_get_option_long(&option_ptr[2])); |
| } |
| } |
| |
| |
| /** |
| * Initialize DHCP. |
| * |
| * Must be called prior to any other dhcp_*() function. |
| * |
| */ |
| void dhcp_init(void) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_init()")); |
| /* this would be the proper way to stop all dhcp clients */ |
| /* but we need lwIP to be running at this point */ |
| /* while(client_list) dhcp_stop(client_list); */ |
| client_list = NULL; |
| } |
| |
| /** |
| * Start DHCP negotiation for a network interface. |
| * |
| * If no DHCP client instance was attached to this interface, |
| * a new client is created first. If a DHCP client instance |
| * was already present, it restarts negotiation. |
| * |
| * @return The DHCP client state, which must be passed for |
| * all subsequential dhcp_*() calls. NULL means there is |
| * no (longer a) DHCP client attached to the interface |
| * (due to unavailable memory or network resources). |
| * |
| */ |
| struct dhcp_state *dhcp_start(struct netif *netif) |
| { |
| struct dhcp_state *state = NULL; |
| struct dhcp_state *list_state = client_list; |
| err_t result = ERR_OK; |
| |
| DEBUGF(DHCP_DEBUG, ("dhcp_start()")); |
| |
| /* find the DHCP client attached to the given interface */ |
| state = dhcp_find_client(netif); |
| DEBUGF(DHCP_DEBUG, ("dhcp_start(): finished parsing through list")); |
| /* a DHCP client already attached to this interface? */ |
| if (state != NULL) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_start(): already active on interface")); |
| /* just restart the DHCP negotiation */ |
| result = dhcp_discover(state); |
| if (result == ERR_OK) |
| { |
| return state; |
| } |
| else |
| { |
| dhcp_stop(state); |
| return NULL; |
| } |
| } |
| |
| DEBUGF(DHCP_DEBUG, ("dhcp_start(): starting new DHCP client")); |
| state = mem_malloc(sizeof(struct dhcp_state)); |
| if (state == NULL) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_start(): could not allocate dhcp_state")); |
| return NULL; |
| } |
| memset(state, 0, sizeof(struct dhcp_state)); |
| |
| DEBUGF(DHCP_DEBUG, ("dhcp_start(): allocated dhcp_state")); |
| state->pcb = udp_new(); |
| if (state->pcb == NULL) { |
| DEBUGF(DHCP_DEBUG, ("dhcp_start(): could not obtain pcb")); |
| mem_free((void *)state); |
| state = NULL; |
| return NULL; |
| } |
| DEBUGF(DHCP_DEBUG, ("dhcp_start(): created new udp pcb")); |
| state->netif = netif; |
| /* enqueue in list of clients */ |
| /* we are last in list */ |
| state->next = NULL; |
| /* empty list? */ |
| if (client_list == NULL) |
| { |
| /* single item at head of list */ |
| client_list = state; |
| } |
| else |
| { |
| /* proceed to the last DHCP client state */ |
| while (list_state->next != NULL) list_state = list_state->next; |
| list_state->next = state; |
| } |
| dhcp_discover(state); |
| return state; |
| } |
| |
| /** |
| * Inform a DHCP server of our manual configuration. |
| * |
| * This informs DHCP servers of our fixed IP address configuration |
| * by send an INFORM message. It does not involve DHCP address |
| * configuration, it is just here to be nice. |
| * |
| */ |
| void dhcp_inform(struct netif *netif) |
| { |
| struct dhcp_state *state = NULL; |
| err_t result = ERR_OK; |
| state = mem_malloc(sizeof(struct dhcp_state)); |
| if (state == NULL) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_inform(): could not allocate dhcp_state")); |
| return; |
| } |
| memset(state, 0, sizeof(struct dhcp_state)); |
| |
| DEBUGF(DHCP_DEBUG, ("dhcp_inform(): allocated dhcp_state")); |
| state->pcb = udp_new(); |
| if (state->pcb == NULL) { |
| DEBUGF(DHCP_DEBUG, ("dhcp_inform(): could not obtain pcb")); |
| mem_free((void *)state); |
| return; |
| } |
| DEBUGF(DHCP_DEBUG, ("dhcp_inform(): created new udp pcb")); |
| state->netif = netif; |
| // we are last in list |
| state->next = NULL; |
| // create and initialize the DHCP message header |
| result = dhcp_create_request(state); |
| if (result == ERR_OK) |
| { |
| |
| dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); |
| dhcp_option_byte(state, DHCP_INFORM); |
| |
| dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); |
| dhcp_option_short(state, 576); |
| |
| dhcp_option_trailer(state); |
| |
| pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); |
| |
| udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); |
| udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT); |
| udp_send(state->pcb, state->p_out); |
| udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT); |
| dhcp_delete_request(state); |
| } |
| |
| if (state != NULL) |
| { |
| if (state->pcb != NULL) udp_remove(state->pcb); |
| state->pcb = NULL; |
| mem_free((void *)state); |
| } |
| } |
| |
| #if DHCP_DOES_ARP_CHECK |
| void dhcp_arp_reply(struct ip_addr *addr) |
| { |
| struct dhcp_state *list_state = client_list; |
| DEBUGF(DHCP_DEBUG, ("dhcp_arp_reply()")); |
| // loop through clients |
| while (list_state != NULL) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_arp_reply(): list_state %p", list_state)); |
| // is this DHCP client doing an ARP check? |
| if (list_state->state == DHCP_CHECKING) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_arp_reply(): CHECKING, arp reply for 0x%08lx", addr->addr)); |
| // does a host respond with the address we |
| // were offered by the DHCP server? |
| if (ip_addr_cmp(addr, &list_state->offered_ip_addr)) |
| { |
| // we will not accept the offered address |
| DEBUGF(DHCP_DEBUG, ("dhcp_arp_reply(): arp reply matched with offered address, declining")); |
| dhcp_decline(list_state); |
| } |
| } |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_arp_reply(): NOT CHECKING")); |
| } |
| // proceed to next timer |
| list_state = list_state->next; |
| } |
| } |
| |
| /** |
| * Decline a |
| * |
| * |
| */ |
| static err_t dhcp_decline(struct dhcp_state *state) |
| { |
| err_t result = ERR_OK; |
| u16_t msecs; |
| DEBUGF(DHCP_DEBUG, ("dhcp_decline()")); |
| dhcp_set_state(state, DHCP_BACKING_OFF); |
| // create and initialize the DHCP message header |
| result = dhcp_create_request(state); |
| if (result == ERR_OK) |
| { |
| dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); |
| dhcp_option_byte(state, DHCP_DECLINE); |
| |
| dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); |
| dhcp_option_short(state, 576); |
| |
| dhcp_option_trailer(state); |
| // resize pbuf to reflect true size of options |
| pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); |
| |
| udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); |
| udp_connect(state->pcb, &state->server_ip_addr, DHCP_SERVER_PORT); |
| udp_send(state->pcb, state->p_out); |
| dhcp_delete_request(state); |
| } |
| state->tries++; |
| msecs = 10*1000; |
| state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; |
| DEBUGF(DHCP_DEBUG, ("dhcp_decline(): set request timeout %u msecs", msecs)); |
| return result; |
| } |
| #endif |
| |
| |
| /** |
| * Start the DHCP process, discover a DHCP server. |
| * |
| */ |
| static err_t dhcp_discover(struct dhcp_state *state) |
| { |
| err_t result = ERR_OK; |
| u16_t msecs; |
| DEBUGF(DHCP_DEBUG, ("dhcp_discover()")); |
| ip_addr_set(&state->offered_ip_addr, IP_ADDR_ANY); |
| // create and initialize the DHCP message header |
| result = dhcp_create_request(state); |
| if (result == ERR_OK) |
| { |
| dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); |
| dhcp_option_byte(state, DHCP_DISCOVER); |
| |
| dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); |
| dhcp_option_short(state, 576); |
| |
| dhcp_option(state, DHCP_OPTION_PARAMETER_REQUEST_LIST, 3); |
| dhcp_option_byte(state, DHCP_OPTION_SUBNET_MASK); |
| dhcp_option_byte(state, DHCP_OPTION_ROUTER); |
| dhcp_option_byte(state, DHCP_OPTION_BROADCAST); |
| |
| dhcp_option_trailer(state); |
| |
| pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); |
| |
| udp_recv(state->pcb, dhcp_recv, state); |
| udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); |
| udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT); |
| |
| udp_send(state->pcb, state->p_out); |
| udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); |
| udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT); |
| dhcp_delete_request(state); |
| } |
| state->tries++; |
| msecs = state->tries < 4 ? (state->tries + 1) * 1000 : 10 * 1000; |
| state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; |
| DEBUGF(DHCP_DEBUG, ("dhcp_discover(): set request timeout %u msecs", msecs)); |
| dhcp_set_state(state, DHCP_SELECTING); |
| return result; |
| } |
| |
| |
| /** |
| * Bind the interface to the offered IP address. |
| * |
| */ |
| static void dhcp_bind(struct dhcp_state *state) |
| { |
| struct ip_addr sn_mask, gw_addr; |
| dhcp_set_state(state, DHCP_BOUND); |
| /* temporary DHCP lease? */ |
| if (state->offered_t1_renew != 0xffffffffUL) { |
| /* set renewal period timer */ |
| DEBUGF(DHCP_DEBUG, ("dhcp_bind(): t1 renewal timer %lu secs", state->offered_t1_renew)); |
| state->t1_timeout = (state->offered_t1_renew + DHCP_COARSE_TIMER_SECS / 2) / DHCP_COARSE_TIMER_SECS; |
| if (state->t1_timeout == 0) state->t1_timeout = 1; |
| DEBUGF(DHCP_DEBUG, ("dhcp_bind(): set request timeout %u msecs", state->offered_t1_renew*1000)); |
| } |
| /* set renewal period timer */ |
| if (state->offered_t2_rebind != 0xffffffffUL) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_bind(): t2 rebind timer %lu secs", state->offered_t2_rebind)); |
| state->t2_timeout = (state->offered_t2_rebind + DHCP_COARSE_TIMER_SECS / 2) / DHCP_COARSE_TIMER_SECS; |
| if (state->t2_timeout == 0) state->t2_timeout = 1; |
| DEBUGF(DHCP_DEBUG, ("dhcp_bind(): set request timeout %u msecs", state->offered_t2_rebind*1000)); |
| } |
| |
| ip_addr_set(&sn_mask, &state->offered_sn_mask); |
| // subnet mask not given |
| if (sn_mask.addr == 0) |
| { |
| // choose a safe subnet mask given the network class |
| u8_t first_octet = ip4_addr1(&sn_mask); |
| if (first_octet <= 127) sn_mask.addr = htonl(0xff000000); |
| else if (first_octet >= 192) sn_mask.addr = htonl(0xffffff00); |
| else sn_mask.addr = htonl(0xffff0000); |
| } |
| DEBUGF(DHCP_DEBUG, ("dhcp_bind(): SN: 0x%08lx", sn_mask.addr)); |
| netif_set_netmask(state->netif, &sn_mask); |
| |
| ip_addr_set(&gw_addr, &state->offered_gw_addr); |
| // gateway address not given |
| if (gw_addr.addr == 0) |
| { |
| gw_addr.addr &= sn_mask.addr; |
| gw_addr.addr |= 0x01000000; |
| } |
| DEBUGF(DHCP_DEBUG, ("dhcp_bind(): GW: 0x%08lx", gw_addr.addr)); |
| netif_set_gw(state->netif, &gw_addr); |
| |
| DEBUGF(DHCP_DEBUG, ("dhcp_bind(): IP: 0x%08lx", state->offered_ip_addr.addr)); |
| netif_set_ipaddr(state->netif, &state->offered_ip_addr); |
| } |
| |
| /** |
| * Renew an existing DHCP lease at the involved DHCP server. |
| * |
| */ |
| err_t dhcp_renew(struct dhcp_state *state) |
| { |
| err_t result; |
| u16_t msecs; |
| DEBUGF(DHCP_DEBUG, ("dhcp_renew()")); |
| dhcp_set_state(state, DHCP_RENEWING); |
| |
| // create and initialize the DHCP message header |
| result = dhcp_create_request(state); |
| if (result == ERR_OK) |
| { |
| |
| dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); |
| dhcp_option_byte(state, DHCP_REQUEST); |
| |
| dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); |
| dhcp_option_short(state, 576); |
| |
| #if 0 |
| dhcp_option(state, DHCP_OPTION_REQUESTED_IP, 4); |
| dhcp_option_long(state, ntohl(state->offered_ip_addr.addr)); |
| #endif |
| |
| #if 0 |
| dhcp_option(state, DHCP_OPTION_SERVER_ID, 4); |
| dhcp_option_long(state, ntohl(state->server_ip_addr.addr)); |
| #endif |
| |
| dhcp_option_trailer(state); |
| |
| pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); |
| |
| udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); |
| udp_connect(state->pcb, &state->server_ip_addr, DHCP_SERVER_PORT); |
| udp_send(state->pcb, state->p_out); |
| dhcp_delete_request(state); |
| } |
| state->tries++; |
| // back-off on retries, but to a maximum of 20 seconds |
| msecs = state->tries < 10 ? state->tries * 2000 : 20 * 1000; |
| state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; |
| DEBUGF(DHCP_DEBUG, ("dhcp_renew(): set request timeout %u msecs", msecs)); |
| return result; |
| } |
| |
| /** |
| * Rebind with a DHCP server for an existing DHCP lease. |
| * |
| */ |
| static err_t dhcp_rebind(struct dhcp_state *state) |
| { |
| err_t result; |
| u16_t msecs; |
| DEBUGF(DHCP_DEBUG, ("dhcp_rebind()")); |
| dhcp_set_state(state, DHCP_REBINDING); |
| |
| // create and initialize the DHCP message header |
| result = dhcp_create_request(state); |
| if (result == ERR_OK) |
| { |
| |
| dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); |
| dhcp_option_byte(state, DHCP_REQUEST); |
| |
| dhcp_option(state, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); |
| dhcp_option_short(state, 576); |
| |
| #if 0 |
| dhcp_option(state, DHCP_OPTION_REQUESTED_IP, 4); |
| dhcp_option_long(state, ntohl(state->offered_ip_addr.addr)); |
| |
| dhcp_option(state, DHCP_OPTION_SERVER_ID, 4); |
| dhcp_option_long(state, ntohl(state->server_ip_addr.addr)); |
| #endif |
| |
| dhcp_option_trailer(state); |
| |
| pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); |
| |
| udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); |
| udp_connect(state->pcb, IP_ADDR_BROADCAST, DHCP_SERVER_PORT); |
| udp_send(state->pcb, state->p_out); |
| udp_connect(state->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT); |
| dhcp_delete_request(state); |
| } |
| state->tries++; |
| msecs = state->tries < 10 ? state->tries * 1000 : 10 * 1000; |
| state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; |
| DEBUGF(DHCP_DEBUG, ("dhcp_rebind(): set request timeout %u msecs", msecs)); |
| return result; |
| } |
| |
| /** |
| * Rebind with a DHCP server for an existing DHCP lease. |
| * |
| */ |
| static err_t dhcp_release(struct dhcp_state *state) |
| { |
| err_t result; |
| u16_t msecs; |
| DEBUGF(DHCP_DEBUG, ("dhcp_release()")); |
| // and idle DHCP client |
| dhcp_set_state(state, DHCP_OFF); |
| |
| // create and initialize the DHCP message header |
| result = dhcp_create_request(state); |
| if (result == ERR_OK) |
| { |
| dhcp_option(state, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN); |
| dhcp_option_byte(state, DHCP_RELEASE); |
| |
| dhcp_option_trailer(state); |
| |
| pbuf_realloc(state->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + state->options_out_len); |
| |
| udp_bind(state->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); |
| udp_connect(state->pcb, &state->server_ip_addr, DHCP_SERVER_PORT); |
| udp_send(state->pcb, state->p_out); |
| dhcp_delete_request(state); |
| } |
| state->tries++; |
| msecs = state->tries < 10 ? state->tries * 1000 : 10 * 1000; |
| state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; |
| DEBUGF(DHCP_DEBUG, ("dhcp_release(): set request timeout %u msecs", msecs)); |
| // remove IP address from interface |
| netif_set_ipaddr(state->netif, IP_ADDR_ANY); |
| netif_set_gw(state->netif, IP_ADDR_ANY); |
| netif_set_netmask(state->netif, IP_ADDR_ANY); |
| return result; |
| } |
| /** |
| * Remove the DHCP client from the interface. |
| * |
| * @param state The DHCP client state |
| */ |
| void dhcp_stop(struct dhcp_state *state) |
| { |
| struct dhcp_state *list_state = client_list; |
| DEBUGF(DHCP_DEBUG, ("dhcp_stop()")); |
| ASSERT("dhcp_stop: state != NULL", state != NULL); |
| ASSERT("dhcp_stop: state->pcb != NULL", state->pcb != NULL); |
| |
| if (state != NULL) |
| { |
| if (state->pcb != NULL) |
| { |
| udp_remove(state->pcb); |
| state->pcb = NULL; |
| } |
| if (state->p != NULL) |
| { |
| pbuf_free(state->p); |
| state->p = NULL; |
| } |
| mem_free((void *)state); |
| // at head of list? |
| if (list_state == state) |
| { |
| // remove ourselves from head |
| client_list = state->next; |
| } |
| // not at head |
| else |
| { |
| // see if we can find a predecessor? |
| while ((list_state != NULL) && (list_state->next != state)) |
| { |
| // proceed to next state, if any |
| list_state = list_state->next; |
| } |
| // found a predecessor? |
| if (list_state != NULL) |
| { |
| // remove ourselves from list |
| list_state->next = state->next; |
| } |
| } |
| } |
| } |
| |
| static void dhcp_set_state(struct dhcp_state *state, unsigned char new_state) |
| { |
| if (new_state != state->state) |
| { |
| state->state = new_state; |
| state->tries = 0; |
| } |
| } |
| |
| |
| static void dhcp_option(struct dhcp_state *state, u8_t option_type, u8_t option_len) |
| { |
| ASSERT("dhcp_option_short: state->options_out_len + 2 + option_len <= DHCP_OPTIONS_LEN", state->options_out_len + 2 + option_len <= DHCP_OPTIONS_LEN); |
| state->msg_out->options[state->options_out_len++] = option_type; |
| state->msg_out->options[state->options_out_len++] = option_len; |
| } |
| static void dhcp_option_byte(struct dhcp_state *state, u8_t value) |
| { |
| ASSERT("dhcp_option_short: state->options_out_len < DHCP_OPTIONS_LEN", state->options_out_len < DHCP_OPTIONS_LEN); |
| state->msg_out->options[state->options_out_len++] = value; |
| } |
| static void dhcp_option_short(struct dhcp_state *state, u16_t value) |
| { |
| ASSERT("dhcp_option_short: state->options_out_len + 2 <= DHCP_OPTIONS_LEN", state->options_out_len + 2 <= DHCP_OPTIONS_LEN); |
| state->msg_out->options[state->options_out_len++] = (value & 0xff00U) >> 8; |
| state->msg_out->options[state->options_out_len++] = value & 0x00ffU; |
| } |
| static void dhcp_option_long(struct dhcp_state *state, u32_t value) |
| { |
| ASSERT("dhcp_option_long: state->options_out_len + 4 <= DHCP_OPTIONS_LEN", state->options_out_len + 4 <= DHCP_OPTIONS_LEN); |
| state->msg_out->options[state->options_out_len++] = (value & 0xff000000UL) >> 24; |
| state->msg_out->options[state->options_out_len++] = (value & 0x00ff0000UL) >> 16; |
| state->msg_out->options[state->options_out_len++] = (value & 0x0000ff00UL) >> 8; |
| state->msg_out->options[state->options_out_len++] = (value & 0x000000ffUL); |
| } |
| |
| /** |
| * Extract the dhcp_msg and options each into linear pieces of memory. |
| * |
| */ |
| static err_t dhcp_unfold_reply(struct dhcp_state *state) |
| { |
| struct pbuf *p = state->p; |
| u8_t *ptr; |
| u16_t i; |
| u16_t j = 0; |
| state->msg_in = NULL; |
| state->options_in = NULL; |
| // options present? |
| if (state->p->tot_len > sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN) |
| { |
| state->options_in_len = state->p->tot_len - (sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN); |
| state->options_in = mem_malloc(state->options_in_len); |
| if (state->options_in == NULL) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_unfold_reply(): could not allocate state->options")); |
| return ERR_MEM; |
| } |
| } |
| state->msg_in = mem_malloc(sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN); |
| if (state->msg_in == NULL) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_unfold_reply(): could not allocate state->msg_in")); |
| mem_free((void *)state->options_in); |
| state->options_in = NULL; |
| return ERR_MEM; |
| } |
| |
| ptr = (u8_t *)state->msg_in; |
| // proceed through struct dhcp_msg |
| for (i = 0; i < sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN; i++) |
| { |
| *ptr++ = ((u8_t *)p->payload)[j++]; |
| // reached end of pbuf? |
| if (j == p->len) |
| { |
| // proceed to next pbuf in chain |
| p = p->next; |
| j = 0; |
| } |
| } |
| DEBUGF(DHCP_DEBUG, ("dhcp_unfold_reply(): copied %u bytes into state->msg_in[]", i)); |
| if (state->options_in != NULL) |
| { |
| ptr = (u8_t *)state->options_in; |
| // proceed through options |
| for (i = 0; i < state->options_in_len; i++) |
| { |
| *ptr++ = ((u8_t *)p->payload)[j++]; |
| // reached end of pbuf? |
| if (j == p->len) |
| { |
| // proceed to next pbuf in chain |
| p = p->next; |
| j = 0; |
| } |
| } |
| DEBUGF(DHCP_DEBUG, ("dhcp_unfold_reply(): copied %u bytes to state->options_in[]", i)); |
| } |
| return ERR_OK; |
| } |
| |
| /** |
| * Extract the dhcp_msg and options into linear pieces of memory. |
| * |
| */ |
| static void dhcp_free_reply(struct dhcp_state *state) |
| { |
| mem_free((void *)state->msg_in); |
| mem_free((void *)state->options_in); |
| DEBUGF(DHCP_DEBUG, ("dhcp_free_reply(): freed")); |
| state->msg_in = NULL; |
| state->options_in = NULL; |
| state->options_in_len = 0; |
| } |
| |
| |
| /** |
| * Match incoming DHCP messages against a DHCP client, and trigger its state machine |
| */ |
| static void dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, u16_t port) |
| { |
| struct dhcp_state *state = (struct dhcp_state *)arg; |
| struct dhcp_msg *reply_msg = (struct dhcp_msg *)p->payload; |
| DEBUGF(DHCP_DEBUG, ("dhcp_recv()")); |
| DEBUGF(DHCP_DEBUG, ("pbuf->len = %u", p->len)); |
| DEBUGF(DHCP_DEBUG, ("pbuf->tot_len = %u", p->tot_len)); |
| state->p = p; |
| if (reply_msg->op == DHCP_BOOTREPLY) |
| { |
| DEBUGF(DHCP_DEBUG, ("state->netif->hwaddr = %02x:%02x:%02x:%02x:%02x:%02x", |
| state->netif->hwaddr[0], state->netif->hwaddr[1], state->netif->hwaddr[2], |
| state->netif->hwaddr[3], state->netif->hwaddr[4], state->netif->hwaddr[5])); |
| // TODO: Add multi network interface support, look up the targetted |
| // interface here. |
| if ((state->netif->hwaddr[0] == reply_msg->chaddr[0]) && |
| (state->netif->hwaddr[1] == reply_msg->chaddr[1]) && |
| (state->netif->hwaddr[2] == reply_msg->chaddr[2]) && |
| (state->netif->hwaddr[3] == reply_msg->chaddr[3]) && |
| (state->netif->hwaddr[4] == reply_msg->chaddr[4]) && |
| (state->netif->hwaddr[5] == reply_msg->chaddr[5])) |
| { |
| // check if the transaction ID matches |
| if (ntohl(reply_msg->xid) == state->xid) |
| { |
| // option fields could be unfold? |
| if (dhcp_unfold_reply(state) == ERR_OK) |
| { |
| u8_t *options_ptr = NULL; |
| DEBUGF(DHCP_DEBUG, ("searching DHCP_OPTION_MESSAGE_TYPE")); |
| options_ptr = dhcp_get_option_ptr(state, DHCP_OPTION_MESSAGE_TYPE); |
| if (options_ptr != NULL) |
| { |
| u8_t msg_type = dhcp_get_option_byte(options_ptr + 2); |
| if (msg_type == DHCP_ACK) |
| { |
| DEBUGF(DHCP_DEBUG, ("DHCP_ACK received")); |
| if (state->state == DHCP_REQUESTING) |
| { |
| dhcp_handle_ack(state); |
| state->request_timeout = 0; |
| #if DHCP_DOES_ARP_CHECK |
| dhcp_check(state); |
| #else |
| dhcp_bind(state); |
| #endif |
| } |
| else if ((state->state == DHCP_REBOOTING) || (state->state == DHCP_REBINDING) ||(state->state == DHCP_RENEWING)) |
| { |
| state->request_timeout = 0; |
| dhcp_bind(state); |
| } |
| } |
| // received a DHCP_NAK in appropriate state? |
| else if ((msg_type == DHCP_NAK) && |
| ((state->state == DHCP_REBOOTING) || (state->state == DHCP_REQUESTING) || |
| (state->state == DHCP_REBINDING) || (state->state == DHCP_RENEWING ))) |
| { |
| DEBUGF(DHCP_DEBUG, ("DHCP_NAK received")); |
| state->request_timeout = 0; |
| dhcp_handle_nak(state); |
| } |
| // received a DHCP_OFFER in DHCP_SELECTING state? |
| else if ((msg_type == DHCP_OFFER) && (state->state == DHCP_SELECTING)) |
| { |
| DEBUGF(DHCP_DEBUG, ("DHCP_OFFER received in DHCP_SELECTING state")); |
| state->request_timeout = 0; |
| dhcp_handle_offer(state); |
| } |
| } |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("DHCP_OPTION_MESSAGE_TYPE option not found")); |
| } |
| dhcp_free_reply(state); |
| } |
| } |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("reply_msg->xid=%lx does not match with state->xid=%lx", |
| ntohl(reply_msg->xid), state->xid)); |
| } |
| } |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("hardware address did not match")); |
| DEBUGF(DHCP_DEBUG, ("reply_msg->chaddr = %02x:%02x:%02x:%02x:%02x:%02x", |
| reply_msg->chaddr[0], reply_msg->chaddr[1], reply_msg->chaddr[2], |
| reply_msg->chaddr[3], reply_msg->chaddr[4], reply_msg->chaddr[5])); |
| } |
| } |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("not a DHCP reply message, but type %u", reply_msg->op)); |
| } |
| |
| pbuf_free(p); |
| } |
| |
| |
| static err_t dhcp_create_request(struct dhcp_state *state) |
| { |
| u16_t i; |
| ASSERT("dhcp_create_request: state->p_out == NULL", state->p_out == NULL); |
| ASSERT("dhcp_create_request: state->msg_out == NULL", state->msg_out == NULL); |
| state->p_out = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct dhcp_msg), PBUF_RAM); |
| if (state->p_out == NULL) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_create_request(): could not allocate pbuf")); |
| return ERR_MEM; |
| } |
| state->xid = xid; |
| xid++; |
| |
| state->msg_out = (struct dhcp_msg *)state->p_out->payload; |
| |
| state->msg_out->op = DHCP_BOOTREQUEST; |
| state->msg_out->htype = DHCP_HTYPE_ETH; |
| state->msg_out->hlen = DHCP_HLEN_ETH; |
| state->msg_out->hops = 0; |
| state->msg_out->xid = htonl(state->xid); |
| state->msg_out->secs = 0; |
| state->msg_out->flags = 0; |
| state->msg_out->ciaddr = state->netif->ip_addr.addr; |
| state->msg_out->yiaddr = 0; |
| state->msg_out->siaddr = 0; |
| state->msg_out->giaddr = 0; |
| for (i = 0; i < DHCP_CHADDR_LEN; i++) state->msg_out->chaddr[i] = state->netif->hwaddr[i]; |
| for (i = 0; i < DHCP_SNAME_LEN; i++) state->msg_out->sname[i] = 0; |
| for (i = 0; i < DHCP_FILE_LEN; i++) state->msg_out->file[i] = 0; |
| state->msg_out->cookie = htonl(0x63825363UL); |
| state->options_out_len = 0; |
| // fill options field with an incrementing array (for debugging purposes) |
| for (i = 0; i < DHCP_OPTIONS_LEN; i++) state->msg_out->options[i] = i; |
| return ERR_OK; |
| } |
| |
| static void dhcp_delete_request(struct dhcp_state *state) |
| { |
| ASSERT("dhcp_free_msg: state->p_out != NULL", state->p_out != NULL); |
| ASSERT("dhcp_free_msg: state->msg_out != NULL", state->msg_out != NULL); |
| pbuf_free(state->p_out); |
| state->p_out = NULL; |
| state->msg_out = NULL; |
| } |
| |
| /** |
| * Add a DHCP message trailer |
| * |
| * Adds the END option to the DHCP message, and up to |
| * three padding bytes. |
| */ |
| |
| static void dhcp_option_trailer(struct dhcp_state *state) |
| { |
| ASSERT("dhcp_option_trailer: state->msg_out != NULL", state->msg_out != NULL); |
| ASSERT("dhcp_option_trailer: state->options_out_len < DHCP_OPTIONS_LEN", state->options_out_len < DHCP_OPTIONS_LEN); |
| state->msg_out->options[state->options_out_len++] = DHCP_OPTION_END; |
| // packet is still too small, or not 4 byte aligned? |
| while ((state->options_out_len < DHCP_MIN_OPTIONS_LEN) || (state->options_out_len & 3)) |
| { |
| //DEBUGF(DHCP_DEBUG, ("dhcp_option_trailer: state->options_out_len=%u, DHCP_OPTIONS_LEN=%u", state->options_out_len, DHCP_OPTIONS_LEN)); |
| ASSERT("dhcp_option_trailer: state->options_out_len < DHCP_OPTIONS_LEN", state->options_out_len < DHCP_OPTIONS_LEN); |
| state->msg_out->options[state->options_out_len++] = 0; |
| } |
| } |
| |
| /** |
| * Find the offset of a DHCP option inside the DHCP message. |
| * |
| * @param client DHCP client |
| * @param option_type |
| * |
| * @return a byte offset into the UDP message where the option was found, or |
| * zero if the given option was not found. |
| */ |
| static u8_t *dhcp_get_option_ptr(struct dhcp_state *state, u8_t option_type) |
| { |
| u8_t overload = DHCP_OVERLOAD_NONE; |
| |
| // options available? |
| if ((state->options_in != NULL) && (state->options_in_len > 0)) |
| { |
| // start with options field |
| u8_t *options = (u8_t *)state->options_in; |
| u16_t offset = 0; |
| // at least 1 byte to read and no end marker, then at least 3 bytes to read? |
| while ((offset < state->options_in_len) && (options[offset] != DHCP_OPTION_END)) |
| { |
| //DEBUGF(DHCP_DEBUG, ("msg_offset=%u, q->len=%u", msg_offset, q->len)); |
| // are the sname and/or file field overloaded with options? |
| if (options[offset] == DHCP_OPTION_OVERLOAD) |
| { |
| DEBUGF(DHCP_DEBUG, ("overloaded message detected")); |
| // skip option type and length |
| offset += 2; |
| overload = options[offset++]; |
| } |
| // requested option found |
| else if (options[offset] == option_type) |
| { |
| DEBUGF(DHCP_DEBUG, ("option found at offset %u in options", offset)); |
| return &options[offset]; |
| } |
| // skip option |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("skipping option %u in options", options[offset])); |
| // skip option type |
| offset++; |
| // skip option length, and then length bytes |
| offset += 1 + options[offset]; |
| } |
| } |
| // is this an overloaded message? |
| if (overload != DHCP_OVERLOAD_NONE) |
| { |
| u16_t field_len; |
| if (overload == DHCP_OVERLOAD_FILE) |
| { |
| DEBUGF(DHCP_DEBUG, ("overloaded file field")); |
| options = (u8_t *)&state->msg_in->file; |
| field_len = DHCP_FILE_LEN; |
| } |
| else if (overload == DHCP_OVERLOAD_SNAME) |
| { |
| DEBUGF(DHCP_DEBUG, ("overloaded sname field")); |
| options = (u8_t *)&state->msg_in->sname; |
| field_len = DHCP_SNAME_LEN; |
| } |
| else // TODO: check if else if () is necessary |
| { |
| DEBUGF(DHCP_DEBUG, ("overloaded sname and file field")); |
| options = (u8_t *)&state->msg_in->sname; |
| field_len = DHCP_FILE_LEN + DHCP_SNAME_LEN; |
| } |
| offset = 0; |
| |
| // at least 1 byte to read and no end marker |
| while ((offset < field_len) && (options[offset] != DHCP_OPTION_END)) |
| { |
| if (options[offset] == option_type) |
| { |
| DEBUGF(DHCP_DEBUG, ("option found at offset=%u", offset)); |
| return &options[offset]; |
| } |
| // skip option |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("skipping option %u", options[offset])); |
| // skip option type |
| offset++; |
| offset += 1 + options[offset]; |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| * Return the byte of DHCP option data. |
| * |
| * @param client DHCP client. |
| * @param ptr pointer obtained by dhcp_get_option_ptr(). |
| * |
| * @return byte value at the given address. |
| */ |
| static u8_t dhcp_get_option_byte(u8_t *ptr) |
| { |
| DEBUGF(DHCP_DEBUG, ("option byte value=%u", *ptr)); |
| return *ptr; |
| } |
| |
| /** |
| * Return the 16-bit value of DHCP option data. |
| * |
| * @param client DHCP client. |
| * @param ptr pointer obtained by dhcp_get_option_ptr(). |
| * |
| * @return byte value at the given address. |
| */ |
| static u16_t dhcp_get_option_short(u8_t *ptr) |
| { |
| u16_t value; |
| value = *ptr++ << 8; |
| value |= *ptr; |
| DEBUGF(DHCP_DEBUG, ("option short value=%u", value)); |
| return value; |
| } |
| |
| /** |
| * Return the 32-bit value of DHCP option data. |
| * |
| * @param client DHCP client. |
| * @param ptr pointer obtained by dhcp_get_option_ptr(). |
| * |
| * @return byte value at the given address. |
| */ |
| static u32_t dhcp_get_option_long(u8_t *ptr) |
| { |
| u32_t value; |
| value = (u32_t)(*ptr++) << 24; |
| value |= (u32_t)(*ptr++) << 16; |
| value |= (u32_t)(*ptr++) << 8; |
| value |= (u32_t)(*ptr++); |
| DEBUGF(DHCP_DEBUG, ("option long value=%lu", value)); |
| return value; |
| } |
| |
| /** |
| * Find the DHCP client attached to a network interface. |
| * |
| * Given an network interface, return the corresponding dhcp state |
| * or NULL if the interface was not under DHCP control. |
| */ |
| struct dhcp_state *dhcp_find_client(struct netif *netif) |
| { |
| struct dhcp_state *state = NULL; |
| struct dhcp_state *list_state = client_list; |
| |
| DEBUGF(DHCP_DEBUG, ("dhcp_find_client()")); |
| while ((state == NULL) && (list_state != NULL)) |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_find_client(): checking state %p", list_state)); |
| // this interface already has a DHCP client attached |
| if (list_state->netif == netif) |
| { |
| state = list_state; |
| DEBUGF(DHCP_DEBUG, ("dhcp_find_client(): interface already under DHCP control")); |
| } |
| if (list_state->next != NULL) |
| { |
| // select the next client state |
| list_state = list_state->next; |
| } |
| // reached end of list |
| else |
| { |
| DEBUGF(DHCP_DEBUG, ("dhcp_find_client(): end of list reached")); |
| break; |
| // { state == NULL } |
| // { list_state is last item in list } |
| } |
| } |
| return state; |
| } |