| /**************************************************************************** |
| * 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 <nuttx/clock.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 <pthread.h> |
| #include <sys/time.h> |
| #include <sys/param.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 <inttypes.h> |
| |
| #include "netutils/netlib.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 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 = false; |
| |
| if (new_len != 0 || old_len != 0) |
| { |
| 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:%"PRIu32" T2:%"PRIu32" 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 = nitems(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 %"PRIu32" 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) |
| { |
| 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 sockaddr_in6 client_addr; |
| 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) |
| }; |
| |
| 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 */ |
| |
| strlcpy(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, SO_BINDTODEVICE, ifname, |
| strlen(ifname)); |
| |
| memset(&client_addr, 0, sizeof(client_addr)); |
| client_addr.sin6_family = AF_INET6; |
| client_addr.sin6_port = htons(DHCPV6_CLIENT_PORT); |
| |
| 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); |
| } |
| } |