blob: 902c61ec542a02a57a5cd65b368a78eb3a991566 [file] [log] [blame]
/*
* 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;
}