From 319c0f9d219dee96f1b0aee9e1ea5bf32ace22ad Mon Sep 17 00:00:00 2001
From: Martine Lenders <m.lenders@fu-berlin.de>
Date: Wed, 9 Aug 2017 16:30:23 +0200
Subject: [PATCH] gnrc_ipv6_nib: implement behavior for router discovery

---
 sys/include/net/gnrc/ipv6/nib.h               |  33 +-
 sys/include/net/gnrc/ipv6/nib/conf.h          |   7 +
 sys/include/net/gnrc/netif2/ipv6.h            |  17 +-
 .../gnrc/application_layer/uhcpc/gnrc_uhcpc.c |  24 +-
 sys/net/gnrc/netif2/gnrc_netif2.c             |  27 +-
 sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c   |   3 +-
 .../gnrc/network_layer/ipv6/nib/_nib-6ln.c    | 189 +++-
 .../gnrc/network_layer/ipv6/nib/_nib-6ln.h    |  64 ++
 .../gnrc/network_layer/ipv6/nib/_nib-6lr.c    |  13 +-
 .../gnrc/network_layer/ipv6/nib/_nib-6lr.h    |  17 +-
 .../gnrc/network_layer/ipv6/nib/_nib-arsm.c   |  40 +-
 .../network_layer/ipv6/nib/_nib-internal.c    |  50 +-
 .../network_layer/ipv6/nib/_nib-internal.h    |  33 +-
 .../gnrc/network_layer/ipv6/nib/_nib-router.c | 203 ++++
 .../gnrc/network_layer/ipv6/nib/_nib-router.h | 132 +++
 sys/net/gnrc/network_layer/ipv6/nib/nib.c     | 867 +++++++++++++++---
 sys/net/gnrc/network_layer/ipv6/nib/nib_pl.c  |  45 +-
 17 files changed, 1524 insertions(+), 240 deletions(-)
 create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-router.c
 create mode 100644 sys/net/gnrc/network_layer/ipv6/nib/_nib-router.h

diff --git a/sys/include/net/gnrc/ipv6/nib.h b/sys/include/net/gnrc/ipv6/nib.h
index 43fe775635..22f1ff989a 100644
--- a/sys/include/net/gnrc/ipv6/nib.h
+++ b/sys/include/net/gnrc/ipv6/nib.h
@@ -14,6 +14,7 @@
  * @todo    Add detailed description
  * @todo    Implement multihop DAD
  * @todo    Implement classic SLAAC
