From 1b36cdf79e4ec23f8ec9e06258ead3f70a2d67d3 Mon Sep 17 00:00:00 2001 From: Martine Lenders <m.lenders@fu-berlin.de> Date: Tue, 2 May 2017 17:05:10 +0200 Subject: [PATCH] nib: implement public NIB functions up to link-local AR --- Makefile.dep | 1 + sys/include/net/gnrc/ipv6/nib.h | 109 +++ .../gnrc/network_layer/ipv6/nib/_nib-6ln.c | 151 +++++ .../gnrc/network_layer/ipv6/nib/_nib-6ln.h | 106 +++ .../gnrc/network_layer/ipv6/nib/_nib-6lr.c | 125 ++++ .../gnrc/network_layer/ipv6/nib/_nib-6lr.h | 147 ++++ .../gnrc/network_layer/ipv6/nib/_nib-arsm.c | 475 +++++++++++++ .../gnrc/network_layer/ipv6/nib/_nib-arsm.h | 190 ++++++ .../network_layer/ipv6/nib/_nib-internal.c | 31 +- .../network_layer/ipv6/nib/_nib-internal.h | 48 +- sys/net/gnrc/network_layer/ipv6/nib/nib.c | 641 ++++++++++++++++++ .../tests-gnrc_ipv6_nib-nc.c | 9 +- 12 files changed, 2015 insertions(+), 18 deletions(-) create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.c create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.h create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.c create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.h create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.h create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/nib.c diff --git a/Makefile.dep b/Makefile.dep index 473880c82e..c44dd293fd 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -330,6 +330,7 @@ endif ifneq (,$(filter gnrc_ipv6_nib_6ln,$(USEMODULE))) USEMODULE += gnrc_ipv6_nib + USEMODULE += gnrc_sixlowpan_nd endif ifneq (,$(filter gnrc_ipv6_nib_router,$(USEMODULE))) diff --git a/sys/include/net/gnrc/ipv6/nib.h b/sys/include/net/gnrc/ipv6/nib.h index cd0631241f..0883a2cc58 100644 --- a/sys/include/net/gnrc/ipv6/nib.h +++ b/sys/include/net/gnrc/ipv6/nib.h @@ -12,6 +12,8 @@ * @brief Neighbor Information Base (NIB) for IPv6 * * @todo Add detailed description + * @todo Implement multihop DAD + * @todo Implement classic SLAAC * @{ * * @file @@ -27,6 +29,12 @@ #include "net/gnrc/ipv6/nib/nc.h" #include "net/gnrc/ipv6/nib/pl.h" +#include "net/icmpv6.h" +#include "net/ipv6/addr.h" +#include "net/ipv6/hdr.h" +#include "net/gnrc/ipv6/nib/nc.h" +#include "net/gnrc/pkt.h" + #ifdef __cplusplus extern "C" { #endif @@ -181,8 +189,109 @@ extern "C" { * context is a valid default router entry representing the router. */ #define GNRC_IPV6_NIB_RTR_TIMEOUT (0x4fcdU) + +/** + * @brief Recalculate reachability timeout time. + * + * This message type is for the event of recalculating the reachability timeout + * time. The expected message context is a valid interface. + * + * @note Only handled with @ref GNRC_IPV6_NIB_CONF_ARSM != 0 + */ +#define GNRC_IPV6_NIB_RECALC_REACH_TIME (0x4fceU) /** @} */ +/** + * @brief Initialize NIB + */ +void gnrc_ipv6_nib_init(void); + +/** + * @brief Adds an interface to be managed by the NIB. + * + * @pre `(KERNEL_PID_UNDEF < iface)` + * + * @param[in] iface The interface to be managed by the NIB + */ +void gnrc_ipv6_nib_init_iface(kernel_pid_t iface); + +/** + * @brief Gets link-layer address of next hop to a destination address + * + * @pre `(dst != NULL) && (nce != NULL)` + * + * @param[in] dst Destination address of a packet. + * @param[in] iface Restrict search to this interface. May be + * `KERNEL_PID_UNDEF` for any interface. + * @param[in] pkt The IPv6 packet in sending order for which the next hop + * is searched. Needed for queuing for with reactive + * routing or address resolution. May be `NULL`. + * Will be released properly on error. + * @param[out] nce The neighbor cache entry of the next hop to @p dst. + * + * @return 0, on success. + * @return -ENETUNREACH if there is no route to host. + * @return -EHOSTUNREACH if the next hop is not reachable or if @p dst was + * link-local, but @p iface was @ref KERNEL_PID_UNDEF (no neighbor + * cache entry will be created in this case and no neighbor + * solicitation sent). + */ +int gnrc_ipv6_nib_get_next_hop_l2addr(const ipv6_addr_t *dst, + kernel_pid_t iface, gnrc_pktsnip_t *pkt, + gnrc_ipv6_nib_nc_t *nce); + +/** + * @brief Handles a received ICMPv6 packet + * + * @pre `iface != KERNEL_PID_UNDEF` + * @pre `ipv6 != NULL` + * @pre `icmpv6 != NULL` + * @pre `icmpv6_len > sizeof(icmpv6_hdr_t)` + * + * @attention The ICMPv6 checksum is supposed to be checked externally! + * + * @note @p ipv6 is just used for the addresses and hop limit. The next + * header field will not be checked for correctness (but should be + * @ref PROTNUM_ICMPV6) + * + * @see [RFC 4861, section 6.1](https://tools.ietf.org/html/rfc4861#section-6.1) + * @see [RFC 4861, section 6.2.6](https://tools.ietf.org/html/rfc4861#section-6.2.6) + * @see [RFC 4861, section 6.3.4](https://tools.ietf.org/html/rfc4861#section-6.3.4) + * @see [RFC 4861, section 7.1](https://tools.ietf.org/html/rfc4861#section-7.1) + * @see [RFC 4861, section 7.2.3](https://tools.ietf.org/html/rfc4861#section-7.2.3) + * @see [RFC 4861, section 7.2.5](https://tools.ietf.org/html/rfc4861#section-7.2.5) + * @see [RFC 4861, section 8.1](https://tools.ietf.org/html/rfc4861#section-8.1) + * @see [RFC 4861, section 8.3](https://tools.ietf.org/html/rfc4861#section-8.3) + * @see [RFC 4862, section 5.4.3](https://tools.ietf.org/html/rfc4862#section-5.4.3) + * @see [RFC 4862, section 5.4.4](https://tools.ietf.org/html/rfc4862#section-5.4.4) + * @see [RFC 4862, section 5.5.3](https://tools.ietf.org/html/rfc4862#section-5.5.3) + * @see [RFC 6775, section 5.5.2](https://tools.ietf.org/html/rfc6775#section-5.5.2) + * @see [RFC 6775, section 5.4](https://tools.ietf.org/html/rfc6775#section-5.4) + * @see [RFC 6775, section 6.3](https://tools.ietf.org/html/rfc6775#section-6.3) + * @see [RFC 6775, section 6.5](https://tools.ietf.org/html/rfc6775#section-6.5) + * @see [RFC 6775, section 8.1.3](https://tools.ietf.org/html/rfc6775#section-8.1.3) + * @see [RFC 6775, section 8.2.1](https://tools.ietf.org/html/rfc6775#section-8.2.1) + * @see [RFC 6775, section 8.2.4](https://tools.ietf.org/html/rfc6775#section-8.2.4) + * @see [RFC 6775, section 8.2.5](https://tools.ietf.org/html/rfc6775#section-8.2.5) + * + * @param[in] iface The interface the packet came over. + * @param[in] ipv6 The IPv6 header of the received packet. + * @param[in] icmpv6 The ICMPv6 header and payload of the received + * packet. + * @param[in] icmpv6_len The number of bytes at @p icmpv6. + */ +void gnrc_ipv6_nib_handle_pkt(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const icmpv6_hdr_t *icmpv6, size_t icmpv6_len); + +/** + * @brief Handles a timer event + * + * @param[in] ctx Context of the timer event. + * @param[in] type Type of the timer event (see [timer event + * types](@ref net_gnrc_ipv6_nib_msg)) + */ +void gnrc_ipv6_nib_handle_timer_event(void *ctx, uint16_t type); + #ifdef __cplusplus } #endif diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.c new file mode 100644 index 0000000000..8f0908bdde --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2017 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 "net/gnrc/ipv6/nib.h" + +#include "_nib-6ln.h" +#include "_nib-6lr.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#if GNRC_IPV6_NIB_CONF_6LN +#if ENABLE_DEBUG +static char addr_str[IPV6_ADDR_MAX_STR_LEN]; +#endif + +static bool _is_iface_eui64(kernel_pid_t iface, const eui64_t *eui64) +{ + eui64_t iface_eui64; + + /* XXX: this *should* return successful so don't test it ;-) */ + gnrc_netapi_get(iface, NETOPT_ADDRESS_LONG, 0, + &iface_eui64, sizeof(iface_eui64)); + return (memcmp(&iface_eui64, eui64, sizeof(iface_eui64)) != 0); +} + +bool _resolve_addr_from_ipv6(const ipv6_addr_t *dst, kernel_pid_t iface, + gnrc_ipv6_nib_nc_t *nce) +{ + gnrc_ipv6_netif_t *netif = gnrc_ipv6_netif_get(iface); + bool res = (netif != NULL) && _is_6ln(netif) && + ipv6_addr_is_link_local(dst); + + if (res) { + memcpy(&nce->ipv6, dst, sizeof(nce->ipv6)); + memcpy(&nce->l2addr, &dst->u64[1], sizeof(dst->u64[1])); + nce->l2addr[0] ^= 0x02; + nce->info = 0; + nce->info |= (iface << GNRC_IPV6_NIB_NC_INFO_IFACE_POS) & + GNRC_IPV6_NIB_NC_INFO_IFACE_MASK; + nce->info |= GNRC_IPV6_NIB_NC_INFO_NUD_STATE_REACHABLE; + nce->info |= GNRC_IPV6_NIB_NC_INFO_AR_STATE_REGISTERED; + nce->l2addr_len = sizeof(dst->u64[1]); + } + return res; +} + +uint8_t _handle_aro(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const icmpv6_hdr_t *icmpv6, + const sixlowpan_nd_opt_ar_t *aro, const ndp_opt_t *sl2ao, + _nib_onl_entry_t *nce) +{ + gnrc_ipv6_netif_t *netif = gnrc_ipv6_netif_get(iface); + +#if !GNRC_IPV6_NIB_CONF_6LR + (void)sl2ao; +#endif + assert(netif != NULL); + if (_is_6ln(netif) && (aro->len == SIXLOWPAN_ND_OPT_AR_LEN)) { + DEBUG("nib: valid ARO received\n"); + DEBUG(" - length: %u\n", aro->len); + DEBUG(" - status: %u\n", aro->status); + DEBUG(" - registration lifetime: %u\n", byteorder_ntohs(aro->ltime)); + DEBUG(" - EUI-64: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + aro->eui64.uint8[0], aro->eui64.uint8[1], aro->eui64.uint8[2], + aro->eui64.uint8[3], aro->eui64.uint8[4], aro->eui64.uint8[5], + aro->eui64.uint8[6], aro->eui64.uint8[7]); + if (icmpv6->type == ICMPV6_NBR_ADV) { + if (!_is_iface_eui64(iface, &aro->eui64)) { + DEBUG("nib: ARO EUI-64 is not mine, ignoring ARO\n"); + return _ADDR_REG_STATUS_IGNORE; + } + switch (aro->status) { + case SIXLOWPAN_ND_STATUS_SUCCESS: { + uint16_t ltime = byteorder_ntohs(aro->ltime); + uint32_t next_ns; + /* if ltime 1min, reschedule NS in 30sec, otherwise 1min + * before timeout */ + next_ns = (ltime == 1U) ? (30 * MS_PER_SEC) : + (byteorder_ntohs(aro->ltime) - 1U) * + SEC_PER_MIN * MS_PER_SEC; + DEBUG("nib: Address registration successful. " + "Scheduling re-registration in %ums\n", + next_ns); + assert(nce != NULL); + _evtimer_add(nce, GNRC_IPV6_NIB_SND_UC_NS, &nce->nud_timeout, + next_ns); + break; + } + case SIXLOWPAN_ND_STATUS_DUP: + DEBUG("nib: Address registration reports duplicate. " + "Removing address %s%%%u\n", + ipv6_addr_to_str(addr_str, + &((ndp_nbr_adv_t *)icmpv6)->tgt, + sizeof(addr_str)), + iface); + gnrc_ipv6_netif_remove_addr(iface, + &((ndp_nbr_adv_t *)icmpv6)->tgt); + /* TODO: generate new address */ + break; + case SIXLOWPAN_ND_STATUS_NC_FULL: { + DEBUG("nib: Router's neighbor cache is full. " + "Searching new router for DAD\n"); + _nib_dr_entry_t *dr = _nib_drl_get(&ipv6->src, iface); + assert(dr != NULL); /* otherwise we wouldn't be here */ + _nib_drl_remove(dr); + if (_nib_drl_iter(NULL) == NULL) { /* no DRL left */ + _nib_iface_t *nib_iface = _nib_iface_get(iface); + nib_iface->rs_sent = 0; + /* TODO: search new router */ + } + else { + assert(dr->next_hop != NULL); + _snd_uc_ns(dr->next_hop, true); + } + } + break; + } + return aro->status; + } +#if GNRC_IPV6_NIB_CONF_6LR + else if (_is_6lr(netif) && (icmpv6->type == ICMPV6_NBR_SOL)) { + return _reg_addr_upstream(iface, ipv6, icmpv6, aro, sl2ao); + } +#endif + } +#if ENABLE_DEBUG + else if (aro->len != SIXLOWPAN_ND_OPT_AR_LEN) { + DEBUG("nib: ARO of unexpected length %u, ignoring ARO\n", aro->len); + } +#endif + return _ADDR_REG_STATUS_IGNORE; +} +#else /* GNRC_IPV6_NIB_CONF_6LN */ +typedef int dont_be_pedantic; +#endif /* GNRC_IPV6_NIB_CONF_6LN */ + + +/** @} */ diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.h b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.h new file mode 100644 index 0000000000..6967c3a8d9 --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 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_gnrc_ipv6_nib + * @{ + * + * @file + * @brief Definitions related to 6Lo node (6LN) functionality of the NIB + * @see @ref GNRC_IPV6_NIB_CONF_6LN + * + * @author Martine Lenders <m.lenders@fu-berlin.de> + */ +#ifndef PRIV_NIB_6LN_H +#define PRIV_NIB_6LN_H + +#include <stdint.h> + +#include "net/gnrc/ipv6/nib/conf.h" +#include "net/sixlowpan/nd.h" + +#include "_nib-arsm.h" +#include "_nib-internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if GNRC_IPV6_NIB_CONF_6LN || defined(DOXYGEN) +/** + * @brief Additional (local) status to ARO status values for tentative + * addresses + */ +#define _ADDR_REG_STATUS_TENTATIVE (3) + +/** + * @brief Additional (local) status to ARO status values for return values + * to signify that the address was ignored + */ +#define _ADDR_REG_STATUS_IGNORE (4) + +/** + * @brief Checks if interface represents a 6LN + * + * @todo Use corresponding function in `gnrc_netif2` instead. + * + * @param[in] netif A network interface. + * + * @return true, when the @p netif represents a 6LN. + * @return false, when the @p netif does not represent a 6LN. + */ +static inline bool _is_6ln(const gnrc_ipv6_netif_t *netif) +{ + return (netif->flags & GNRC_IPV6_NETIF_FLAGS_SIXLOWPAN); +} + +/** + * @brief Resolves address statically from destination address using reverse + * translation of the IID + * + * @param[in] dst A destination address. + * @param[in] iface The interface to @p dst. + * @param[out] nce Neighbor cache entry to resolve into + * + * @return true when @p nce was set, false when not. + */ +bool _resolve_addr_from_ipv6(const ipv6_addr_t *dst, kernel_pid_t iface, + gnrc_ipv6_nib_nc_t *nce); + +/** + * @brief Handles ARO + * + * @param[in] iface The interface the ARO-carrying message came over. + * @param[in] ipv6 The IPv6 header of the message carrying the ARO. + * @param[in] icmpv6 The message carrying the ARO. + * @param[in] aro ARO that carries the address registration information. + * @param[in] sl2ao SL2AO associated with the ARO. + * @param[in] nce Neighbor cache entry the ARO is supposed to change. + * + * @return registration status of the address (including + * @ref _ADDR_REG_STATUS_TENTATIVE and @ref _ADDR_REG_STATUS_IGNORE). + */ +uint8_t _handle_aro(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const icmpv6_hdr_t *icmpv6, + const sixlowpan_nd_opt_ar_t *aro, const ndp_opt_t *sl2ao, + _nib_onl_entry_t *nce); +#else /* GNRC_IPV6_NIB_CONF_6LN || defined(DOXYGEN) */ +#define _is_6ln(netif) (false) +#define _resolve_addr_from_ipv6(dst, iface, nce) (false) +/* _handle_aro() doesn't make sense without 6LR so don't even use it + * => throw error in case it is compiled in => don't define it here as NOP macro + */ +#endif /* GNRC_IPV6_NIB_CONF_6LN || defined(DOXYGEN) */ + + +#ifdef __cplusplus +} +#endif + +#endif /* PRIV_NIB_6LN_H */ +/** @} */ diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.c new file mode 100644 index 0000000000..8a7614f370 --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017 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 <mlenders@inf.fu-berlin.de> + */ + +#include "net/gnrc/ipv6/nib.h" +#include "net/gnrc/sixlowpan/nd.h" + +#include "_nib-6lr.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#if GNRC_IPV6_NIB_CONF_6LR +#if ENABLE_DEBUG +static char addr_str[IPV6_ADDR_MAX_STR_LEN]; +#endif + +static uint8_t _update_nce_ar_state(const sixlowpan_nd_opt_ar_t *aro, + _nib_onl_entry_t *nce) +{ + if (nce != NULL) { + memcpy(&nce->eui64, &aro->eui64, sizeof(aro->eui64)); + _evtimer_add(nce, GNRC_IPV6_NIB_ADDR_REG_TIMEOUT, + &nce->addr_reg_timeout, + byteorder_ntohs(aro->ltime) * SEC_PER_MIN * MS_PER_SEC); + _set_ar_state(nce, + GNRC_IPV6_NIB_NC_INFO_AR_STATE_REGISTERED); + DEBUG("nib: Successfully registered %s\n", + ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str))); + return SIXLOWPAN_ND_STATUS_SUCCESS; + } + else { + DEBUG("nib: Could not register %s, neighbor cache was full\n", + ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str))); + return SIXLOWPAN_ND_STATUS_NC_FULL; + } +} + +uint8_t _reg_addr_upstream(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const icmpv6_hdr_t *icmpv6, + const sixlowpan_nd_opt_ar_t *aro, + const ndp_opt_t *sl2ao) +{ + if (!ipv6_addr_is_unspecified(&ipv6->src) && (sl2ao != NULL)) { + _nib_onl_entry_t *nce = _nib_onl_get(&ipv6->src, iface); + + DEBUG("nib: Trying to register %s with EUI-64 " + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str)), + aro->eui64.uint8[0], aro->eui64.uint8[1], aro->eui64.uint8[2], + aro->eui64.uint8[3], aro->eui64.uint8[4], aro->eui64.uint8[5], + aro->eui64.uint8[6], aro->eui64.uint8[7]); + if ((nce == NULL) || !(nce->mode & _NC) || + (memcmp(&nce->eui64, &aro->eui64, sizeof(aro->eui64)) == 0)) { +#if GNRC_IPV6_NIB_CONF_MULTIHOP_DAD + /* TODO */ +#endif + if (byteorder_ntohs(aro->ltime) != 0) { + _handle_sl2ao(iface, ipv6, icmpv6, sl2ao); + _update_nce_ar_state(aro, nce); + } + else if (nce != NULL) { + _nib_nc_remove(nce); + return SIXLOWPAN_ND_STATUS_SUCCESS; + } + } + else { + DEBUG("nib: Could not register %s, duplicate entry with EUI-64 " + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str)), + nce->eui64.uint8[0], nce->eui64.uint8[1], nce->eui64.uint8[2], + nce->eui64.uint8[3], nce->eui64.uint8[4], nce->eui64.uint8[5], + nce->eui64.uint8[6], nce->eui64.uint8[7]); + return SIXLOWPAN_ND_STATUS_DUP; + } + } + return _ADDR_REG_STATUS_IGNORE; +} + +gnrc_pktsnip_t *_copy_and_handle_aro(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const ndp_nbr_sol_t *nbr_sol, + const sixlowpan_nd_opt_ar_t *aro, + const ndp_opt_t *sl2ao) +{ + gnrc_pktsnip_t *reply_aro = NULL; + + if (aro != NULL) { + uint8_t status = _handle_aro(iface, ipv6, (icmpv6_hdr_t *)nbr_sol, aro, + sl2ao, NULL); + + if ((status != _ADDR_REG_STATUS_TENTATIVE) && + (status != _ADDR_REG_STATUS_IGNORE)) { + reply_aro = gnrc_sixlowpan_nd_opt_ar_build(status, + byteorder_ntohs(aro->ltime), + (eui64_t *)&aro->eui64, + NULL); + if (reply_aro == NULL) { + DEBUG("nib: No space left in packet buffer. Not replying NS"); + } + } +#if GNRC_IPV6_NIB_CONF_MULTIHOP_DAD + else if (status != _ADDR_REG_STATUS_IGNORE) { + DEBUG("nib: Address was marked TENTATIVE => not replying NS, " + "waiting for DAC\n"); + } +#endif + } + return reply_aro; +} +#else /* GNRC_IPV6_NIB_CONF_6LR */ +typedef int dont_be_pedantic; +#endif /* GNRC_IPV6_NIB_CONF_6LR */ + +/** @} */ diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.h b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.h new file mode 100644 index 0000000000..0fc0049e58 --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017 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_gnrc_ipv6_nib + * @{ + * + * @file + * @brief Definitions related to 6Lo router (6LR) functionality of the NIB + * @see @ref GNRC_IPV6_NIB_CONF_6LR + * + * @author Martine Lenders <m.lenders@fu-berlin.de> + */ +#ifndef PRIV_NIB_6LR_H +#define PRIV_NIB_6LR_H + + +#include "net/gnrc/ipv6/nib/conf.h" +#include "net/ndp.h" +#include "net/sixlowpan/nd.h" + +#include "_nib-arsm.h" +#include "_nib-6ln.h" +#include "_nib-internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if GNRC_IPV6_NIB_CONF_6LR || defined(DOXYGEN) +/** + * @brief Checks if interface represents a 6LR + * + * @todo Use corresponding function in `gnrc_netif2` instead. + * + * @param[in] netif A network interface. + * + * @return true, when the @p netif represents a 6LR. + * @return false, when the @p netif does not represent a 6LR. + */ +static inline bool _is_6lr(const gnrc_ipv6_netif_t *netif) +{ + return _is_6ln(netif) && (netif->flags & GNRC_IPV6_NETIF_FLAGS_ROUTER); +} + +/** + * @brief Gets address registration state of a neighbor + * + * @param[in] entry Neighbor cache entry representing the neighbor. + * + * @return Address registration state of the @p entry. + */ +static inline uint16_t _get_ar_state(const _nib_onl_entry_t *entry) +{ + return (entry->info & GNRC_IPV6_NIB_NC_INFO_AR_STATE_MASK); +} + +/** + * @brief Sets address registration state of a neighbor + * + * @param[in] entry Neighbor cache entry representing the neighbor. + * @param[in] state Address registration state for the neighbor. + */ +static inline void _set_ar_state(_nib_onl_entry_t *entry, uint16_t state) +{ + entry->info &= ~GNRC_IPV6_NIB_NC_INFO_AR_STATE_MASK; + entry->info |= state; +} + +/** + * @brief Checks if the received message is a router solicitation and + * the interface represents a 6Lo router + * + * @see [RFC 6775](https://tools.ietf.org/html/rfc6775#section-6.3) + * + * @param[in] netif A network interface. + * @param[in] icmpv6 An ICMPv6 message. + */ +static inline bool _rtr_sol_on_6lr(const gnrc_ipv6_netif_t *netif, + const icmpv6_hdr_t *icmpv6) +{ + return _is_6lr(netif) && (icmpv6->type == ICMPV6_RTR_SOL); +} + +/** + * @brief Registers an address to the (upstream; in case of multihop DAD) + * router + * + * @param[in] iface The interface the ARO-carrying NS came over. + * @param[in] ipv6 The IPv6 header of the message carrying the ARO. + * @param[in] icmpv6 The neighbor solicitation carrying the ARO + * (handed over as @ref icmpv6_hdr_t, since it is just + * handed to the SL2AO handler function). + * @param[in] aro ARO that carries the address registration information. + * @param[in] sl2ao SL2AO associated with the ARO. + * + * @return registration status of the address (including + * @ref _ADDR_REG_STATUS_TENTATIVE and @ref _ADDR_REG_STATUS_IGNORE). + */ +uint8_t _reg_addr_upstream(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const icmpv6_hdr_t *icmpv6, + const sixlowpan_nd_opt_ar_t *aro, + const ndp_opt_t *sl2ao); + + +/** + * @brief Handles and copies ARO from NS to NA + * + * @param[in] iface The interface the ARO-carrying NS came over. + * @param[in] ipv6 The IPv6 header of the message carrying the original + * ARO. + * @param[in] nbr_sol The neighbor solicitation carrying the original ARO + * (handed over as @ref icmpv6_hdr_t, since it is just + * handed to @ref _handle_aro()). + * @param[in] aro The original ARO + * @param[in] sl2ao SL2AO associated with the ARO. + * + * @return registration status of the address (including + * @ref _ADDR_REG_STATUS_TENTATIVE and @ref _ADDR_REG_STATUS_IGNORE). + */ +gnrc_pktsnip_t *_copy_and_handle_aro(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const ndp_nbr_sol_t *nbr_sol, + const sixlowpan_nd_opt_ar_t *aro, + const ndp_opt_t *sl2ao); +#else /* GNRC_IPV6_NIB_CONF_6LR || defined(DOXYGEN) */ +#define _is_6lr(netif) (false) +#define _rtr_sol_on_6lr(netif, icmpv6) (false) +#define _get_ar_state(nbr) (_ADDR_REG_STATUS_IGNORE) +#define _set_ar_state(nbr, state) (void)nbr; (void)state +#define _copy_and_handle_aro(iface, ipv6, icmpv6, aro, sl2ao) \ + (NULL) +/* _reg_addr_upstream() doesn't make sense without 6LR so don't even use it + * => throw error in case it is compiled in => don't define it here as NOP macro + */ +#endif /* GNRC_IPV6_NIB_CONF_6LR || defined(DOXYGEN) */ + +#ifdef __cplusplus +} +#endif + +#endif /* PRIV_NIB_6LR_H */ +/** @} */ diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c new file mode 100644 index 0000000000..c678f3cecb --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2017 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 "xtimer.h" +#include "net/gnrc/ndp2.h" +#include "net/gnrc/ipv6/nib.h" + +#include "_nib-arsm.h" +#include "_nib-6lr.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#if ENABLE_DEBUG +static char addr_str[IPV6_ADDR_MAX_STR_LEN]; +#endif + +/** + * @brief Determines supposed link-layer address from interface and option + * length + * + * @param[in] netif A network interface. + * @param[in] opt A SL2AO or TL2AO. + * + * @return The length of the L2 address carried in @p opt. + */ +static inline unsigned _get_l2addr_len(gnrc_ipv6_netif_t *netif, + const ndp_opt_t *opt); + +void _snd_ns(const ipv6_addr_t *tgt, gnrc_ipv6_netif_t *netif, + const ipv6_addr_t *src, const ipv6_addr_t *dst) +{ + gnrc_pktsnip_t *ext_opt = NULL; + + gnrc_ndp2_nbr_sol_send(tgt, netif, src, dst, ext_opt); +} + +void _snd_uc_ns(_nib_onl_entry_t *nbr, bool reset) +{ + gnrc_ipv6_netif_t *netif = gnrc_ipv6_netif_get(_nib_onl_get_if(nbr)); + _nib_iface_t *iface = _nib_iface_get(_nib_onl_get_if(nbr)); + + DEBUG("unicast to %s (retrans. timer = %ums)\n", + ipv6_addr_to_str(addr_str, &nbr->ipv6, sizeof(addr_str)), + (unsigned)iface->retrans_time); + assert((netif != NULL) && (iface != NULL)); +#if GNRC_IPV6_NIB_CONF_ARSM + if (reset) { + nbr->ns_sent = 0; + } +#else + (void)reset; +#endif + _snd_ns(&nbr->ipv6, netif, NULL, &nbr->ipv6); + _evtimer_add(nbr, GNRC_IPV6_NIB_SND_UC_NS, &nbr->nud_timeout, + iface->retrans_time); +#if GNRC_IPV6_NIB_CONF_ARSM + nbr->ns_sent++; +#endif +} + +void _handle_sl2ao(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const icmpv6_hdr_t *icmpv6, const ndp_opt_t *sl2ao) +{ + _nib_onl_entry_t *nce = _nib_onl_get(&ipv6->src, iface); + gnrc_ipv6_netif_t *netif = gnrc_ipv6_netif_get(iface); + unsigned l2addr_len; + + assert(netif != NULL); + l2addr_len = _get_l2addr_len(netif, sl2ao); + if (l2addr_len == 0U) { + DEBUG("nib: Unexpected SL2AO length. Ignoring SL2AO\n"); + return; + } +#if GNRC_IPV6_NIB_CONF_ARSM + if ((nce != NULL) && (nce->mode & _NC) && + ((nce->l2addr_len != l2addr_len) || + (memcmp(nce->l2addr, sl2ao + 1, nce->l2addr_len) != 0)) && + /* a 6LR MUST NOT modify an existing NCE based on an SL2AO in an RS + * see https://tools.ietf.org/html/rfc6775#section-6.3 */ + !_rtr_sol_on_6lr(netif, icmpv6)) { + DEBUG("nib: L2 address differs. Setting STALE\n"); + evtimer_del(&_nib_evtimer, &nce->nud_timeout.event); + _set_nud_state(nce, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE); + } +#endif /* GNRC_IPV6_NIB_CONF_ARSM */ + if ((nce == NULL) || !(nce->mode & _NC)) { + DEBUG("nib: Creating NCE for (ipv6 = %s, iface = %u, nud_state = STALE)\n", + ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str)), iface); + nce = _nib_nc_add(&ipv6->src, iface, + GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE); + if (nce != NULL) { + if (icmpv6->type == ICMPV6_NBR_SOL) { + nce->info &= ~GNRC_IPV6_NIB_NC_INFO_IS_ROUTER; + } +#if GNRC_IPV6_NIB_CONF_MULTIHOP_DAD && GNRC_IPV6_NIB_CONF_6LR + else if (_rtr_sol_on_6lr(netif, icmpv6)) { + DEBUG("nib: Setting newly created entry to tentative\n"); + _set_ar_state(nce, GNRC_IPV6_NIB_NC_INFO_AR_STATE_TENTATIVE); + _evtimer_add(nce, GNRC_IPV6_NIB_ADDR_REG_TIMEOUT, + &nce->addr_reg_timeout, + SIXLOWPAN_ND_TENTATIVE_NCE_SEC_LTIME * MS_PER_SEC); + } +#endif + } +#if ENABLE_DEBUG + else { + DEBUG("nib: Neighbor cache full\n"); + } +#endif + } + /* not else to include NCE created in nce == NULL branch */ + if ((nce != NULL) && (nce->mode & _NC)) { + if (icmpv6->type == ICMPV6_RTR_ADV) { + DEBUG("nib: %s%%%u is a router\n", + ipv6_addr_to_str(addr_str, &nce->ipv6, sizeof(addr_str)), + iface); + nce->info |= GNRC_IPV6_NIB_NC_INFO_IS_ROUTER; + } + else if (icmpv6->type != ICMPV6_NBR_SOL) { + DEBUG("nib: %s%%%u is probably not a router\n", + ipv6_addr_to_str(addr_str, &nce->ipv6, sizeof(addr_str)), + iface); + nce->info &= ~GNRC_IPV6_NIB_NC_INFO_IS_ROUTER; + } +#if GNRC_IPV6_NIB_CONF_ARSM + /* a 6LR MUST NOT modify an existing NCE based on an SL2AO in an RS + * see https://tools.ietf.org/html/rfc6775#section-6.3 */ + if (!_rtr_sol_on_6lr(netif, icmpv6)) { + nce->l2addr_len = l2addr_len; + memcpy(nce->l2addr, sl2ao + 1, l2addr_len); + } +#endif + } +} + +static inline unsigned _get_l2addr_len(gnrc_ipv6_netif_t *netif, + const ndp_opt_t *opt) +{ +#if GNRC_IPV6_NIB_CONF_6LN + if (_is_6ln(netif)) { + switch (opt->len) { + case 1U: + return 2U; + case 2U: + return 8U; + default: + return 0U; + } + } +#else + (void)netif; +#endif /* GNRC_IPV6_NIB_CONF_6LN */ + if (opt->len == 1U) { + return 6U; + } + return 0U; +} + +#if GNRC_IPV6_NIB_CONF_ARSM +/** + * @brief Calculates exponential back-off for retransmission timer for + * neighbor solicitations + * + * @param[in] ns_sent Neighbor solicitations sent up until now. + * @param[in] retrans_timer Currently configured retransmission timer. + * + * @return exponential back-off of the retransmission timer + */ +static inline uint32_t _exp_backoff_retrans_timer(uint8_t ns_sent, + uint32_t retrans_timer); +#if GNRC_IPV6_NIB_CONF_REDIRECT +/** + * @brief Checks if the carrier of the TL2AO was a redirect message + * + * @param[in] icmpv6 An ICMPv6 header. + * @param[in] tl2ao A TL2AO. + * + * @return result of icmpv6_hdr_t::type == ICMPV6_REDIRECT for @p icmp and + * ndp_opt_t::type == NDP_OPT_TL2A for @p tl2ao. + */ +static inline bool _redirect_with_tl2ao(icmpv6_hdr_t *icmpv6, ndp_opt_t *tl2ao); +#else /* GNRC_IPV6_NIB_CONF_REDIRECT */ +/* just fall through if redirect not handled */ +#define _redirect_with_tl2ao(a, b) (false) +#endif /* GNRC_IPV6_NIB_CONF_REDIRECT */ + +static inline bool _oflag_set(const ndp_nbr_adv_t *nbr_adv); +static inline bool _sflag_set(const ndp_nbr_adv_t *nbr_adv); +static inline bool _rflag_set(const ndp_nbr_adv_t *nbr_adv); + +/** + * @brief Checks if the information in the TL2AO would change the + * corresponding neighbor cache entry + * + * @param[in] nce A neighbor cache entry. + * @param[in] tl2ao The TL2AO. + * @param[in] iface The interface the TL2AO came over. + * @param[in] tl2ao_addr_len Length of the L2 address in the TL2AO. + * + * @return `true`, if the TL2AO changes the NCE. + * @return `false`, if the TL2AO does not change the NCE. + */ +static inline bool _tl2ao_changes_nce(_nib_onl_entry_t *nce, + const ndp_opt_t *tl2ao, + kernel_pid_t iface, + unsigned tl2ao_addr_len); + +void _handle_snd_ns(_nib_onl_entry_t *nbr) +{ + const uint16_t state = _get_nud_state(nbr); + + DEBUG("nib: Retransmit neighbor solicitation\n"); + switch (state) { + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE: + if (nbr->ns_sent >= NDP_MAX_MC_SOL_NUMOF) { + _nib_nc_remove(nbr); + return; + } + _probe_nbr(nbr, false); + break; + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_PROBE: + if (nbr->ns_sent >= NDP_MAX_UC_SOL_NUMOF) { + _set_nud_state(nbr, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE); + } + /* falls through intentionally */ + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE: + _probe_nbr(nbr, false); + break; + default: + break; + } +} + +void _handle_state_timeout(_nib_onl_entry_t *nbr) +{ + uint16_t new_state = GNRC_IPV6_NIB_NC_INFO_NUD_STATE_PROBE; + + switch (_get_nud_state(nbr)) { + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_REACHABLE: + DEBUG("nib: Timeout reachability\n"); + new_state = GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE; + /* falls through intentionally */ + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_DELAY: + _set_nud_state(nbr, new_state); + if (new_state == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_PROBE) { + DEBUG("nib: Timeout DELAY state\n"); + _probe_nbr(nbr, true); + } + break; + } +} + +void _probe_nbr(_nib_onl_entry_t *nbr, bool reset) +{ + const uint16_t state = _get_nud_state(nbr); + DEBUG("nib: Probing "); + switch (state) { + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNMANAGED: + DEBUG("UNMANAGED entry %s => skipping\n", + ipv6_addr_to_str(addr_str, &nbr->ipv6, sizeof(addr_str))); + break; + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE: + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE: { + _nib_iface_t *iface = _nib_iface_get(_nib_onl_get_if(nbr)); + uint32_t next_ns = _evtimer_lookup(nbr, + GNRC_IPV6_NIB_SND_MC_NS); + if (next_ns > iface->retrans_time) { + gnrc_ipv6_netif_t *netif = gnrc_ipv6_netif_get(_nib_onl_get_if(nbr)); + ipv6_addr_t sol_nodes; + uint32_t retrans_time = iface->retrans_time; + + DEBUG("multicast to %s's solicited nodes ", + ipv6_addr_to_str(addr_str, &nbr->ipv6, + sizeof(addr_str))); + assert(netif != NULL); + if (reset) { + nbr->ns_sent = 0; + } + if (state == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE) { + /* first 3 retransmissions in PROBE, assume 1 higher to + * not send after iface->retrans_timer sec again, + * but the next backoff after that => subtract 2 */ + retrans_time = _exp_backoff_retrans_timer(nbr->ns_sent - 2, + retrans_time); + } + DEBUG("(retrans. timer = %ums)\n", (unsigned)retrans_time); + ipv6_addr_set_solicited_nodes(&sol_nodes, &nbr->ipv6); + _snd_ns(&nbr->ipv6, netif, NULL, &sol_nodes); + _evtimer_add(nbr, GNRC_IPV6_NIB_SND_MC_NS, &nbr->nud_timeout, + retrans_time); + if (nbr->ns_sent < UINT8_MAX) { + /* cap ns_sent at UINT8_MAX to prevent backoff reset */ + nbr->ns_sent++; + } + } +#if ENABLE_DEBUG + else { + DEBUG("multicast to %s's solicited nodes (skipping since there is already " + "a multicast NS within %ums)\n", + ipv6_addr_to_str(addr_str, &nbr->ipv6, + sizeof(addr_str)), + (unsigned)iface->retrans_time); + } +#endif + } + break; + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_PROBE: + default: + _snd_uc_ns(nbr, reset); + break; + } +} + +void _handle_adv_l2(kernel_pid_t iface, _nib_onl_entry_t *nce, + const icmpv6_hdr_t *icmpv6, const ndp_opt_t *tl2ao) +{ + gnrc_ipv6_netif_t *netif = gnrc_ipv6_netif_get(iface); + unsigned l2addr_len = 0; + + assert(nce != NULL); + assert(netif != NULL); + if (tl2ao != NULL) { + l2addr_len = _get_l2addr_len(netif, tl2ao); + if (l2addr_len == 0U) { + DEBUG("nib: Unexpected TL2AO length. Ignoring TL2AO\n"); + return; + } + } + if ((_get_nud_state(nce) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE) || + _oflag_set((ndp_nbr_adv_t *)icmpv6) || + _redirect_with_tl2ao(icmpv6, tl2ao) || + _tl2ao_changes_nce(nce, tl2ao, iface, l2addr_len)) { + bool nce_was_incomplete = + (_get_nud_state(nce) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE); + if (tl2ao != NULL) { + nce->l2addr_len = l2addr_len; + memcpy(nce->l2addr, tl2ao + 1, l2addr_len); + } + else { + nce->l2addr_len = 0; + } + if (_sflag_set((ndp_nbr_adv_t *)icmpv6)) { + _set_reachable(iface, nce); + } + else if ((icmpv6->type != ICMPV6_NBR_ADV) || + !_sflag_set((ndp_nbr_adv_t *)icmpv6) || + (!nce_was_incomplete && + _tl2ao_changes_nce(nce, tl2ao, iface, l2addr_len))) { + DEBUG("nib: Set %s%%%u to STALE\n", + ipv6_addr_to_str(addr_str, &nce->ipv6, sizeof(addr_str)), + iface); + evtimer_del(&_nib_evtimer, &nce->nud_timeout.event); + _set_nud_state(nce, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE); + } + if (_oflag_set((ndp_nbr_adv_t *)icmpv6) || + ((icmpv6->type == ICMPV6_NBR_ADV) && nce_was_incomplete)) { + if (_rflag_set((ndp_nbr_adv_t *)icmpv6)) { + nce->info |= GNRC_IPV6_NIB_NC_INFO_IS_ROUTER; + } + else { + nce->info &= ~GNRC_IPV6_NIB_NC_INFO_IS_ROUTER; + } + } +#if GNRC_IPV6_NIB_CONF_QUEUE_PKT && MODULE_GNRC_IPV6 + /* send queued packets */ + gnrc_pktqueue_t *ptr; + DEBUG("nib: Sending queued packets\n"); + while ((ptr = gnrc_pktqueue_remove_head(&nce->pktqueue)) != NULL) { + if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_IPV6, + GNRC_NETREG_DEMUX_CTX_ALL, + ptr->pkt)) { + DEBUG("nib: No receivers for packet\n"); + gnrc_pktbuf_release_error(ptr->pkt, EBADF); + } + ptr->pkt = NULL; + } +#endif /* GNRC_IPV6_NIB_CONF_QUEUE_PKT */ + if ((icmpv6->type == ICMPV6_NBR_ADV) && + !_sflag_set((ndp_nbr_adv_t *)icmpv6) && + (_get_nud_state(nce) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_REACHABLE) && + _tl2ao_changes_nce(nce, tl2ao, iface, l2addr_len)) { + evtimer_del(&_nib_evtimer, &nce->nud_timeout.event); + _set_nud_state(nce, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE); + } + } + else if ((icmpv6->type == ICMPV6_NBR_ADV) && + (_get_nud_state(nce) != GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE) && + (_get_nud_state(nce) != GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNMANAGED) && + _sflag_set((ndp_nbr_adv_t *)icmpv6) && + !_tl2ao_changes_nce(nce, tl2ao, iface, l2addr_len)) { + _set_reachable(iface, nce); + } +} + +void _set_reachable(unsigned iface, _nib_onl_entry_t *nce) +{ + _nib_iface_t *nib_netif = _nib_iface_get(iface); + + DEBUG("nib: Set %s%%%u to REACHABLE for %ums\n", + ipv6_addr_to_str(addr_str, &nce->ipv6, sizeof(addr_str)), + iface, (unsigned)nib_netif->reach_time); + _set_nud_state(nce, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_REACHABLE); + _evtimer_add(nce, GNRC_IPV6_NIB_REACH_TIMEOUT, &nce->nud_timeout, + nib_netif->reach_time); +} + +/* internal functions */ +static inline uint32_t _exp_backoff_retrans_timer(uint8_t ns_sent, + uint32_t retrans_timer) +{ + uint32_t tmp = random_uint32_range(NDP_MIN_RANDOM_FACTOR, + NDP_MAX_RANDOM_FACTOR); + + /* backoff according to https://tools.ietf.org/html/rfc7048 with + * BACKOFF_MULTIPLE == 2 */ + tmp = ((1 << ns_sent) * retrans_timer * tmp) / US_PER_MS; + /* random factors were statically multiplied with 1000 ^ */ + if (tmp > NDP_MAX_RETRANS_TIMER_MS) { + tmp = NDP_MAX_RETRANS_TIMER_MS; + } + return tmp; +} + +#if GNRC_IPV6_NIB_CONF_REDIRECT +static inline bool _redirect_with_tl2ao(icmpv6_hdr_t *icmpv6, ndp_opt_t *tl2ao) +{ + return (icmpv6->type == ICMPV6_REDIRECT) && (tl2ao != NULL); +} +#endif + +static inline bool _tl2ao_changes_nce(_nib_onl_entry_t *nce, + const ndp_opt_t *tl2ao, + kernel_pid_t iface, + unsigned tl2ao_addr_len) +{ + return ((tl2ao != NULL) && + (((nce->l2addr_len != tl2ao_addr_len) && + (memcmp(nce->l2addr, tl2ao + 1, tl2ao_addr_len) != 0)) || + (_nib_onl_get_if(nce) != (unsigned)iface))); +} + +static inline bool _oflag_set(const ndp_nbr_adv_t *nbr_adv) +{ + return (nbr_adv->type == ICMPV6_NBR_ADV) && + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_O); +} + +static inline bool _sflag_set(const ndp_nbr_adv_t *nbr_adv) +{ + return (nbr_adv->type == ICMPV6_NBR_ADV) && + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_S); +} + +static inline bool _rflag_set(const ndp_nbr_adv_t *nbr_adv) +{ + return (nbr_adv->type == ICMPV6_NBR_ADV) && + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_R); +} + +#endif /* GNRC_IPV6_NIB_CONF_ARSM */ + +/** @} */ diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.h b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.h new file mode 100644 index 0000000000..44b681273d --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017 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_gnrc_ipv6_nib + * @internal + * @{ + * + * @file + * @brief Definitions related to the address resolution state machine (ARSM) + * of the NIB + * @see @ref GNRC_IPV6_NIB_CONF_ARSM + * + * @author Martine Lenders <m.lenders@fu-berlin.de> + */ +#ifndef PRIV_NIB_ARSM_H +#define PRIV_NIB_ARSM_H + +#include <stdint.h> + +#include "net/gnrc/ipv6/nib/conf.h" +#include "net/ndp.h" +#include "net/icmpv6.h" + +#include "_nib-internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Sends neighbor solicitation (including ARO if required) + * + * @pre `(tgt != NULL) && !ipv6_addr_is_multicast(tgt)` + * @pre `(netif != NULL) && (dst != NULL)` + * + * @param[in] tgt The target address of the neighbor solicitation. + * May not be NULL and **MUST NOT** be multicast. + * @param[in] netif Interface to send over. May not be NULL. + * @param[in] src Source address for the neighbor solicitation. Will be + * chosen from the interface according to @p dst, if NULL. + * @param[in] dst Destination address for neighbor solicitation. May not + * be NULL. + */ +void _snd_ns(const ipv6_addr_t *tgt, gnrc_ipv6_netif_t *netif, + const ipv6_addr_t *src, const ipv6_addr_t *dst); + +/** + * @brief Sends unicast neighbor solicitation and reset corresponding timer + * event + * + * @note Neighbor solicitations are used *by* the ARSM, but also by other + * mechanisms (e.g. duplicate address detection 6Lo address + * resolution). This is why it is defined here, but not exclusively + * available when @ref GNRC_IPV6_NIB_CONF_ARSM is set. + * + * @param[in] nbr Neighbor to send neighbor solicitation to. + * @param[in] reset Reset probe counter. + */ +void _snd_uc_ns(_nib_onl_entry_t *nbr, bool reset); + +/** + * @brief Handles SL2AO + * + * @note This is here (but not only available with + * @ref GNRC_IPV6_NIB_CONF_ARSM set) since it is closely related + * to the ARSM, but ARSM isn't the only mechanism using it (e.g. the + * 6Lo address registration uses it). + * + * @param[in] iface Interface the SL2AO was sent over. + * @param[in] ipv6 IPv6 header of the message carrying the SL2AO. + * @param[in] icmpv6 ICMPv6 header of the message carrying the SL2AO. + * @param[in] sl2ao The SL2AO + */ +void _handle_sl2ao(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const icmpv6_hdr_t *icmpv6, const ndp_opt_t *sl2ao); + +#if GNRC_IPV6_NIB_CONF_ARSM || defined(DOXYGEN) +/** + * @brief Handler for @ref GNRC_IPV6_NIB_SND_UC_NS and + * @ref GNRC_IPV6_NIB_SND_UC_NS event handler + * + * @param[in] nbr Neighbor to send the neighbor solicitation to. + */ +void _handle_snd_ns(_nib_onl_entry_t *nbr); + +/** + * @brief Handler for @ref GNRC_IPV6_NIB_DELAY_TIMEOUT and + * @ref GNRC_IPV6_NIB_REACH_TIMEOUT event handler + * + * @param[in] nbr Neighbor to handle the state timeout for to. + */ +void _handle_state_timeout(_nib_onl_entry_t *nbr); + +/** + * @brief Probes neighbor with neighbor solicitations + * + * @param[in] nbr Neighbor to probe. + * @param[in] reset Reset probe counter. + */ +void _probe_nbr(_nib_onl_entry_t *nbr, bool reset); + +/** + * @brief Handles advertised link-layer information + * + * This can either be an TL2AO or for a link-layer without addresses just a + * neighbor advertisement. + * + * @param[in] iface Interface the link-layer information was advertised + * over. + * @param[in] nce Neighbor cache entry that is updated by the advertised + * link-layer information. + * @param[in] icmpv6 The ICMPv6 message (neighbor advertisement or redirect + * message) that carries the link-layer information. + * @param[in] tl2ao The TL2AO carrying the link-layer information. May be + * NULL for link-layers without addresses. + */ +void _handle_adv_l2(kernel_pid_t iface, _nib_onl_entry_t *nce, + const icmpv6_hdr_t *icmpv6, const ndp_opt_t *tl2ao); + +/** + * @brief Sets a neighbor cache entry reachable and starts the required + * event timers + * + * @param[in] iface Interface to the NCE + * @param[in] nce The neighbor cache entry to set reachable + */ +void _set_reachable(unsigned iface, _nib_onl_entry_t *nce); + +/** + * @brief Initializes interface for address registration state machine + * + * @param[in] nib_iface An interface + */ +static inline void _init_iface_arsm(_nib_iface_t *nib_iface) +{ + nib_iface->reach_time_base = NDP_REACH_MS; + nib_iface->retrans_time = NDP_RETRANS_TIMER_MS; + _nib_iface_recalc_reach_time(nib_iface); +} + +/** + * @brief Gets neighbor unreachability state of a neighbor + * + * @param[in] entry Neighbor cache entry representing the neighbor. + * + * @return Neighbor unreachability state of the @p entry. + */ +static inline uint16_t _get_nud_state(_nib_onl_entry_t *entry) +{ + return (entry->info & GNRC_IPV6_NIB_NC_INFO_NUD_STATE_MASK); +} + +/** + * @brief Sets neighbor unreachablility state of a neighbor + * + * @param[in] entry Neighbor cache entry representing the neighbor. + * @param[in] state Neighbor unreachability state for the neighbor. + */ +static inline void _set_nud_state(_nib_onl_entry_t *entry, uint16_t state) +{ + entry->info &= ~GNRC_IPV6_NIB_NC_INFO_NUD_STATE_MASK; + entry->info |= state; +} + +#else /* GNRC_IPV6_NIB_CONF_ARSM || defined(DOXYGEN) */ +#define _handle_snd_ns(ctx) (void)ctx +#define _handle_state_timeout(ctx) (void)ctx +#define _probe_nbr(nbr, reset) (void)nbr; (void)reset +#define _init_iface_arsm(netif) (void)netif +#define _handle_adv_l2(netif, nce, icmpv6, tl2ao) (void)netif; (void)nce; \ + (void)icmpv6; (void)tl2ao +#define _set_reachable(netif, nce) (void)netif; (void)nce +#define _init_iface_arsm(netif) (void)netif + +#define _get_nud_state(entry) (GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNMANAGED) +#define _set_nud_state(entry, state) (void)entry; (void)state +#endif /* GNRC_IPV6_NIB_CONF_ARSM || defined(DOXYGEN) */ + +#ifdef __cplusplus +} +#endif + +#endif /* PRIV_NIB_ARSM_H */ +/** @} */ diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c index 129c60b617..565bb2352f 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c @@ -13,6 +13,7 @@ * @author Martine Lenders <m.lenders@fu-berlin.de> */ +#include <errno.h> #include <stdbool.h> #include <string.h> @@ -245,9 +246,23 @@ void _nib_nc_remove(_nib_onl_entry_t *node) ipv6_addr_to_str(addr_str, &node->ipv6, sizeof(addr_str)), _nib_onl_get_if(node)); node->mode &= ~(_NC); + evtimer_del((evtimer_t *)&_nib_evtimer, &node->snd_na.event); #if GNRC_IPV6_NIB_CONF_ARSM evtimer_del((evtimer_t *)&_nib_evtimer, &node->nud_timeout.event); #endif +#if GNRC_IPV6_NIB_CONF_6LR + evtimer_del((evtimer_t *)&_nib_evtimer, &node->addr_reg_timeout.event); +#endif +#if GNRC_IPV6_NIB_CONF_QUEUE_PKT + gnrc_pktqueue_t *tmp; + for (gnrc_pktqueue_t *ptr = node->pktqueue; + (ptr != NULL) && (tmp = (ptr->next), 1); + ptr = tmp) { + gnrc_pktqueue_t *entry = gnrc_pktqueue_remove(&node->pktqueue, ptr); + gnrc_pktbuf_release_error(entry->pkt, EHOSTUNREACH); + entry->pkt = NULL; + } +#endif /* GNRC_IPV6_NIB_CONF_QUEUE_PKT */ _nib_onl_clear(node); } @@ -772,6 +787,20 @@ _nib_iface_t *_nib_iface_get(unsigned iface) return ni; } +#if GNRC_IPV6_NIB_CONF_ARSM +void _nib_iface_recalc_reach_time(_nib_iface_t *iface) +{ + uint32_t factor = random_uint32_range(NDP_MIN_RANDOM_FACTOR, + NDP_MAX_RANDOM_FACTOR); + + /* random factor was times 1000 so we need to divide it again */ + iface->reach_time = (iface->reach_time_base * factor) / 1000; + _evtimer_add(iface, GNRC_IPV6_NIB_RECALC_REACH_TIME, + &iface->recalc_reach_time, + GNRC_IPV6_NIB_CONF_REACH_TIME_RESET); +} +#endif + static void _override_node(const ipv6_addr_t *addr, unsigned iface, _nib_onl_entry_t *node) { @@ -799,7 +828,7 @@ uint32_t _evtimer_lookup(const void *ctx, uint16_t type) evtimer_msg_event_t *event = (evtimer_msg_event_t *)_nib_evtimer.events; uint32_t offset = 0; - DEBUG("nib: lookup ctx = %p, type = %u\n", (void *)ctx, type); + DEBUG("nib: lookup ctx = %p, type = %04x\n", (void *)ctx, type); while (event != NULL) { offset += event->event.offset; if ((event->msg.type == type) && diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h index bd6eb009f1..24e39f4234 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h @@ -108,16 +108,31 @@ typedef struct _nib_onl_entry { * @note Only available if @ref GNRC_IPV6_NIB_CONF_ARSM != 0. */ uint8_t l2addr[GNRC_IPV6_NIB_L2ADDR_MAX_LEN]; +#endif /** - * @brief Event for @ref GNRC_IPV6_NIB_REACH_TIMEOUT and + * @brief Event for @ref GNRC_IPV6_NIB_SND_UC_NS, + * @ref GNRC_IPV6_NIB_SND_MC_NS, @ref GNRC_IPV6_NIB_REACH_TIMEOUT and * @ref GNRC_IPV6_NIB_DELAY_TIMEOUT * - * @note Events of these types can't be in the event queue at the same - * time (since they only have one NUD state at a time). Because of - * this we can use one event for both of them (but need the - * different types, since the events are handled differently) + * @note Four event types + * 1. To easier distinguish multicast probes in _evtimer_lookup for + * rate-limiting from unicast probes. + * 2. Since the types can't be in the event queue at the same time + * (since they only have one NUD state at a time and probing is + * one of these states). Because of this we can use one event + * for all of them (but need the different types, since the + * events are handled differently). + * @note This is also available with @ref GNRC_IPV6_NIB_CONF_ARSM == 0, + * since 6Lo address registration uses it to time the sending of + * neighbor solicitations. */ evtimer_msg_event_t nud_timeout; + /** + * @brief Event for @ref GNRC_IPV6_NIB_SND_NA + */ + evtimer_msg_event_t snd_na; +#if GNRC_IPV6_NIB_CONF_6LR || defined(DOXYGEN) + evtimer_msg_event_t addr_reg_timeout; /**< Event for @ref GNRC_IPV6_NIB_ADDR_REG_TIMEOUT */ #endif /** @@ -135,14 +150,12 @@ typedef struct _nib_onl_entry { * @see [Mode flags for entries](@ref net_gnrc_ipv6_nib_mode). */ uint8_t mode; -#if GNRC_IPV6_NIB_CONF_ARSM || defined(DOXYGEN) /** * @brief Neighbor solicitations sent for probing - * - * @note Only available if @ref GNRC_IPV6_NIB_CONF_ARSM != 0. */ uint8_t ns_sent; +#if GNRC_IPV6_NIB_CONF_ARSM || defined(DOXYGEN) /** * @brief length in bytes of _nib_onl_entry_t::l2addr * @@ -190,8 +203,8 @@ typedef struct { */ uint32_t reach_time_base; uint32_t reach_time; /**< reachable time (in ms) */ - uint32_t retrans_time; /**< retransmission time (in ms) */ #endif + uint32_t retrans_time; /**< retransmission time (in ms) */ #if GNRC_IPV6_NIB_CONF_ROUTER || defined(DOXYGEN) /** * @brief timestamp in milliseconds of last unsolicited router @@ -200,6 +213,12 @@ typedef struct { * @note Only available if @ref GNRC_IPV6_NIB_CONF_ROUTER. */ uint32_t last_ra; +#endif +#if GNRC_IPV6_NIB_CONF_ARSM || defined(DOXYGEN) + /** + * @brief Event for @ref GNRC_IPV6_NIB_RECALC_REACH_TIME + */ + evtimer_msg_event_t recalc_reach_time; #endif kernel_pid_t pid; /**< identifier of the interface */ #if GNRC_IPV6_NIB_CONF_ROUTER || defined(DOXYGEN) @@ -789,6 +808,17 @@ int _nib_get_route(const ipv6_addr_t *dst, gnrc_pktsnip_t *ctx, */ _nib_iface_t *_nib_iface_get(unsigned iface); +/** + * @brief Recalculates randomized reachable time of an interface. + * + * @param[in] iface An interface. + */ +#if GNRC_IPV6_NIB_CONF_ARSM +void _nib_iface_recalc_reach_time(_nib_iface_t *iface); +#else +#define _nib_iface_recalc_reach_time(iface) (void)iface +#endif + /** * @brief Looks up if an event is queued in the event timer * diff --git a/sys/net/gnrc/network_layer/ipv6/nib/nib.c b/sys/net/gnrc/network_layer/ipv6/nib/nib.c new file mode 100644 index 0000000000..b2bcf0930b --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/nib/nib.c @@ -0,0 +1,641 @@ +/* + * Copyright (C) 2017 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 <errno.h> +#include <stdbool.h> + +#include "net/ipv6/addr.h" +#include "net/gnrc/nettype.h" +#include "net/gnrc/ipv6/netif.h" +#include "net/gnrc/ipv6/nib.h" +#include "net/gnrc/ndp2.h" +#include "net/gnrc/pktqueue.h" +#include "net/gnrc/sixlowpan/nd.h" +#include "net/ndp.h" +#include "net/sixlowpan/nd.h" + +#include "_nib-internal.h" +#include "_nib-arsm.h" +#include "_nib-6ln.h" +#include "_nib-6lr.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" +#if ENABLE_DEBUG +#include "xtimer.h" +#endif + +#if ENABLE_DEBUG +static char addr_str[IPV6_ADDR_MAX_STR_LEN]; +#endif + +#if GNRC_IPV6_NIB_CONF_QUEUE_PKT +static gnrc_pktqueue_t _queue_pool[GNRC_IPV6_NIB_NUMOF]; +#endif + +/** + * @internal + * @{ + */ +static void _handle_nbr_sol(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const ndp_nbr_sol_t *nbr_sol, size_t icmpv6_len); +static void _handle_nbr_adv(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const ndp_nbr_adv_t *nbr_adv, size_t icmpv6_len); + +static bool _resolve_addr(const ipv6_addr_t *dst, kernel_pid_t iface, + gnrc_pktsnip_t *pkt, gnrc_ipv6_nib_nc_t *nce, + _nib_onl_entry_t *entry); + +static void _handle_snd_na(gnrc_pktsnip_t *pkt); + +/* interface flag checks */ +#if GNRC_IPV6_NIB_CONF_ROUTER +static inline bool _is_rtr(const gnrc_ipv6_netif_t *netif) +{ + return (netif->flags & GNRC_IPV6_NETIF_FLAGS_ROUTER); +} +#endif +/** @} */ + +void gnrc_ipv6_nib_init(void) +{ + evtimer_event_t *tmp; + + mutex_lock(&_nib_mutex); + for (evtimer_event_t *ptr = _nib_evtimer.events; + (ptr != NULL) && (tmp = (ptr->next), 1); + ptr = tmp) { + evtimer_del((evtimer_t *)(&_nib_evtimer), ptr); + } + _nib_init(); + mutex_unlock(&_nib_mutex); +} + +void gnrc_ipv6_nib_init_iface(kernel_pid_t iface) +{ + _nib_iface_t *nib_iface; + + assert(iface > KERNEL_PID_UNDEF); + DEBUG("nib: Initialize interface %u\n", (unsigned)iface); + mutex_lock(&_nib_mutex); + nib_iface = _nib_iface_get(iface); +#ifdef TEST_SUITES + if (nib_iface == NULL) { + /* in the unittests old NC and NIB are mixed, so this function leads to + * crashes. To prevent this we early exit here, if the interface was + * not found + * TODO: remove when gnrc_ipv6_nc is removed. + */ + mutex_unlock(&_nib_mutex); + return; + } +#else + assert(nib_iface != NULL); +#endif + /* TODO: + * - set link-local address here for stateless address auto-configuration + * and 6LN + * - join solicited nodes group of link-local address here for address + * resolution here + * - join all router group of link-local address here on router node here + * - become an router advertising interface here on non-6LR here */ + + _init_iface_arsm(nib_iface); + nib_iface->rs_sent = 0; + nib_iface->na_sent = 0; +#if GNRC_IPV6_NIB_CONF_ROUTER + nib_iface->last_ra = UINT32_MAX; + nib_iface->ra_sent = 0; +#endif + mutex_unlock(&_nib_mutex); +} + +int gnrc_ipv6_nib_get_next_hop_l2addr(const ipv6_addr_t *dst, + kernel_pid_t iface, gnrc_pktsnip_t *pkt, + gnrc_ipv6_nib_nc_t *nce) +{ + int res = 0; + + mutex_lock(&_nib_mutex); + do { /* XXX: hidden goto ;-) */ + if (ipv6_addr_is_link_local(dst)) { + /* TODO: Prefix-based on-link determination */ + if ((iface == KERNEL_PID_UNDEF) || + !_resolve_addr(dst, iface, pkt, nce, + _nib_onl_get(dst, iface))) { + res = -EHOSTUNREACH; + break; + } + } + else { + /* TODO: Off-link next hop determination */ + res = -EHOSTUNREACH; + } + } while (0); + mutex_unlock(&_nib_mutex); + return res; +} + +void gnrc_ipv6_nib_handle_pkt(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const icmpv6_hdr_t *icmpv6, size_t icmpv6_len) +{ + DEBUG("nib: Handle packet (icmpv6->type = %u)\n", icmpv6->type); + mutex_lock(&_nib_mutex); + switch (icmpv6->type) { +#if GNRC_IPV6_NIB_CONF_ROUTER + case ICMPV6_RTR_SOL: + /* TODO */ + break; +#endif /* GNRC_IPV6_NIB_CONF_ROUTER */ + case ICMPV6_RTR_ADV: + /* TODO */ + break; + case ICMPV6_NBR_SOL: + _handle_nbr_sol(iface, ipv6, (ndp_nbr_sol_t *)icmpv6, icmpv6_len); + break; + case ICMPV6_NBR_ADV: + _handle_nbr_adv(iface, ipv6, (ndp_nbr_adv_t *)icmpv6, icmpv6_len); + break; +#if GNRC_IPV6_NIB_CONF_REDIRECT + case ICMPV6_REDIRECT: + /* TODO */ + break; +#endif /* GNRC_IPV6_NIB_CONF_REDIRECT */ +#if GNRC_IPV6_NIB_CONF_MULTIHOP_DAD + case ICMPV6_DAR: + /* TODO */ + break; + case ICMPV6_DAC: + /* TODO */ + break; +#endif /* GNRC_IPV6_NIB_CONF_MULTIHOP_DAD */ + } + mutex_unlock(&_nib_mutex); +} + +void gnrc_ipv6_nib_handle_timer_event(void *ctx, uint16_t type) +{ + DEBUG("nib: Handle timer event (ctx = %p, type = 0x%04x, now = %ums)\n", + ctx, type, (unsigned)xtimer_now_usec() / 1000); + mutex_lock(&_nib_mutex); + switch (type) { +#if GNRC_IPV6_NIB_CONF_ARSM + case GNRC_IPV6_NIB_SND_UC_NS: + case GNRC_IPV6_NIB_SND_MC_NS: + _handle_snd_ns(ctx); + break; + case GNRC_IPV6_NIB_REACH_TIMEOUT: + case GNRC_IPV6_NIB_DELAY_TIMEOUT: + _handle_state_timeout(ctx); + break; + case GNRC_IPV6_NIB_RECALC_REACH_TIME: + _nib_iface_recalc_reach_time(ctx); + break; +#endif /* GNRC_IPV6_NIB_CONF_ARSM */ + case GNRC_IPV6_NIB_SND_NA: + _handle_snd_na(ctx); + break; + case GNRC_IPV6_NIB_SEARCH_RTR: + /* TODO */ + break; + case GNRC_IPV6_NIB_RECONFIRM_RTR: + /* TODO */ + break; +#if GNRC_IPV6_NIB_CONF_ROUTER + case GNRC_IPV6_NIB_REPLY_RS: + /* TODO */ + break; + case GNRC_IPV6_NIB_SND_MC_RA: + /* TODO */ + break; +#endif /* GNRC_IPV6_NIB_CONF_ROUTER */ +#if GNRC_IPV6_NIB_CONF_6LN + case GNRC_IPV6_NIB_ADDR_REG_TIMEOUT: + /* TODO */ + break; + case GNRC_IPV6_NIB_6LO_CTX_TIMEOUT: + /* TODO */ + break; +#endif /* GNRC_IPV6_NIB_CONF_6LN */ +#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C + case GNRC_IPV6_NIB_ABR_TIMEOUT: + /* TODO */ + break; +#endif /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */ + case GNRC_IPV6_NIB_PFX_TIMEOUT: + /* TODO */ + break; + case GNRC_IPV6_NIB_RTR_TIMEOUT: + /* TODO */ + break; + default: + break; + } + mutex_unlock(&_nib_mutex); +} + +/* Iterator for NDP options in a packet */ +#define FOREACH_OPT(ndp_pkt, opt, icmpv6_len) \ + for (opt = (ndp_opt_t *)(ndp_pkt + 1); \ + icmpv6_len > 0; \ + icmpv6_len -= (opt->len << 3), \ + opt = (ndp_opt_t *)(((uint8_t *)opt) + (opt->len << 3))) + +static size_t _get_l2src(kernel_pid_t iface, uint8_t *l2src, + size_t l2src_maxlen) +{ + bool try_long = false; + int res; + uint16_t l2src_len; + /* maximum address length that fits into a minimum length (8) S/TL2A + * option */ + const uint16_t max_short_len = 6; + + /* try getting source address */ + if ((gnrc_netapi_get(iface, NETOPT_SRC_LEN, 0, &l2src_len, + sizeof(l2src_len)) >= 0) && + (l2src_len > max_short_len)) { + try_long = true; + } + + if (try_long && ((res = gnrc_netapi_get(iface, NETOPT_ADDRESS_LONG, 0, + l2src, l2src_maxlen)) > max_short_len)) { + l2src_len = (uint16_t)res; + } + else if ((res = gnrc_netapi_get(iface, NETOPT_ADDRESS, 0, l2src, + l2src_maxlen)) >= 0) { + l2src_len = (uint16_t)res; + } + else { + DEBUG("nib: No link-layer address found.\n"); + l2src_len = 0; + } + + return l2src_len; +} + +static void _send_delayed_nbr_adv(const gnrc_ipv6_netif_t *netif, + const ipv6_addr_t *tgt, + const ipv6_addr_t *dst, + gnrc_pktsnip_t *reply_aro) +{ + gnrc_pktsnip_t *nbr_adv, *extra_opts = reply_aro; + _nib_onl_entry_t *nce; + uint8_t reply_flags = NDP_NBR_ADV_FLAGS_S; + +#if GNRC_IPV6_NIB_CONF_ROUTER + if (_is_rtr(netif)) { + reply_flags |= NDP_NBR_ADV_FLAGS_R; + } +#endif + if (ipv6_addr_is_multicast(dst)) { + uint8_t l2addr[GNRC_IPV6_NIB_L2ADDR_MAX_LEN]; + size_t l2addr_len = _get_l2src(netif->pid, l2addr, sizeof(l2addr)); + if (l2addr_len > 0) { + extra_opts = gnrc_ndp2_opt_tl2a_build(l2addr, l2addr_len, + extra_opts); + if (extra_opts == NULL) { + DEBUG("nib: No space left in packet buffer. Not replying NS"); + gnrc_pktbuf_release(reply_aro); + return; + } + } + else { + reply_flags |= NDP_NBR_ADV_FLAGS_O; + } + } + else { + reply_flags |= NDP_NBR_ADV_FLAGS_O; + } + nbr_adv = gnrc_ndp2_nbr_adv_build(tgt, reply_flags, extra_opts); + if (nbr_adv == NULL) { + DEBUG("nib: No space left in packet buffer. Not replying NS"); + gnrc_pktbuf_release(extra_opts); + return; + } + nce = _nib_onl_get(tgt, netif->pid); + if ((nce != NULL) && (nce->mode & _NC)) { + /* usually this should be the case, but when NCE is full, just + * ignore the sending. Other nodes in this anycast group are + * then preferred */ + _evtimer_add(nce, GNRC_IPV6_NIB_SND_NA, + &nce->snd_na, + random_uint32_range(0, NDP_MAX_ANYCAST_MS_DELAY)); + } +} + +static void _handle_nbr_sol(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const ndp_nbr_sol_t *nbr_sol, size_t icmpv6_len) +{ + size_t tmp_len = icmpv6_len - sizeof(ndp_nbr_sol_t); + ndp_opt_t *opt; + ipv6_addr_t *local; + + /* check validity, see: https://tools.ietf.org/html/rfc4861#section-7.1.1 */ + /* checksum is checked by GNRC's ICMPv6 module */ + if ((ipv6->hl != 255U) || (nbr_sol->code != 0U) || + (icmpv6_len < sizeof(ndp_nbr_sol_t)) || + ipv6_addr_is_multicast(&nbr_sol->tgt) || + (ipv6_addr_is_unspecified(&ipv6->src) && + !ipv6_addr_is_solicited_node(&ipv6->dst))) { + DEBUG("nib: Received neighbor solicitation is invalid. Discarding silently\n"); + DEBUG(" - IP Hop Limit: %u (should be 255)\n", ipv6->hl); + DEBUG(" - ICMP code: %u (should be 0)\n", nbr_sol->code); + DEBUG(" - ICMP length: %u (should > %u)\n", icmpv6_len, + sizeof(ndp_nbr_sol_t)); + DEBUG(" - Target address: %s (should not be multicast)\n", + ipv6_addr_to_str(addr_str, &nbr_sol->tgt, sizeof(addr_str))); + DEBUG(" - Source address: %s\n", + ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str))); + DEBUG(" - Destination address: %s (should be of format " + "ff02::1:ffxx:xxxx if source address is ::)\n", + ipv6_addr_to_str(addr_str, &ipv6->dst, sizeof(addr_str))); + return; + } + /* check if target is assigned only now in case the length was wrong */ + local = gnrc_ipv6_netif_find_addr(iface, &nbr_sol->tgt); + if (local == NULL) { + DEBUG("nib: Target address %s is not assigned to a local interface\n", + ipv6_addr_to_str(addr_str, &nbr_sol->tgt, sizeof(addr_str))); + return; + } + /* pre-check option length */ + FOREACH_OPT(nbr_sol, opt, tmp_len) { + if (tmp_len > icmpv6_len) { + DEBUG("nib: Payload length (%u) of NS doesn't align with options\n", + (unsigned)icmpv6_len); + return; + } + if (opt->len == 0U) { + DEBUG("nib: Option of length 0 detected. " + "Discarding neighbor solicitation silently\n"); + return; + } + } + DEBUG("nib: Received valid neighbor solicitation:\n"); + DEBUG(" - Target address: %s\n", + ipv6_addr_to_str(addr_str, &nbr_sol->tgt, sizeof(addr_str))); + DEBUG(" - Source address: %s\n", + ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str))); + DEBUG(" - Destination address: %s\n", + ipv6_addr_to_str(addr_str, &ipv6->dst, sizeof(addr_str))); +#if GNRC_IPV6_NIB_CONF_SLAAC + /* TODO SLAAC behavior */ +#endif /* GNRC_IPV6_NIB_CONF_SLAAC */ + if (!ipv6_addr_is_unspecified(&ipv6->src)) { +#if GNRC_IPV6_NIB_CONF_6LR + ndp_opt_t *sl2ao = NULL; + sixlowpan_nd_opt_ar_t *aro = NULL; +#else /* GNRC_IPV6_NIB_CONF_6LR */ +#define sl2ao (NULL) +#define aro (NULL) +#endif /* GNRC_IPV6_NIB_CONF_6LR */ + gnrc_ipv6_netif_t *netif; + gnrc_pktsnip_t *reply_aro = NULL; + tmp_len = icmpv6_len - sizeof(ndp_nbr_sol_t); + + netif = gnrc_ipv6_netif_get(iface); + /* TODO: Set STALE NCE if link-layer has no addresses */ + FOREACH_OPT(nbr_sol, opt, tmp_len) { + switch (opt->type) { + case NDP_OPT_SL2A: +#if GNRC_IPV6_NIB_CONF_6LR + if (_is_6lr(netif)) { + DEBUG("nib: Storing SL2AO for later handling\n"); + sl2ao = opt; + break; + } +#endif /* GNRC_IPV6_NIB_CONF_6LR */ + _handle_sl2ao(iface, ipv6, (const icmpv6_hdr_t *)nbr_sol, + opt); + break; +#if GNRC_IPV6_NIB_CONF_6LR + case NDP_OPT_AR: + DEBUG("nib: Storing ARO for later handling\n"); + aro = (sixlowpan_nd_opt_ar_t *)opt; + break; +#endif /* GNRC_IPV6_NIB_CONF_6LR */ + default: + DEBUG("nib: Ignoring unrecognized option type %u for NS\n", + opt->type); + } + } + reply_aro = _copy_and_handle_aro(iface, ipv6, nbr_sol, aro, sl2ao); + /* check if target address is anycast */ + if (gnrc_ipv6_netif_addr_is_non_unicast(local)) { + _send_delayed_nbr_adv(netif, &nbr_sol->tgt, &ipv6->dst, reply_aro); + } + else { + gnrc_ndp2_nbr_adv_send(&nbr_sol->tgt, netif, &ipv6->src, + ipv6_addr_is_multicast(&ipv6->dst), + reply_aro); + } + } +} + +static void _handle_nbr_adv(kernel_pid_t iface, const ipv6_hdr_t *ipv6, + const ndp_nbr_adv_t *nbr_adv, size_t icmpv6_len) +{ + size_t tmp_len = icmpv6_len - sizeof(ndp_nbr_adv_t); + ndp_opt_t *opt; + _nib_onl_entry_t *nce; + + /* check validity, see: https://tools.ietf.org/html/rfc4861#section-7.1.2 */ + /* checksum is checked by GNRC's ICMPv6 module */ + if ((ipv6->hl != 255U) || (nbr_adv->code != 0U) || + (icmpv6_len < sizeof(ndp_nbr_adv_t)) || + ipv6_addr_is_multicast(&nbr_adv->tgt) || + (ipv6_addr_is_multicast(&ipv6->dst) && + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_S))) { + DEBUG("nib: Received neighbor advertisement is invalid. Discarding silently\n"); + DEBUG(" - IP Hop Limit: %u (should be 255)\n", ipv6->hl); + DEBUG(" - ICMP code: %u (should be 0)\n", nbr_adv->code); + DEBUG(" - ICMP length: %u (should > %u)\n", icmpv6_len, + sizeof(ndp_nbr_adv_t)); + DEBUG(" - Target address: %s (should not be multicast)\n", + ipv6_addr_to_str(addr_str, &nbr_adv->tgt, sizeof(addr_str))); + DEBUG(" - Destination address: %s\n", + ipv6_addr_to_str(addr_str, &ipv6->dst, sizeof(addr_str))); + DEBUG(" - Flags: %c%c%c (S must not be set if destination is multicast)\n", + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_R) ? 'R' : '-', + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_S) ? 'S' : '-', + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_O) ? 'O' : '-'); + return; + } + /* pre-check option length */ + FOREACH_OPT(nbr_adv, opt, tmp_len) { + if (tmp_len > icmpv6_len) { + DEBUG("nib: Payload length (%u) of NA doesn't align with options\n", + (unsigned)icmpv6_len); + return; + } + if (opt->len == 0U) { + DEBUG("nib: Option of length 0 detected. " + "Discarding neighbor advertisement silently\n"); + return; + } + } + DEBUG("nib: Received valid neighbor advertisement:\n"); + DEBUG(" - Target address: %s\n", + ipv6_addr_to_str(addr_str, &nbr_adv->tgt, sizeof(addr_str))); + DEBUG(" - Source address: %s\n", + ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str))); + DEBUG(" - Destination address: %s\n", + ipv6_addr_to_str(addr_str, &ipv6->dst, sizeof(addr_str))); + DEBUG(" - Flags: %c%c%c\n", + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_R) ? 'R' : '-', + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_S) ? 'S' : '-', + (nbr_adv->flags & NDP_NBR_ADV_FLAGS_O) ? 'O' : '-'); +#if GNRC_IPV6_NIB_CONF_SLAAC + /* TODO SLAAC behavior */ +#endif + if (((nce = _nib_onl_get(&nbr_adv->tgt, iface)) != NULL) && + (nce->mode & _NC)) { +#if GNRC_IPV6_NIB_CONF_ARSM + bool tl2ao_avail = false; +#endif + + tmp_len = icmpv6_len - sizeof(ndp_nbr_adv_t); + FOREACH_OPT(nbr_adv, opt, tmp_len) { + switch (opt->type) { +#if GNRC_IPV6_NIB_CONF_ARSM + case NDP_OPT_TL2A: + _handle_adv_l2(iface, nce, (icmpv6_hdr_t *)nbr_adv, opt); + tl2ao_avail = true; + break; +#endif +#if GNRC_IPV6_NIB_CONF_6LN + case NDP_OPT_AR: + _handle_aro(iface, ipv6, (const icmpv6_hdr_t *)nbr_adv, + (const sixlowpan_nd_opt_ar_t *)opt, opt, nce); + break; +#endif + default: + DEBUG("nib: Ignoring unrecognized option type %u for NA\n", + opt->type); + } + } +#if GNRC_IPV6_NIB_CONF_ARSM + if (!tl2ao_avail && (nbr_adv->flags & NDP_NBR_ADV_FLAGS_S) && + (_get_nud_state(nce) != GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE)) { + /* reachability confirmed without TL2AO */ + _set_reachable(iface, nce); + } + /* TODO: handling for of advertised link-layer with link-layers without + * addresses */ + /* _handle_adv_l2(iface, nce, (icmpv6_hdr_t *)nbr_adv, NULL); */ +#endif + } +} + +static inline bool _is_reachable(_nib_onl_entry_t *entry) +{ + (void)entry; /* _get_nud_state() might just resolved to UNMANAGED as macro */ + switch (_get_nud_state(entry)) { + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE: + case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE: + return false; + default: + return true; + } +} + +#if GNRC_IPV6_NIB_CONF_QUEUE_PKT +static gnrc_pktqueue_t *_alloc_queue_entry(gnrc_pktsnip_t *pkt) +{ + for (int i = 0; i < GNRC_IPV6_NIB_NUMOF; i++) { + if (_queue_pool[i].pkt == NULL) { + _queue_pool[i].pkt = pkt; + return &_queue_pool[i]; + } + } + return NULL; +} +#endif + +static bool _resolve_addr(const ipv6_addr_t *dst, kernel_pid_t iface, + gnrc_pktsnip_t *pkt, gnrc_ipv6_nib_nc_t *nce, + _nib_onl_entry_t *entry) +{ + bool res = false; +#if GNRC_IPV6_NIB_CONF_ARSM + if ((entry != NULL) && (entry->mode & _NC) && _is_reachable(entry)) { + if (_get_nud_state(entry) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE) { + _set_nud_state(entry, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_DELAY); + _evtimer_add(entry, GNRC_IPV6_NIB_DELAY_TIMEOUT, + &entry->nud_timeout, NDP_DELAY_FIRST_PROBE_MS); + } + _nib_nc_get(entry, nce); + res = true; + } +#else + if (entry != NULL) { + _nib_nc_get(entry, nce); + res = true; + } +#endif + else if (!(res = _resolve_addr_from_ipv6(dst, iface, nce))) { +#if GNRC_IPV6_NIB_CONF_ARSM + bool reset = false; +#endif + if ((entry == NULL) || !(entry->mode & _NC)) { + entry = _nib_nc_add(dst, iface, + GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE); + if (entry == NULL) { + return false; + } +#if GNRC_IPV6_NIB_CONF_ARSM + reset = true; +#endif + } + if (pkt != NULL) { +#if GNRC_IPV6_NIB_CONF_QUEUE_PKT + if (_get_nud_state(entry) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE) { + gnrc_pktqueue_t *queue_entry = _alloc_queue_entry(pkt); + + if (queue_entry != NULL) { + gnrc_pktqueue_add(&entry->pktqueue, queue_entry); + } + } + else { + gnrc_pktbuf_release_error(pkt, EHOSTUNREACH); + } +#else /* GNRC_IPV6_NIB_CONF_QUEUE_PKT */ + gnrc_pktbuf_release_error(pkt, EHOSTUNREACH); +#endif /* GNRC_IPV6_NIB_CONF_QUEUE_PKT */ + } +#if GNRC_IPV6_NIB_CONF_ARSM + _probe_nbr(entry, reset); +#endif + } + return res; +} + +static void _handle_snd_na(gnrc_pktsnip_t *pkt) +{ +#ifdef MODULE_GNRC_IPV6 + DEBUG("nib: Send delayed neighbor advertisement\n"); + if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_IPV6, GNRC_NETREG_DEMUX_CTX_ALL, + pkt)) { + DEBUG("nib: No receivers for neighbor advertisement\n"); + gnrc_pktbuf_release_error(pkt, EBADF); + } +#else + (void)pkt; + DEBUG("nib: No IPv6 module to send delayed neighbor advertisement\n"); +#endif +} + +/** @} */ diff --git a/tests/unittests/tests-gnrc_ipv6_nib/tests-gnrc_ipv6_nib-nc.c b/tests/unittests/tests-gnrc_ipv6_nib/tests-gnrc_ipv6_nib-nc.c index f700008389..c014c9e007 100644 --- a/tests/unittests/tests-gnrc_ipv6_nib/tests-gnrc_ipv6_nib-nc.c +++ b/tests/unittests/tests-gnrc_ipv6_nib/tests-gnrc_ipv6_nib-nc.c @@ -31,14 +31,7 @@ static void set_up(void) { - evtimer_event_t *tmp; - - for (evtimer_event_t *ptr = _nib_evtimer.events; - (ptr != NULL) && (tmp = (ptr->next), 1); - ptr = tmp) { - evtimer_del((evtimer_t *)(&_nib_evtimer), ptr); - } - _nib_init(); + gnrc_ipv6_nib_init(); } /* -- GitLab