diff --git a/src/core/dhcp.c b/src/core/dhcp.c new file mode 100644 index 00000000..89458329 --- /dev/null +++ b/src/core/dhcp.c @@ -0,0 +1,1317 @@ +/* + * Copyright (c) 2001, 2002 Leon Woestenberg + * Copyright (c) 2001, 2002 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 + * + * 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. + * + * DONE: + * - Obtaining renewing, rebinding a lease. + * - ARP check on the offered address, and address declines on in-use + * addresses. (Can be disabled). + * - Reobtaining a lease + * - Concurrent multiple interface configuration and lease maintenance + * - A periodical polling timer method and a callback timer approach + * TODO: + * - Add JavaDoc style documentation (API, internals). + * - this client will fail on servers using file/sname field overloading + * - make the unfold routine smarter to handle this + * - Support for interfaces other than Ethernet (SLIP, PPP, ...) + * - ... + * + * Please coordinate changes and requests with Leon Woestenberg + * + * + * 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) + * to start the dhcp client to configure the interface by + * obtaining an IP address lease and maintaining it. + * + * Use dhcp_stop(client) to stop the lease. + * + */ +#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" + +/// transaction identifier, unique over all DHCP client and 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, process 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); +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); +void dhcp_arp_reply(struct ip_addr *addr); + +// 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); + +// find a client in the singly-linked list of DHCP clients +struct dhcp_state *dhcp_find_client(struct netif *netif); + +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(): request timeout %u msecs", msecs)); + dhcp_set_state(state, DHCP_BACKING_OFF); +} + +// checks if the offered address is already in use +// it does so by sending an ARP query +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); + + //return /*result*/; + } + state->tries++; + msecs = state->tries * 1000; + state->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; + DEBUGF(DHCP_DEBUG, ("dhcp_check(): request timeout %u msecs", msecs)); + dhcp_set_state(state, DHCP_CHECKING); +} + +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, ignore others + */ +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(): request timeout %u msecs", msecs)); + dhcp_set_state(state, DHCP_REQUESTING); + return result; +} + +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; + } +} + +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; + } +} + +// something has timed out, handle this +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 + { + struct netif *netif = state->netif; + 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 + { + struct netif *netif = state->netif; + DEBUGF(DHCP_DEBUG, ("dhcp_timeout(): REBINDING, release, restart")); + dhcp_release(state); + dhcp_discover(state); + } + } +} + +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); + } +} +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; + state->offered_t0_lease; + state->offered_t1_renew; + state->offered_t2_rebind; + 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; + } + + 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); + } + 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); + } + ip_addr_set(&state->offered_ip_addr, &state->msg_in->yiaddr); + + 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])); + } + + 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])); + } + + 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()")); +} + +/** + * Start DHCP negotiation for a network interface. + * + * If no DHCP client instance was attached to this interface, + * a new client is created first. + */ +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; + } + bzero(state, 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 == NULL } + list_state->next = state; + } + dhcp_discover(state); + return state; +} + +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; + } + bzero(state, 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(): request timeout %u msecs", msecs)); + return result; +} +#endif + + +/** + * Start the DHCP process, discover a 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(): 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); + + if (state->offered_t1_renew != 0xffffffffUL) + { + 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(): request timeout %u msecs", state->offered_t1_renew*1000)); + } + 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(): 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); +} + + + +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(): request timeout %u msecs", msecs)); + return result; +} + +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(): request timeout %u msecs", msecs)); + return result; +} + +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(): 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; +} + +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; + } +} + +// 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; +} + +static u8_t dhcp_get_option_byte(u8_t *ptr) +{ + DEBUGF(DHCP_DEBUG, ("option byte value=%u", *ptr)); + return *ptr; +} + +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; +} + +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; + err_t result = ERR_OK; + + 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; +} \ No newline at end of file diff --git a/src/include/lwip/dhcp.h b/src/include/lwip/dhcp.h new file mode 100644 index 00000000..745592f6 --- /dev/null +++ b/src/include/lwip/dhcp.h @@ -0,0 +1,194 @@ +/** @file + */ + +#ifndef __LWIP_DHCP_H__ +#define __LWIP_DHCP_H__ + +#include "udp.h" +#include "lwip/netif.h" +#include "lwipopts.h" + +// period (in seconds) of the application calling dhcp_coarse_tmr() +#define DHCP_COARSE_TIMER_SECS 60 +// period (in milliseconds) of the application calling dhcp_fine_tmr() +#define DHCP_FINE_TIMER_MSECS 500 + +struct dhcp_state +{ + struct dhcp_state *next; // for linked list purposes + u8_t state; // current DHCP state (of DHCP state machine) + u8_t tries; // retries of current request + u32_t xid; // id of last sent request + struct netif *netif; // interface to be configured + struct udp_pcb *pcb; // our connection + + struct pbuf *p; // (first) pbuf of incoming msg + struct dhcp_msg *msg_in; // incoming msg + struct dhcp_msg *options_in; // incoming msg options + u16_t options_in_len; // ingoing msg options length + + struct pbuf *p_out; // pbuf of outcoming msg + struct dhcp_msg *msg_out; // outgoing msg + u16_t options_out_len; // outgoing msg options length + u16_t request_timeout; // #ticks with period DHCP_FINE_TIMER_SECS for request timeout + u16_t t1_timeout; // #ticks with period DHCP_COARSE_TIMER_SECS for renewal time + u16_t t2_timeout; // #ticks with period DHCP_COARSE_TIMER_SECS for rebind time + struct ip_addr server_ip_addr; // dhcp server address that offered this lease + struct ip_addr offered_ip_addr; + struct ip_addr offered_sn_mask; + struct ip_addr offered_gw_addr; + struct ip_addr offered_bc_addr; + u32_t offered_t0_lease; // lease period (in seconds) + u32_t offered_t1_renew; // recommended renew time (usually 50% of lease period) + u32_t offered_t2_rebind; // recommended rebind time (usually 66% of lease period) +}; + +// always present fields of any DHCP message +struct dhcp_msg +{ + u8_t op; + u8_t htype; + u8_t hlen; + u8_t hops; + u32_t xid; + u16_t secs; + u16_t flags; + u32_t ciaddr; + u32_t yiaddr; + u32_t siaddr; + u32_t giaddr; +#define DHCP_CHADDR_LEN 16U + u8_t chaddr[DHCP_CHADDR_LEN]; +#define DHCP_SNAME_LEN 64U + u8_t sname[DHCP_SNAME_LEN]; +#define DHCP_FILE_LEN 128U + u8_t file[DHCP_FILE_LEN]; + u32_t cookie; +#define DHCP_MIN_OPTIONS_LEN 68U +// allow this to be configured in lwipopts.h, but not too small +#if ((!defined(DHCP_OPTIONS_LEN)) || (DHCP_OPTIONS_LEN < DHCP_MIN_OPTIONS_LEN)) +// set this to be sufficient for your options in outgoing DHCP msgs +# define DHCP_OPTIONS_LEN DHCP_MIN_OPTIONS_LEN +#endif + u8_t options[DHCP_OPTIONS_LEN]; +}; + +// initialize DHCP client +void dhcp_init(void); +// start DHCP configuration +struct dhcp_state *dhcp_start(struct netif *netif); +// stop DHCP configuration +void dhcp_stop(struct dhcp_state *state); +// enforce lease renewal +err_t dhcp_renew(struct dhcp_state *state); +// inform server of our IP address +void dhcp_inform(struct netif *netif); + +// if enabled, check whether the offered IP address is not in use, using ARP +#if DHCP_DOES_ARP_CHECK +void dhcp_arp_reply(struct ip_addr *addr); +#endif +// to be called regularly +#if (DHCP_TIMER_CALLBACKS == 0) +// to be called every minute +void dhcp_coarse_tmr(void); +// to be called every half second +void dhcp_fine_tmr(void); +#endif + +// DHCP message item offsets and length +#define DHCP_MSG_OFS (UDP_DATA_OFS) + #define DHCP_OP_OFS (DHCP_MSG_OFS + 0) + #define DHCP_HTYPE_OFS (DHCP_MSG_OFS + 1) + #define DHCP_HLEN_OFS (DHCP_MSG_OFS + 2) + #define DHCP_HOPS_OFS (DHCP_MSG_OFS + 3) + #define DHCP_XID_OFS (DHCP_MSG_OFS + 4) + #define DHCP_SECS_OFS (DHCP_MSG_OFS + 8) + #define DHCP_FLAGS_OFS (DHCP_MSG_OFS + 10) + #define DHCP_CIADDR_OFS (DHCP_MSG_OFS + 12) + #define DHCP_YIADDR_OFS (DHCP_MSG_OFS + 16) + #define DHCP_SIADDR_OFS (DHCP_MSG_OFS + 20) + #define DHCP_GIADDR_OFS (DHCP_MSG_OFS + 24) + #define DHCP_CHADDR_OFS (DHCP_MSG_OFS + 28) + #define DHCP_SNAME_OFS (DHCP_MSG_OFS + 44) + #define DHCP_FILE_OFS (DHCP_MSG_OFS + 108) +#define DHCP_MSG_LEN 236 + +#define DHCP_COOKIE_OFS (DHCP_MSG_OFS + DHCP_MSG_LEN) +#define DHCP_OPTIONS_OFS (DHCP_MSG_OFS + DHCP_MSG_LEN + 4) + +#define DHCP_CLIENT_PORT 68 +#define DHCP_SERVER_PORT 67 + +// DHCP client states +#define DHCP_REQUESTING 1 +#define DHCP_INIT 2 +#define DHCP_REBOOTING 3 +#define DHCP_REBINDING 4 +#define DHCP_RENEWING 5 +#define DHCP_SELECTING 6 +#define DHCP_INFORMING 7 +#define DHCP_CHECKING 8 +#define DHCP_PERMANENT 9 +#define DHCP_BOUND 10 +#define DHCP_BACKING_OFF 11 +#define DHCP_OFF 12 + +#define DHCP_BOOTREQUEST 1 +#define DHCP_BOOTREPLY 2 + +#define DHCP_DISCOVER 1 +#define DHCP_OFFER 2 +#define DHCP_REQUEST 3 +#define DHCP_DECLINE 4 +#define DHCP_ACK 5 +#define DHCP_NAK 6 +#define DHCP_RELEASE 7 +#define DHCP_INFORM 8 + +#define DHCP_HTYPE_ETH 1 + +#define DHCP_HLEN_ETH 6 + +#define DHCP_BROADCAST_FLAG 15 +#define DHCP_BROADCAST_MASK (1 << DHCP_FLAG_BROADCAST) + +// BootP options +#define DHCP_OPTION_PAD 0 +#define DHCP_OPTION_SUBNET_MASK 1 // RFC 2132 3.3 +#define DHCP_OPTION_ROUTER 3 +#define DHCP_OPTION_HOSTNAME 12 +#define DHCP_OPTION_IP_TTL 23 +#define DHCP_OPTION_MTU 26 +#define DHCP_OPTION_BROADCAST 28 +#define DHCP_OPTION_TCP_TTL 37 +#define DHCP_OPTION_END 255 + +// DHCP options +#define DHCP_OPTION_REQUESTED_IP 50 // RFC 2132 9.1, requested IP address +#define DHCP_OPTION_LEASE_TIME 51 // RFC 2132 9.2, time in seconds, in 4 bytes +#define DHCP_OPTION_OVERLOAD 52 // RFC2132 9.3, use file and/or sname field for options + +#define DHCP_OPTION_MESSAGE_TYPE 53 // RFC 2132 9.6, important for DHCP +#define DHCP_OPTION_MESSAGE_TYPE_LEN 1 + + +#define DHCP_OPTION_SERVER_ID 54 // RFC 2131 9.7, server IP address +#define DHCP_OPTION_PARAMETER_REQUEST_LIST 55 // RFC 2131 9.8, requested option types + +#define DHCP_OPTION_MAX_MSG_SIZE 57 // RFC 2131 9.10, message size accepted >= 576 +#define DHCP_OPTION_MAX_MSG_SIZE_LEN 2 + +#define DHCP_OPTION_T1 58 // T1 renewal time +#define DHCP_OPTION_T2 59 // T2 rebinding time +#define DHCP_OPTION_CLIENT_ID 61 +#define DHCP_OPTION_TFTP_SERVERNAME 66 +#define DHCP_OPTION_BOOTFILE 67 + +// possible combinations of overloading the file and sname fields with options +#define DHCP_OVERLOAD_NONE 0 +#define DHCP_OVERLOAD_FILE 1 +#define DHCP_OVERLOAD_SNAME 2 +#define DHCP_OVERLOAD_SNAME_FILE 3 + +#endif //__LWIP_DHCP_H__