+ * @todo    Implement MLD
  * @{
  *
  * @file
@@ -86,16 +87,6 @@ extern "C" {
  */
 #define GNRC_IPV6_NIB_SEARCH_RTR            (0x4fc3U)
 
-/**
- * @brief   Reconfirm router event.
- *
- * This message type is for the event the reconfirmation of a router (which
- * implies sending a unicast Router Solicitation). The expected message context
- * is a pointer to a valid on-link entry representing the router that is to be
- * confirmed.
- */
-#define GNRC_IPV6_NIB_RECONFIRM_RTR         (0x4fc4U)
-
 /**
  * @brief   Reply Router Solicitation event.
  *
@@ -153,17 +144,6 @@ extern "C" {
  */
 #define GNRC_IPV6_NIB_ADDR_REG_TIMEOUT      (0x4fc9U)
 
-/**
- * @brief   6LoWPAN context timeout event.
- *
- * This message type is for the event of a 6LoWPAN compression context timeout.
- * The expected message context is the compression context's numerical
- * identifier.
- *
- * @note    Only handled with @ref GNRC_IPV6_NIB_CONF_6LN != 0
- */
-#define GNRC_IPV6_NIB_6LO_CTX_TIMEOUT       (0x4fcaU)
-
 /**
  * @brief   Authoritative border router timeout event.
  *
@@ -201,6 +181,17 @@ extern "C" {
  * @note    Only handled with @ref GNRC_IPV6_NIB_CONF_ARSM != 0
  */
 #define GNRC_IPV6_NIB_RECALC_REACH_TIME     (0x4fceU)
+
+/**
+ * @brief   Reregister address.
+ *
+ * This message type is for the event of reregistering an IPv6 address to the
+ * upstream router. The expected message context is an IPv6 address assigned to
+ * one of the nodes interfaces.
+ *
+ * @note    Only handled with @ref GNRC_IPV6_NIB_CONF_6LN != 0
+ */
+#define GNRC_IPV6_NIB_REREG_ADDRESS         (0x4fcfU)
 /** @} */
 
 /**
diff --git a/sys/include/net/gnrc/ipv6/nib/conf.h b/sys/include/net/gnrc/ipv6/nib/conf.h
index 90992df165..999350eaad 100644
--- a/sys/include/net/gnrc/ipv6/nib/conf.h
+++ b/sys/include/net/gnrc/ipv6/nib/conf.h
@@ -29,6 +29,9 @@ extern "C" {
 #ifndef GNRC_IPV6_NIB_CONF_6LBR
 #define GNRC_IPV6_NIB_CONF_6LBR         (1)
 #endif
+#ifndef GNRC_IPV6_NIB_NUMOF
+#define GNRC_IPV6_NIB_NUMOF             (16)
+#endif
 #endif
 
 #ifdef MODULE_GNRC_IPV6_NIB_6LR
@@ -174,8 +177,12 @@ extern "C" {
  * @see [RFC 6775, section 8.1](https://tools.ietf.org/html/rfc6775#section-8.1)
  */
 #ifndef GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+#if GNRC_IPV6_NIB_CONF_6LN
+#define GNRC_IPV6_NIB_CONF_MULTIHOP_P6C (1)
+#else
 #define GNRC_IPV6_NIB_CONF_MULTIHOP_P6C (0)
 #endif
+#endif
 
 /**
  * @brief   Multihop duplicate address detection
diff --git a/sys/include/net/gnrc/netif2/ipv6.h b/sys/include/net/gnrc/netif2/ipv6.h
index 4357ddcf5b..56e5dfe26e 100644
--- a/sys/include/net/gnrc/netif2/ipv6.h
+++ b/sys/include/net/gnrc/netif2/ipv6.h
@@ -148,6 +148,18 @@ typedef struct {
      *          and @ref net_gnrc_ipv6_nib "NIB"
      */
     evtimer_msg_event_t search_rtr;
+#if GNRC_IPV6_NIB_CONF_6LN || DOXYGEN
+    /**
+     * @brief   Timers for address re-registration
+     *
+     * @note    Only available with module @ref net_gnrc_ipv6 "gnrc_ipv6" and
+     *          @ref net_gnrc_ipv6_nib "NIB" and if
+     *          @ref GNRC_IPV6_NIB_CONF_6LN != 0
+     * @note    Might also be usable in the later default SLAAC implementation
+     *          for NS retransmission timers.
+     */
+    evtimer_msg_event_t addrs_timers[GNRC_NETIF2_IPV6_ADDRS_NUMOF];
+#endif
 
 #if GNRC_IPV6_NIB_CONF_ROUTER || DOXYGEN
     /**
@@ -210,16 +222,13 @@ typedef struct {
      */
     uint8_t ra_sent;
 #endif
-#if GNRC_IPV6_NIB_CONF_6LN || DOXYGEN
     /**
      * @brief   number of unsolicited router solicitations scheduled
      *
      * @note    Only available with module @ref net_gnrc_ipv6 "gnrc_ipv6" and
-     *          @ref net_gnrc_ipv6_nib "NIB" and if
-     *          @ref GNRC_IPV6_NIB_CONF_6LN != 0
+     *          @ref net_gnrc_ipv6_nib "NIB"
      */
     uint8_t rs_sent;
-#endif
     /**
      * @brief   number of unsolicited neighbor advertisements scheduled
      *
diff --git a/sys/net/gnrc/application_layer/uhcpc/gnrc_uhcpc.c b/sys/net/gnrc/application_layer/uhcpc/gnrc_uhcpc.c
index 05984803a0..cdcc01eea0 100644
--- a/sys/net/gnrc/application_layer/uhcpc/gnrc_uhcpc.c
+++ b/sys/net/gnrc/application_layer/uhcpc/gnrc_uhcpc.c
@@ -6,12 +6,10 @@
  * directory for more details.
  */
 
-#include "net/fib.h"
+#include "net/gnrc/ipv6/nib.h"
 #include "net/gnrc/ipv6.h"
-#include "net/gnrc/ipv6/nc.h"
-#include "net/gnrc/ipv6/netif.h"
 #include "net/gnrc/netapi.h"
-#include "net/gnrc/netif.h"
+#include "net/gnrc/netif2.h"
 #include "net/ipv6/addr.h"
 #include "net/netdev.h"
 #include "net/netopt.h"
@@ -31,19 +29,13 @@ static void set_interface_roles(void)
         kernel_pid_t dev = netif->pid;
         int is_wired = gnrc_netapi_get(dev, NETOPT_IS_WIRED, 0, NULL, 0);
         if ((!gnrc_border_interface) && (is_wired == 1)) {
-            ipv6_addr_t addr;
+            ipv6_addr_t addr, defroute = IPV6_ADDR_UNSPECIFIED;
             gnrc_border_interface = dev;
 
             ipv6_addr_from_str(&addr, "fe80::2");
             gnrc_netapi_set(dev, NETOPT_IPV6_ADDR, 64 << 8, &addr, sizeof(addr));
-#ifdef MODULE_FIB
-            ipv6_addr_t defroute = IPV6_ADDR_UNSPECIFIED;
-
             ipv6_addr_from_str(&addr, "fe80::1");
-            fib_add_entry(&gnrc_ipv6_fib_table, dev, defroute.u8, 16,
-                    0x00, addr.u8, 16, 0,
-                    (uint32_t)FIB_LIFETIME_NO_EXPIRE);
-#endif
+            gnrc_ipv6_nib_ft_add(&defroute, IPV6_ADDR_BIT_LEN, &addr, dev);
         }
         else if ((!gnrc_wireless_interface) && (is_wired != 1)) {
             gnrc_wireless_interface = dev;
@@ -92,6 +84,10 @@ void uhcp_handle_prefix(uint8_t *prefix, uint8_t prefix_len, uint16_t lifetime,
 
     gnrc_netapi_set(gnrc_wireless_interface, NETOPT_IPV6_ADDR, (64 << 8),
                     prefix, sizeof(ipv6_addr_t));
+#if defined(MODULE_GNRC_IPV6_NIB) && GNRC_IPV6_NIB_CONF_6LBR && \
+    GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+    gnrc_ipv6_nib_abr_add((ipv6_addr_t *)prefix);
+#endif
     gnrc_netapi_set(gnrc_wireless_interface, NETOPT_IPV6_ADDR_REMOVE, 0,
                     &_prefix, sizeof(_prefix));
     print_str("gnrc_uhcpc: uhcp_handle_prefix(): configured new prefix ");
@@ -101,6 +97,10 @@ void uhcp_handle_prefix(uint8_t *prefix, uint8_t prefix_len, uint16_t lifetime,
     if (!ipv6_addr_is_unspecified(&_prefix)) {
         gnrc_netapi_set(gnrc_wireless_interface, NETOPT_IPV6_ADDR_REMOVE, 0,
                         &_prefix, sizeof(_prefix));
+#if defined(MODULE_GNRC_IPV6_NIB) && GNRC_IPV6_NIB_CONF_6LBR && \
+    GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+        gnrc_ipv6_nib_abr_del(&_prefix);
+#endif
         print_str("gnrc_uhcpc: uhcp_handle_prefix(): removed old prefix ");
         ipv6_addr_print(&_prefix);
         puts("/64");
diff --git a/sys/net/gnrc/netif2/gnrc_netif2.c b/sys/net/gnrc/netif2/gnrc_netif2.c
index 2d643bea4f..75ba222795 100644
--- a/sys/net/gnrc/netif2/gnrc_netif2.c
+++ b/sys/net/gnrc/netif2/gnrc_netif2.c
@@ -550,10 +550,29 @@ int gnrc_netif2_ipv6_addr_add(gnrc_netif2_t *netif, const ipv6_addr_t *addr,
     }
     netif->ipv6.addrs_flags[idx] = flags;
     memcpy(&netif->ipv6.addrs[idx], addr, sizeof(netif->ipv6.addrs[idx]));
-    /* TODO:
-     *  - update prefix list, if flags == VALID
-     *  - with SLAAC, send out NS otherwise for DAD probing */
+#ifdef MODULE_GNRC_IPV6_NIB
+    if (_get_state(netif, idx) == GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_VALID) {
+        void *state = NULL;
+        gnrc_ipv6_nib_pl_t ple;
+        bool in_pl = false;
+
+        while (gnrc_ipv6_nib_pl_iter(netif->pid, &state, &ple)) {
+            if (ipv6_addr_match_prefix(&ple.pfx, addr) >= pfx_len) {
+                in_pl = true;
+            }
+        }
+        if (!in_pl) {
+        gnrc_ipv6_nib_pl_set(netif->pid, addr, pfx_len, UINT32_MAX, UINT32_MAX);
+        }
+    }
+#if GNRC_IPV6_NIB_CONF_SLAAC
+    else {
+        /* TODO: send out NS to solicited nodes for DAD probing */
+    }
+#endif
+#else
     (void)pfx_len;
+#endif
     gnrc_netif2_release(netif);
     return idx;
 }
@@ -569,8 +588,6 @@ void gnrc_netif2_ipv6_addr_remove(gnrc_netif2_t *netif,
     if (idx >= 0) {
         netif->ipv6.addrs_flags[idx] = 0;
         ipv6_addr_set_unspecified(&netif->ipv6.addrs[idx]);
-        /* TODO:
-         *  - update prefix list, if necessary */
     }
     gnrc_netif2_release(netif);
 }
diff --git a/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c b/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c
index e70da32d35..7fd41fb085 100644
--- a/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c
+++ b/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c
@@ -290,17 +290,16 @@ static void *_event_loop(void *args)
             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:
+            case GNRC_IPV6_NIB_REREG_ADDRESS:
                 DEBUG("ipv6: NIB timer event received\n");
                 gnrc_ipv6_nib_handle_timer_event(msg.content.ptr, msg.type);
                 break;
diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.c
index e4beb0e09f..982635b9b8 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.c
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.c
@@ -15,6 +15,7 @@
 
 #include "net/gnrc/netif2/internal.h"
 #include "net/gnrc/ipv6/nib.h"
+#include "net/gnrc/ndp2.h"
 
 #include "_nib-6ln.h"
 #include "_nib-6lr.h"
@@ -27,11 +28,13 @@
 static char addr_str[IPV6_ADDR_MAX_STR_LEN];
 #endif
 
+extern void _handle_search_rtr(gnrc_netif2_t *netif);
+
 static inline bool _is_iface_eui64(gnrc_netif2_t *netif, const eui64_t *eui64)
 {
     /* TODO: adapt for short addresses */
     return (netif->l2addr_len == sizeof(eui64_t)) &&
-            (memcmp(&netif->l2addr, eui64, netif->l2addr_len) == 0);
+           (memcmp(&netif->l2addr, eui64, netif->l2addr_len) == 0);
 }
 
 static inline uint8_t _reverse_iid(const ipv6_addr_t *dst,
@@ -47,7 +50,7 @@ static inline uint8_t _reverse_iid(const ipv6_addr_t *dst,
             l2addr[4] = dst->u8[14];
             l2addr[5] = dst->u8[15];
             return ETHERNET_ADDR_LEN;
-#endif
+#endif  /* MODULE_NETDEV_ETH */
 #ifdef MODULE_NETDEV_IEEE802154
         case NETDEV_TYPE_IEEE802154:
             /* assume address was based on EUI-64
@@ -55,12 +58,12 @@ static inline uint8_t _reverse_iid(const ipv6_addr_t *dst,
             memcpy(l2addr, &dst->u64[1], sizeof(dst->u64[1]));
             l2addr[0] ^= 0x02;
             return sizeof(dst->u64[1]);
-#endif
+#endif  /* MODULE_NETDEV_IEEE802154 */
 #ifdef MODULE_CC110X
         case NETDEV_TYPE_CC110X:
             l2addr[0] = dst->u8[15];
             return sizeof(uint8_t);
-#endif
+#endif  /* MODULE_CC110X */
         default:
             (void)dst;
             (void)l2addr;
@@ -104,9 +107,6 @@ uint8_t _handle_aro(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
                     const sixlowpan_nd_opt_ar_t *aro, const ndp_opt_t *sl2ao,
                     _nib_onl_entry_t *nce)
 {
-#if !GNRC_IPV6_NIB_CONF_6LR
-    (void)sl2ao;
-#endif
     assert(netif != NULL);
     if (gnrc_netif2_is_6ln(netif) && (aro->len == SIXLOWPAN_ND_OPT_AR_LEN)) {
         DEBUG("nib: valid ARO received\n");
@@ -125,23 +125,27 @@ uint8_t _handle_aro(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
             switch (aro->status) {
                 case SIXLOWPAN_ND_STATUS_SUCCESS: {
                     uint16_t ltime = byteorder_ntohs(aro->ltime);
-                    uint32_t next_ns;
+                    uint32_t rereg_time;
+                    int idx = gnrc_netif2_ipv6_addr_idx(netif, &ipv6->dst);
                     /* 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 %" PRIu32 "ms\n",
-                          next_ns);
-                    assert(nce != NULL);
-                    _evtimer_add(nce, GNRC_IPV6_NIB_SND_UC_NS, &nce->nud_timeout,
-                                 next_ns);
+                    rereg_time = (ltime == 1U) ? (30 * MS_PER_SEC) :
+                                 (ltime - 1U) * SEC_PER_MIN * MS_PER_SEC;
+                    DEBUG("nib: Address registration of %s successful. "
+                          "Scheduling re-registration in %" PRIu32 "ms\n",
+                          ipv6_addr_to_str(addr_str, &ipv6->dst,
+                                           sizeof(addr_str)), rereg_time);
+                    netif->ipv6.addrs_flags[idx] &= ~GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_MASK;
+                    netif->ipv6.addrs_flags[idx] |= GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_VALID;
+                    _evtimer_add(&netif->ipv6.addrs[idx],
+                                 GNRC_IPV6_NIB_REREG_ADDRESS,
+                                 &netif->ipv6.addrs_timers[idx],
+                                 rereg_time);
                     break;
                 }
                 case SIXLOWPAN_ND_STATUS_DUP:
                     DEBUG("nib: Address registration reports duplicate. "
-                               "Removing address %s%%%u\n",
+                          "Removing address %s%%%u\n",
                           ipv6_addr_to_str(addr_str,
                                            &ipv6->dst,
                                            sizeof(addr_str)), netif->pid);
@@ -150,17 +154,18 @@ uint8_t _handle_aro(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
                     break;
                 case SIXLOWPAN_ND_STATUS_NC_FULL: {
                         DEBUG("nib: Router's neighbor cache is full. "
-                                   "Searching new router for DAD\n");
+                              "Searching new router for DAD\n");
                         _nib_dr_entry_t *dr = _nib_drl_get(&ipv6->src, netif->pid);
                         assert(dr != NULL); /* otherwise we wouldn't be here */
                         _nib_drl_remove(dr);
                         if (_nib_drl_iter(NULL) == NULL) { /* no DRL left */
                             netif->ipv6.rs_sent = 0;
-                            /* TODO: search new router */
+                            /* search (hopefully) new router */
+                            _handle_search_rtr(netif);
                         }
                         else {
                             assert(dr->next_hop != NULL);
-                            _snd_uc_ns(dr->next_hop, true);
+                            _handle_rereg_address(&ipv6->dst);
                         }
                     }
                     break;
@@ -170,17 +175,153 @@ uint8_t _handle_aro(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
 #if GNRC_IPV6_NIB_CONF_6LR
         else if (gnrc_netif2_is_6lr(netif) &&
                  (icmpv6->type == ICMPV6_NBR_SOL)) {
-            return _reg_addr_upstream(netif, ipv6, icmpv6, aro, sl2ao);
+            assert(nce != NULL);
+            return _reg_addr_upstream(netif, ipv6, icmpv6, aro, sl2ao, nce);
         }
-#endif
+#else   /* GNRC_IPV6_NIB_CONF_6LR */
+        (void)sl2ao;
+        (void)nce;
+#endif  /* GNRC_IPV6_NIB_CONF_6LR */
     }
 #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
+#endif  /* ENABLE_DEBUG */
     return _ADDR_REG_STATUS_IGNORE;
 }
+
+static inline bool _is_tentative(const gnrc_netif2_t *netif, int idx)
+{
+    return (gnrc_netif2_ipv6_addr_get_state(netif, idx) &
+            GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_TENTATIVE);
+}
+
+static inline bool _is_valid(const gnrc_netif2_t *netif, int idx)
+{
+    return (gnrc_netif2_ipv6_addr_get_state(netif, idx) ==
+            GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_VALID);
+}
+
+void _handle_rereg_address(const ipv6_addr_t *addr)
+{
+    gnrc_netif2_t *netif = gnrc_netif2_get_by_ipv6_addr(addr);
+    _nib_dr_entry_t *router = _nib_drl_get_dr();
+
+    if ((netif != NULL) && (router != NULL)) {
+        assert((unsigned)netif->pid == _nib_onl_get_if(router->next_hop));
+        DEBUG("nib: Re-registering %s",
+              ipv6_addr_to_str(addr_str, addr, sizeof(addr_str)));
+        DEBUG(" with upstream router %s\n",
+              ipv6_addr_to_str(addr_str, &router->next_hop->ipv6,
+                               sizeof(addr_str)));
+        _snd_ns(&router->next_hop->ipv6, netif, addr, &router->next_hop->ipv6);
+    }
+    else {
+        DEBUG("nib: Couldn't re-register %s, no current router found or address "
+              "wasn't assigned to any interface anymore.\n",
+              ipv6_addr_to_str(addr_str, addr, sizeof(addr_str)));
+    }
+    if (netif != NULL) {
+        int idx = gnrc_netif2_ipv6_addr_idx(netif, addr);
+
+        if (_is_valid(netif, idx) || (_is_tentative(netif, idx) &&
+             (gnrc_netif2_ipv6_addr_dad_trans(netif, idx) <
+              SIXLOWPAN_ND_REG_TRANSMIT_NUMOF))) {
+            uint32_t retrans_time;
+
+            if (_is_valid(netif, idx)) {
+                retrans_time = SIXLOWPAN_ND_MAX_RS_SEC_INTERVAL;
+            }
+            else {
+                retrans_time = netif->ipv6.retrans_time;
+                /* increment encoded retransmission count */
+                netif->ipv6.addrs_flags[idx]++;
+            }
+            _evtimer_add(&netif->ipv6.addrs[idx], GNRC_IPV6_NIB_REREG_ADDRESS,
+                         &netif->ipv6.addrs_timers[idx], retrans_time);
+        }
+        else {
+            netif->ipv6.rs_sent = 0;
+            _handle_search_rtr(netif);
+        }
+    }
+}
+
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+_nib_abr_entry_t *_handle_abro(const sixlowpan_nd_opt_abr_t *abro)
+{
+    _nib_abr_entry_t *abr = NULL;
+
+    if (abro->len != SIXLOWPAN_ND_OPT_ABR_LEN) {
+        /* ignore silently */
+        return NULL;
+    }
+    abr = _nib_abr_add(&abro->braddr);
+    if (abr != NULL) {
+        uint32_t abro_version = sixlowpan_nd_opt_abr_get_version(abro);
+        uint16_t ltime = byteorder_ntohs(abro->ltime);
+
+        if (abr->version >= abro_version) {
+            abr->version = abro_version;
+            abr->valid_until = _now_min() + ltime;
+        }
+        /* correct for default value */
+        ltime = (ltime == 0) ? SIXLOWPAN_ND_OPT_ABR_LTIME_DEFAULT : ltime;
+        _evtimer_add(abr, GNRC_IPV6_NIB_ABR_TIMEOUT, &abr->timeout,
+                     /* UINT16_MAX min < UINT32_MAX ms so no risk of overflow */
+                     MS_PER_SEC * SEC_PER_MIN * ltime);
+    }
+    return abr;
+}
+#endif /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+uint32_t _handle_6co(const icmpv6_hdr_t *icmpv6,
+                     const sixlowpan_nd_opt_6ctx_t *sixco,
+                     _nib_abr_entry_t *abr)
+#else   /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+uint32_t _handle_6co(const icmpv6_hdr_t *icmpv6,
+                     const sixlowpan_nd_opt_6ctx_t *sixco)
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+{
+    uint16_t ltime;
+
+#ifdef MODULE_GNRC_SIXLOWPAN_CTX
+    uint8_t cid;
+#endif  /* MODULE_GNRC_SIXLOWPAN_CTX */
+
+    if ((sixco->len != SIXLOWPAN_ND_OPT_6CTX_LEN_MIN) ||
+        ((sixco->len != SIXLOWPAN_ND_OPT_6CTX_LEN_MAX) &&
+         (sixco->ctx_len > 64U)) ||
+        (icmpv6->type != ICMPV6_RTR_ADV)) {
+        DEBUG("nib: received 6CO of invalid length (%u), must be %u "
+              "or wasn't delivered by RA."
+              "\n",
+              sixco->len,
+              (sixco->ctx_len > 64U) ? SIXLOWPAN_ND_OPT_6CTX_LEN_MAX :
+                                       SIXLOWPAN_ND_OPT_6CTX_LEN_MIN);
+        return UINT32_MAX;
+    }
+    ltime = byteorder_ntohs(sixco->ltime);
+#ifdef MODULE_GNRC_SIXLOWPAN_CTX
+    cid = sixlowpan_nd_opt_6ctx_get_cid(sixco);
+    gnrc_sixlowpan_ctx_update(cid, (ipv6_addr_t *)(sixco + 1), sixco->ctx_len,
+                              ltime, sixlowpan_nd_opt_6ctx_is_comp(sixco));
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+    assert(abr != NULL);    /* should have been set in _handle_abro() */
+    if (ltime == 0) {
+        bf_unset(abr->ctxs, cid);
+    }
+    else {
+        bf_set(abr->ctxs, cid);
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+#else   /* MODULE_GNRC_SIXLOWPAN_CTX */
+    (void)abr;
+#endif  /* MODULE_GNRC_SIXLOWPAN_CTX */
+    return ltime * SEC_PER_MIN * MS_PER_SEC;
+}
 #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
index 1ba822bf58..d371aebc93 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.h
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6ln.h
@@ -23,6 +23,8 @@
 
 #include "net/gnrc/ipv6/nib/conf.h"
 #include "net/sixlowpan/nd.h"
+#include "timex.h"
+#include "xtimer.h"
 
 #include "_nib-arsm.h"
 #include "_nib-internal.h"
@@ -31,6 +33,12 @@
 extern "C" {
 #endif
 
+static inline uint32_t _now_min(void)
+{
+    return (uint32_t)((xtimer_now_usec64() / (US_PER_SEC * SEC_PER_MIN)) &
+                      UINT32_MAX);
+}
+
 #if GNRC_IPV6_NIB_CONF_6LN || defined(DOXYGEN)
 /**
  * @brief   Additional (local) status to ARO status values for tentative
@@ -57,6 +65,37 @@ extern "C" {
 bool _resolve_addr_from_ipv6(const ipv6_addr_t *dst, gnrc_netif2_t *netif,
                              gnrc_ipv6_nib_nc_t *nce);
 
+/**
+ * @brief   Calculates exponential backoff for RS retransmissions
+ *
+ * @see [RFC 6775, section 5.3](https://tools.ietf.org/html/rfc6775#section-5.3)
+ *
+ * @param[in] netif The network interface that the RS will be sent over.
+ *
+ * @return  The interval in ms to the next RS
+ */
+static inline uint32_t _get_next_rs_interval(const gnrc_netif2_t *netif)
+{
+    if (gnrc_netif2_is_6ln(netif)) {
+        if (netif->ipv6.rs_sent < SIXLOWPAN_ND_MAX_RS_NUMOF) {
+            return SIXLOWPAN_ND_RS_MSEC_INTERVAL;
+        }
+        else {
+            unsigned exp = netif->ipv6.rs_sent - SIXLOWPAN_ND_MAX_RS_NUMOF;
+            uint32_t tmp = SIXLOWPAN_ND_RS_MSEC_INTERVAL +
+                           ((1 << exp) * (NDP_RS_MS_INTERVAL));
+
+            if (tmp > (SIXLOWPAN_ND_MAX_RS_SEC_INTERVAL * MS_PER_SEC)) {
+                tmp = SIXLOWPAN_ND_MAX_RS_SEC_INTERVAL * MS_PER_SEC;
+            }
+            return tmp;
+        }
+    }
+    else {
+        return NDP_RS_MS_INTERVAL;
+    }
+}
+
 /**
  * @brief   Handles ARO
  *
@@ -74,11 +113,36 @@ uint8_t _handle_aro(gnrc_netif2_t *netif, 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);
+
+/**
+ * @brief   Handler for @ref GNRC_IPV6_NIB_REREG_ADDRESS event handler
+ *
+ * @param[in] addr  An IPv6 address.
+ */
+void _handle_rereg_address(const ipv6_addr_t *addr);
+
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C || defined(DOXYGEN)
+_nib_abr_entry_t *_handle_abro(const sixlowpan_nd_opt_abr_t *abro);
+uint32_t _handle_6co(const icmpv6_hdr_t *icmpv6,
+                     const sixlowpan_nd_opt_6ctx_t *sixco,
+                     _nib_abr_entry_t *abr);
+#else   /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C || defined(DOXYGEN) */
+uint32_t _handle_6co(const icmpv6_hdr_t *icmpv6,
+                     const sixlowpan_nd_opt_6ctx_t *sixco);
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C || defined(DOXYGEN) */
 #else   /* GNRC_IPV6_NIB_CONF_6LN || defined(DOXYGEN) */
 #define _resolve_addr_from_ipv6(dst, netif, 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
  */
+#define _get_next_rs_interval(netif)                (NDP_RS_MS_INTERVAL)
+#define _handle_rereg_address(netif)                (void)netif
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C || defined(DOXYGEN)
+#define _handle_abro(abro)                          (NULL)
+#define _handle_6co(icmpv6, sixco, abr)             (UINT32_MAX)
+#else   /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C || defined(DOXYGEN) */
+#define _handle_6co(icmpv6, sixco)                  (UINT32_MAX)
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C || defined(DOXYGEN) */
 #endif  /* GNRC_IPV6_NIB_CONF_6LN || defined(DOXYGEN) */
 
 
diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.c
index 9cfaf55f0a..4342b0b354 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.c
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.c
@@ -51,11 +51,9 @@ static uint8_t _update_nce_ar_state(const sixlowpan_nd_opt_ar_t *aro,
 uint8_t _reg_addr_upstream(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
                            const icmpv6_hdr_t *icmpv6,
                            const sixlowpan_nd_opt_ar_t *aro,
-                           const ndp_opt_t *sl2ao)
+                           const ndp_opt_t *sl2ao, _nib_onl_entry_t *nce)
 {
     if (!ipv6_addr_is_unspecified(&ipv6->src) && (sl2ao != NULL)) {
-        _nib_onl_entry_t *nce = _nib_onl_get(&ipv6->src, netif->pid);
-
         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)),
@@ -66,9 +64,11 @@ uint8_t _reg_addr_upstream(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
             (memcmp(&nce->eui64, &aro->eui64, sizeof(aro->eui64)) == 0)) {
 #if GNRC_IPV6_NIB_CONF_MULTIHOP_DAD
             /* TODO */
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_DAD */
             if (aro->ltime.u16 != 0) {
                 _handle_sl2ao(netif, ipv6, icmpv6, sl2ao);
+                /* re-get NCE in case it was updated */
+                nce = _nib_onl_get(&ipv6->src, netif->pid);
                 return _update_nce_ar_state(aro, nce);
             }
             else if (nce != NULL) {
@@ -76,7 +76,8 @@ uint8_t _reg_addr_upstream(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
                 return SIXLOWPAN_ND_STATUS_SUCCESS;
             }
         }
-        else {
+        else if (_get_ar_state(nce) != GNRC_IPV6_NIB_NC_INFO_AR_STATE_GC) {
+            /* ignore address registration requests from upstream */
             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)),
@@ -116,7 +117,7 @@ gnrc_pktsnip_t *_copy_and_handle_aro(gnrc_netif2_t *netif,
             DEBUG("nib: Address was marked TENTATIVE => not replying NS, "
                   "waiting for DAC\n");
         }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_DAD */
     }
     return reply_aro;
 }
diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.h b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.h
index 6248600e69..e7b7661a55 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.h
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-6lr.h
@@ -83,6 +83,9 @@ static inline bool _rtr_sol_on_6lr(const gnrc_netif2_t *netif,
  *                      handed to the SL2AO handler function).
  * @param[in] aro       ARO that carries the address registration information.
  * @param[in] sl2ao     SL2AO associated with the ARO.
+ * @param[in] nce       The local neighbor cache entry the registration
+ *                      information is supposed to be copied into. May be NULL
+ *                      (this might create one).
  *
  * @return  registration status of the address (including
  *          @ref _ADDR_REG_STATUS_TENTATIVE and @ref _ADDR_REG_STATUS_IGNORE).
@@ -90,7 +93,7 @@ static inline bool _rtr_sol_on_6lr(const gnrc_netif2_t *netif,
 uint8_t _reg_addr_upstream(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
                            const icmpv6_hdr_t *icmpv6,
                            const sixlowpan_nd_opt_ar_t *aro,
-                           const ndp_opt_t *sl2ao);
+                           const ndp_opt_t *sl2ao, _nib_onl_entry_t *nce);
 
 
 /**
@@ -112,15 +115,23 @@ gnrc_pktsnip_t *_copy_and_handle_aro(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv
                                      const ndp_nbr_sol_t *nbr_sol,
                                      const sixlowpan_nd_opt_ar_t *aro,
                                      const ndp_opt_t *sl2ao);
+
+/**
+ * @brief   Sets the @ref GNRC_NETIF2_FLAGS_IPV6_RTR_ADV flags of an interface
+ *
+ * @param[in] netif The interface.
+ */
+void _set_rtr_adv(gnrc_netif2_t *netif);
 #else   /* GNRC_IPV6_NIB_CONF_6LR || defined(DOXYGEN) */
 #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(netif, 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
  */
+#define _copy_and_handle_aro(netif, ipv6, icmpv6, aro, sl2ao) \
+                                        (NULL)
+#define _set_rtr_adv(netif)             (void)netif
 #endif  /* GNRC_IPV6_NIB_CONF_6LR || defined(DOXYGEN) */
 
 #ifdef __cplusplus
diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c
index 22ac8c2ce1..a1d814e10e 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c
@@ -19,9 +19,10 @@
 #include "net/gnrc/netif2/internal.h"
 #ifdef MODULE_GNRC_SIXLOWPAN_ND
 #include "net/gnrc/sixlowpan/nd.h"
-#endif
+#endif  /* MODULE_GNRC_SIXLOWPAN_ND */
 
 #include "_nib-arsm.h"
+#include "_nib-router.h"
 #include "_nib-6lr.h"
 
 #define ENABLE_DEBUG    (0)
@@ -88,16 +89,16 @@ void _snd_uc_ns(_nib_onl_entry_t *nbr, bool reset)
     if (reset) {
         nbr->ns_sent = 0;
     }
-#else
+#else   /* GNRC_IPV6_NIB_CONF_ARSM */
     (void)reset;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
     _snd_ns(&nbr->ipv6, netif, NULL, &nbr->ipv6);
     _evtimer_add(nbr, GNRC_IPV6_NIB_SND_UC_NS, &nbr->nud_timeout,
                  netif->ipv6.retrans_time);
     gnrc_netif2_release(netif);
 #if GNRC_IPV6_NIB_CONF_ARSM
     nbr->ns_sent++;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
 }
 
 void _handle_sl2ao(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
@@ -118,7 +119,7 @@ void _handle_sl2ao(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
          (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)) {
+        !_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(netif, nce, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE);
@@ -142,13 +143,13 @@ void _handle_sl2ao(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
                              &nce->addr_reg_timeout,
                              SIXLOWPAN_ND_TENTATIVE_NCE_SEC_LTIME * MS_PER_SEC);
             }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_DAD && GNRC_IPV6_NIB_CONF_6LR */
         }
 #if ENABLE_DEBUG
         else {
             DEBUG("nib: Neighbor cache full\n");
         }
-#endif
+#endif  /* ENABLE_DEBUG */
     }
     /* not else to include NCE created in nce == NULL branch */
     if ((nce != NULL) && (nce->mode & _NC)) {
@@ -171,7 +172,7 @@ void _handle_sl2ao(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
             nce->l2addr_len = l2addr_len;
             memcpy(nce->l2addr, sl2ao + 1, l2addr_len);
         }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
     }
 }
 
@@ -183,17 +184,17 @@ static inline unsigned _get_l2addr_len(gnrc_netif2_t *netif,
         case NETDEV_TYPE_CC110X:
             (void)opt;
             return sizeof(uint8_t);
-#endif
+#endif  /* MODULE_CC110X */
 #ifdef MODULE_NETDEV_ETH
         case NETDEV_TYPE_ETHERNET:
             (void)opt;
             return ETHERNET_ADDR_LEN;
-#endif
+#endif  /* MODULE_NETDEV_ETH */
 #ifdef MODULE_NETDEV_NRFMIN
         case NETDEV_TYPE_NRFMIN:
             (void)opt;
             return sizeof(uint16_t);
-#endif
+#endif  /* MODULE_NETDEV_NRFMIN */
 #ifdef MODULE_NETDEV_IEEE802154
         case NETDEV_TYPE_IEEE802154:
             switch (opt->len) {
@@ -204,7 +205,7 @@ static inline unsigned _get_l2addr_len(gnrc_netif2_t *netif,
                 default:
                     return 0U;
             }
-#endif
+#endif  /* MODULE_NETDEV_IEEE802154 */
         default:
             (void)opt;
             return 0U;
@@ -314,6 +315,7 @@ void _handle_state_timeout(_nib_onl_entry_t *nbr)
 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:
@@ -363,7 +365,7 @@ void _probe_nbr(_nib_onl_entry_t *nbr, bool reset)
                                            sizeof(addr_str)),
                           (unsigned)netif->ipv6.retrans_time);
                 }
-#endif
+#endif  /* ENABLE_DEBUG */
                 gnrc_netif2_release(netif);
             }
             break;
@@ -483,14 +485,14 @@ void _set_nud_state(gnrc_netif2_t *netif, _nib_onl_entry_t *nce,
 
 #if GNRC_IPV6_NIB_CONF_ROUTER
     gnrc_netif2_acquire(netif);
-    if ((netif != NULL) && (netif->ipv6.route_info_cb)) {
-        netif->ipv6.route_info_cb(GNRC_IPV6_NIB_ROUTE_INFO_TYPE_NSC,
-                                  &nce->ipv6, (void *)((intptr_t)state));
+    if (netif != NULL) {
+        _call_route_info_cb(netif, GNRC_IPV6_NIB_ROUTE_INFO_TYPE_NSC,
+                            &nce->ipv6, (void *)((intptr_t)state));
     }
     gnrc_netif2_release(netif);
-#else
+#else   /* GNRC_IPV6_NIB_CONF_ROUTER */
     (void)netif;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
 }
 
 /* internal functions */
@@ -515,7 +517,7 @@ static inline bool _redirect_with_tl2ao(icmpv6_hdr_t *icmpv6, ndp_opt_t *tl2ao)
 {
     return (icmpv6->type == ICMPV6_REDIRECT) && (tl2ao != NULL);
 }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_REDIRECT */
 
 static inline bool _tl2ao_changes_nce(_nib_onl_entry_t *nce,
                                       const ndp_opt_t *tl2ao,
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 4b1be07f50..98d528d839 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c
@@ -25,6 +25,7 @@
 #include "random.h"
 
 #include "_nib-internal.h"
+#include "_nib-router.h"
 
 #define ENABLE_DEBUG    (0)
 #include "debug.h"
@@ -39,7 +40,7 @@ static _nib_dr_entry_t _def_routers[GNRC_IPV6_NIB_DEFAULT_ROUTER_NUMOF];
 
 #if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
 static _nib_abr_entry_t _abrs[GNRC_IPV6_NIB_ABR_NUMOF];
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
 
 #if ENABLE_DEBUG
 static char addr_str[IPV6_ADDR_MAX_STR_LEN];
@@ -62,8 +63,8 @@ void _nib_init(void)
     memset(_dsts, 0, sizeof(_dsts));
 #if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
     memset(_abrs, 0, sizeof(_abrs));
-#endif
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+#endif  /* TEST_SUITES */
     evtimer_init_msg(&_nib_evtimer);
     /* TODO: load ABR information from persistent memory */
 }
@@ -103,7 +104,7 @@ _nib_onl_entry_t *_nib_onl_alloc(const ipv6_addr_t *addr, unsigned iface)
     else {
         DEBUG("  NIB full\n");
     }
-#endif
+#endif  /* ENABLE_DEBUG */
     return node;
 }
 
@@ -233,15 +234,15 @@ void _nib_nc_set_reachable(_nib_onl_entry_t *node)
     if (netif == NULL) {
         return;
     }
-#endif
+#endif  /* TEST_SUITES */
     DEBUG("nib: set %s%%%u reachable (reachable time = %u)\n",
           ipv6_addr_to_str(addr_str, &node->ipv6, sizeof(addr_str)),
           _nib_onl_get_if(node), (unsigned)netif->ipv6.reach_time);
     _evtimer_add(node, GNRC_IPV6_NIB_REACH_TIMEOUT, &node->nud_timeout,
                  netif->ipv6.reach_time);
-#else
+#else   /* GNRC_IPV6_NIB_CONF_ARSM */
     (void)node;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
 }
 
 void _nib_nc_remove(_nib_onl_entry_t *node)
@@ -253,10 +254,13 @@ void _nib_nc_remove(_nib_onl_entry_t *node)
     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
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
+#if GNRC_IPV6_NIB_CONF_ROUTER
+    evtimer_del((evtimer_t *)&_nib_evtimer, &node->reply_rs.event);
+#endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
 #if GNRC_IPV6_NIB_CONF_6LR
     evtimer_del((evtimer_t *)&_nib_evtimer, &node->addr_reg_timeout.event);
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_6LR */
 #if GNRC_IPV6_NIB_CONF_QUEUE_PKT
     gnrc_pktqueue_t *tmp;
     for (gnrc_pktqueue_t *ptr = node->pktqueue;
@@ -293,17 +297,17 @@ void _nib_nc_get(const _nib_onl_entry_t *node, gnrc_ipv6_nib_nc_t *nce)
             return;
         }
     }
-#else
+#else   /* GNRC_IPV6_NIB_CONF_6LN */
     /* Prevent unused function error thrown by clang */
     (void)_get_l2addr_from_ipv6;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
     nce->l2addr_len = node->l2addr_len;
     memcpy(&nce->l2addr, &node->l2addr, node->l2addr_len);
-#else
+#else   /* GNRC_IPV6_NIB_CONF_ARSM */
     assert(ipv6_addr_is_link_local(&nce->ipv6));
     _get_l2addr_from_ipv6(nce->l2addr, &node->ipv6);
     nce->l2addr_len = sizeof(uint64_t);
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
 }
 
 _nib_dr_entry_t *_nib_drl_add(const ipv6_addr_t *router_addr, unsigned iface)
@@ -495,7 +499,7 @@ static inline bool _in_abrs(const _nib_abr_entry_t *abr)
 {
     return (abr < (_abrs + GNRC_IPV6_NIB_ABR_NUMOF));
 }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
 
 void _nib_offl_clear(_nib_offl_entry_t *dst)
 {
@@ -588,14 +592,22 @@ int _nib_get_route(const ipv6_addr_t *dst, gnrc_pktsnip_t *pkt,
           (void *)pkt);
     _nib_offl_entry_t *offl = _nib_offl_get_match(dst);
 
-    assert((dst != NULL) && (fte != NULL));
     if ((offl == NULL) || (offl->mode == _PL)) {
         /* give default router precedence over PLE */
         _nib_dr_entry_t *router = _nib_drl_get_dr();
 
         if ((router == NULL) && (offl == NULL)) {
+#if GNRC_IPV6_NIB_CONF_ROUTER
+            gnrc_netif2_t *ptr = NULL;
+
+            while ((ptr = gnrc_netif2_iter(ptr))) {
+                _call_route_info_cb(ptr,
+                                    GNRC_IPV6_NIB_ROUTE_INFO_TYPE_RRQ,
+                                    dst, pkt);
+            }
+#else   /* GNRC_IPV6_NIB_CONF_ROUTER */
             (void)pkt;
-            /* TODO: ask RRP to search for route (using pkt) */
+#endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
             return -ENETUNREACH;
         }
         else if (router != NULL) {
@@ -629,7 +641,7 @@ void _nib_pl_remove(_nib_offl_entry_t *nib_offl)
             }
         }
     }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
 }
 
 #if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
