nuttx-apps/netutils/dhcp6c/dhcp6c.c
songlinzhang f85388578c Add dhcp6c module
Signed-off-by: songlinzhang <songlinzhang@xiaomi.com>
2022-03-19 23:25:50 +08:00

1959 lines
51 KiB
C

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