mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
2d1160426d
The DHCPv6 server might send reponses multiple times. The DHCPv6 client will only handle the first response, if additional responses are comming in they are left in the RX queue. That results in the client always reading the response of a previous transaction on any subsequent transactions. In this case the client will try again, creating a new transaction - that will again only read the previous response. To fix this, discard previous responses by flushing the RX queue before sending a new message to the DHCPv6 server. fixes #13834
866 lines
28 KiB
C
866 lines
28 KiB
C
/*
|
|
* Copyright (C) 2018 Freie Universität Berlin
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @{
|
|
*
|
|
* @file
|
|
* @author Martine Lenders <m.lenders@fu-berlin.de>
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include "event.h"
|
|
#include "kernel_defines.h"
|
|
#include "net/dhcpv6/client.h"
|
|
#include "net/dhcpv6.h"
|
|
#include "net/sock/udp.h"
|
|
#include "random.h"
|
|
#include "timex.h"
|
|
#include "xtimer.h"
|
|
#include "xtimer/implementation.h"
|
|
|
|
#define ENABLE_DEBUG (0)
|
|
#include "debug.h"
|
|
|
|
#include "_dhcpv6.h"
|
|
|
|
/**
|
|
* @brief Representation of a generic lease
|
|
*/
|
|
typedef struct {
|
|
union {
|
|
uint32_t id;
|
|
struct {
|
|
uint16_t netif;
|
|
uint16_t type;
|
|
} info;
|
|
} ia_id;
|
|
} lease_t;
|
|
|
|
/**
|
|
* @brief Representation of a DHCPv6 prefix deligation lease
|
|
* @extends lease_t
|
|
*/
|
|
typedef struct {
|
|
lease_t parent;
|
|
ipv6_addr_t pfx;
|
|
uint8_t pfx_len;
|
|
uint8_t leased;
|
|
} pfx_lease_t;
|
|
|
|
/**
|
|
* @brief Client representation of a DHCPv6 server
|
|
*/
|
|
typedef struct {
|
|
dhcpv6_duid_t duid;
|
|
uint32_t t1;
|
|
uint8_t pref;
|
|
uint8_t duid_len;
|
|
} server_t;
|
|
|
|
static uint8_t send_buf[DHCPV6_CLIENT_BUFLEN];
|
|
static uint8_t recv_buf[DHCPV6_CLIENT_BUFLEN];
|
|
static uint8_t best_adv[DHCPV6_CLIENT_BUFLEN];
|
|
static uint8_t duid[DHCPV6_CLIENT_DUID_LEN];
|
|
static pfx_lease_t pfx_leases[DHCPV6_CLIENT_PFX_LEASE_MAX];
|
|
static server_t server;
|
|
static xtimer_t timer, rebind_timer;
|
|
static event_queue_t *event_queue;
|
|
static sock_udp_t sock;
|
|
static sock_udp_ep_t local = { .family = AF_INET6, .port = DHCPV6_CLIENT_PORT };
|
|
static sock_udp_ep_t remote = { .family = AF_INET6, .port = DHCPV6_SERVER_PORT,
|
|
.addr = {
|
|
.ipv6 = DHCPV6_ALL_RELAY_AGENTS_AND_SERVERS
|
|
} };
|
|
static uint32_t sol_max_rt = DHCPV6_SOL_MAX_RT;
|
|
static uint32_t t2, rebind_time;
|
|
static uint32_t transaction_start;
|
|
static uint32_t transaction_id;
|
|
static uint8_t duid_len = sizeof(dhcpv6_duid_l2_t);
|
|
|
|
static void _post_solicit_servers(void *args);
|
|
static void _solicit_servers(event_t *event);
|
|
static void _request(event_t *event);
|
|
static void _renew(event_t *event);
|
|
static void _rebind(event_t *event);
|
|
|
|
static event_t solicit_servers = { .handler = _solicit_servers };
|
|
static event_t request = { .handler = _request };
|
|
static event_t renew = { .handler = _renew };
|
|
static event_t rebind = { .handler = _rebind };
|
|
|
|
#ifdef MODULE_AUTO_INIT_DHCPV6_CLIENT
|
|
static char _thread_stack[DHCPV6_CLIENT_STACK_SIZE];
|
|
static void *_thread(void *args);
|
|
static kernel_pid_t _thread_pid;
|
|
|
|
void dhcpv6_client_auto_init(void)
|
|
{
|
|
if (_thread_pid <= 0) {
|
|
_thread_pid = thread_create(_thread_stack, DHCPV6_CLIENT_STACK_SIZE,
|
|
DHCPV6_CLIENT_PRIORITY,
|
|
THREAD_CREATE_STACKTEST,
|
|
_thread, NULL, "dhcpv6-client");
|
|
}
|
|
}
|
|
|
|
static void *_thread(void *args)
|
|
{
|
|
(void)args;
|
|
event_queue_t event_queue;
|
|
event_queue_init(&event_queue);
|
|
dhcpv6_client_init(&event_queue, SOCK_ADDR_ANY_NETIF);
|
|
/* TODO: add configuration for IA_NA here, when implemented */
|
|
dhcpv6_client_start();
|
|
event_loop(&event_queue); /* never returns */
|
|
return NULL;
|
|
}
|
|
#endif /* MODULE_AUTO_INIT_DHCPV6_CLIENT */
|
|
|
|
void dhcpv6_client_init(event_queue_t *eq, uint16_t netif)
|
|
{
|
|
assert(eq->waiter != NULL);
|
|
event_queue = eq;
|
|
local.netif = netif;
|
|
remote.netif = netif;
|
|
}
|
|
|
|
void dhcpv6_client_start(void)
|
|
{
|
|
uint32_t delay = random_uint32_range(0, DHCPV6_SOL_MAX_DELAY * US_PER_SEC);
|
|
|
|
duid_len = dhcpv6_client_get_duid_l2(local.netif,
|
|
(dhcpv6_duid_l2_t *)&duid);
|
|
if (duid_len > 0) {
|
|
sock_udp_create(&sock, &local, NULL, 0);
|
|
timer.callback = _post_solicit_servers;
|
|
xtimer_set(&timer, delay);
|
|
}
|
|
}
|
|
|
|
void dhcpv6_client_req_ia_pd(unsigned netif, unsigned pfx_len)
|
|
{
|
|
pfx_lease_t *lease = NULL;
|
|
|
|
assert(pfx_len <= 128);
|
|
for (unsigned i = 0; i < DHCPV6_CLIENT_PFX_LEASE_MAX; i++) {
|
|
if (pfx_leases[i].parent.ia_id.id == 0) {
|
|
lease = &pfx_leases[i];
|
|
lease->parent.ia_id.info.netif = netif;
|
|
lease->parent.ia_id.info.type = DHCPV6_OPT_IA_PD;
|
|
lease->pfx_len = pfx_len;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _post_solicit_servers(void *args)
|
|
{
|
|
(void)args;
|
|
event_post(event_queue, &solicit_servers);
|
|
}
|
|
|
|
static void _post_renew(void *args)
|
|
{
|
|
(void)args;
|
|
event_post(event_queue, &renew);
|
|
}
|
|
|
|
static void _post_rebind(void *args)
|
|
{
|
|
(void)args;
|
|
event_post(event_queue, &rebind);
|
|
}
|
|
|
|
static void _generate_tid(void)
|
|
{
|
|
transaction_id = random_uint32() & 0xffffff;
|
|
}
|
|
|
|
static void _set_tid(uint8_t *tgt)
|
|
{
|
|
tgt[0] = (transaction_id & 0xff0000) >> 16;
|
|
tgt[1] = (transaction_id & 0xff00) >> 8;
|
|
tgt[2] = transaction_id & 0xff;
|
|
}
|
|
|
|
static inline bool _is_tid(dhcpv6_msg_t *msg)
|
|
{
|
|
uint32_t tid = (((uint32_t)msg->tid[0]) << 16) |
|
|
(((uint32_t)msg->tid[1]) << 8) |
|
|
(msg->tid[2]);
|
|
|
|
return (transaction_id == (tid));
|
|
}
|
|
|
|
static inline uint32_t _now_cs(void)
|
|
{
|
|
return (uint32_t)(xtimer_now_usec64() / US_PER_CS);
|
|
}
|
|
|
|
static inline uint16_t _compose_cid_opt(dhcpv6_opt_duid_t *cid)
|
|
{
|
|
uint16_t len = duid_len;
|
|
|
|
cid->type = byteorder_htons(DHCPV6_OPT_CID);
|
|
cid->len = byteorder_htons(len);
|
|
memcpy(cid->duid, duid, duid_len);
|
|
return len + sizeof(dhcpv6_opt_t);
|
|
}
|
|
|
|
static inline uint16_t _compose_sid_opt(dhcpv6_opt_duid_t *sid)
|
|
{
|
|
uint16_t len = server.duid_len;
|
|
|
|
sid->type = byteorder_htons(DHCPV6_OPT_SID);
|
|
sid->len = byteorder_htons(len);
|
|
memcpy(sid->duid, server.duid.u8, server.duid_len);
|
|
return len + sizeof(dhcpv6_opt_t);
|
|
}
|
|
|
|
static inline uint16_t _get_elapsed_time(void)
|
|
{
|
|
uint32_t now = _now_cs();
|
|
uint32_t elapsed_time = transaction_start - now;
|
|
|
|
if (elapsed_time > UINT16_MAX) {
|
|
/* xtimer_now_usec64() overflowed since transaction_start */
|
|
elapsed_time = (UINT32_MAX - transaction_start) + now + 1;
|
|
}
|
|
return elapsed_time;
|
|
}
|
|
|
|
static inline size_t _compose_elapsed_time_opt(dhcpv6_opt_elapsed_time_t *time)
|
|
{
|
|
uint16_t len = 2U;
|
|
|
|
time->type = byteorder_htons(DHCPV6_OPT_ELAPSED_TIME);
|
|
time->len = byteorder_htons(len);
|
|
time->elapsed_time = byteorder_htons(_get_elapsed_time());
|
|
return len + sizeof(dhcpv6_opt_t);
|
|
}
|
|
|
|
static inline size_t _compose_oro_opt(dhcpv6_opt_oro_t *oro, uint16_t *opts,
|
|
unsigned opts_num)
|
|
{
|
|
uint16_t len = 2U * opts_num;
|
|
|
|
oro->type = byteorder_htons(DHCPV6_OPT_ORO);
|
|
oro->len = byteorder_htons(len);
|
|
for (unsigned i = 0; i < opts_num; i++) {
|
|
oro->opt_codes[i] = byteorder_htons(opts[i]);
|
|
}
|
|
return len + sizeof(dhcpv6_opt_t);
|
|
}
|
|
|
|
static inline size_t _compose_ia_pd_opt(dhcpv6_opt_ia_pd_t *ia_pd,
|
|
uint32_t ia_id, uint16_t opts_len)
|
|
{
|
|
uint16_t len = 12U + opts_len;
|
|
|
|
ia_pd->type = byteorder_htons(DHCPV6_OPT_IA_PD);
|
|
ia_pd->len = byteorder_htons(len);
|
|
ia_pd->ia_id = byteorder_htonl(ia_id);
|
|
ia_pd->t1.u32 = 0;
|
|
ia_pd->t2.u32 = 0;
|
|
return len + sizeof(dhcpv6_opt_t);
|
|
}
|
|
|
|
static inline size_t _add_ia_pd_from_config(uint8_t *buf)
|
|
{
|
|
size_t msg_len = 0;
|
|
|
|
for (unsigned i = 0; i < DHCPV6_CLIENT_PFX_LEASE_MAX; i++) {
|
|
uint32_t ia_id = pfx_leases[i].parent.ia_id.id;
|
|
if (ia_id != 0) {
|
|
dhcpv6_opt_ia_pd_t *ia_pd = (dhcpv6_opt_ia_pd_t *)(&buf[msg_len]);
|
|
|
|
msg_len += _compose_ia_pd_opt(ia_pd, ia_id, 0U);
|
|
}
|
|
}
|
|
return msg_len;
|
|
}
|
|
|
|
static inline int32_t get_rand_us_factor(void)
|
|
{
|
|
int32_t res = ((int32_t)random_uint32_range(0, 200 * US_PER_MS));
|
|
res -= 100 * US_PER_SEC;
|
|
return res;
|
|
}
|
|
|
|
static inline uint32_t _irt_us(uint16_t irt, bool greater_irt)
|
|
{
|
|
uint32_t irt_us = (irt * US_PER_SEC);
|
|
int32_t factor = get_rand_us_factor();
|
|
|
|
if (greater_irt && (factor < 0)) {
|
|
factor = -factor;
|
|
}
|
|
irt_us += (factor * irt_us) / US_PER_SEC;
|
|
return irt_us;
|
|
}
|
|
|
|
static inline uint32_t _sub_rt_us(uint32_t rt_prev_us, uint16_t mrt)
|
|
{
|
|
uint32_t sub_rt_us = (2 * rt_prev_us) +
|
|
((get_rand_us_factor() * rt_prev_us) / US_PER_SEC);
|
|
|
|
if (sub_rt_us > (mrt * US_PER_SEC)) {
|
|
uint32_t mrt_us = mrt * US_PER_SEC;
|
|
|
|
sub_rt_us = mrt_us + ((get_rand_us_factor() * mrt_us) / US_PER_SEC);
|
|
}
|
|
return sub_rt_us;
|
|
}
|
|
|
|
static inline size_t _opt_len(dhcpv6_opt_t *opt)
|
|
{
|
|
return sizeof(dhcpv6_opt_t) + byteorder_ntohs(opt->len);
|
|
}
|
|
|
|
static inline dhcpv6_opt_t *_opt_next(dhcpv6_opt_t *opt)
|
|
{
|
|
return (dhcpv6_opt_t *)(((uint8_t *)opt) + _opt_len(opt));
|
|
}
|
|
|
|
static bool _check_status_opt(dhcpv6_opt_status_t *status)
|
|
{
|
|
/* DHCPV6_STATUS_SUCCESS is 0, so we don't need to fix byte order */
|
|
#if ENABLE_DEBUG
|
|
if ((status != NULL) && (status->code.u16 != DHCPV6_STATUS_SUCCESS)) {
|
|
size_t msg_len = byteorder_ntohs(status->len);
|
|
char msg[msg_len - 1];
|
|
|
|
strncpy(msg, status->msg, msg_len - 2);
|
|
DEBUG("DHCPv6 client: server returned error (%u) \"%s\"\n",
|
|
byteorder_ntohs(status->code), msg);
|
|
}
|
|
#endif
|
|
return (status == NULL) || (status->code.u16 == DHCPV6_STATUS_SUCCESS);
|
|
}
|
|
|
|
static bool _check_cid_opt(dhcpv6_opt_duid_t *cid)
|
|
{
|
|
#if ENABLE_DEBUG
|
|
if ((byteorder_ntohs(cid->len) != duid_len) ||
|
|
(memcmp(cid->duid, duid, duid_len) != 0)) {
|
|
DEBUG("DHCPv6 client: message is not for me\n");
|
|
}
|
|
#endif
|
|
return ((byteorder_ntohs(cid->len) == duid_len) &&
|
|
(memcmp(cid->duid, duid, duid_len) == 0));
|
|
}
|
|
|
|
static bool _check_sid_opt(dhcpv6_opt_duid_t *sid)
|
|
{
|
|
#if ENABLE_DEBUG
|
|
if ((byteorder_ntohs(sid->len) != server.duid_len) ||
|
|
(memcmp(sid->duid, server.duid.u8, server.duid_len) != 0)) {
|
|
DEBUG("DHCPv6 client: message is not from my server\n");
|
|
}
|
|
#endif
|
|
return ((byteorder_ntohs(sid->len) == server.duid_len) &&
|
|
(memcmp(sid->duid, server.duid.u8, server.duid_len) == 0));
|
|
}
|
|
|
|
/* discard stale messages in the receive buffer */
|
|
static void _flush_stale_replies(sock_udp_t *sock)
|
|
{
|
|
int res;
|
|
while ((res = sock_udp_recv(sock, recv_buf, sizeof(recv_buf), 0, NULL)) >= 0) {
|
|
DEBUG("DHCPv6 client: discarding %d stale bytes\n", res);
|
|
}
|
|
}
|
|
|
|
static int _preparse_advertise(uint8_t *adv, size_t len, uint8_t **buf)
|
|
{
|
|
dhcpv6_opt_duid_t *cid = NULL, *sid = NULL;
|
|
dhcpv6_opt_pref_t *pref = NULL;
|
|
dhcpv6_opt_status_t *status = NULL;
|
|
dhcpv6_opt_ia_pd_t *ia_pd = NULL;
|
|
size_t orig_len = len;
|
|
uint8_t pref_val = 0;
|
|
|
|
DEBUG("DHCPv6 client: received ADVERTISE\n");
|
|
if ((len < sizeof(dhcpv6_msg_t)) || !_is_tid((dhcpv6_msg_t *)adv)) {
|
|
DEBUG("DHCPv6 client: packet too small or transaction ID wrong\n");
|
|
return -1;
|
|
}
|
|
len -= sizeof(dhcpv6_msg_t);
|
|
for (dhcpv6_opt_t *opt = (dhcpv6_opt_t *)(&adv[sizeof(dhcpv6_msg_t)]);
|
|
len > 0; len -= _opt_len(opt), opt = _opt_next(opt)) {
|
|
if (len > orig_len) {
|
|
DEBUG("DHCPv6 client: ADVERTISE options overflow packet boundaries\n");
|
|
return -1;
|
|
}
|
|
switch (byteorder_ntohs(opt->type)) {
|
|
case DHCPV6_OPT_CID:
|
|
cid = (dhcpv6_opt_duid_t *)opt;
|
|
break;
|
|
case DHCPV6_OPT_SID:
|
|
sid = (dhcpv6_opt_duid_t *)opt;
|
|
break;
|
|
case DHCPV6_OPT_STATUS:
|
|
status = (dhcpv6_opt_status_t *)opt;
|
|
break;
|
|
case DHCPV6_OPT_IA_PD:
|
|
ia_pd = (dhcpv6_opt_ia_pd_t *)opt;
|
|
break;
|
|
case DHCPV6_OPT_PREF:
|
|
pref = (dhcpv6_opt_pref_t *)opt;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if ((cid == NULL) || (sid == NULL) || (ia_pd == NULL)) {
|
|
DEBUG("DHCPv6 client: ADVERTISE does not contain either server ID, "
|
|
"client ID or IA_PD option\n");
|
|
return -1;
|
|
}
|
|
if (!_check_status_opt(status) || !_check_cid_opt(cid)) {
|
|
return -1;
|
|
}
|
|
if (pref != NULL) {
|
|
pref_val = pref->value;
|
|
}
|
|
if ((server.duid_len == 0) || (pref_val > server.pref)) {
|
|
memcpy(best_adv, recv_buf, orig_len);
|
|
if (buf != NULL) {
|
|
*buf = best_adv;
|
|
}
|
|
server.duid_len = byteorder_ntohs(sid->len);
|
|
memcpy(server.duid.u8, sid->duid, server.duid_len);
|
|
server.pref = pref_val;
|
|
}
|
|
return pref_val;
|
|
}
|
|
|
|
static void _schedule_t2(void)
|
|
{
|
|
if (t2 < UINT32_MAX) {
|
|
uint64_t t2_usec = t2 * US_PER_SEC;
|
|
|
|
rebind_time = (xtimer_now_usec64() / US_PER_SEC) + t2;
|
|
xtimer_remove(&rebind_timer);
|
|
rebind_timer.callback = _post_rebind;
|
|
DEBUG("DHCPv6 client: scheduling REBIND in %lu sec\n",
|
|
(unsigned long)t2);
|
|
xtimer_set64(&rebind_timer, t2_usec);
|
|
}
|
|
}
|
|
|
|
static void _schedule_t1_t2(void)
|
|
{
|
|
if (server.t1 < UINT32_MAX) {
|
|
uint64_t t1_usec = server.t1 * US_PER_SEC;
|
|
|
|
xtimer_remove(&timer);
|
|
timer.callback = _post_renew;
|
|
DEBUG("DHCPv6 client: scheduling RENEW in %lu sec\n",
|
|
(unsigned long)server.t1);
|
|
xtimer_set64(&timer, t1_usec);
|
|
}
|
|
_schedule_t2();
|
|
}
|
|
|
|
static void _parse_advertise(uint8_t *adv, size_t len)
|
|
{
|
|
dhcpv6_opt_smr_t *smr = NULL;
|
|
|
|
/* might not have been executed when not received in first retransmission
|
|
* window => redo even if already done */
|
|
if (_preparse_advertise(adv, len, NULL) < 0) {
|
|
uint32_t delay = _irt_us(DHCPV6_SOL_TIMEOUT, true);
|
|
/* SOLICIT new server */
|
|
timer.callback = _post_solicit_servers;
|
|
xtimer_set(&timer, delay);
|
|
return;
|
|
}
|
|
DEBUG("DHCPv6 client: scheduling REQUEST\n");
|
|
event_post(event_queue, &request);
|
|
for (dhcpv6_opt_t *opt = (dhcpv6_opt_t *)(&adv[sizeof(dhcpv6_msg_t)]);
|
|
len > 0; len -= _opt_len(opt), opt = _opt_next(opt)) {
|
|
switch (byteorder_ntohs(opt->type)) {
|
|
case DHCPV6_OPT_IA_PD:
|
|
for (unsigned i = 0; i < DHCPV6_CLIENT_PFX_LEASE_MAX; i++) {
|
|
dhcpv6_opt_ia_pd_t *ia_pd = (dhcpv6_opt_ia_pd_t *)opt;
|
|
unsigned pd_t1, pd_t2;
|
|
uint32_t ia_id = byteorder_ntohl(ia_pd->ia_id);
|
|
size_t ia_pd_len = byteorder_ntohs(ia_pd->len) -
|
|
(sizeof(dhcpv6_opt_ia_pd_t) - sizeof(dhcpv6_opt_t));
|
|
size_t ia_pd_orig_len = ia_pd_len;
|
|
|
|
if (pfx_leases[i].parent.ia_id.id != ia_id) {
|
|
continue;
|
|
}
|
|
/* check for status */
|
|
for (dhcpv6_opt_t *ia_pd_opt = (dhcpv6_opt_t *)(ia_pd + 1);
|
|
ia_pd_len > 0;
|
|
ia_pd_len -= _opt_len(ia_pd_opt),
|
|
ia_pd_opt = _opt_next(ia_pd_opt)) {
|
|
if (ia_pd_len > ia_pd_orig_len) {
|
|
DEBUG("DHCPv6 client: IA_PD options overflow option "
|
|
"boundaries\n");
|
|
return;
|
|
}
|
|
switch (byteorder_ntohs(ia_pd_opt->type)) {
|
|
case DHCPV6_OPT_STATUS: {
|
|
if (!_check_status_opt((dhcpv6_opt_status_t *)ia_pd_opt)) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
pd_t1 = byteorder_ntohl(ia_pd->t1);
|
|
pd_t2 = byteorder_ntohl(ia_pd->t2);
|
|
if ((pd_t1 != 0) && (pd_t2 != 0) &&
|
|
(server.t1 > pd_t1) && (t2 > pd_t2)) {
|
|
server.t1 = pd_t1;
|
|
t2 = pd_t2;
|
|
_schedule_t2();
|
|
}
|
|
}
|
|
break;
|
|
case DHCPV6_OPT_SMR:
|
|
smr = (dhcpv6_opt_smr_t *)opt;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (smr != NULL) {
|
|
sol_max_rt = byteorder_ntohl(smr->value);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static bool _parse_reply(uint8_t *rep, size_t len)
|
|
{
|
|
dhcpv6_opt_duid_t *cid = NULL, *sid = NULL;
|
|
dhcpv6_opt_ia_pd_t *ia_pd = NULL;
|
|
dhcpv6_opt_status_t *status = NULL;
|
|
dhcpv6_opt_smr_t *smr = NULL;
|
|
size_t orig_len = len;
|
|
|
|
DEBUG("DHCPv6 client: received REPLY\n");
|
|
if ((len < sizeof(dhcpv6_msg_t)) || !_is_tid((dhcpv6_msg_t *)rep)) {
|
|
DEBUG("DHCPv6 client: packet too small or transaction ID wrong\n");
|
|
return false;
|
|
}
|
|
for (dhcpv6_opt_t *opt = (dhcpv6_opt_t *)(&rep[sizeof(dhcpv6_msg_t)]);
|
|
len > 0; len -= _opt_len(opt), opt = _opt_next(opt)) {
|
|
if (len > orig_len) {
|
|
DEBUG("DHCPv6 client: ADVERTISE options overflow packet boundaries\n");
|
|
return false;
|
|
}
|
|
switch (byteorder_ntohs(opt->type)) {
|
|
case DHCPV6_OPT_CID:
|
|
cid = (dhcpv6_opt_duid_t *)opt;
|
|
break;
|
|
case DHCPV6_OPT_SID:
|
|
sid = (dhcpv6_opt_duid_t *)opt;
|
|
break;
|
|
case DHCPV6_OPT_STATUS:
|
|
status = (dhcpv6_opt_status_t *)opt;
|
|
break;
|
|
case DHCPV6_OPT_IA_PD:
|
|
ia_pd = (dhcpv6_opt_ia_pd_t *)opt;
|
|
break;
|
|
case DHCPV6_OPT_SMR:
|
|
smr = (dhcpv6_opt_smr_t *)opt;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if ((cid == NULL) || (sid == NULL) || (ia_pd == NULL)) {
|
|
DEBUG("DHCPv6 client: ADVERTISE does not contain either server ID, "
|
|
"client ID or IA_PD option\n");
|
|
return false;
|
|
}
|
|
if (!_check_cid_opt(cid) || !_check_sid_opt(sid)) {
|
|
return false;
|
|
}
|
|
if (smr != NULL) {
|
|
sol_max_rt = byteorder_ntohl(smr->value);
|
|
}
|
|
if (!_check_status_opt(status)) {
|
|
return false;
|
|
}
|
|
len = orig_len - sizeof(dhcpv6_msg_t);
|
|
for (dhcpv6_opt_t *opt = (dhcpv6_opt_t *)(&rep[sizeof(dhcpv6_msg_t)]);
|
|
len > 0; len -= _opt_len(opt), opt = _opt_next(opt)) {
|
|
switch (byteorder_ntohs(opt->type)) {
|
|
case DHCPV6_OPT_IA_PD:
|
|
for (unsigned i = 0; i < DHCPV6_CLIENT_PFX_LEASE_MAX; i++) {
|
|
dhcpv6_opt_iapfx_t *iapfx = NULL;
|
|
pfx_lease_t *lease = &pfx_leases[i];
|
|
ia_pd = (dhcpv6_opt_ia_pd_t *)opt;
|
|
unsigned pd_t1, pd_t2;
|
|
uint32_t ia_id = byteorder_ntohl(ia_pd->ia_id);
|
|
size_t ia_pd_len = byteorder_ntohs(ia_pd->len) -
|
|
(sizeof(dhcpv6_opt_ia_pd_t) - sizeof(dhcpv6_opt_t));
|
|
size_t ia_pd_orig_len = ia_pd_len;
|
|
|
|
if (lease->parent.ia_id.id != ia_id) {
|
|
continue;
|
|
}
|
|
/* check for status */
|
|
for (dhcpv6_opt_t *ia_pd_opt = (dhcpv6_opt_t *)(ia_pd + 1);
|
|
ia_pd_len > 0;
|
|
ia_pd_len -= _opt_len(ia_pd_opt),
|
|
ia_pd_opt = _opt_next(ia_pd_opt)) {
|
|
if (ia_pd_len > ia_pd_orig_len) {
|
|
DEBUG("DHCPv6 client: IA_PD options overflow option "
|
|
"boundaries\n");
|
|
return false;
|
|
}
|
|
switch (byteorder_ntohs(ia_pd_opt->type)) {
|
|
case DHCPV6_OPT_STATUS: {
|
|
if (!_check_status_opt((dhcpv6_opt_status_t *)ia_pd_opt)) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
case DHCPV6_OPT_IAPFX: {
|
|
dhcpv6_opt_iapfx_t *this_iapfx = (dhcpv6_opt_iapfx_t *)ia_pd_opt;
|
|
if ((!lease->leased) ||
|
|
(iapfx == NULL) ||
|
|
((this_iapfx->pfx_len == lease->pfx_len) &&
|
|
ipv6_addr_match_prefix(&this_iapfx->pfx,
|
|
&lease->pfx) >= lease->pfx_len)) {
|
|
/* only take first prefix for now */
|
|
iapfx = this_iapfx;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
pd_t1 = byteorder_ntohl(ia_pd->t1);
|
|
pd_t2 = byteorder_ntohl(ia_pd->t2);
|
|
if ((pd_t1 != 0) && (pd_t2 != 0) &&
|
|
((server.t1 == 0) || (server.t1 >= pd_t1)) &&
|
|
((t2 == 0) || (t2 >= pd_t2))) {
|
|
server.t1 = pd_t1;
|
|
t2 = pd_t2;
|
|
_schedule_t1_t2();
|
|
}
|
|
if ((iapfx != NULL)) {
|
|
uint32_t valid = byteorder_ntohl(iapfx->valid);
|
|
uint32_t pref = byteorder_ntohl(iapfx->pref);
|
|
|
|
lease->pfx_len = iapfx->pfx_len;
|
|
lease->leased = 1U;
|
|
ipv6_addr_init_prefix(&lease->pfx,
|
|
&iapfx->pfx,
|
|
iapfx->pfx_len);
|
|
if (iapfx->pfx_len > 0) {
|
|
dhcpv6_client_conf_prefix(
|
|
lease->parent.ia_id.info.netif, &lease->pfx,
|
|
lease->pfx_len, valid, pref
|
|
);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void _solicit_servers(event_t *event)
|
|
{
|
|
dhcpv6_msg_t *msg = (dhcpv6_msg_t *)&send_buf[0];
|
|
dhcpv6_opt_elapsed_time_t *time;
|
|
uint8_t *buf = NULL;
|
|
uint32_t retrans_timeout = _irt_us(DHCPV6_SOL_TIMEOUT, true);
|
|
size_t msg_len = sizeof(dhcpv6_msg_t);
|
|
int res, best_res = 0;
|
|
bool first_rt = true;
|
|
uint16_t oro_opts[] = { DHCPV6_OPT_SMR };
|
|
|
|
(void)event;
|
|
_generate_tid();
|
|
msg->type = DHCPV6_SOLICIT;
|
|
_set_tid(msg->tid);
|
|
msg_len += _compose_cid_opt((dhcpv6_opt_duid_t *)&send_buf[msg_len]);
|
|
transaction_start = _now_cs();
|
|
time = (dhcpv6_opt_elapsed_time_t *)&send_buf[msg_len];
|
|
msg_len += _compose_elapsed_time_opt(time);
|
|
msg_len += _compose_oro_opt((dhcpv6_opt_oro_t *)&send_buf[msg_len], oro_opts,
|
|
ARRAY_SIZE(oro_opts));
|
|
msg_len += _add_ia_pd_from_config(&send_buf[msg_len]);
|
|
DEBUG("DHCPv6 client: send SOLICIT\n");
|
|
_flush_stale_replies(&sock);
|
|
res = sock_udp_send(&sock, send_buf, msg_len, &remote);
|
|
assert(res > 0); /* something went terribly wrong */
|
|
while (((res = sock_udp_recv(&sock, recv_buf, sizeof(recv_buf),
|
|
retrans_timeout, NULL)) <= 0) ||
|
|
(first_rt && (res > 0)) ||
|
|
((res > 0) && (recv_buf[0] != DHCPV6_ADVERTISE))) {
|
|
if (first_rt && (res > 0) && (recv_buf[0] == DHCPV6_ADVERTISE)) {
|
|
int parse_res;
|
|
|
|
DEBUG("DHCPv6 client: initial transmission, collect best advertise\n");
|
|
retrans_timeout -= (_get_elapsed_time() * US_PER_CS);
|
|
parse_res = _preparse_advertise(recv_buf, res, &buf);
|
|
if (buf != NULL) {
|
|
best_res = res;
|
|
}
|
|
if ((parse_res == UINT8_MAX) ||
|
|
(retrans_timeout > (DHCPV6_SOL_MAX_RT * US_PER_SEC))) {
|
|
/* retrans_timeout underflowed => don't retry to receive */
|
|
break;
|
|
}
|
|
}
|
|
else if (buf == NULL) {
|
|
DEBUG("DHCPv6 client: resend SOLICIT\n");
|
|
first_rt = false;
|
|
retrans_timeout = _sub_rt_us(retrans_timeout, DHCPV6_SOL_MAX_RT);
|
|
_compose_elapsed_time_opt(time);
|
|
res = sock_udp_send(&sock, send_buf, msg_len, &remote);
|
|
assert(res > 0); /* something went terribly wrong */
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
if (buf == NULL) {
|
|
buf = recv_buf;
|
|
best_res = res;
|
|
}
|
|
if (best_res > 0) {
|
|
_parse_advertise(buf, best_res);
|
|
}
|
|
}
|
|
|
|
static void _request_renew_rebind(uint8_t type)
|
|
{
|
|
dhcpv6_msg_t *msg = (dhcpv6_msg_t *)&send_buf[0];
|
|
dhcpv6_opt_elapsed_time_t *time;
|
|
uint32_t retrans_timeout;
|
|
size_t msg_len = sizeof(dhcpv6_msg_t);
|
|
int res;
|
|
uint16_t oro_opts[] = { DHCPV6_OPT_SMR };
|
|
uint8_t retrans = 0;
|
|
uint16_t irt;
|
|
uint16_t mrt;
|
|
uint16_t mrc = 0;
|
|
uint32_t mrd = 0;
|
|
|
|
switch (type) {
|
|
case DHCPV6_REQUEST:
|
|
irt = DHCPV6_REQ_TIMEOUT;
|
|
mrt = DHCPV6_REQ_MAX_RT;
|
|
mrc = DHCPV6_REQ_MAX_RC;
|
|
break;
|
|
case DHCPV6_RENEW:
|
|
irt = DHCPV6_REN_TIMEOUT;
|
|
mrt = DHCPV6_REN_MAX_RT;
|
|
mrd = rebind_time - t2;
|
|
break;
|
|
case DHCPV6_REBIND: {
|
|
irt = DHCPV6_REB_TIMEOUT;
|
|
mrt = DHCPV6_REB_MAX_RT;
|
|
/* calculate MRD from prefix leases */
|
|
for (unsigned i = 0; i < DHCPV6_CLIENT_PFX_LEASE_MAX; i++) {
|
|
const pfx_lease_t *lease = &pfx_leases[i];
|
|
uint32_t valid_until = dhcpv6_client_prefix_valid_until(
|
|
lease->parent.ia_id.info.netif,
|
|
&lease->pfx, lease->pfx_len
|
|
);
|
|
if (valid_until > mrd) {
|
|
mrd = valid_until;
|
|
}
|
|
}
|
|
if (mrd > 0) {
|
|
/* all leases already expired, don't try to rebind and
|
|
* solicit immediately */
|
|
_post_solicit_servers(NULL);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
retrans_timeout = _irt_us(irt, false);
|
|
_generate_tid();
|
|
msg->type = type;
|
|
_set_tid(msg->tid);
|
|
msg_len += _compose_cid_opt((dhcpv6_opt_duid_t *)&send_buf[msg_len]);
|
|
if (type != DHCPV6_REBIND) {
|
|
msg_len += _compose_sid_opt((dhcpv6_opt_duid_t *)&send_buf[msg_len]);
|
|
}
|
|
transaction_start = _now_cs();
|
|
time = (dhcpv6_opt_elapsed_time_t *)&send_buf[msg_len];
|
|
msg_len += _compose_elapsed_time_opt(time);
|
|
msg_len += _compose_oro_opt((dhcpv6_opt_oro_t *)&send_buf[msg_len], oro_opts,
|
|
ARRAY_SIZE(oro_opts));
|
|
msg_len += _add_ia_pd_from_config(&send_buf[msg_len]);
|
|
_flush_stale_replies(&sock);
|
|
while (sock_udp_send(&sock, send_buf, msg_len, &remote) <= 0) {}
|
|
while (((res = sock_udp_recv(&sock, recv_buf, sizeof(recv_buf),
|
|
retrans_timeout, NULL)) <= 0) ||
|
|
((res > 0) && (recv_buf[0] != DHCPV6_REPLY))) {
|
|
if ((mrd > 0) && (_get_elapsed_time() > (mrd * CS_PER_SEC))) {
|
|
break;
|
|
}
|
|
retrans_timeout = _sub_rt_us(retrans_timeout, mrt);
|
|
if ((mrc > 0) && (++retrans) >= mrc) {
|
|
break;
|
|
}
|
|
_compose_elapsed_time_opt(time);
|
|
DEBUG("DHCPv6 client: resend %s\n",
|
|
(type == DHCPV6_REQUEST) ? "REQUEST" :
|
|
(type == DHCPV6_RENEW) ? "RENEW": "REBIND");
|
|
sock_udp_send(&sock, send_buf, msg_len, &remote);
|
|
}
|
|
if ((res > 0) && (recv_buf[0] == DHCPV6_REPLY)) {
|
|
if (!_parse_reply(recv_buf, res)) {
|
|
/* try again */
|
|
event_post(event_queue, &request);
|
|
}
|
|
}
|
|
else if (type == DHCPV6_REBIND) {
|
|
_post_solicit_servers(NULL);
|
|
}
|
|
}
|
|
|
|
static void _request(event_t *event)
|
|
{
|
|
(void)event;
|
|
DEBUG("DHCPv6 client: send REQUEST\n");
|
|
_request_renew_rebind(DHCPV6_REQUEST);
|
|
}
|
|
|
|
static void _renew(event_t *event)
|
|
{
|
|
(void)event;
|
|
DEBUG("DHCPv6 client: send RENEW\n");
|
|
_request_renew_rebind(DHCPV6_RENEW);
|
|
}
|
|
|
|
static void _rebind(event_t *event)
|
|
{
|
|
(void)event;
|
|
DEBUG("DHCPv6 client: send REBIND\n");
|
|
_request_renew_rebind(DHCPV6_REBIND);
|
|
}
|
|
|
|
/** @} */
|