From 1b36cdf79e4ec23f8ec9e06258ead3f70a2d67d3 Mon Sep 17 00:00:00 2001
From: Martine Lenders <m.lenders@fu-berlin.de>
Date: Tue, 2 May 2017 17:05:10 +0200
Subject: [PATCH] nib: implement public NIB functions up to link-local AR

---
 Makefile.dep                                  |   1 +
 sys/include/net/gnrc/ipv6/nib.h               | 109 +++
 .../gnrc/network_layer/ipv6/nib/_nib-6ln.c    | 151 +++++
 .../gnrc/network_layer/ipv6/nib/_nib-6ln.h    | 106 +++
 .../gnrc/network_layer/ipv6/nib/_nib-6lr.c    | 125 ++++
 .../gnrc/network_layer/ipv6/nib/_nib-6lr.h    | 147 ++++
 .../gnrc/network_layer/ipv6/nib/_nib-arsm.c   | 475 +++++++++++++
 .../gnrc/network_layer/ipv6/nib/_nib-arsm.h   | 190 ++++++
 .../network_layer/ipv6/nib/_nib-internal.c    |  31 +-
 .../network_layer/ipv6/nib/_nib-internal.h    |  48 +-
 sys/net/gnrc/network_layer/ipv6/nib/nib.c     | 641 ++++++++++++++++++
 .../tests-gnrc_ipv6_nib-nc.c                  |   9 +-
 12 files changed, 2015 insertions(+), 18 deletions(-)
 create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.c
 create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.h
 create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.c
 create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.h
 create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c
 create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.h
 create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/nib.c

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