From f85388578c1f6c07aee0334b6cd09106a4032a16 Mon Sep 17 00:00:00 2001 From: songlinzhang Date: Mon, 14 Mar 2022 16:35:01 +0800 Subject: [PATCH] Add dhcp6c module Signed-off-by: songlinzhang --- 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(+) create mode 100644 include/netutils/dhcp6c.h create mode 100644 netutils/dhcp6c/Kconfig create mode 100644 netutils/dhcp6c/Make.defs create mode 100644 netutils/dhcp6c/Makefile create mode 100644 netutils/dhcp6c/dhcp6c.c diff --git a/include/netutils/dhcp6c.h b/include/netutils/dhcp6c.h new file mode 100644 index 000000000..b798274c0 --- /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 +#include +#include + +/**************************************************************************** + * 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 000000000..b4d3c30ba --- /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 000000000..e8c11b05d --- /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 000000000..c4c659693 --- /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 000000000..cbfe56042 --- /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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, "", 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); + } +}