From 284b9500baf0f5038c27510bee9f95a82e1130f2 Mon Sep 17 00:00:00 2001
From: Pavel Kovar <pavel.kovar@nxp.com>
Date: Tue, 21 Nov 2023 16:01:01 +0100
Subject: [PATCH] DHCPv6: Add stateful mode and prefix delegation support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Overhaul of dhcpv6 client to support IPv6 address autoconfiguration as per
RFC 8415 (stateful DHCPv6 with prefix delegation) and
RFC 3736 (stateless DHCPv6).

Interface:

dhcp6_enable() enables DHCPv6 for a netif. (Originaly dhcp6_enable_stateless)
dhcp6_disable() disable DHCPv6 for a netif (unchanged)
dhcp6_set_client_id_duid() enables use of the app’s own DUID instead of hwaddr.

define LWIP_IPV6_DHCP6_STATEFUL to 1 to support a stateful client.
define LWIP_IPV6_DHCP6_PD to 1 to request a prefix from your router.

Use LWIP_DHCP6_ORO_OPTS_CUSTOM with LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM
to set and proccess additional ORO options.

Limitations and features good to know about:
 - Solicitation is sent only on the router advertisement reception
 - Client always picks the first advertisement and continues with that server.
 - Rapid commit is not supported
 - Client is not keeping a list of multiple potential servers, so rebinding is not supported. The client will transit to the solicitation state instead.
 - When the server replies to renew with address or prefix with zero valid time, the client goes to the solicitation state.

When enabled, requests are only issued after the receipt of RA with the corresponding bits (M/O) set.

Signed-off-by: Pavel Kovar <pavel.kovar@nxp.com>
---
 src/core/ipv6/dhcp6.c         | 1743 +++++++++++++++++++++++++--------
 src/include/lwip/dhcp6.h      |   61 +-
 src/include/lwip/prot/dhcp6.h |  121 ++-
 test/unit/Filelists.cmake     |    1 +
 test/unit/Filelists.mk        |    1 +
 test/unit/dhcp6/test_dhcp6.c  | 1536 +++++++++++++++++++++++++++++
 test/unit/dhcp6/test_dhcp6.h  |   36 +
 test/unit/lwip_unittests.c    |    2 +
 test/unit/lwipopts.h          |    5 +
 9 files changed, 3089 insertions(+), 417 deletions(-)
 create mode 100644 test/unit/dhcp6/test_dhcp6.c
 create mode 100644 test/unit/dhcp6/test_dhcp6.h

diff --git a/src/core/ipv6/dhcp6.c b/src/core/ipv6/dhcp6.c
index e6a7e64d..fbf7621b 100644
--- a/src/core/ipv6/dhcp6.c
+++ b/src/core/ipv6/dhcp6.c
@@ -4,28 +4,31 @@
  * @defgroup dhcp6 DHCPv6
  * @ingroup ip6
  * DHCPv6 client: IPv6 address autoconfiguration as per
- * RFC 3315 (stateful DHCPv6) and
+ * RFC 8415 (stateful DHCPv6 with prefix delegation) and
  * RFC 3736 (stateless DHCPv6).
  *
- * For now, only stateless DHCPv6 is implemented!
+ * Limitations and features you should know about:
+ *  - Solicitation is sent only on router advertisement reception
+ *  - Client always picks first advertisement and continues with that server.
+ *  - Rapid commit is not supported
+ *  - Client is not keeping list of multiple potential servers so rebinding is
+ *    not supported. Client will transit to solicitation state instead.
+ *  - When server replies to renew with address or prefix with zero valid time
+ *    client goes to solicitation state.
  *
- * 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)<br>
- * dhcp6_enable_stateless() enables stateless DHCPv6 for a netif (stateful disabled)<br>
+ * dhcp6_enable() enables DHCPv6 for a netif <br>
  * dhcp6_disable() disable DHCPv6 for a netif
  *
+ * define LWIP_IPV6_DHCP6_STATEFUL to 1 to support stateful client (Can give you an address using DHCP6)
+ * define LWIP_IPV6_DHCP6_PD to 1 to request prefix form your router.
+ *
  * When enabled, requests are only issued after receipt of RA with the
  * corresponding bits set.
  */
 
 /*
  * Copyright (c) 2018 Simon Goldschmidt
+ * Copyright 2023 NXP
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without modification,
@@ -61,22 +64,31 @@
 
 #include "lwip/dhcp6.h"
 #include "lwip/prot/dhcp6.h"
+#include "lwip/prot/iana.h"
 #include "lwip/def.h"
 #include "lwip/udp.h"
 #include "lwip/dns.h"
 
 #include <string.h>
+#include <lwip/dhcp.h>
+#include <lwip/sys.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)
+
+#if defined(LWIP_DHCP6_ORO_OPTS_CUSTOM) && !defined(LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM)
+#warning "Custom option set in LWIP_DHCP6_ORO_OPTS_CUSTOM but missing definition of LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM to parse it"
+#endif
+#if !defined(LWIP_DHCP6_ORO_OPTS_CUSTOM) && defined(LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM)
+#warning "LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM to parse registered to parse custom option but no option set in LWIP_DHCP6_ORO_OPTS_CUSTOM"
 #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)
+
+#ifdef LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM
+extern void LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM(const struct netif * netif, u16_t type, u8_t state, const struct pbuf * p, u16_t offset, u16_t opt_len);
 #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
@@ -87,42 +99,7 @@
 #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)
-
+#define dhcp6_sec_to_ticks(sec) ((sec*1000)/DHCP6_TIMER_MSECS)
 
 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);
@@ -130,9 +107,30 @@ const ip_addr_t dhcp6_All_DHCP6_Servers = IPADDR6_INIT_HOST(0xFF020000, 0, 0, 0x
 static struct udp_pcb *dhcp6_pcb;
 static u8_t dhcp6_pcb_refcount;
 
+static const u16_t requested_options[] = {
+#if LWIP_DHCP6_PROVIDE_DNS_SERVERS
+    DHCP6_OPTION_DNS_SERVERS,
+#endif
+#if LWIP_DHCP6_GET_NTP_SRV
+    DHCP6_OPTION_SNTP_SERVERS,
+#endif
+#ifdef LWIP_DHCP6_ORO_OPTS_CUSTOM
+  LWIP_DHCP6_ORO_OPTS_CUSTOM,
+#endif
+    0U /* end of list */
+};
+
 
-/* 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);
+static void dhcp6_information_request(struct netif *netif, struct dhcp6 *dhcp6);
+#if LWIP_IPV6_DHCP6_STATEFUL
+static void dhcp6_solicitation(struct netif *netif, struct dhcp6 *dhcp6);
+static void dhcp6_request(struct netif *netif, struct dhcp6 *dhcp6);
+static void dhcp6_renew(struct netif *netif, struct dhcp6 *dhcp6);
+static void dhcp6_rebind(struct netif *netif, struct dhcp6 *dhcp6);
+static void dhcp6_release(struct netif *netif, struct dhcp6 *dhcp6);
+static void set_addr_not_advertised(struct dhcp6 *dhcp6);
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
 
 /** Ensure DHCP PCB is allocated and bound */
 static err_t
@@ -190,29 +188,12 @@ dhcp6_set_struct(struct netif *netif, struct dhcp6 *dhcp6)
 
   /* clear data structure */
   memset(dhcp6, 0, sizeof(struct dhcp6));
-  /* dhcp6_set_state(&dhcp, DHCP6_STATE_OFF); */
+#if LWIP_IPV6_DHCP6_STATEFUL
+  dhcp6->addr_idx = -1;
+#endif
   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)
 {
@@ -221,17 +202,20 @@ dhcp6_get_struct(struct netif *netif, const char *dbg_requester)
     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));
+      LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+                  ("%s: could not allocate dhcp6\n", dbg_requester));
       return NULL;
     }
-
     /* clear data structure, this implies DHCP6_STATE_OFF */
     memset(dhcp6, 0, sizeof(struct dhcp6));
+#if LWIP_IPV6_DHCP6_STATEFUL
+    dhcp6->addr_idx = -1;
+#endif
     /* 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));
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("%s: using existing DHCPv6 client\n", dbg_requester));
   }
 
   if (!dhcp6->pcb_allocated) {
@@ -240,128 +224,296 @@ dhcp6_get_struct(struct netif *netif, const char *dbg_requester)
       netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6, NULL);
       return NULL;
     }
-    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("%s: allocated dhcp6\n", dbg_requester));
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("%s: dhcp6 successfully allocated\n", dbg_requester));
     dhcp6->pcb_allocated = 1;
   }
   return dhcp6;
 }
 
+static void
+dhcp6_free_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: DHCPv6 client already freed\n", dbg_requester));
+  } else {
+#if LWIP_IPV6_DHCP6_STATEFUL
+    if(dhcp6->server_id != NULL) {
+      mem_free(dhcp6->server_id);
+    }
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+    mem_free(dhcp6);
+    netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6, NULL);
+    dhcp6_dec_pcb_refcount();
+  }
+}
+
 /*
  * Set the DHCPv6 state
- * If the state changed, reset the number of tries.
+ * If the state changed, reset the number of tries and set retransmission params
+ * according RFC.
  */
 static void
-dhcp6_set_state(struct dhcp6 *dhcp6, u8_t new_state, const char *dbg_caller)
+dhcp6_set_state(struct netif *netif, 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",
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp6_set_state: %d -> %d (caller: %s)\n",
     dhcp6->state, new_state, dbg_caller));
   if (new_state != dhcp6->state) {
     dhcp6->state = new_state;
     dhcp6->tries = 0;
     dhcp6->request_timeout = 0;
-  }
-}
+#if LWIP_IPV6_DHCP6_STATEFUL
+    dhcp6->max_request_timeout = 0;
+    dhcp6->max_retrans_cnt = 0;
+    dhcp6->max_retrans_dur = 0;
+#endif
 
-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;
-}
+    switch (dhcp6->state) {
 
-/*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;
-}*/
+      case DHCP6_STATE_OFF:
+        dhcp6_free_struct(netif, dbg_caller);
+        /* dhcp6 struct doesn't exist here anymore. */
+        return;
 
-/**
- * @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\n"));
-  return ERR_VAL;
+      case DHCP6_STATE_IDLE:
+        break;
+
+      case DHCP6_STATE_SL_REQUESTING_CONFIG:
+        dhcp6_information_request(netif, dhcp6);
+        dhcp6->request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_INF_TIMEOUT);
+        dhcp6->max_request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_INF_MAX_RT);
+        break;
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+      case DHCP6_STATE_SF_INIT_DELAY:
+        /* something random between 0 and SOL_MAX delay Fixme: randomize*/
+        dhcp6->request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_SOL_MAX_DELAY)/2;
+        break;
+
+      case DHCP6_STATE_SF_SOLICITING:
+        /* Set initial IA NA T1 and T2 preference to no preference */
+        set_addr_not_advertised(dhcp6);
+        dhcp6->addr_t1 = 0;
+        dhcp6->addr_t2 = 0;
+        dhcp6_solicitation(netif, dhcp6);
+        dhcp6->request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_SOL_TIMEOUT);
+        dhcp6->max_request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_SOL_MAX_RT);
+        break;
+
+      case DHCP6_STATE_SF_REQUESTING:
+        dhcp6_request(netif, dhcp6);
+        dhcp6->request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_REQ_TIMEOUT);
+        dhcp6->max_request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_REQ_MAX_RT);
+        dhcp6->max_retrans_cnt = DHCP6_PAR_REQ_MAX_RC;
+        break;
+
+      case DHCP6_STATE_SF_BOUND:
+        dhcp6->request_timeout = 0;
+        break;
+
+      case DHCP6_STATE_SF_RENEWING:
+          dhcp6_renew(netif, dhcp6);
+          dhcp6->request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_REN_TIMEOUT);
+          dhcp6->max_request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_REN_MAX_RT);
+          break;
+
+      case DHCP6_STATE_SF_REBINDING:
+          dhcp6_rebind(netif, dhcp6);
+          dhcp6->request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_REB_TIMEOUT);
+          dhcp6->max_request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_REB_MAX_RT);
+          break;
+
+      case DHCP6_STATE_SF_RELEASING:
+          dhcp6_release(netif, dhcp6);
+          dhcp6->request_timeout = dhcp6_sec_to_ticks(DHCP6_PAR_REL_TIMEOUT);
+          dhcp6->max_retrans_cnt = DHCP6_PAR_REL_MAX_RC;
+          break;
+
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+      default:
+          LWIP_ASSERT("Unhandled state at dhcp6_set_state", 0);
+    } /* switch */
+    dhcp6->request_timeout_cntr = dhcp6->request_timeout;
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_set_state: setting request timeout to %"U16_F" ms\n",
+                                                dhcp6->request_timeout * DHCP6_TIMER_MSECS));
+  } /* if state changed */
 }
 
 /**
  * @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.
+ * Enable DHCPv6 on this netif
+ * Requests are sent on receipt of an RA message.
  *
  * 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)
+dhcp6_enable(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));
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp6_enable_stateful(netif=%p) %c%c%"U16_F"\n",
+              (void *)netif, netif->name[0], netif->name[1], (u16_t)netif->num));
+  LWIP_ASSERT_CORE_LOCKED();
 
-  dhcp6 = dhcp6_get_struct(netif, "dhcp6_enable_stateless()");
+  dhcp6 = dhcp6_get_struct(netif, "dhcp6_enable_stateful");
   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\n"));
-    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\n"));
+  if (dhcp6->state == DHCP6_STATE_OFF) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp6_enable: DHCPv6 enabled\n"));
+    dhcp6_set_state(netif, dhcp6, DHCP6_STATE_IDLE, "dhcp6_enable");
+  } else if (dhcp6->state == DHCP6_STATE_OFF) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp6_enable: DHCPv6 is in releasing process. Be enabled when done.\n"));
+    dhcp6->state_after_release = DHCP6_STATE_IDLE;
+    return ERR_INPROGRESS;
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp6_enable: DHCPv6 is not disabled\n"));
+    return ERR_ALREADY;
   }
-  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.
+ * Disable DHCPv6 on specified netif and dealocates it't memory.
  */
 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));
+  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;
-      }
+    switch (dhcp6->state) {
+      case DHCP6_STATE_OFF:
+          break;
+      case DHCP6_STATE_IDLE:
+      case DHCP6_STATE_SL_REQUESTING_CONFIG:
+#if LWIP_IPV6_DHCP6_STATEFUL
+      case DHCP6_STATE_SF_INIT_DELAY:
+      case DHCP6_STATE_SF_SOLICITING:
+      case DHCP6_STATE_SF_REQUESTING:
+#endif
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_OFF, "disable");
+          break;
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+      case DHCP6_STATE_SF_BOUND:
+      case DHCP6_STATE_SF_RENEWING:
+      case DHCP6_STATE_SF_REBINDING:
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_RELEASING, "disable");
+          break;
+
+      case DHCP6_STATE_SF_RELEASING:
+          break;
+
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+      default:
+          LWIP_ASSERT("Unhandled state at dhcp6_set_state", 0);
     }
+    dhcp6->state_after_release = DHCP6_STATE_OFF;
+  }
+}
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+/**
+ * Allows you to set a reference to custom DUID of client identifier.
+ *
+ * DUID format is described in RFC 8415 section 11.
+ *
+ * By default lwIP will send DUID Based on Link-Layer Address (DUID-LL).
+ *
+ * @param netif
+ * @param duid      Ptr to DUID data or NULL to set default DUID mentioned above.
+ * @param duid_len  Length of DUID
+ */
+void dhcp6_set_client_id_duid(struct netif *netif, const void *duid, u16_t duid_len)
+{
+  struct dhcp6 *dhcp6;
+  LWIP_ASSERT("netif is NULL", netif != NULL);
+  LWIP_ASSERT_CORE_LOCKED();
+  dhcp6 = netif_dhcp6_data(netif);
+  LWIP_ASSERT("Enable dhcp6 first", dhcp6 != NULL);
+  LWIP_ASSERT("Duid must be null or longer than 0", ( (duid != NULL) && (duid_len > 0) ) || (duid == NULL) );
+  if (dhcp6 != NULL) {
+    dhcp6->cli_id_usr_duid = duid;
+    dhcp6->cli_id_usr_duid_len = duid_len;
+  }
+}
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
+#if LWIP_IPV6_DHCP6_PD
+/**
+ * Register function which will be called on change of delegated prefix.
+ * @param netif
+ * @param callback function
+ */
+void
+dhcp6_register_pd_callback(struct netif *netif, dhcp6_pd_callback_fn callback)
+{
+  struct dhcp6 *dhcp6;
+  LWIP_ASSERT("netif is NULL", netif != NULL);
+  LWIP_ASSERT_CORE_LOCKED();
+  dhcp6 = netif_dhcp6_data(netif);
+  LWIP_ASSERT("Enable dhcp6 first", dhcp6 != NULL);
+  if (dhcp6 != NULL) {
+    dhcp6->pd_callback = callback;
+  }
+}
+
+/**
+ * Register function which will be called on change of delegated prefix.
+ * @param netif
+ * @returns pointer to internal @ref struct dhcp6_delegated_prefix or NULL if client is disabled.
+ *
+ */
+const struct dhcp6_delegated_prefix *
+dhcp6_get_delegated_prefix(struct netif *netif)
+{
+  struct dhcp6 *dhcp6;
+  LWIP_ASSERT("netif is NULL", netif != NULL);
+  LWIP_ASSERT_CORE_LOCKED();
+  dhcp6 = netif_dhcp6_data(netif);
+
+  if (dhcp6 != NULL) {
+    return &dhcp6->prefix;
   }
+  return NULL;
+}
+
+/* Return true if we have the prefix stored by dhcp6_parse_ia_pd. Doesn't checks valid time.*/
+static u8_t
+is_prefix_advertised(const struct dhcp6 *dhcp6)
+{
+  return (dhcp6->prefix.prefix_len > 0);
+}
+
+/* Sets prefix to be "not delegated". is_prefix_advertised() will return false.*/
+static void
+set_prefix_not_delegated(struct dhcp6 *dhcp6)
+{
+  dhcp6->prefix.prefix_len = 0U;
+}
+#endif /* LWIP_IPV6_DHCP6_PD */
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+static u8_t
+is_addr_advertised(const struct dhcp6 *dhcp6)
+{
+  return !(ip6_addr_isany(&dhcp6->addr));
+}
+
+/* Sets address to be "not advertised". is_addr_advertised() will then return false. */
+static void
+set_addr_not_advertised(struct dhcp6 *dhcp6)
+{
+  ip6_addr_set_any(&dhcp6->addr);
 }
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
 
 /**
  * Create a DHCPv6 request, fill in common headers
@@ -385,20 +537,22 @@ dhcp6_create_msg(struct netif *netif, struct dhcp6 *dhcp6, u8_t message_type,
   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"));
+                ("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;
+#if LWIP_IPV6_DHCP6_STATEFUL
+    dhcp6->trans_started_at = sys_now();
+#endif
   }
 
   LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE,
-              ("transaction id xid(%"X32_F")\n", dhcp6->xid));
+              (" 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);
@@ -408,9 +562,19 @@ dhcp6_create_msg(struct netif *netif, struct dhcp6 *dhcp6, u8_t message_type,
   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;
 }
 
+#if LWIP_IPV6_DHCP6_PD
+static u16_t
+dhcp6_option_byte(u16_t options_out_len, u8_t *options, u8_t value)
+{
+  options[options_out_len++] = value;
+  return options_out_len;
+}
+#endif /* LWIP_IPV6_DHCP6_PD */
+
 static u16_t
 dhcp6_option_short(u16_t options_out_len, u8_t *options, u16_t value)
 {
@@ -419,280 +583,767 @@ dhcp6_option_short(u16_t options_out_len, u8_t *options, u16_t value)
   return options_out_len;
 }
 
