diff --git a/Makefile.dep b/Makefile.dep index dde5823a2e..6ec0f59b9a 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -32,6 +32,16 @@ ifneq (,$(filter csma_sender,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter dhcpv6_%,$(USEMODULE))) + USEMODULE += dhcpv6 +endif + +ifneq (,$(filter dhcpv6_client,$(USEMODULE))) + USEMODULE += event + USEMODULE += random + USEMODULE += xtimer +endif + ifneq (,$(filter gnrc_mac,$(USEMODULE))) USEMODULE += gnrc_priority_pktqueue USEMODULE += csma_sender diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index e7f747a3ef..2b6b193665 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -1,4 +1,5 @@ PSEUDOMODULES += at_urc +PSEUDOMODULES += auto_init_dhcpv6_client PSEUDOMODULES += auto_init_gnrc_rpl PSEUDOMODULES += can_mbox PSEUDOMODULES += can_pm @@ -10,6 +11,7 @@ PSEUDOMODULES += core_% PSEUDOMODULES += cortexm_fpu PSEUDOMODULES += cpu_check_address PSEUDOMODULES += devfs_% +PSEUDOMODULES += dhcpv6_% PSEUDOMODULES += ecc_% PSEUDOMODULES += emb6_router PSEUDOMODULES += event_% diff --git a/sys/Makefile b/sys/Makefile index 91a09e7b1f..ade293b93a 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -13,6 +13,9 @@ endif ifneq (,$(filter posix_semaphore,$(USEMODULE))) DIRS += posix/semaphore endif +ifneq (,$(filter dhcpv6,$(USEMODULE))) + DIRS += net/application_layer/dhcpv6 +endif ifneq (,$(filter posix_sockets,$(USEMODULE))) DIRS += posix/sockets endif diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index b72b856875..0f66dad7e3 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -620,4 +620,10 @@ void auto_init(void) test_utils_interactive_sync(); #endif #endif /* MODULE_TEST_UTILS_INTERACTIVE_SYNC */ + +#ifdef MODULE_AUTO_INIT_DHCPV6_CLIENT + DEBUG("auto_init DHCPv6 client"); + extern void dhcpv6_client_auto_init(void); + dhcpv6_client_auto_init(); +#endif /* MODULE_AUTO_INIT_DHCPV6_CLIENT */ } diff --git a/sys/include/net/dhcpv6.h b/sys/include/net/dhcpv6.h index cff402c9c9..0d3e9ffed9 100644 --- a/sys/include/net/dhcpv6.h +++ b/sys/include/net/dhcpv6.h @@ -32,7 +32,9 @@ extern "C" { * @{ */ #define DHCPV6_CLIENT_PORT (546U) /**< client port */ +#ifndef DHCPV6_SERVER_PORT /* only reconfigure for testing!!1! */ #define DHCPV6_SERVER_PORT (547U) /**< server and relay agent port */ +#endif /** @} */ /** diff --git a/sys/include/net/dhcpv6/client.h b/sys/include/net/dhcpv6/client.h new file mode 100644 index 0000000000..41a7686aa8 --- /dev/null +++ b/sys/include/net/dhcpv6/client.h @@ -0,0 +1,168 @@ +/* + * 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. + */ + +/** + * @defgroup net_dhcpv6_client DHCPv6 client + * @ingroup net_dhcpv6 + * @brief DHCPv6 client implementation + * @{ + * + * @file + * @brief DHCPv6 client definitions + * + * @author Martine Lenders + */ +#ifndef NET_DHCPV6_CLIENT_H +#define NET_DHCPV6_CLIENT_H + +#include "byteorder.h" +#include "event.h" +#include "net/ipv6/addr.h" +#include "thread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Auto-initialization parameters + */ +#ifndef DHCPV6_CLIENT_STACK_SIZE +#define DHCPV6_CLIENT_STACK_SIZE (THREAD_STACKSIZE_DEFAULT) /**< stack size */ +#endif + +#ifndef DHCPV6_CLIENT_PRIORITY +#define DHCPV6_CLIENT_PRIORITY (THREAD_PRIORITY_MAIN - 2) /**< priority */ +#endif +/** @} */ + +/** + * @brief Static length of the DUID + */ +#define DHCPV6_CLIENT_DUID_LEN (sizeof(dhcpv6_duid_l2_t) + 8U) +#define DHCPV6_CLIENT_BUFLEN (256) /**< length for send and receive buffer */ +#ifndef DHCPV6_CLIENT_SERVER_MAX +#define DHCPV6_CLIENT_SERVER_MAX (1U) /**< maximum number of servers to store */ +#endif +#ifndef DHCPV6_CLIENT_PFX_LEASE_MAX +#define DHCPV6_CLIENT_PFX_LEASE_MAX (1U) /**< maximum number of prefix leases to store */ +#endif + +/** + * @name DHCPv6 unique identifier (DUID) definitions + * @see [RFC 8415, section 11](https://tools.ietf.org/html/rfc8415#section-11) + * @{ + */ +/** + * @brief DUID based on link-layer address plus time + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_DUID_TYPE_L2 */ + network_uint16_t l2type; /**< [hardware type](@ref net_arp_hwtype)) */ + /* link-layer address follows this header */ +} dhcpv6_duid_l2_t; + +#if defined(MODULE_AUTO_INIT_DHCPV6_CLIENT) || defined(DOXYGEN) +/** + * @brief Auto-initializes the client in its own thread + * + * @note Only available with (and called by) the `dhcpv6_client_auto_init` + * module. + */ +void dhcpv6_client_auto_init(void); +#endif /* MODULE_DHCPV6_CLIENT_AUTO_INIT */ + +/** + * @brief Initializes the client + * + * @pre `event_queue->waiter != NULL` + * + * @param[in] event_queue Event queue to use with the client. Needs to be + * initialized in the handler thread. + * @param[in] netif The network interface the client should listen on. + * SOCK_ADDR_ANY_NETIF for any interface + */ +void dhcpv6_client_init(event_queue_t *event_queue, uint16_t netif); + +/** + * @brief Let the server start listening + * + * This needs to be called *after* all desired [configuration functions] + * (@ref net_dhcpv6_client_conf) where called. + */ +void dhcpv6_client_start(void); + +/** + * @name Configuration functions + * @anchor net_dhcpv6_client_conf + * @{ + */ +/** + * @brief Configures the client to request prefix delegation for a network + * interface from a server + * + * @pre `pfx_len <= 128` + * + * @param[in] netif The interface to request the prefix delegation for. + * @param[in] pfx_len The desired length of the prefix (note that the server + * might not consider this request). Must be <= 128 + */ +void dhcpv6_client_req_ia_pd(unsigned netif, unsigned pfx_len); +/** @} */ + +/** + * @name Stack-specific functions + * + * These functions need to be provided by the network-stack implementation. + * @{ + */ +/** + * @brief Get the link-layer address DUID for the client + * + * @param[in] netif The network interface the client is bound to. May be + * SOCK_ADDR_ANY_NETIF for any interface. + * @param[out] duid The resulting DUID. + * + * @return length of the @p duid on success. + * @return 0, on error. + */ +unsigned dhcpv6_client_get_duid_l2(unsigned netif, dhcpv6_duid_l2_t *duid); + +/** + * @brief Configures a prefix delegation lease that is provided by the server. + * + * @param[in] netif Network interface the prefix delegation was for. + * @param[in] pfx Prefix for the prefix delegation. + * @param[in] pfx_len Length of @p pfx in bits. + * @param[in] valid Valid lifetime of the prefix delegation. + * @param[in] pref Preferred lifetime of the prefix delegation. + */ +void dhcpv6_client_conf_prefix(unsigned netif, const ipv6_addr_t *pfx, + unsigned pfx_len, uint32_t valid, + uint32_t pref); + +/** + * @brief Determines how long the prefix delegation lease is still valid. + * + * @param[in] netif Network interface the prefix delegation was for. + * @param[in] pfx Prefix of the prefix delegation + * @param[in] pfx_len Length of @p pfx in bits. + * + * @return Remaining valid lifetime of the prefix delegation lease in seconds. + */ +uint32_t dhcpv6_client_prefix_valid_until(unsigned netif, + const ipv6_addr_t *pfx, + unsigned pfx_len); +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* NET_DHCPV6_CLIENT_H */ +/** @} */ diff --git a/sys/net/application_layer/dhcpv6/Makefile b/sys/net/application_layer/dhcpv6/Makefile new file mode 100644 index 0000000000..cd1af2456e --- /dev/null +++ b/sys/net/application_layer/dhcpv6/Makefile @@ -0,0 +1,3 @@ +SUBMODULES := 1 + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/application_layer/dhcpv6/_dhcpv6.h b/sys/net/application_layer/dhcpv6/_dhcpv6.h new file mode 100644 index 0000000000..a0b2fa0ddb --- /dev/null +++ b/sys/net/application_layer/dhcpv6/_dhcpv6.h @@ -0,0 +1,222 @@ +/* + * 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. + */ + +/** + * @ingroup net_dhcpv6 + * @{ + * + * @file + * @brief Internal DHCPv6 definitions + * @note This header is based on [RFC 8415](https://tools.ietf.org/html/rfc8415) + * + * @author Martine Lenders + */ +#ifndef PRIV_DHCPV6_H +#define PRIV_DHCPV6_H + +#include + +#include "byteorder.h" +#include "net/ipv6/addr.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @name DHCPv6 multicast addresses + * @see [RFC 8415, section 7.1] + * (https://tools.ietf.org/html/rfc8415#section-7.1) + * @{ + */ +/** + * @brief Multicast address used by clients to communicate with neighboring + * relay agents and servers + * + * @note Corresponds with `All_DHCP_Relay_Agents_and_Servers` in the draft. + * + */ +#define DHCPV6_ALL_RELAY_AGENTS_AND_SERVERS { 0xff, 0x02, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x01, 0x00, 0x02 \ + } +/** @} */ + +/** + * @name DHCPv6 transmission and retransmission parameters + * @see [RFC 8415, section 7.6] + * (https://tools.ietf.org/html/rfc8415#section-7.6) + * @{ + */ +#define DHCPV6_SOL_MAX_DELAY (1U) /**< SOL_MAX_DELAY (in sec) */ +#define DHCPV6_SOL_TIMEOUT (1U) /**< SOL_TIMEOUT (in sec) */ +#define DHCPV6_SOL_MAX_RT (3600U) /**< SOL_MAX_RT (in sec) */ + +#define DHCPV6_REQ_TIMEOUT (1U) /**< REQ_TIMEOUT (in sec) */ +#define DHCPV6_REQ_MAX_RT (30U) /**< REQ_MAX_RT (in sec) */ +#define DHCPV6_REQ_MAX_RC (10U) /**< REQ_MAX_RC */ + +#define DHCPV6_REN_TIMEOUT (10U) /**< REN_TIMEOUT (in sec) */ +#define DHCPV6_REN_MAX_RT (600U) /**< REN_MAX_RT (in sec) */ + +#define DHCPV6_REB_TIMEOUT (10U) /**< REB_TIMEOUT (in sec) */ +#define DHCPV6_REB_MAX_RT (600U) /**< REB_MAX_RT (in sec) */ +/** @} */ + +#define DHCPV6_DUID_MAX_LEN (128U) /**< maximum length of DUID */ + +/** + * @name DHCPv6 message formats + * @{ + */ +/** + * @brief Client/Server message header + * @see [RFC 8415, section 8] + * (https://tools.ietf.org/html/rfc8415#section-8) + */ +typedef struct __attribute__((packed)) { + uint8_t type; /**< message type (see [DHCPv6 messeg types ](@ref net_dhcp6_msg_types)) */ + uint8_t tid[3]; /**< transaction ID */ +} dhcpv6_msg_t; +/** @} */ + +/** + * @brief Generic storage DUID + */ +typedef union { + uint8_t u8[DHCPV6_DUID_MAX_LEN]; /**< array representation */ + dhcpv6_duid_l2_t duid_l2; /**< DUID-L2 type */ +} dhcpv6_duid_t; +/** @} */ + +/** + * @name DHCPv6 options + * @{ + */ +/** + * @brief General DHCPv6 option format + * @see [RFC 8415, section 21.1] + * (https://tools.ietf.org/html/rfc8415#section-21.1) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< [option code](@ref net_dhcp6_opt_codes) */ + network_uint16_t len; /**< length of dhcpv6_opt_t::data in byte */ + uint8_t data[]; /**< option data */ +} dhcpv6_opt_t; + +/** + * @brief DHCPv6 client or server identifier option format + * @see [RFC 8415, section 21.2] + * (https://tools.ietf.org/html/rfc8415#section-21.2) + * @see [RFC 8415, section 21.3] + * (https://tools.ietf.org/html/rfc8415#section-21.3) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_OPT_CID or DHCPV6_OPT_SID */ + network_uint16_t len; /**< length of dhcpv6_opt_t::duid in byte */ + uint8_t duid[]; /**< the DUID of the client or server */ +} dhcpv6_opt_duid_t; + +/** + * @brief DHCPv6 option request option format + * @see [RFC 8415, section 21.7] + * (https://tools.ietf.org/html/rfc8415#section-21.7) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_OPT_ORO */ + network_uint16_t len; /**< 2 * number of dhcpv6_opt_oro_t::opt_codes */ + network_uint16_t opt_codes[]; /**< option-code for an option requested by the client */ +} dhcpv6_opt_oro_t; + +/** + * @brief DHCPv6 preference option format + * @see [RFC 8415, section 21.8] + * (https://tools.ietf.org/html/rfc8415#section-21.8) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_OPT_PREF */ + network_uint16_t len; /**< always 1 */ + uint8_t value; /**< preference value for the server */ +} dhcpv6_opt_pref_t; +/** @} */ + +/** + * @brief DHCPv6 elapsed time option format + * @see [RFC 8415, section 21.9] + * (https://tools.ietf.org/html/rfc8415#section-21.9) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_OPT_ELAPSED_TIME */ + network_uint16_t len; /**< always 2 */ + /** + * @brief amount of time since client began current DHCPv6 transaction + * (in cs) */ + network_uint16_t elapsed_time; +} dhcpv6_opt_elapsed_time_t; + +/** + * @brief DHCPv6 status code option format + * @see [RFC 8415, section 21.13] + * (https://tools.ietf.org/html/rfc8415#section-21.13) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_OPT_STATUS */ + network_uint16_t len; /**< 2 + length of dhcpv6_opt_status_t::msg in byte */ + network_uint16_t code; /**< [status code](@ref net_dhcp6_status_codes) */ + char msg[]; /**< UTF-8 encoded text string (not 0-terminated!) */ +} dhcpv6_opt_status_t; + +/** + * @brief DHCPv6 identity association for prefix delegation option (IA_PD) + * format + * @see [RFC 8415, section 21.21] + * (https://tools.ietf.org/html/rfc8415#section-21.21) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_OPT_IA_PD */ + network_uint16_t len; /**< 12 + length of dhcpv6_opt_ia_pd_t::opts in byte */ + network_uint32_t ia_id; /**< Unique ID for this IA_PD */ + network_uint32_t t1; /**< DHCPv6 T1 time (in sec) */ + network_uint32_t t2; /**< DHCPv6 T2 time (in sec) */ + uint8_t opts[]; /**< IA_PD options */ +} dhcpv6_opt_ia_pd_t; + +/** + * @brief DHCPv6 IA prefix option format + * @see [RFC 8415, section 21.22] + * (https://tools.ietf.org/html/rfc8415#section-21.22) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_OPT_IAPFX */ + network_uint16_t len; /**< 25 + length of dhcpv6_opt_iapfx_t::opts in byte */ + network_uint32_t pref; /**< preferred lifetime (in sec) */ + network_uint32_t valid; /**< valid lifetime (in sec) */ + uint8_t pfx_len; /**< length of dhcpv6_opt_iapfx_t::pfx in bits */ + ipv6_addr_t pfx; /**< the prefix */ + uint8_t opts[]; /**< IAprefix options */ +} dhcpv6_opt_iapfx_t; + +/** + * @brief DHCPv6 SOL_MAX_RT option format + * @see [RFC 8415, section 21.24] + * (https://tools.ietf.org/html/rfc8415#section-21.24) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_OPT_SMR */ + network_uint16_t len; /**< always 4 */ + network_uint32_t value; /**< overriding value for SOL_MAX_RT (in sec) */ +} dhcpv6_opt_smr_t; + +#ifdef __cplusplus +} +#endif + +#endif /* PRIV_DHCPV6_H */ +/** @} */ diff --git a/sys/net/application_layer/dhcpv6/client.c b/sys/net/application_layer/dhcpv6/client.c new file mode 100644 index 0000000000..7546395baa --- /dev/null +++ b/sys/net/application_layer/dhcpv6/client.c @@ -0,0 +1,847 @@ +/* + * 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 + */ + +#include + +#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; + } + } +} + +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)); +} + +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; + } + 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 false; + } + 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) { + 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); + 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; + 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); + 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 + ); + } + return true; + } + } + 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"); + 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]); + 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); +} + +/** @} */