diff --git a/Makefile.dep b/Makefile.dep index 1725687b73ed21dd2ad4e6ca25a3689d6f4e8608..c44dd293fdb929a6581e24ef4826ae3b6e03c24c 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -319,8 +319,27 @@ ifneq (,$(filter gnrc_ipv6_nc,$(USEMODULE))) USEMODULE += ipv6_addr endif +ifneq (,$(filter gnrc_ipv6_nib_6lbr,$(USEMODULE))) + USEMODULE += gnrc_ipv6_nib_6lr +endif + +ifneq (,$(filter gnrc_ipv6_nib_6lr,$(USEMODULE))) + USEMODULE += gnrc_ipv6_nib_6ln + USEMODULE += gnrc_ipv6_nib_router +endif + +ifneq (,$(filter gnrc_ipv6_nib_6ln,$(USEMODULE))) + USEMODULE += gnrc_ipv6_nib + USEMODULE += gnrc_sixlowpan_nd +endif + +ifneq (,$(filter gnrc_ipv6_nib_router,$(USEMODULE))) + USEMODULE += gnrc_ipv6_nib +endif + ifneq (,$(filter gnrc_ipv6_nib,$(USEMODULE))) USEMODULE += evtimer + USEMODULE += gnrc_ndp2 USEMODULE += ipv6_addr USEMODULE += random endif diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 3c3e4dd89b03c858100c29ca158ebb155bce7e37..08bb19d0b4a3e4ec8579877c0f073117a83541cc 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -11,6 +11,10 @@ PSEUDOMODULES += emb6_router PSEUDOMODULES += gnrc_ipv6_default PSEUDOMODULES += gnrc_ipv6_router PSEUDOMODULES += gnrc_ipv6_router_default +PSEUDOMODULES += gnrc_ipv6_nib_6lbr +PSEUDOMODULES += gnrc_ipv6_nib_6ln +PSEUDOMODULES += gnrc_ipv6_nib_6lr +PSEUDOMODULES += gnrc_ipv6_nib_router PSEUDOMODULES += gnrc_netdev_default PSEUDOMODULES += gnrc_neterr PSEUDOMODULES += gnrc_netapi_callbacks diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index bbbd2ae2ef537b8a920d80c7799461acd5e7c636..15a4a6d348c41905e34952d694022a7b45e77c68 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -88,6 +88,11 @@ #include "net/gcoap.h" #endif +#ifdef MODULE_GNRC_IPV6_NIB +#include "net/gnrc/ipv6/nib.h" +#endif + + #define ENABLE_DEBUG (0) #include "debug.h" @@ -157,6 +162,10 @@ void auto_init(void) extern void auto_init_devfs(void); auto_init_devfs(); #endif +#ifdef MODULE_GNRC_IPV6_NIB + DEBUG("Auto init gnrc_ipv6_nib module.\n"); + gnrc_ipv6_nib_init(); +#endif /* initialize network devices */ #ifdef MODULE_AUTO_INIT_GNRC_NETIF diff --git a/sys/include/net/gnrc/ipv6/nib.h b/sys/include/net/gnrc/ipv6/nib.h index cd0631241fe5e2f6e6ec89a081da592d0aad750f..0883a2cc583b20847cc1d8c6a8ac2d5e5ed2bbb5 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/include/net/gnrc/ipv6/nib/conf.h b/sys/include/net/gnrc/ipv6/nib/conf.h index 4710ede5f5092903e92c6367afee7c49f25fab37..e3c28286e0aa3c43d1f9882092923d1d72bc281d 100644 --- a/sys/include/net/gnrc/ipv6/nib/conf.h +++ b/sys/include/net/gnrc/ipv6/nib/conf.h @@ -24,6 +24,47 @@ extern "C" { #endif +/* some pseudo-module based configuration, doc: see below */ +#ifdef MODULE_GNRC_IPV6_NIB_6LBR +#ifndef GNRC_IPV6_NIB_CONF_6LBR +#define GNRC_IPV6_NIB_CONF_6LBR (1) +#endif +#endif + +#ifdef MODULE_GNRC_IPV6_NIB_6LR +#ifndef GNRC_IPV6_NIB_CONF_6LR +#define GNRC_IPV6_NIB_CONF_6LR (1) +#endif +#ifndef GNRC_IPV6_NIB_CONF_SLAAC +#define GNRC_IPV6_NIB_CONF_SLAAC (0) +#endif +#endif + +#ifdef MODULE_GNRC_IPV6_NIB_6LN +#ifndef GNRC_IPV6_NIB_CONF_6LN +#define GNRC_IPV6_NIB_CONF_6LN (1) +#endif +#ifndef GNRC_IPV6_NIB_CONF_SLAAC +#define GNRC_IPV6_NIB_CONF_SLAAC (0) +#endif +#ifndef GNRC_IPV6_NIB_CONF_QUEUE_PKT +#define GNRC_IPV6_NIB_CONF_QUEUE_PKT (0) +#endif +#if !GNRC_IPV6_NIB_CONF_6LR +# ifndef GNRC_IPV6_NIB_CONF_ARSM +# define GNRC_IPV6_NIB_CONF_ARSM (0) +# endif +# ifndef GNRC_IPV6_NIB_NUMOF +/* only needs to store default router */ +# define GNRC_IPV6_NIB_NUMOF (1) +# endif +#endif +#endif + +#ifdef MODULE_GNRC_IPV6_NIB_ROUTER +#define GNRC_IPV6_NIB_CONF_ROUTER (1) +#endif + /** * @name Compile flags * @brief Compile flags to (de-)activate certain features for NIB diff --git a/sys/net/gnrc/network_layer/icmpv6/gnrc_icmpv6.c b/sys/net/gnrc/network_layer/icmpv6/gnrc_icmpv6.c index 539019b247928edceb04a0f4d716494525e6fd3a..1ce69e48ac14612d259136b00cfca38c164892e7 100644 --- a/sys/net/gnrc/network_layer/icmpv6/gnrc_icmpv6.c +++ b/sys/net/gnrc/network_layer/icmpv6/gnrc_icmpv6.c @@ -23,7 +23,11 @@ #include "kernel_types.h" #include "net/ipv6/hdr.h" #include "net/gnrc.h" +#ifndef MODULE_GNRC_IPV6_NIB #include "net/gnrc/ndp.h" +#else +#include "net/gnrc/ipv6/nib.h" +#endif #include "net/protnum.h" #include "od.h" #include "utlist.h" @@ -94,6 +98,7 @@ void gnrc_icmpv6_demux(kernel_pid_t iface, gnrc_pktsnip_t *pkt) break; #endif +#ifndef MODULE_GNRC_IPV6_NIB #if (defined(MODULE_GNRC_NDP_ROUTER) || defined(MODULE_GNRC_SIXLOWPAN_ND_ROUTER)) case ICMPV6_RTR_SOL: DEBUG("icmpv6: router solicitation received\n"); @@ -126,6 +131,18 @@ void gnrc_icmpv6_demux(kernel_pid_t iface, gnrc_pktsnip_t *pkt) DEBUG("icmpv6: redirect message received\n"); /* TODO */ break; +#else /* MODULE_GNRC_IPV6_NIB */ + case ICMPV6_RTR_SOL: + case ICMPV6_RTR_ADV: + case ICMPV6_NBR_SOL: + case ICMPV6_NBR_ADV: + case ICMPV6_REDIRECT: + case ICMPV6_DAR: + case ICMPV6_DAC: + DEBUG("icmpv6: NDP message received. Handle with gnrc_ipv6_nib\n"); + gnrc_ipv6_nib_handle_pkt(iface, ipv6->data, hdr, icmpv6->size); + break; +#endif /* MODULE_GNRC_IPV6_NIB */ default: DEBUG("icmpv6: unknown type field %u\n", hdr->type); diff --git a/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c b/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c index 68c1b12126b6e1ec1826ab455f2e114df967532d..ff208242b9a1515256109cb613134151f2b55289 100644 --- a/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c +++ b/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c @@ -29,7 +29,11 @@ #include "thread.h" #include "utlist.h" +#ifndef MODULE_GNRC_IPV6_NIB #include "net/gnrc/ipv6/nc.h" +#else +#include "net/gnrc/ipv6/nib.h" +#endif #include "net/gnrc/ipv6/netif.h" #include "net/gnrc/ipv6/whitelist.h" #include "net/gnrc/ipv6/blacklist.h" @@ -286,6 +290,7 @@ static void *_event_loop(void *args) msg_reply(&msg, &reply); break; +#ifndef MODULE_GNRC_IPV6_NIB #ifdef MODULE_GNRC_NDP case GNRC_NDP_MSG_RTR_TIMEOUT: DEBUG("ipv6: Router timeout received\n"); @@ -361,6 +366,26 @@ static void *_event_loop(void *args) &(nc_entry->ipv6_addr), false); break; #endif +#else /* MODULE_GNRC_IPV6_NIB */ + case GNRC_IPV6_NIB_SND_UC_NS: + case GNRC_IPV6_NIB_SND_MC_NS: + case GNRC_IPV6_NIB_SND_NA: + case GNRC_IPV6_NIB_SEARCH_RTR: + case GNRC_IPV6_NIB_RECONFIRM_RTR: + case GNRC_IPV6_NIB_REPLY_RS: + case GNRC_IPV6_NIB_SND_MC_RA: + case GNRC_IPV6_NIB_REACH_TIMEOUT: + case GNRC_IPV6_NIB_DELAY_TIMEOUT: + case GNRC_IPV6_NIB_ADDR_REG_TIMEOUT: + case GNRC_IPV6_NIB_6LO_CTX_TIMEOUT: + case GNRC_IPV6_NIB_ABR_TIMEOUT: + case GNRC_IPV6_NIB_PFX_TIMEOUT: + case GNRC_IPV6_NIB_RTR_TIMEOUT: + case GNRC_IPV6_NIB_RECALC_REACH_TIME: + DEBUG("ipv6: NIB timer event received\n"); + gnrc_ipv6_nib_handle_timer_event(msg.content.ptr, msg.type); + break; +#endif /* MODULE_GNRC_IPV6_NIB */ default: break; } @@ -624,6 +649,7 @@ static void _send_multicast(kernel_pid_t iface, gnrc_pktsnip_t *pkt, #endif /* GNRC_NETIF_NUMOF */ } +#ifndef MODULE_GNRC_IPV6_NIB static inline kernel_pid_t _next_hop_l2addr(uint8_t *l2addr, uint8_t *l2addr_len, kernel_pid_t iface, ipv6_addr_t *dst, gnrc_pktsnip_t *pkt) @@ -653,6 +679,7 @@ static inline kernel_pid_t _next_hop_l2addr(uint8_t *l2addr, uint8_t *l2addr_len #endif return found_iface; } +#endif /* MODULE_GNRC_IPV6_NIB */ static void _send(gnrc_pktsnip_t *pkt, bool prep_hdr) { @@ -744,6 +771,7 @@ static void _send(gnrc_pktsnip_t *pkt, bool prep_hdr) } } else { +#ifndef MODULE_GNRC_IPV6_NIB uint8_t l2addr_len = GNRC_IPV6_NC_L2_ADDR_MAX; uint8_t l2addr[l2addr_len]; @@ -764,6 +792,26 @@ static void _send(gnrc_pktsnip_t *pkt, bool prep_hdr) } _send_unicast(iface, l2addr, l2addr_len, pkt); +#else /* MODULE_GNRC_IPV6_NIB */ + gnrc_ipv6_nib_nc_t nce; + + if (gnrc_ipv6_nib_get_next_hop_l2addr(&hdr->dst, iface, pkt, + &nce) < 0) { + /* packet is released by NIB */ + return; + } + + if (prep_hdr) { + if (_fill_ipv6_hdr(iface, ipv6, payload) < 0) { + /* error on filling up header */ + gnrc_pktbuf_release(pkt); + return; + } + } + + _send_unicast(gnrc_ipv6_nib_nc_get_iface(&nce), nce.l2addr, + nce.l2addr_len, pkt); +#endif /* MODULE_GNRC_IPV6_NIB */ } } diff --git a/sys/net/gnrc/network_layer/ipv6/netif/gnrc_ipv6_netif.c b/sys/net/gnrc/network_layer/ipv6/netif/gnrc_ipv6_netif.c index b9bf3934d451b7abbc3c0eda6fe550be930fbbef..8d71d9092e0284b681f9ac1641793181eb60d5a9 100644 --- a/sys/net/gnrc/network_layer/ipv6/netif/gnrc_ipv6_netif.c +++ b/sys/net/gnrc/network_layer/ipv6/netif/gnrc_ipv6_netif.c @@ -25,6 +25,9 @@ #include "net/eui64.h" #include "net/ipv6/addr.h" +#ifdef MODULE_GNRC_IPV6_NIB +#include "net/gnrc/ipv6/nib.h" +#endif #include "net/gnrc/ndp.h" #include "net/gnrc/netapi.h" #include "net/gnrc/netif.h" @@ -173,9 +176,11 @@ static void _ipv6_netif_remove(gnrc_ipv6_netif_t *entry) return; } +#ifndef MODULE_GNRC_IPV6_NIB #ifdef MODULE_GNRC_NDP gnrc_ndp_netif_remove(entry); #endif +#endif /* MODULE_GNRC_IPV6_NIB */ mutex_lock(&entry->mutex); xtimer_remove(&entry->rtr_sol_timer); @@ -235,9 +240,13 @@ void gnrc_ipv6_netif_add(kernel_pid_t pid) mutex_unlock(&free_entry->mutex); +#ifndef MODULE_GNRC_IPV6_NIB #ifdef MODULE_GNRC_NDP gnrc_ndp_netif_add(free_entry); #endif +#else /* MODULE_GNRC_IPV6_NIB */ + gnrc_ipv6_nib_init_iface(pid); +#endif /* MODULE_GNRC_IPV6_NIB */ DEBUG(" * pid = %" PRIkernel_pid " ", free_entry->pid); DEBUG("cur_hl = %d ", free_entry->cur_hl); 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 0000000000000000000000000000000000000000..8f0908bdde84e60251cd77c87d518ec528da4537 --- /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 0000000000000000000000000000000000000000..6967c3a8d91d9cfd2f15f29f91d4af0c9e048b07 --- /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 0000000000000000000000000000000000000000..8a7614f3709c3eee09fd6e2f80daf43fb89c9cf0 --- /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 0000000000000000000000000000000000000000..0fc0049e58f1af0d673595ea121d055c854da3d7 --- /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 0000000000000000000000000000000000000000..c678f3cecb4fe01be5c866bf6fac48c1073aef39 --- /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 0000000000000000000000000000000000000000..44b681273d0cc590e8e6192e0eaa23f31781878e --- /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 129c60b61783410e678c2d443d4ead779c607920..565bb2352fce3e9e16c462faf8e96e8ebc73d497 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 bd6eb009f1b998b7d9727e9dff10898e9a926da0..24e39f4234472996486279eb78e170e1525f27bd 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 0000000000000000000000000000000000000000..b2bcf0930b26088810059e251d527dc3b1393c3d --- /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/gnrc_ipv6_nib/Makefile b/tests/gnrc_ipv6_nib/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..317243122f29ad2340f2bc287d77988184694d6a --- /dev/null +++ b/tests/gnrc_ipv6_nib/Makefile @@ -0,0 +1,21 @@ +# name of your application +APPLICATION = gnrc_ipv6_nib +include ../Makefile.tests_common + +BOARD_INSUFFICIENT_MEMORY := chronos nucleo32-f031 nucleo32-f042 + +USEMODULE += gnrc_ipv6 +USEMODULE += gnrc_ipv6_nib +USEMODULE += embunit + +CFLAGS += -DDEVELHELP +CFLAGS += -DGNRC_NETTYPE_NDP2=GNRC_NETTYPE_TEST +CFLAGS += -DGNRC_PKTBUF_SIZE=512 +CFLAGS += -DTEST_SUITES + +include $(RIOTBASE)/Makefile.include + +test: +# `testrunner` calls `make term` recursively, results in duplicated `TERMFLAGS`. +# So clears `TERMFLAGS` before run. + TERMFLAGS= tests/01-run.py diff --git a/tests/gnrc_ipv6_nib/common.h b/tests/gnrc_ipv6_nib/common.h new file mode 100644 index 0000000000000000000000000000000000000000..97720c85b90a7beba46e62aa286d4ed94474b441 --- /dev/null +++ b/tests/gnrc_ipv6_nib/common.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +/** + * @defgroup tests_gnrc_ipv6_nib Common header for GNRC's NIB tests + * @ingroup tests + * @brief Common definitions for GNRC's NIB tests + * @{ + * + * @file + * + * @author Martine Lenders <m.lenders@fu-berlin.de> + */ +#ifndef COMMON_H +#define COMMON_H + +#include <stdio.h> + +#include "net/gnrc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define _CALL(fn) _common_set_up(); _set_up(); puts("Calling " # fn); fn() + +extern kernel_pid_t _mock_netif_pid; + +void _tests_init(void); +int _mock_netif_get(gnrc_netapi_opt_t *opt); +void _common_set_up(void); + + +#ifdef __cplusplus +} +#endif + +#endif /* COMMON_H */ +/** @} */ diff --git a/tests/gnrc_ipv6_nib/main.c b/tests/gnrc_ipv6_nib/main.c new file mode 100644 index 0000000000000000000000000000000000000000..a409571ecc323ccd1904c560f461de18d5155388 --- /dev/null +++ b/tests/gnrc_ipv6_nib/main.c @@ -0,0 +1,792 @@ +/* + * 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 tests + * @{ + * + * @file + * @brief Tests default configuration of GNRC's Network Information Base + * + * @author Martine Lenders <m.lenders@fu-berlin.de> + * + * @} + */ + +#include <errno.h> +#include <stdio.h> + +#include "cib.h" +#include "common.h" +#include "embUnit.h" +#include "embUnit/embUnit.h" +#include "net/ethernet.h" +#include "net/gnrc.h" +#include "net/gnrc/ipv6/nib.h" +#include "net/gnrc/ipv6/nib/nc.h" +#include "net/ndp.h" +#include "sched.h" + +#define _BUFFER_SIZE (128) +#define _LL0 (0xce) +#define _LL1 (0xab) +#define _LL2 (0xfe) +#define _LL3 (0xad) +#define _LL4 (0xf7) +#define _LL5 (0x26) + +static const uint8_t _loc_l2[] = { _LL0, _LL1, _LL2, _LL3, _LL4, _LL5 }; +static const ipv6_addr_t _loc_ll = { { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + _LL0 ^ 2, _LL1, _LL2, 0xff, 0xfe, _LL3, _LL4, _LL5 + } }; +static const ipv6_addr_t _loc_sol_nodes = { { + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xff, _LL3, _LL4, _LL5 + } }; +#define _loc_iid _loc_ll.u64[1].u8 +static const uint8_t _rem_l2[] = { _LL0, _LL1, _LL2, _LL3, _LL4, _LL5 + 1 }; +static const ipv6_addr_t _rem_ll = { { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + _LL0 ^ 2, _LL1, _LL2, 0xff, 0xfe, _LL3, _LL4, _LL5 + 1 + } }; +#define _rem_iid _rem_ll.u64[1].u8 +static uint8_t _buffer[_BUFFER_SIZE]; +static ipv6_hdr_t *ipv6 = (ipv6_hdr_t *)&_buffer[0]; +static icmpv6_hdr_t *icmpv6 = (icmpv6_hdr_t *)&_buffer[sizeof(ipv6_hdr_t)]; + +static inline size_t ceil8(size_t size); + +static void _set_up(void) +{ + _common_set_up(); + memset(_buffer, 0, sizeof(_buffer)); + gnrc_pktbuf_init(); + /* remove messages */ + while (msg_avail()) { + msg_t msg; + msg_receive(&msg); + } +} + +static void test_get_next_hop_l2addr__link_local_EHOSTUNREACH(kernel_pid_t iface) +{ + msg_t msg; + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + gnrc_pktsnip_t *pkt; + + TEST_ASSERT_EQUAL_INT(-EHOSTUNREACH, + gnrc_ipv6_nib_get_next_hop_l2addr(&_rem_ll, iface, + NULL, &nce)); + if (iface != KERNEL_PID_UNDEF) { + ndp_nbr_sol_t *nbr_sol; + bool contains_sl2ao = false; + + TEST_ASSERT_MESSAGE(gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "Expected neighbor cache entry"); + TEST_ASSERT_MESSAGE(ipv6_addr_equal(&_rem_ll, &nce.ipv6), + "_rem_ll != nce->ipv6"); + TEST_ASSERT_EQUAL_INT(0, nce.l2addr_len); + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE, + gnrc_ipv6_nib_nc_get_nud_state(&nce)); + TEST_ASSERT(!gnrc_ipv6_nib_nc_is_router(&nce)); + TEST_ASSERT_EQUAL_INT(iface, gnrc_ipv6_nib_nc_get_iface(&nce)); + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_AR_STATE_GC, + gnrc_ipv6_nib_nc_get_ar_state(&nce)); + TEST_ASSERT_EQUAL_INT(1, msg_avail()); + msg_receive(&msg); + TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type); + pkt = msg.content.ptr; + TEST_ASSERT_NOT_NULL(pkt->next); + TEST_ASSERT_NOT_NULL(pkt->next->next); + TEST_ASSERT_EQUAL_INT(sizeof(ndp_nbr_sol_t), pkt->next->next->size); + nbr_sol = pkt->next->next->data; + TEST_ASSERT_EQUAL_INT(ICMPV6_NBR_SOL, nbr_sol->type); + TEST_ASSERT_NOT_NULL(pkt->next->next->next); + for (gnrc_pktsnip_t *opt_snip = pkt->next->next->next; opt_snip != NULL; + opt_snip = opt_snip->next) { + ndp_opt_t *opt = opt_snip->data; + if (opt->type == NDP_OPT_SL2A) { + contains_sl2ao = true; + TEST_ASSERT_EQUAL_INT(1U, opt->len); + TEST_ASSERT_MESSAGE(memcmp(&_loc_l2, opt + 1, + sizeof(_loc_l2)) == 0, + "src_l2 != pkt->l2"); + } + } + TEST_ASSERT_MESSAGE(contains_sl2ao, "Sent NS does not contain SL2AO"); + gnrc_pktbuf_release(pkt); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + } +} + +static void test_get_next_hop_l2addr__link_local_EHOSTUNREACH_no_iface(void) +{ + test_get_next_hop_l2addr__link_local_EHOSTUNREACH(KERNEL_PID_UNDEF); +} + +static void test_get_next_hop_l2addr__link_local_EHOSTUNREACH_iface(void) +{ + test_get_next_hop_l2addr__link_local_EHOSTUNREACH(_mock_netif_pid); +} + +static void test_get_next_hop_l2addr__link_local_static_conf(void) +{ + gnrc_ipv6_nib_nc_t nce; + + TEST_ASSERT_EQUAL_INT(0, gnrc_ipv6_nib_nc_set(&_rem_ll, _mock_netif_pid, + _rem_l2, sizeof(_rem_l2))); + TEST_ASSERT_EQUAL_INT(0, gnrc_ipv6_nib_get_next_hop_l2addr(&_rem_ll, + _mock_netif_pid, + NULL, &nce)); + TEST_ASSERT_MESSAGE((memcmp(&_rem_ll, &nce.ipv6, sizeof(_rem_ll)) == 0), + "_rem_ll != nce.ipv6"); + TEST_ASSERT_EQUAL_INT(sizeof(_rem_l2), nce.l2addr_len); + TEST_ASSERT_MESSAGE((memcmp(&_rem_l2, &nce.l2addr, nce.l2addr_len) == 0), + "_rem_l2 != nce.l2addr"); + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNMANAGED, + gnrc_ipv6_nib_nc_get_nud_state(&nce)); + TEST_ASSERT_EQUAL_INT(_mock_netif_pid, gnrc_ipv6_nib_nc_get_iface(&nce)); + TEST_ASSERT(!gnrc_ipv6_nib_nc_is_router(&nce)); + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_AR_STATE_MANUAL, + gnrc_ipv6_nib_nc_get_ar_state(&nce)); + TEST_ASSERT_EQUAL_INT(0, msg_avail()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +void _simulate_ndp_handshake(const ipv6_addr_t *src, const ipv6_addr_t *dst, + uint8_t adv_flags) +{ + msg_t msg; + gnrc_ipv6_nib_nc_t nce; + ndp_nbr_adv_t *nbr_adv = (ndp_nbr_adv_t *)icmpv6; + ndp_opt_t *tl2ao = (ndp_opt_t *)&_buffer[sizeof(ipv6_hdr_t) + + sizeof(ndp_nbr_adv_t)]; + + /* trigger sending of neighbor discovery */ + TEST_ASSERT_EQUAL_INT(-EHOSTUNREACH, + gnrc_ipv6_nib_get_next_hop_l2addr(dst, + _mock_netif_pid, + NULL, &nce)); + TEST_ASSERT_EQUAL_INT(1, msg_avail()); + /* clear message queue */ + msg_receive(&msg); + TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type); + gnrc_pktbuf_release(msg.content.ptr); + /* generate neighbor advertisement */ + ipv6_hdr_set_version(ipv6); + ipv6->hl = 255U; + /* this simulates a reply, so dst and src need to be switched */ + memcpy(&ipv6->src, dst, sizeof(ipv6->src)); + memcpy(&ipv6->dst, src, sizeof(ipv6->dst)); + nbr_adv->type = ICMPV6_NBR_ADV; + /* checksum isn't checked by gnrc_ipv6_nib_handle_pkt() */ + nbr_adv->flags = adv_flags; + memcpy(&nbr_adv->tgt, dst, sizeof(nbr_adv->tgt)); + tl2ao->type = NDP_OPT_TL2A; + tl2ao->len = 1; + memcpy(tl2ao + 1, _rem_l2, sizeof(_rem_l2)); + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, (icmpv6_hdr_t *)nbr_adv, + sizeof(ndp_nbr_adv_t) + 8U); +} + +static void test_get_next_hop_l2addr__link_local_after_handshake(uint8_t adv_flags) +{ + gnrc_ipv6_nib_nc_t nce; + + _simulate_ndp_handshake(&_loc_ll, &_rem_ll, adv_flags); + TEST_ASSERT_EQUAL_INT(0, gnrc_ipv6_nib_get_next_hop_l2addr(&_rem_ll, + _mock_netif_pid, + NULL, &nce)); + TEST_ASSERT_MESSAGE((memcmp(&_rem_ll, &nce.ipv6, sizeof(_rem_ll)) == 0), + "_rem_ll != nce.ipv6"); + TEST_ASSERT_EQUAL_INT(sizeof(_rem_l2), nce.l2addr_len); + TEST_ASSERT_MESSAGE((memcmp(&_rem_l2, &nce.l2addr, nce.l2addr_len) == 0), + "_rem_l2 != nce.l2addr"); + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_NUD_STATE_REACHABLE, + gnrc_ipv6_nib_nc_get_nud_state(&nce)); + TEST_ASSERT_EQUAL_INT(_mock_netif_pid, gnrc_ipv6_nib_nc_get_iface(&nce)); + if (adv_flags & NDP_NBR_ADV_FLAGS_R) { + TEST_ASSERT(gnrc_ipv6_nib_nc_is_router(&nce)); + } + else { + TEST_ASSERT(!gnrc_ipv6_nib_nc_is_router(&nce)); + } + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_AR_STATE_GC, + gnrc_ipv6_nib_nc_get_ar_state(&nce)); + TEST_ASSERT_EQUAL_INT(0, msg_avail()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_get_next_hop_l2addr__link_local_after_handshake_iface(void) +{ + test_get_next_hop_l2addr__link_local_after_handshake(NDP_NBR_ADV_FLAGS_S); +} + +static void test_get_next_hop_l2addr__link_local_after_handshake_iface_router(void) +{ + test_get_next_hop_l2addr__link_local_after_handshake(NDP_NBR_ADV_FLAGS_S | + NDP_NBR_ADV_FLAGS_R); +} + +static void test_get_next_hop_l2addr__link_local_after_handshake_no_iface(void) +{ + gnrc_ipv6_nib_nc_t nce; + + _simulate_ndp_handshake(&_loc_ll, &_rem_ll, NDP_NBR_ADV_FLAGS_S); + TEST_ASSERT_EQUAL_INT(-EHOSTUNREACH, + gnrc_ipv6_nib_get_next_hop_l2addr(&_rem_ll, + KERNEL_PID_UNDEF, + NULL, &nce)); +} + +static void test_handle_pkt__unknown_type(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + + ipv6_hdr_set_version(ipv6); + ipv6->hl = 255U; + memcpy(&ipv6->src, &_loc_ll, sizeof(ipv6->src)); + memcpy(&ipv6->dst, &_rem_ll, sizeof(ipv6->dst)); + icmpv6->type = ICMPV6_ECHO_REQ; + icmpv6->code = 0; + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, + sizeof(icmpv6_hdr_t)); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ +} + +static size_t _set_nbr_sol(const ipv6_addr_t *ipv6_src, + const ipv6_addr_t *ipv6_dst, + uint8_t ipv6_hl, uint8_t nbr_sol_code, + const ipv6_addr_t *nbr_sol_tgt, + const uint8_t *sl2ao_addr, size_t sl2ao_addr_len) +{ + size_t icmpv6_len = sizeof(ndp_nbr_sol_t); + ndp_nbr_sol_t *nbr_sol = (ndp_nbr_sol_t *)icmpv6; + + ipv6_hdr_set_version(ipv6); + ipv6->hl = ipv6_hl; + memcpy(&ipv6->src, ipv6_src, sizeof(ipv6->src)); + memcpy(&ipv6->dst, ipv6_dst, sizeof(ipv6->dst)); + nbr_sol->type = ICMPV6_NBR_SOL; + nbr_sol->code = nbr_sol_code; + memcpy(&nbr_sol->tgt, nbr_sol_tgt, sizeof(nbr_sol->tgt)); + + if ((sl2ao_addr != NULL) && (sl2ao_addr_len > 0)) { + ndp_opt_t *sl2ao = (ndp_opt_t *)&_buffer[sizeof(ipv6_hdr_t) + + sizeof(ndp_nbr_sol_t)]; + + sl2ao->type = NDP_OPT_SL2A; + sl2ao->len = ceil8(sizeof(ndp_opt_t) + sl2ao_addr_len) / 8; + memcpy(sl2ao + 1, sl2ao_addr, sl2ao_addr_len); + icmpv6_len += ceil8(sizeof(ndp_opt_t) + sl2ao_addr_len); + } + + return icmpv6_len; +} + +static void test_handle_pkt__nbr_sol__invalid_hl(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_sol_nodes, 194U, 0U, + &_loc_ll, _rem_l2, sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_code(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_sol_nodes, 255U, 201U, + &_loc_ll, _rem_l2, sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_icmpv6_len(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + + _set_nbr_sol(&_rem_ll, &_loc_sol_nodes, 255U, 0U, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, + sizeof(ndp_nbr_sol_t) - 1); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_tgt(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_sol_nodes, 255U, 0U, + &ipv6_addr_all_routers_site_local, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_opt_len(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_sol_nodes, 255U, 0U, + &_loc_ll, _rem_l2, sizeof(_rem_l2)); + ndp_opt_t *opt = (ndp_opt_t *)&_buffer[icmpv6_len]; + + opt->type = NDP_OPT_SL2A; + opt->len = 0U; + icmpv6_len += sizeof(ndp_opt_t); + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_dst(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&ipv6_addr_unspecified, &_loc_ll, 255U, 0U, + &_loc_ll, NULL, 0); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_sl2ao(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&ipv6_addr_unspecified, &_loc_sol_nodes, + 255U, 0U, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__tgt_not_assigned(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_sol_nodes, + 255U, 0U, &_rem_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_pkt_is_nbr_adv(gnrc_pktsnip_t *pkt, const ipv6_addr_t *dst, + const ipv6_addr_t *tgt, + const uint8_t *tgt_l2addr, + size_t tgt_l2addr_len) +{ + gnrc_netif_hdr_t *netif_hdr; + ipv6_hdr_t *ipv6_hdr; + ndp_nbr_adv_t *nbr_adv; + ndp_opt_t *tl2ao; + + /* first snip is a netif header to _mock_netif_pid */ + TEST_ASSERT_NOT_NULL(pkt); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_NETIF, pkt->type); + TEST_ASSERT(sizeof(gnrc_netif_hdr_t) <= pkt->size); + netif_hdr = pkt->data; + TEST_ASSERT_EQUAL_INT(_mock_netif_pid, netif_hdr->if_pid); + /* second snip is an IPv6 header to dst */ + TEST_ASSERT_NOT_NULL(pkt->next); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_IPV6, pkt->next->type); + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_hdr_t), pkt->next->size); + ipv6_hdr = pkt->next->data; + TEST_ASSERT(!ipv6_addr_is_multicast(&ipv6_hdr->dst)); + TEST_ASSERT_MESSAGE(ipv6_addr_equal(dst, &ipv6_hdr->dst), + "dst != ipv6_hdr->dst"); + TEST_ASSERT_EQUAL_INT(255, ipv6_hdr->hl); + /* third snip is a valid solicited neighbor advertisement to tgt */ + TEST_ASSERT_NOT_NULL(pkt->next->next); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_ICMPV6, pkt->next->next->type); + TEST_ASSERT_EQUAL_INT(sizeof(ndp_nbr_adv_t), pkt->next->next->size); + nbr_adv = pkt->next->next->data; + TEST_ASSERT_EQUAL_INT(ICMPV6_NBR_ADV, nbr_adv->type); + TEST_ASSERT_EQUAL_INT(0, nbr_adv->code); + TEST_ASSERT(!ipv6_addr_is_multicast(&nbr_adv->tgt)); + TEST_ASSERT_MESSAGE(ipv6_addr_equal(tgt, &nbr_adv->tgt), + "tgt != nbr_adv->tgt"); + TEST_ASSERT(nbr_adv->flags & NDP_NBR_ADV_FLAGS_S); + /* fourth snip is a TL2AO for tgt_l2addr */ + TEST_ASSERT_NOT_NULL(pkt->next->next->next); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_UNDEF, pkt->next->next->next->type); + TEST_ASSERT_EQUAL_INT(ceil8(sizeof(ndp_opt_t) + tgt_l2addr_len), + pkt->next->next->next->size); + tl2ao = pkt->next->next->next->data; + TEST_ASSERT_EQUAL_INT(NDP_OPT_TL2A, tl2ao->type); + TEST_ASSERT_EQUAL_INT(1, tl2ao->len); + TEST_ASSERT_MESSAGE(memcmp(tl2ao + 1, tgt_l2addr, tgt_l2addr_len) == 0, + "tl2ao.l2addr != tgt_l2addr"); + /* no further options */ + TEST_ASSERT_NULL(pkt->next->next->next->next); +} + +static void test_handle_pkt__nbr_sol__ll_src(unsigned exp_nud_state, + unsigned exp_ar_state) +{ + msg_t msg; + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_sol_nodes, + 255U, 0U, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "Expected neighbor cache entry"); + TEST_ASSERT_MESSAGE(ipv6_addr_equal(&_rem_ll, &nce.ipv6), + "_rem_ll != nce->ipv6"); + TEST_ASSERT_EQUAL_INT(sizeof(_rem_l2), nce.l2addr_len); + TEST_ASSERT_MESSAGE(memcmp(_rem_l2, nce.l2addr, nce.l2addr_len) == 0, + "_rem_l2 != nce.l2addr"); + TEST_ASSERT_EQUAL_INT(exp_nud_state, gnrc_ipv6_nib_nc_get_nud_state(&nce)); + TEST_ASSERT(!gnrc_ipv6_nib_nc_is_router(&nce)); + TEST_ASSERT_EQUAL_INT(_mock_netif_pid, gnrc_ipv6_nib_nc_get_iface(&nce)); + TEST_ASSERT_EQUAL_INT(exp_ar_state, gnrc_ipv6_nib_nc_get_ar_state(&nce)); + TEST_ASSERT_EQUAL_INT(1, msg_avail()); + msg_receive(&msg); + TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type); + test_pkt_is_nbr_adv(msg.content.ptr, &_rem_ll, &_loc_ll, _loc_l2, + sizeof(_loc_l2)); + gnrc_pktbuf_release(msg.content.ptr); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_handle_pkt__nbr_sol__ll_src_empty_nc(void) +{ + test_handle_pkt__nbr_sol__ll_src(GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE, + GNRC_IPV6_NIB_NC_INFO_AR_STATE_GC); +} + +static void test_handle_pkt__nbr_sol__ll_src_unmanaged_nce(void) +{ + test_get_next_hop_l2addr__link_local_static_conf(); + /* unmanaged entry stays unmanaged */ + test_handle_pkt__nbr_sol__ll_src(GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNMANAGED, + GNRC_IPV6_NIB_NC_INFO_AR_STATE_MANUAL); +} + +static void test_handle_pkt__nbr_sol__ll_src_no_sl2ao(void) +{ + msg_t msg; + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_sol_nodes, + 255U, 0U, &_loc_ll, NULL, 0); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + TEST_ASSERT_EQUAL_INT(1, msg_avail()); + msg_receive(&msg); + TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type); + test_pkt_is_nbr_adv(msg.content.ptr, &_rem_ll, &_loc_ll, _loc_l2, + sizeof(_loc_l2)); + gnrc_pktbuf_release(msg.content.ptr); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static size_t _set_nbr_adv(const ipv6_addr_t *ipv6_src, + const ipv6_addr_t *ipv6_dst, + uint8_t ipv6_hl, uint8_t nbr_adv_code, + uint8_t nbr_adv_flags, + const ipv6_addr_t *nbr_adv_tgt, + const uint8_t *tl2ao_addr, size_t tl2ao_addr_len) +{ + size_t icmpv6_len = sizeof(ndp_nbr_adv_t); + ndp_nbr_adv_t *nbr_adv = (ndp_nbr_adv_t *)icmpv6; + + ipv6_hdr_set_version(ipv6); + ipv6->hl = ipv6_hl; + memcpy(&ipv6->src, ipv6_src, sizeof(ipv6->src)); + memcpy(&ipv6->dst, ipv6_dst, sizeof(ipv6->dst)); + nbr_adv->type = ICMPV6_NBR_ADV; + nbr_adv->code = nbr_adv_code; + nbr_adv->flags = nbr_adv_flags; + memcpy(&nbr_adv->tgt, nbr_adv_tgt, sizeof(nbr_adv->tgt)); + + if ((tl2ao_addr != NULL) || (tl2ao_addr_len > 0)) { + ndp_opt_t *tl2ao = (ndp_opt_t *)&_buffer[sizeof(ipv6_hdr_t) + + sizeof(ndp_nbr_adv_t)]; + + tl2ao->type = NDP_OPT_TL2A; + tl2ao->len = ceil8(sizeof(ndp_opt_t) + tl2ao_addr_len) / 8; + memcpy(tl2ao + 1, tl2ao_addr, tl2ao_addr_len); + icmpv6_len += ceil8(sizeof(ndp_opt_t) + tl2ao_addr_len); + } + + return icmpv6_len; +} + +static void test_handle_pkt__nbr_adv__invalid_hl(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_ll, 194U, 0U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_code(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_ll, 255U, 201U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_icmpv6_len(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + + _set_nbr_adv(&_rem_ll, &_loc_ll, 255U, 0U, NDP_NBR_ADV_FLAGS_S, + &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, + sizeof(ndp_nbr_adv_t) - 1); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_tgt(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_ll, 255U, 0U, + NDP_NBR_ADV_FLAGS_S, + &ipv6_addr_all_routers_site_local, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_flags(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &ipv6_addr_all_nodes_link_local, + 255U, 0U, NDP_NBR_ADV_FLAGS_S, &_loc_ll, + NULL, 0); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_opt_len(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_ll, 255U, 0U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + ndp_opt_t *opt = (ndp_opt_t *)&_buffer[icmpv6_len]; + + opt->type = NDP_OPT_SL2A; + opt->len = 0U; + icmpv6_len += sizeof(ndp_opt_t); + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__unspecified_src(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&ipv6_addr_unspecified, &_loc_ll, 255U, 0U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__unsolicited(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_sol_nodes, 255U, 0U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, + _rem_l2, sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static Test *tests_gnrc_ipv6_nib(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + /* gnrc_ipv6_nib_init() and gnrc_ipv6_nib_init_iface() "tested" in + * set-up (otherwise the following tests wouldn't work) */ + /* TODO: ENETUNREACH when non-link-local communication is implemented */ + new_TestFixture(test_get_next_hop_l2addr__link_local_EHOSTUNREACH_no_iface), + new_TestFixture(test_get_next_hop_l2addr__link_local_EHOSTUNREACH_iface), + new_TestFixture(test_get_next_hop_l2addr__link_local_static_conf), + new_TestFixture(test_get_next_hop_l2addr__link_local_after_handshake_iface), + new_TestFixture(test_get_next_hop_l2addr__link_local_after_handshake_iface_router), + new_TestFixture(test_get_next_hop_l2addr__link_local_after_handshake_no_iface), + new_TestFixture(test_handle_pkt__unknown_type), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_hl), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_code), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_icmpv6_len), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_tgt), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_opt_len), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_dst), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_sl2ao), + new_TestFixture(test_handle_pkt__nbr_sol__tgt_not_assigned), + /* TODO add tests for unspecified source (involves SLAAC) */ + new_TestFixture(test_handle_pkt__nbr_sol__ll_src_empty_nc), + new_TestFixture(test_handle_pkt__nbr_sol__ll_src_unmanaged_nce), + new_TestFixture(test_handle_pkt__nbr_sol__ll_src_no_sl2ao), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_hl), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_code), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_icmpv6_len), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_tgt), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_flags), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_opt_len), + new_TestFixture(test_handle_pkt__nbr_adv__unspecified_src), + new_TestFixture(test_handle_pkt__nbr_adv__unsolicited), + /* solicited tested in get_next_hop_l2addr */ + /* gnrc_ipv6_nib_handle_timer_event not testable in this context since + * we do not have access to the (internally defined) contexts required + * for it */ + }; + + EMB_UNIT_TESTCALLER(tests, _set_up, NULL, fixtures); + + return (Test *)&tests; +} + +int main(void) +{ + _tests_init(); + + TESTS_START(); + TESTS_RUN(tests_gnrc_ipv6_nib()); + TESTS_END(); + + return 0; +} + +int _mock_netif_get(gnrc_netapi_opt_t *opt) +{ + switch (opt->opt) { + case NETOPT_ADDRESS: + if (opt->data_len < sizeof(_loc_l2)) { + return -EOVERFLOW; + } + memcpy(opt->data, _loc_l2, sizeof(_loc_l2)); + return sizeof(_loc_l2); + case NETOPT_SRC_LEN: { + uint16_t *val = opt->data; + if (opt->data_len != sizeof(uint16_t)) { + return -EOVERFLOW; + } + *val = sizeof(_loc_l2); + return sizeof(uint16_t); + } + case NETOPT_IPV6_IID: + if (opt->data_len < sizeof(_loc_iid)) { + return -EOVERFLOW; + } + memcpy(opt->data, _loc_iid, sizeof(_loc_iid)); + return sizeof(_loc_iid); + case NETOPT_IS_WIRED: + return 1; + case NETOPT_MAX_PACKET_SIZE: { + uint16_t *val = opt->data; + if (opt->data_len != sizeof(uint16_t)) { + return -EOVERFLOW; + } + *val = ETHERNET_DATA_LEN; + return sizeof(uint16_t); + } + default: + return -ENOTSUP; + } +} + +static inline size_t ceil8(size_t size) +{ + if (size % 8) { + return ((size / 8) + 1) * 8; + } + else { + return size; + } +} diff --git a/tests/gnrc_ipv6_nib/mockup_netif.c b/tests/gnrc_ipv6_nib/mockup_netif.c new file mode 100644 index 0000000000000000000000000000000000000000..45f4074ddd4f19896b9bbba3c4e44d32f88df374 --- /dev/null +++ b/tests/gnrc_ipv6_nib/mockup_netif.c @@ -0,0 +1,81 @@ +/* + * 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 "common.h" +#include "msg.h" +#include "net/gnrc.h" +#include "net/gnrc/ipv6/nib.h" +#include "net/gnrc/ipv6/netif.h" +#include "net/gnrc/netdev.h" +#include "sched.h" +#include "thread.h" + +#define _MSG_QUEUE_SIZE (2) + +kernel_pid_t _mock_netif_pid = KERNEL_PID_UNDEF; + +static char _mock_netif_stack[THREAD_STACKSIZE_DEFAULT]; +static gnrc_netreg_entry_t dumper; +static msg_t _main_msg_queue[_MSG_QUEUE_SIZE]; +static msg_t _mock_netif_msg_queue[_MSG_QUEUE_SIZE]; + +static void *_mock_netif_thread(void *args) +{ + msg_t msg, reply = { .type = GNRC_NETAPI_MSG_TYPE_ACK }; + + (void)args; + msg_init_queue(_mock_netif_msg_queue, _MSG_QUEUE_SIZE); + while (1) { + msg_receive(&msg); + switch (msg.type) { + case GNRC_NETAPI_MSG_TYPE_GET: + reply.content.value = (uint32_t)_mock_netif_get(msg.content.ptr); + break; + case GNRC_NETAPI_MSG_TYPE_SET: + reply.content.value = (uint32_t)(-ENOTSUP); + break; + case GNRC_NETAPI_MSG_TYPE_SND: + case GNRC_NETAPI_MSG_TYPE_RCV: + gnrc_pktbuf_release(msg.content.ptr); + } + msg_reply(&msg, &reply); + } + return NULL; +} + +void _common_set_up(void) +{ + gnrc_ipv6_nib_init(); + gnrc_ipv6_nib_init_iface(_mock_netif_pid); +} + +void _tests_init(void) +{ + msg_init_queue(_main_msg_queue, _MSG_QUEUE_SIZE); + _mock_netif_pid = thread_create(_mock_netif_stack, + sizeof(_mock_netif_stack), + GNRC_NETDEV_MAC_PRIO, + THREAD_CREATE_STACKTEST, + _mock_netif_thread, NULL, "mock_netif"); + assert(_mock_netif_pid > KERNEL_PID_UNDEF); + gnrc_netif_add(_mock_netif_pid); + gnrc_ipv6_netif_init_by_dev(); + thread_yield(); + gnrc_netreg_entry_init_pid(&dumper, GNRC_NETREG_DEMUX_CTX_ALL, + sched_active_pid); + gnrc_netreg_register(GNRC_NETTYPE_NDP2, &dumper); +} + +/** @} */ diff --git a/tests/gnrc_ipv6_nib/tests/01-run.py b/tests/gnrc_ipv6_nib/tests/01-run.py new file mode 100755 index 0000000000000000000000000000000000000000..c12bc5b8ca72fe0dddf3f2b354410e7984771947 --- /dev/null +++ b/tests/gnrc_ipv6_nib/tests/01-run.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de> +# Copyright (C) 2016 Takuo Yonezawa <Yonezawa-T2@mail.dnp.co.jp> +# +# 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. + +import os +import sys + +sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner')) +import testrunner + +def testfunc(child): + child.expect(r"OK \(\d+ tests\)") + +if __name__ == "__main__": + sys.exit(testrunner.run(testfunc, timeout=1)) diff --git a/tests/gnrc_ipv6_nib_6ln/Makefile b/tests/gnrc_ipv6_nib_6ln/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..13839797d67d60680e7d14b0a9aba21961c616e1 --- /dev/null +++ b/tests/gnrc_ipv6_nib_6ln/Makefile @@ -0,0 +1,24 @@ +# name of your application +APPLICATION = gnrc_ipv6_nib_6ln +include ../Makefile.tests_common + +BOARD_INSUFFICIENT_MEMORY := chronos nucleo-f030 nucleo-l053 nucleo32-f031 + nucleo32-l031 nucleo32-f042 stm32f0discovery + +USEMODULE += gnrc_ipv6 +USEMODULE += gnrc_sixlowpan +USEMODULE += gnrc_ipv6_nib_6ln +USEMODULE += embunit +USEMODULE += netopt + +CFLAGS += -DDEVELHELP +CFLAGS += -DGNRC_NETTYPE_NDP2=GNRC_NETTYPE_TEST +CFLAGS += -DGNRC_PKTBUF_SIZE=512 +CFLAGS += -DTEST_SUITES + +include $(RIOTBASE)/Makefile.include + +test: +# `testrunner` calls `make term` recursively, results in duplicated `TERMFLAGS`. +# So clears `TERMFLAGS` before run. + TERMFLAGS= tests/01-run.py diff --git a/tests/gnrc_ipv6_nib_6ln/common.h b/tests/gnrc_ipv6_nib_6ln/common.h new file mode 100644 index 0000000000000000000000000000000000000000..97720c85b90a7beba46e62aa286d4ed94474b441 --- /dev/null +++ b/tests/gnrc_ipv6_nib_6ln/common.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +/** + * @defgroup tests_gnrc_ipv6_nib Common header for GNRC's NIB tests + * @ingroup tests + * @brief Common definitions for GNRC's NIB tests + * @{ + * + * @file + * + * @author Martine Lenders <m.lenders@fu-berlin.de> + */ +#ifndef COMMON_H +#define COMMON_H + +#include <stdio.h> + +#include "net/gnrc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define _CALL(fn) _common_set_up(); _set_up(); puts("Calling " # fn); fn() + +extern kernel_pid_t _mock_netif_pid; + +void _tests_init(void); +int _mock_netif_get(gnrc_netapi_opt_t *opt); +void _common_set_up(void); + + +#ifdef __cplusplus +} +#endif + +#endif /* COMMON_H */ +/** @} */ diff --git a/tests/gnrc_ipv6_nib_6ln/main.c b/tests/gnrc_ipv6_nib_6ln/main.c new file mode 100644 index 0000000000000000000000000000000000000000..dbf14f86aed12a1f479f77037b85a5a6803ec8e4 --- /dev/null +++ b/tests/gnrc_ipv6_nib_6ln/main.c @@ -0,0 +1,668 @@ +/* + * 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 tests + * @{ + * + * @file + * @brief Tests default configuration of GNRC's Network Information Base + * + * @author Martine Lenders <m.lenders@fu-berlin.de> + * + * @} + */ + +#include <errno.h> +#include <stdio.h> + +#include "cib.h" +#include "common.h" +#include "embUnit.h" +#include "embUnit/embUnit.h" +#include "net/ipv6.h" +#include "net/gnrc.h" +#include "net/gnrc/ipv6/nib.h" +#include "net/gnrc/ipv6/nib/nc.h" +#include "net/ndp.h" +#include "sched.h" + +#define _BUFFER_SIZE (128) +#define _LL0 (0xce) +#define _LL1 (0xab) +#define _LL2 (0xfe) +#define _LL3 (0xad) +#define _LL4 (0xf7) +#define _LL5 (0x26) +#define _LL6 (0xef) +#define _LL7 (0xa4) + +static const uint8_t _loc_l2[] = { _LL0, _LL1, _LL2, _LL3, + _LL4, _LL5, _LL6, _LL7 }; +static const ipv6_addr_t _loc_ll = { { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + _LL0 ^ 2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + } }; +#define _loc_iid _loc_ll.u64[1].u8 +static const uint8_t _rem_l2[] = { _LL0, _LL1, _LL2, _LL3, + _LL4, _LL5, _LL6, _LL7 + 1 }; +static const ipv6_addr_t _rem_ll = { { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + _LL0 ^ 2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1 + } }; +#define _rem_iid _rem_ll.u64[1].u8 +static uint8_t _buffer[_BUFFER_SIZE]; +static ipv6_hdr_t *ipv6 = (ipv6_hdr_t *)&_buffer[0]; +static icmpv6_hdr_t *icmpv6 = (icmpv6_hdr_t *)&_buffer[sizeof(ipv6_hdr_t)]; + +static inline size_t ceil8(size_t size); + +static void _set_up(void) +{ + _common_set_up(); + memset(_buffer, 0, sizeof(_buffer)); + gnrc_pktbuf_init(); + /* remove messages */ + while (msg_avail()) { + msg_t msg; + msg_receive(&msg); + } +} + +static void test_get_next_hop_l2addr__link_local_EHOSTUNREACH(void) +{ + gnrc_ipv6_nib_nc_t nce; + + TEST_ASSERT_EQUAL_INT(-EHOSTUNREACH, + gnrc_ipv6_nib_get_next_hop_l2addr(&_rem_ll, + KERNEL_PID_UNDEF, + NULL, &nce)); + TEST_ASSERT_EQUAL_INT(0, msg_avail()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_get_next_hop_l2addr__link_local_static_conf(void) +{ + gnrc_ipv6_nib_nc_t nce; + + TEST_ASSERT_EQUAL_INT(0, gnrc_ipv6_nib_nc_set(&_rem_ll, _mock_netif_pid, + _rem_l2, sizeof(_rem_l2))); + TEST_ASSERT_EQUAL_INT(0, gnrc_ipv6_nib_get_next_hop_l2addr(&_rem_ll, + _mock_netif_pid, + NULL, &nce)); + TEST_ASSERT_MESSAGE((memcmp(&_rem_ll, &nce.ipv6, sizeof(_rem_ll)) == 0), + "_rem_ll != nce.ipv6"); + TEST_ASSERT_EQUAL_INT(sizeof(_rem_l2), nce.l2addr_len); + TEST_ASSERT_MESSAGE((memcmp(&_rem_l2, &nce.l2addr, nce.l2addr_len) == 0), + "_rem_l2 != nce.l2addr"); + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNMANAGED, + gnrc_ipv6_nib_nc_get_nud_state(&nce)); + TEST_ASSERT_EQUAL_INT(_mock_netif_pid, gnrc_ipv6_nib_nc_get_iface(&nce)); + TEST_ASSERT(!gnrc_ipv6_nib_nc_is_router(&nce)); + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_AR_STATE_MANUAL, + gnrc_ipv6_nib_nc_get_ar_state(&nce)); + TEST_ASSERT_EQUAL_INT(0, msg_avail()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_get_next_hop_l2addr__link_local(void) +{ + gnrc_ipv6_nib_nc_t nce; + + TEST_ASSERT_EQUAL_INT(0, gnrc_ipv6_nib_get_next_hop_l2addr(&_rem_ll, + _mock_netif_pid, + NULL, &nce)); + TEST_ASSERT_MESSAGE((memcmp(&_rem_ll, &nce.ipv6, sizeof(_rem_ll)) == 0), + "_rem_ll != nce.ipv6"); + TEST_ASSERT_EQUAL_INT(sizeof(_rem_l2), nce.l2addr_len); + TEST_ASSERT_MESSAGE((memcmp(&_rem_l2, &nce.l2addr, nce.l2addr_len) == 0), + "_rem_l2 != nce.l2addr"); + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_NUD_STATE_REACHABLE, + gnrc_ipv6_nib_nc_get_nud_state(&nce)); + TEST_ASSERT_EQUAL_INT(_mock_netif_pid, gnrc_ipv6_nib_nc_get_iface(&nce)); + TEST_ASSERT(!gnrc_ipv6_nib_nc_is_router(&nce)); + TEST_ASSERT_EQUAL_INT(GNRC_IPV6_NIB_NC_INFO_AR_STATE_REGISTERED, + gnrc_ipv6_nib_nc_get_ar_state(&nce)); + TEST_ASSERT_EQUAL_INT(0, msg_avail()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_handle_pkt__unknown_type(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + + ipv6_hdr_set_version(ipv6); + ipv6->hl = 255U; + memcpy(&ipv6->src, &_loc_ll, sizeof(ipv6->src)); + memcpy(&ipv6->dst, &_rem_ll, sizeof(ipv6->dst)); + icmpv6->type = ICMPV6_ECHO_REQ; + icmpv6->code = 0; + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, + sizeof(icmpv6_hdr_t)); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ +} + +static size_t _set_nbr_sol(const ipv6_addr_t *ipv6_src, + const ipv6_addr_t *ipv6_dst, + uint8_t ipv6_hl, uint8_t nbr_sol_code, + const ipv6_addr_t *nbr_sol_tgt, + const uint8_t *sl2ao_addr, size_t sl2ao_addr_len) +{ + size_t icmpv6_len = sizeof(ndp_nbr_sol_t); + ndp_nbr_sol_t *nbr_sol = (ndp_nbr_sol_t *)icmpv6; + + ipv6_hdr_set_version(ipv6); + ipv6->hl = ipv6_hl; + memcpy(&ipv6->src, ipv6_src, sizeof(ipv6->src)); + memcpy(&ipv6->dst, ipv6_dst, sizeof(ipv6->dst)); + nbr_sol->type = ICMPV6_NBR_SOL; + nbr_sol->code = nbr_sol_code; + memcpy(&nbr_sol->tgt, nbr_sol_tgt, sizeof(nbr_sol->tgt)); + + if ((sl2ao_addr != NULL) && (sl2ao_addr_len > 0)) { + ndp_opt_t *sl2ao = (ndp_opt_t *)&_buffer[sizeof(ipv6_hdr_t) + + sizeof(ndp_nbr_sol_t)]; + + sl2ao->type = NDP_OPT_SL2A; + sl2ao->len = ceil8(sizeof(ndp_opt_t) + sl2ao_addr_len) / 8; + memcpy(sl2ao + 1, sl2ao_addr, sl2ao_addr_len); + icmpv6_len += ceil8(sizeof(ndp_opt_t) + sl2ao_addr_len); + } + + return icmpv6_len; +} + +static void test_handle_pkt__nbr_sol__invalid_hl(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_ll, 194U, 0U, + &_loc_ll, _rem_l2, sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_code(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_ll, 255U, 201U, + &_loc_ll, _rem_l2, sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_icmpv6_len(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + + _set_nbr_sol(&_rem_ll, &_loc_ll, 255U, 0U, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, + sizeof(ndp_nbr_sol_t) - 1); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_tgt(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_ll, 255U, 0U, + &ipv6_addr_all_routers_site_local, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_opt_len(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_ll, 255U, 0U, + &_loc_ll, _rem_l2, sizeof(_rem_l2)); + ndp_opt_t *opt = (ndp_opt_t *)&_buffer[icmpv6_len]; + + opt->type = NDP_OPT_SL2A; + opt->len = 0U; + icmpv6_len += sizeof(ndp_opt_t); + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_dst(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&ipv6_addr_unspecified, &_loc_ll, 255U, 0U, + &_loc_ll, NULL, 0); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__invalid_sl2ao(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&ipv6_addr_unspecified, &_loc_ll, + 255U, 0U, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_sol__tgt_not_assigned(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_ll, + 255U, 0U, &_rem_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_pkt_is_nbr_adv(gnrc_pktsnip_t *pkt, const ipv6_addr_t *dst, + const ipv6_addr_t *tgt) +{ + gnrc_netif_hdr_t *netif_hdr; + ipv6_hdr_t *ipv6_hdr; + ndp_nbr_adv_t *nbr_adv; + + /* first snip is a netif header to _mock_netif_pid */ + TEST_ASSERT_NOT_NULL(pkt); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_NETIF, pkt->type); + TEST_ASSERT(sizeof(gnrc_netif_hdr_t) <= pkt->size); + netif_hdr = pkt->data; + TEST_ASSERT_EQUAL_INT(_mock_netif_pid, netif_hdr->if_pid); + /* second snip is an IPv6 header to dst */ + TEST_ASSERT_NOT_NULL(pkt->next); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_IPV6, pkt->next->type); + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_hdr_t), pkt->next->size); + ipv6_hdr = pkt->next->data; + TEST_ASSERT(!ipv6_addr_is_multicast(&ipv6_hdr->dst)); + TEST_ASSERT_MESSAGE(ipv6_addr_equal(dst, &ipv6_hdr->dst), + "dst != ipv6_hdr->dst"); + TEST_ASSERT_EQUAL_INT(255, ipv6_hdr->hl); + /* third snip is a valid solicited neighbor advertisement to tgt */ + TEST_ASSERT_NOT_NULL(pkt->next->next); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_ICMPV6, pkt->next->next->type); + TEST_ASSERT_EQUAL_INT(sizeof(ndp_nbr_adv_t), pkt->next->next->size); + nbr_adv = pkt->next->next->data; + TEST_ASSERT_EQUAL_INT(ICMPV6_NBR_ADV, nbr_adv->type); + TEST_ASSERT_EQUAL_INT(0, nbr_adv->code); + TEST_ASSERT(!ipv6_addr_is_multicast(&nbr_adv->tgt)); + TEST_ASSERT_MESSAGE(ipv6_addr_equal(tgt, &nbr_adv->tgt), + "tgt != nbr_adv->tgt"); + TEST_ASSERT(nbr_adv->flags & NDP_NBR_ADV_FLAGS_S); + /* no further options */ + TEST_ASSERT_NULL(pkt->next->next->next); +} + +static void test_handle_pkt__nbr_sol__ll_src(unsigned exp_nud_state, + unsigned exp_ar_state) +{ + msg_t msg; + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_ll, + 255U, 0U, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "Expected neighbor cache entry"); + TEST_ASSERT_MESSAGE(ipv6_addr_equal(&_rem_ll, &nce.ipv6), + "_rem_ll != nce->ipv6"); + TEST_ASSERT_EQUAL_INT(sizeof(_rem_l2), nce.l2addr_len); + TEST_ASSERT_MESSAGE(memcmp(_rem_l2, nce.l2addr, nce.l2addr_len) == 0, + "_rem_l2 != nce.l2addr"); + TEST_ASSERT_EQUAL_INT(exp_nud_state, gnrc_ipv6_nib_nc_get_nud_state(&nce)); + TEST_ASSERT(!gnrc_ipv6_nib_nc_is_router(&nce)); + TEST_ASSERT_EQUAL_INT(_mock_netif_pid, gnrc_ipv6_nib_nc_get_iface(&nce)); + TEST_ASSERT_EQUAL_INT(exp_ar_state, gnrc_ipv6_nib_nc_get_ar_state(&nce)); + TEST_ASSERT_EQUAL_INT(1, msg_avail()); + msg_receive(&msg); + TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type); + test_pkt_is_nbr_adv(msg.content.ptr, &_rem_ll, &_loc_ll); + gnrc_pktbuf_release(msg.content.ptr); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_handle_pkt__nbr_sol__ll_src_empty_nc(void) +{ + test_handle_pkt__nbr_sol__ll_src(GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE, + GNRC_IPV6_NIB_NC_INFO_AR_STATE_GC); +} + +static void test_handle_pkt__nbr_sol__ll_src_unmanaged_nce(void) +{ + test_get_next_hop_l2addr__link_local_static_conf(); + /* unmanaged entry stays unmanaged */ + test_handle_pkt__nbr_sol__ll_src(GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNMANAGED, + GNRC_IPV6_NIB_NC_INFO_AR_STATE_MANUAL); +} + +static void test_handle_pkt__nbr_sol__ll_src_no_sl2ao(void) +{ + msg_t msg; + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_sol(&_rem_ll, &_loc_ll, + 255U, 0U, &_loc_ll, NULL, 0); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + TEST_ASSERT_EQUAL_INT(1, msg_avail()); + msg_receive(&msg); + TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type); + test_pkt_is_nbr_adv(msg.content.ptr, &_rem_ll, &_loc_ll); + gnrc_pktbuf_release(msg.content.ptr); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static size_t _set_nbr_adv(const ipv6_addr_t *ipv6_src, + const ipv6_addr_t *ipv6_dst, + uint8_t ipv6_hl, uint8_t nbr_adv_code, + uint8_t nbr_adv_flags, + const ipv6_addr_t *nbr_adv_tgt, + const uint8_t *tl2ao_addr, size_t tl2ao_addr_len) +{ + size_t icmpv6_len = sizeof(ndp_nbr_adv_t); + ndp_nbr_adv_t *nbr_adv = (ndp_nbr_adv_t *)icmpv6; + + ipv6_hdr_set_version(ipv6); + ipv6->hl = ipv6_hl; + memcpy(&ipv6->src, ipv6_src, sizeof(ipv6->src)); + memcpy(&ipv6->dst, ipv6_dst, sizeof(ipv6->dst)); + nbr_adv->type = ICMPV6_NBR_ADV; + nbr_adv->code = nbr_adv_code; + nbr_adv->flags = nbr_adv_flags; + memcpy(&nbr_adv->tgt, nbr_adv_tgt, sizeof(nbr_adv->tgt)); + + if ((tl2ao_addr != NULL) && (tl2ao_addr_len > 0)) { + ndp_opt_t *tl2ao = (ndp_opt_t *)&_buffer[sizeof(ipv6_hdr_t) + + sizeof(ndp_nbr_adv_t)]; + + tl2ao->type = NDP_OPT_TL2A; + tl2ao->len = ceil8(sizeof(ndp_opt_t) + tl2ao_addr_len) / 8; + memcpy(tl2ao + 1, tl2ao_addr, tl2ao_addr_len); + icmpv6_len += ceil8(sizeof(ndp_opt_t) + tl2ao_addr_len); + } + + return icmpv6_len; +} + +static void test_handle_pkt__nbr_adv__invalid_hl(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_ll, 194U, 0U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_code(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_ll, 255U, 201U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_icmpv6_len(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + + _set_nbr_adv(&_rem_ll, &_loc_ll, 255U, 0U, NDP_NBR_ADV_FLAGS_S, + &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, + sizeof(ndp_nbr_adv_t) - 1); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_tgt(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_ll, 255U, 0U, + NDP_NBR_ADV_FLAGS_S, + &ipv6_addr_all_routers_site_local, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_flags(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &ipv6_addr_all_nodes_link_local, + 255U, 0U, NDP_NBR_ADV_FLAGS_S, &_loc_ll, + NULL, 0); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__invalid_opt_len(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_ll, 255U, 0U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + ndp_opt_t *opt = (ndp_opt_t *)&_buffer[icmpv6_len]; + + opt->type = NDP_OPT_SL2A; + opt->len = 0U; + icmpv6_len += sizeof(ndp_opt_t); + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__unspecified_src(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&ipv6_addr_unspecified, &_loc_ll, 255U, 0U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, _rem_l2, + sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + /* TODO: check other views as well */ + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static void test_handle_pkt__nbr_adv__unsolicited(void) +{ + gnrc_ipv6_nib_nc_t nce; + void *state = NULL; + size_t icmpv6_len = _set_nbr_adv(&_rem_ll, &_loc_ll, 255U, 0U, + NDP_NBR_ADV_FLAGS_S, &_loc_ll, + _rem_l2, sizeof(_rem_l2)); + + gnrc_ipv6_nib_handle_pkt(_mock_netif_pid, ipv6, icmpv6, icmpv6_len); + TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), + "There is an unexpected neighbor cache entry"); + TEST_ASSERT_EQUAL_INT(0, msg_avail()); +} + +static Test *tests_gnrc_ipv6_nib(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + /* gnrc_ipv6_nib_init() and gnrc_ipv6_nib_init_iface() "tested" in + * set-up (otherwise the following tests wouldn't work) */ + /* TODO: ENETUNREACH when non-link-local communication is implemented */ + new_TestFixture(test_get_next_hop_l2addr__link_local_EHOSTUNREACH), + new_TestFixture(test_get_next_hop_l2addr__link_local_static_conf), + new_TestFixture(test_get_next_hop_l2addr__link_local), + new_TestFixture(test_handle_pkt__unknown_type), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_hl), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_code), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_icmpv6_len), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_tgt), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_opt_len), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_dst), + new_TestFixture(test_handle_pkt__nbr_sol__invalid_sl2ao), + new_TestFixture(test_handle_pkt__nbr_sol__tgt_not_assigned), + /* TODO add tests for unspecified source (involves SLAAC) */ + new_TestFixture(test_handle_pkt__nbr_sol__ll_src_empty_nc), + new_TestFixture(test_handle_pkt__nbr_sol__ll_src_unmanaged_nce), + new_TestFixture(test_handle_pkt__nbr_sol__ll_src_no_sl2ao), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_hl), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_code), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_icmpv6_len), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_tgt), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_flags), + new_TestFixture(test_handle_pkt__nbr_adv__invalid_opt_len), + new_TestFixture(test_handle_pkt__nbr_adv__unspecified_src), + new_TestFixture(test_handle_pkt__nbr_adv__unsolicited), + /* solicited tested in get_next_hop_l2addr */ + /* gnrc_ipv6_nib_handle_timer_event not testable in this context since + * we do not have access to the (internally defined) contexts required + * for it */ + }; + + EMB_UNIT_TESTCALLER(tests, _set_up, NULL, fixtures); + + return (Test *)&tests; +} + +int main(void) +{ + _tests_init(); + + TESTS_START(); + TESTS_RUN(tests_gnrc_ipv6_nib()); + TESTS_END(); + + return 0; +} + +int _mock_netif_get(gnrc_netapi_opt_t *opt) +{ + switch (opt->opt) { + case NETOPT_ADDRESS: + if (opt->data_len < sizeof(_loc_l2)) { + return -EOVERFLOW; + } + memcpy(opt->data, _loc_l2, sizeof(_loc_l2)); + return sizeof(_loc_l2); + case NETOPT_SRC_LEN: { + uint16_t *val = opt->data; + if (opt->data_len != sizeof(uint16_t)) { + return -EOVERFLOW; + } + *val = sizeof(_loc_l2); + return sizeof(uint16_t); + } + case NETOPT_IPV6_IID: + if (opt->data_len < sizeof(_loc_iid)) { + return -EOVERFLOW; + } + memcpy(opt->data, _loc_iid, sizeof(_loc_iid)); + return sizeof(_loc_iid); + case NETOPT_IS_WIRED: + return 1; + case NETOPT_MAX_PACKET_SIZE: { + uint16_t *val = opt->data; + if (opt->data_len != sizeof(uint16_t)) { + return -EOVERFLOW; + } + *val = IPV6_MIN_MTU; + return sizeof(uint16_t); + } + case NETOPT_PROTO: { + gnrc_nettype_t *val = opt->data; + if (opt->data_len != sizeof(gnrc_nettype_t)) { + return -EOVERFLOW; + } + *val = GNRC_NETTYPE_SIXLOWPAN; + return sizeof(gnrc_nettype_t); + } + default: + return -ENOTSUP; + } +} + +static inline size_t ceil8(size_t size) +{ + if (size % 8) { + return ((size / 8) + 1) * 8; + } + else { + return size; + } +} diff --git a/tests/gnrc_ipv6_nib_6ln/mockup_netif.c b/tests/gnrc_ipv6_nib_6ln/mockup_netif.c new file mode 100644 index 0000000000000000000000000000000000000000..45f4074ddd4f19896b9bbba3c4e44d32f88df374 --- /dev/null +++ b/tests/gnrc_ipv6_nib_6ln/mockup_netif.c @@ -0,0 +1,81 @@ +/* + * 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 "common.h" +#include "msg.h" +#include "net/gnrc.h" +#include "net/gnrc/ipv6/nib.h" +#include "net/gnrc/ipv6/netif.h" +#include "net/gnrc/netdev.h" +#include "sched.h" +#include "thread.h" + +#define _MSG_QUEUE_SIZE (2) + +kernel_pid_t _mock_netif_pid = KERNEL_PID_UNDEF; + +static char _mock_netif_stack[THREAD_STACKSIZE_DEFAULT]; +static gnrc_netreg_entry_t dumper; +static msg_t _main_msg_queue[_MSG_QUEUE_SIZE]; +static msg_t _mock_netif_msg_queue[_MSG_QUEUE_SIZE]; + +static void *_mock_netif_thread(void *args) +{ + msg_t msg, reply = { .type = GNRC_NETAPI_MSG_TYPE_ACK }; + + (void)args; + msg_init_queue(_mock_netif_msg_queue, _MSG_QUEUE_SIZE); + while (1) { + msg_receive(&msg); + switch (msg.type) { + case GNRC_NETAPI_MSG_TYPE_GET: + reply.content.value = (uint32_t)_mock_netif_get(msg.content.ptr); + break; + case GNRC_NETAPI_MSG_TYPE_SET: + reply.content.value = (uint32_t)(-ENOTSUP); + break; + case GNRC_NETAPI_MSG_TYPE_SND: + case GNRC_NETAPI_MSG_TYPE_RCV: + gnrc_pktbuf_release(msg.content.ptr); + } + msg_reply(&msg, &reply); + } + return NULL; +} + +void _common_set_up(void) +{ + gnrc_ipv6_nib_init(); + gnrc_ipv6_nib_init_iface(_mock_netif_pid); +} + +void _tests_init(void) +{ + msg_init_queue(_main_msg_queue, _MSG_QUEUE_SIZE); + _mock_netif_pid = thread_create(_mock_netif_stack, + sizeof(_mock_netif_stack), + GNRC_NETDEV_MAC_PRIO, + THREAD_CREATE_STACKTEST, + _mock_netif_thread, NULL, "mock_netif"); + assert(_mock_netif_pid > KERNEL_PID_UNDEF); + gnrc_netif_add(_mock_netif_pid); + gnrc_ipv6_netif_init_by_dev(); + thread_yield(); + gnrc_netreg_entry_init_pid(&dumper, GNRC_NETREG_DEMUX_CTX_ALL, + sched_active_pid); + gnrc_netreg_register(GNRC_NETTYPE_NDP2, &dumper); +} + +/** @} */ diff --git a/tests/gnrc_ipv6_nib_6ln/tests/01-run.py b/tests/gnrc_ipv6_nib_6ln/tests/01-run.py new file mode 100755 index 0000000000000000000000000000000000000000..c12bc5b8ca72fe0dddf3f2b354410e7984771947 --- /dev/null +++ b/tests/gnrc_ipv6_nib_6ln/tests/01-run.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de> +# Copyright (C) 2016 Takuo Yonezawa <Yonezawa-T2@mail.dnp.co.jp> +# +# 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. + +import os +import sys + +sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner')) +import testrunner + +def testfunc(child): + child.expect(r"OK \(\d+ tests\)") + +if __name__ == "__main__": + sys.exit(testrunner.run(testfunc, timeout=1)) 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 f7000083890892e398f5309e93fd32ef32de9257..c014c9e0076adfa3973cfb1f69c9b7657b646ffb 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(); } /*