@@ -660,7 +672,7 @@ _nib_abr_entry_t *_nib_abr_add(const ipv6_addr_t *addr)
     else {
         DEBUG("  NIB full\n");
     }
-#endif
+#endif  /* ENABLE_DEBUG */
     return abr;
 }
 
@@ -682,7 +694,7 @@ void _nib_abr_remove(const ipv6_addr_t *addr)
                     gnrc_sixlowpan_ctx_remove(i);
                 }
             }
-#endif
+#endif  /* MODULE_GNRC_SIXLOWPAN_CTX */
             memset(abr, 0, sizeof(_nib_abr_entry_t));
         }
     }
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 97af9ec93e..9b914c12a1 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h
@@ -60,6 +60,15 @@ extern "C" {
                                  to this @ref _nib_onl_entry_t */
 /** @} */
 
+/**
+ * @name    Off-link entry flags
+ * @anchor  net_gnrc_ipv6_nib_offl_flags
+ * @{
+ */
+#define _PFX_ON_LINK    (0x0001)
+#define _PFX_SLAAC      (0x0002)
+/** @} */
+
 /**
  * @brief   Shorter name for convenience ;-)
  */
@@ -131,6 +140,9 @@ typedef struct _nib_onl_entry {
      * @brief Event for @ref GNRC_IPV6_NIB_SND_NA
      */
     evtimer_msg_event_t snd_na;
+#if GNRC_IPV6_NIB_CONF_ROUTER || defined(DOXYGEN)
+    evtimer_msg_event_t reply_rs;           /**< Event for @ref GNRC_IPV6_NIB_REPLY_RS */
+#endif
 #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
@@ -150,12 +162,14 @@ 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
      *
@@ -187,6 +201,7 @@ typedef struct {
     evtimer_msg_event_t pfx_timeout;
     uint8_t mode;               /**< [mode](@ref net_gnrc_ipv6_nib_mode) of the
                                  *   off-link entry */
+    uint16_t flags;             /**< [flags](@ref net_gnrc_ipv6_nib_offl_flags */
     uint32_t valid_until;       /**< timestamp (in ms) until which the prefix
                                      valid (UINT32_MAX means forever) */
     uint32_t pref_until;        /**< timestamp (in ms) until which the prefix
@@ -201,6 +216,8 @@ typedef struct {
     ipv6_addr_t addr;               /**< The address of the border router */
     uint32_t version;               /**< last received version of the info of
                                      *   the _nib_abr_entry_t::addr */
+    uint32_t valid_until;           /**< timestamp (in minutes) until which
+                                     *   information is valid */
     evtimer_msg_event_t timeout;    /**< timeout of the information */
     /**
      * @brief   Bitfield marking the prefixes in the NIB's off-link entries
@@ -589,11 +606,15 @@ static inline void _nib_dc_remove(_nib_offl_entry_t *nib_offl)
  * @pre     `(pfx != NULL) && (pfx != "::") && (pfx_len != 0) && (pfx_len <= 128)`
  * @pre     `(pref_ltime <= valid_ltime)`
  *
- * @param[in] iface     The interface to the prefix is added to.
- * @param[in] pfx       The IPv6 prefix or address of the destination.
- *                      May not be NULL or unspecified address. Use
- *                      @ref _nib_drl_add() for default route destinations.
- * @param[in] pfx_len   The length in bits of @p pfx in bits.
+ * @param[in] iface         The interface to the prefix is added to.
+ * @param[in] pfx           The IPv6 prefix or address of the destination.
+ *                          May not be NULL or unspecified address. Use
+ *                          @ref _nib_drl_add() for default route destinations.
+ * @param[in] pfx_len       The length in bits of @p pfx in bits.
+ * @param[in] valid_ltime   Valid lifetime in microseconds. `UINT32_MAX` for
+ *                          infinite.
+ * @param[in] pref_ltime    Preferred lifetime in microseconds. `UINT32_MAX` for
+ *                          infinite.
  *
  * @return  A new or existing off-link entry with _nib_offl_entry_t::pfx set to
  *          @p pfx.
diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.c
new file mode 100644
index 0000000000..90d95baad1
--- /dev/null
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.c
@@ -0,0 +1,203 @@
+/*
+ * 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 "net/gnrc/ndp2.h"
+#include "net/gnrc/netif2/internal.h"
+#include "net/gnrc/sixlowpan/nd.h"
+
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+#include "_nib-6ln.h"
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+#include "_nib-router.h"
+
+#define ENABLE_DEBUG    (0)
+#include "debug.h"
+
+#if GNRC_IPV6_NIB_CONF_ROUTER
+#if ENABLE_DEBUG
+static char addr_str[IPV6_ADDR_MAX_STR_LEN];
+#endif
+
+static void _snd_ra(gnrc_netif2_t *netif, const ipv6_addr_t *dst,
+                    bool final, _nib_abr_entry_t *abr);
+
+void _handle_reply_rs(_nib_onl_entry_t *host)
+{
+    gnrc_netif2_t *netif = gnrc_netif2_get_by_pid(_nib_onl_get_if(host));
+
+    assert(netif != NULL);
+    gnrc_netif2_acquire(netif);
+    if (gnrc_netif2_is_rtr_adv(netif)) {
+        _snd_rtr_advs(netif, &host->ipv6, false);
+    }
+    gnrc_netif2_release(netif);
+}
+
+void _handle_snd_mc_ra(gnrc_netif2_t *netif)
+{
+    gnrc_netif2_acquire(netif);
+    assert(netif != NULL);
+    if (!gnrc_netif2_is_6ln(netif)) {
+        bool final_ra = (netif->ipv6.ra_sent > (UINT8_MAX - NDP_MAX_FIN_RA_NUMOF));
+        uint32_t next_ra_time = random_uint32_range(NDP_MIN_RA_INTERVAL_MS,
+                                                    NDP_MAX_RA_INTERVAL_MS);
+
+        /* router has router advertising interface or the RA is one of the
+         * (now deactivated) routers final one */
+        if (final_ra || gnrc_netif2_is_rtr_adv(netif)) {
+            _snd_rtr_advs(netif, NULL, final_ra);
+            netif->ipv6.last_ra = (xtimer_now_usec64() / US_PER_MS) & UINT32_MAX;
+            if ((netif->ipv6.ra_sent < NDP_MAX_INIT_RA_NUMOF) || final_ra) {
+                if ((netif->ipv6.ra_sent < NDP_MAX_INIT_RA_NUMOF) &&
+                    (next_ra_time > NDP_MAX_INIT_RA_INTERVAL)) {
+                    next_ra_time = NDP_MAX_INIT_RA_INTERVAL;
+                }
+                netif->ipv6.ra_sent++;
+            }
+            /* netif->ipv6.ra_sent overflowed => this was our last final RA */
+            if (netif->ipv6.ra_sent != 0) {
+                _evtimer_add(netif, GNRC_IPV6_NIB_SND_MC_RA, &netif->ipv6.snd_mc_ra,
+                             next_ra_time);
+            }
+        }
+    }
+    gnrc_netif2_release(netif);
+}
+
+void _snd_rtr_advs(gnrc_netif2_t *netif, const ipv6_addr_t *dst, bool final)
+{
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+    _nib_abr_entry_t *abr = NULL;
+
+    DEBUG("nib: Send router advertisements for each border router:\n");
+    while ((abr = _nib_abr_iter(abr))) {
+        DEBUG("    - %s\n", ipv6_addr_to_str(addr_str, &abr->addr,
+                                             sizeof(addr_str)));
+        _snd_ra(netif, dst, final, abr);
+    }
+#else   /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+    _snd_ra(netif, dst, final, NULL);
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+}
+
+static gnrc_pktsnip_t *_offl_to_pio(_nib_offl_entry_t *offl,
+                                    gnrc_pktsnip_t *ext_opts)
+{
+    uint32_t now = (xtimer_now_usec64() / US_PER_MS) & UINT32_MAX;
+    gnrc_pktsnip_t *pio;
+    uint8_t flags = 0;
+    uint32_t valid_ltime = (offl->valid_until == UINT32_MAX) ? UINT32_MAX :
+                           (offl->valid_until - now);
+    uint32_t pref_ltime = (offl->pref_until == UINT32_MAX) ? UINT32_MAX :
+                          (offl->pref_until - now);
+
+    DEBUG("nib: Build PIO for %s/%u\n",
+          ipv6_addr_to_str(addr_str, &offl->pfx, sizeof(addr_str)),
+          offl->pfx_len);
+    if (offl->flags & _PFX_ON_LINK) {
+        flags |= NDP_OPT_PI_FLAGS_L;
+    }
+    if (offl->flags & _PFX_SLAAC) {
+        flags |= NDP_OPT_PI_FLAGS_A;
+    }
+    pio = gnrc_ndp2_opt_pi_build(&offl->pfx, offl->pfx_len, valid_ltime,
+                                 pref_ltime, flags, ext_opts);
+
+    if ((pio == NULL) && (ext_opts != NULL)) {
+        DEBUG("nib: No space left in packet buffer. Not adding PIO\n");
+        return NULL;
+    }
+    return pio;
+}
+
+static gnrc_pktsnip_t *_build_ext_opts(gnrc_netif2_t *netif,
+                                       _nib_abr_entry_t *abr)
+{
+    gnrc_pktsnip_t *ext_opts = NULL;
+    _nib_offl_entry_t *pfx = NULL;
+    unsigned id = netif->pid;
+
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+    uint16_t ltime;
+    gnrc_pktsnip_t *abro;
+
+#ifdef MODULE_GNRC_SIXLOWPAN_CTX
+    for (int i = 0; i < GNRC_SIXLOWPAN_CTX_SIZE; i++) {
+        gnrc_sixlowpan_ctx_t *ctx;
+        if (bf_isset(abr->ctxs, i) &&
+            ((ctx = gnrc_sixlowpan_ctx_lookup_id(i)) != NULL)) {
+            gnrc_pktsnip_t *sixco = gnrc_sixlowpan_nd_opt_6ctx_build(
+                                            ctx->prefix_len, ctx->flags_id,
+                                            ctx->ltime, &ctx->prefix, NULL);
+            if (sixco == NULL) {
+                DEBUG("nib: No space left in packet buffer. Not adding 6LO\n");
+                return NULL;
+            }
+            ext_opts = sixco;
+        }
+    }
+#endif  /* MODULE_GNRC_SIXLOWPAN_CTX */
+    while ((pfx = _nib_abr_iter_pfx(abr, pfx))) {
+        if (_nib_onl_get_if(pfx->next_hop) == id) {
+            if ((ext_opts = _offl_to_pio(pfx, ext_opts)) == NULL) {
+                return NULL;
+            }
+        }
+    }
+    ltime = (gnrc_netif2_is_6lbr(netif)) ?
+            (SIXLOWPAN_ND_OPT_ABR_LTIME_DEFAULT) :
+            (abr->valid_until - _now_min());
+    (void)ltime;    /* gnrc_sixlowpan_nd_opt_abr_build might evaluate to NOP */
+    abro = gnrc_sixlowpan_nd_opt_abr_build(abr->version, ltime, &abr->addr,
+                                           ext_opts);
+    if (abro == NULL) {
+        DEBUG("nib: No space left in packet buffer. Not adding ABRO\n");
+        return NULL;
+    }
+    ext_opts = abro;
+#else   /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+    (void)abr;
+    while ((pfx = _nib_offl_iter(pfx))) {
+        if ((pfx->mode & _PL) && (_nib_onl_get_if(pfx->next_hop) == id)) {
+            if ((ext_opts = _offl_to_pio(pfx, ext_opts)) == NULL) {
+                return NULL;
+            }
+        }
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+    return ext_opts;
+}
+
+void _set_rtr_adv(gnrc_netif2_t *netif)
+{
+    DEBUG("nib: set RTR_ADV flag for interface %i\n", netif->pid);
+    netif->ipv6.ra_sent = 0;
+    netif->flags |= GNRC_NETIF2_FLAGS_IPV6_RTR_ADV;
+    _handle_snd_mc_ra(netif);
+}
+
+static void _snd_ra(gnrc_netif2_t *netif, const ipv6_addr_t *dst,
+                    bool final, _nib_abr_entry_t *abr)
+{
+    gnrc_pktsnip_t *ext_opts = _build_ext_opts(netif, abr);
+
+    gnrc_ndp2_rtr_adv_send(netif, NULL, dst, final, ext_opts);
+}
+#else  /* GNRC_IPV6_NIB_CONF_ROUTER */
+typedef int dont_be_pedantic;
+#endif /* GNRC_IPV6_NIB_CONF_ROUTER */
+
+/** @} */
diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.h b/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.h
new file mode 100644
index 0000000000..1205dc555f
--- /dev/null
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.h
@@ -0,0 +1,132 @@
+/*
+ * 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 router functionality of NIB
+ *
+ * @author  Martine Lenders <m.lenders@fu-berlin.de>
+ */
+#ifndef PRIV_NIB_ROUTER_H
+#define PRIV_NIB_ROUTER_H
+
+#include "net/gnrc/ipv6/nib/conf.h"
+#include "net/gnrc/netif2/internal.h"
+#include "net/gnrc/netif2/ipv6.h"
+#include "net/ipv6/addr.h"
+#include "net/ndp.h"
+
+#include "_nib-internal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GNRC_IPV6_NIB_CONF_ROUTER || defined(DOXYGEN)
+/**
+ * @brief   Initializes interface for router behavior
+ *
+ * @param[in] netif An interface.
+ */
+static inline void _init_iface_router(gnrc_netif2_t *netif)
+{
+    netif->ipv6.rtr_ltime = NDP_RTR_LTIME_SEC;
+    netif->ipv6.last_ra = UINT32_MAX;
+    netif->ipv6.ra_sent = 0;
+    netif->flags |= GNRC_NETIF2_FLAGS_IPV6_FORWARDING;
+#if !GNRC_IPV6_NIB_CONF_6LR || GNRC_IPV6_NIB_CONF_6LBR
+    netif->flags |= GNRC_NETIF2_FLAGS_IPV6_RTR_ADV;
+#endif  /* !GNRC_IPV6_NIB_CONF_6LR || GNRC_IPV6_NIB_CONF_6LBR */
+#if GNRC_IPV6_NIB_CONF_6LBR
+    netif->flags |= GNRC_NETIF2_FLAGS_6LO_ABR;
+#endif  /* GNRC_IPV6_NIB_CONF_6LBR */
+    gnrc_netif2_ipv6_group_join(netif, &ipv6_addr_all_routers_link_local);
+}
+
+/**
+ * @brief   Helper function to safely call the
+ *          [route info callback](@ref gnrc_netif2_ipv6_t::route_info_cb) of an
+ *          interface
+ *
+ * @param[in] netif     An interface.
+ * @param[in] type      [Type](@ref net_gnrc_ipv6_nib_route_info_type) of the
+ *                      route info.
+ * @param[in] ctx_addr  Context address of the route info.
+ * @param[in] ctx       Further context of the route info.
+ */
+static inline void _call_route_info_cb(gnrc_netif2_t *netif, unsigned type,
+                                       const ipv6_addr_t *ctx_addr,
+                                       const void *ctx)
+{
+    if (netif->ipv6.route_info_cb != NULL) {
+        netif->ipv6.route_info_cb(type, ctx_addr, ctx);
+    }
+}
+
+/**
+ * @brief   Handler for @ref GNRC_IPV6_NIB_REPLY_RS event handler
+ *
+ * @param[in] host  Host that sent the router solicitation
+ */
+void _handle_reply_rs(_nib_onl_entry_t *host);
+
+/**
+ * @brief   Handler for @ref GNRC_IPV6_NIB_SND_MC_RA event handler
+ *
+ * @param[in] netif Network interface to send multicast router advertisement
+ *                  over.
+ */
+void _handle_snd_mc_ra(gnrc_netif2_t *netif);
+
+/**
+ * @brief   Set the @ref GNRC_NETIF2_FLAGS_IPV6_RTR_ADV flag for an interface
+ *          and starts advertising that interface as a router
+ *
+ * @param[in] netif Interface to set the @ref GNRC_NETIF2_FLAGS_IPV6_RTR_ADV
+ *                  for.
+ */
+void _set_rtr_adv(gnrc_netif2_t *netif);
+
+/**
+ * @brief   Send router advertisements
+ *
+ * If @ref GNRC_IPV6_NIB_CONF_MULTIHOP_P6C is not 0 this sends one router
+ * advertisement per configured ABR, otherwise it just sends one single router
+ * advertisement for the interface.
+ *
+ * @param[in] netif The interface to send the router advertisement over.
+ * @param[in] dst   Destination address for the router advertisement.
+ * @param[in] final The router advertisement are the final ones of the @p netif
+ *                  (because it was set to be a non-forwarding interface e.g.).
+ */
+void _snd_rtr_advs(gnrc_netif2_t *netif, const ipv6_addr_t *dst,
+                  bool final);
+#else  /* GNRC_IPV6_NIB_CONF_ROUTER */
+#define _init_iface_router(netif)                       (void)netif
+#define _call_route_info_cb(netif, type, ctx_addr, ctx) (void)netif; \
+                                                        (void)type; \
+                                                        (void)ctx_addr; \
+                                                        (void)ctx
+#define _handle_reply_rs(host)                          (void)host
+#define _handle_snd_mc_ra(netif)                        (void)netif
+#define _set_rtr_adv(netif)                             (void)netif
+#define _snd_rtr_advs(netif, dst, final)                (void)netif; \
+                                                        (void)dst; \
+                                                        (void)final
+#endif /* GNRC_IPV6_NIB_CONF_ROUTER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PRIV_NIB_ROUTER_H */
+/** @} */
diff --git a/sys/net/gnrc/network_layer/ipv6/nib/nib.c b/sys/net/gnrc/network_layer/ipv6/nib/nib.c
index 58219d4503..9ff8d15275 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/nib.c
+++ b/sys/net/gnrc/network_layer/ipv6/nib/nib.c
@@ -29,6 +29,7 @@
 
 #include "_nib-internal.h"
 #include "_nib-arsm.h"
+#include "_nib-router.h"
 #include "_nib-6ln.h"
 #include "_nib-6lr.h"
 
@@ -44,12 +45,18 @@ static char addr_str[IPV6_ADDR_MAX_STR_LEN];
 
 #if GNRC_IPV6_NIB_CONF_QUEUE_PKT
 static gnrc_pktqueue_t _queue_pool[GNRC_IPV6_NIB_NUMOF];
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_QUEUE_PKT */
 
 /**
  * @internal
  * @{
  */
+#if GNRC_IPV6_NIB_CONF_ROUTER
+static void _handle_rtr_sol(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
+                            const ndp_rtr_sol_t *rtr_sol, size_t icmpv6_len);
+#endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
+static void _handle_rtr_adv(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
+                            const ndp_rtr_adv_t *rtr_adv, size_t icmpv6_len);
 static void _handle_nbr_sol(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
                             const ndp_nbr_sol_t *nbr_sol, size_t icmpv6_len);
 static void _handle_nbr_adv(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
@@ -59,7 +66,19 @@ static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif2_t *netif,
                           gnrc_pktsnip_t *pkt, gnrc_ipv6_nib_nc_t *nce,
                           _nib_onl_entry_t *entry);
 
+static void _handle_pfx_timeout(_nib_offl_entry_t *pfx);
+static void _handle_rtr_timeout(_nib_dr_entry_t *router);
 static void _handle_snd_na(gnrc_pktsnip_t *pkt);
+#if GNRC_IPV6_NIB_CONF_6LN || GNRC_IPV6_NIB_CONF_SLAAC
+static void _auto_configure_addr(gnrc_netif2_t *netif, const ipv6_addr_t *pfx,
+                                 uint8_t pfx_len);
+#else   /* GNRC_IPV6_NIB_CONF_6LN || GNRC_IPV6_NIB_CONF_SLAAC */
+#define _auto_configure_addr(netif, pfx, pfx_len)   (void)netif; \
+                                                    (void)pfx; \
+                                                    (void)pfx_len
+#endif  /* GNRC_IPV6_NIB_CONF_6LN || GNRC_IPV6_NIB_CONF_SLAAC */
+/* needs to be exported for 6LN's ARO handling */
+void _handle_search_rtr(gnrc_netif2_t *netif);
 /** @} */
 
 void gnrc_ipv6_nib_init(void)
@@ -78,51 +97,20 @@ void gnrc_ipv6_nib_init(void)
 
 void gnrc_ipv6_nib_init_iface(gnrc_netif2_t *netif)
 {
-    ipv6_addr_t addr = IPV6_ADDR_UNSPECIFIED;
-
     assert(netif != NULL);
     DEBUG("nib: Initialize interface %u\n", netif->pid);
     gnrc_netif2_acquire(netif);
-    /* 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(netif);
     netif->ipv6.retrans_time = NDP_RETRANS_TIMER_MS;
-    netif->ipv6.na_sent = 0;
-#if GNRC_IPV6_NIB_CONF_ROUTER
-    netif->ipv6.rtr_ltime = 1800U;
-    netif->ipv6.last_ra = UINT32_MAX;
-    netif->ipv6.ra_sent = 0;
-    netif->flags |= GNRC_NETIF2_FLAGS_IPV6_FORWARDING;
-#if !GNRC_IPV6_NIB_CONF_6LR || GNRC_IPV6_NIB_CONF_6LBR
-    netif->flags |= GNRC_NETIF2_FLAGS_IPV6_RTR_ADV;
-#endif
-#if GNRC_IPV6_NIB_CONF_6LBR
-    netif->flags |= GNRC_NETIF2_FLAGS_6LO_ABR;
-#endif
-    memcpy(&addr, &ipv6_addr_all_routers_link_local, sizeof(addr));
-    if (gnrc_netif2_ipv6_group_join(netif, &addr) < 0) {
-        LOG_ERROR("nib: Can't join link-local all-routers on interface %u\n",
-                  netif->pid);
-        return;
-    }
-#endif
+#if GNRC_IPV6_NIB_CONF_SLAAC || GNRC_IPV6_NIB_CONF_6LN
+    /* TODO: set differently dependent on GNRC_IPV6_NIB_CONF_SLAAC if
+     * alternatives exist */
+    netif->ipv6.aac_mode = GNRC_NETIF2_AAC_AUTO;
+#endif  /* GNRC_IPV6_NIB_CONF_SLAAC || GNRC_IPV6_NIB_CONF_6LN */
+    _init_iface_router(netif);
 #if GNRC_IPV6_NIB_CONF_6LN
     netif->ipv6.rs_sent = 0;
-#endif
-    memcpy(&addr, &ipv6_addr_all_nodes_link_local, sizeof(addr));
-    if (gnrc_netif2_ipv6_group_join(netif, &addr) < 0) {
-        LOG_ERROR("nib: Can't join link-local all-nodes on interface %u\n",
-                  netif->pid);
-        return;
-    }
-#if GNRC_IPV6_NIB_CONF_6LN || GNRC_IPV6_NIB_CONF_SLAAC
-#if GNRC_IPV6_NIB_CONF_6LN
     if (netif->device_type == NETDEV_TYPE_IEEE802154) {
         /* see https://tools.ietf.org/html/rfc6775#section-5.2 */
         uint16_t src_len = IEEE802154_LONG_ADDRESS_LEN;
@@ -134,35 +122,52 @@ void gnrc_ipv6_nib_init_iface(gnrc_netif2_t *netif)
          * directly everything else would deadlock anyway */
         netif->ops->set(netif, &opt);
     }
-#endif
-    uint8_t flags = GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_VALID;
-    /* TODO: set TENTATIVE as soon as there is a SLAAC implementation if not
-     * 6LN ;-) */
-
-    gnrc_netif2_ipv6_get_iid(netif, (eui64_t *)&addr.u64[1]);
-    ipv6_addr_set_link_local_prefix(&addr);
-    if (gnrc_netif2_ipv6_addr_add(netif, &addr, 64U, flags) < 0) {
-        LOG_ERROR("nib: Can't add link-local address on interface %u\n",
-                  netif->pid);
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+    netif->ipv6.na_sent = 0;
+    if (gnrc_netif2_ipv6_group_join(netif,
+                                    &ipv6_addr_all_nodes_link_local) < 0) {
+        DEBUG("nib: Can't join link-local all-nodes on interface %u\n",
+              netif->pid);
+        gnrc_netif2_release(netif);
         return;
     }
-#if GNRC_IPV6_NIB_CONF_ARSM
-    /* TODO: SHOULD delay join between 0 and MAX_RTR_SOLICITATION_DELAY */
-    ipv6_addr_set_solicited_nodes(&addr, &addr);
-    if (gnrc_netif2_ipv6_group_join(netif, &addr) < 0) {
-        LOG_ERROR("nib: Can't join solicited-nodes of link-local address on "
-                  "interface %u\n", netif->pid);
-        return;
+    _auto_configure_addr(netif, &ipv6_addr_link_local_prefix, 64U);
+    if (!(gnrc_netif2_is_rtr_adv(netif)) ||
+        (gnrc_netif2_is_6ln(netif) && !gnrc_netif2_is_6lbr(netif))) {
+        uint32_t next_rs_time = random_uint32_range(0, NDP_MAX_RS_MS_DELAY);
+
+        _evtimer_add(netif, GNRC_IPV6_NIB_SEARCH_RTR, &netif->ipv6.search_rtr,
+                     next_rs_time);
     }
-#endif
-#if GNRC_IPV6_NIB_CONF_SLAAC
-    /* TODO send NS to solicited nodes and wait netif->ipv6.retrans_time to
-     * confirm uniqueness of the link-local address */
-#endif
-#endif
+#if GNRC_IPV6_NIB_CONF_ROUTER
+    else {
+        _handle_snd_mc_ra(netif);
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
     gnrc_netif2_release(netif);
 }
 
+static bool _on_link(const ipv6_addr_t *dst, unsigned *iface)
+{
+    _nib_offl_entry_t *entry = NULL;
+
+#if GNRC_IPV6_NIB_CONF_6LN
+    if (*iface != 0) {
+        if (gnrc_netif2_is_6ln(gnrc_netif2_get_by_pid(*iface))) {
+            return ipv6_addr_is_link_local(dst);
+        }
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+    while ((entry = _nib_offl_iter(entry))) {
+        if ((entry->mode & _PL) && (entry->flags & _PFX_ON_LINK) &&
+            (ipv6_addr_match_prefix(dst, &entry->pfx) >= entry->pfx_len)) {
+            *iface = _nib_onl_get_if(entry->next_hop);
+            return true;
+        }
+    }
+    return ipv6_addr_is_link_local(dst);
+}
+
 int gnrc_ipv6_nib_get_next_hop_l2addr(const ipv6_addr_t *dst,
                                       gnrc_netif2_t *netif, gnrc_pktsnip_t *pkt,
                                       gnrc_ipv6_nib_nc_t *nce)
@@ -175,19 +180,71 @@ int gnrc_ipv6_nib_get_next_hop_l2addr(const ipv6_addr_t *dst,
     gnrc_netif2_acquire(netif);
     mutex_lock(&_nib_mutex);
     do {    /* XXX: hidden goto ;-) */
-        if (ipv6_addr_is_link_local(dst)) {
-            /* TODO: Prefix-based on-link determination */
+        _nib_onl_entry_t *node = _nib_onl_get(dst,
+                                              (netif == NULL) ? 0 : netif->pid);
+        /* consider neighbor cache entries first */
+        unsigned iface = (node == NULL) ? 0 : _nib_onl_get_if(node);
+
+        if ((node != NULL) || _on_link(dst, &iface)) {
+            DEBUG("nib: %s is on-link or in NC, start address resolution\n",
+                  ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)));
+            /* on-link prefixes return their interface */
+            if (!ipv6_addr_is_link_local(dst) && (iface != 0)) {
+                /* release preassumed interface */
+                gnrc_netif2_release(netif);
+                netif = gnrc_netif2_get_by_pid(iface);
+                gnrc_netif2_acquire(netif);
+            }
             if ((netif == NULL) ||
-                !_resolve_addr(dst, netif, pkt, nce,
-                               _nib_onl_get(dst, netif->pid))) {
+                !_resolve_addr(dst, netif, pkt, nce, node)) {
+                DEBUG("nib: host unreachable\n");
                 res = -EHOSTUNREACH;
                 break;
             }
         }
         else {
-            /* TODO: Off-link next hop determination;
-             *       might need netif locking */
-            res = -EHOSTUNREACH;
+            gnrc_ipv6_nib_ft_t route;
+
+            DEBUG("nib: %s is off-link, resolve route\n",
+                  ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)));
+            res = _nib_get_route(dst, pkt, &route);
+            if ((res < 0) || ipv6_addr_is_unspecified(&route.next_hop)) {
+                DEBUG("nib: no route to %s found or is prefix list entry, "
+                      "search neighbor cache\n",
+                      ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)));
+
+                if (res == 0) {
+                    DEBUG("nib: prefix list entry => taking dst as next hop\n");
+                    memcpy(&route.next_hop, dst, sizeof(route.next_hop));
+                }
+                else {
+                    res = -ENETUNREACH;
+                    break;
+                }
+            }
+            if ((netif != NULL) && (netif->pid != route.iface)) {
+                /* drop pre-assumed netif */
+                gnrc_netif2_release(netif);
+            }
+            if ((netif == NULL) || (netif->pid != route.iface)) {
+                /* get actual netif */
+                netif = gnrc_netif2_get_by_pid(route.iface);
+                gnrc_netif2_acquire(netif);
+            }
+            node = _nib_onl_get(&route.next_hop,
+                                (netif == NULL) ? netif->pid : 0);
+            if (_resolve_addr(&route.next_hop, netif, pkt, nce, node)) {
+                _call_route_info_cb(netif,
+                                    GNRC_IPV6_NIB_ROUTE_INFO_TYPE_RN,
+                                    &route.dst,
+                                    (void *)((intptr_t)route.dst_len));
+#if GNRC_IPV6_NIB_CONF_DC
+                _nib_dc_add(&route.next_hop, netif->pid, dst);
+#endif  /* GNRC_IPV6_NIB_CONF_DC */
+            }
+            else {
+                res = -EHOSTUNREACH;
+            }
         }
     } while (0);
     mutex_unlock(&_nib_mutex);
@@ -205,11 +262,11 @@ void gnrc_ipv6_nib_handle_pkt(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
     switch (icmpv6->type) {
 #if GNRC_IPV6_NIB_CONF_ROUTER
         case ICMPV6_RTR_SOL:
-            /* TODO */
+            _handle_rtr_sol(netif, ipv6, (ndp_rtr_sol_t *)icmpv6, icmpv6_len);
             break;
 #endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
         case ICMPV6_RTR_ADV:
-            /* TODO */
+            _handle_rtr_adv(netif, ipv6, (ndp_rtr_adv_t *)icmpv6, icmpv6_len);
             break;
         case ICMPV6_NBR_SOL:
             _handle_nbr_sol(netif, ipv6, (ndp_nbr_sol_t *)icmpv6, icmpv6_len);
@@ -241,7 +298,6 @@ void gnrc_ipv6_nib_handle_timer_event(void *ctx, uint16_t type)
           ctx, type, (unsigned)xtimer_now_usec() / 1000);
     mutex_lock(&_nib_mutex);
     switch (type) {
-        /* TODO: remember netif locking if ctx is a gnrc_netif2_t */
 #if GNRC_IPV6_NIB_CONF_ARSM
         case GNRC_IPV6_NIB_SND_UC_NS:
         case GNRC_IPV6_NIB_SND_MC_NS:
@@ -259,38 +315,37 @@ void gnrc_ipv6_nib_handle_timer_event(void *ctx, uint16_t type)
             _handle_snd_na(ctx);
             break;
         case GNRC_IPV6_NIB_SEARCH_RTR:
-            /* TODO */
-            break;
-        case GNRC_IPV6_NIB_RECONFIRM_RTR:
-            /* TODO */
+            _handle_search_rtr(ctx);
             break;
 #if GNRC_IPV6_NIB_CONF_ROUTER
         case GNRC_IPV6_NIB_REPLY_RS:
-            /* TODO */
+            _handle_reply_rs(ctx);
             break;
         case GNRC_IPV6_NIB_SND_MC_RA:
-            /* TODO */
+            _handle_snd_mc_ra(ctx);
             break;
 #endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
-#if GNRC_IPV6_NIB_CONF_6LN
+#if GNRC_IPV6_NIB_CONF_6LR
         case GNRC_IPV6_NIB_ADDR_REG_TIMEOUT:
-            /* TODO */
+            _nib_nc_remove(ctx);
             break;
-        case GNRC_IPV6_NIB_6LO_CTX_TIMEOUT:
-            /* TODO */
-            break;
-#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+#endif  /* GNRC_IPV6_NIB_CONF_6LR */
 #if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
         case GNRC_IPV6_NIB_ABR_TIMEOUT:
-            /* TODO */
+            _nib_abr_remove(&((_nib_abr_entry_t *)ctx)->addr);
             break;
 #endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
         case GNRC_IPV6_NIB_PFX_TIMEOUT:
-            /* TODO */
+            _handle_pfx_timeout(ctx);
             break;
         case GNRC_IPV6_NIB_RTR_TIMEOUT:
-            /* TODO */
+            _handle_rtr_timeout(ctx);
+            break;
+#if GNRC_IPV6_NIB_CONF_6LN
+        case GNRC_IPV6_NIB_REREG_ADDRESS:
+            _handle_rereg_address(ctx);
             break;
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
         default:
             break;
     }
@@ -300,18 +355,39 @@ void gnrc_ipv6_nib_handle_timer_event(void *ctx, uint16_t type)
 #if GNRC_IPV6_NIB_CONF_ROUTER
 void gnrc_ipv6_nib_change_rtr_adv_iface(gnrc_netif2_t *netif, bool enable)
 {
+    gnrc_netif2_acquire(netif);
     if (enable) {
-        netif->flags |= GNRC_NETIF2_FLAGS_IPV6_RTR_ADV;
-        /* TODO: start router advertisements */
+        _set_rtr_adv(netif);
     }
     else {
+        uint32_t next_rs_time = random_uint32_range(0, NDP_MAX_RS_MS_DELAY);
+
+        netif->ipv6.ra_sent = (UINT8_MAX - NDP_MAX_FIN_RA_NUMOF) + 1;
         netif->flags &= ~GNRC_NETIF2_FLAGS_IPV6_RTR_ADV;
-        /* TODO:
-         *  - start final router advertisements,
-         *  - start router solicitations? */
+        /* send final router advertisements */
+        _handle_snd_mc_ra(netif);
+        _evtimer_add(netif, GNRC_IPV6_NIB_SEARCH_RTR, &netif->ipv6.search_rtr,
+                     next_rs_time);
     }
+    gnrc_netif2_release(netif);
 }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
+
+/*
+ * @internal
+ * @{
+ */
+static void _handle_mtuo(gnrc_netif2_t *netif, const icmpv6_hdr_t *icmpv6,
+                         const ndp_opt_mtu_t *mtuo);
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+static uint32_t _handle_pio(gnrc_netif2_t *netif, const icmpv6_hdr_t *icmpv6,
+                            const ndp_opt_pi_t *pio,
+                            _nib_abr_entry_t *abr);
+#else   /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+static uint32_t _handle_pio(gnrc_netif2_t *netif, const icmpv6_hdr_t *icmpv6,
+                            const ndp_opt_pi_t *pio);
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+/** @} */
 
 /* Iterator for NDP options in a packet */
 #define FOREACH_OPT(ndp_pkt, opt, icmpv6_len) \
@@ -320,16 +396,323 @@ void gnrc_ipv6_nib_change_rtr_adv_iface(gnrc_netif2_t *netif, bool enable)
          icmpv6_len -= (opt->len << 3), \
          opt = (ndp_opt_t *)(((uint8_t *)opt) + (opt->len << 3)))
 
+#if GNRC_IPV6_NIB_CONF_ROUTER
+static void _handle_rtr_sol(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
+                            const ndp_rtr_sol_t *rtr_sol, size_t icmpv6_len)
+{
+    size_t tmp_len = icmpv6_len - sizeof(ndp_rtr_sol_t);
+    _nib_onl_entry_t *nce = NULL;
+    ndp_opt_t *opt;
+    uint32_t next_ra_delay = random_uint32_range(0, NDP_MAX_RA_DELAY);
+
+    assert(netif != NULL);
+    /* check validity, see: https://tools.ietf.org/html/rfc4861#section-6.1.1 */
+    /* checksum is checked by GNRC's ICMPv6 module */
+    if (!(gnrc_netif2_is_rtr(netif)) || (ipv6->hl != 255U) ||
+        (rtr_sol->code != 0U) || (icmpv6_len < sizeof(ndp_rtr_sol_t))) {
+        DEBUG("nib: Received router solicitation is invalid (or interface %i "
+              "is not a forwarding interface). Discarding silently\n",
+              netif->pid);
+        DEBUG("     - IP Hop Limit: %u (should be 255)\n", ipv6->hl);
+        DEBUG("     - ICMP code: %u (should be 0)\n", rtr_sol->code);
+        DEBUG("     - ICMP length: %u (should > %u)\n", icmpv6_len,
+              sizeof(ndp_rtr_sol_t));
+        return;
+    }
+    /* pre-check option length */
+    FOREACH_OPT(rtr_sol, opt, tmp_len) {
+        if (tmp_len > icmpv6_len) {
+            DEBUG("nib: Payload length (%u) of RS doesn't align with options\n",
+                  (unsigned)icmpv6_len);
+            return;
+        }
+        if (opt->len == 0U) {
+            DEBUG("nib: Option of length 0 detected. "
+                  "Discarding router solicitation silently\n");
+            return;
+        }
+        if ((opt->type == NDP_OPT_SL2A) &&
+            ipv6_addr_is_unspecified(&ipv6->src)) {
+            DEBUG("nib: RS contains SLLAO, but source was unspecfied. "
+                  "Discarding router solicitation silently\n");
+            return;
+        }
+    }
+    DEBUG("nib: Received valid router solicitation:\n");
+    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 (!ipv6_addr_is_unspecified(&ipv6->src)) {
+        tmp_len = icmpv6_len - sizeof(ndp_rtr_sol_t);
+        FOREACH_OPT(rtr_sol, opt, tmp_len) {
+            switch (opt->type) {
+                case NDP_OPT_SL2A:
+                    if (!gnrc_netif2_is_6ln(netif)) {
+                        _handle_sl2ao(netif, ipv6, (const icmpv6_hdr_t *)rtr_sol,
+                                      opt);
+                    }
+
+                    break;
+                default:
+                    break;
+            }
+        }
+        nce = _nib_onl_get(&ipv6->src, netif->pid);
+    }
+    if (!gnrc_netif2_is_6ln(netif)) {
+        uint32_t next_ra_scheduled = _evtimer_lookup(netif,
+                                                     GNRC_IPV6_NIB_SND_MC_RA);
+        if (next_ra_scheduled < next_ra_delay) {
+            DEBUG("nib: There is a MC RA scheduled within the next %" PRIu32 "ms. "
+                  "Using that to advertise router\n", next_ra_scheduled);
+            return;
+        }
+        else if (nce != NULL) {
+            /* we send unicast RAs so we do not need to rate-limit as
+             * https://tools.ietf.org/html/rfc4861#section-6.2.6 asks for */
+            _evtimer_add(nce, GNRC_IPV6_NIB_REPLY_RS, &nce->reply_rs,
+                         next_ra_delay);
+        }
+        else {
+            uint32_t now = (xtimer_now_usec64() / MS_PER_SEC) & UINT32_MAX;
+
+            /* check for integer overflows and initial value of last_ra */
+            if (((netif->ipv6.last_ra > (UINT32_MAX - NDP_MIN_MS_DELAY_BETWEEN_RAS) &&
+                  (now < NDP_MIN_MS_DELAY_BETWEEN_RAS))) ||
+                ((now - NDP_MIN_MS_DELAY_BETWEEN_RAS) > netif->ipv6.last_ra)) {
+                next_ra_delay += NDP_MIN_MS_DELAY_BETWEEN_RAS;
+            }
+            _evtimer_add(netif, GNRC_IPV6_NIB_SND_MC_RA, &netif->ipv6.snd_mc_ra,
+                         next_ra_delay);
+        }
+    }
+#if GNRC_IPV6_NIB_CONF_6LR
+    else if (gnrc_netif2_is_rtr(netif) && gnrc_netif2_is_rtr_adv(netif)) {
+        _snd_rtr_advs(netif, &ipv6->src, false);
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_6LR */
+}
+#endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
+
+static inline uint32_t _min(uint32_t a, uint32_t b)
+{
+    return (a < b) ? a : b;
+}
+
+static void _handle_rtr_adv(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
+                            const ndp_rtr_adv_t *rtr_adv, size_t icmpv6_len)
+{
+    size_t tmp_len = icmpv6_len - sizeof(ndp_rtr_adv_t);
+    _nib_dr_entry_t *dr = NULL;
+    ndp_opt_t *opt;
+
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+    sixlowpan_nd_opt_abr_t *abro = NULL;
+    _nib_abr_entry_t *abr = NULL;
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+    uint32_t next_timeout = UINT32_MAX;
+
+    assert(netif != NULL);
+    /* check validity, see: https://tools.ietf.org/html/rfc4861#section-6.1.1 */
+    /* checksum is checked by GNRC's ICMPv6 module */
+    if (!(ipv6_addr_is_link_local(&ipv6->src)) ||
+        (ipv6->hl != 255U) || (rtr_adv->code != 0U) ||
+        (icmpv6_len < sizeof(ndp_rtr_adv_t)) ||
+        (!gnrc_netif2_is_6ln(netif) &&
+         (byteorder_ntohs(rtr_adv->ltime) > NDP_RTR_ADV_LTIME_SEC_MAX))) {
+        DEBUG("nib: Received router 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", rtr_adv->code);
+        DEBUG("     - ICMP length: %u (should > %u)\n", (unsigned)icmpv6_len,
+              sizeof(ndp_rtr_adv_t));
+        DEBUG("     - Source address: %s (should be link-local)\n",
+              ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str)));
+        DEBUG("     - Router lifetime: %u (should be <= 9000 on non-6LN)\n",
+              byteorder_ntohs(rtr_adv->ltime));
+        return;
+    }
+    /* pre-check option length */
+    FOREACH_OPT(rtr_adv, opt, tmp_len) {
+        if (tmp_len > icmpv6_len) {
+            DEBUG("nib: Payload length (%u) of RA doesn't align with options\n",
+                  (unsigned)icmpv6_len);
+            return;
+        }
+        if (opt->len == 0U) {
+            DEBUG("nib: Option of length 0 detected. "
+                  "Discarding router advertisement silently\n");
+            return;
+        }
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+        if (opt->type == NDP_OPT_ABR) {
+            if (abro != NULL) {
+                DEBUG("nib: More than one ABRO. "
+                      "Discarding router advertisement silently\n");
+                return;
+            }
+            abro = (sixlowpan_nd_opt_abr_t *)opt;
+        }
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+    }
+    DEBUG("nib: Received valid router advertisement:\n");
+    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("     - Cur Hop Limit: %u\n", rtr_adv->cur_hl);
+    DEBUG("     - Flags: %c%c\n",
+          (rtr_adv->flags & NDP_RTR_ADV_FLAGS_M) ? 'M' : '-',
+          (rtr_adv->flags & NDP_RTR_ADV_FLAGS_O) ? 'O' : '-');
+    DEBUG("     - Router Lifetime: %us\n", byteorder_ntohs(rtr_adv->ltime));
+    DEBUG("     - Reachable Time: %" PRIu32 "ms\n",
+          byteorder_ntohl(rtr_adv->reach_time));
+    DEBUG("     - Retrans Timer: %" PRIu32 "ms\n",
+          byteorder_ntohl(rtr_adv->retrans_timer));
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+    if (abro != NULL) {
+        if ((abr = _handle_abro(abro)) == NULL) {
+            DEBUG("nib: could not allocate space for new border router or "
+                  "there is no new information in the RA. "
+                  "Discarding silently\n");
+            return;
+        }
+        /* UINT16_MAX * 60 * 1000 < UINT32_MAX so there are no overflows */
+        next_timeout = _min(next_timeout,
+                            byteorder_ntohs(abro->ltime) * SEC_PER_MIN *
+                            MS_PER_SEC);
+    }
+#if !GNRC_IPV6_NIB_CONF_6LBR
+    else {
+        DEBUG("nib: multihop prefix and context dissemination activated,\n"
+              "     but no ABRO found. Discarding router advertisement silently\n");
+        return;
+    }
+#endif  /* !GNRC_IPV6_NIB_CONF_6LBR */
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+    if (rtr_adv->ltime.u16 != 0) {
+        dr = _nib_drl_add(&ipv6->src, netif->pid);
+        if (dr != NULL) {
+            dr->ltime = byteorder_ntohs(rtr_adv->ltime);
+        }
+        else {
+            DEBUG("nib: default router list is full. Ignoring RA from %s\n",
+                  ipv6_addr_to_str(addr_str, &ipv6->src, sizeof(addr_str)));
+            return;
+        }
+        /* UINT16_MAX * 1000 < UINT32_MAX so there are no overflows */
+        next_timeout = _min(next_timeout, dr->ltime * MS_PER_SEC);
+    }
+    else {
+        dr = _nib_drl_get(&ipv6->src, netif->pid);
+
+        DEBUG("nib: router lifetime was 0. Removing router and routes via it.");
+        if (dr != NULL) {
+            _handle_rtr_timeout(dr);
+        }
+        dr = NULL;
+    }
+    if (rtr_adv->cur_hl != 0) {
+        netif->cur_hl = rtr_adv->cur_hl;
+    }
+#if GNRC_IPV6_NIB_CONF_ARSM
+    if (rtr_adv->reach_time.u32 != 0) {
+        uint32_t reach_time = byteorder_ntohl(rtr_adv->reach_time);
+
+        if (reach_time != netif->ipv6.reach_time_base) {
+            _evtimer_add(netif, GNRC_IPV6_NIB_RECALC_REACH_TIME,
+                         &netif->ipv6.recalc_reach_time,
+                         GNRC_IPV6_NIB_CONF_REACH_TIME_RESET);
+            netif->ipv6.reach_time_base = reach_time;
+            _recalc_reach_time(&netif->ipv6);
+        }
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
+    if (rtr_adv->retrans_timer.u32 != 0) {
+        netif->ipv6.retrans_time = byteorder_ntohl(rtr_adv->retrans_timer);
+    }
+#if GNRC_IPV6_NIB_CONF_6LN
+    if ((dr != NULL) && gnrc_netif2_is_6ln(netif) &&
+        !gnrc_netif2_is_6lbr(netif) &&
+        !(netif->flags & GNRC_NETIF2_FLAGS_6LO_ADDRS_REG)) {
+        /* (register addresses already assigned)*/
+        for (int i = 0; i < GNRC_NETIF2_IPV6_ADDRS_NUMOF; i++) {
+            if ((netif->ipv6.addrs_flags[i] != 0)) {
+                _handle_rereg_address(&netif->ipv6.addrs[i]);
+            }
+        }
+        netif->flags |= GNRC_NETIF2_FLAGS_6LO_ADDRS_REG;
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+    tmp_len = icmpv6_len - sizeof(ndp_rtr_adv_t);
+    FOREACH_OPT(rtr_adv, opt, tmp_len) {
+        switch (opt->type) {
+            case NDP_OPT_SL2A:
+                _handle_sl2ao(netif, ipv6, (const icmpv6_hdr_t *)rtr_adv,
+                              opt);
+
+                break;
+            case NDP_OPT_MTU:
+                _handle_mtuo(netif, (const icmpv6_hdr_t *)rtr_adv,
+                             (ndp_opt_mtu_t *)opt);
+                break;
+            case NDP_OPT_PI: {
+                uint32_t min_pfx_timeout;
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+                min_pfx_timeout = _handle_pio(netif,
+                                              (const icmpv6_hdr_t *)rtr_adv,
+                                              (ndp_opt_pi_t *)opt, abr);
+#else   /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+                min_pfx_timeout = _handle_pio(netif,
+                                              (const icmpv6_hdr_t *)rtr_adv,
+                                              (ndp_opt_pi_t *)opt);
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+                next_timeout = _min(next_timeout, min_pfx_timeout);
+                break;
+            }
+            /* ABRO was already secured in the option check above */
+#if GNRC_IPV6_NIB_CONF_6LN
+            case NDP_OPT_6CTX:
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+                next_timeout = _min(_handle_6co((icmpv6_hdr_t *)rtr_adv,
+                                                (sixlowpan_nd_opt_6ctx_t *)opt,
+                                                abr), next_timeout);
+#else   /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+                next_timeout = _min(_handle_6co((icmpv6_hdr_t *)rtr_adv,
+                                                (sixlowpan_nd_opt_6ctx_t *)opt),
+                                    next_timeout);
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+                break;
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+            default:
+                break;
+        }
+    }
+    /* stop sending router solicitations
+     * see https://tools.ietf.org/html/rfc4861#section-6.3.7 */
+    evtimer_del(&_nib_evtimer, &netif->ipv6.search_rtr.event);
+#if GNRC_IPV6_NIB_CONF_6LN
+    if (gnrc_netif2_is_6ln(netif) && !gnrc_netif2_is_6lbr(netif)) {
+        _set_rtr_adv(netif);
+        /* but re-fetch information from router in time */
+        _evtimer_add(netif, GNRC_IPV6_NIB_SEARCH_RTR,
+                     &netif->ipv6.search_rtr, (next_timeout >> 2) * 3);
+        /* i.e. 3/4 of the time before the earliest expires */
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+}
+
 static inline size_t _get_l2src(const gnrc_netif2_t *netif, uint8_t *l2src)
 {
 #if GNRC_NETIF2_L2ADDR_MAXLEN > 0
     memcpy(l2src, netif->l2addr, netif->l2addr_len);
     return netif->l2addr_len;
-#else
+#else   /* GNRC_NETIF2_L2ADDR_MAXLEN > 0 */
     (void)netif;
     (void)l2src;
     return 0;
-#endif
+#endif  /* GNRC_NETIF2_L2ADDR_MAXLEN > 0 */
 }
 
 static void _send_delayed_nbr_adv(const gnrc_netif2_t *netif,
@@ -345,7 +728,7 @@ static void _send_delayed_nbr_adv(const gnrc_netif2_t *netif,
     if (gnrc_netif2_is_rtr(netif)) {
         reply_flags |= NDP_NBR_ADV_FLAGS_R;
     }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
 #if GNRC_NETIF2_L2ADDR_MAXLEN > 0
     if (ipv6_addr_is_multicast(dst)) {
         uint8_t l2addr[GNRC_NETIF2_L2ADDR_MAXLEN];
@@ -369,7 +752,7 @@ static void _send_delayed_nbr_adv(const gnrc_netif2_t *netif,
     }
 #else /* GNRC_NETIF2_L2ADDR_MAXLEN > 0 */
     reply_flags |= NDP_NBR_ADV_FLAGS_O;
-#endif
+#endif  /* GNRC_NETIF2_L2ADDR_MAXLEN > 0 */
     /* discard const qualifier */
     nbr_adv = gnrc_ndp2_nbr_adv_build(tgt, reply_flags, extra_opts);
     if (nbr_adv == NULL) {
@@ -382,8 +765,7 @@ static void _send_delayed_nbr_adv(const gnrc_netif2_t *netif,
         /* 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,
+        _evtimer_add(nce, GNRC_IPV6_NIB_SND_NA, &nce->snd_na,
                      random_uint32_range(0, NDP_MAX_ANYCAST_MS_DELAY));
     }
 }
@@ -412,7 +794,7 @@ static void _handle_nbr_sol(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
         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",
+              "ff02::1:ffxx:xxxx if source address is ::)\n",
               ipv6_addr_to_str(addr_str, &ipv6->dst, sizeof(addr_str)));
         return;
     }
@@ -484,6 +866,7 @@ static void _handle_nbr_sol(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
                 default:
                     DEBUG("nib: Ignoring unrecognized option type %u for NS\n",
                           opt->type);
+                    break;
             }
         }
         reply_aro = _copy_and_handle_aro(netif, ipv6, nbr_sol, aro, sl2ao);
@@ -554,12 +937,12 @@ static void _handle_nbr_adv(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
           (nbr_adv->flags & NDP_NBR_ADV_FLAGS_O) ? 'O' : '-');
 #if GNRC_IPV6_NIB_CONF_SLAAC
     /* TODO SLAAC behavior */
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_SLAAC */
     if (((nce = _nib_onl_get(&nbr_adv->tgt, netif->pid)) != NULL) &&
         (nce->mode & _NC)) {
 #if GNRC_IPV6_NIB_CONF_ARSM
         bool tl2ao_avail = false;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
 
         tmp_len = icmpv6_len - sizeof(ndp_nbr_adv_t);
         FOREACH_OPT(nbr_adv, opt, tmp_len) {
@@ -569,13 +952,13 @@ static void _handle_nbr_adv(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
                     _handle_adv_l2(netif, nce, (icmpv6_hdr_t *)nbr_adv, opt);
                     tl2ao_avail = true;
                     break;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
 #if GNRC_IPV6_NIB_CONF_6LN
                 case NDP_OPT_AR:
                     _handle_aro(netif, ipv6, (const icmpv6_hdr_t *)nbr_adv,
                                 (const sixlowpan_nd_opt_ar_t *)opt, opt, nce);
                     break;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
                 default:
                     DEBUG("nib: Ignoring unrecognized option type %u for NA\n",
                           opt->type);
@@ -590,7 +973,7 @@ static void _handle_nbr_adv(gnrc_netif2_t *netif, const ipv6_hdr_t *ipv6,
         if (!(netif->flags & GNRC_NETIF2_FLAGS_HAS_L2ADDR)) {
             _handle_adv_l2(netif, nce, (icmpv6_hdr_t *)nbr_adv, NULL);
         }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
     }
 }
 
@@ -606,7 +989,7 @@ static inline bool _is_reachable(_nib_onl_entry_t *entry)
             return true;
     }
 }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
 
 #if GNRC_IPV6_NIB_CONF_QUEUE_PKT
 static gnrc_pktqueue_t *_alloc_queue_entry(gnrc_pktsnip_t *pkt)
@@ -619,13 +1002,14 @@ static gnrc_pktqueue_t *_alloc_queue_entry(gnrc_pktsnip_t *pkt)
     }
     return NULL;
 }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_QUEUE_PKT */
 
 static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif2_t *netif,
                           gnrc_pktsnip_t *pkt, gnrc_ipv6_nib_nc_t *nce,
                           _nib_onl_entry_t *entry)
 {
     bool res = false;
+
     if ((netif != NULL) && (netif->device_type == NETDEV_TYPE_SLIP)) {
         /* XXX: Linux doesn't do neighbor discovery for SLIP so no use sending
          * NS and since SLIP doesn't have link-layer addresses anyway, we can
@@ -648,7 +1032,7 @@ static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif2_t *netif,
         _nib_nc_get(entry, nce);
         res = true;
     }
-#else
+#else   /* GNRC_IPV6_NIB_CONF_ARSM */
     if (entry != NULL) {
         DEBUG("nib: resolve address %s%%%u from neighbor cache\n",
               ipv6_addr_to_str(addr_str, &entry->ipv6, sizeof(addr_str)),
@@ -656,11 +1040,11 @@ static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif2_t *netif,
         _nib_nc_get(entry, nce);
         res = true;
     }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
     else if (!(res = _resolve_addr_from_ipv6(dst, netif, nce))) {
 #if GNRC_IPV6_NIB_CONF_ARSM
         bool reset = false;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
 
         DEBUG("nib: resolve address %s by probing neighbors\n",
               ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)));
@@ -671,14 +1055,16 @@ static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif2_t *netif,
                 return false;
             }
 #if GNRC_IPV6_NIB_CONF_ROUTER
-            if ((netif != NULL) && (netif->ipv6.route_info_cb != NULL)) {
-                netif->ipv6.route_info_cb(GNRC_IPV6_NIB_ROUTE_INFO_TYPE_NSC,
-                      dst, (void *)GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE);
+            if (netif != NULL) {
+                _call_route_info_cb(netif,
+                                    GNRC_IPV6_NIB_ROUTE_INFO_TYPE_NSC,
+                                    dst,
+                                    (void *)GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE);
             }
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ROUTER */
 #if GNRC_IPV6_NIB_CONF_ARSM
             reset = true;
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
         }
         if (pkt != NULL) {
 #if GNRC_IPV6_NIB_CONF_QUEUE_PKT
@@ -698,7 +1084,7 @@ static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif2_t *netif,
         }
 #if GNRC_IPV6_NIB_CONF_ARSM
         _probe_nbr(entry, reset);
-#endif
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
     }
     return res;
 }
@@ -712,10 +1098,257 @@ static void _handle_snd_na(gnrc_pktsnip_t *pkt)
         DEBUG("nib: No receivers for neighbor advertisement\n");
         gnrc_pktbuf_release_error(pkt, EBADF);
     }