+#if LWIP_IPV6_DHCP6_STATEFUL
 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)
+dhcp6_option_unsigned(u16_t options_out_len, u8_t *options, u32_t value)
 {
-  size_t i;
-  u16_t ret;
+  options[options_out_len++] = (u8_t)((value & 0xff000000U) >> 24);
+  options[options_out_len++] = (u8_t)((value & 0x00ff0000U) >> 16);
+  options[options_out_len++] = (u8_t)((value & 0x0000ff00U) >>  8);
+  options[options_out_len++] = (u8_t) (value & 0x000000ffU);
+  return options_out_len;
+}
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
 
-  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);
+static u16_t
+dhcp6_option_optionrequest_size(void)
+{
+  const u16_t header_size = 4U;
+  const u16_t option_size = 2U;
 
-  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]);
+  u8_t num_requested_options = 0;
+  while (requested_options[num_requested_options] != 0) {
+    num_requested_options++;
   }
-  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));
+  return header_size + (option_size * num_requested_options);
 }
 
-
-#if LWIP_IPV6_DHCP6_STATELESS
-static void
-dhcp6_information_request(struct netif *netif, struct dhcp6 *dhcp6)
+static u16_t
+dhcp6_option_optionrequest(u16_t options_out_len, u8_t *options, u16_t max_len)
 {
-  const u16_t requested_options[] = {
-#if LWIP_DHCP6_PROVIDE_DNS_SERVERS
-    DHCP6_OPTION_DNS_SERVERS,
-    DHCP6_OPTION_DOMAIN_LIST
-#endif
-#if LWIP_DHCP6_GET_NTP_SRV
-    , DHCP6_OPTION_SNTP_SERVERS
-#endif
-  };
+  size_t i;
+  u16_t ret;
+  const u16_t header_size = 4U;
 
-  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"));
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" Add opt option request: "));
 
-    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);
+  LWIP_ASSERT("dhcp6_option_optionrequest: packet too small",
+    options_out_len + dhcp6_option_optionrequest_size() <= max_len);
+  LWIP_UNUSED_ARG(max_len);
 
-    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++;
+  ret = dhcp6_option_short(options_out_len, options, DHCP6_OPTION_ORO);
+  ret = dhcp6_option_short(ret, options, dhcp6_option_optionrequest_size() - header_size);
+  for (i = 0; requested_options[i] != 0; i++) {
+    ret = dhcp6_option_short(ret, options, requested_options[i]);
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("%"U16_F", ", requested_options[i]));
   }
-  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);
-  }
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("\n"));
 
-  return ERR_OK;
+  return ret;
 }
 
-static void
-dhcp6_abort_config_request(struct dhcp6 *dhcp6)
+#if LWIP_IPV6_DHCP6_STATEFUL
+static u16_t
+dhcp6_option_ia_na_size(struct dhcp6 *dhcp6, u8_t include_addr)
 {
-  if (dhcp6->state == DHCP6_STATE_REQUESTING_CONFIG) {
-    /* abort running request */
-    dhcp6_set_state(dhcp6, DHCP6_STATE_STATELESS_IDLE, "dhcp6_abort_config_request");
+  if (include_addr && (!is_addr_advertised(dhcp6))) {
+    /* We don't have address to add */
+    return 0;
+
+  } else {
+    const u16_t option_iaaddr_len = 28;
+    const u16_t ia_na_len = 16 + ((include_addr) ? option_iaaddr_len : 0);
+
+    return ia_na_len;
   }
 }
 
-/* Handle a REPLY to INFOREQUEST
- * This parses DNS and NTP server addresses from the reply.
+/**
+ * Add Identity association for Non temporary address request option into msg
  */
-static void
-dhcp6_handle_config_reply(struct netif *netif, struct pbuf *p_msg_in)
+static u16_t
+dhcp6_option_ia_na(struct netif *netif, struct dhcp6 *dhcp6, u8_t include_addr,  u16_t options_out_len, u8_t *options, u16_t max_len)
 {
-  struct dhcp6 *dhcp6 = netif_dhcp6_data(netif);
+  if (include_addr && (!is_addr_advertised(dhcp6))) {
+    /* We don't have address to add */
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" Add opt IA_NA: Not adding IA_NA because srv didn't offered any\n"));
+    return options_out_len;
 
-  LWIP_UNUSED_ARG(dhcp6);
-  LWIP_UNUSED_ARG(p_msg_in);
+  } else {
+    u16_t ret;
+    const u16_t ia_na_len = dhcp6_option_ia_na_size(dhcp6, include_addr);
 
-#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;
-
-    ip_addr_set_zero_ip6(&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 */
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" Add opt IA_NA\n"));
 
-#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);
+    LWIP_ASSERT("dhcp6_option_ia_na: packet too small", options_out_len + ia_na_len <= max_len);
+    LWIP_UNUSED_ARG(max_len);
+
+    ret = dhcp6_option_short(options_out_len, options, DHCP6_OPTION_IA_NA);
+    ret = dhcp6_option_short(ret, options, ia_na_len - 4); /* exclude option header */
+
+    memcpy(&options[ret], &netif->hwaddr[1], 4); /* IAID */
+    ret += 4;
+
+    /* 0 means no client preference for those values */
+    ret = dhcp6_option_unsigned(ret, options, dhcp6->addr_t1);
+    ret = dhcp6_option_unsigned(ret, options, dhcp6->addr_t2);
+
+    if (include_addr) {
+      const u16_t option_iaaddr_len = 24;
+
+      ret = dhcp6_option_short(ret, options, DHCP6_OPTION_IAADDR);
+      ret = dhcp6_option_short(ret, options, option_iaaddr_len);
+
+      memcpy(&options[ret], dhcp6->addr.addr, sizeof(dhcp6->addr.addr));
+      ret += sizeof(dhcp6->addr.addr);
+
+      ret = dhcp6_option_unsigned(ret, options, dhcp6->addr_pref); /* preferred time */
+      ret = dhcp6_option_unsigned(ret, options, dhcp6->addr_valid); /* valid time */
     }
-    dhcp6_set_ntp_servers(n, ntp_server_addrs);
+
+    return ret;
   }
-#endif /* LWIP_DHCP6_GET_NTP_SRV */
 }
-#endif /* LWIP_IPV6_DHCP6_STATELESS */
+#endif  /* LWIP_IPV6_DHCP6_STATEFUL */
 
-/** This function is called from nd6 module when an RA message 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)
+#if LWIP_IPV6_DHCP6_PD
+static u16_t
+dhcp6_option_ia_pd_size(const struct dhcp6 *dhcp6, u8_t include_prefix)
 {
-  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 (include_prefix && (!is_prefix_advertised(dhcp6))) {
+    /* We don't have any prefix to add */
+    return 0;
+  } else {
+    const u16_t ia_prefix_len = 25 + 4;
+    const u16_t ia_pd_len = 16 + ((include_prefix) ? ia_prefix_len : 0);
 
-#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);
-      }
-    }
+    return ia_pd_len;
   }
-#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.
+ * Add Identity association for prefix delegation option into msg
  */
-static err_t
-dhcp6_parse_reply(struct pbuf *p, struct dhcp6 *dhcp6)
+static u16_t
+dhcp6_option_ia_pd(struct netif *netif, struct dhcp6 *dhcp6, u8_t include_prefix, u16_t options_out_len, u8_t *options, u16_t max_len)
 {
-  u16_t offset;
-  u16_t offset_max;
-  u16_t options_idx;
-  struct dhcp6_msg *msg_in;
+  if (include_prefix && (!is_prefix_advertised(dhcp6))) {
+    /* We don't have any prefix to add */
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" Add opt IA_PD: Not adding IA_PD because srv didn't offered any\n"));
+    return options_out_len;
 
-  LWIP_UNUSED_ARG(dhcp6);
+  } else {
+    u16_t ret;
 
-  /* 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 across 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;
+    const u16_t ia_prefix_len = 25;
+    const u16_t ia_pd_len = dhcp6_option_ia_pd_size(dhcp6, include_prefix);
+
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" Add opt option IA_PD\n"));
+
+    LWIP_ASSERT("dhcp6_option_ia_pd: packet too small.",
+                options_out_len + ia_pd_len <= max_len);
+    LWIP_UNUSED_ARG(max_len);
+
+
+    ret = dhcp6_option_short(options_out_len, options, DHCP6_OPTION_IA_PD);
+    ret = dhcp6_option_short(ret, options, ia_pd_len - 4);
+
+    memcpy(&options[ret], &netif->hwaddr[1], 4); /* IAID */
+    ret += 4;
+
+    ret = dhcp6_option_unsigned(ret, options, include_prefix ? dhcp6->ia_pd_t1 : 0); /* T1 */
+    ret = dhcp6_option_unsigned(ret, options, include_prefix ? dhcp6->ia_pd_t2 : 0); /* T2 */
+
+    if (include_prefix) {
+      ret = dhcp6_option_short(ret, options, DHCP6_OPTION_IAPREFIX);
+      ret = dhcp6_option_short(ret, options, ia_prefix_len);
+      ret = dhcp6_option_unsigned(ret, options, dhcp6->prefix.prefix_pref); /* preferred time */
+      ret = dhcp6_option_unsigned(ret, options, dhcp6->prefix.prefix_valid); /* valid time */
+      ret = dhcp6_option_byte(ret, options, dhcp6->prefix.prefix_len);
+
+      memcpy(&options[ret], dhcp6->prefix.prefix.addr, sizeof(dhcp6->prefix.prefix.addr));
+      ret += sizeof(dhcp6->prefix.prefix.addr);
     }
-    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;
+
+    return ret;
+  }
+}
+#endif /* LWIP_IPV6_DHCP6_PD */
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+static u16_t
+dhcp6_option_client_identifier_size(struct netif *netif, struct dhcp6 *dhcp6)
+{
+  const u16_t duid_ll_len = 4 + netif->hwaddr_len;
+  const u16_t clientid_len = 4 + ( (dhcp6->cli_id_usr_duid != NULL) ? dhcp6->cli_id_usr_duid_len : duid_ll_len );
+  return clientid_len;
+}
+
+static u16_t
+dhcp6_option_client_identifier(struct netif *netif, struct dhcp6 *dhcp6, u16_t options_out_len, u8_t *options, u16_t max_len)
+{
+  u16_t ret;
+
+  const u16_t clientid_len = dhcp6_option_client_identifier_size(netif, dhcp6);
+
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" Add opt client identifier: (Using hw addr as DUID)\n"));
+
+  LWIP_ASSERT("dhcp6_option_client_identifier: packet too small",
+                (options_out_len + clientid_len) <= max_len);
+  LWIP_UNUSED_ARG(max_len);
+
+  ret = dhcp6_option_short(options_out_len, options, DHCP6_OPTION_CLIENTID);
+  ret = dhcp6_option_short(ret, options, clientid_len - 4);
+
+  if (dhcp6->cli_id_usr_duid != NULL) {
+    memcpy(&options[ret], dhcp6->cli_id_usr_duid, dhcp6->cli_id_usr_duid_len);
+    ret += dhcp6->cli_id_usr_duid_len;
+  } else {
+  /*
+    0                   1                   2                   3
+    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |               3               |    hardware type (16 bits)    |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   .                                                               .
+   .             link-layer address (variable length)              .
+   .                                                               .
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   */
+
+    ret = dhcp6_option_short(ret, options, DHCP6_DUID_LL);
+    ret = dhcp6_option_short(ret, options, LWIP_IANA_HWTYPE_ETHERNET);
+    memcpy(&options[ret], netif->hwaddr, 6);
+    ret += 6;
+  }
+
+  return ret;
+}
+
+
+static u16_t
+dhcp6_option_server_identifier_size(struct dhcp6 *dhcp6)
+{
+  return dhcp6->server_id_len;
+}
+
+static u16_t
+dhcp6_option_server_identifier(struct dhcp6 *dhcp6,const u16_t options_out_len, u8_t *options, u16_t max_len)
+{
+  const u16_t len = dhcp6_option_server_identifier_size(dhcp6);
+
+  LWIP_ASSERT("dhcp6_option_server_identifier: dhcp6->server_id != NULL", dhcp6->server_id != NULL);
+  LWIP_ASSERT("dhcp6_option_server_identifier: packet too small",
+              options_out_len + len <= max_len);
+  LWIP_UNUSED_ARG(max_len);
+
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" Add opt server ID\n"));
+
+  memcpy(&options[options_out_len], dhcp6->server_id, len);
+
+  return options_out_len + len;
+}
+
+
+static u16_t
+dhcp6_option_elapsed_size(void)
+{
+  return 6U;
+}
+
+static u16_t
+dhcp6_option_elapsed(struct dhcp6 *dhcp6, u16_t options_out_len, u8_t *options, u16_t max_len)
+{
+  u16_t ret;
+  const u32_t elapsed_32 = (sys_now() - dhcp6->trans_started_at) / 10;
+  const u16_t elapsed_16 = (elapsed_32 >= 0xffff) ? 0xffff : (u16_t)elapsed_32;
+
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" Add opt elapsed: %"U16_F"0 ms\n", elapsed_16));
+
+  LWIP_ASSERT("dhcp6_option_elapsed: packet too small",
+              (options_out_len + dhcp6_option_elapsed_size()) <= max_len);
+  LWIP_UNUSED_ARG(max_len);
+
+  ret = dhcp6_option_short(options_out_len, options, DHCP6_OPTION_ELAPSED_TIME);
+  ret = dhcp6_option_short(ret, options, dhcp6_option_elapsed_size() - 4);
+  ret = dhcp6_option_short(ret, options, elapsed_16);
+
+  return ret;
+}
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
+/* All options are added, shrink the pbuf to the required size */
+static void
+dhcp6_msg_finalize(struct dhcp6 *dhcp6, 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 (dhcp6->tries < 255) {
+    dhcp6->tries++;
+  }
+
+}
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+/**
+ * Compose and send statefull message
+ * @param dhcp6
+ * @param message_type dhcp6 message type
+ * @param fill_addr_and_prefix  true: If addr/prefix is present in dhcp6 struct ia_na/ia_pd with addr/prefix will be
+ *                                    appended to msg. If not present ia_na/ia_pd will not be added to msg.
+ *                              false: ia_pd/ia_na without prefix/address will be appended to msg.
+ * @param fill_srvid  If true copies server id into
+ */
+static void
+dhcp6_send_stateful_msg(struct netif *netif, struct dhcp6 *dhcp6, u8_t message_type,
+                        u8_t fill_addr_and_prefix, u8_t fill_srvid)
+{
+  struct pbuf *p_out;
+  u16_t options_out_len;
+  u16_t size = dhcp6_option_client_identifier_size(netif, dhcp6) + dhcp6_option_optionrequest_size() +
+               dhcp6_option_elapsed_size();
+
+#if LWIP_IPV6_DHCP6_PD
+    size += dhcp6_option_ia_pd_size(dhcp6, fill_addr_and_prefix);
+#endif
+
+  if (!dhcp6->addr_by_slaac) {
+    size += dhcp6_option_ia_na_size(dhcp6, fill_addr_and_prefix);
+  }
+  if (fill_srvid) {
+    size += dhcp6_option_server_identifier_size(dhcp6);
+  }
+
+  /* create and initialize the DHCP message header */
+  p_out = dhcp6_create_msg(netif, dhcp6, message_type, size, &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);
+    const u16_t options_max_len = size;
+
+    if (!dhcp6->addr_by_slaac) {
+      options_out_len = dhcp6_option_ia_na(netif, dhcp6, fill_addr_and_prefix,
+                                           options_out_len, options, options_max_len);
     }
 
-    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);
+    options_out_len = dhcp6_option_client_identifier(netif, dhcp6, options_out_len, options, options_max_len);
+    if (fill_srvid) {
+      options_out_len = dhcp6_option_server_identifier(dhcp6, options_out_len, options, options_max_len);
+    }
+    options_out_len = dhcp6_option_optionrequest(options_out_len, options, options_max_len);
+    options_out_len = dhcp6_option_elapsed(dhcp6, options_out_len, options, options_max_len);
+#if LWIP_IPV6_DHCP6_PD
+    options_out_len = dhcp6_option_ia_pd(netif, dhcp6, fill_addr_and_prefix, options_out_len, options, options_max_len);
+#endif
+    dhcp6_msg_finalize(dhcp6, 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, ("dhcp6_send_stateful_msg: sent, udp_sendto_if returned %d\n", (int)err));
+    LWIP_UNUSED_ARG(err);
+  } else {
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp6_send_stateful_msg: could not allocate DHCP6 request\n"));
+  }
+}
+
+static void
+dhcp6_solicitation(struct netif *netif, struct dhcp6 *dhcp6)
+{
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_solicitation: preparing msg\n"));
+  dhcp6_send_stateful_msg(netif, dhcp6, DHCP6_SOLICIT, 0, 0);
+}
+
+static void
+dhcp6_request(struct netif *netif, struct dhcp6 *dhcp6)
+{
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_request: preparing msg\n"));
+  dhcp6_send_stateful_msg(netif, dhcp6, DHCP6_REQUEST, 1, 1);
+}
+
+static void
+dhcp6_renew(struct netif *netif, struct dhcp6 *dhcp6)
+{
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_renew: preparing msg\n"));
+  dhcp6_send_stateful_msg(netif, dhcp6, DHCP6_RENEW, 1, 1);
+}
+
+static void
+dhcp6_rebind(struct netif *netif, struct dhcp6 *dhcp6)
+{
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_rebind: preparing msg\n"));
+  dhcp6_send_stateful_msg(netif, dhcp6, DHCP6_RENEW, 1, 0);
+}
+
+static void
+dhcp6_release(struct netif *netif, struct dhcp6 *dhcp6)
+{
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_release: preparing msg\n"));
+  dhcp6_send_stateful_msg(netif, dhcp6, DHCP6_RELEASE, 1, 1);
+}
+#endif /* LWIP_IPV6_DHCP6_STATEFULL */
+
+#if LWIP_IPV6_DHCP6_STATELESS
+/**
+ * Send information request (stateless)
+ */
+static void
+dhcp6_information_request(struct netif *netif, struct dhcp6 *dhcp6)
+{
+  struct pbuf *p_out;
+  u16_t options_out_len;
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_information_request: preparing msg\n"));
+  /* create and initialize the DHCP message header */
+  p_out = dhcp6_create_msg(netif, dhcp6, DHCP6_INFOREQUEST, dhcp6_option_optionrequest_size(), &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);
+
+    options_out_len = dhcp6_option_optionrequest(options_out_len, options, p_out->len);
+
+    dhcp6_msg_finalize(dhcp6, 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, ("dhcp6_information_request: sent with return code %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"));
+  }
+}
+
+#endif /* LWIP_IPV6_DHCP6_STATELESS */
+
+/** This function is called from nd6 module when an RA message is received
+ * It triggers DHCPv6 requests (if enabled).
+ *
+ * @param managed_addr_config Managed Address Config Flag tells the host if it should use stateful DHCPv6
+ * @param other_config        Other Config Flag tells the host that there is other information the router can provide.
+ */
+void
+dhcp6_nd6_ra_trigger(struct netif *netif, u8_t managed_addr_config, u8_t other_config)
+{
+  struct dhcp6 *dhcp6;
+#if !LWIP_IPV6_DHCP6_STATEFUL
+  LWIP_UNUSED_ARG(managed_addr_config);
+#endif
+
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+  dhcp6 = netif_dhcp6_data(netif);
+
+  if (dhcp6 != NULL) {
+
+    switch (dhcp6->state) {
+      case DHCP6_STATE_IDLE:
+#if LWIP_IPV6_DHCP6_STATEFUL
+        dhcp6->addr_by_slaac = !managed_addr_config;
+
+        if (managed_addr_config) {
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_INIT_DELAY, "ra_trigger M flag");
+          break;
+        }
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
+        if (other_config) {
+#if LWIP_IPV6_DHCP6_PD
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_INIT_DELAY, "ra_trigger O flag");
+#else
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SL_REQUESTING_CONFIG, "ra_trigger O flag");
+#endif
+          break;
+        }
         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);
