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();
 }
 
 /*