You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2022/03/19 15:25:58 UTC

[incubator-nuttx-apps] branch master updated: Add dhcp6c module

This is an automated email from the ASF dual-hosted git repository.

xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx-apps.git


The following commit(s) were added to refs/heads/master by this push:
     new f853885  Add dhcp6c module
f853885 is described below

commit f85388578c1f6c07aee0334b6cd09106a4032a16
Author: songlinzhang <so...@xiaomi.com>
AuthorDate: Mon Mar 14 16:35:01 2022 +0800

    Add dhcp6c module
    
    Signed-off-by: songlinzhang <so...@xiaomi.com>
---
 include/netutils/dhcp6c.h |   76 ++
 netutils/dhcp6c/Kconfig   |   18 +
 netutils/dhcp6c/Make.defs |   22 +
 netutils/dhcp6c/Makefile  |   27 +
 netutils/dhcp6c/dhcp6c.c  | 1958 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 2101 insertions(+)

diff --git a/include/netutils/dhcp6c.h b/include/netutils/dhcp6c.h
new file mode 100644
index 0000000..b798274
--- /dev/null
+++ b/include/netutils/dhcp6c.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+ * apps/include/netutils/dhcp6c.h
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __APPS_INCLUDE_NETUTILS_DHCP6C_H
+#define __APPS_INCLUDE_NETUTILS_DHCP6C_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <netinet/in.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+struct dhcp6c_state
+{
+  struct in6_addr addr;
+  struct in6_addr pd;       /* prefix address */
+  struct in6_addr dns;
+  struct in6_addr netmask;
+  uint8_t pl;               /* prefix len */
+  uint32_t t1;
+  uint32_t t2;
+};
+
+typedef void (*dhcp6c_callback_t)(FAR struct dhcp6c_state *presult);
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+FAR void *dhcp6c_open(FAR const char *interface);
+int dhcp6c_request(FAR void *handle, FAR struct dhcp6c_state *presult);
+int dhcp6c_request_async(FAR void *handle, dhcp6c_callback_t callback);
+void dhcp6c_cancel(FAR void *handle);
+void dhcp6c_close(FAR void *handle);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __APPS_INCLUDE_NETUTILS_DHCP6C_H */
diff --git a/netutils/dhcp6c/Kconfig b/netutils/dhcp6c/Kconfig
new file mode 100644
index 0000000..b4d3c30
--- /dev/null
+++ b/netutils/dhcp6c/Kconfig
@@ -0,0 +1,18 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+config NETUTILS_DHCP6C
+    bool "DHCPv6 client"
+    default n
+    depends on NET_IPv6
+    depends on NET_UDP && NET_UDP_CHECKSUMS
+
+if NETUTILS_DHCP6C
+
+config NETUTILS_DHCP6C_REQUEST_TIMEOUT
+    int "Default single request timeout in second"
+    default 6
+
+endif
diff --git a/netutils/dhcp6c/Make.defs b/netutils/dhcp6c/Make.defs
new file mode 100644
index 0000000..e8c11b0
--- /dev/null
+++ b/netutils/dhcp6c/Make.defs
@@ -0,0 +1,22 @@
+# apps/netutils/dhcp6c/Make.defs
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.  The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+ifeq ($(CONFIG_NETUTILS_DHCP6C), y)
+CONFIGURED_APPS += $(APPDIR)/netutils/dhcp6c
+endif
diff --git a/netutils/dhcp6c/Makefile b/netutils/dhcp6c/Makefile
new file mode 100644
index 0000000..c4c6596
--- /dev/null
+++ b/netutils/dhcp6c/Makefile
@@ -0,0 +1,27 @@
+############################################################################
+# apps/netutils/dhcp6c/Makefile
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.  The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs
+
+ifeq ($(CONFIG_NETUTILS_DHCP6C),y)
+CSRCS += dhcp6c.c
+endif
+
+include $(APPDIR)/Application.mk
diff --git a/netutils/dhcp6c/dhcp6c.c b/netutils/dhcp6c/dhcp6c.c
new file mode 100644
index 0000000..cbfe560
--- /dev/null
+++ b/netutils/dhcp6c/dhcp6c.c
@@ -0,0 +1,1958 @@
+/****************************************************************************
+ * apps/netutils/dhcp6c/dhcp6c.c
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/compiler.h>
+
+#include <time.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <limits.h>
+#include <resolv.h>
+#include <string.h>
+#include <unistd.h>
+#include <debug.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <malloc.h>
+#include <sys/time.h>
+#include <nuttx/clock.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/udp.h>
+#include <net/if.h>
+#include <net/ethernet.h>
+#include <arpa/inet.h>
+
+#include "netutils/dhcp6c.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define DHCPV6_ALL_RELAYS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+                             0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02}}}
+#define DHCPV6_CLIENT_PORT 546
+#define DHCPV6_SERVER_PORT 547
+#define DHCPV6_DUID_LLADDR 3
+#define DHCPV6_REQ_DELAY 1
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+#define dhcpv6_for_each_option(_o, start, end, otype, olen, odata)\
+    for ((_o) = (FAR uint8_t *)(start); (_o) + 4 <= (FAR uint8_t *)(end) &&\
+        ((otype) = (_o)[0] << 8 | (_o)[1]) && ((odata) = (FAR void *)&(_o)[4]) &&\
+        ((olen) = (_o)[2] << 8 | (_o)[3]) + (odata) <= (FAR uint8_t *)(end); \
+        (_o) += 4 + ((_o)[2] << 8 | (_o)[3]))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+enum dhcpv6_opt_e
+{
+  DHCPV6_OPT_CLIENTID = 1,
+  DHCPV6_OPT_SERVERID = 2,
+  DHCPV6_OPT_IA_NA = 3,
+  DHCPV6_OPT_IA_ADDR = 5,
+  DHCPV6_OPT_ORO = 6,
+  DHCPV6_OPT_PREF = 7,
+  DHCPV6_OPT_ELAPSED = 8,
+  DHCPV6_OPT_RELAY_MSG = 9,
+  DHCPV6_OPT_AUTH = 11,
+  DHCPV6_OPT_STATUS = 13,
+  DHCPV6_OPT_RAPID_COMMIT = 14,
+  DHCPV6_OPT_RECONF_MESSAGE = 19,
+  DHCPV6_OPT_RECONF_ACCEPT = 20,
+  DHCPV6_OPT_DNS_SERVERS = 23,
+  DHCPV6_OPT_DNS_DOMAIN = 24,
+  DHCPV6_OPT_IA_PD = 25,
+  DHCPV6_OPT_IA_PREFIX = 26,
+  DHCPV6_OPT_INFO_REFRESH = 32,
+  DHCPV6_OPT_FQDN = 39,
+  DHCPV6_OPT_NTP_SERVER = 56,
+  DHCPV6_OPT_SIP_SERVER_D = 21,
+  DHCPV6_OPT_SIP_SERVER_A = 22,
+};
+
+enum dhcpv6_opt_npt_e
+{
+  NTP_SRV_ADDR = 1,
+  NTP_MC_ADDR = 2,
+  NTP_SRV_FQDN = 3
+};
+
+enum dhcpv6_msg_e
+{
+  DHCPV6_MSG_UNKNOWN = 0,
+  DHCPV6_MSG_SOLICIT = 1,
+  DHCPV6_MSG_ADVERT = 2,
+  DHCPV6_MSG_REQUEST = 3,
+  DHCPV6_MSG_RENEW = 5,
+  DHCPV6_MSG_REBIND = 6,
+  DHCPV6_MSG_REPLY = 7,
+  DHCPV6_MSG_RELEASE = 8,
+  DHCPV6_MSG_DECLINE = 9,
+  DHCPV6_MSG_RECONF = 10,
+  DHCPV6_MSG_INFO_REQ = 11,
+  DHCPV6_MSG_MAX
+};
+
+enum dhcpv6_status_e
+{
+  DHCPV6_NOADDRSAVAIL = 2,
+  DHCPV6_NOPREFIXAVAIL = 6
+};
+
+enum dhcpv6_mode_e
+{
+  DHCPV6_UNKNOWN,
+  DHCPV6_STATELESS,
+  DHCPV6_STATEFUL
+};
+
+enum dhcpv6_state_e
+{
+  STATE_CLIENT_ID,
+  STATE_SERVER_ID,
+  STATE_SERVER_CAND,
+  STATE_ORO,
+  STATE_DNS,
+  STATE_SEARCH,
+  STATE_IA_NA,
+  STATE_IA_PD,
+  STATE_CUSTOM_OPTS,
+  STATE_SNTP_IP,
+  STATE_SNTP_FQDN,
+  STATE_SIP_IP,
+  STATE_SIP_FQDN,
+  STATE_MAX
+};
+
+enum dhcp6c_ia_mode_e
+{
+  IA_MODE_NONE,
+  IA_MODE_TRY,
+  IA_MODE_FORCE,
+};
+
+/* DHCPV6 Protocol Headers */
+
+begin_packed_struct struct dhcpv6_header_s
+{
+  uint8_t msg_type;
+  uint8_t tr_id[3];
+} end_packed_struct;
+
+begin_packed_struct struct dhcpv6_ia_hdr_s
+{
+  uint16_t type;
+  uint16_t len;
+  uint32_t iaid;
+  uint32_t t1;
+  uint32_t t2;
+} end_packed_struct;
+
+begin_packed_struct struct dhcpv6_ia_addr_s
+{
+  uint16_t type;
+  uint16_t len;
+  struct in6_addr addr;
+  uint32_t preferred;
+  uint32_t valid;
+} end_packed_struct;
+
+begin_packed_struct struct dhcpv6_ia_prefix_s
+{
+  uint16_t type;
+  uint16_t len;
+  uint32_t preferred;
+  uint32_t valid;
+  uint8_t prefix;
+  struct in6_addr addr;
+} end_packed_struct;
+
+struct dhcpv6_server_cand_s
+{
+  bool has_noaddravail;
+  bool wants_reconfigure;
+  int16_t preference;
+  uint8_t duid_len;
+  uint8_t duid[130];
+};
+
+struct dhcp6c_retx_s
+{
+  bool delay;
+  uint8_t init_timeo;
+  uint16_t max_timeo;
+  char name[8];
+  int(*handler_reply)(FAR void *handle, enum dhcpv6_msg_e orig,
+                      FAR const void *opt, FAR const void *end,
+                      uint32_t elapsed);
+  int(*handler_finish)(FAR void *handle, uint32_t elapsed);
+};
+
+struct dhcp6c_state_s
+{
+  pthread_t thread;
+  bool cancel;
+  dhcp6c_callback_t callback;
+  int sockfd;
+  int urandom_fd;
+  int ifindex;
+  time_t t1;
+  time_t t2;
+  time_t t3;
+  bool request_prefix;
+  enum dhcp6c_ia_mode_e ia_mode;
+  bool accept_reconfig;
+  FAR uint8_t *state_data[STATE_MAX];
+  size_t state_len[STATE_MAX];
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int dhcp6c_handle_reconfigure(FAR void *handle,
+                                     enum dhcpv6_msg_e orig,
+                                     FAR const void *opt,
+                                     FAR const void *end,
+                                     uint32_t elapsed);
+static int dhcp6c_handle_advert(FAR void *handle, enum dhcpv6_msg_e orig,
+                                FAR const void *opt, FAR const void *end,
+                                uint32_t elapsed);
+static int dhcp6c_commit_advert(FAR void *handle, uint32_t elapsed);
+static int dhcp6c_handle_reply(FAR void *handle, enum dhcpv6_msg_e orig,
+                               FAR const void *opt, FAR const void *end,
+                               uint32_t elapsed);
+static int dhcp6c_handle_rebind_reply(FAR void *handle,
+                                      enum dhcpv6_msg_e orig,
+                                      FAR const void *opt,
+                                      FAR const void *end,
+                                      uint32_t elapsed);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* RFC 3315 - 5.5 Timeout and Delay values */
+
+static const struct dhcp6c_retx_s g_dhcp6c_retx[DHCPV6_MSG_MAX] =
+{
+  {false, 1, 120, "<POLL>", dhcp6c_handle_reconfigure, NULL},
+  {true, 1, 120, "SOLICIT", dhcp6c_handle_advert, dhcp6c_commit_advert},
+  {0},
+  {true, 1, 30, "REQUEST", dhcp6c_handle_reply, NULL},
+  {0},
+  {false, 10, 600, "RENEW", dhcp6c_handle_reply, NULL},
+  {false, 10, 600, "REBIND", dhcp6c_handle_rebind_reply, NULL},
+  {0},
+  {false, 1, 600, "RELEASE", NULL, NULL},
+  {false, 1, 3, "DECLINE", NULL, NULL},
+  {0},
+  {true, 1, 120, "INFOREQ", dhcp6c_handle_reply, NULL},
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static uint64_t dhcp6c_get_milli_time(void)
+{
+  struct timespec t;
+
+  clock_gettime(CLOCK_MONOTONIC, &t);
+
+  return t.tv_sec * MSEC_PER_SEC + t.tv_nsec / USEC_PER_SEC;
+}
+
+static FAR uint8_t *dhcp6c_resize_state(FAR void *handle,
+                                        enum dhcpv6_state_e state,
+                                        ssize_t len)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  FAR uint8_t *n;
+
+  if (len == 0)
+    {
+      return pdhcp6c->state_data[state] + pdhcp6c->state_len[state];
+    }
+
+  n = realloc(pdhcp6c->state_data[state], pdhcp6c->state_len[state] + len);
+  if (n != NULL || pdhcp6c->state_len[state] + len == 0)
+    {
+      pdhcp6c->state_data[state] = n;
+      n += pdhcp6c->state_len[state];
+      pdhcp6c->state_len[state] += len;
+    }
+
+  return n;
+}
+
+static void dhcp6c_clear_state(FAR void *handle, enum dhcpv6_state_e state)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+
+  pdhcp6c->state_len[state] = 0;
+}
+
+static void dhcp6c_add_state(FAR void *handle, enum dhcpv6_state_e state,
+                             FAR const void *data, size_t len)
+{
+  FAR uint8_t *n = dhcp6c_resize_state(handle, state, len);
+
+  if (n != NULL)
+    {
+      memcpy(n, data, len);
+    }
+}
+
+static size_t dhcp6c_remove_state(FAR void *handle,
+                                  enum dhcpv6_state_e state,
+                                  size_t offset, size_t len)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  FAR uint8_t *data = pdhcp6c->state_data[state];
+  ssize_t len_after = pdhcp6c->state_len[state] - (offset + len);
+
+  if (len_after < 0)
+    {
+      return pdhcp6c->state_len[state];
+    }
+
+  memmove(data + offset, data + offset + len, len_after);
+
+  return pdhcp6c->state_len[state] -= len;
+}
+
+static bool dhcp6c_commit_state(FAR void *handle, enum dhcpv6_state_e state,
+                                size_t old_len)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  size_t new_len = pdhcp6c->state_len[state] - old_len;
+  FAR uint8_t *old_data = pdhcp6c->state_data[state];
+  FAR uint8_t *new_data = old_data + old_len;
+  bool upd = (new_len != old_len) ||
+             (memcmp(old_data, new_data, new_len) != 0);
+
+  memmove(old_data, new_data, new_len);
+  dhcp6c_resize_state(handle, state, -old_len);
+
+  return upd;
+}
+
+static FAR void *dhcp6c_get_state(FAR void *handle,
+                                  enum dhcpv6_state_e state,
+                                  FAR size_t *len)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+
+  *len = pdhcp6c->state_len[state];
+  return pdhcp6c->state_data[state];
+}
+
+static void dhcp6c_get_result(FAR void *handle,
+                              FAR struct dhcp6c_state *presult)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  size_t s_len;
+  FAR uint8_t *s_data;
+  uint16_t olen;
+  uint16_t otype;
+  FAR uint8_t *ite;
+  FAR uint8_t *odata;
+  FAR struct dhcpv6_ia_addr_s *addr;
+  FAR struct dhcpv6_ia_prefix_s *pd;
+  FAR struct in6_addr *dns;
+  char addr_str[INET6_ADDRSTRLEN];
+
+  if (handle == NULL || presult == NULL)
+    {
+      return;
+    }
+
+  s_data = dhcp6c_get_state(handle, STATE_IA_NA, &s_len);
+  dhcpv6_for_each_option(ite, s_data, s_data + s_len, otype, olen, odata)
+    {
+      addr = (FAR void *)(odata - 4);
+      memcpy(&presult->addr, &addr->addr, sizeof(presult->addr));
+      inet_ntop(AF_INET6, &presult->addr, addr_str, sizeof(addr_str));
+      ninfo("IA_NA %s for iface %i\n", addr_str, pdhcp6c->ifindex);
+    }
+
+  s_data = dhcp6c_get_state(handle, STATE_IA_PD, &s_len);
+  dhcpv6_for_each_option(ite, s_data, s_data + s_len, otype, olen, odata)
+    {
+      pd = (FAR void *)(odata - 4);
+      memcpy(&presult->pd, &pd->addr, sizeof(presult->pd));
+      presult->pl = pd->prefix;
+      netlib_prefix2ipv6netmask(presult->pl, &presult->netmask);
+      inet_ntop(AF_INET6, &presult->pd, addr_str, sizeof(addr_str));
+      ninfo("IA_PD %s for iface %i\n", addr_str, pdhcp6c->ifindex);
+      inet_ntop(AF_INET6, &presult->netmask, addr_str, sizeof(addr_str));
+      ninfo("netmask %s for iface %i\n", addr_str, pdhcp6c->ifindex);
+    }
+
+  dns = dhcp6c_get_state(handle, STATE_DNS, &s_len);
+  memcpy(&presult->dns, dns, sizeof(presult->dns));
+  inet_ntop(AF_INET6, &presult->dns, addr_str, sizeof(addr_str));
+  ninfo("DNS server %s for iface %i\n", addr_str, pdhcp6c->ifindex);
+
+  presult->t1 = pdhcp6c->t1;
+  presult->t2 = pdhcp6c->t2;
+  ninfo("T1:%d T2:%d for iface %i\n", presult->t1, presult->t2,
+        pdhcp6c->ifindex);
+}
+
+static void dhcp6c_switch_process(FAR void *handle, FAR const char *name)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  struct dhcp6c_state result;
+
+  ninfo("Process switch to %s\n", name);
+
+  /* Delete lost prefixes and user opts */
+
+  dhcp6c_clear_state(handle, STATE_CUSTOM_OPTS);
+
+  if (pdhcp6c->callback != NULL)
+    {
+      memset(&result, 0, sizeof(result));
+      dhcp6c_get_result(pdhcp6c, &result);
+      pdhcp6c->callback(&result);
+    }
+}
+
+static void dhcp6c_remove_addrs(FAR void *handle)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  size_t ia_na_len;
+  FAR uint8_t *ite;
+  FAR uint8_t *odata;
+  FAR uint8_t *ia_na = dhcp6c_get_state(handle, STATE_IA_NA, &ia_na_len);
+  uint16_t otype;
+  uint16_t olen;
+  FAR struct dhcpv6_ia_addr_s *addr;
+  char addr_str[INET6_ADDRSTRLEN];
+
+  dhcpv6_for_each_option(ite, ia_na, ia_na + ia_na_len, otype, olen, odata)
+    {
+      addr = (FAR void *)(odata - 4);
+      inet_ntop(AF_INET6, &addr->addr, addr_str, sizeof(addr_str));
+      ninfo("removing address %s/128 for iface %i\n",
+            addr_str, pdhcp6c->ifindex);
+    }
+}
+
+static void dhcp6c_set_iov(FAR struct iovec *piov,
+                           FAR void *base, size_t len)
+{
+  piov->iov_base = base;
+  piov->iov_len = len;
+}
+
+static void dhcp6c_send(FAR void *handle, enum dhcpv6_msg_e type,
+                        uint8_t trid[3], uint32_t ecs)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  char fqdn_buf[256];
+  struct
+  {
+    uint16_t type;
+    uint16_t len;
+    uint8_t flags;
+    uint8_t data[256];
+  } fqdn;
+
+  size_t fqdn_len;
+  size_t cl_id_len;
+  FAR void *cl_id;
+  size_t srv_id_len;
+  FAR void *srv_id;
+  size_t ia_pd_len;
+  FAR void *ia_pd;
+  struct dhcpv6_ia_hdr_s hdr_ia_pd;
+  struct dhcpv6_ia_prefix_s pref;
+  size_t ia_na_len;
+  FAR void *ia_na;
+  struct dhcpv6_ia_hdr_s hdr_ia_na;
+  struct
+  {
+    uint16_t type;
+    uint16_t length;
+  } reconf_accept;
+
+  uint16_t oro_refresh;
+  size_t oro_len;
+  FAR void *oro;
+  struct
+  {
+    uint8_t type;
+    uint8_t trid[3];
+    uint16_t elapsed_type;
+    uint16_t elapsed_len;
+    uint16_t elapsed_value;
+    uint16_t oro_type;
+    uint16_t oro_len;
+  } hdr;
+
+  struct iovec iov[11];
+  size_t cnt;
+  struct sockaddr_in6 dest =
+  {
+    AF_INET6, htons(DHCPV6_SERVER_PORT),
+    0, DHCPV6_ALL_RELAYS, pdhcp6c->ifindex
+  };
+
+  struct msghdr msg;
+
+  /* Build FQDN */
+
+  gethostname(fqdn_buf, sizeof(fqdn_buf));
+  fqdn_len = 5 + dn_comp(fqdn_buf, fqdn.data, sizeof(fqdn.data), NULL, NULL);
+  fqdn.type = htons(DHCPV6_OPT_FQDN);
+  fqdn.len = htons(fqdn_len - 4);
+  fqdn.flags = 0;
+
+  /* Build Client ID */
+
+  cl_id = dhcp6c_get_state(handle, STATE_CLIENT_ID, &cl_id_len);
+
+  /* Build Server ID */
+
+  srv_id = dhcp6c_get_state(handle, STATE_SERVER_ID, &srv_id_len);
+
+  /* Build IA_PDs */
+
+  ia_pd = dhcp6c_get_state(handle, STATE_IA_PD, &ia_pd_len);
+  hdr_ia_pd.type = htons(DHCPV6_OPT_IA_PD);
+  hdr_ia_pd.len = htons(sizeof(hdr_ia_pd) - 4 + ia_pd_len);
+  hdr_ia_pd.iaid = 1;
+  hdr_ia_pd.t1 = 0;
+  hdr_ia_pd.t2 = 0;
+  pref.type = htons(DHCPV6_OPT_IA_PREFIX);
+  pref.len = htons(25);
+  pref.prefix = pdhcp6c->request_prefix;
+
+  if (ia_pd_len == 0 && pdhcp6c->request_prefix &&
+      (type == DHCPV6_MSG_SOLICIT || type == DHCPV6_MSG_REQUEST))
+    {
+      ia_pd = &pref;
+      ia_pd_len = sizeof(pref);
+    }
+
+  /* Build IA_NAs */
+
+  ia_na = dhcp6c_get_state(handle, STATE_IA_NA, &ia_na_len);
+  hdr_ia_na.type = htons(DHCPV6_OPT_IA_NA);
+  hdr_ia_na.len = htons(sizeof(hdr_ia_na) - 4 + ia_na_len);
+  hdr_ia_na.iaid = 1;
+  hdr_ia_na.t1 = 0;
+  hdr_ia_na.t2 = 0;
+
+  /* Reconfigure Accept */
+
+  reconf_accept.type = htons(DHCPV6_OPT_RECONF_ACCEPT);
+  reconf_accept.length = 0;
+
+  /* Request Information Refresh */
+
+  oro_refresh = htons(DHCPV6_OPT_INFO_REFRESH);
+
+  /* Prepare Header */
+
+  oro = dhcp6c_get_state(handle, STATE_ORO, &oro_len);
+  hdr.type = type;
+  hdr.trid[0] = trid[0];
+  hdr.trid[1] = trid[1];
+  hdr.trid[2] = trid[2];
+  hdr.elapsed_type = htons(DHCPV6_OPT_ELAPSED);
+  hdr.elapsed_len =  htons(2);
+  hdr.elapsed_value = htons((ecs > 0xffff) ? 0xffff : ecs);
+  hdr.oro_type = htons(DHCPV6_OPT_ORO);
+  hdr.oro_len = htons(oro_len);
+
+  /* Prepare iov */
+
+  dhcp6c_set_iov(&iov[0], &hdr, sizeof(hdr));
+  dhcp6c_set_iov(&iov[1], oro, oro_len);
+  dhcp6c_set_iov(&iov[2], &oro_refresh, 0);
+  dhcp6c_set_iov(&iov[3], cl_id, cl_id_len);
+  dhcp6c_set_iov(&iov[4], srv_id, srv_id_len);
+  dhcp6c_set_iov(&iov[5], &reconf_accept, 0);
+  dhcp6c_set_iov(&iov[6], &fqdn, fqdn_len);
+  dhcp6c_set_iov(&iov[7], &hdr_ia_na, sizeof(hdr_ia_na));
+  dhcp6c_set_iov(&iov[8], ia_na, ia_na_len);
+  dhcp6c_set_iov(&iov[9], &hdr_ia_pd, sizeof(hdr_ia_pd));
+  dhcp6c_set_iov(&iov[10], ia_pd, ia_pd_len);
+
+  cnt = ARRAY_SIZE(iov);
+  if (type == DHCPV6_MSG_INFO_REQ)
+    {
+      cnt = 5;
+      iov[2].iov_len = sizeof(oro_refresh);
+      hdr.oro_len = htons(oro_len + sizeof(oro_refresh));
+    }
+  else if (!pdhcp6c->request_prefix)
+    {
+      cnt = 9;
+    }
+
+  /* Disable IAs if not used */
+
+  if (type == DHCPV6_MSG_SOLICIT)
+    {
+      iov[5].iov_len = sizeof(reconf_accept);
+    }
+  else if (type != DHCPV6_MSG_REQUEST)
+    {
+      if (ia_na_len == 0)
+        {
+          iov[7].iov_len = 0;
+        }
+
+      if (ia_pd_len == 0)
+        {
+          iov[9].iov_len = 0;
+        }
+    }
+
+  if (pdhcp6c->ia_mode == IA_MODE_NONE)
+    {
+      iov[7].iov_len = 0;
+    }
+
+  msg.msg_name = &dest;
+  msg.msg_namelen = sizeof(dest);
+  msg.msg_iov = iov;
+  msg.msg_iovlen = cnt;
+  msg.msg_control = NULL;
+  msg.msg_controllen = 0;
+  msg.msg_flags = 0;
+
+  sendmsg(pdhcp6c->sockfd, &msg, 0);
+}
+
+static int64_t dhcp6c_rand_delay(FAR void *handle, int64_t time)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  int random;
+
+  read(pdhcp6c->urandom_fd, &random, sizeof(random));
+  return (time * (random % 1000)) / 10000;
+}
+
+static bool dhcp6c_response_is_valid(FAR void *handle, FAR const void *buf,
+                                     ssize_t len,
+                                     const uint8_t transaction[3],
+                                     enum dhcpv6_msg_e type)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  FAR const struct dhcpv6_header_s *rep = buf;
+  FAR uint8_t *ite;
+  FAR uint8_t *end;
+  FAR uint8_t *odata;
+  uint16_t otype;
+  uint16_t olen;
+  bool clientid_ok = false;
+  bool serverid_ok = false;
+  size_t client_id_len;
+  size_t server_id_len;
+  FAR void *client_id;
+  FAR void *server_id;
+
+  if (len < sizeof(*rep) ||
+      memcmp(rep->tr_id, transaction, sizeof(rep->tr_id)) != 0)
+    {
+      return false;
+    }
+
+  if (type == DHCPV6_MSG_SOLICIT)
+    {
+      if (rep->msg_type != DHCPV6_MSG_ADVERT &&
+          rep->msg_type != DHCPV6_MSG_REPLY)
+        {
+          return false;
+        }
+    }
+  else if (type == DHCPV6_MSG_UNKNOWN)
+    {
+      if (!pdhcp6c->accept_reconfig ||
+          rep->msg_type != DHCPV6_MSG_RECONF)
+        {
+          return false;
+        }
+    }
+  else if (rep->msg_type != DHCPV6_MSG_REPLY)
+    {
+      return false;
+    }
+
+  end = ((FAR uint8_t *)buf) + len;
+  client_id = dhcp6c_get_state(handle, STATE_CLIENT_ID, &client_id_len);
+  server_id = dhcp6c_get_state(handle, STATE_SERVER_ID, &server_id_len);
+
+  dhcpv6_for_each_option(ite, &rep[1], end, otype, olen, odata)
+    {
+      if (otype == DHCPV6_OPT_CLIENTID)
+        {
+          clientid_ok = (olen + 4u == client_id_len) &&
+                        (memcmp((odata - 4), client_id, client_id_len) == 0);
+        }
+      else if (otype == DHCPV6_OPT_SERVERID)
+        {
+          serverid_ok = (olen + 4u == server_id_len) &&
+                        (memcmp((odata - 4), server_id, server_id_len) == 0);
+        }
+    }
+
+  return clientid_ok && (serverid_ok || server_id_len == 0);
+}
+
+static int dhcp6c_command(FAR void *handle, enum dhcpv6_msg_e type)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  const int buf_length = 1536;
+  FAR uint8_t *buf = (FAR uint8_t *)malloc(buf_length);
+  uint32_t timeout = CONFIG_NETUTILS_DHCP6C_REQUEST_TIMEOUT < 3 ? 3 :
+                     CONFIG_NETUTILS_DHCP6C_REQUEST_TIMEOUT;
+  FAR const struct dhcp6c_retx_s *retx = &g_dhcp6c_retx[type];
+  uint64_t start;
+  uint64_t round_start;
+  uint64_t round_end;
+  uint64_t elapsed;
+  uint8_t trid[3];
+  ssize_t len = -1;
+  int64_t rto = 0;
+
+  if (buf == NULL)
+    {
+      return -1;
+    }
+
+  if (retx->delay)
+    {
+      struct timespec ts;
+      ts.tv_sec = 0;
+      ts.tv_nsec = dhcp6c_rand_delay(handle, 10 * DHCPV6_REQ_DELAY);
+      nanosleep(&ts, NULL);
+    }
+
+  if (type == DHCPV6_MSG_RELEASE || type == DHCPV6_MSG_DECLINE)
+    {
+      timeout = 3;
+    }
+  else if (type == DHCPV6_MSG_UNKNOWN)
+    {
+      timeout = pdhcp6c->t1;
+    }
+  else if (type == DHCPV6_MSG_RENEW)
+    {
+      timeout = pdhcp6c->t2 - pdhcp6c->t1;
+    }
+  else if (type == DHCPV6_MSG_REBIND)
+    {
+      timeout = pdhcp6c->t3 - pdhcp6c->t2;
+    }
+
+  if (timeout == 0)
+    {
+      len = -1;
+      goto end;
+    }
+
+  ninfo("Sending %s (timeout %u s)\n", retx->name, timeout);
+  start = dhcp6c_get_milli_time();
+  round_start = start;
+
+  /* Generate transaction ID */
+
+  read(pdhcp6c->urandom_fd, trid, sizeof(trid));
+
+  do
+    {
+      rto = (rto == 0) ? (retx->init_timeo * MSEC_PER_SEC +
+              dhcp6c_rand_delay(handle, retx->init_timeo * MSEC_PER_SEC)) :
+              (2 * rto + dhcp6c_rand_delay(handle, rto));
+
+      if (rto >= retx->max_timeo * MSEC_PER_SEC)
+        {
+          rto = retx->max_timeo * MSEC_PER_SEC +
+                dhcp6c_rand_delay(handle, retx->max_timeo * MSEC_PER_SEC);
+        }
+
+      /* Calculate end for this round and elapsed time */
+
+      round_end = round_start + rto;
+      elapsed = round_start - start;
+
+      /* Don't wait too long */
+
+      if (round_end - start > timeout * MSEC_PER_SEC)
+        {
+          round_end = timeout * MSEC_PER_SEC + start;
+        }
+
+      /* Built and send package */
+
+      if (type != DHCPV6_MSG_UNKNOWN)
+        {
+          dhcp6c_send(handle, type, trid, elapsed / 10);
+        }
+
+      /* Receive rounds */
+
+      for (; len < 0 && round_start < round_end;
+              round_start = dhcp6c_get_milli_time())
+        {
+          /* Set timeout for receiving */
+
+          uint64_t t = round_end - round_start;
+          struct timeval retime =
+          {
+            t / MSEC_PER_SEC, (t % MSEC_PER_SEC) * MSEC_PER_SEC
+          };
+
+          /* check for dhcp6c_close */
+
+          if (pdhcp6c->cancel)
+            {
+              len = -1;
+              goto end;
+            }
+
+          setsockopt(pdhcp6c->sockfd, SOL_SOCKET, SO_RCVTIMEO,
+                     &retime, sizeof(retime));
+
+          /* Receive cycle */
+
+          len = recv(pdhcp6c->sockfd, buf, buf_length, 0);
+          if (type != DHCPV6_MSG_UNKNOWN)
+            {
+              ninfo("%s[type:%d] recv len[%d]\n", __func__, type, len);
+            }
+
+          if (!dhcp6c_response_is_valid(handle, buf, len, trid, type))
+            {
+              len = -1;
+            }
+
+          if (len > 0)
+            {
+              FAR uint8_t *opt = &buf[4];
+              FAR uint8_t *opt_end = opt + len - 4;
+
+              round_start = dhcp6c_get_milli_time();
+              elapsed = round_start - start;
+              ninfo("Got a valid reply after %ums\n", (unsigned)elapsed);
+
+              if (retx->handler_reply != NULL)
+                {
+                  len = retx->handler_reply(handle, type, opt,
+                                            opt_end, elapsed / MSEC_PER_SEC);
+                }
+            }
+        }
+
+      if (retx->handler_finish != NULL)
+        {
+          len = retx->handler_finish(handle, elapsed / MSEC_PER_SEC);
+        }
+    }
+  while (len < 0 && elapsed / MSEC_PER_SEC < timeout);
+
+end:
+  free(buf);
+  return len;
+}
+
+static int dhcp6c_poll_reconfigure(FAR void *handle)
+{
+  int ret = dhcp6c_command(handle, DHCPV6_MSG_UNKNOWN);
+  if (ret != -1)
+    {
+      ret = dhcp6c_command(handle, ret);
+    }
+
+  return ret;
+}
+
+/* Collect all advertised servers */
+
+static int dhcp6c_handle_advert(FAR void *handle, enum dhcpv6_msg_e orig,
+                                FAR const void *opt, FAR const void *end,
+                                uint32_t elapsed)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  uint16_t olen;
+  uint16_t otype;
+  FAR uint8_t *ite0;
+  FAR uint8_t *odata;
+  struct dhcpv6_server_cand_s cand;
+
+  memset(&cand, 0, sizeof(cand));
+  dhcpv6_for_each_option(ite0, opt, end, otype, olen, odata)
+    {
+      if (otype == DHCPV6_OPT_SERVERID && olen <= 130)
+        {
+          memcpy(cand.duid, odata, olen);
+          cand.duid_len = olen;
+        }
+      else if (otype == DHCPV6_OPT_STATUS && olen >= 2 && !odata[0]
+               && odata[1] == DHCPV6_NOADDRSAVAIL)
+        {
+          if (pdhcp6c->ia_mode == IA_MODE_FORCE)
+            {
+              return -1;
+            }
+          else
+            {
+              cand.has_noaddravail = true;
+              cand.preference -= 1000;
+            }
+        }
+      else if (otype == DHCPV6_OPT_STATUS && olen >= 2 && !odata[0]
+               && odata[1] == DHCPV6_NOPREFIXAVAIL)
+        {
+          cand.preference -= 2000;
+        }
+      else if (otype == DHCPV6_OPT_PREF && olen >= 1 &&
+               cand.preference >= 0)
+        {
+          cand.preference = odata[1];
+        }
+      else if (otype == DHCPV6_OPT_RECONF_ACCEPT)
+        {
+          cand.wants_reconfigure = true;
+        }
+      else if (otype == DHCPV6_OPT_IA_PD && pdhcp6c->request_prefix)
+        {
+          FAR struct dhcpv6_ia_hdr_s *h = (FAR void *)odata;
+          FAR uint8_t *oend = odata + olen;
+          FAR uint8_t *ite1;
+          FAR uint8_t *d;
+
+          dhcpv6_for_each_option(ite1, &h[1], oend, otype, olen, d)
+            {
+              if (otype == DHCPV6_OPT_IA_PREFIX)
+                {
+                  cand.preference += 2000;
+                }
+              else if (otype == DHCPV6_OPT_STATUS &&
+                       olen >= 2 && d[0] == 0 &&
+                       d[1] == DHCPV6_NOPREFIXAVAIL)
+                {
+                  cand.preference -= 2000;
+                }
+            }
+        }
+    }
+
+  if (cand.duid_len > 0)
+    {
+      dhcp6c_add_state(handle, STATE_SERVER_CAND, &cand, sizeof(cand));
+    }
+
+  return 0;
+}
+
+static int dhcp6c_commit_advert(FAR void *handle, uint32_t elapsed)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  size_t cand_len;
+  FAR struct dhcpv6_server_cand_s *c = NULL;
+  FAR struct dhcpv6_server_cand_s *cand = dhcp6c_get_state(handle,
+                                                           STATE_SERVER_CAND,
+                                                           &cand_len);
+  bool retry = false;
+
+  for (size_t i = 0; i < cand_len / sizeof(*c); ++i)
+    {
+      if (cand[i].has_noaddravail)
+        {
+          retry = true;
+        }
+
+      if (c == NULL || c->preference < cand[i].preference)
+        {
+          c = &cand[i];
+        }
+    }
+
+  if (retry && pdhcp6c->ia_mode == IA_MODE_TRY)
+    {
+      /* We give it a second try without the IA_NA */
+
+      pdhcp6c->ia_mode = IA_MODE_NONE;
+      return dhcp6c_command(handle, DHCPV6_MSG_SOLICIT);
+    }
+
+  if (c != NULL)
+    {
+      uint16_t hdr[2] =
+      {
+        htons(DHCPV6_OPT_SERVERID), htons(c->duid_len)
+      };
+
+      dhcp6c_add_state(handle, STATE_SERVER_ID, hdr, sizeof(hdr));
+      dhcp6c_add_state(handle, STATE_SERVER_ID, c->duid, c->duid_len);
+      pdhcp6c->accept_reconfig = c->wants_reconfigure;
+    }
+
+  dhcp6c_clear_state(handle, STATE_SERVER_CAND);
+
+  if (c == NULL)
+    {
+      return -1;
+    }
+  else if (pdhcp6c->request_prefix || pdhcp6c->ia_mode != IA_MODE_NONE)
+    {
+      return DHCPV6_STATEFUL;
+    }
+  else
+    {
+      return DHCPV6_STATELESS;
+    }
+}
+
+static time_t dhcp6c_parse_ia(FAR void *handle, FAR void *opt, FAR void *end)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  uint32_t timeout = UINT32_MAX;
+  uint16_t otype;
+  uint16_t olen;
+  uint16_t stype;
+  uint16_t slen;
+  FAR uint8_t *ite0;
+  FAR uint8_t *odata;
+  FAR uint8_t *sdata;
+
+  /* Update address IA */
+
+  dhcpv6_for_each_option(ite0, opt, end, otype, olen, odata)
+    {
+      if (otype == DHCPV6_OPT_IA_PREFIX)
+        {
+          FAR struct dhcpv6_ia_prefix_s *prefix = (FAR void *)(odata - 4);
+          FAR struct dhcpv6_ia_prefix_s *local = NULL;
+          uint32_t valid;
+          uint32_t pref;
+          size_t pd_len;
+          FAR uint8_t *pd;
+          FAR uint8_t *ite1;
+
+          if (olen + 4u < sizeof(*prefix))
+            {
+              continue;
+            }
+
+          olen = sizeof(*prefix);
+          valid = ntohl(prefix->valid);
+          pref = ntohl(prefix->preferred);
+
+          if (pref > valid)
+            {
+              continue;
+            }
+
+          /* Search matching IA */
+
+          pd = dhcp6c_get_state(handle, STATE_IA_PD, &pd_len);
+          dhcpv6_for_each_option(ite1, pd, pd + pd_len,
+                                 stype, slen, sdata)
+            {
+              if (memcmp(sdata + 8, odata + 8,
+                         sizeof(local->addr) + 1) == 0)
+                {
+                  local = (FAR void *)(sdata - 4);
+                }
+            }
+
+          if (local != NULL)
+            {
+              local->preferred = prefix->preferred;
+              local->valid = prefix->valid;
+            }
+          else
+            {
+              dhcp6c_add_state(handle, STATE_IA_PD, prefix, olen);
+            }
+
+          if (timeout > valid)
+            {
+              timeout = valid;
+            }
+        }
+      else if (otype == DHCPV6_OPT_IA_ADDR)
+        {
+          FAR struct dhcpv6_ia_addr_s *addr = (FAR void *)(odata - 4);
+          FAR struct dhcpv6_ia_addr_s *local = NULL;
+          uint32_t pref;
+          uint32_t valid;
+          size_t na_len;
+          FAR uint8_t *na;
+          FAR uint8_t *ite1;
+
+          if (olen + 4u < sizeof(*addr))
+            {
+              continue;
+            }
+
+          olen = sizeof(*addr);
+          pref = ntohl(addr->preferred);
+          valid = ntohl(addr->valid);
+
+          if (pref > valid)
+            {
+              continue;
+            }
+
+          /* Search matching IA */
+
+          na = dhcp6c_get_state(handle, STATE_IA_NA, &na_len);
+          dhcpv6_for_each_option(ite1, na, na + na_len,
+                                 stype, slen, sdata)
+            {
+              if (memcmp(sdata, odata, sizeof(local->addr)) == 0)
+                {
+                  local = (FAR void *)(sdata - 4);
+                }
+            }
+
+          if (local != NULL)
+            {
+              local->preferred = addr->preferred;
+              local->valid = addr->valid;
+            }
+          else
+            {
+              dhcp6c_add_state(handle, STATE_IA_NA, addr, olen);
+            }
+
+          if (timeout > valid)
+            {
+              timeout = valid;
+            }
+        }
+    }
+
+  return timeout;
+}
+
+static int dhcp6c_handle_reply(FAR void *handle, enum dhcpv6_msg_e orig,
+                               FAR const void *opt, FAR const void *end,
+                               uint32_t elapsed)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  uint16_t otype;
+  uint16_t olen;
+  FAR uint8_t *ite0;
+  FAR uint8_t *odata;
+  bool have_update = false;
+  size_t ia_na_len;
+  size_t ia_pd_len;
+  size_t dns_len;
+  size_t search_len;
+  size_t sntp_ip_len;
+  size_t sntp_dns_len;
+  size_t sip_ip_len;
+  size_t sip_fqdn_len;
+  FAR uint8_t *ia_na = dhcp6c_get_state(handle, STATE_IA_NA, &ia_na_len);
+  FAR uint8_t *ia_pd = dhcp6c_get_state(handle, STATE_IA_PD, &ia_pd_len);
+  FAR uint8_t *ia_end;
+  pdhcp6c->t1 = UINT32_MAX;
+  pdhcp6c->t2 = UINT32_MAX;
+  pdhcp6c->t3 = UINT32_MAX;
+
+  dhcp6c_get_state(handle, STATE_DNS, &dns_len);
+  dhcp6c_get_state(handle, STATE_SEARCH, &search_len);
+  dhcp6c_get_state(handle, STATE_SNTP_IP, &sntp_ip_len);
+  dhcp6c_get_state(handle, STATE_SNTP_FQDN, &sntp_dns_len);
+  dhcp6c_get_state(handle, STATE_SIP_IP, &sip_ip_len);
+  dhcp6c_get_state(handle, STATE_SIP_FQDN, &sip_fqdn_len);
+
+  /* Decrease valid and preferred lifetime of prefixes */
+
+  dhcpv6_for_each_option(ite0, ia_pd, ia_pd + ia_pd_len, otype, olen, odata)
+    {
+      FAR struct dhcpv6_ia_prefix_s *p = (FAR void *)(odata - 4);
+      uint32_t valid = ntohl(p->valid);
+      uint32_t pref = ntohl(p->preferred);
+
+      if (valid != UINT32_MAX)
+        {
+          p->valid = (valid < elapsed) ? 0 : htonl(valid - elapsed);
+        }
+
+      if (pref != UINT32_MAX)
+        {
+          p->preferred = (pref < elapsed) ? 0 : htonl(pref - elapsed);
+        }
+    }
+
+  /* Decrease valid and preferred lifetime of addresses */
+
+  dhcpv6_for_each_option(ite0, ia_na, ia_na + ia_na_len, otype, olen, odata)
+    {
+      FAR struct dhcpv6_ia_addr_s *p = (FAR void *)(odata - 4);
+      uint32_t valid = ntohl(p->valid);
+      uint32_t pref = ntohl(p->preferred);
+
+      if (valid != UINT32_MAX)
+        {
+          p->valid = (valid < elapsed) ? 0 : htonl(valid - elapsed);
+        }
+
+      if (pref != UINT32_MAX)
+        {
+          p->preferred = (pref < elapsed) ? 0 : htonl(pref - elapsed);
+        }
+    }
+
+  /* Parse and find all matching IAs */
+
+  dhcpv6_for_each_option(ite0, opt, end, otype, olen, odata)
+    {
+      if ((otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA)
+           && olen > sizeof(struct dhcpv6_ia_hdr_s))
+        {
+          FAR struct dhcpv6_ia_hdr_s *ia_hdr = (FAR void *)(odata - 4);
+          time_t l_t1 = ntohl(ia_hdr->t1);
+          time_t l_t2 = ntohl(ia_hdr->t2);
+          uint16_t stype;
+          uint16_t slen;
+          FAR uint8_t *ite1;
+          FAR uint8_t *sdata;
+          time_t n;
+
+          /* Test ID and T1-T2 validity */
+
+          if (ia_hdr->iaid != 1 || l_t2 < l_t1)
+            {
+              continue;
+            }
+
+          /* Test status and bail if error */
+
+          dhcpv6_for_each_option(ite1, &ia_hdr[1], odata + olen,
+                                 stype, slen, sdata)
+            {
+              if (stype == DHCPV6_OPT_STATUS && slen >= 2 &&
+                  (sdata[0] || sdata[1]))
+                {
+                  continue;
+                }
+            }
+
+          /* Update times */
+
+          if (l_t1 > 0 && pdhcp6c->t1 > l_t1)
+            {
+              pdhcp6c->t1 = l_t1;
+            }
+
+          if (l_t2 > 0 && pdhcp6c->t2 > l_t2)
+            {
+              pdhcp6c->t2 = l_t2;
+            }
+
+          /* Always report update in case we have IA_PDs so that
+           * the state-script is called with updated times
+           */
+
+          if (otype == DHCPV6_OPT_IA_PD && pdhcp6c->request_prefix)
+            {
+              have_update = true;
+            }
+
+          n = dhcp6c_parse_ia(handle, &ia_hdr[1], odata + olen);
+          if (n < pdhcp6c->t1)
+            {
+              pdhcp6c->t1 = n;
+            }
+
+          if (n < pdhcp6c->t2)
+            {
+              pdhcp6c->t2 = n;
+            }
+
+          if (n < pdhcp6c->t3)
+            {
+              pdhcp6c->t3 = n;
+            }
+        }
+      else if (otype == DHCPV6_OPT_DNS_SERVERS)
+        {
+          if (olen % 16 == 0)
+            {
+              dhcp6c_add_state(handle, STATE_DNS, odata, olen);
+            }
+        }
+      else if (otype == DHCPV6_OPT_DNS_DOMAIN)
+        {
+          dhcp6c_add_state(handle, STATE_SEARCH, odata, olen);
+        }
+      else if (otype == DHCPV6_OPT_NTP_SERVER)
+        {
+          uint16_t stype;
+          uint16_t slen;
+          FAR uint8_t *sdata;
+          FAR uint8_t *ite1;
+
+          /* Test status and bail if error */
+
+          dhcpv6_for_each_option(ite1, odata, odata + olen,
+                                 stype, slen, sdata)
+            {
+              if (slen == 16 &&
+                  (stype == NTP_MC_ADDR || stype == NTP_SRV_ADDR))
+                {
+                  dhcp6c_add_state(handle, STATE_SNTP_IP, sdata, slen);
+                }
+              else if (slen > 0 && stype == NTP_SRV_FQDN)
+                {
+                  dhcp6c_add_state(handle, STATE_SNTP_FQDN, sdata, slen);
+                }
+            }
+        }
+      else if (otype == DHCPV6_OPT_SIP_SERVER_A)
+        {
+          if (olen == 16)
+            {
+              dhcp6c_add_state(handle, STATE_SIP_IP, odata, olen);
+            }
+        }
+      else if (otype == DHCPV6_OPT_SIP_SERVER_D)
+        {
+          dhcp6c_add_state(handle, STATE_SIP_FQDN, odata, olen);
+        }
+      else if (otype == DHCPV6_OPT_INFO_REFRESH && olen >= 4)
+        {
+          uint32_t refresh = ntohl(*((FAR uint32_t *)odata));
+          if (refresh < (uint32_t)pdhcp6c->t1)
+            {
+              pdhcp6c->t1 = refresh;
+            }
+        }
+      else if (otype != DHCPV6_OPT_CLIENTID && otype != DHCPV6_OPT_SERVERID)
+        {
+          dhcp6c_add_state(handle, STATE_CUSTOM_OPTS, (odata - 4), olen + 4);
+        }
+    }
+
+  if (opt != NULL)
+    {
+      size_t new_ia_pd_len;
+      size_t new_ia_na_len;
+      have_update |= dhcp6c_commit_state(handle, STATE_DNS, dns_len);
+      have_update |= dhcp6c_commit_state(handle, STATE_SEARCH, search_len);
+      have_update |= dhcp6c_commit_state(handle, STATE_SNTP_IP,
+                                         sntp_ip_len);
+      have_update |= dhcp6c_commit_state(handle, STATE_SNTP_FQDN,
+                                         sntp_dns_len);
+      have_update |= dhcp6c_commit_state(handle, STATE_SIP_IP, sip_ip_len);
+      have_update |= dhcp6c_commit_state(handle, STATE_SIP_FQDN,
+                                         sip_fqdn_len);
+      dhcp6c_get_state(handle, STATE_IA_PD, &new_ia_pd_len);
+      dhcp6c_get_state(handle, STATE_IA_NA, &new_ia_na_len);
+      have_update |= (new_ia_pd_len != ia_pd_len) ||
+                     (new_ia_na_len != ia_na_len);
+    }
+
+  /* Delete prefixes with 0 valid-time */
+
+  ia_pd = dhcp6c_get_state(handle, STATE_IA_PD, &ia_pd_len);
+  ia_end = ia_pd + ia_pd_len;
+  dhcpv6_for_each_option(ite0, ia_pd, ia_end, otype, olen, odata)
+    {
+      FAR struct dhcpv6_ia_prefix_s *p = (FAR void *)(odata - 4);
+      while (!p->valid)
+        {
+          ia_end = ia_pd + dhcp6c_remove_state(handle, STATE_IA_PD,
+                   (FAR uint8_t *)p - ia_pd, olen + 4);
+          have_update = true;
+        }
+    }
+
+  /* Delete addresses with 0 valid-time */
+
+  ia_na = dhcp6c_get_state(handle, STATE_IA_NA, &ia_na_len);
+  ia_end = ia_na + ia_na_len;
+  dhcpv6_for_each_option(ite0, ia_na, ia_end, otype, olen, odata)
+    {
+      FAR struct dhcpv6_ia_addr_s *p = (FAR void *)(odata - 4);
+      while (!p->valid)
+        {
+          ia_end = ia_na + dhcp6c_remove_state(handle, STATE_IA_NA,
+                   (FAR uint8_t *)p - ia_na, olen + 4);
+          have_update = true;
+        }
+    }
+
+  return have_update;
+}
+
+static int dhcp6c_handle_reconfigure(FAR void *handle,
+                                     enum dhcpv6_msg_e orig,
+                                     FAR const void *opt,
+                                     FAR const void *end,
+                                     uint32_t elapsed)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  uint16_t otype;
+  uint16_t olen;
+  FAR uint8_t *odata;
+  FAR uint8_t *ite;
+  uint8_t msg = DHCPV6_MSG_RENEW;
+
+  /* TODO: should verify the reconfigure message */
+
+  dhcpv6_for_each_option(ite, opt, end, otype, olen, odata)
+    {
+      if (otype == DHCPV6_OPT_RECONF_MESSAGE && olen == 1 &&
+          (odata[0] == DHCPV6_MSG_RENEW ||
+           odata[0] == DHCPV6_MSG_INFO_REQ))
+        {
+          msg = odata[0];
+        }
+    }
+
+  pdhcp6c->t1 -= elapsed;
+  pdhcp6c->t2 -= elapsed;
+  pdhcp6c->t3 -= elapsed;
+
+  if (pdhcp6c->t1 < 0)
+    {
+      pdhcp6c->t1 = 0;
+    }
+
+  if (pdhcp6c->t2 < 0)
+    {
+      pdhcp6c->t2 = 0;
+    }
+
+  if (pdhcp6c->t3 < 0)
+    {
+      pdhcp6c->t3 = 0;
+    }
+
+  dhcp6c_handle_reply(handle, DHCPV6_MSG_UNKNOWN, NULL, NULL, elapsed);
+
+  return msg;
+}
+
+static int dhcp6c_handle_rebind_reply(FAR void *handle,
+                                      enum dhcpv6_msg_e orig,
+                                      FAR const void *opt,
+                                      FAR const void *end,
+                                      uint32_t elapsed)
+{
+  dhcp6c_handle_advert(handle, orig, opt, end, elapsed);
+  if (dhcp6c_commit_advert(handle, elapsed) < 0)
+    {
+      return -1;
+    }
+
+  return dhcp6c_handle_reply(handle, orig, opt, end, elapsed);
+}
+
+static int dhcp6c_single_request(FAR void *args)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)args;
+  FAR const char *process = NULL;
+  enum dhcpv6_mode_e mode;
+  enum dhcpv6_msg_e type;
+  int ret = -1;
+
+  dhcp6c_clear_state(pdhcp6c, STATE_SERVER_ID);
+  dhcp6c_clear_state(pdhcp6c, STATE_SERVER_CAND);
+  dhcp6c_clear_state(pdhcp6c, STATE_IA_PD);
+  dhcp6c_clear_state(pdhcp6c, STATE_SNTP_IP);
+  dhcp6c_clear_state(pdhcp6c, STATE_SNTP_FQDN);
+  dhcp6c_clear_state(pdhcp6c, STATE_SIP_IP);
+  dhcp6c_clear_state(pdhcp6c, STATE_SIP_FQDN);
+  dhcp6c_clear_state(pdhcp6c, STATE_CUSTOM_OPTS);
+  ret = dhcp6c_command(pdhcp6c, DHCPV6_MSG_SOLICIT);
+  if (ret < 0)
+    {
+      return -1;
+    }
+  else if (ret == DHCPV6_STATELESS)
+    {
+      mode = DHCPV6_STATELESS;
+      type = DHCPV6_MSG_INFO_REQ;
+      process = "informed";
+    }
+  else
+    {
+      mode = DHCPV6_STATEFUL;
+      type = DHCPV6_MSG_REQUEST;
+      process = "bound";
+    }
+
+  ret = dhcp6c_command(pdhcp6c, type);
+  if (ret >= 0)
+    {
+      ret = mode;
+      dhcp6c_switch_process(pdhcp6c, process);
+    }
+
+  return ret;
+}
+
+static int dhcp6c_lease(FAR void *args, uint8_t type)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)args;
+  enum dhcpv6_mode_e mode = (enum dhcpv6_mode_e)type;
+  size_t ia_pd_len;
+  size_t ia_na_len;
+  size_t ia_pd_new;
+  size_t ia_na_new;
+  size_t server_id_len;
+  int ret = -1;
+
+  if (mode == DHCPV6_STATELESS)
+    {
+      /* Stateless mode */
+
+      while (!pdhcp6c->cancel)
+        {
+          /* Wait for T1 to expire or until we get a reconfigure */
+
+          ret = dhcp6c_poll_reconfigure(pdhcp6c);
+          if (ret >= 0)
+            {
+              dhcp6c_switch_process(pdhcp6c, "informed");
+            }
+
+          if (pdhcp6c->cancel)
+            {
+              break;
+            }
+
+          /* Information-Request */
+
+          ret = dhcp6c_command(pdhcp6c, DHCPV6_MSG_INFO_REQ);
+          if (ret < 0)
+            {
+              nerr("DHCPV6_MSG_INFO_REQ error\n");
+              break;
+            }
+          else
+            {
+              dhcp6c_switch_process(pdhcp6c, "informed");
+            }
+        }
+    }
+  else
+    {
+      /* Stateful mode */
+
+      while (!pdhcp6c->cancel)
+        {
+          /* Renew Cycle
+           * Wait for T1 to expire or until we get a reconfigure
+           */
+
+          ret = dhcp6c_poll_reconfigure(pdhcp6c);
+          if (ret >= 0)
+            {
+              dhcp6c_switch_process(pdhcp6c, "updated");
+            }
+
+          if (pdhcp6c->cancel)
+            {
+              break;
+            }
+
+          dhcp6c_get_state(pdhcp6c, STATE_IA_PD, &ia_pd_len);
+          dhcp6c_get_state(pdhcp6c, STATE_IA_NA, &ia_na_len);
+
+          /* If we have any IAs, send renew, otherwise request */
+
+          if (ia_pd_len == 0 && ia_na_len == 0)
+            ret = dhcp6c_command(pdhcp6c, DHCPV6_MSG_REQUEST);
+          else
+            ret = dhcp6c_command(pdhcp6c, DHCPV6_MSG_RENEW);
+
+          if (pdhcp6c->cancel)
+            {
+              break;
+            }
+
+          if (ret >= 0)
+            {
+              /* Publish updates */
+
+              dhcp6c_switch_process(pdhcp6c, "updated");
+            }
+          else
+            {
+              /* Remove binding */
+
+              dhcp6c_clear_state(pdhcp6c, STATE_SERVER_ID);
+
+              /* If we have IAs, try rebind otherwise restart */
+
+              ret = dhcp6c_command(pdhcp6c, DHCPV6_MSG_REBIND);
+              dhcp6c_get_state(pdhcp6c, STATE_IA_PD, &ia_pd_new);
+              dhcp6c_get_state(pdhcp6c, STATE_IA_NA, &ia_na_new);
+
+              /* We lost all our IAs, restart */
+
+              if (ret < 0 || (ia_pd_new == 0 && ia_pd_len) ||
+                  (ia_na_new == 0 && ia_na_len))
+                {
+                  break;
+                }
+              else if (ret >= 0)
+                {
+                  dhcp6c_switch_process(pdhcp6c, "rebound");
+                }
+            }
+        }
+    }
+
+  dhcp6c_get_state(pdhcp6c, STATE_IA_PD, &ia_pd_len);
+  dhcp6c_get_state(pdhcp6c, STATE_IA_NA, &ia_na_len);
+  dhcp6c_get_state(pdhcp6c, STATE_SERVER_ID, &server_id_len);
+
+  /* Add all prefixes to lost prefixes */
+
+  dhcp6c_clear_state(pdhcp6c, STATE_IA_PD);
+  dhcp6c_switch_process(pdhcp6c, "unbound");
+
+  /* Remove assigned addresses */
+
+  if (ia_na_len > 0)
+    {
+      dhcp6c_remove_addrs(pdhcp6c);
+    }
+
+  if (server_id_len > 0 && (ia_pd_len > 0 || ia_na_len > 0))
+    {
+      dhcp6c_command(pdhcp6c, DHCPV6_MSG_RELEASE);
+    }
+
+  return ret;
+}
+
+static FAR void *dhcp6c_run(FAR void *args)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)args;
+  int ret;
+
+  while (!pdhcp6c->cancel)
+    {
+      ret = dhcp6c_single_request(pdhcp6c);
+      if (ret > 0)
+        {
+          dhcp6c_lease(pdhcp6c, ret);
+        }
+    }
+
+  return 0;
+}
+
+static FAR void *dhcp6c_precise_open(FAR const char *ifname,
+                                     enum dhcp6c_ia_mode_e ia_mode,
+                                     bool request_pd,
+                                     uint16_t opt[], int cnt)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c;
+  struct ifreq ifr;
+  size_t client_id_len;
+  int val = 1;
+  uint16_t oro[] =
+  {
+    htons(DHCPV6_OPT_DNS_SERVERS),
+    htons(DHCPV6_OPT_DNS_DOMAIN),
+    htons(DHCPV6_OPT_NTP_SERVER),
+    htons(DHCPV6_OPT_SIP_SERVER_A),
+    htons(DHCPV6_OPT_SIP_SERVER_D)
+  };
+
+  struct sockaddr_in6 client_addr =
+  {
+    AF_INET6,
+    htons(DHCPV6_CLIENT_PORT),
+    0,
+    {0},
+    0
+  };
+
+  pdhcp6c = malloc(sizeof(struct dhcp6c_state_s));
+  if (pdhcp6c == NULL)
+    {
+      return NULL;
+    }
+
+  memset(pdhcp6c, 0, sizeof(*pdhcp6c));
+  pdhcp6c->urandom_fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY);
+  if (pdhcp6c->urandom_fd < 0)
+    {
+      free(pdhcp6c);
+      return NULL;
+    }
+
+  pdhcp6c->sockfd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+  if (pdhcp6c->sockfd < 0)
+    {
+      close(pdhcp6c->urandom_fd);
+      free(pdhcp6c);
+      return NULL;
+    }
+
+  /* Detect interface */
+
+  strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+  if (ioctl(pdhcp6c->sockfd, SIOCGIFINDEX, &ifr))
+    {
+      close(pdhcp6c->urandom_fd);
+      close(pdhcp6c->sockfd);
+      free(pdhcp6c);
+      return NULL;
+    }
+
+  pdhcp6c->ifindex = ifr.ifr_ifindex;
+
+  /* Create client DUID */
+
+  dhcp6c_get_state(pdhcp6c, STATE_CLIENT_ID, &client_id_len);
+  if (client_id_len == 0)
+    {
+      uint8_t duid[14] =
+      {
+        0, DHCPV6_OPT_CLIENTID, 0, 10, 0,
+        DHCPV6_DUID_LLADDR, 0, 1
+      };
+
+      uint8_t zero[ETHER_ADDR_LEN];
+      struct ifreq ifs[100];
+      FAR struct ifreq *ifp;
+      FAR struct ifreq *ifend;
+      struct ifconf ifc;
+
+      ioctl(pdhcp6c->sockfd, SIOCGIFHWADDR, &ifr);
+      memcpy(&duid[8], ifr.ifr_hwaddr.sa_data, ETHER_ADDR_LEN);
+      memset(zero, 0, sizeof(zero));
+      ifc.ifc_req = ifs;
+      ifc.ifc_len = sizeof(ifs);
+
+      if (memcmp(&duid[8], zero, ETHER_ADDR_LEN) == 0 &&
+          ioctl(pdhcp6c->sockfd, SIOCGIFCONF, &ifc) >= 0)
+        {
+          /* If our interface doesn't have an address... */
+
+          ifend = ifs + (ifc.ifc_len / sizeof(struct ifreq));
+          for (ifp = ifc.ifc_req; ifp < ifend &&
+               memcmp(&duid[8], zero, 6) == 0; ifp++)
+            {
+              memcpy(ifr.ifr_name, ifp->ifr_name,
+                     sizeof(ifr.ifr_name));
+              ioctl(pdhcp6c->sockfd, SIOCGIFHWADDR, &ifr);
+              memcpy(&duid[8], ifr.ifr_hwaddr.sa_data,
+                     ETHER_ADDR_LEN);
+            }
+        }
+
+      dhcp6c_add_state(pdhcp6c, STATE_CLIENT_ID, duid, sizeof(duid));
+    }
+
+  /* Create ORO */
+
+  dhcp6c_add_state(pdhcp6c, STATE_ORO, oro, sizeof(oro));
+
+  /* Configure IPv6-options */
+
+  setsockopt(pdhcp6c->sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
+  setsockopt(pdhcp6c->sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+  setsockopt(pdhcp6c->sockfd, SOL_SOCKET, UDP_BINDTODEVICE, ifname,
+             strlen(ifname));
+  if (bind(pdhcp6c->sockfd, (struct sockaddr *)&client_addr,
+           sizeof(client_addr)) != 0)
+    {
+      close(pdhcp6c->urandom_fd);
+      close(pdhcp6c->sockfd);
+      free(pdhcp6c);
+      return NULL;
+    }
+
+  pdhcp6c->thread = 0;
+  pdhcp6c->cancel = false;
+  pdhcp6c->t1 = 0;
+  pdhcp6c->t2 = 0;
+  pdhcp6c->t3 = 0;
+  pdhcp6c->request_prefix = request_pd;
+  switch (ia_mode)
+    {
+      case IA_MODE_NONE:
+      case IA_MODE_TRY:
+      case IA_MODE_FORCE:
+        break;
+      default:
+        ia_mode = IA_MODE_TRY;
+        break;
+    }
+
+  pdhcp6c->ia_mode = ia_mode;
+  pdhcp6c->accept_reconfig = false;
+  if (opt != NULL && cnt > 0)
+    {
+      uint16_t opttype;
+      for (int i = 0; i < cnt; i++)
+        {
+          opttype = htons(opt[i]);
+          dhcp6c_add_state(pdhcp6c, STATE_ORO, &opttype, 2);
+        }
+    }
+
+  return pdhcp6c;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+FAR void *dhcp6c_open(FAR const char *interface)
+{
+  return dhcp6c_precise_open(interface, IA_MODE_TRY, true, NULL, 0);
+}
+
+int dhcp6c_request(FAR void *handle, FAR struct dhcp6c_state *presult)
+{
+  int ret;
+
+  if (handle == NULL)
+    {
+      return ERROR;
+    }
+
+  ret = dhcp6c_single_request(handle);
+  if (ret >= 0)
+    {
+      dhcp6c_get_result(handle, presult);
+      return OK;
+    }
+  else
+    {
+      return ERROR;
+    }
+}
+
+int dhcp6c_request_async(FAR void *handle, dhcp6c_callback_t callback)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  int ret;
+
+  if (handle == NULL)
+    {
+      return ERROR;
+    }
+
+  if (pdhcp6c->thread)
+    {
+      nerr("DHCP6C thread already running\n");
+      return ERROR;
+    }
+
+  pdhcp6c->callback = callback;
+  ret = pthread_create(&pdhcp6c->thread, NULL, dhcp6c_run, pdhcp6c);
+  if (ret != 0)
+    {
+      nerr("Failed to start the DHCP6C thread\n");
+      return ERROR;
+    }
+
+  return OK;
+}
+
+void dhcp6c_cancel(FAR void *handle)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+  sighandler_t old;
+  int ret;
+
+  if (pdhcp6c != NULL)
+    {
+      pdhcp6c->cancel = true;
+      if (pdhcp6c->thread)
+        {
+          old = signal(SIGQUIT, SIG_IGN);
+
+          /* Signal the dhcp6c_run */
+
+          ret = pthread_kill(pdhcp6c->thread, SIGQUIT);
+          if (ret != 0)
+            {
+              nerr("pthread_kill DHCP6C thread\n");
+            }
+
+          /* Wait for the end of dhcp6c_run */
+
+          ret = pthread_join(pdhcp6c->thread, NULL);
+          if (ret != 0)
+            {
+              nerr("pthread_join DHCP6C thread\n");
+            }
+
+          pdhcp6c->thread = 0;
+          signal(SIGQUIT, old);
+        }
+    }
+}
+
+void dhcp6c_close(FAR void *handle)
+{
+  FAR struct dhcp6c_state_s *pdhcp6c = (FAR struct dhcp6c_state_s *)handle;
+
+  if (pdhcp6c != NULL)
+    {
+      dhcp6c_cancel(pdhcp6c);
+      if (pdhcp6c->urandom_fd > 0)
+        {
+          close(pdhcp6c->urandom_fd);
+        }
+
+      if (pdhcp6c->sockfd > 0)
+        {
+          close(pdhcp6c->sockfd);
+        }
+
+      for (int i = 0; i < STATE_MAX; i++)
+        {
+          if (pdhcp6c->state_data[i] != NULL)
+            {
+              free(pdhcp6c->state_data[i]);
+            }
+        }
+
+      free(pdhcp6c);
+    }
+}