+      case DHCP6_STATE_OFF:
+      case DHCP6_STATE_SL_REQUESTING_CONFIG:
+#if LWIP_IPV6_DHCP6_STATEFUL
+      case DHCP6_STATE_SF_INIT_DELAY:
+      case DHCP6_STATE_SF_SOLICITING:
+      case DHCP6_STATE_SF_REQUESTING:
+      case DHCP6_STATE_SF_BOUND:
+      case DHCP6_STATE_SF_RENEWING:
+      case DHCP6_STATE_SF_REBINDING:
+      case DHCP6_STATE_SF_RELEASING:
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
         break;
+
+      default:
+        LWIP_ASSERT("Unhandled state at dhcp6_nd6_ra_trigger", 0);
+    } /* switch */
+  }
+
+}
+ /* copies option into bufer and retrns its len or -1 and sets offset param to beginning of option data (after header) */
+static s16_t dhcp6_find_option(const struct pbuf *p, u16_t option_code, u16_t *offset)
+{
+  const u16_t opt_hdr_len = 4;
+
+  while (((*offset) + opt_hdr_len) <= p->tot_len) {
+    const u8_t hdr_len = sizeof(struct dhcp6_option_header);
+    u8_t hdr_buf[sizeof(struct dhcp6_option_header)];
+
+    struct dhcp6_option_header *hdr =
+            (struct dhcp6_option_header *) pbuf_get_contiguous(p, hdr_buf, hdr_len, hdr_len, *offset);
+
+    if (hdr != NULL) {
+      u16_t next_offset;
+      const u16_t found_opt_code = lwip_ntohs(hdr->option_code);
+      const u16_t found_opt_len = lwip_ntohs(hdr->option_len);
+
+      *offset += hdr_len;
+      next_offset = *offset + found_opt_len;
+
+      if (next_offset > p->tot_len) {
+        return -1; /* bad packet */
+      }
+
+      if (found_opt_code == option_code) {
+        return found_opt_len;
+      }
+
+      *offset = next_offset;
+    } else {
+      return -1;
+    }
+  }
+  return -1;
+}
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+/* stores server id in to dhcp6 struct return 1 on success */
+static u8_t
+dhcp6_parse_and_store_serverid(struct pbuf *p, u16_t offset, struct dhcp6 *dhcp6)
+{
+  void *ptr;
+  /* store option including its header.  */
+  const s16_t server_id_len = dhcp6_find_option(p, DHCP6_OPTION_SERVERID, &offset) + 4;
+  offset -= 4;
+
+  if (server_id_len < 0) {
+    return 0;
+  }
+
+  if (dhcp6->server_id != NULL) {
+    mem_free(dhcp6->server_id);
+  }
+  dhcp6->server_id = mem_malloc(server_id_len);
+
+  if (dhcp6->server_id == NULL) {
+    return 0;
+  }
+
+  ptr = pbuf_get_contiguous(p, dhcp6->server_id, server_id_len, server_id_len, offset);
+  if (ptr == NULL) {
+    return 0;
+  } else if (ptr != dhcp6->server_id) {
+    memcpy(dhcp6->server_id, ptr, server_id_len);
+    dhcp6->server_id_len = server_id_len;
+  }
+
+  return 1;
+}
+
+/* returns 0 if received and  */
+static u8_t
+dhcp6_parse_and_check_serverid_different(struct pbuf *p, u16_t offset, struct dhcp6 *dhcp6)
+{
+  u16_t are_different;
+  /* store option including its header.  */
+  const s16_t server_id_len = dhcp6_find_option(p, DHCP6_OPTION_SERVERID, &offset) + 4;
+  offset -= 4;
+
+  if (server_id_len < 0) {
+    return 2;
+  } else if (server_id_len != dhcp6->server_id_len) {
+    return 3;
+  }
+
+  LWIP_ASSERT("DHCP6: Check of server id requested but we haven't stored any", dhcp6->server_id != NULL);
+
+  are_different = pbuf_memcmp(p, offset, dhcp6->server_id, server_id_len);
+
+  return are_different;
+}
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
 #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;
+/* Parses list of dns servers and sets them into lwIP */
+static u8_t
+dhcp6_parse_dns_servers(const struct netif *netif, const struct pbuf *p, u16_t offset)
+{
+  uint8_t i;
+  ip_addr_t addr;
+  s16_t dns_opt_len = dhcp6_find_option(p, DHCP6_OPTION_DNS_SERVERS, &offset);
+
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" dhcp6_parse_dns_servers:"));
+
+  if (dns_opt_len < 16 ) {
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" got none\n"));
+    return 0;
+  }
+
+  ip_addr_set_zero_ip6(&addr);
+
+  for(i=0; dns_opt_len >= 16 && i < LWIP_DHCP6_MAX_DNS_SERVERS; i++) {
+    uint16_t n = pbuf_copy_partial(p, &addr.u_addr.ip6.addr, 16, offset);
+    if (n == 16) {
+      ip6_addr_assign_zone(&addr.u_addr.ip6, IP6_UNKNOWN, netif);
+      dns_setserver(i, &addr);
+      offset += 16;
+      dns_opt_len -= 16;
+      LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" %s,", ipaddr_ntoa(&addr)));
+    } else {
+      break;
+    }
+  }
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("\n"));
+
+  /* Reset remaining slots */
+  for (; i < LWIP_DHCP6_MAX_DNS_SERVERS; i++) {
+      dns_setserver(i, NULL);
+  }
+
+  return 1;
+}
 #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;
+/* stores server id in to dhcp6 struct return 1 on success */
+static u8_t
+dhcp6_parse_sntp_servers(const struct netif * netif, const struct pbuf *p, u16_t offset)
+{
+  uint8_t num = 0;
+  ip_addr_t addrs[LWIP_DHCP6_MAX_SNTP_SERVERS];
+  s16_t sntp_opt_len = dhcp6_find_option(p, DHCP6_OPTION_SNTP_SERVERS, &offset);
+
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" dhcp6_parse_sntp_servers:"));
+  if (sntp_opt_len < 16) {
+    LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" got none\n"));
+  }
+
+  for (; sntp_opt_len >= 16 && num < LWIP_DHCP6_MAX_SNTP_SERVERS; num++) {
+    ip_addr_set_zero_ip6(&addrs[num]);
+    if (pbuf_copy_partial(p, &addrs[num].u_addr.ip6.addr, 16, offset) == 16) {
+      ip6_addr_assign_zone(&addrs[num].u_addr.ip6, IP6_UNKNOWN, netif);
+      offset += 16;
+      sntp_opt_len -= 16;
+      LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" %s,", ipaddr_ntoa(&addrs[num])));
+    } else {
+      break;
+    }
+  }
+
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("\n"));
+
+  dhcp6_set_ntp_servers(num, addrs);
+
+  return (num > 0) ? 1 : 0;
+}
+#endif /* LWIP_DHCP6_GET_NTP_SRV */
+
+#ifdef LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM
+static void
+dhcp6_parse_oro_opts_custom(const struct netif * netif, const struct dhcp6 * dhcp6, const struct pbuf *p, u16_t offset)
+{
+  u8_t i = 0;
+  u16_t opt_offset = offset;
+
+  /* Ignore opts already handled by dhcp client */
+#if LWIP_DHCP6_PROVIDE_DNS_SERVERS
+  i++;
+#endif
+#if LWIP_DHCP6_GET_NTP_SRV
+  i++;
+#endif
+
+  for (; requested_options[i] != 0; i++) {
+    s16_t opt_len = dhcp6_find_option(p, requested_options[i], &opt_offset);
+    if (opt_len >= 0) {
+      LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" dhcp6_parse_oro_opts_custom: got %"U16_F"\n", requested_options[i]));
+      LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM(netif, requested_options[i], dhcp6->state, p, opt_offset, opt_len);
     }
   }
-  return ERR_OK;
 }
+#endif /* LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM */
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+static u8_t
+dhcp6_parse_ia_na(struct pbuf *p, u16_t offset, struct dhcp6 *dhcp6)
+{
+  const u16_t ia_len = dhcp6_find_option(p, DHCP6_OPTION_IA_NA, &offset);
+
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" dhcp6_parse_ia_na:"));
+
+  if (ia_len >= (sizeof(struct dhcp6_option_ia) + sizeof(struct dhcp6_option_ia_address) + 4)) {
+    struct dhcp6_option_ia buf;
+    const struct dhcp6_option_ia *opt_ia =
+            (const struct dhcp6_option_ia *)pbuf_get_contiguous(p, &buf, sizeof(buf), sizeof(buf), offset);
+    if (opt_ia != NULL) {
+      u16_t ia_opt_offset = offset + sizeof(struct dhcp6_option_ia); /* Address option is inside ia option. */
+      const u16_t ia_addr_opt_len = dhcp6_find_option(p, DHCP6_OPTION_IAADDR, &ia_opt_offset);
+
+      if (ia_addr_opt_len >= sizeof(struct dhcp6_option_ia_address)) {
+        struct dhcp6_option_ia_address a_buf;
+        const struct dhcp6_option_ia_address *opt_ia_addr =
+                (struct dhcp6_option_ia_address *)pbuf_get_contiguous(
+                        p, &a_buf, sizeof(a_buf), sizeof(a_buf), ia_opt_offset);
+
+        if (opt_ia_addr != NULL) {
+          dhcp6->addr_t1 = lwip_ntohl(opt_ia->t1);
+          dhcp6->addr_t2 = lwip_ntohl(opt_ia->t2);
+
+          memcpy(&dhcp6->addr.addr, &opt_ia_addr->address, sizeof(opt_ia_addr->address));
+          dhcp6->addr_pref = lwip_ntohl(opt_ia_addr->preferred);
+          dhcp6->addr_valid = lwip_ntohl(opt_ia_addr->valid);
+
+          LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" %s t1=%"U32_F" t2=%"U32_F" prf=%"U32_F" val=%"U32_F"\n",
+                      ip6addr_ntoa(&dhcp6->addr), dhcp6->addr_t1, dhcp6->addr_t2, dhcp6->addr_pref, dhcp6->addr_valid));
+          return 1;
+        }
+      }
+    }
+  }
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" got nothing\n"));
+  return 0;
+}
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
+#if LWIP_IPV6_DHCP6_PD
+
+/* stores server id in to dhcp6 struct */
+static u8_t
+dhcp6_parse_ia_pd(struct pbuf *p, u16_t offset, struct dhcp6 *dhcp6)
+{
+  const s16_t ia_len = dhcp6_find_option(p, DHCP6_OPTION_IA_PD, &offset);
+
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" dhcp6_parse_ia_pd:"));
+
+  if (ia_len >= (s16_t)(sizeof(struct dhcp6_option_ia) + sizeof(struct dhcp6_option_ia_pd) + 4)) {
+    struct dhcp6_option_ia buf;
+    const struct dhcp6_option_ia *opt_ia =
+            (struct dhcp6_option_ia *)pbuf_get_contiguous(p, &buf, sizeof(buf), sizeof(buf), offset);
+
+    u16_t ia_opt_offset = offset + sizeof(struct dhcp6_option_ia); /* Address option is inside ia option. */
+    const s16_t ia_addr_opt_len = dhcp6_find_option(p, DHCP6_OPTION_IAPREFIX, &ia_opt_offset);
+
+    if (ia_addr_opt_len >= (s16_t)sizeof(struct dhcp6_option_ia_pd)) {
+      struct dhcp6_option_ia_pd p_buf;
+      const struct dhcp6_option_ia_pd *opt_ia_pref =
+              (const struct dhcp6_option_ia_pd *) pbuf_get_contiguous(
+                      p, &p_buf, sizeof(p_buf), sizeof(p_buf), ia_opt_offset);
+
+      dhcp6->ia_pd_t1 = lwip_ntohl(opt_ia->t1);
+      dhcp6->ia_pd_t2 = lwip_ntohl(opt_ia->t2);
+
+      memcpy(&dhcp6->prefix.prefix.addr, &opt_ia_pref->prefix, sizeof(opt_ia_pref->prefix));
+      dhcp6->prefix.prefix_len = opt_ia_pref->prefix_length;
+      dhcp6->prefix.prefix_pref = lwip_ntohl(opt_ia_pref->preferred);
+      dhcp6->prefix.prefix_valid = lwip_ntohl(opt_ia_pref->valid);
+
+      LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" %s/%"U16_F" t1=%"U32_F" t2=%"U32_F" prf=%"U32_F" val=%"U32_F"\n",
+              ip6addr_ntoa(&dhcp6->prefix.prefix), (u16_t)dhcp6->prefix.prefix_len,
+              dhcp6->ia_pd_t1, dhcp6->ia_pd_t1, dhcp6->prefix.prefix_pref, dhcp6->prefix.prefix_valid));
+
+      return 1;
+    }
+  }
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, (" got nothing\n"));
+  return 0;
+}
+#endif /* LWIP_IPV6_DHCP6_PD */
 
 static void
 dhcp6_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
@@ -702,6 +1353,7 @@ dhcp6_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr
   struct dhcp6_msg *reply_msg = (struct dhcp6_msg *)p->payload;
   u8_t msg_type;
   u32_t xid;
+  const u16_t opt_offset = sizeof(struct dhcp6_msg);
 
   LWIP_UNUSED_ARG(arg);
 
@@ -712,10 +1364,8 @@ dhcp6_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr
 
   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));
+  LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE,("dhcp6_recv(pbuf=%p len=%"U16_F", tot_len=%"U16_F") from DHCPv6 srv %s\n",
+                                            (void *)p, p->len, p->tot_len, ipaddr_ntoa(addr)));
   /* prevent warnings about unused arguments */
   LWIP_UNUSED_ARG(pcb);
   LWIP_UNUSED_ARG(addr);
@@ -735,36 +1385,169 @@ dhcp6_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr
                 ("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"));
