summaryrefslogtreecommitdiff
path: root/lwip/src/core/ipv6/dhcp6.c
diff options
context:
space:
mode:
Diffstat (limited to 'lwip/src/core/ipv6/dhcp6.c')
-rw-r--r--lwip/src/core/ipv6/dhcp6.c812
1 files changed, 812 insertions, 0 deletions
diff --git a/lwip/src/core/ipv6/dhcp6.c b/lwip/src/core/ipv6/dhcp6.c
new file mode 100644
index 0000000..7cf98a5
--- /dev/null
+++ b/lwip/src/core/ipv6/dhcp6.c
@@ -0,0 +1,812 @@
+/**
+ * @file
+ *
+ * @defgroup dhcp6 DHCPv6
+ * @ingroup ip6
+ * DHCPv6 client: IPv6 address autoconfiguration as per
+ * RFC 3315 (stateful DHCPv6) and
+ * RFC 3736 (stateless DHCPv6).
+ *
+ * For now, only stateless DHCPv6 is implemented!
+ *
+ * TODO:
+ * - enable/disable API to not always start when RA is received
+ * - stateful DHCPv6 (for now, only stateless DHCPv6 for DNS and NTP servers works)
+ * - create Client Identifier?
+ * - only start requests if a valid local address is available on the netif
+ * - only start information requests if required (not for every RA)
+ *
+ * dhcp6_enable_stateful() enables stateful DHCPv6 for a netif (stateless disabled)\n
+ * dhcp6_enable_stateless() enables stateless DHCPv6 for a netif (stateful disabled)\n
+ * dhcp6_disable() disable DHCPv6 for a netif
+ *
+ * When enabled, requests are only issued after receipt of RA with the
+ * corresponding bits set.
+ */
+
+/*
+ * Copyright (c) 2018 Simon Goldschmidt
+ * 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 part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt <goldsimon@gmx.de>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6 && LWIP_IPV6_DHCP6 /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/dhcp6.h"
+#include "lwip/prot/dhcp6.h"
+#include "lwip/def.h"
+#include "lwip/udp.h"
+#include "lwip/dns.h"
+
+#include <string.h>
+
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+#ifndef LWIP_HOOK_DHCP6_APPEND_OPTIONS
+#define LWIP_HOOK_DHCP6_APPEND_OPTIONS(netif, dhcp6, state, msg, msg_type, options_len_ptr, max_len)
+#endif
+#ifndef LWIP_HOOK_DHCP6_PARSE_OPTION
+#define LWIP_HOOK_DHCP6_PARSE_OPTION(netif, dhcp6, state, msg, msg_type, option, len, pbuf, offset) do { LWIP_UNUSED_ARG(msg); } while(0)
+#endif
+
+#if LWIP_DNS && LWIP_DHCP6_MAX_DNS_SERVERS
+#if DNS_MAX_SERVERS > LWIP_DHCP6_MAX_DNS_SERVERS
+#define LWIP_DHCP6_PROVIDE_DNS_SERVERS LWIP_DHCP6_MAX_DNS_SERVERS
+#else
+#define LWIP_DHCP6_PROVIDE_DNS_SERVERS DNS_MAX_SERVERS
+#endif
+#else
+#define LWIP_DHCP6_PROVIDE_DNS_SERVERS 0
+#endif
+
+
+/** Option handling: options are parsed in dhcp6_parse_reply
+ * and saved in an array where other functions can load them from.
+ * This might be moved into the struct dhcp6 (not necessarily since
+ * lwIP is single-threaded and the array is only used while in recv
+ * callback). */
+enum dhcp6_option_idx {
+ DHCP6_OPTION_IDX_CLI_ID = 0,
+ DHCP6_OPTION_IDX_SERVER_ID,
+#if LWIP_DHCP6_PROVIDE_DNS_SERVERS
+ DHCP6_OPTION_IDX_DNS_SERVER,
+ DHCP6_OPTION_IDX_DOMAIN_LIST,
+#endif /* LWIP_DHCP_PROVIDE_DNS_SERVERS */
+#if LWIP_DHCP6_GET_NTP_SRV
+ DHCP6_OPTION_IDX_NTP_SERVER,
+#endif /* LWIP_DHCP_GET_NTP_SRV */
+ DHCP6_OPTION_IDX_MAX
+};
+
+struct dhcp6_option_info {
+ u8_t option_given;
+ u16_t val_start;
+ u16_t val_length;
+};
+
+/** Holds the decoded option info, only valid while in dhcp6_recv. */
+struct dhcp6_option_info dhcp6_rx_options[DHCP6_OPTION_IDX_MAX];
+
+#define dhcp6_option_given(dhcp6, idx) (dhcp6_rx_options[idx].option_given != 0)
+#define dhcp6_got_option(dhcp6, idx) (dhcp6_rx_options[idx].option_given = 1)
+#define dhcp6_clear_option(dhcp6, idx) (dhcp6_rx_options[idx].option_given = 0)
+#define dhcp6_clear_all_options(dhcp6) (memset(dhcp6_rx_options, 0, sizeof(dhcp6_rx_options)))
+#define dhcp6_get_option_start(dhcp6, idx) (dhcp6_rx_options[idx].val_start)
+#define dhcp6_get_option_length(dhcp6, idx) (dhcp6_rx_options[idx].val_length)
+#define dhcp6_set_option(dhcp6, idx, start, len) do { dhcp6_rx_options[idx].val_start = (start); dhcp6_rx_options[idx].val_length = (len); }while(0)
+
+
+const ip_addr_t dhcp6_All_DHCP6_Relay_Agents_and_Servers = IPADDR6_INIT_HOST(0xFF020000, 0, 0, 0x00010002);
+const ip_addr_t dhcp6_All_DHCP6_Servers = IPADDR6_INIT_HOST(0xFF020000, 0, 0, 0x00010003);
+
+static struct udp_pcb *dhcp6_pcb;
+static u8_t dhcp6_pcb_refcount;
+
+
+/* receive, unfold, parse and free incoming messages */
+static void dhcp6_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);
+
+/** Ensure DHCP PCB is allocated and bound */
+static err_t
+dhcp6_inc_pcb_refcount(void)
+{
+ if (dhcp6_pcb_refcount == 0) {
+ LWIP_ASSERT("dhcp6_inc_pcb_refcount(): memory leak", dhcp6_pcb == NULL);
+
+ /* allocate UDP PCB */
+ dhcp6_pcb = udp_new_ip6();
+
+ if (dhcp6_pcb == NULL) {
+ return ERR_MEM;
+ }
+
+ ip_set_option(dhcp6_pcb, SOF_BROADCAST);
+
+ /* set up local and remote port for the pcb -> listen on all interfaces on all src/dest IPs */
+ udp_bind(dhcp6_pcb, IP6_ADDR_ANY, DHCP6_CLIENT_PORT);
+ udp_recv(dhcp6_pcb, dhcp6_recv, NULL);
+ }
+
+ dhcp6_pcb_refcount++;
+
+ return ERR_OK;
+}
+
+/** Free DHCP PCB if the last netif stops using it */
+static void
+dhcp6_dec_pcb_refcount(void)
+{
+ LWIP_ASSERT("dhcp6_pcb_refcount(): refcount error", (dhcp6_pcb_refcount > 0));
+ dhcp6_pcb_refcount--;
+
+ if (dhcp6_pcb_refcount == 0) {
+ udp_remove(dhcp6_pcb);
+ dhcp6_pcb = NULL;
+ }
+}
+
+/**
+ * @ingroup dhcp6
+ * Set a statically allocated struct dhcp6 to work with.
+ * Using this prevents dhcp6_start to allocate it using mem_malloc.
+ *
+ * @param netif the netif for which to set the struct dhcp
+ * @param dhcp6 (uninitialised) dhcp6 struct allocated by the application
+ */
+void
+dhcp6_set_struct(struct netif *netif, struct dhcp6 *dhcp6)
+{
+ LWIP_ASSERT("netif != NULL", netif != NULL);
+ LWIP_ASSERT("dhcp6 != NULL", dhcp6 != NULL);
+ LWIP_ASSERT("netif already has a struct dhcp6 set", netif_dhcp6_data(netif) == NULL);
+
+ /* clear data structure */
+ memset(dhcp6, 0, sizeof(struct dhcp6));
+ /* dhcp6_set_state(&dhcp, DHCP6_STATE_OFF); */
+ netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6, dhcp6);
+}
+
+/**
+ * @ingroup dhcp6
+ * Removes a struct dhcp6 from a netif.
+ *
+ * ATTENTION: Only use this when not using dhcp6_set_struct() to allocate the
+ * struct dhcp6 since the memory is passed back to the heap.
+ *
+ * @param netif the netif from which to remove the struct dhcp
+ */
+void dhcp6_cleanup(struct netif *netif)
+{
+ LWIP_ASSERT("netif != NULL", netif != NULL);
+
+ if (netif_dhcp6_data(netif) != NULL) {
+ mem_free(netif_dhcp6_data(netif));
+ netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6, NULL);
+ }
+}
+
+static struct dhcp6*
+dhcp6_get_struct(struct netif *netif, const char *dbg_requester)
+{
+ struct dhcp6 *dhcp6 = netif_dhcp6_data(netif);
+ if (dhcp6 == NULL) {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("%s: mallocing new DHCPv6 client\n", dbg_requester));
+ dhcp6 = (struct dhcp6 *)mem_malloc(sizeof(struct dhcp6));
+ if (dhcp6 == NULL) {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("%s: could not allocate dhcp6\n", dbg_requester));
+ return NULL;
+ }
+
+ /* clear data structure, this implies DHCP6_STATE_OFF */
+ memset(dhcp6, 0, sizeof(struct dhcp6));
+ /* store this dhcp6 client in the netif */
+ netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6, dhcp6);
+ } else {
+ /* already has DHCP6 client attached */
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("%s: using existing DHCPv6 client\n", dbg_requester));
+ }
+
+ if (!dhcp6->pcb_allocated) {
+ if (dhcp6_inc_pcb_refcount() != ERR_OK) { /* ensure DHCP6 PCB is allocated */
+ mem_free(dhcp6);
+ netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6, NULL);
+ return NULL;
+ }
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("%s: allocated dhcp6", dbg_requester));
+ dhcp6->pcb_allocated = 1;
+ }
+ return dhcp6;
+}
+
+/*
+ * Set the DHCPv6 state
+ * If the state changed, reset the number of tries.
+ */
+static void
+dhcp6_set_state(struct dhcp6 *dhcp6, u8_t new_state, const char *dbg_caller)
+{
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("DHCPv6 state: %d -> %d (%s)\n",
+ dhcp6->state, new_state, dbg_caller));
+ if (new_state != dhcp6->state) {
+ dhcp6->state = new_state;
+ dhcp6->tries = 0;
+ dhcp6->request_timeout = 0;
+ }
+}
+
+static int
+dhcp6_stateless_enabled(struct dhcp6 *dhcp6)
+{
+ if ((dhcp6->state == DHCP6_STATE_STATELESS_IDLE) ||
+ (dhcp6->state == DHCP6_STATE_REQUESTING_CONFIG)) {
+ return 1;
+ }
+ return 0;
+}
+
+/*static int
+dhcp6_stateful_enabled(struct dhcp6 *dhcp6)
+{
+ if (dhcp6->state == DHCP6_STATE_OFF) {
+ return 0;
+ }
+ if (dhcp6_stateless_enabled(dhcp6)) {
+ return 0;
+ }
+ return 1;
+}*/
+
+/**
+ * @ingroup dhcp6
+ * Enable stateful DHCPv6 on this netif
+ * Requests are sent on receipt of an RA message with the
+ * ND6_RA_FLAG_MANAGED_ADDR_CONFIG flag set.
+ *
+ * A struct dhcp6 will be allocated for this netif if not
+ * set via @ref dhcp6_set_struct before.
+ *
+ * @todo: stateful DHCPv6 not supported, yet
+ */
+err_t
+dhcp6_enable_stateful(struct netif *netif)
+{
+ LWIP_UNUSED_ARG(netif);
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("stateful dhcp6 not implemented yet"));
+ return ERR_VAL;
+}
+
+/**
+ * @ingroup dhcp6
+ * Enable stateless DHCPv6 on this netif
+ * Requests are sent on receipt of an RA message with the
+ * ND6_RA_FLAG_OTHER_CONFIG flag set.
+ *
+ * A struct dhcp6 will be allocated for this netif if not
+ * set via @ref dhcp6_set_struct before.
+ */
+err_t
+dhcp6_enable_stateless(struct netif *netif)
+{
+ struct dhcp6 *dhcp6;
+
+ LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp6_enable_stateless(netif=%p) %c%c%"U16_F"\n", (void *)netif, netif->name[0], netif->name[1], (u16_t)netif->num));
+
+ dhcp6 = dhcp6_get_struct(netif, "dhcp6_enable_stateless()");
+ if (dhcp6 == NULL) {
+ return ERR_MEM;
+ }
+ if (dhcp6_stateless_enabled(dhcp6)) {
+ LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp6_enable_stateless(): stateless DHCPv6 already enabled"));
+ return ERR_OK;
+ } else if (dhcp6->state != DHCP6_STATE_OFF) {
+ /* stateful running */
+ /* @todo: stop stateful once it is implemented */
+ LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp6_enable_stateless(): switching from stateful to stateless DHCPv6"));
+ }
+ LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp6_enable_stateless(): stateless DHCPv6 enabled\n"));
+ dhcp6_set_state(dhcp6, DHCP6_STATE_STATELESS_IDLE, "dhcp6_enable_stateless");
+ return ERR_OK;
+}
+
+/**
+ * @ingroup dhcp6
+ * Disable stateful or stateless DHCPv6 on this netif
+ * Requests are sent on receipt of an RA message with the
+ * ND6_RA_FLAG_OTHER_CONFIG flag set.
+ */
+void
+dhcp6_disable(struct netif *netif)
+{
+ struct dhcp6 *dhcp6;
+
+ LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp6_disable(netif=%p) %c%c%"U16_F"\n", (void *)netif, netif->name[0], netif->name[1], (u16_t)netif->num));
+
+ dhcp6 = netif_dhcp6_data(netif);
+ if (dhcp6 != NULL) {
+ if (dhcp6->state != DHCP6_STATE_OFF) {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_disable(): DHCPv6 disabled (old state: %s)\n",
+ (dhcp6_stateless_enabled(dhcp6) ? "stateless" : "stateful")));
+ dhcp6_set_state(dhcp6, DHCP6_STATE_OFF, "dhcp6_disable");
+ if (dhcp6->pcb_allocated != 0) {
+ dhcp6_dec_pcb_refcount(); /* free DHCPv6 PCB if not needed any more */
+ dhcp6->pcb_allocated = 0;
+ }
+ }
+ }
+}
+
+/**
+ * Create a DHCPv6 request, fill in common headers
+ *
+ * @param netif the netif under DHCPv6 control
+ * @param dhcp6 dhcp6 control struct
+ * @param message_type message type of the request
+ * @param opt_len_alloc option length to allocate
+ * @param options_out_len option length on exit
+ * @return a pbuf for the message
+ */
+static struct pbuf *
+dhcp6_create_msg(struct netif *netif, struct dhcp6 *dhcp6, u8_t message_type,
+ u16_t opt_len_alloc, u16_t *options_out_len)
+{
+ struct pbuf *p_out;
+ struct dhcp6_msg *msg_out;
+
+ LWIP_ERROR("dhcp6_create_msg: netif != NULL", (netif != NULL), return NULL;);
+ LWIP_ERROR("dhcp6_create_msg: dhcp6 != NULL", (dhcp6 != NULL), return NULL;);
+ p_out = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct dhcp6_msg) + opt_len_alloc, PBUF_RAM);
+ if (p_out == NULL) {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+ ("dhcp6_create_msg(): could not allocate pbuf\n"));
+ return NULL;
+ }
+ LWIP_ASSERT("dhcp6_create_msg: check that first pbuf can hold struct dhcp6_msg",
+ (p_out->len >= sizeof(struct dhcp6_msg) + opt_len_alloc));
+
+ /* @todo: limit new xid for certain message types? */
+ /* reuse transaction identifier in retransmissions */
+ if (dhcp6->tries == 0) {
+ dhcp6->xid = LWIP_RAND() & 0xFFFFFF;
+ }
+
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE,
+ ("transaction id xid(%"X32_F")\n", dhcp6->xid));
+
+ msg_out = (struct dhcp6_msg *)p_out->payload;
+ memset(msg_out, 0, sizeof(struct dhcp6_msg) + opt_len_alloc);
+
+ msg_out->msgtype = message_type;
+ msg_out->transaction_id[0] = (u8_t)(dhcp6->xid >> 16);
+ msg_out->transaction_id[1] = (u8_t)(dhcp6->xid >> 8);
+ msg_out->transaction_id[2] = (u8_t)dhcp6->xid;
+ *options_out_len = 0;
+ return p_out;
+}
+
+static u16_t
+dhcp6_option_short(u16_t options_out_len, u8_t *options, u16_t value)
+{
+ options[options_out_len++] = (u8_t)((value & 0xff00U) >> 8);
+ options[options_out_len++] = (u8_t) (value & 0x00ffU);
+ return options_out_len;
+}
+
+static u16_t
+dhcp6_option_optionrequest(u16_t options_out_len, u8_t *options, const u16_t *req_options,
+ u16_t num_req_options, u16_t max_len)
+{
+ size_t i;
+ u16_t ret;
+
+ LWIP_ASSERT("dhcp6_option_optionrequest: options_out_len + sizeof(struct dhcp6_msg) + addlen <= max_len",
+ sizeof(struct dhcp6_msg) + options_out_len + 4U + (2U * num_req_options) <= max_len);
+ LWIP_UNUSED_ARG(max_len);
+
+ ret = dhcp6_option_short(options_out_len, options, DHCP6_OPTION_ORO);
+ ret = dhcp6_option_short(ret, options, 2 * num_req_options);
+ for (i = 0; i < num_req_options; i++) {
+ ret = dhcp6_option_short(ret, options, req_options[i]);
+ }
+ return ret;
+}
+
+/* All options are added, shrink the pbuf to the required size */
+static void
+dhcp6_msg_finalize(u16_t options_out_len, struct pbuf *p_out)
+{
+ /* shrink the pbuf to the actual content length */
+ pbuf_realloc(p_out, (u16_t)(sizeof(struct dhcp6_msg) + options_out_len));
+}
+
+
+#if LWIP_IPV6_DHCP6_STATELESS
+static void
+dhcp6_information_request(struct netif *netif, struct dhcp6 *dhcp6)
+{
+ const u16_t requested_options[] = {DHCP6_OPTION_DNS_SERVERS, DHCP6_OPTION_DOMAIN_LIST, DHCP6_OPTION_SNTP_SERVERS};
+ u16_t msecs;
+ struct pbuf *p_out;
+ u16_t options_out_len;
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_information_request()\n"));
+ /* create and initialize the DHCP message header */
+ p_out = dhcp6_create_msg(netif, dhcp6, DHCP6_INFOREQUEST, 4 + sizeof(requested_options), &options_out_len);
+ if (p_out != NULL) {
+ err_t err;
+ struct dhcp6_msg *msg_out = (struct dhcp6_msg *)p_out->payload;
+ u8_t *options = (u8_t *)(msg_out + 1);
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_information_request: making request\n"));
+
+ options_out_len = dhcp6_option_optionrequest(options_out_len, options, requested_options,
+ LWIP_ARRAYSIZE(requested_options), p_out->len);
+ LWIP_HOOK_DHCP6_APPEND_OPTIONS(netif, dhcp6, DHCP6_STATE_REQUESTING_CONFIG, msg_out,
+ DHCP6_INFOREQUEST, options_out_len, p_out->len);
+ dhcp6_msg_finalize(options_out_len, p_out);
+
+ err = udp_sendto_if(dhcp6_pcb, p_out, &dhcp6_All_DHCP6_Relay_Agents_and_Servers, DHCP6_SERVER_PORT, netif);
+ pbuf_free(p_out);
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp6_information_request: INFOREQUESTING -> %d\n", (int)err));
+ LWIP_UNUSED_ARG(err);
+ } else {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp6_information_request: could not allocate DHCP6 request\n"));
+ }
+ dhcp6_set_state(dhcp6, DHCP6_STATE_REQUESTING_CONFIG, "dhcp6_information_request");
+ if (dhcp6->tries < 255) {
+ dhcp6->tries++;
+ }
+ msecs = (u16_t)((dhcp6->tries < 6 ? 1 << dhcp6->tries : 60) * 1000);
+ dhcp6->request_timeout = (u16_t)((msecs + DHCP6_TIMER_MSECS - 1) / DHCP6_TIMER_MSECS);
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp6_information_request(): set request timeout %"U16_F" msecs\n", msecs));
+}
+
+static err_t
+dhcp6_request_config(struct netif *netif, struct dhcp6 *dhcp6)
+{
+ /* stateless mode enabled and no request running? */
+ if (dhcp6->state == DHCP6_STATE_STATELESS_IDLE) {
+ /* send Information-request and wait for answer; setup receive timeout */
+ dhcp6_information_request(netif, dhcp6);
+ }
+
+ return ERR_OK;
+}
+
+static void
+dhcp6_abort_config_request(struct dhcp6 *dhcp6)
+{
+ if (dhcp6->state == DHCP6_STATE_REQUESTING_CONFIG) {
+ /* abort running request */
+ dhcp6_set_state(dhcp6, DHCP6_STATE_STATELESS_IDLE, "dhcp6_abort_config_request");
+ }
+}
+
+/* Handle a REPLY to INFOREQUEST
+ * This parses DNS and NTP server addresses from the reply.
+ */
+static void
+dhcp6_handle_config_reply(struct netif *netif, struct pbuf *p_msg_in)
+{
+ struct dhcp6 *dhcp6 = netif_dhcp6_data(netif);
+
+ LWIP_UNUSED_ARG(dhcp6);
+ LWIP_UNUSED_ARG(p_msg_in);
+
+#if LWIP_DHCP6_PROVIDE_DNS_SERVERS
+ if (dhcp6_option_given(dhcp6, DHCP6_OPTION_IDX_DNS_SERVER)) {
+ ip_addr_t dns_addr;
+ ip6_addr_t *dns_addr6;
+ u16_t op_start = dhcp6_get_option_start(dhcp6, DHCP6_OPTION_IDX_DNS_SERVER);
+ u16_t op_len = dhcp6_get_option_length(dhcp6, DHCP6_OPTION_IDX_DNS_SERVER);
+ u16_t idx;
+ u8_t n;
+
+ memset(&dns_addr, 0, sizeof(dns_addr));
+ dns_addr6 = ip_2_ip6(&dns_addr);
+ for (n = 0, idx = op_start; (idx < op_start + op_len) && (n < LWIP_DHCP6_PROVIDE_DNS_SERVERS);
+ n++, idx += sizeof(struct ip6_addr_packed)) {
+ u16_t copied = pbuf_copy_partial(p_msg_in, dns_addr6, sizeof(struct ip6_addr_packed), idx);
+ if (copied != sizeof(struct ip6_addr_packed)) {
+ /* pbuf length mismatch */
+ return;
+ }
+ ip6_addr_assign_zone(dns_addr6, IP6_UNKNOWN, netif);
+ /* @todo: do we need a different offset than DHCP(v4)? */
+ dns_setserver(n, &dns_addr);
+ }
+ }
+ /* @ todo: parse and set Domain Search List */
+#endif /* LWIP_DHCP6_PROVIDE_DNS_SERVERS */
+
+#if LWIP_DHCP6_GET_NTP_SRV
+ if (dhcp6_option_given(dhcp6, DHCP6_OPTION_IDX_NTP_SERVER)) {
+ ip_addr_t ntp_server_addrs[LWIP_DHCP6_MAX_NTP_SERVERS];
+ u16_t op_start = dhcp6_get_option_start(dhcp6, DHCP6_OPTION_IDX_NTP_SERVER);
+ u16_t op_len = dhcp6_get_option_length(dhcp6, DHCP6_OPTION_IDX_NTP_SERVER);
+ u16_t idx;
+ u8_t n;
+
+ for (n = 0, idx = op_start; (idx < op_start + op_len) && (n < LWIP_DHCP6_MAX_NTP_SERVERS);
+ n++, idx += sizeof(struct ip6_addr_packed)) {
+ u16_t copied;
+ ip6_addr_t *ntp_addr6 = ip_2_ip6(&ntp_server_addrs[n]);
+ ip_addr_set_zero_ip6(&ntp_server_addrs[n]);
+ copied = pbuf_copy_partial(p_msg_in, ntp_addr6, sizeof(struct ip6_addr_packed), idx);
+ if (copied != sizeof(struct ip6_addr_packed)) {
+ /* pbuf length mismatch */
+ return;
+ }
+ ip6_addr_assign_zone(ntp_addr6, IP6_UNKNOWN, netif);
+ }
+ dhcp6_set_ntp_servers(n, ntp_server_addrs);
+ }
+#endif /* LWIP_DHCP6_GET_NTP_SRV */
+}
+#endif /* LWIP_IPV6_DHCP6_STATELESS */
+
+/** This function is called from nd6 module when an RA messsage is received
+ * It triggers DHCPv6 requests (if enabled).
+ */
+void
+dhcp6_nd6_ra_trigger(struct netif *netif, u8_t managed_addr_config, u8_t other_config)
+{
+ struct dhcp6 *dhcp6;
+
+ LWIP_ASSERT("netif != NULL", netif != NULL);
+ dhcp6 = netif_dhcp6_data(netif);
+
+ LWIP_UNUSED_ARG(managed_addr_config);
+ LWIP_UNUSED_ARG(other_config);
+ LWIP_UNUSED_ARG(dhcp6);
+
+#if LWIP_IPV6_DHCP6_STATELESS
+ if (dhcp6 != NULL) {
+ if (dhcp6_stateless_enabled(dhcp6)) {
+ if (other_config) {
+ dhcp6_request_config(netif, dhcp6);
+ } else {
+ dhcp6_abort_config_request(dhcp6);
+ }
+ }
+ }
+#endif /* LWIP_IPV6_DHCP6_STATELESS */
+}
+
+/**
+ * Parse the DHCPv6 message and extract the DHCPv6 options.
+ *
+ * Extract the DHCPv6 options (offset + length) so that we can later easily
+ * check for them or extract the contents.
+ */
+static err_t
+dhcp6_parse_reply(struct pbuf *p, struct dhcp6 *dhcp6)
+{
+ u16_t offset;
+ u16_t offset_max;
+ u16_t options_idx;
+ struct dhcp6_msg *msg_in;
+
+ LWIP_UNUSED_ARG(dhcp6);
+
+ /* clear received options */
+ dhcp6_clear_all_options(dhcp6);
+ msg_in = (struct dhcp6_msg *)p->payload;
+
+ /* parse options */
+
+ options_idx = sizeof(struct dhcp6_msg);
+ /* parse options to the end of the received packet */
+ offset_max = p->tot_len;
+
+ offset = options_idx;
+ /* at least 4 byte to read? */
+ while ((offset + 4 <= offset_max)) {
+ u8_t op_len_buf[4];
+ u8_t *op_len;
+ u16_t op;
+ u16_t len;
+ u16_t val_offset = (u16_t)(offset + 4);
+ if (val_offset < offset) {
+ /* overflow */
+ return ERR_BUF;
+ }
+ /* copy option + length, might be split accross pbufs */
+ op_len = (u8_t *)pbuf_get_contiguous(p, op_len_buf, 4, 4, offset);
+ if (op_len == NULL) {
+ /* failed to get option and length */
+ return ERR_VAL;
+ }
+ op = (op_len[0] << 8) | op_len[1];
+ len = (op_len[2] << 8) | op_len[3];
+ offset = val_offset + len;
+ if (offset < val_offset) {
+ /* overflow */
+ return ERR_BUF;
+ }
+
+ switch (op) {
+ case (DHCP6_OPTION_CLIENTID):
+ dhcp6_got_option(dhcp6, DHCP6_OPTION_IDX_CLI_ID);
+ dhcp6_set_option(dhcp6, DHCP6_OPTION_IDX_CLI_ID, val_offset, len);
+ break;
+ case (DHCP6_OPTION_SERVERID):
+ dhcp6_got_option(dhcp6, DHCP6_OPTION_IDX_SERVER_ID);
+ dhcp6_set_option(dhcp6, DHCP6_OPTION_IDX_SERVER_ID, val_offset, len);
+ break;
+#if LWIP_DHCP6_PROVIDE_DNS_SERVERS
+ case (DHCP6_OPTION_DNS_SERVERS):
+ dhcp6_got_option(dhcp6, DHCP6_OPTION_IDX_DNS_SERVER);
+ dhcp6_set_option(dhcp6, DHCP6_OPTION_IDX_DNS_SERVER, val_offset, len);
+ break;
+ case (DHCP6_OPTION_DOMAIN_LIST):
+ dhcp6_got_option(dhcp6, DHCP6_OPTION_IDX_DOMAIN_LIST);
+ dhcp6_set_option(dhcp6, DHCP6_OPTION_IDX_DOMAIN_LIST, val_offset, len);
+ break;
+#endif /* LWIP_DHCP6_PROVIDE_DNS_SERVERS */
+#if LWIP_DHCP6_GET_NTP_SRV
+ case (DHCP6_OPTION_SNTP_SERVERS):
+ dhcp6_got_option(dhcp6, DHCP6_OPTION_IDX_NTP_SERVER);
+ dhcp6_set_option(dhcp6, DHCP6_OPTION_IDX_NTP_SERVER, val_offset, len);
+ break;
+#endif /* LWIP_DHCP6_GET_NTP_SRV*/
+ default:
+ LWIP_DEBUGF(DHCP6_DEBUG, ("skipping option %"U16_F" in options\n", op));
+ LWIP_HOOK_DHCP6_PARSE_OPTION(ip_current_netif(), dhcp6, dhcp6->state, msg_in,
+ msg_in->msgtype, op, len, q, val_offset);
+ break;
+ }
+ }
+ return ERR_OK;
+}
+
+static void
+dhcp6_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+ struct netif *netif = ip_current_input_netif();
+ struct dhcp6 *dhcp6 = netif_dhcp6_data(netif);
+ struct dhcp6_msg *reply_msg = (struct dhcp6_msg *)p->payload;
+ u8_t msg_type;
+ u32_t xid;
+
+ LWIP_UNUSED_ARG(arg);
+
+ /* Caught DHCPv6 message from netif that does not have DHCPv6 enabled? -> not interested */
+ if ((dhcp6 == NULL) || (dhcp6->pcb_allocated == 0)) {
+ goto free_pbuf_and_return;
+ }
+
+ LWIP_ERROR("invalid server address type", IP_IS_V6(addr), goto free_pbuf_and_return;);
+
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_recv(pbuf = %p) from DHCPv6 server %s port %"U16_F"\n", (void *)p,
+ ipaddr_ntoa(addr), port));
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("pbuf->len = %"U16_F"\n", p->len));
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("pbuf->tot_len = %"U16_F"\n", p->tot_len));
+ /* prevent warnings about unused arguments */
+ LWIP_UNUSED_ARG(pcb);
+ LWIP_UNUSED_ARG(addr);
+ LWIP_UNUSED_ARG(port);
+
+ if (p->len < sizeof(struct dhcp6_msg)) {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("DHCPv6 reply message or pbuf too short\n"));
+ goto free_pbuf_and_return;
+ }
+
+ /* match transaction ID against what we expected */
+ xid = reply_msg->transaction_id[0] << 16;
+ xid |= reply_msg->transaction_id[1] << 8;
+ xid |= reply_msg->transaction_id[2];
+ if (xid != dhcp6->xid) {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
+ ("transaction id mismatch reply_msg->xid(%"X32_F")!= dhcp6->xid(%"X32_F")\n", xid, dhcp6->xid));
+ goto free_pbuf_and_return;
+ }
+ /* option fields could be unfold? */
+ if (dhcp6_parse_reply(p, dhcp6) != ERR_OK) {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+ ("problem unfolding DHCPv6 message - too short on memory?\n"));
+ goto free_pbuf_and_return;
+ }
+
+ /* read DHCP message type */
+ msg_type = reply_msg->msgtype;
+ /* message type is DHCP6 REPLY? */
+ if (msg_type == DHCP6_REPLY) {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("DHCP6_REPLY received\n"));
+#if LWIP_IPV6_DHCP6_STATELESS
+ /* in info-requesting state? */
+ if (dhcp6->state == DHCP6_STATE_REQUESTING_CONFIG) {
+ dhcp6_set_state(dhcp6, DHCP6_STATE_STATELESS_IDLE, "dhcp6_recv");
+ dhcp6_handle_config_reply(netif, p);
+ } else
+#endif /* LWIP_IPV6_DHCP6_STATELESS */
+ {
+ /* @todo: handle reply in other states? */
+ }
+ } else {
+ /* @todo: handle other message types */
+ }
+
+free_pbuf_and_return:
+ pbuf_free(p);
+}
+
+/**
+ * A DHCPv6 request has timed out.
+ *
+ * The timer that was started with the DHCPv6 request has
+ * timed out, indicating no response was received in time.
+ */
+static void
+dhcp6_timeout(struct netif *netif, struct dhcp6 *dhcp6)
+{
+ LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp6_timeout()\n"));
+
+ LWIP_UNUSED_ARG(netif);
+ LWIP_UNUSED_ARG(dhcp6);
+
+#if LWIP_IPV6_DHCP6_STATELESS
+ /* back-off period has passed, or server selection timed out */
+ if (dhcp6->state == DHCP6_STATE_REQUESTING_CONFIG) {
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_timeout(): retrying information request\n"));
+ dhcp6_information_request(netif, dhcp6);
+ }
+#endif /* LWIP_IPV6_DHCP6_STATELESS */
+}
+
+/**
+ * DHCPv6 timeout handling (this function must be called every 500ms,
+ * see @ref DHCP6_TIMER_MSECS).
+ *
+ * A DHCPv6 server is expected to respond within a short period of time.
+ * This timer checks whether an outstanding DHCPv6 request is timed out.
+ */
+void
+dhcp6_tmr(void)
+{
+ struct netif *netif;
+ /* loop through netif's */
+ NETIF_FOREACH(netif) {
+ struct dhcp6 *dhcp6 = netif_dhcp6_data(netif);
+ /* only act on DHCPv6 configured interfaces */
+ if (dhcp6 != NULL) {
+ /* timer is active (non zero), and is about to trigger now */
+ if (dhcp6->request_timeout > 1) {
+ dhcp6->request_timeout--;
+ } else if (dhcp6->request_timeout == 1) {
+ dhcp6->request_timeout--;
+ /* { dhcp6->request_timeout == 0 } */
+ LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp6_tmr(): request timeout\n"));
+ /* this client's request timeout triggered */
+ dhcp6_timeout(netif, dhcp6);
+ }
+ }
+ }
+}
+
+#endif /* LWIP_IPV6 && LWIP_IPV6_DHCP6 */