/**************************************************************************** * 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 #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 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) { 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 */ 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, 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); } }