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);
+ }
+}