-#else
+#else   /* MODULE_GNRC_IPV6 */
     (void)pkt;
     DEBUG("nib: No IPv6 module to send delayed neighbor advertisement\n");
-#endif
+#endif  /* MODULE_GNRC_IPV6 */
+}
+
+static void _handle_pfx_timeout(_nib_offl_entry_t *pfx)
+{
+    gnrc_netif2_t *netif = gnrc_netif2_get_by_pid(_nib_onl_get_if(pfx->next_hop));
+    uint32_t now = (xtimer_now_usec64() / US_PER_MS) & UINT32_MAX;
+
+    gnrc_netif2_acquire(netif);
+    if (now >= pfx->valid_until) {
+        evtimer_del(&_nib_evtimer, &pfx->pfx_timeout.event);
+        for (int i = 0; i < GNRC_NETIF2_IPV6_ADDRS_NUMOF; i++) {
+            if (ipv6_addr_match_prefix(&netif->ipv6.addrs[i],
+                                       &pfx->pfx) >= pfx->pfx_len) {
+                gnrc_netif2_ipv6_addr_remove(netif, &netif->ipv6.addrs[i]);
+            }
+        }
+        pfx->mode &= ~_PL;
+        _nib_offl_clear(pfx);
+    }
+    else if (now >= pfx->pref_until) {
+        for (int i = 0; i < GNRC_NETIF2_IPV6_ADDRS_NUMOF; i++) {
+            if (ipv6_addr_match_prefix(&netif->ipv6.addrs[i],
+                                       &pfx->pfx) >= pfx->pfx_len) {
+                netif->ipv6.addrs_flags[i] &= ~GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_MASK;
+                netif->ipv6.addrs_flags[i] |= GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_DEPRECATED;
+            }
+        }
+        _evtimer_add(pfx, GNRC_IPV6_NIB_PFX_TIMEOUT, &pfx->pfx_timeout,
+                     pfx->valid_until - now);
+    }
+    gnrc_netif2_release(netif);
+}
+
+static void _handle_rtr_timeout(_nib_dr_entry_t *router)
+{
+    if ((router->next_hop != NULL) && (router->next_hop->mode & _DRL)) {
+        _nib_offl_entry_t *route = NULL;
+        unsigned iface = _nib_onl_get_if(router->next_hop);
+        ipv6_addr_t addr;
+
+        memcpy(&addr, &router->next_hop, sizeof(addr));
+        _nib_drl_remove(router);
+        /* also remove all routes to that router */
+        while ((route = _nib_offl_iter(route))) {
+            if ((route->next_hop != NULL) &&
+                (_nib_onl_get_if(route->next_hop) == iface) &&
+                (ipv6_addr_equal(&route->next_hop->ipv6, &addr))) {
+                route->mode = _EMPTY;
+                route->next_hop->mode &= ~_DST;
+                _nib_offl_clear(route);
+                /* XXX routing protocol get's informed in case NUD
+                 * determines ipv6->src (still in neighbor cache) to be
+                 * unreachable */
+            }
+        }
+    }
+}
+
+void _handle_search_rtr(gnrc_netif2_t *netif)
+{
+    gnrc_netif2_acquire(netif);
+    if (!(gnrc_netif2_is_rtr_adv(netif)) || gnrc_netif2_is_6ln(netif)) {
+        uint32_t next_rs = _evtimer_lookup(netif, GNRC_IPV6_NIB_SEARCH_RTR);
+        uint32_t interval = _get_next_rs_interval(netif);
+
+        if (next_rs > interval) {
+            gnrc_ndp2_rtr_sol_send(netif, &ipv6_addr_all_routers_link_local);
+            if (netif->ipv6.rs_sent < 10U) {
+                /* with more the backoff (required in RFC 6775) is truncated
+                 * anyway and this way we prevent overflows. 10 is arbitrary, so
+                 * we do not need a define here */
+                netif->ipv6.rs_sent++;
+            }
+            if ((netif->ipv6.rs_sent < NDP_MAX_RS_NUMOF) ||
+                gnrc_netif2_is_6ln(netif)) {
+                /* 6LN will solicitate indefinitely */
+                _evtimer_add(netif, GNRC_IPV6_NIB_SEARCH_RTR,
+                             &netif->ipv6.search_rtr, interval);
+            }
+        }
+    }
+    gnrc_netif2_release(netif);
+}
+
+static void _handle_mtuo(gnrc_netif2_t *netif, const icmpv6_hdr_t *icmpv6,
+                         const ndp_opt_mtu_t *mtuo)
+{
+    if ((mtuo->len != NDP_OPT_MTU_LEN) || (icmpv6->type != ICMPV6_RTR_ADV)) {
+        return;
+    }
+    if (byteorder_ntohl(mtuo->mtu) >= IPV6_MIN_MTU) {
+        netif->ipv6.mtu = byteorder_ntohl(mtuo->mtu);
+    }
+}
+
+static void _remove_prefix(const ipv6_addr_t *pfx, unsigned pfx_len)
+{
+    _nib_offl_entry_t *offl = NULL;
+
+    while ((offl = _nib_offl_iter(offl))) {
+        if ((offl->mode & _PL) &&
+            (offl->pfx_len == pfx_len) &&
+            (ipv6_addr_match_prefix(&offl->pfx, pfx) >= pfx_len)) {
+            _nib_pl_remove(offl);
+        }
+    }
+    return;
+}
+
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+static uint32_t _handle_pio(gnrc_netif2_t *netif, const icmpv6_hdr_t *icmpv6,
+                            const ndp_opt_pi_t *pio, _nib_abr_entry_t *abr)
+#else   /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+static uint32_t _handle_pio(gnrc_netif2_t *netif, const icmpv6_hdr_t *icmpv6,
+                            const ndp_opt_pi_t *pio)
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+{
+    uint32_t valid_ltime;
+    uint32_t pref_ltime;
+
+    valid_ltime = byteorder_ntohl(pio->valid_ltime);
+    pref_ltime = byteorder_ntohl(pio->pref_ltime);
+    if ((pio->len != NDP_OPT_PI_LEN) || (icmpv6->type != ICMPV6_RTR_ADV) ||
+        ipv6_addr_is_link_local(&pio->prefix) || (valid_ltime < pref_ltime)) {
+        DEBUG("nib: ignoring PIO with invalid data\n");
+        return UINT32_MAX;
+    }
+    DEBUG("nib: received valid Prefix Information option:\n");
+    DEBUG("     - Prefix: %s/%u\n",
+          ipv6_addr_to_str(addr_str, &pio->prefix, sizeof(addr_str)),
+          pio->prefix_len);
+    DEBUG("     - Flags: %c%c\n",
+          (pio->flags & NDP_OPT_PI_FLAGS_L) ? 'L' : '-',
+          (pio->flags & NDP_OPT_PI_FLAGS_A) ? 'A' : '-');
+    DEBUG("     - Valid lifetime: %" PRIu32 "\n",
+          byteorder_ntohl(pio->valid_ltime));
+    DEBUG("     - Preferred lifetime: %" PRIu32 "\n",
+          byteorder_ntohl(pio->pref_ltime));
+
+#if GNRC_IPV6_NIB_CONF_SLAAC || GNRC_IPV6_NIB_CONF_6LN
+    if (pio->flags & NDP_OPT_PI_FLAGS_A) {
+        _auto_configure_addr(netif, &pio->prefix, pio->prefix_len);
+    }
+#endif /* GNRC_IPV6_NIB_CONF_SLAAC || GNRC_IPV6_NIB_CONF_6LN */
+    if ((pio->flags & NDP_OPT_PI_FLAGS_L) || gnrc_netif2_is_6lr(netif)) {
+        _nib_offl_entry_t *pfx;
+
+        if (pio->valid_ltime.u32 == 0) {
+            DEBUG("nib: PIO for %s/%u with lifetime 0. Removing prefix.\n",
+                  ipv6_addr_to_str(addr_str, &pio->prefix, sizeof(addr_str)),
+                  pio->prefix_len);
+            _remove_prefix(&pio->prefix, pio->prefix_len);
+            return UINT32_MAX;
+        }
+
+        if (valid_ltime < UINT32_MAX) { /* UINT32_MAX means infinite lifetime */
+            /* the valid lifetime is given in seconds, but our timers work in
+             * microseconds, so we have to scale down to the smallest possible
+             * value (UINT32_MAX). This is however alright since we ask for a
+             * new router advertisement before this timeout expires */
+            valid_ltime = (valid_ltime > (UINT32_MAX / MS_PER_SEC)) ?
+                          UINT32_MAX : valid_ltime * MS_PER_SEC;
+        }
+        if (pref_ltime < UINT32_MAX) { /* UINT32_MAX means infinite lifetime */
+            /* same treatment for pref_ltime */
+            pref_ltime = (pref_ltime > (UINT32_MAX / MS_PER_SEC)) ?
+                         UINT32_MAX : pref_ltime * MS_PER_SEC;
+        }
+        if ((pfx = _nib_pl_add(netif->pid, &pio->prefix, pio->prefix_len,
+                               valid_ltime, pref_ltime))) {
+#if GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+            assert(abr != NULL);    /* should have been set in _handle_abro() */
+            _nib_abr_add_pfx(abr, pfx);
+#endif  /* GNRC_IPV6_NIB_CONF_MULTIHOP_P6C */
+            if (pio->flags & NDP_OPT_PI_FLAGS_L) {
+                pfx->flags |= _PFX_ON_LINK;
+            }
+            if (pio->flags & NDP_OPT_PI_FLAGS_A) {
+                pfx->flags |= _PFX_SLAAC;
+            }
+            return _min(pref_ltime, valid_ltime);
+        }
+    }
+    return UINT32_MAX;
+}
+
+#if GNRC_IPV6_NIB_CONF_6LN || GNRC_IPV6_NIB_CONF_SLAAC
+static void _auto_configure_addr(gnrc_netif2_t *netif, const ipv6_addr_t *pfx,
+                                 uint8_t pfx_len)
+{
+    ipv6_addr_t addr = IPV6_ADDR_UNSPECIFIED;
+    int idx;
+    uint8_t flags = GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_TENTATIVE;
+
+    DEBUG("nib: add address based on %s/%u automatically to interface %u\n",
+          ipv6_addr_to_str(addr_str, pfx, sizeof(addr_str)),
+          pfx_len, netif->pid);
+#if GNRC_IPV6_NIB_CONF_6LN
+    bool new_address = false;
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+    gnrc_netif2_ipv6_get_iid(netif, (eui64_t *)&addr.u64[1]);
+    ipv6_addr_init_prefix(&addr, pfx, pfx_len);
+    if ((idx = gnrc_netif2_ipv6_addr_idx(netif, &addr)) < 0) {
+        if ((idx = gnrc_netif2_ipv6_addr_add(netif, &addr, pfx_len, flags)) < 0) {
+            DEBUG("nib: Can't add link-local address on interface %u\n",
+                  netif->pid);
+            return;
+        }
+#if GNRC_IPV6_NIB_CONF_6LN
+        new_address = true;
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+    }
+
+#if GNRC_IPV6_NIB_CONF_6LN
+    if (gnrc_netif2_is_6ln(netif)) {
+        /* don't do this beforehand or risk a deadlock:
+         *  * gnrc_netif2_ipv6_addr_add() adds VALID (i.e. manually configured
+         *    addresses to the prefix list locking the NIB's mutex which is already
+         *    locked here) */
+        netif->ipv6.addrs_flags[idx] &= ~GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_MASK;
+        netif->ipv6.addrs_flags[idx] |= GNRC_NETIF2_IPV6_ADDRS_FLAGS_STATE_VALID;
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+    (void)idx;
+    /* TODO: make this line conditional on 6LN when there is a SLAAC
+     * implementation */
+#if GNRC_IPV6_NIB_CONF_ARSM
+    /* TODO: SHOULD delay join between 0 and MAX_RTR_SOLICITATION_DELAY
+     * for SLAAC */
+    ipv6_addr_set_solicited_nodes(&addr, &addr);
+    if (gnrc_netif2_ipv6_group_join(netif, &addr) < 0) {
+        DEBUG("nib: Can't join solicited-nodes of link-local address on "
+              "interface %u\n", netif->pid);
+        return;
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_ARSM */
+#if GNRC_IPV6_NIB_CONF_6LN
+    if (new_address && gnrc_netif2_is_6ln(netif) &&
+        !gnrc_netif2_is_6lbr(netif)) {
+        _handle_rereg_address(&netif->ipv6.addrs[idx]);
+    }
+#endif  /* GNRC_IPV6_NIB_CONF_6LN */
+#if GNRC_IPV6_NIB_CONF_SLAAC
+    /* TODO send NS to solicited nodes and wait netif->ipv6.retrans_time to
+     * confirm uniqueness of the link-local address */
+#endif  /* GNRC_IPV6_NIB_CONF_SLAAC */
 }
+#endif  /* GNRC_IPV6_NIB_CONF_6LN || GNRC_IPV6_NIB_CONF_SLAAC */
 
 /** @} */
diff --git a/sys/net/gnrc/network_layer/ipv6/nib/nib_pl.c b/sys/net/gnrc/network_layer/ipv6/nib/nib_pl.c
index 125582a2a7..49abce66a5 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/nib_pl.c
+++ b/sys/net/gnrc/network_layer/ipv6/nib/nib_pl.c
@@ -18,10 +18,12 @@
 #include <stdio.h>
 
 #include "net/gnrc/ipv6/nib/pl.h"
+#include "net/gnrc/netif2/internal.h"
 #include "timex.h"
 #include "xtimer.h"
 
 #include "_nib-internal.h"
+#include "_nib-router.h"
 
 int gnrc_ipv6_nib_pl_set(unsigned iface,
                          const ipv6_addr_t *pfx, unsigned pfx_len,
@@ -47,8 +49,40 @@ int gnrc_ipv6_nib_pl_set(unsigned iface,
     if (dst == NULL) {
         res = -ENOMEM;
     }
+#ifdef MODULE_GNRC_NETIF2
+    gnrc_netif2_t *netif = gnrc_netif2_get_by_pid(iface);
+    int idx;
+
+    if (netif == NULL) {
+        mutex_unlock(&_nib_mutex);
+        return res;
+    }
+    gnrc_netif2_acquire(netif);
+    if (!gnrc_netif2_is_6ln(netif) &&
+        ((idx = gnrc_netif2_ipv6_addr_match(netif, pfx)) >= 0) &&
+        (ipv6_addr_match_prefix(&netif->ipv6.addrs[idx], pfx) >= pfx_len)) {
+        dst->flags |= _PFX_ON_LINK;
+    }
+    if (netif->ipv6.aac_mode == GNRC_NETIF2_AAC_AUTO) {
+        dst->flags |= _PFX_SLAAC;
+    }
+#if GNRC_IPV6_NIB_CONF_6LBR && GNRC_IPV6_NIB_CONF_MULTIHOP_P6C
+    if (gnrc_netif2_is_6lbr(netif)) {
+        _nib_abr_entry_t *abr = NULL;
+
+        while ((abr = _nib_abr_iter(abr))) {
+            abr->version++;
+            _nib_abr_add_pfx(abr, dst);
+        }
+    }
+#endif
+    gnrc_netif2_release(netif);
+#endif  /* MODULE_GNRC_NETIF2 */
     mutex_unlock(&_nib_mutex);
-    /* TODO: send RA with PIO, if iface is allowed to send RAs */
+#if defined(MODULE_GNRC_NETIF2) && GNRC_IPV6_NIB_CONF_ROUTER
+    /* update prefixes down-stream */
+    _handle_snd_mc_ra(netif);
+#endif
     return res;
 }
 
@@ -66,7 +100,14 @@ void gnrc_ipv6_nib_pl_del(unsigned iface,
             (ipv6_addr_match_prefix(pfx, &dst->pfx) >= pfx_len)) {
             _nib_pl_remove(dst);
             mutex_unlock(&_nib_mutex);
-            /* TODO: send RA with PIO, if iface is allowed to send RAs */
+#if GNRC_IPV6_NIB_CONF_ROUTER
+            gnrc_netif2_t *netif = gnrc_netif2_get_by_pid(iface);
+
+            if (netif) {
+                /* update prefixes down-stream */
+                _handle_snd_mc_ra(netif);
+            }
+#endif
             return;
         }
     }
-- 
GitLab