+  switch (msg_type) {
+    case 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
+      /* in info-requesting state? */
+      switch (dhcp6->state) {
+        case DHCP6_STATE_SL_REQUESTING_CONFIG:
+#if LWIP_DHCP6_PROVIDE_DNS_SERVERS
+          dhcp6_parse_dns_servers(netif, p, opt_offset);
+#endif
+#if LWIP_DHCP6_GET_NTP_SRV
+          dhcp6_parse_sntp_servers(netif, p, opt_offset);
+#endif /* LWIP_DHCP6_PROVIDE_DNS_SERVERS */
+#ifdef LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM
+          dhcp6_parse_oro_opts_custom(netif, dhcp6, p, opt_offset);
+#endif
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_IDLE, "dhcp6_recv");
+          break;
 #endif /* LWIP_IPV6_DHCP6_STATELESS */
-    {
-      /* @todo: handle reply in other states? */
-    }
-  } else {
-    /* @todo: handle other message types */
-  }
+#if LWIP_IPV6_DHCP6_STATEFUL
+        case DHCP6_STATE_SF_RENEWING:
+        case DHCP6_STATE_SF_REQUESTING: {
+          u8_t success;
+          u8_t refused_by_srv = 0;
+          if (dhcp6_parse_and_check_serverid_different(p, opt_offset, dhcp6)) {
+            LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
+                        ("DHCPv6 reply has different server ID. Ignoring.\n"));
+            break; /* reply is not for us */
+          }
+#if LWIP_IPV6_DHCP6_PD
+          success = dhcp6_parse_ia_pd(p, opt_offset, dhcp6);
+          if (!success) {
+            set_prefix_not_delegated(dhcp6);
+            LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("Using DHCPv6 advertisement without prefix.\n"));
+          } else {
+            refused_by_srv = (dhcp6->prefix.prefix_valid == 0);
+            if (refused_by_srv) {
+              LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("DHCPv6 srv revoked delegated prefix (valid time is zero).\n"));
+            }
+          }
+#endif /* LWIP_IPV6_DHCP6_PD */
+
+#if LWIP_DHCP6_PROVIDE_DNS_SERVERS
+          dhcp6_parse_dns_servers(netif, p, opt_offset);
+#endif
+#if LWIP_DHCP6_GET_NTP_SRV
+          dhcp6_parse_sntp_servers(netif, p, opt_offset);
+#endif
+#ifdef LWIP_HOOK_DHCP6_PARSE_ORO_OPTS_CUSTOM
+          dhcp6_parse_oro_opts_custom(netif, dhcp6, p, opt_offset);
+#endif
+
+          if (!dhcp6->addr_by_slaac) {
+            success = dhcp6_parse_ia_na(p, opt_offset, dhcp6);
+            if (!success) {
+              set_addr_not_advertised(dhcp6);
+            } else {
+              LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("Using DHCPv6 advertisement without address\n"));
+              netif_add_ip6_address(netif, &dhcp6->addr, &dhcp6->addr_idx);
+              if (dhcp6->addr_idx >= 0) {
+                netif_ip6_addr_set_pref_life(netif, dhcp6->addr_idx, dhcp6->addr_pref);
+                netif_ip6_addr_set_valid_life(netif, dhcp6->addr_idx, dhcp6->addr_valid);
+                if (dhcp6->addr_valid <= 0) {
+                  /* Server is refusing our renew request. */
+                  refused_by_srv = 1;
+                  netif_ip6_addr_set_state(netif, dhcp6->addr_idx, IP6_ADDR_INVALID);
+                } else if (ip6_addr_isvalid(netif_ip6_addr_state(netif, 1))) {
+                  netif_ip6_addr_set_state(netif, dhcp6->addr_idx, IP6_ADDR_PREFERRED);
+                } else {
+                  netif_ip6_addr_set_state(netif, dhcp6->addr_idx, IP6_ADDR_TENTATIVE);
+                }
+              } else {
+                LWIP_ASSERT("Not enough space for IPv6 add from DHCP.", dhcp6->addr_idx < 0);
+              }
+            }
+          } else {
+            set_addr_not_advertised(dhcp6);
+          }
+#if LWIP_IPV6_DHCP6_PD
+          if (dhcp6->pd_callback != NULL) {
+            dhcp6->pd_callback(netif, &dhcp6->prefix, (dhcp6->prefix.prefix_valid > 0));
+          }
+
+          /*dhcp6_set_state(netif, dhcp6, valid ? DHCP6_STATE_SF_BOUND, "dhcp6_recv"); toto nejak vykut - potrebujeme se dostat do soliciting pokud jakykoliv valid time = 0 a
+          a zahlasit to callbackem o prefixu.*/
+
+#endif /* LWIP_IPV6_DHCP6_PD */
+          dhcp6_set_state(netif, dhcp6, refused_by_srv ? DHCP6_STATE_SF_SOLICITING : DHCP6_STATE_SF_BOUND, "dhcp6_recv");
+          break;
+        } /* case DHCP6_STATE_SF_REQUESTING */
+
+        case DHCP6_STATE_SF_RELEASING:
+          if (dhcp6_parse_and_check_serverid_different(p, opt_offset, dhcp6)) {
+            LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
+                        ("Ignoring reply with different server ID.\n"));
+            break; /* reply is not for us */
+          }
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_OFF, "dhcp6_recv");
+
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
+        default:
+          break;
+      } /* switch (dhcp6->state) */
+
+      break;
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+    case DHCP6_ADVERTISE:
+      if (dhcp6->state == DHCP6_STATE_SF_SOLICITING) {
+        /* TODO handle rapid commit option, handle preference option, seek for the best server. */
+        int success = dhcp6_parse_and_store_serverid(p, opt_offset, dhcp6);
+        if (!success) {
+          LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("DHCP6_ADVERTISE without server id\n"));
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_SOLICITING, "dhcp6_recv");
+          break;
+        }
+
+#if LWIP_IPV6_DHCP6_PD
+        success = dhcp6_parse_ia_pd(p, opt_offset, dhcp6);
+        if (!success) {
+          set_prefix_not_delegated(dhcp6);
+          LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("Using dhcp6 advertisement without prefix\n"));
+        }
+#endif /* LWIP_IPV6_DHCP6_PD */
+
+        if (!dhcp6->addr_by_slaac) {
+          success = dhcp6_parse_ia_na(p, opt_offset, dhcp6);
+          if (!success) {
+            set_addr_not_advertised(dhcp6);
+            LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("Using dhcp6 advertisement without address\n"));
+          }
+        } else {
+          set_addr_not_advertised(dhcp6);
+        }
+        dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_REQUESTING, "dhcp6_recv");
+      }
+      break;
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
+    default:
+      break;
+
+  } /* switch (msg_type) */
 
 free_pbuf_and_return:
   pbuf_free(p);
 }
 
+static void
+dhcp6_update_retrans_timeout(struct dhcp6 *dhcp6)
+{
+  dhcp6->request_timeout *= 2;
+  if (dhcp6->request_timeout > dhcp6->max_request_timeout) {
+    dhcp6->request_timeout = dhcp6->max_request_timeout;
+  }
+  dhcp6->request_timeout_cntr = dhcp6->request_timeout;
+}
+
 /**
  * A DHCPv6 request has timed out.
  *
@@ -779,15 +1562,141 @@ dhcp6_timeout(struct netif *netif, struct dhcp6 *dhcp6)
   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);
+  switch (dhcp6->state) {
+
+    case DHCP6_STATE_OFF:
+    case DHCP6_STATE_IDLE:
+      break;
+    case DHCP6_STATE_SL_REQUESTING_CONFIG:
+      LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_timeout: retrying information request\n"));
+      dhcp6_information_request(netif, dhcp6);
+      break;
+#if LWIP_IPV6_DHCP6_STATEFUL
+    case DHCP6_STATE_SF_INIT_DELAY:
+      dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_SOLICITING, "dhcp6_timeout from INIT_DELAY");
+      break;
+    case DHCP6_STATE_SF_SOLICITING:
+      LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_timeout: retrying solicitation\n"));
+      dhcp6_solicitation(netif, dhcp6);
+      break;
+    case DHCP6_STATE_SF_REQUESTING:
+      if (dhcp6->tries >= dhcp6->max_retrans_cnt) {
+        dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_SOLICITING, "dhcp6_timeout from REQUESTING");
+      } else {
+        LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_timeout: retrying request\n"));
+        dhcp6_request(netif, dhcp6);
+      }
+      break;
+
+    case DHCP6_STATE_SF_BOUND:
+      break;
+
+    case DHCP6_STATE_SF_RENEWING:
+      LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_timeout: retrying renew request\n"));
+      dhcp6_renew(netif, dhcp6);
+      break;
+    case DHCP6_STATE_SF_REBINDING:
+      dhcp6_rebind(netif, dhcp6);
+      break;
+    case DHCP6_STATE_SF_RELEASING:
+      if (dhcp6->tries >= dhcp6->max_retrans_cnt) {
+        dhcp6_set_state(netif, dhcp6, DHCP6_STATE_OFF, "dhcp6_timeout from RELEASING");
+      } else {
+        LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_timeout: retrying release request\n"));
+        dhcp6_release(netif, dhcp6);
+      }
+      break;
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+    default:
+      LWIP_ASSERT("Unhandled state in dhcp6_timeout", 0);
   }
-#endif /* LWIP_IPV6_DHCP6_STATELESS */
+
+  dhcp6_update_retrans_timeout(dhcp6);
+
+/*#endif  LWIP_IPV6_DHCP6_STATELESS */
 }
 
+#if LWIP_IPV6_DHCP6_STATEFUL
+static void
+dhcp6_1s_tick(struct netif * netif, struct dhcp6 *dhcp6) {
+
+  switch (dhcp6->state) {
+    case DHCP6_STATE_SF_BOUND:
+
+#if LWIP_IPV6_DHCP6_PD
+      if (!is_prefix_advertised(dhcp6)) {
+        /* nothing to do */
+      }
+      else if (dhcp6->prefix.prefix_pref == 1) {
+        dhcp6->prefix.prefix_pref = 0;
+        /* Valid time of prefix expired */
+        dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_RENEWING, "dhcp6_1s_tick");
+        break;
+      } else if (dhcp6->prefix.prefix_pref > 0) {
+        dhcp6->prefix.prefix_pref--;
+      }
+#endif /* LWIP_IPV6_DHCP6_PD */
+
+      if (is_addr_advertised(dhcp6)) {
+        if (dhcp6->addr_t1 == 1) {
+          dhcp6->addr_t1 = 0;
+          /* Valid time of prefix expired */
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_RENEWING, "dhcp6_1s_tick");
+          break;
+        } else if (dhcp6->addr_t1 > 0) {
+          dhcp6->addr_t1--;
+        }
+
+        if (netif_ip6_addr_pref_life(netif, dhcp6->addr_idx) == 0) {
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_RENEWING, "dhcp6_1s_tick");
+          break;
+        }
+      }
+
+      /* Intentional fall through */
+
+    case DHCP6_STATE_SF_RENEWING:
+#if LWIP_IPV6_DHCP6_PD
+      if (!is_prefix_advertised(dhcp6)) {
+        /* nothing to do */
+      }
+      else if (dhcp6->prefix.prefix_valid == 1) {
+        dhcp6->prefix.prefix_valid = 0;
+        /* Valid time of prefix expired */
+        if (dhcp6->pd_callback != NULL) {
+          dhcp6->pd_callback(netif, &dhcp6->prefix, 0);
+        }
+        dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_SOLICITING, "dhcp6_1s_tick");
+        break;
+      } else if (dhcp6->prefix.prefix_valid > 0) {
+        dhcp6->prefix.prefix_valid--;
+      }
+#endif /* LWIP_IPV6_DHCP6_PD */
+
+      if (is_addr_advertised(dhcp6)) {
+        if (dhcp6->addr_t2 == 1) {
+          dhcp6->addr_t2 = 0;
+          /* Valid time of prefix expired */
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_RENEWING, "dhcp6_1s_tick");
+          break;
+        } else if (dhcp6->addr_t2 > 0) {
+          dhcp6->addr_t2--;
+        }
+
+        if (netif_ip6_addr_valid_life(netif, dhcp6->addr_idx) == 0) {
+          dhcp6_set_state(netif, dhcp6, DHCP6_STATE_SF_SOLICITING, "dhcp6_1s_tick");
+          break;
+        }
+      }
+      break;
+
+    default:
+      break;
+  }
+}
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
+
 /**
  * DHCPv6 timeout handling (this function must be called every 500ms,
  * see @ref DHCP6_TIMER_MSECS).
@@ -799,23 +1708,41 @@ void
 dhcp6_tmr(void)
 {
   struct netif *netif;
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+  u8_t second_elapsed = 0;
+  static u16_t ticks_ms = 0;
+  if (ticks_ms >= 1000) {
+    ticks_ms -= 1000;
+    second_elapsed = 1;
+  }
+  ticks_ms += DHCP6_TIMER_MSECS;
+#endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
   /* 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--;
+#if LWIP_IPV6_DHCP6_STATEFUL
+      if (second_elapsed) {
+        dhcp6_1s_tick(netif, dhcp6);
+      }
+#endif
+
+      /* timer is active (non-zero), and is about to trigger now */
+      if (dhcp6->request_timeout_cntr > 1) {
+        dhcp6->request_timeout_cntr--;
+      } else if (dhcp6->request_timeout_cntr == 1) {
+        dhcp6->request_timeout_cntr--;
         /* { dhcp6->request_timeout == 0 } */
-        LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp6_tmr(): request timeout\n"));
+        LWIP_DEBUGF(DHCP6_DEBUG | LWIP_DBG_TRACE, ("dhcp6_tmr: request timeout\n"));
         /* this client's request timeout triggered */
         dhcp6_timeout(netif, dhcp6);
       }
     }
-  }
+  } /* NETIF_FOREACH() */
 }
 
+
 #endif /* LWIP_IPV6 && LWIP_IPV6_DHCP6 */
diff --git a/src/include/lwip/dhcp6.h b/src/include/lwip/dhcp6.h
index 5cc4a015..71031b98 100644
--- a/src/include/lwip/dhcp6.h
+++ b/src/include/lwip/dhcp6.h
@@ -8,6 +8,7 @@
 
 /*
  * Copyright (c) 2018 Simon Goldschmidt
+ * Copyright 2023 NXP
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without modification,
@@ -54,6 +55,24 @@ extern "C" {
 /** period (in milliseconds) of the application calling dhcp6_tmr() */
 #define DHCP6_TIMER_MSECS   500
 
+#if LWIP_IPV6_DHCP6_STATEFUL
+void dhcp6_set_client_id_duid(struct netif *netif, const void *duid, u16_t duid_len);
+#endif
+
+#if LWIP_IPV6_DHCP6_PD
+struct dhcp6_delegated_prefix
+{
+  ip6_addr_t prefix;
+  u8_t prefix_len;
+  u32_t prefix_pref; /* Prefix preferred lifetime [s] */
+  u32_t prefix_valid; /* Prefix valid lifetime [s] */
+};
+
+typedef void (*dhcp6_pd_callback_fn)(struct netif * netif, const struct dhcp6_delegated_prefix * prefix, u8_t valid);
+void dhcp6_register_pd_callback(struct netif *netif, dhcp6_pd_callback_fn callback);
+const struct dhcp6_delegated_prefix * dhcp6_get_delegated_prefix(struct netif * netif);
+#endif
+
 struct dhcp6
 {
   /** transaction identifier of last sent request */
@@ -62,15 +81,48 @@ struct dhcp6
   u8_t pcb_allocated;
   /** current DHCPv6 state machine state */
   u8_t state;
+  /** State after releasing of address is done */
+  u8_t state_after_release;
   /** retries of current request */
   u8_t tries;
-  /** if request config is triggered while another action is active, this keeps track of it */
-  u8_t request_config_pending;
   /** #ticks with period DHCP6_TIMER_MSECS for request timeout */
+  u16_t request_timeout_cntr;
   u16_t request_timeout;
+
+  /** upper bound of request timeout in #ticks */
+  u16_t max_request_timeout;
+
 #if LWIP_IPV6_DHCP6_STATEFUL
-  /* @todo: add more members here to keep track of stateful DHCPv6 data, like lease times */
+  /** Maximal count of retransmissions */
+  u8_t max_retrans_cnt;
+    /** Maximal duration of retransmissions */
+  u16_t max_retrans_dur;
+
+  u32_t trans_started_at;
+  void *server_id; /* Dynamically allocated mem with server id including its option header */
+  u8_t server_id_len; /* Length of server id including its option header */
+
+  const void *cli_id_usr_duid; /* Client ID DUID supplied by user */
+  u16_t cli_id_usr_duid_len; /* Lenght of client ID DUID supplied by user */
+
+  ip6_addr_t addr;
+  s8_t  addr_idx;
+  u32_t addr_pref;
+  u32_t addr_valid;
+  u32_t addr_t1;
+  u32_t addr_t2;
+
+  u8_t addr_by_slaac;
+
 #endif /* LWIP_IPV6_DHCP6_STATEFUL */
+
+#if LWIP_IPV6_DHCP6_PD
+  struct dhcp6_delegated_prefix prefix;
+  u32_t ia_pd_t1;
+  u32_t ia_pd_t2;
+
+  dhcp6_pd_callback_fn pd_callback;
+#endif /* LWIP_IPV6_DHCP6_PD  */
 };
 
 void dhcp6_set_struct(struct netif *netif, struct dhcp6 *dhcp6);
@@ -78,8 +130,7 @@ void dhcp6_set_struct(struct netif *netif, struct dhcp6 *dhcp6);
 #define dhcp6_remove_struct(netif) netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6, NULL)
 void dhcp6_cleanup(struct netif *netif);
 
-err_t dhcp6_enable_stateful(struct netif *netif);
-err_t dhcp6_enable_stateless(struct netif *netif);
+err_t dhcp6_enable(struct netif *netif);
 void dhcp6_disable(struct netif *netif);
 
 void dhcp6_tmr(void);
diff --git a/src/include/lwip/prot/dhcp6.h b/src/include/lwip/prot/dhcp6.h
index 0754c91b..29ada921 100644
--- a/src/include/lwip/prot/dhcp6.h
+++ b/src/include/lwip/prot/dhcp6.h
@@ -5,6 +5,7 @@
 
 /*
  * Copyright (c) 2017 Simon Goldschmidt <goldsimon@gmx.de>
+ * Copyright 2023 NXP
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without modification,
@@ -32,13 +33,16 @@
  * This file is part of the lwIP TCP/IP stack.
  *
  * Author: Simon Goldschmidt <goldsimon@gmx.de>
- *
  */
 #ifndef LWIP_HDR_PROT_DHCP6_H
 #define LWIP_HDR_PROT_DHCP6_H
 
 #include "lwip/opt.h"
 
+#if LWIP_IPV6_DHCP6_STATEFUL
+#include "lwip/ip6.h"
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -66,12 +70,93 @@ PACK_STRUCT_END
 #  include "arch/epstruct.h"
 #endif
 
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+/** minimum set of fields of any DHCPv6 message */
+struct dhcp6_option_header
+{
+  PACK_STRUCT_FIELD(u16_t option_code);
+  PACK_STRUCT_FIELD(u16_t option_len);
+  /* options follow */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct dhcp6_option_ia_address
+{
+    PACK_STRUCT_FLD_S(ip6_addr_p_t address);
+    PACK_STRUCT_FIELD(u32_t preferred);
+    PACK_STRUCT_FIELD(u32_t valid);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+#endif  /* LWIP_IPV6_DHCP6_STATEFUL */
+
+#if LWIP_IPV6_DHCP6_PD
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+/** minimum set of fields of any DHCPv6 message */
+struct dhcp6_option_ia_pd
+{
+  PACK_STRUCT_FIELD(u32_t preferred);
+  PACK_STRUCT_FIELD(u32_t valid);
+  PACK_STRUCT_FLD_8(u8_t prefix_length);
+  PACK_STRUCT_FLD_S(ip6_addr_p_t prefix);
+  /* options follows */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+#endif /* LWIP_IPV6_DHCP6_PD */
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+/** minimum set of fields of any DHCPv6 message */
+struct dhcp6_option_ia /*TODO*/
+{
+  PACK_STRUCT_FIELD(u32_t ia_id);
+  PACK_STRUCT_FIELD(u32_t t1);
+  PACK_STRUCT_FIELD(u32_t t2);
+  /* options follow */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
 
 /* DHCP6 client states */
 typedef enum {
-  DHCP6_STATE_OFF               = 0,
-  DHCP6_STATE_STATELESS_IDLE    = 1,
-  DHCP6_STATE_REQUESTING_CONFIG = 2
+  DHCP6_STATE_OFF = 0,
+  DHCP6_STATE_IDLE,
+
+  DHCP6_STATE_STATELESS=64,
+  DHCP6_STATE_SL_REQUESTING_CONFIG,
+
+  DHCP6_STATE_STATEFULL=128,
+  DHCP6_STATE_SF_INIT_DELAY,
+  DHCP6_STATE_SF_SOLICITING,
+  DHCP6_STATE_SF_REQUESTING,
+  DHCP6_STATE_SF_BOUND,
+  DHCP6_STATE_SF_RENEWING,
+  DHCP6_STATE_SF_REBINDING,
+  DHCP6_STATE_SF_RELEASING
 } dhcp6_state_enum_t;
 
 /* DHCPv6 message types */
@@ -128,9 +213,37 @@ typedef enum {
 /* More options see https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml */
 #define DHCP6_OPTION_DNS_SERVERS    23 /* RFC 3646 */
 #define DHCP6_OPTION_DOMAIN_LIST    24 /* RFC 3646 */
+#define DHCP6_OPTION_IA_PD          25 /*	RFC3633 RFC8415 */
+#define DHCP6_OPTION_IAPREFIX       26 /*	RFC3633 RFC8415 */
 #define DHCP6_OPTION_SNTP_SERVERS   31 /* RFC 4075 */
 
 
+/* DHCP default parametters */
+#define DHCP6_PAR_SOL_MAX_DELAY     1 /*[s] Max delay of first Solicit */
+#define DHCP6_PAR_SOL_TIMEOUT       1 /*[s] Initial Solicit timeout */
+#define DHCP6_PAR_SOL_MAX_RT      120 /*[s] Max Solicit timeout value */
+#define DHCP6_PAR_REQ_TIMEOUT       1 /*[s] Initial Request timeout */
+#define DHCP6_PAR_REQ_MAX_RT       30 /*[s] Max Request timeout value */
+#define DHCP6_PAR_REQ_MAX_RC       10 /*    Max Request retry attempts */
+#define DHCP6_PAR_CNF_MAX_DELAY     1 /*[s] Max delay of first Confirm */
+#define DHCP6_PAR_CNF_TIMEOUT       1 /*[s] Initial Confirm timeout */
+#define DHCP6_PAR_CNF_MAX_RT        4 /*[s] Max Confirm timeout */
+#define DHCP6_PAR_CNF_MAX_RD       10 /*[s] Max Confirm duration */
+#define DHCP6_PAR_REN_TIMEOUT      10 /*[s] Initial Renew timeout */
+#define DHCP6_PAR_REN_MAX_RT      600 /*[s] Max Renew timeout value */
+#define DHCP6_PAR_REB_TIMEOUT      10 /*[s] Initial Rebind timeout */
+#define DHCP6_PAR_REB_MAX_RT      600 /*[s] Max Rebind timeout value */
+#define DHCP6_PAR_INF_MAX_DELAY     1 /*[s] Max delay of first Information-request */
+#define DHCP6_PAR_INF_TIMEOUT       1 /*[s] Initial Information-request timeout */
+#define DHCP6_PAR_INF_MAX_RT      120 /*[s] Max Information-request timeout value */
+#define DHCP6_PAR_REL_TIMEOUT       1 /*[s] Initial Release timeout */
+#define DHCP6_PAR_REL_MAX_RC        5 /*    MAX Release attempts */
+#define DHCP6_PAR_DEC_TIMEOUT       1 /*[s] Initial Decline timeout */
+#define DHCP6_PAR_DEC_MAX_RC        5 /*    Max Decline attempts */
+#define DHCP6_PAR_REC_TIMEOUT       2 /*[s] Initial Reconfigure timeout */
+#define DHCP6_PAR_REC_MAX_RC        8 /*    Max Reconfigure attempts */
+#define DHCP6_PAR_HOP_COUNT_LIMIT  32 /*    Max hop count in a Relay-forward message */
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/test/unit/Filelists.cmake b/test/unit/Filelists.cmake
index b3db893c..cc513e16 100644
--- a/test/unit/Filelists.cmake
+++ b/test/unit/Filelists.cmake
@@ -24,6 +24,7 @@ set(LWIP_TESTFILES
 	${LWIP_TESTDIR}/core/test_pbuf.c
 	${LWIP_TESTDIR}/core/test_timers.c
 	${LWIP_TESTDIR}/dhcp/test_dhcp.c
+	${LWIP_TESTDIR}/dhcp6/test_dhcp6.c
 	${LWIP_TESTDIR}/etharp/test_etharp.c
 	${LWIP_TESTDIR}/ip4/test_ip4.c
 	${LWIP_TESTDIR}/ip6/test_ip6.c
diff --git a/test/unit/Filelists.mk b/test/unit/Filelists.mk
index c33e1b04..ce3b643a 100644
--- a/test/unit/Filelists.mk
+++ b/test/unit/Filelists.mk
@@ -40,6 +40,7 @@ TESTFILES=$(TESTDIR)/lwip_unittests.c \
 	$(TESTDIR)/core/test_pbuf.c \
 	$(TESTDIR)/core/test_timers.c \
 	$(TESTDIR)/dhcp/test_dhcp.c \
+	$(TESTDIR)/dhcp6/test_dhcp6.c \
 	$(TESTDIR)/etharp/test_etharp.c \
 	$(TESTDIR)/ip4/test_ip4.c \
 	$(TESTDIR)/ip6/test_ip6.c \
diff --git a/test/unit/dhcp6/test_dhcp6.c b/test/unit/dhcp6/test_dhcp6.c
new file mode 100644
index 00000000..295fdf45
--- /dev/null
+++ b/test/unit/dhcp6/test_dhcp6.c
@@ -0,0 +1,1536 @@
+/*
+ * Copyright 2023 NXP
+ * 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.
+ *
+ */
+
+#include "test_dhcp6.h"
+
+#include "lwip/netif.h"
+#include "lwip/dhcp6.h"
+#include "lwip/nd6.h"
+#include "lwip/prot/dhcp6.h"
+#include "lwip/etharp.h"
+#include "netif/ethernet.h"
+#include "lwip/dns.h"
+#include "lwip/apps/sntp.h"
+
+static const uint16_t OPT_NOT_FOUND = 0xffffU;
+static const uint16_t IP6_WRONG_ZONE = 0xfaU;
+
+enum pkt_opts {
+    OPT_CLI_ID = 1,
+    OPT_SRV_ID = 1<<1,
+    OPT_IA_PD  = 1<<2,
+    OPT_IA_NA  = 1<<3,
+    OPT_DNS_SRV = 1<<4,
+    OPT_DOMAIN_SRCH_LST = 1<<5,
+    OPT_SNTP_SRV = 1<<6,
+    OPT_IA_PD_0  = 1<<7,
+    OPT_IA_NA_0  = 1<<8
+};
+
+enum dhcp_msg_type {
+    DHCP_MSG_ADVERTISE = 2,
+    DHCP_MSG_REPLY = 7
+};
+
+static const unsigned pkt_ip_len_pos = 18;
+static const unsigned pkt_udp_len_pos = 58;
+
+static uint8_t pkt_headers[] = {
+        /* 00 */0x00,0x23,0xc1,0xde,0xd0,0x0d, /*dst mac*/
+        /* 06 */0x08,0x00,0x27,0xd4,0x10,0xbb, /*src mac*/
+        /* 12 */0x86,0xdd, /*type ipv6*/
+        /* 14 */0x60,0x00,0x00,0x00,
+        /* 18 */0x00,0x5d, /* IPv6 payload length */
+        /* 20 */0x11,0x40,
+        /* 22 */0xfe,0x80,0x00,0x00, 0x00,0x00,0x00,0x00, 0x0a,0x00,0x27,0xff, 0xfe,0xd4,0x10,0xbb, /*src ip*/
+        /* 38 */0xfe,0x80,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x23, 0xc1,0xde,0xd0,0x0d, /*dst ip*/
+        /* 54 */0x02,0x23, 0x02,0x22, /* src, dst ports*/
+        /* 58 */0x00,0x5d, /* UDP packet length including UDP header */
+        /* 60 *//*0xa1,0x3d,*/ 0x00,0x00, /* UDP chksum 0 = no checksum */
+};
+
+static uint8_t pkt_dhcp_advertise[] = {
+        0x02
+};
+
+static uint8_t pkt_dhcp_reply[] = {
+        0x07
+};
+
+static uint8_t pkt_xid[] = { /* Transaction id */
+        0x00, 0x00, 0x00
+};
+
+static uint8_t pkt_server_id[] = {
+        0x00, 0x02, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x38, 0x25, 0xe8, 0x08, 0x00, 0x27, 0xd4, 0x10,
+        0xbb
+};
+
+#if LWIP_IPV6_DHCP6_STATEFUL
+static const uint8_t pkt_client_id_custom_duid[] = {
+        0x00, 0x04, 0xac, 0x26, 0x6e, 0xf2, 0xfd, 0x37,
+        0xa9, 0x85, 0x61, 0x0c, 0x57, 0x05, 0x92, 0x59,
+        0x1a, 0x4c
+};
+#endif
+
+
+
+static unsigned pkt_client_id_len = 0;
+static uint8_t pkt_client_id[128];
+
+static const unsigned pkt_iaid_pos = 4;
+
+static uint8_t pkt_ia_pd[] = {
+        0x00,0x19,0x00,0x29,
+        0x27,0xfe,0x8f,0x95, /*IAID*/
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+        /*IA Prefix:*/
+        0x00,0x1a,0x00,0x19,
+        0x00,0x00,0x11,0x94, /* Preferred time  4500*/
+        0x00,0x00,0x1c,0x20, /* Valid time 7200 */
+        0x40, /*Prefix length 64*/
+        0x20,0x01, 0x00,0x00, 0x00,0x00, 0xfe,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 /* 2001:0000:0000:fe00::*/
+};
+
+static uint8_t pkt_ia_pd_0[] = {
+        0x00,0x19,0x00,0x29,
+        0x27,0xfe,0x8f,0x95, /*IAID*/
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+        /*IA Prefix:*/
+        0x00,0x1a,0x00,0x19,
+        0x00,0x00,0x00,0x00, /* Preferred time 0*/
+        0x00,0x00,0x00,0x00, /* Valid time 0 == request refused*/
+        0x40, /*Prefix length 64*/
+        0x20,0x01, 0x00,0x00, 0x00,0x00, 0xfe,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 /* 2001:0000:0000:fe00::*/
+};
+
+static uint8_t pkt_ia_na[] = {
+        0x00,0x03,0x00,0x28,
+        0x1d,0x00,0xfc,0xea, /* IAID */
+        0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* T1, T2 */
+
+        /*IA address:*/
+        0x00,0x05, 0x00,0x18,
+        0x20,0x01, 0x0d,0xba, 0x01,0xA0, 0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8, 0xA9,0xA0,
+        /* 2001:0dba:01a0:a1a2:a3a4:a5a6:a7a8:a9a0 */
+
+        0x00,0x00,0x01,0x77, /* Preferred time 375s */
+        0x00,0x00,0x02,0x58  /* Valid time 600 */
+};
+
+static uint8_t pkt_ia_na_0[] = {
+        0x00,0x03,0x00,0x28,
+        0x1d,0x00,0xfc,0xea, /* IAID */
+        0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* T1, T2 */
+
+        /*IA address:*/
+        0x00,0x05, 0x00,0x18,
+        0x20,0x01, 0x0d,0xba, 0x01,0xA0, 0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8, 0xA9,0xA0,
+        /* 2001:0dba:01a0:a1a2:a3a4:a5a6:a7a8:a9a0 */
+
+        0x00,0x00,0x00,0x00, /* Preferred time 0s */
+        0x00,0x00,0x00,0x00  /* Valid time 0 == request refused */
+};
+
+static uint8_t pkt_dns_srv[] = {
+        0x00, 0x17, 0x00, 0x10, 0x20, 0x01, 0x0d, 0xba,
+        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x01
+};
+
+static uint8_t pkt_domain_srch_lst[] = {
+        0x00, 0x18, 0x00, 0x0d,
+        0x05,
+        0x63, 0x69, 0x73, 0x63, 0x6f, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, /* cisco.local. */
+        0x00
+};
+
+static uint8_t pkt_sntp_srv[] = {
+        0x00, 0x1F, 0x00, 0x20,
+        0x20, 0x01, 0x0d, 0xba, 0x01, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+        0x20, 0x01, 0x0d, 0xba, 0x01, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03
+};
+
+extern u32_t lwip_sys_now;
+
+static struct netif net_test;
+
+static int allow_override_client_id = 0;
+
+static uint32_t elapsed_ms = 0;
+
+static void tick_500ms_lwip(void)
+{
+  lwip_sys_now += 500;
+  if (lwip_sys_now % 1000 == 0) {
+    nd6_tmr();  /* call every 1s */
+  }
+  dhcp6_tmr(); /* call every 500ms */
+
+}
+
+void dhcp6_oro_hook(const struct netif * netif, u16_t type, uint8_t state, const struct pbuf * p, uint16_t offset, uint16_t opt_len);
+void dhcp6_oro_hook(const struct netif * netif, u16_t type, uint8_t state, const struct pbuf * p, uint16_t offset, uint16_t opt_len) {
+  (void)state;
+
+  opt_len += 4; /* Add header size */
+
+  fail_if(netif != &net_test);
+
+  fail_if(type != 24, "dhcp6_oro_hook: expected option type 24 got %u", type);
+
+  fail_if(opt_len != sizeof(pkt_domain_srch_lst), "dhcp6_oro_hook: opt_len (%u) != (%u) sizeof(pkt_domain_srch_lst)",
+          opt_len, (uint16_t)sizeof(pkt_domain_srch_lst));
+
+  fail_if(pbuf_memcmp(p, offset-4, pkt_domain_srch_lst, opt_len));
+}
+
+static err_t lwip_tx_func(struct netif *netif, struct pbuf *p);
+static err_t output_ip6_chk_no_send(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr);
+static void reset_client_id(void);
+
+static const char* msg_type_to_str(unsigned type)
+{
+  static char buf[32];
+
+  switch(type) {
+    case 1:   return "1 (SOLICIT)";
+    case 2:   return "2 (ADVERTISE)";
+    case 3:   return "3 (REQUEST)";
+    case 4:   return "4 (CONFIRM)";
+    case 5:   return "5 (RENEW)";
+    case 6:   return "6 (REBIND)";
+    case 7:   return "7 (REPLY)";
+    case 8:   return "8 (RELEASE)";
+    case 9:   return "9 (DECLINE)";
+    case 10:  return "10 (RECONFIGURE)";
+    case 11:  return "11 (INFOREQUEST)";
+    case 12:  return "12 (RELAYFORW)";
+    case 13:  return "13 (RELAYREPL)";
+    default:
+      snprintf(buf, sizeof(buf), "%u (unknown)", type);
+      return buf;
+  }
+}
+
+static const char* addr_state_to_str(uint8_t state)
+{
+  if (ip6_addr_isinvalid(state)) {
+    return "invalid";
+  } else if (ip6_addr_ispreferred(state)) {
+    return "valid preferred";
+  } else if (ip6_addr_isdeprecated(state)) {
+    return "valid deprecated";
+  } else if (ip6_addr_istentative(state)) {
+    return "tentative";
+  } else if (ip6_addr_isduplicated(state)) {
+    return "duplicated";
+  } else {
+    static char buf[32];
+    snprintf(buf, sizeof(buf), "unhandled state 0x%x", state);
+    return buf;
+  }
+}
+
+static void reset_dns_servers(void)
+{
+  int i;
+
+  ip_addr_t wrong_addr = ip_addr_any;
+  wrong_addr.u_addr.ip6.zone = IP6_WRONG_ZONE;
+
+  for(i=0; i<LWIP_DHCP6_MAX_DNS_SERVERS; i++) {
+    dns_setserver(i, &wrong_addr);
+  }
+}
+
+static void reset_sntp_servers(void)
+{
+  int i;
+
+  ip_addr_t wrong_addr = ip_addr_any;
+  wrong_addr.u_addr.ip6.zone = IP6_WRONG_ZONE;
+
+  sntp_servermode_dhcp(1);
+
+  for(i=0; i<SNTP_MAX_SERVERS; i++) {
+    sntp_setserver(i, &wrong_addr);
+  }
+}
+
+
+
+static err_t testif_init(struct netif *netif)
+{
+  netif->name[0] = 'c';
+  netif->name[1] = 'h';
+  netif->output = etharp_output;
+  netif->linkoutput = lwip_tx_func;
+  netif->output_ip6 = output_ip6_chk_no_send;
+  netif->mtu = 1500;
+  netif->hwaddr_len = 6;
+  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
+
+  netif->hwaddr[0] = 0x00;
+  netif->hwaddr[1] = 0x23;
+  netif->hwaddr[2] = 0xC1;
+  netif->hwaddr[3] = 0xDE;
+  netif->hwaddr[4] = 0xD0;
+  netif->hwaddr[5] = 0x0D;
+
+  return ERR_OK;
+}
+
+static void dhcp6_setup(void)
+{
+  /*lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));*/
+
+  reset_client_id();
+  allow_override_client_id = 0;
+  lwip_sys_now = 0;
+
+  netif_add(&net_test, NULL, NULL, NULL, &net_test, testif_init, ethernet_input);
+  netif_set_link_up(&net_test);
+  netif_set_up(&net_test);
+
+  netif_create_ip6_linklocal_address(&net_test, 0);
+  netif_ip6_addr_set_state(&net_test, 0, IP6_ADDR_PREFERRED);
+
+  net_test.output_ip6 = &output_ip6_chk_no_send;
+}
+
+static void dhcp6_teardown(void)
+{
+  /*lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));*/
+  netif_remove(&net_test);
+}
+
+static err_t lwip_tx_func(struct netif *netif, struct pbuf *p)
+{
+  fail_unless(netif == &net_test);
+  fail_unless(p == NULL);
+
+  return ERR_OK;
+}
+
+static void send_pkt(struct netif *netif, enum dhcp_msg_type type, int opts)
+{
+  struct pbuf *p, *q;
+  static uint8_t buf[512];
+  uint8_t *p_buf = buf;
+  size_t len = 0;
+  uint16_t udp_pkt_with_its_hdr_len;
+
+  memset(buf, 1, sizeof(buf));
+  memcpy((void*)(&buf[len]), pkt_headers, sizeof(pkt_headers));
+  len += sizeof(pkt_headers);
+
+  switch (type) {
+    case DHCP_MSG_ADVERTISE:
+      memcpy((void *) (&buf[len]), pkt_dhcp_advertise, sizeof(pkt_dhcp_advertise));
+      len += sizeof(pkt_dhcp_advertise);
+      break;
+    case DHCP_MSG_REPLY:
+      memcpy((void *) (&buf[len]), pkt_dhcp_reply, sizeof(pkt_dhcp_reply));
+      len += sizeof(pkt_dhcp_reply);
+      break;
+    default:
+      fail("Unhandled DHCP msg type");
+  }
+
+  memcpy((void*)(&buf[len]), pkt_xid, sizeof(pkt_xid));
+  len += sizeof(pkt_xid);
+
+
+  if (opts & OPT_CLI_ID) {
+    fail_unless(pkt_client_id_len != 0, "Append of client id to msg requested but no client id received so far");
+    memcpy((void*)(&buf[len]), pkt_client_id, pkt_client_id_len);
+    fail_if(pkt_client_id[3] != pkt_client_id_len - 4);
+    len += pkt_client_id_len;
+  }
+
+  if (opts & OPT_SRV_ID) {
+    memcpy((void*)(&buf[len]), pkt_server_id, sizeof(pkt_server_id));
+    fail_if(pkt_server_id[3] != sizeof(pkt_server_id)-4);
+    len += sizeof(pkt_server_id);
+  }
+
+  if (opts & OPT_IA_NA) {
+    memcpy((void*)(&buf[len]), pkt_ia_na, sizeof(pkt_ia_na));
+    fail_if(pkt_ia_na[3] != sizeof(pkt_ia_na)-4);
+    len += sizeof(pkt_ia_na);
+  }
+
+  if (opts & OPT_IA_NA_0) {
+    memcpy((void*)(&buf[len]), pkt_ia_na_0, sizeof(pkt_ia_na_0));
+    fail_if(pkt_ia_na_0[3] != sizeof(pkt_ia_na_0)-4);
+    len += sizeof(pkt_ia_na_0);
+  }
+
+  if (opts & OPT_IA_PD) {
+    memcpy((void*)(&buf[len]), pkt_ia_pd, sizeof(pkt_ia_pd));
+    fail_if(pkt_ia_pd[3] != sizeof(pkt_ia_pd)-4);
+    len += sizeof(pkt_ia_pd);
+  }
+
+  if (opts & OPT_IA_PD_0) {
+    memcpy((void*)(&buf[len]), pkt_ia_pd_0, sizeof(pkt_ia_pd_0));
+    fail_if(pkt_ia_pd_0[3] != sizeof(pkt_ia_pd_0)-4);
+    len += sizeof(pkt_ia_pd_0);
+  }
+
+  if (opts & OPT_DNS_SRV) {
+    memcpy((void*)(&buf[len]), pkt_dns_srv, sizeof(pkt_dns_srv));
+    fail_if(pkt_dns_srv[3] != sizeof(pkt_dns_srv)-4);
+    len += sizeof(pkt_dns_srv);
+  }
+
+  if (opts & OPT_DOMAIN_SRCH_LST) {
+    memcpy((void*)(&buf[len]), pkt_domain_srch_lst, sizeof(pkt_domain_srch_lst));
+    fail_if(pkt_domain_srch_lst[3] != sizeof(pkt_domain_srch_lst)-4);
+    len += sizeof(pkt_domain_srch_lst);
+  }
+
+  if (opts & OPT_SNTP_SRV) {
+    memcpy((void*)(&buf[len]), pkt_sntp_srv, sizeof(pkt_sntp_srv));
+    fail_if(pkt_sntp_srv[3] != sizeof(pkt_sntp_srv)-4);
+    len += sizeof(pkt_sntp_srv);
+  }
+
+  /* Adjust lengths in UDP and IPv6 header */
+  udp_pkt_with_its_hdr_len = len - 54;
+  buf[pkt_ip_len_pos  ] = udp_pkt_with_its_hdr_len << 8;
+  buf[pkt_ip_len_pos+1] = udp_pkt_with_its_hdr_len & 0xffU;
+  buf[pkt_udp_len_pos  ] = udp_pkt_with_its_hdr_len << 8;
+  buf[pkt_udp_len_pos+1] = udp_pkt_with_its_hdr_len & 0xffU;
+
+  p = pbuf_alloc(PBUF_RAW, (u16_t)len, PBUF_RAM);
+
+
+  fail_unless(p != NULL);
+  for(q = p; q != NULL; q = q->next) {
+    /* copy to pbuf chain */
+    memset(q->payload, 0xf0, q->len);
+    memcpy(q->payload, p_buf, q->len);
+    p_buf += q->len;
+  }
+  netif->input(p, netif);
+}
+
+static int16_t find_udp_hdr(const struct pbuf *p)
+{
+  const uint16_t ipv6_hdr_len = 40;
+  const uint8_t * const d = (const uint8_t *) p->payload;
+  uint8_t next_hdr;
+  fail_if(p->tot_len < ipv6_hdr_len, "Packet length %u < IPv6 header length %u", p->tot_len, ipv6_hdr_len);
+
+  next_hdr = d[6];
+
+  if (next_hdr == 17) { /* Next header is UDP 17 */
+    return ipv6_hdr_len;
+  } else {
+    /* Ignore ICMPv6 packets. (nd6 module sends them because we call its timer) */
+    fail_unless(next_hdr == 58, "Got packet with next header %u which is not UDP(17) nor ICMPv6(58)", next_hdr);
+    return -1;
+  }
+}
+
+static uint16_t parse_udp_hdr(const struct pbuf *p, uint16_t pos, uint16_t * src_port, uint16_t * dst_port)
+{
+  const uint8_t * const d = (const uint8_t *) p->payload;
+
+  fail_unless(pos+6 <= p->len, "Packet too small");
+
+  *src_port =  d[pos++]<<8;
+  *src_port += d[pos++];
+
+  *dst_port =  d[pos++]<<8;
+  *dst_port += d[pos++];
+
+  return pos + 4;
+}
+
+static uint16_t parse_dhcp_hdr(const struct pbuf *p, uint16_t pos, uint8_t * msg_type, uint32_t * transaction_id)
+{
+  const uint8_t * d = (const uint8_t *) p->payload;
+
+  uint8_t dd[100];
+  memcpy(dd, d, 100);
+
+  fail_unless(pos+4 <= p->len, "Packet too small");
+
+  *msg_type = d[pos++];
+
+  *transaction_id =  d[pos++]<<16;
+  *transaction_id += d[pos++]<<8;
+  *transaction_id += d[pos++];
+
+  return pos;
+}
+
+static uint16_t parse_ia_id(const struct pbuf *p, uint16_t pos, uint32_t * ia_id)
+{
+  const uint8_t * const d = (const uint8_t *) p->payload;
+  fail_if(pos == OPT_NOT_FOUND);
+  fail_unless(pos+12 <= p->len, "Packet too small");
+
+  *ia_id =  d[pos++] << 24;
+  *ia_id += d[pos++] << 16;
+  *ia_id += d[pos++] <<  8;
+  *ia_id += d[pos++];
+
+  return pos + 8; /* Skip T1, T2 point to the begining of nested option */
+}
+
+/* Sets flags (enum pkt_opts) with found options */
+static uint16_t parse_oro(const struct pbuf *p, uint16_t pos, uint16_t opt_len, unsigned * const found_opts)
+{
+  const uint8_t * const d = (const uint8_t *) p->payload;
+  const unsigned num_opts = opt_len / 2; /* Each opt has 2 bytes */
+  unsigned i;
+  fail_unless(pos + opt_len <= p->len, "Packet too small");
+
+  *found_opts = 0U;
+
+  for(i=0; i<num_opts; i++) {
+
+    uint16_t opt = d[pos++] << 8;
+    opt += d[pos++];
+
+    switch (opt) {
+      case DHCP6_OPTION_DNS_SERVERS /* 23 */:
+        *found_opts |= OPT_DNS_SRV;
+        break;
+
+      case DHCP6_OPTION_DOMAIN_LIST /* 24 */:
+        *found_opts |= OPT_DOMAIN_SRCH_LST;
+        break;
+
+      case DHCP6_OPTION_SNTP_SERVERS /* 31 */:
+        *found_opts |= OPT_SNTP_SRV;
+        break;
+
+      default:
+        break;
+    }
+  }
+  return pos;
+}
+
+/* Returns elapsed time in ms */
+static uint32_t parse_elapsed_ms(const struct pbuf *p, uint16_t pos, uint16_t opt_len )
+{
+  const uint8_t * const d = (const uint8_t *) p->payload;
+  uint16_t t;
+  fail_if(opt_len != 2, "Elapsed option len is %u instead of 2", opt_len);
+  fail_unless(pos + opt_len <= p->len, "Packet too small");
+
+  t  = d[pos++] << 8;
+  t += d[pos++];
+
+  return t*10;
+}
+
+static uint16_t find_opt(const struct pbuf *p, uint16_t pos, uint16_t opt_type, uint16_t * opt_len)
+{
+  const uint8_t * d = (const uint8_t *) p->payload;
+  uint16_t found_type;
+  int prev_found_type = -1;
+  unsigned prev_found_type_pos = 0;
+
+  fail_unless(pos+4 <= p->len, "Packet too small");
+
+  while((pos+4) <= p->len) {
+    found_type = d[pos++] << 8;
+    found_type += d[pos++];
+
+    *opt_len = d[pos++] << 8;
+    *opt_len += d[pos++];
+
+    fail_unless(found_type < 150, "Found option type > 150 (%u) at %u byte. (Previous opt was %d at %u byte)",
+                found_type, pos-4, prev_found_type, prev_found_type_pos);
+
+    fail_unless(*opt_len <= (p->len - pos),
+                "Found option %u at %u byte with length %u exceeding remaining packet length %u. "
+                "(Previous opt was %d at %u byte)",
+                found_type, pos-4, *opt_len, p->len - pos,
+                prev_found_type, prev_found_type_pos);
+
+    if (opt_type == found_type) {
+      return pos;
+    }
+
+    prev_found_type = found_type;
+    prev_found_type_pos = pos - 4;
+    pos += *opt_len;
+  }
+
+  return OPT_NOT_FOUND;
+}
+
+static void store_xid(uint32_t xid)
+{
+  pkt_xid[0] = (xid>>16) & 0xffU;
+  pkt_xid[1] = (xid>> 8) & 0xffU;
+  pkt_xid[2] = (xid    ) & 0xffU;
+}
+
+static void store_client_id(const struct pbuf *p, uint16_t pos, size_t len)
+{
+  const uint8_t * d = (const uint8_t *) p->payload;
+  fail_if(pos < 4);
+  pos -= 4; /* Copy whole opt including header */
+
+  fail_unless(len <= sizeof(pkt_client_id)-4);
+  len += 4;
+
+  if (!allow_override_client_id) {
+    fail_unless(pkt_client_id_len == 0, "Trying to override client id stored form previous run");
+  }
+
+  memcpy(pkt_client_id, &d[pos], len);
+  pkt_client_id_len = len;
+}
+
+static void reset_client_id(void)
+{
+  pkt_client_id_len = 0;
+}
+
+static void store_ia_na_id(uint32_t ia_id)
+{
+  pkt_ia_na[pkt_iaid_pos + 0] = (ia_id>>24) & 0xffU;
+  pkt_ia_na[pkt_iaid_pos + 1] = (ia_id>>16) & 0xffU;
+  pkt_ia_na[pkt_iaid_pos + 2] = (ia_id>> 8) & 0xffU;
+  pkt_ia_na[pkt_iaid_pos + 3] = (ia_id    ) & 0xffU;
+}
+
+#if LWIP_IPV6_DHCP6_PD
+static void store_ia_pd_id(uint32_t ia_id)
+{
+  pkt_ia_pd[pkt_iaid_pos + 0] = (ia_id>>24) & 0xffU;
+  pkt_ia_pd[pkt_iaid_pos + 1] = (ia_id>>16) & 0xffU;
+  pkt_ia_pd[pkt_iaid_pos + 2] = (ia_id>> 8) & 0xffU;
+  pkt_ia_pd[pkt_iaid_pos + 3] = (ia_id    ) & 0xffU;
+}
+#endif
+
+static err_t output_ip6_chk_no_send(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr)
+{
+  uint16_t src_port = 0;
+  uint16_t dst_port = 0;
+  uint8_t dhcp6_msg_type = 255;
+  uint32_t transaction_id = 0;
+
+  int16_t pos = find_udp_hdr(p);
+  if (pos<0) {
+    return ERR_OK; /* Not interested in non UDP packets */
+  }
+
+  LWIP_UNUSED_ARG(ipaddr);
+
+  fail_unless(netif == &net_test);
+  fail_unless(p != NULL);
+
+  pos = parse_udp_hdr(p, pos, &src_port, &dst_port);
+  fail_unless( src_port == 546, "Unexpected msg sent from src port %u (instead of 546)", src_port);
+  fail_unless( dst_port == 547, "Unexpected msg sent to dst port %u (instead of 547)", dst_port );
+
+  pos = parse_dhcp_hdr(p, pos, &dhcp6_msg_type, &transaction_id);
+  fail("Unexpected message of type %s sent from DHCP client", msg_type_to_str(dhcp6_msg_type));
+
+  return ERR_OK;
+}
+
+static unsigned pkt_solicit_cnt;
+static err_t output_ip6_chk_solicit(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr)
+{
+  uint16_t src_port = 0;
+  uint16_t dst_port = 0;
+  uint8_t dhcp6_msg_type = 255;
+  uint32_t transaction_id = 0;
+
+  uint16_t opt_cli_id_pos;
+  uint16_t opt_cli_id_len;
+  uint16_t opt_elapsed_pos;
+  uint16_t opt_elapsed_len;
+  uint16_t opt_iapd_pos;
+  uint16_t opt_iapd_len;
+#if LWIP_IPV6_DHCP6_PD
+  uint32_t opt_iapd_iaid;
+#endif
+  uint16_t opt_iana_pos;
+  uint16_t opt_iana_len;
+  uint32_t opt_iana_iaid;
+  uint16_t opt_oro_pos;
+  uint16_t opt_oro_len;
+  unsigned found_oro_requests;
+
+  int16_t pos = find_udp_hdr(p);
+  if (pos<0) {
+    return ERR_OK; /* Not interested in non UDP packets */
+  }
+
+  fail_unless(netif == &net_test);
+  fail_unless(p != NULL);
+  LWIP_UNUSED_ARG(ipaddr);
+
+  pos = parse_udp_hdr(p, pos, &src_port, &dst_port);
+  fail_unless( src_port == 546 );
+  fail_unless( dst_port == 547 );
+
+  pos = parse_dhcp_hdr(p, pos, &dhcp6_msg_type, &transaction_id);
+  fail_unless( dhcp6_msg_type == 1, "Got msg of type %s from DHCP client instead of %s",
+               msg_type_to_str(dhcp6_msg_type), msg_type_to_str(1));
+
+  opt_cli_id_pos = find_opt(p, pos, 1 /*client identifier*/, &opt_cli_id_len);
+  fail_unless(opt_cli_id_pos != OPT_NOT_FOUND);
+
+  opt_elapsed_pos = find_opt(p, pos, 8 /*elapsed time*/, &opt_elapsed_len);
+  fail_unless(opt_elapsed_pos != OPT_NOT_FOUND);
+  fail_unless(opt_elapsed_len == 2);
+  elapsed_ms = parse_elapsed_ms(p, opt_elapsed_pos, opt_elapsed_len);
+
+  opt_iapd_pos = find_opt(p, pos, 25 /*IA PD*/, &opt_iapd_len);
+#if LWIP_IPV6_DHCP6_PD
+  fail_unless(opt_iapd_pos != OPT_NOT_FOUND);
+  fail_unless(opt_iapd_len == 12);
+#else
+  fail_unless(opt_iapd_pos == OPT_NOT_FOUND);
+#endif
+
+  opt_iana_pos = find_opt(p, pos, 3 /*IA NA*/, &opt_iana_len);
+  fail_unless(opt_iana_pos != OPT_NOT_FOUND);
+  fail_unless(opt_iana_len == 12);
+
+  opt_oro_pos = find_opt(p, pos, 6 /*ORO option request*/, &opt_oro_len);
+  fail_unless(opt_oro_pos != OPT_NOT_FOUND);
+  parse_oro(p, opt_oro_pos, opt_oro_len, &found_oro_requests);
+#if LWIP_DHCP6_MAX_DNS_SERVERS
+  fail_unless(found_oro_requests & OPT_DNS_SRV);
+#else
+  fail_if(found_oro_requests & OPT_DNS_SRV,
+          "LWIP_DHCP6_MAX_DNS_SERVERS is 0 but DNS server option found in solicitation");
+#endif
+#if LWIP_DHCP6_GET_NTP_SRV
+  fail_unless(found_oro_requests & OPT_SNTP_SRV);
+#endif
+
+
+  store_xid(transaction_id);
+  store_client_id(p, opt_cli_id_pos, opt_cli_id_len);
+
+#if LWIP_IPV6_DHCP6_PD
+  parse_ia_id(p, opt_iapd_pos, &opt_iapd_iaid);
+  store_ia_pd_id(opt_iapd_iaid);
+#endif
+
+  parse_ia_id(p, opt_iana_pos, &opt_iana_iaid);
+  store_ia_na_id(opt_iana_iaid);
+
+  pkt_solicit_cnt++;
+
+  return ERR_OK;
+}
+
+static unsigned pkt_request_cnt;
+static err_t output_ip6_chk_request(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr)
+{
+  uint16_t src_port = 0;
+  uint16_t dst_port = 0;
+  uint8_t dhcp6_msg_type = 255;
+  uint32_t transaction_id = 0;
+
+  uint16_t opt_elapsed_pos;
+  uint16_t opt_elapsed_len;
+  uint16_t opt_iapd_pos;
+  uint16_t opt_iapd_len;
+  uint16_t opt_iana_pos;
+  uint16_t opt_iana_len;
+  uint16_t opt_server_id_pos;
+  uint16_t opt_server_id_len;
+  uint16_t opt_client_id_pos;
+  uint16_t opt_client_id_len;
+  uint16_t opt_oro_pos;
+  uint16_t opt_oro_len;
+  unsigned found_oro_requests;
+
+  int16_t pos = find_udp_hdr(p);
+  if (pos<0) {
+    return ERR_OK; /* Not interested in non UDP packets */
+  }
+
+  fail_unless(netif == &net_test);
+  fail_unless(p != NULL);
+  LWIP_UNUSED_ARG(ipaddr);
+
+  pos = parse_udp_hdr(p, pos, &src_port, &dst_port);
+  fail_unless( src_port == 546 );
+  fail_unless( dst_port == 547 );
+
+  pos = parse_dhcp_hdr(p, pos, &dhcp6_msg_type, &transaction_id);
+  fail_unless( dhcp6_msg_type == 3, "Got msg of type %s from DHCP client instead of %s",
+               msg_type_to_str(dhcp6_msg_type), msg_type_to_str(3));
+
+  opt_client_id_pos = find_opt(p, pos, 1 /*Client ID*/, &opt_client_id_len);
+  fail_unless(opt_client_id_pos != OPT_NOT_FOUND);
+
+  opt_server_id_pos = find_opt(p, pos, 2 /*Server ID*/, &opt_server_id_len);
+  fail_unless(opt_server_id_pos != OPT_NOT_FOUND);
+  fail_unless(opt_server_id_len == sizeof(pkt_server_id)-4);
+
+  opt_iana_pos = find_opt(p, pos, 3 /*IA NA*/, &opt_iana_len);
+  fail_unless(opt_iana_pos != OPT_NOT_FOUND);
+  fail_unless(opt_iana_len == 40);
+
+  opt_elapsed_pos = find_opt(p, pos, 8 /*Elapsed time*/, &opt_elapsed_len);
+  fail_unless(opt_elapsed_pos != OPT_NOT_FOUND);
+  fail_unless(opt_elapsed_len == 2);
+  elapsed_ms = parse_elapsed_ms(p, opt_elapsed_pos, opt_elapsed_len);
+
+  opt_iapd_pos = find_opt(p, pos, 25 /*IA PD*/, &opt_iapd_len);
+#if LWIP_IPV6_DHCP6_PD
+  fail_unless(opt_iapd_pos != OPT_NOT_FOUND);
+  fail_unless(opt_iapd_len == 41); /* Must include prefix */
+#else
+  fail_unless(opt_iapd_pos == OPT_NOT_FOUND);
+#endif
+
+  opt_oro_pos = find_opt(p, pos, 6 /*ORO option request*/, &opt_oro_len);
+  fail_unless(opt_oro_pos != OPT_NOT_FOUND);
+  parse_oro(p, opt_oro_pos, opt_oro_len, &found_oro_requests);
+#if LWIP_DHCP6_MAX_DNS_SERVERS
+  fail_unless(found_oro_requests & OPT_DNS_SRV);
+#else
+  fail_if(found_oro_requests & OPT_DNS_SRV,
+          "LWIP_DHCP6_MAX_DNS_SERVERS is 0 but DNS server option found in request");
+#endif
+
+#if LWIP_DHCP6_GET_NTP_SRV
+  fail_unless(found_oro_requests & OPT_SNTP_SRV);
+#endif
+
+  store_xid(transaction_id);
+
+  pkt_request_cnt++;
+  return ERR_OK;
+}
+
+static unsigned pkt_release_cnt;
+static err_t output_ip6_chk_release(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr)
+{
+  uint16_t src_port = 0;
+  uint16_t dst_port = 0;
+  uint8_t dhcp6_msg_type = 255;
+  uint32_t transaction_id = 0;
+
+  uint16_t opt_elapsed_pos;
+  uint16_t opt_elapsed_len;
+  uint16_t opt_iapd_pos;
+  uint16_t opt_iapd_len;
+  uint16_t opt_iana_pos;
+  uint16_t opt_iana_len;
+  uint16_t opt_server_id_pos;
+  uint16_t opt_server_id_len;
+  uint16_t opt_client_id_pos;
+  uint16_t opt_client_id_len;
+
+  int16_t pos = find_udp_hdr(p);
+  if (pos<0) {
+    return ERR_OK; /* Not interested in non UDP packets */
+  }
+
+  fail_unless(netif == &net_test);
+  fail_unless(p != NULL);
+  LWIP_UNUSED_ARG(ipaddr);
+
+  pos = parse_udp_hdr(p, pos, &src_port, &dst_port);
+  fail_unless( src_port == 546 );
+  fail_unless( dst_port == 547 );
+
+  pos = parse_dhcp_hdr(p, pos, &dhcp6_msg_type, &transaction_id);
+  fail_unless( dhcp6_msg_type == 8, "Got msg of type %s from DHCP client instead of %s",
+               msg_type_to_str(dhcp6_msg_type), msg_type_to_str(8));
+
+  opt_client_id_pos = find_opt(p, pos, 1 /*Client ID*/, &opt_client_id_len);
+  fail_unless(opt_client_id_pos != OPT_NOT_FOUND);
+
+  opt_server_id_pos = find_opt(p, pos, 2 /*Server ID*/, &opt_server_id_len);
+  fail_unless(opt_server_id_pos != OPT_NOT_FOUND);
+  fail_unless(opt_server_id_len == sizeof(pkt_server_id)-4);
+
+  opt_iana_pos = find_opt(p, pos, 3 /*IA NA*/, &opt_iana_len);
+  fail_unless(opt_iana_pos != OPT_NOT_FOUND);
+  fail_unless(opt_iana_len == 40);
+
+  opt_elapsed_pos = find_opt(p, pos, 8 /*Elapsed time*/, &opt_elapsed_len);
+  fail_unless(opt_elapsed_pos != OPT_NOT_FOUND);
+  fail_unless(opt_elapsed_len == 2);
+  elapsed_ms = parse_elapsed_ms(p, opt_elapsed_pos, opt_elapsed_len);
+
+  opt_iapd_pos = find_opt(p, pos, 25 /*IA PD*/, &opt_iapd_len);
+#if LWIP_IPV6_DHCP6_PD
+  fail_unless(opt_iapd_pos != OPT_NOT_FOUND);
+  fail_unless(opt_iapd_len == 41); /* Must include prefix */
+#else
+  fail_unless(opt_iapd_pos == OPT_NOT_FOUND);
+#endif
+
+  store_xid(transaction_id);
+
+  pkt_release_cnt++;
+
+  return ERR_OK;
+}
+
+static unsigned pkt_renew_cnt;
+static err_t output_ip6_chk_renew(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr)
+{
+  uint16_t src_port = 0;
+  uint16_t dst_port = 0;
+  uint8_t dhcp6_msg_type = 255;
+  uint32_t transaction_id = 0;
+
+  uint16_t opt_elapsed_pos;
+  uint16_t opt_elapsed_len;
+  uint16_t opt_iapd_pos;
+  uint16_t opt_iapd_len;
+  uint16_t opt_iana_pos;
+  uint16_t opt_iana_len;
+  uint16_t opt_server_id_pos;
+  uint16_t opt_server_id_len;
+  uint16_t opt_client_id_pos;
+  uint16_t opt_client_id_len;
+  uint16_t opt_oro_pos;
+  uint16_t opt_oro_len;
+  unsigned found_oro_requests;
+
+  int16_t pos = find_udp_hdr(p);
+  if (pos<0) {
+    return ERR_OK; /* Not interested in non UDP packets */
+  }
+
+  fail_unless(netif == &net_test);
+  fail_unless(p != NULL);
+  LWIP_UNUSED_ARG(ipaddr);
+
+  pos = parse_udp_hdr(p, pos, &src_port, &dst_port);
+  fail_unless( src_port == 546 );
+  fail_unless( dst_port == 547 );
+
+  pos = parse_dhcp_hdr(p, pos, &dhcp6_msg_type, &transaction_id);
+  fail_unless( dhcp6_msg_type == 5, "Got msg of type %s from DHCP client instead of %s",
+               msg_type_to_str(dhcp6_msg_type), msg_type_to_str(5));
+
+  opt_client_id_pos = find_opt(p, pos, 1 /*Client ID*/, &opt_client_id_len);
+  fail_unless(opt_client_id_pos != OPT_NOT_FOUND);
+
+  opt_server_id_pos = find_opt(p, pos, 2 /*Server ID*/, &opt_server_id_len);
+  fail_unless(opt_server_id_pos != OPT_NOT_FOUND);
+  fail_unless(opt_server_id_len == sizeof(pkt_server_id)-4);
+
+  opt_iana_pos = find_opt(p, pos, 3 /*IA NA*/, &opt_iana_len);
+  fail_unless(opt_iana_pos != OPT_NOT_FOUND);
+  fail_unless(opt_iana_len == 40);
+
+  opt_elapsed_pos = find_opt(p, pos, 8 /*Elapsed time*/, &opt_elapsed_len);
+  fail_unless(opt_elapsed_pos != OPT_NOT_FOUND);
+  fail_unless(opt_elapsed_len == 2);
+  elapsed_ms = parse_elapsed_ms(p, opt_elapsed_pos, opt_elapsed_len);
+
+  opt_iapd_pos = find_opt(p, pos, 25 /*IA PD*/, &opt_iapd_len);
+#if LWIP_IPV6_DHCP6_PD
+  fail_unless(opt_iapd_pos != OPT_NOT_FOUND);
+  fail_unless(opt_iapd_len == 41); /* Must include prefix */
+#else
+  fail_unless(opt_iapd_pos == OPT_NOT_FOUND);
+#endif
+
+  opt_oro_pos = find_opt(p, pos, 6 /*ORO option request*/, &opt_oro_len);
+  fail_unless(opt_oro_pos != OPT_NOT_FOUND);
+  parse_oro(p, opt_oro_pos, opt_oro_len, &found_oro_requests);
+#if LWIP_DHCP6_MAX_DNS_SERVERS
+  fail_unless(found_oro_requests & OPT_DNS_SRV);
+#else
+  fail_if(found_oro_requests & OPT_DNS_SRV,
+          "LWIP_DHCP6_MAX_DNS_SERVERS is 0 but DNS server option found in renew request");
+#endif
+#if LWIP_DHCP6_GET_NTP_SRV
+  fail_unless(found_oro_requests & OPT_SNTP_SRV);
+#endif
+
+  store_xid(transaction_id);
+
+  pkt_renew_cnt++;
+
+  return ERR_OK;
+}
+
+static unsigned pkt_inforeq_cnt;
+static err_t output_ip6_chk_inforeq(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr)
+{
+  uint16_t src_port = 0;
+  uint16_t dst_port = 0;
+  uint8_t dhcp6_msg_type = 255;
+  uint32_t transaction_id = 0;
+
+  uint16_t opt_oro_pos;
+  uint16_t opt_oro_len;
+  unsigned found_oro_requests;
+
+
+  int16_t pos = find_udp_hdr(p);
+  if (pos<0) {
+    return ERR_OK; /* Not interested in non UDP packets */
+  }
+
+  fail_unless(netif == &net_test);
+  fail_unless(p != NULL);
+  LWIP_UNUSED_ARG(ipaddr);
+
+  pos = parse_udp_hdr(p, pos, &src_port, &dst_port);
+  fail_unless( src_port == 546 );
+  fail_unless( dst_port == 547 );
+
+  pos = parse_dhcp_hdr(p, pos, &dhcp6_msg_type, &transaction_id);
+  fail_unless( dhcp6_msg_type == 11, "Got msg of type %s from DHCP client instead of %s",
+               msg_type_to_str(dhcp6_msg_type), msg_type_to_str(11));
+
+
+  opt_oro_pos = find_opt(p, pos, 6 /*ORO option request*/, &opt_oro_len);
+  fail_unless(opt_oro_pos != OPT_NOT_FOUND);
+  parse_oro(p, opt_oro_pos, opt_oro_len, &found_oro_requests);
+#if LWIP_DHCP6_MAX_DNS_SERVERS
+  fail_unless(found_oro_requests & OPT_DNS_SRV);
+#endif
+#if LWIP_DHCP6_MAX_DNS_SERVERS
+  fail_unless(found_oro_requests & OPT_SNTP_SRV);
+#endif
+
+  store_xid(transaction_id);
+  pkt_inforeq_cnt++;
+  return ERR_OK;
+}
+
+#if LWIP_IPV6_DHCP6_PD
+static const struct dhcp6_delegated_prefix * prefix;
+static u8_t prefix_valid;
+
+static void prefix_cb(struct netif* netif, const struct dhcp6_delegated_prefix * _prefix, u8_t valid) {
+  (void) netif;
+  prefix = _prefix;
+  prefix_valid = valid;
+}
+#endif
+
+/*
+ * Test basic happy flow DHCP session.
+ * Validate that xid is checked.
+ */
+START_TEST(test_dhcp6_stateful)
+{
+  int i;
+  err_t ret;
+  /*u32_t xid;*/
+  LWIP_UNUSED_ARG(_i);
+
+
+
+  ret = dhcp6_enable(&net_test);
+  fail_unless(ret == ERR_OK);
+
+  /* Test one more time */
+  ret = dhcp6_enable(&net_test);
+  fail_unless(ret == ERR_ALREADY);
+
+  /* Got RA with M bit set */
+  dhcp6_nd6_ra_trigger(&net_test, 1, 0);
+
+  pkt_solicit_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_solicit;
+
+  tick_500ms_lwip();
+
+  fail_unless(pkt_solicit_cnt>0);
+
+  net_test.output_ip6 = &output_ip6_chk_no_send;
+
+  /* Prepare for request to be sent by client and send advertisement */
+  net_test.output_ip6 = output_ip6_chk_request;
+  send_pkt(&net_test, DHCP_MSG_ADVERTISE, OPT_SRV_ID | OPT_CLI_ID | OPT_IA_PD | OPT_IA_NA | OPT_DNS_SRV | OPT_SNTP_SRV | OPT_DOMAIN_SRCH_LST);
+
+
+  net_test.output_ip6 = &output_ip6_chk_no_send;
+#if LWIP_IPV6_DHCP6_PD
+  prefix_valid = 0;
+  memset(&prefix, 0xa5, sizeof(prefix));
+  dhcp6_register_pd_callback(&net_test, &prefix_cb);
+#endif
+  reset_dns_servers();
+  reset_sntp_servers();
+
+  /* send reply */
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_CLI_ID | OPT_IA_PD | OPT_IA_NA | OPT_DNS_SRV | OPT_SNTP_SRV | OPT_DOMAIN_SRCH_LST);
+
+  net_test.output_ip6 = &output_ip6_chk_no_send;
+
+#if LWIP_IPV6_DHCP6_PD
+  fail_if(prefix_valid == 0);
+  fail_unless(prefix->prefix_len == 64);
+
+  fail_unless(prefix->prefix_pref == 4500);
+  fail_unless(prefix->prefix_valid == 7200);
+#endif
+
+  fail_unless(ip6_addr_istentative(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s", addr_state_to_str(netif_ip6_addr_state(&net_test, 1)));
+  fail_if(ip6_addr_isany(netif_ip6_addr(&net_test, 1)));
+  fail_unless(netif_ip6_addr_pref_life(&net_test, 1) == 375);
+  fail_unless(netif_ip6_addr_valid_life(&net_test, 1) == 600);
+
+#if LWIP_DHCP6_MAX_DNS_SERVERS
+  fail_if(ip6_addr_isany(&dns_getserver(0)->u_addr.ip6));
+  fail_if(dns_getserver(0)->u_addr.ip6.zone == IP6_WRONG_ZONE);
+#else
+  fail_unless(ip6_addr_isany(&dns_getserver(0)->u_addr.ip6),
+          "LWIP_DHCP6_MAX_DNS_SERVERS is 0 but DNS server 0 was changed by dhcp client");
+#endif
+
+#if LWIP_DHCP6_GET_NTP_SRV
+  fail_if(ip6_addr_isany(&sntp_getserver(0)->u_addr.ip6));
+  fail_if(sntp_getserver(0)->u_addr.ip6.zone == IP6_WRONG_ZONE);
+#else
+  fail_unless(ip6_addr_isany(&sntp_getserver(0)->u_addr.ip6),
+              "DHCP6 client sets SNTP server but LWIP_DHCP6_GET_NTP_SRV is disabled.");
+#endif
+
+  /* Wait 5s */
+  for(i=0; i<5*2; i++) {
+    tick_500ms_lwip();
+  }
+
+#if LWIP_IPV6_DHCP6_PD
+  /* Times should be 5s shorter now */
+  fail_unless(prefix->prefix_pref == (4500-5));
+  fail_unless(prefix->prefix_valid == (7200-5));
+#endif
+
+  /* Wait 370s to let expire pref time of address */
+  pkt_renew_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_renew;
+  reset_dns_servers();
+  reset_sntp_servers();
+
+  fail_unless(ip6_addr_ispreferred(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s  but should be preferred (pref time = %u)",
+              addr_state_to_str(netif_ip6_addr_state(&net_test, 1)), netif_ip6_addr_pref_life(&net_test, 1));
+
+  for(i=0; i<370*2; i++) {
+    tick_500ms_lwip();
+  }
+  fail_unless(pkt_renew_cnt > 0);
+
+  fail_unless(ip6_addr_isdeprecated(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s, but should be deprecated (pref time = %u)",
+              addr_state_to_str(netif_ip6_addr_state(&net_test, 1)), netif_ip6_addr_pref_life(&net_test, 1));
+
+  /* reply to renew */
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_CLI_ID | OPT_IA_PD | OPT_IA_NA | OPT_DNS_SRV | OPT_SNTP_SRV | OPT_DOMAIN_SRCH_LST);
+
+  fail_unless(ip6_addr_ispreferred(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s, but should be preferred (pref time = %u)",
+              addr_state_to_str(netif_ip6_addr_state(&net_test, 1)), netif_ip6_addr_pref_life(&net_test, 1));
+  fail_if(ip6_addr_isany(netif_ip6_addr(&net_test, 1)));
+  fail_unless(netif_ip6_addr_pref_life(&net_test, 1) == 375);
+  fail_unless(netif_ip6_addr_valid_life(&net_test, 1) == 600);
+
+#if LWIP_DHCP6_MAX_DNS_SERVERS
+  fail_if(ip6_addr_isany(&dns_getserver(0)->u_addr.ip6));
+  fail_if(dns_getserver(0)->u_addr.ip6.zone == IP6_WRONG_ZONE);
+#else
+  fail_unless(ip6_addr_isany(&dns_getserver(0)->u_addr.ip6),
+              "LWIP_DHCP6_MAX_DNS_SERVERS is 0 but DNS server 0 was changed by dhcp client");
+#endif
+
+#if LWIP_DHCP6_GET_NTP_SRV
+  fail_if(ip6_addr_isany(&sntp_getserver(0)->u_addr.ip6));
+  fail_if(sntp_getserver(0)->u_addr.ip6.zone == IP6_WRONG_ZONE);
+#else
+  fail_unless(ip6_addr_isany(&sntp_getserver(0)->u_addr.ip6),
+              "DHCP6 client sets SNTP server but LWIP_DHCP6_GET_NTP_SRV is disabled.");
+#endif
+
+  pkt_release_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_release;
+
+  /* Clean up */
+  dhcp6_disable(&net_test); /* wait somehow for disabling? we should get reply from server*/
+  fail_unless(pkt_release_cnt > 0);
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_CLI_ID);
+  fail_if(netif_get_client_data(&net_test, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6) != NULL);
+}
+END_TEST
+
+/*
+ * Test basic happy flow DHCP session.
+ * Validate that xid is checked.
+ */
+START_TEST(test_dhcp6_stateless)
+{
+  err_t ret;
+  /*u32_t xid;*/
+  LWIP_UNUSED_ARG(_i);
+
+  ret = dhcp6_enable(&net_test);
+  fail_unless(ret == ERR_OK);
+
+  /* Test one more time */
+  ret = dhcp6_enable(&net_test);
+  fail_unless(ret == ERR_ALREADY);
+
+  reset_dns_servers();
+  reset_sntp_servers();
+
+  /* Got RA with M bit NOT set, prepare for request to be sent by client */
+  pkt_inforeq_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_inforeq;
+  dhcp6_nd6_ra_trigger(&net_test, 0, 1);
+
+  fail_unless(pkt_inforeq_cnt > 0);
+
+  /* Send reply */
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_DNS_SRV | OPT_SNTP_SRV | OPT_DOMAIN_SRCH_LST);
+
+#if LWIP_DHCP6_MAX_DNS_SERVERS
+  fail_if(ip6_addr_isany(&dns_getserver(0)->u_addr.ip6));
+  fail_if(dns_getserver(0)->u_addr.ip6.zone == IP6_WRONG_ZONE);
+#endif
+
+#if LWIP_DHCP6_MAX_DNS_SERVERS
+  fail_if(ip6_addr_isany(&sntp_getserver(0)->u_addr.ip6));
+  fail_if(sntp_getserver(0)->u_addr.ip6.zone == IP6_WRONG_ZONE);
+#else
+  fail_unless(ip6_addr_isany(&sntp_getserver(0)->u_addr.ip6),
+              "DHCP6 client sets SNTP server but LWIP_DHCP6_MAX_DNS_SERVERS is disabled.");
+#endif
+
+  /* Client should set nothing on disable */
+  net_test.output_ip6 = &output_ip6_chk_no_send;
+
+  /* Clean up */
+  dhcp6_disable(&net_test);
+  fail_if(netif_get_client_data(&net_test, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6) != NULL);
+
+}
+END_TEST
+
+
+/*
+ * 1) Send two solicitations
+ * 2) Get advertise
+ * 3) Send request until MRC expires
+ * 4) Check if starts sending solicitations again
+ */
+START_TEST(test_dhcp6_stateful_solicit_req_resend)
+{
+  int i;
+  err_t ret;
+  LWIP_UNUSED_ARG(_i);
+
+  ret = dhcp6_enable(&net_test);
+  fail_unless(ret == ERR_OK);
+
+  /* Got RA with M bit set */
+  dhcp6_nd6_ra_trigger(&net_test, 1, 0);
+
+  pkt_solicit_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_solicit;
+
+  tick_500ms_lwip();
+
+  fail_unless(pkt_solicit_cnt == 1);
+  fail_if(elapsed_ms != 0, "Non-zero value of elapsed time (%u ms) in first solicit message.", elapsed_ms);
+
+  reset_client_id();
+
+  for (i=0; i<2*2; i++) tick_500ms_lwip(); /* wait 2s*/
+  fail_unless(pkt_solicit_cnt == 2);
+  fail_if(elapsed_ms != 2000, "wrong elapsed time (%u ms != 2000)  in solicit message after 2s.", elapsed_ms);
+
+
+  /* Prepare for request to be sent by client, send advertise and wait until it reaches REQ_MAX_RC and then goes back to sending solicitations. */
+  pkt_request_cnt = 0;
+  net_test.output_ip6 = output_ip6_chk_request;
+  send_pkt(&net_test, DHCP_MSG_ADVERTISE, OPT_SRV_ID | OPT_CLI_ID | OPT_IA_PD | OPT_IA_NA | OPT_DNS_SRV | OPT_SNTP_SRV | OPT_DOMAIN_SRCH_LST);
+
+  for (i=0; i<180*2; i++) tick_500ms_lwip(); /* wait 180s to get maximum (10) requests */
+  fail_unless(pkt_request_cnt == 10);
+
+  /* prepare to get solicitation again */
+  pkt_solicit_cnt = 0;
+  reset_client_id();
+  net_test.output_ip6 = &output_ip6_chk_solicit;
+
+  /* Move in time */
+  tick_500ms_lwip();
+  tick_500ms_lwip();
+  fail_unless(pkt_solicit_cnt == 1);
+
+  /* prepare to get request */
+  pkt_request_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_request;
+
+  send_pkt(&net_test, DHCP_MSG_ADVERTISE, OPT_SRV_ID | OPT_CLI_ID | OPT_IA_PD | OPT_IA_NA | OPT_DNS_SRV | OPT_SNTP_SRV | OPT_DOMAIN_SRCH_LST);
+
+  fail_unless(pkt_request_cnt == 1);
+
+  net_test.output_ip6 = &output_ip6_chk_no_send;
+
+  /* send reply */
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_CLI_ID | OPT_IA_PD | OPT_IA_NA | OPT_DNS_SRV | OPT_SNTP_SRV | OPT_DOMAIN_SRCH_LST);
+
+  /* Now we should be bound */
+  fail_unless(ip6_addr_istentative(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s", addr_state_to_str(netif_ip6_addr_state(&net_test, 1)));
+  fail_if(ip6_addr_isany(netif_ip6_addr(&net_test, 1)));
+  fail_unless(netif_ip6_addr_pref_life(&net_test, 1) == 375);
+
+  /* Wait 374s */
+  for(i=0; i<374*2; i++) tick_500ms_lwip();
+
+  /* prepare to get renew */
+  pkt_renew_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_renew;
+
+  /* Wait 1s to get first renew request */
+  for(i=0; i<1*2; i++) tick_500ms_lwip();
+
+  fail_unless(pkt_renew_cnt == 1);
+
+  fail_unless(ip6_addr_isdeprecated(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s  but should be deprecated (pref time = %u) (valid time = %u)",
+              addr_state_to_str(netif_ip6_addr_state(&net_test, 1)),
+              netif_ip6_addr_pref_life(&net_test, 1), netif_ip6_addr_valid_life(&net_test, 1));
+
+  /* Now wait for remaining valid life (224s) for timeout in state renewing */
+
+  for(i=0; i<224*2; i++) {
+    tick_500ms_lwip();
+  }
+  fail_unless(pkt_renew_cnt = 5);
+
+  /* Our IP address is not valid anymore and because this implementation doesn't hold list of other DHCP servers
+   * check if client will send solicitations again */
+
+  /* prepare to get solicitation again */
+  pkt_solicit_cnt = 0;
+  reset_client_id();
+  allow_override_client_id = 1;
+  net_test.output_ip6 = &output_ip6_chk_solicit;
+
+  /* Move in time */
+  tick_500ms_lwip();
+  tick_500ms_lwip();
+  fail_unless(pkt_solicit_cnt > 0);
+
+  fail_unless(ip6_addr_isinvalid(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s, but should be invalid (valid time = %u)",
+              addr_state_to_str(netif_ip6_addr_state(&net_test, 1)), netif_ip6_addr_valid_life(&net_test, 1));
+
+  /* Clean up */
+  net_test.output_ip6 = &output_ip6_chk_no_send; /* In soliciting state we shouldn't send anything on disable */
+  dhcp6_disable(&net_test);
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_CLI_ID);
+  fail_if(netif_get_client_data(&net_test, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6) != NULL);
+}
+END_TEST
+
+/*
+ * 1) get addr and prefix
+ * 2) try to renew
+ * 3) get addr and prefix with 0 valid times
+ * 4) Check if starts sending solicitations again
+ */
+START_TEST(test_dhcp6_stateful_refuse)
+{
+  int i;
+  err_t ret;
+  LWIP_UNUSED_ARG(_i);
+
+  ret = dhcp6_enable(&net_test);
+  fail_unless(ret == ERR_OK);
+
+#if LWIP_IPV6_DHCP6_PD
+  prefix_valid = 0;
+  memset(&prefix, 0xa5, sizeof(prefix));
+  dhcp6_register_pd_callback(&net_test, &prefix_cb);
+#endif
+
+  /* Got RA with M and O bits set */
+  dhcp6_nd6_ra_trigger(&net_test, 1, 1);
+
+  pkt_solicit_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_solicit;
+
+  tick_500ms_lwip();
+
+  fail_unless(pkt_solicit_cnt == 1);
+
+  /* Prepare for request to be sent by client, send advertise */
+  pkt_request_cnt = 0;
+  net_test.output_ip6 = output_ip6_chk_request;
+  send_pkt(&net_test, DHCP_MSG_ADVERTISE, OPT_SRV_ID | OPT_CLI_ID | OPT_IA_PD | OPT_IA_NA );
+
+  net_test.output_ip6 = &output_ip6_chk_no_send;
+
+  /* send reply */
+
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_CLI_ID | OPT_IA_PD | OPT_IA_NA );
+
+  /* Now we should be bound */
+  fail_unless(ip6_addr_istentative(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s", addr_state_to_str(netif_ip6_addr_state(&net_test, 1)));
+  fail_if(ip6_addr_isany(netif_ip6_addr(&net_test, 1)));
+  fail_unless(netif_ip6_addr_pref_life(&net_test, 1) == 375);
+
+  /* Wait 374s */
+  for(i=0; i<374*2; i++) tick_500ms_lwip();
+
+  /* prepare to get renew */
+  pkt_renew_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_renew;
+
+  /* Wait 1s to get first renew request */
+  for(i=0; i<1*2; i++) tick_500ms_lwip();
+
+  fail_unless(pkt_renew_cnt == 1);
+
+  fail_unless(ip6_addr_isdeprecated(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s  but should be deprecated (pref time = %u) (valid time = %u)",
+              addr_state_to_str(netif_ip6_addr_state(&net_test, 1)),
+              netif_ip6_addr_pref_life(&net_test, 1), netif_ip6_addr_valid_life(&net_test, 1));
+
+  /* Prepare for solicitation and send reply refusing address */
+  pkt_solicit_cnt = 0;
+  allow_override_client_id = 1;
+#if LWIP_IPV6_DHCP6_PD
+  prefix_valid = 1;
+#endif
+  net_test.output_ip6 = &output_ip6_chk_solicit;
+
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_CLI_ID | OPT_IA_PD_0 | OPT_IA_NA_0 );
+
+  fail_unless(pkt_solicit_cnt == 1);
+  allow_override_client_id = 0;
+  fail_unless(ip6_addr_isinvalid(netif_ip6_addr_state(&net_test, 1)),
+              "addr in state %s, but should be invalid (valid time = %u)",
+              addr_state_to_str(netif_ip6_addr_state(&net_test, 1)), netif_ip6_addr_valid_life(&net_test, 1));
+#if LWIP_IPV6_DHCP6_PD
+  fail_if(prefix_valid, "Prefix callback not called or called with prefix set as valid");
+#endif
+
+  /* Clean up */
+  net_test.output_ip6 = &output_ip6_chk_no_send; /* In soliciting state we shouldn't send anything on disable */
+  dhcp6_disable(&net_test);
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_CLI_ID);
+  fail_if(netif_get_client_data(&net_test, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6) != NULL);
+}
+END_TEST
+
+/*
+ * 1) get addr and prefix
+ * 2) try to renew
+ * 3) get addr and prefix with 0 valid times
+ * 4) Check if starts sending solicitations again
+ */
+START_TEST(test_dhcp6_stateful_custom_client_id)
+{
+#if LWIP_IPV6_DHCP6_STATEFUL
+  err_t ret;
+  LWIP_UNUSED_ARG(_i);
+
+  ret = dhcp6_enable(&net_test);
+  fail_unless(ret == ERR_OK);
+
+  dhcp6_set_client_id_duid(&net_test, pkt_client_id_custom_duid, sizeof(pkt_client_id_custom_duid));
+
+  /* Got RA with M and O bits set */
+  dhcp6_nd6_ra_trigger(&net_test, 1, 1);
+
+  pkt_solicit_cnt = 0;
+  net_test.output_ip6 = &output_ip6_chk_solicit;
+
+  tick_500ms_lwip();
+
+  fail_unless(pkt_solicit_cnt == 1);
+  fail_unless(pkt_client_id_len == sizeof(pkt_client_id_custom_duid) + 4);
+  fail_if(memcmp(pkt_client_id_custom_duid, &pkt_client_id[4], sizeof(pkt_client_id_custom_duid)) != 0);
+
+  /* Clean up */
+  net_test.output_ip6 = &output_ip6_chk_no_send; /* In soliciting state we shouldn't send anything on disable */
+  dhcp6_disable(&net_test);
+  send_pkt(&net_test, DHCP_MSG_REPLY, OPT_SRV_ID | OPT_CLI_ID);
+  fail_if(netif_get_client_data(&net_test, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP6) != NULL);
+#endif /* #if LWIP_IPV6_DHCP6_STATEFUL */
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+dhcp6_suite(void)
+{
+  testfunc tests[] = {
+#if LWIP_IPV6_DHCP6_STATEFUL
+    TESTFUNC(test_dhcp6_stateful),
+    TESTFUNC(test_dhcp6_stateful_solicit_req_resend),
+    TESTFUNC(test_dhcp6_stateful_refuse),
+    TESTFUNC(test_dhcp6_stateful_custom_client_id),
+#endif
+#if !LWIP_IPV6_DHCP6_PD
+    TESTFUNC(test_dhcp6_stateless),
+#endif
+  };
+
+  /* suppress unused function warnings: */
+#if !LWIP_IPV6_DHCP6_STATEFUL
+  (void) &test_dhcp6_stateful;
+  (void) &test_dhcp6_stateful_solicit_req_resend;
+  (void) &test_dhcp6_stateful_refuse;
+  (void) &test_dhcp6_stateful_custom_client_id;
+#elif LWIP_IPV6_DHCP6_PD
+  (void) &test_dhcp6_stateless; /* suppress unused function warning */
+#endif
+
+
+  return create_suite("DHCP6", tests, sizeof(tests)/sizeof(testfunc), dhcp6_setup, dhcp6_teardown);
+}
diff --git a/test/unit/dhcp6/test_dhcp6.h b/test/unit/dhcp6/test_dhcp6.h
new file mode 100644
index 00000000..b24086c8
--- /dev/null
+++ b/test/unit/dhcp6/test_dhcp6.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 NXP
+ * 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.
+ *
+ */
+
+#ifndef LWIP_HDR_TEST_DHCP6_H
+#define LWIP_HDR_TEST_DHCP6_H
+
+#include "../lwip_check.h"
+
+Suite* dhcp6_suite(void);
+
+#endif
diff --git a/test/unit/lwip_unittests.c b/test/unit/lwip_unittests.c
index d088a066..88b467d1 100644
--- a/test/unit/lwip_unittests.c
+++ b/test/unit/lwip_unittests.c
@@ -14,6 +14,7 @@
 #include "core/test_timers.h"
 #include "etharp/test_etharp.h"
 #include "dhcp/test_dhcp.h"
+#include "dhcp6/test_dhcp6.h"
 #include "mdns/test_mdns.h"
 #include "mqtt/test_mqtt.h"
 #include "api/test_sockets.h"
@@ -95,6 +96,7 @@ int main(void)
     timers_suite,
     etharp_suite,
     dhcp_suite,
+    dhcp6_suite,
     mdns_suite,
     mqtt_suite,
     sockets_suite
diff --git a/test/unit/lwipopts.h b/test/unit/lwipopts.h
index 03579ee7..80de3846 100644
--- a/test/unit/lwipopts.h
+++ b/test/unit/lwipopts.h
@@ -54,6 +54,11 @@
 /* Enable DHCP to test it */
 #define LWIP_DHCP                       1
 
+/* Enable DHCP6 to test it */
+#define LWIP_IPV6_DHCP6                 1
+#define LWIP_IPV6_DHCP6_STATEFUL        1
+#define LWIP_IPV6_DHCP6_PD              1
+
 /* Enable DNS, with random source port to avoid alloc in dns_init */
 #define LWIP_DNS                        1
 #define LWIP_DNS_SECURE (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_RAND_SRC_PORT)
-- 
2.34.1

