From b033ff590b13a5cb8c57e5c60507cbc3175044bf Mon Sep 17 00:00:00 2001
From: Martine Lenders <mail@martine-lenders.eu>
Date: Mon, 31 Aug 2015 14:25:17 +0200
Subject: [PATCH] gnrc_ndp_host: initial import

---
 Makefile.dep                                  |  20 +-
 sys/include/net/gnrc/ipv6/netif.h             |  12 ++
 sys/include/net/gnrc/ndp.h                    |  66 +++++-
 sys/include/net/gnrc/ndp/host.h               |  54 +++++
 sys/include/net/gnrc/ndp/internal.h           |  41 ++++
 sys/net/gnrc/Makefile                         |   3 +
 sys/net/gnrc/network_layer/ndp/gnrc_ndp.c     | 124 ++++++++++-
 sys/net/gnrc/network_layer/ndp/host/Makefile  |   3 +
 .../network_layer/ndp/host/gnrc_ndp_host.c    |  56 +++++
 .../ndp/internal/gnrc_ndp_internal.c          | 204 +++++++++++++-----
 10 files changed, 513 insertions(+), 70 deletions(-)
 create mode 100644 sys/include/net/gnrc/ndp/host.h
 create mode 100644 sys/net/gnrc/network_layer/ndp/host/Makefile
 create mode 100644 sys/net/gnrc/network_layer/ndp/host/gnrc_ndp_host.c

diff --git a/Makefile.dep b/Makefile.dep
index 3a83561f17..0f1bdb16cb 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -80,19 +80,29 @@ endif
 ifneq (,$(filter gnrc_ipv6_default,$(USEMODULE)))
   USEMODULE += gnrc_ipv6
   USEMODULE += gnrc_icmpv6
-  USEMODULE += gnrc_ndp
-  USEMODULE += gnrc_ndp_internal
-  USEMODULE += gnrc_ndp_node
+  USEMODULE += gnrc_ndp_host
 endif
 
 ifneq (,$(filter gnrc_ipv6_router_default,$(USEMODULE)))
   USEMODULE += gnrc_ipv6_router
   USEMODULE += gnrc_icmpv6
-  USEMODULE += gnrc_ndp
-  USEMODULE += gnrc_ndp_internal
   USEMODULE += gnrc_ndp_node
 endif
 
+ifneq (,$(filter gnrc_ndp_host,$(USEMODULE)))
+  USEMODULE += gnrc_ndp_node
+  USEMODULE += random
+  USEMODULE += vtimer
+endif
+
+ifneq (,$(filter gnrc_ndp_node,$(USEMODULE)))
+  USEMODULE += gnrc_ndp_internal
+endif
+
+ifneq (,$(filter gnrc_ndp_%,$(USEMODULE)))
+  USEMODULE += gnrc_ndp
+endif
+
 ifneq (,$(filter gnrc_ndp,$(USEMODULE)))
   USEMODULE += gnrc_icmpv6
   USEMODULE += random
diff --git a/sys/include/net/gnrc/ipv6/netif.h b/sys/include/net/gnrc/ipv6/netif.h
index 8e6c0310ec..ac0e796b51 100644
--- a/sys/include/net/gnrc/ipv6/netif.h
+++ b/sys/include/net/gnrc/ipv6/netif.h
@@ -194,6 +194,18 @@ extern "C" {
  */
 #define GNRC_IPV6_NETIF_FLAGS_IS_WIRED          (0x0080)
 
+/**
+ * @brief   Offset of the router advertisement flags compared to the position in router
+ *          advertisements.
+ */
+#define GNRC_IPV6_NETIF_FLAGS_RTR_ADV_POS       (8U)
+
+/**
+ * @brief   Mask for flags intended for router advertisements.
+ * @note    Please expand if more router advertisement flags are introduced.
+ */
+#define GNRC_IPV6_NETIF_FLAGS_RTR_ADV_MASK      (0xc000)
+
 /**
  * @brief   Flag to indicate that the interface has other address
  *          configuration.
diff --git a/sys/include/net/gnrc/ndp.h b/sys/include/net/gnrc/ndp.h
index bfea5be09e..87fbd39bc2 100644
--- a/sys/include/net/gnrc/ndp.h
+++ b/sys/include/net/gnrc/ndp.h
@@ -22,6 +22,7 @@
 #define GNRC_NDP_H_
 
 #include <inttypes.h>
+#include <stdlib.h>
 
 #include "byteorder.h"
 #include "net/ndp.h"
@@ -31,21 +32,49 @@
 #include "net/gnrc/ipv6/nc.h"
 #include "net/gnrc/ipv6/netif.h"
 
+#include "net/gnrc/ndp/host.h"
+#include "net/gnrc/ndp/internal.h"
 #include "net/gnrc/ndp/node.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-#define GNRC_NDP_MSG_RTR_TIMEOUT        (0x0211)    /**< Message type for router timeouts */
-#define GNRC_NDP_MSG_ADDR_TIMEOUT       (0x0212)    /**< Message type for address timeouts */
-#define GNRC_NDP_MSG_NBR_SOL_RETRANS    (0x0213)    /**< Message type for multicast
+#define GNRC_NDP_MSG_RTR_TIMEOUT        (0x0210)    /**< Message type for router timeouts */
+#define GNRC_NDP_MSG_ADDR_TIMEOUT       (0x0211)    /**< Message type for address timeouts */
+#define GNRC_NDP_MSG_NBR_SOL_RETRANS    (0x0212)    /**< Message type for multicast
                                                      *   neighbor solicitation retransmissions */
-#define GNRC_NDP_MSG_NC_STATE_TIMEOUT   (0x0214)    /**< Message type for neighbor cache state timeouts */
+#define GNRC_NDP_MSG_RTR_SOL_RETRANS    (0x0215)    /**< Message type for periodic router solicitations */
+#define GNRC_NDP_MSG_NC_STATE_TIMEOUT   (0x0216)    /**< Message type for neighbor cache state timeouts */
 
 /**
+ * @name    Host constants
  * @{
+ * @see     <a href="https://tools.ietf.org/html/rfc4861#section-10">
+ *              RFC 4861, section 10
+ *          </a>
+ */
+/**
+ * @brief   Upper bound for randomised delay in seconds for initial
+ *          router solicitation transmissions
+ */
+#define GNRC_NDP_MAX_RTR_SOL_DELAY      (1U)
+
+/**
+ * @brief   Interval in seconds between initial router solicitation
+ *          transmissions
+ */
+#define GNRC_NDP_MAX_RTR_SOL_INT        (4U)
+
+/**
+ * @brief   Maximum number of  initial router solicitation transmissions
+ */
+#define GNRC_NDP_MAX_RTR_SOL_NUMOF      (3U)
+/** @} */
+
+/**
  * @name    Node constants
+ * @{
  * @see     <a href="https://tools.ietf.org/html/rfc4861#section-10">
  *              RFC 4861, section 10
  *          </a>
@@ -132,6 +161,21 @@ void gnrc_ndp_nbr_adv_handle(kernel_pid_t iface, gnrc_pktsnip_t *pkt,
                              ipv6_hdr_t *ipv6, ndp_nbr_adv_t *nbr_adv,
                              size_t icmpv6_size);
 
+/**
+ * @brief   Handles received router advertisements
+ *
+ * @todo    As router check consistency as described in RFC 4861, section 6.2.3
+ *
+ * @param[in] iface         The receiving interface.
+ * @param[in] pkt           The received packet.
+ * @param[in] ipv6          The IPv6 header in @p pkt.
+ * @param[in] rtr_adv       The router advertisement in @p pkt.
+ * @param[in] icmpv6_size   The overall size of the router advertisement.
+ */
+void gnrc_ndp_rtr_adv_handle(kernel_pid_t iface, gnrc_pktsnip_t *pkt,
+                             ipv6_hdr_t *ipv6, ndp_rtr_adv_t *rtr_adv,
+                             size_t icmpv6_size);
+
 /**
  * @brief   Retransmits a multicast neighbor solicitation for an incomplete or
  *          probing neighbor cache entry @p nc_entry,
@@ -233,6 +277,20 @@ gnrc_pktsnip_t *gnrc_ndp_nbr_sol_build(ipv6_addr_t *tgt, gnrc_pktsnip_t *options
 gnrc_pktsnip_t *gnrc_ndp_nbr_adv_build(uint8_t flags, ipv6_addr_t *tgt,
                                        gnrc_pktsnip_t *options);
 
+/**
+ * @brief   Builds a router solicitation message for sending.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4861#section-4.1">
+ *          RFC 4861, section 4.1
+ *      </a>
+ *
+ * @param[in] options   Options to append to the router solicitation.
+ *
+ * @return  The resulting ICMPv6 packet on success.
+ * @return  NULL, on failure.
+ */
+gnrc_pktsnip_t *gnrc_ndp_rtr_sol_build(gnrc_pktsnip_t *options);
+
 /**
  * @brief   Builds a generic NDP option.
  *
diff --git a/sys/include/net/gnrc/ndp/host.h b/sys/include/net/gnrc/ndp/host.h
new file mode 100644
index 0000000000..0fc2edf8e9
--- /dev/null
+++ b/sys/include/net/gnrc/ndp/host.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 Martine Lenders <mlenders@inf.fu-berlin.de>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup    net_gnrc_ndp_host Host-specific part of router discovery.
+ * @ingroup     net_gnrc_ndp
+ * @brief       Host-specific part for the router discovery in IPv6
+ *              neighbor discovery.
+ * @{
+ *
+ * @file
+ * @brief   Host-specific router discovery definitions
+ *
+ * @author  Martine Lenders <mlenders@inf.fu-berlin.de>
+ */
+#ifndef GNRC_NDP_HOST_H_
+#define GNRC_NDP_HOST_H_
+
+#include "net/gnrc/ipv6/netif.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Initializes interface @p iface as host.
+ *
+ * @pre iface != NULL
+ *
+ * @param[in] iface An IPv6 interface
+ */
+void gnrc_ndp_host_init(gnrc_ipv6_netif_t *iface);
+
+/**
+ * @brief   Sends a router solicitation over interface @p iface
+ *          and reset the timer for the next one.
+ *
+ * @pre iface != NULL
+ *
+ * @param[in] iface An IPv6 interface
+ */
+void gnrc_ndp_host_retrans_rtr_sol(gnrc_ipv6_netif_t *iface);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GNRC_NDP_HOST_H_ */
+/** @} */
diff --git a/sys/include/net/gnrc/ndp/internal.h b/sys/include/net/gnrc/ndp/internal.h
index 52b42dd00b..6765fcea89 100644
--- a/sys/include/net/gnrc/ndp/internal.h
+++ b/sys/include/net/gnrc/ndp/internal.h
@@ -22,6 +22,7 @@
 #ifndef GNRC_NDP_INTERNAL_H_
 #define GNRC_NDP_INTERNAL_H_
 
+#include "kernel_types.h"
 #include "net/ipv6/addr.h"
 #include "net/ipv6/hdr.h"
 #include "net/ndp.h"
@@ -88,6 +89,16 @@ void gnrc_ndp_internal_send_nbr_sol(kernel_pid_t iface, ipv6_addr_t *tgt,
 void gnrc_ndp_internal_send_nbr_adv(kernel_pid_t iface, ipv6_addr_t *tgt, ipv6_addr_t *dst,
                                     bool supply_tl2a, gnrc_pktsnip_t *ext_opts);
 
+/**
+ * @brief   Send precompiled router solicitation to @p dst.
+ *
+ * @internal
+ *
+ * @param[in] iface Interface to send over. May not be KERNEL_PID_UNDEF.
+ * @param[in] dst   Destination for the router solicitation. ff02::2 if NULL.
+ */
+void gnrc_ndp_internal_send_rtr_sol(kernel_pid_t iface, ipv6_addr_t *dst);
+
 /**
  * @brief   Handles a SL2A option.
  *
@@ -121,6 +132,36 @@ int gnrc_ndp_internal_tl2a_opt_handle(gnrc_pktsnip_t *pkt, ipv6_hdr_t *ipv6,
                                       uint8_t icmpv6_type, ndp_opt_t *tl2a_opt,
                                       uint8_t *l2addr);
 
+/**
+ * @brief   Handles a MTU option.
+ *
+ * @internal
+ *
+ * @param[in] iface         Interface the MTU option was received on.
+ * @param[in] icmpv6_type   ICMPv6 type of the message carrying the option.
+ * @param[in] mtu_opt       A MTU option.
+ *
+ * @return  true, on success (or if the node should silently ignore the option).
+ * @return  false, if MTU option was not valid.
+ */
+bool gnrc_ndp_internal_mtu_opt_handle(kernel_pid_t iface, uint8_t icmpv6_type,
+                                      ndp_opt_mtu_t *mtu_opt);
+
+/**
+ * @brief   Handles a PI option.
+ *
+ * @internal
+ *
+ * @param[in] iface         Interface the PI option was received on.
+ * @param[in] icmpv6_type   ICMPv6 type of the message carrying the option.
+ * @param[in] pi_opt        A PI option.
+ *
+ * @return  true, on success (or if the node should silently ignore the option).
+ * @return  false, if PIO was not valid.
+ */
+bool gnrc_ndp_internal_pi_opt_handle(kernel_pid_t iface, uint8_t icmpv6_type,
+                                     ndp_opt_pi_t *pi_opt);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile
index 90d84c340b..bee9e79773 100644
--- a/sys/net/gnrc/Makefile
+++ b/sys/net/gnrc/Makefile
@@ -25,6 +25,9 @@ endif
 ifneq (,$(filter gnrc_ndp_internal,$(USEMODULE)))
     DIRS += network_layer/ndp/internal
 endif
+ifneq (,$(filter gnrc_ndp_host,$(USEMODULE)))
+    DIRS += network_layer/ndp/host
+endif
 ifneq (,$(filter gnrc_ndp_node,$(USEMODULE)))
     DIRS += network_layer/ndp/node
 endif
diff --git a/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c b/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c
index e79cd12a7b..1d7fa505e8 100644
--- a/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c
+++ b/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c
@@ -275,6 +275,111 @@ void gnrc_ndp_nbr_adv_handle(kernel_pid_t iface, gnrc_pktsnip_t *pkt,
     return;
 }
 
+static inline void _set_reach_time(gnrc_ipv6_netif_t *if_entry, uint32_t mean)
+{
+    uint32_t reach_time = genrand_uint32_range(GNRC_NDP_MIN_RAND, GNRC_NDP_MAX_RAND);
+
+    if_entry->reach_time_base = mean;
+    /* to avoid floating point number computation and have higher value entropy, the
+     * boundaries for the random value are multiplied by 10 and we need to account for that */
+    reach_time = (reach_time * if_entry->reach_time_base) / 10;
+    if_entry->reach_time = timex_set(0, reach_time);
+    timex_normalize(&if_entry->reach_time);
+}
+
+void gnrc_ndp_rtr_adv_handle(kernel_pid_t iface, gnrc_pktsnip_t *pkt, ipv6_hdr_t *ipv6,
+                             ndp_rtr_adv_t *rtr_adv, size_t icmpv6_size)
+{
+    uint8_t *buf = (uint8_t *)(rtr_adv + 1);
+    gnrc_ipv6_nc_t *nc_entry = NULL;
+    gnrc_ipv6_netif_t *if_entry = gnrc_ipv6_netif_get(iface);
+    uint8_t l2src[GNRC_IPV6_NC_L2_ADDR_MAX];
+    int sicmpv6_size = (int)icmpv6_size, l2src_len = 0;
+    uint16_t opt_offset = 0;
+
+    assert(if_entry != NULL);
+    if (!ipv6_addr_is_link_local(&ipv6->src) ||
+        ipv6_addr_is_multicast(&ipv6->src) ||
+        (ipv6->hl != 255) || (rtr_adv->code != 0) ||
+        (icmpv6_size < sizeof(ndp_rtr_adv_t))) {
+        DEBUG("ndp: router advertisement was invalid\n");
+        /* ipv6 releases */
+        return;
+    }
+    /* get source from default router list */
+    nc_entry = gnrc_ipv6_nc_get(iface, &ipv6->src);
+    if (nc_entry == NULL) { /* not in default router list */
+        /* create default router list entry */
+        nc_entry = gnrc_ipv6_nc_add(iface, &ipv6->src, NULL, 0,
+                                    GNRC_IPV6_NC_IS_ROUTER);
+        if (nc_entry == NULL) {
+            DEBUG("ndp: error on default router list entry creation\n");
+            return;
+        }
+    }
+    else if ((nc_entry->flags & GNRC_IPV6_NC_IS_ROUTER) && (byteorder_ntohs(rtr_adv->ltime) == 0)) {
+        nc_entry->flags &= ~GNRC_IPV6_NC_IS_ROUTER;
+    }
+    else {
+        nc_entry->flags |= GNRC_IPV6_NC_IS_ROUTER;
+    }
+    /* set router life timer */
+    if (rtr_adv->ltime.u16 != 0) {
+        vtimer_remove(&nc_entry->rtr_timeout);
+        vtimer_set_msg(&nc_entry->rtr_timeout,
+                       timex_set(byteorder_ntohs(rtr_adv->ltime), 0),
+                       thread_getpid(), GNRC_NDP_MSG_RTR_TIMEOUT, nc_entry);
+    }
+    /* set current hop limit from message if available */
+    if (rtr_adv->cur_hl != 0) {
+        if_entry->cur_hl = rtr_adv->cur_hl;
+    }
+    /* set flags from message */
+    if_entry->flags &= ~GNRC_IPV6_NETIF_FLAGS_RTR_ADV_MASK;
+    if_entry->flags |= (rtr_adv->flags << GNRC_IPV6_NETIF_FLAGS_RTR_ADV_POS) &
+                       GNRC_IPV6_NETIF_FLAGS_RTR_ADV_MASK;
+    /* set reachable time from message if it is not the same as the random base
+     * value */
+    if ((rtr_adv->reach_time.u32 != 0) &&
+        (if_entry->reach_time_base != byteorder_ntohl(rtr_adv->reach_time))) {
+        _set_reach_time(if_entry, byteorder_ntohl(rtr_adv->reach_time));
+    }
+    /* set retransmission timer from message */
+    if (rtr_adv->retrans_timer.u32 != 0) {
+        if_entry->retrans_timer = timex_set(0, byteorder_ntohl(rtr_adv->retrans_timer));
+        timex_normalize(&if_entry->retrans_timer);
+    }
+    mutex_unlock(&if_entry->mutex);
+    sicmpv6_size -= sizeof(ndp_rtr_adv_t);
+    /* parse options */
+    while (sicmpv6_size > 0) {
+        ndp_opt_t *opt = (ndp_opt_t *)(buf + opt_offset);
+        switch (opt->type) {
+            case NDP_OPT_SL2A:
+                if ((l2src_len = gnrc_ndp_internal_sl2a_opt_handle(pkt, ipv6, rtr_adv->type, opt,
+                                                                   l2src)) < 0) {
+                    /* -ENOTSUP can not happen */
+                    /* invalid source link-layer address option */
+                    return;
+                }
+                break;
+            case NDP_OPT_MTU:
+                if (!gnrc_ndp_internal_mtu_opt_handle(iface, rtr_adv->type, (ndp_opt_mtu_t *)opt)) {
+                    /* invalid MTU option */
+                    return;
+                }
+                break;
+            case NDP_OPT_PI:
+                if (!gnrc_ndp_internal_pi_opt_handle(iface, rtr_adv->type, (ndp_opt_pi_t *)opt)) {
+                    /* invalid prefix information option */
+                    return;
+                }
+                break;
+        }
+    }
+    _stale_nc(iface, &ipv6->src, l2src, l2src_len);
+}
+
 void gnrc_ndp_retrans_nbr_sol(gnrc_ipv6_nc_t *nc_entry)
 {
     if ((gnrc_ipv6_nc_get_state(nc_entry) == GNRC_IPV6_NC_STATE_INCOMPLETE) ||
@@ -350,14 +455,9 @@ void gnrc_ndp_state_timeout(gnrc_ipv6_nc_t *nc_entry)
 
 void gnrc_ndp_netif_add(gnrc_ipv6_netif_t *iface)
 {
-    uint32_t reach_time = genrand_uint32_range(GNRC_NDP_MIN_RAND, GNRC_NDP_MAX_RAND);
-
     /* set default values */
     mutex_lock(&iface->mutex);
-    iface->reach_time_base = GNRC_NDP_REACH_TIME;
-    reach_time = (reach_time * iface->reach_time_base) / 10;
-    iface->reach_time = timex_set(0, reach_time);
-    timex_normalize(&iface->reach_time);
+    _set_reach_time(iface, GNRC_NDP_REACH_TIME);
     iface->retrans_timer = timex_set(0, GNRC_NDP_RETRANS_TIMER);
     timex_normalize(&iface->retrans_timer);
     mutex_unlock(&iface->mutex);
@@ -417,6 +517,18 @@ gnrc_pktsnip_t *gnrc_ndp_nbr_adv_build(uint8_t flags, ipv6_addr_t *tgt,
     return pkt;
 }
 
+gnrc_pktsnip_t *gnrc_ndp_rtr_sol_build(gnrc_pktsnip_t *options)
+{
+    gnrc_pktsnip_t *pkt;
+    DEBUG("ndp: building router solicitation message\n");
+    pkt = gnrc_icmpv6_build(options, ICMPV6_RTR_SOL, 0, sizeof(ndp_rtr_sol_t));
+    if (pkt != NULL) {
+        ndp_rtr_sol_t *rtr_sol = pkt->data;
+        rtr_sol->resv.u32 = 0;
+    }
+    return pkt;
+}
+
 static inline size_t _ceil8(uint8_t length)
 {
     /* NDP options use units of 8 byte for there length field, so round up */
diff --git a/sys/net/gnrc/network_layer/ndp/host/Makefile b/sys/net/gnrc/network_layer/ndp/host/Makefile
new file mode 100644
index 0000000000..c3b51fb8da
--- /dev/null
+++ b/sys/net/gnrc/network_layer/ndp/host/Makefile
@@ -0,0 +1,3 @@
+MODULE = gnrc_ndp_host
+
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/gnrc/network_layer/ndp/host/gnrc_ndp_host.c b/sys/net/gnrc/network_layer/ndp/host/gnrc_ndp_host.c
new file mode 100644
index 0000000000..e1cbd37bcd
--- /dev/null
+++ b/sys/net/gnrc/network_layer/ndp/host/gnrc_ndp_host.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 Martine Lenders <mlenders@inf.fu-berlin.de>
+ *
+ * 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
+ */
+
+#include <inttypes.h>
+#include "random.h"
+#include "net/gnrc/ipv6.h"
+#include "net/gnrc/ndp.h"
+#include "net/gnrc/ndp/internal.h"
+#include "vtimer.h"
+
+#include "net/gnrc/ndp/host.h"
+
+#define ENABLE_DEBUG    (0)
+#include "debug.h"
+
+static inline void _reschedule_rtr_sol(gnrc_ipv6_netif_t *iface, timex_t delay)
+{
+    vtimer_remove(&iface->rtr_sol_timer);
+    vtimer_set_msg(&iface->rtr_sol_timer, delay, gnrc_ipv6_pid, GNRC_NDP_MSG_RTR_SOL_RETRANS,
+                   iface);
+}
+
+void gnrc_ndp_host_init(gnrc_ipv6_netif_t *iface)
+{
+    uint32_t interval = genrand_uint32_range(0, GNRC_NDP_MAX_RTR_SOL_DELAY * SEC_IN_USEC);
+    mutex_lock(&iface->mutex);
+    iface->rtr_sol_count = GNRC_NDP_MAX_RTR_SOL_NUMOF;
+    DEBUG("ndp host: delayed initial router solicitation by %" PRIu32 " usec.\n", interval);
+    _reschedule_rtr_sol(iface, timex_set(0, interval));
+    mutex_unlock(&iface->mutex);
+}
+
+void gnrc_ndp_host_retrans_rtr_sol(gnrc_ipv6_netif_t *iface)
+{
+    mutex_lock(&iface->mutex);
+    if (iface->rtr_sol_count > 1) { /* regard off-by-one error */
+        DEBUG("ndp hst: retransmit rtr sol in %d sec\n", GNRC_NDP_MAX_RTR_SOL_INT);
+        iface->rtr_sol_count--;
+        _reschedule_rtr_sol(iface, timex_set(GNRC_NDP_MAX_RTR_SOL_INT, 0));
+    }
+    mutex_unlock(&iface->mutex);
+    gnrc_ndp_internal_send_rtr_sol(iface->pid, NULL);
+}
+
+/** @} */
diff --git a/sys/net/gnrc/network_layer/ndp/internal/gnrc_ndp_internal.c b/sys/net/gnrc/network_layer/ndp/internal/gnrc_ndp_internal.c
index 5e1650e486..e1af1a8f7f 100644
--- a/sys/net/gnrc/network_layer/ndp/internal/gnrc_ndp_internal.c
+++ b/sys/net/gnrc/network_layer/ndp/internal/gnrc_ndp_internal.c
@@ -33,11 +33,9 @@ static gnrc_ipv6_nc_t *_last_router = NULL; /* last router chosen as default
                                              * router. Only used if reachability
                                              * is suspect (i. e. incomplete or
                                              * not at all) */
-
-/**
- * @brief   Get L2 address from interface
- */
-static uint16_t _get_l2src(uint8_t *l2src, size_t l2src_size, kernel_pid_t iface);
+static gnrc_pktsnip_t *_build_headers(kernel_pid_t iface, gnrc_pktsnip_t *payload,
+                                      ipv6_addr_t *dst, ipv6_addr_t *src);
+static size_t _get_l2src(kernel_pid_t iface, uint8_t *l2src, size_t l2src_maxlen);
 
 /**
  * @brief   Sends @ref GNRC_NETAPI_MSG_TYPE_SND delayed.
@@ -160,7 +158,8 @@ void gnrc_ndp_internal_send_nbr_adv(kernel_pid_t iface, ipv6_addr_t *tgt, ipv6_a
     DEBUG("dst: %s, supply_tl2a: %d)\n",
           ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)), supply_tl2a);
 
-    if (gnrc_ipv6_netif_get(iface)->flags & GNRC_IPV6_NETIF_FLAGS_ROUTER) {
+    if ((gnrc_ipv6_netif_get(iface)->flags & GNRC_IPV6_NETIF_FLAGS_ROUTER) &&
+        (gnrc_ipv6_netif_get(iface)->flags & GNRC_IPV6_NETIF_FLAGS_RTR_ADV)) {
         adv_flags |= NDP_NBR_ADV_FLAGS_R;
     }
 
@@ -173,9 +172,9 @@ void gnrc_ndp_internal_send_nbr_adv(kernel_pid_t iface, ipv6_addr_t *tgt, ipv6_a
 
     if (supply_tl2a) {
         uint8_t l2src[8];
-        uint16_t l2src_len;
+        size_t l2src_len;
         /* we previously checked if we are the target, so we can take our L2src */
-        l2src_len = _get_l2src(l2src, sizeof(l2src), iface);
+        l2src_len = _get_l2src(iface, l2src, sizeof(l2src));
 
         if (l2src_len > 0) {
             /* add target address link-layer address option */
@@ -202,32 +201,13 @@ void gnrc_ndp_internal_send_nbr_adv(kernel_pid_t iface, ipv6_addr_t *tgt, ipv6_a
         gnrc_pktbuf_release(pkt);
         return;
     }
-
     pkt = hdr;
-    hdr = gnrc_ipv6_hdr_build(pkt, NULL, 0, (uint8_t *)dst,
-                              sizeof(ipv6_addr_t));
-
+    hdr = _build_headers(iface, pkt, dst, NULL);
     if (hdr == NULL) {
-        DEBUG("ndp internal: error allocating IPv6 header.\n");
+        DEBUG("ndp internal: error adding lower-layer headers.\n");
         gnrc_pktbuf_release(pkt);
         return;
     }
-
-    ((ipv6_hdr_t *)hdr->data)->hl = 255;
-
-    pkt = hdr;
-    /* add netif header for send interface specification */
-    hdr = gnrc_netif_hdr_build(NULL, 0, NULL, 0);
-
-    if (hdr == NULL) {
-        DEBUG("ndp internal: error allocating netif header.\n");
-        return;
-    }
-
-    ((gnrc_netif_hdr_t *)hdr->data)->if_pid = iface;
-
-    LL_PREPEND(pkt, hdr);
-
     if (gnrc_ipv6_netif_addr_is_non_unicast(tgt)) {
         /* avoid collision for anycast addresses
          * (see https://tools.ietf.org/html/rfc4861#section-7.2.7) */
@@ -238,10 +218,10 @@ void gnrc_ndp_internal_send_nbr_adv(kernel_pid_t iface, ipv6_addr_t *tgt, ipv6_a
               delay.seconds);
 
         /* nc_entry must be set so no need to check it */
-        _send_delayed(&nc_entry->nbr_adv_timer, delay, pkt);
+        _send_delayed(&nc_entry->nbr_adv_timer, delay, hdr);
     }
     else {
-        gnrc_netapi_send(gnrc_ipv6_pid, pkt);
+        gnrc_netapi_send(gnrc_ipv6_pid, hdr);
     }
 }
 
@@ -250,7 +230,6 @@ void gnrc_ndp_internal_send_nbr_sol(kernel_pid_t iface, ipv6_addr_t *tgt,
 {
     gnrc_pktsnip_t *hdr, *pkt = NULL;
     ipv6_addr_t *src = NULL;
-    size_t src_len = 0;
 
     DEBUG("ndp internal: send neighbor solicitation (iface: %" PRIkernel_pid ", tgt: %s, ",
           iface, ipv6_addr_to_str(addr_str, tgt, sizeof(addr_str)));
@@ -259,9 +238,8 @@ void gnrc_ndp_internal_send_nbr_sol(kernel_pid_t iface, ipv6_addr_t *tgt,
     /* check if there is a fitting source address to target */
     if ((src = gnrc_ipv6_netif_find_best_src_addr(iface, tgt)) != NULL) {
         uint8_t l2src[8];
-        uint16_t l2src_len;
-        src_len = sizeof(ipv6_addr_t);
-        l2src_len = _get_l2src(l2src, sizeof(l2src), iface);
+        size_t l2src_len;
+        l2src_len = _get_l2src(iface, l2src, sizeof(l2src));
 
         if (l2src_len > 0) {
             /* add source address link-layer address option */
@@ -282,33 +260,55 @@ void gnrc_ndp_internal_send_nbr_sol(kernel_pid_t iface, ipv6_addr_t *tgt,
         gnrc_pktbuf_release(pkt);
         return;
     }
-
     pkt = hdr;
-    hdr = gnrc_ipv6_hdr_build(pkt, (uint8_t *)src, src_len, (uint8_t *)dst,
-                              sizeof(ipv6_addr_t));
-
+    hdr = _build_headers(iface, pkt, dst, src);
     if (hdr == NULL) {
-        DEBUG("ndp internal: error allocating IPv6 header.\n");
+        DEBUG("ndp internal: error adding lower-layer headers.\n");
         gnrc_pktbuf_release(pkt);
         return;
     }
+    gnrc_netapi_send(gnrc_ipv6_pid, hdr);
+}
 
-    ((ipv6_hdr_t *)hdr->data)->hl = 255;
+void gnrc_ndp_internal_send_rtr_sol(kernel_pid_t iface, ipv6_addr_t *dst)
+{
+    gnrc_pktsnip_t *hdr, *pkt = NULL;
+    ipv6_addr_t *src = NULL, all_routers = IPV6_ADDR_ALL_ROUTERS_LINK_LOCAL;
+    DEBUG("ndp internal: send router solicitation (iface: %" PRIkernel_pid ", dst: ff02::2)\n",
+          iface);
+    if (dst == NULL) {
+        dst = &all_routers;
+    }
+    /* check if there is a fitting source address to target */
+    if ((src = gnrc_ipv6_netif_find_best_src_addr(iface, dst)) != NULL) {
+        uint8_t l2src[8];
+        size_t l2src_len;
+        l2src_len = _get_l2src(iface, l2src, sizeof(l2src));
+        if (l2src_len > 0) {
+            /* add source address link-layer address option */
+            pkt = gnrc_ndp_opt_sl2a_build(l2src, l2src_len, NULL);
 
+            if (pkt == NULL) {
+                DEBUG("ndp internal: error allocating Source Link-layer address option.\n");
+                gnrc_pktbuf_release(pkt);
+                return;
+            }
+        }
+    }
+    hdr = gnrc_ndp_rtr_sol_build(pkt);
+    if (hdr == NULL) {
+        DEBUG("ndp internal: error allocating router solicitation.\n");
+        gnrc_pktbuf_release(pkt);
+        return;
+    }
     pkt = hdr;
-    /* add netif header for send interface specification */
-    hdr = gnrc_netif_hdr_build(NULL, 0, NULL, 0);
-
+    hdr = _build_headers(iface, pkt, dst, src);
     if (hdr == NULL) {
-        DEBUG("ndp internal: error allocating netif header.\n");
+        DEBUG("ndp internal: error adding lower-layer headers.\n");
+        gnrc_pktbuf_release(pkt);
         return;
     }
-
-    ((gnrc_netif_hdr_t *)hdr->data)->if_pid = iface;
-
-    LL_PREPEND(pkt, hdr);
-
-    gnrc_netapi_send(gnrc_ipv6_pid, pkt);
+    gnrc_netapi_send(gnrc_ipv6_pid, hdr);
 }
 
 int gnrc_ndp_internal_sl2a_opt_handle(gnrc_pktsnip_t *pkt, ipv6_hdr_t *ipv6, uint8_t icmpv6_type,
@@ -335,6 +335,7 @@ int gnrc_ndp_internal_sl2a_opt_handle(gnrc_pktsnip_t *pkt, ipv6_hdr_t *ipv6, uin
           gnrc_netif_addr_to_str(addr_str, sizeof(addr_str), sl2a, sl2a_len));
 
     switch (icmpv6_type) {
+        case ICMPV6_RTR_ADV:
         case ICMPV6_NBR_SOL:
             if (sl2a_len == 0) {  /* in case there was no source address in l2 */
                 sl2a_len = (sl2a_opt->len / 8) - sizeof(ndp_opt_t);
@@ -396,11 +397,81 @@ int gnrc_ndp_internal_tl2a_opt_handle(gnrc_pktsnip_t *pkt, ipv6_hdr_t *ipv6,
             return 0;
     }
 }
-static uint16_t _get_l2src(uint8_t *l2src, size_t l2src_size, kernel_pid_t iface)
+
+bool gnrc_ndp_internal_mtu_opt_handle(kernel_pid_t iface, uint8_t icmpv6_type,
+                                      ndp_opt_mtu_t *mtu_opt)
+{
+    gnrc_ipv6_netif_t *if_entry = gnrc_ipv6_netif_get(iface);
+
+    assert(if_entry != NULL);
+    if ((mtu_opt->len != NDP_OPT_MTU_LEN)) {
+        DEBUG("ndp: invalid MTU option received\n");
+        return false;
+    }
+    if (icmpv6_type != ICMPV6_RTR_ADV) {
+        /* else discard silently */
+        return true;
+    }
+    mutex_lock(&if_entry->mutex);
+    if_entry->mtu = byteorder_ntohl(mtu_opt->mtu);
+    mutex_unlock(&if_entry->mutex);
+    return true;
+}
+
+bool gnrc_ndp_internal_pi_opt_handle(kernel_pid_t iface, uint8_t icmpv6_type,
+                                     ndp_opt_pi_t *pi_opt)
+{
+    ipv6_addr_t *prefix;
+    gnrc_ipv6_netif_addr_t *netif_addr;
+
+    if ((pi_opt->len != NDP_OPT_MTU_LEN)) {
+        DEBUG("ndp: invalid MTU option received\n");
+        return false;
+    }
+    if (icmpv6_type != ICMPV6_RTR_ADV || ipv6_addr_is_link_local(&pi_opt->prefix)) {
+        /* else discard silently */
+        return true;
+    }
+    prefix = gnrc_ipv6_netif_find_addr(iface, &pi_opt->prefix);
+    if (((prefix == NULL) ||
+         (gnrc_ipv6_netif_addr_get(prefix)->prefix_len != pi_opt->prefix_len)) &&
+        (pi_opt->valid_ltime.u32 != 0)) {
+        prefix = gnrc_ipv6_netif_add_addr(iface, &pi_opt->prefix,
+                                          pi_opt->prefix_len,
+                                          pi_opt->flags & NDP_OPT_PI_FLAGS_MASK);
+        if (prefix == NULL) {
+            DEBUG("ndp: could not add prefix to interface %d\n", iface);
+            return false;
+        }
+    }
+    netif_addr = gnrc_ipv6_netif_addr_get(prefix);
+    if (pi_opt->valid_ltime.u32 == 0) {
+        if (prefix != NULL) {
+            gnrc_ipv6_netif_remove_addr(iface, &netif_addr->addr);
+        }
+
+        return true;
+    }
+    netif_addr->valid = byteorder_ntohl(pi_opt->valid_ltime);
+    netif_addr->preferred = byteorder_ntohl(pi_opt->pref_ltime);
+    vtimer_remove(&netif_addr->valid_timeout);
+    if (netif_addr->valid != UINT32_MAX) {
+        vtimer_set_msg(&netif_addr->valid_timeout,
+                       timex_set(byteorder_ntohl(pi_opt->valid_ltime), 0),
+                       thread_getpid(), GNRC_NDP_MSG_ADDR_TIMEOUT, &netif_addr->addr);
+    }
+    /* TODO: preferred lifetime for address auto configuration */
+    /* on-link flag MUST stay set if it was */
+    netif_addr->flags &= ~NDP_OPT_PI_FLAGS_A;
+    netif_addr->flags |= (pi_opt->flags & NDP_OPT_PI_FLAGS_MASK);
+    return true;
+}
+
+static size_t _get_l2src(kernel_pid_t iface, uint8_t *l2src, size_t l2src_maxlen)
 {
     bool try_long = false;
     int res;
-    uint16_t l2src_len;
+    size_t l2src_len;
     /* maximum address length that fits into a minimum length (8) S/TL2A option */
     const uint16_t max_short_len = 6;
 
@@ -412,11 +483,11 @@ static uint16_t _get_l2src(uint8_t *l2src, size_t l2src_size, kernel_pid_t iface
     }
 
     if (try_long && ((res = gnrc_netapi_get(iface, NETOPT_ADDRESS_LONG, 0,
-                                            l2src, l2src_size)) > max_short_len)) {
+                                            l2src, l2src_maxlen)) > max_short_len)) {
         l2src_len = (uint16_t)res;
     }
     else if ((res = gnrc_netapi_get(iface, NETOPT_ADDRESS, 0, l2src,
-                                    l2src_size)) >= 0) {
+                                    l2src_maxlen)) >= 0) {
         l2src_len = (uint16_t)res;
     }
     else {
@@ -427,4 +498,27 @@ static uint16_t _get_l2src(uint8_t *l2src, size_t l2src_size, kernel_pid_t iface
     return l2src_len;
 }
 
+static gnrc_pktsnip_t *_build_headers(kernel_pid_t iface, gnrc_pktsnip_t *payload,
+                                      ipv6_addr_t *dst, ipv6_addr_t *src)
+{
+    gnrc_pktsnip_t *l2hdr;
+    gnrc_pktsnip_t *iphdr = gnrc_ipv6_hdr_build(payload, (uint8_t *)src, sizeof(ipv6_addr_t),
+                                                (uint8_t *)dst, sizeof(ipv6_addr_t));
+    if (iphdr == NULL) {
+        DEBUG("ndp internal: error allocating IPv6 header.\n");
+        return NULL;
+    }
+    ((ipv6_hdr_t *)iphdr->data)->hl = 255;
+    /* add netif header for send interface specification */
+    l2hdr = gnrc_netif_hdr_build(NULL, 0, NULL, 0);
+    if (l2hdr == NULL) {
+        DEBUG("ndp internal: error allocating netif header.\n");
+        gnrc_pktbuf_remove_snip(iphdr, iphdr);
+        return NULL;
+    }
+    ((gnrc_netif_hdr_t *)l2hdr->data)->if_pid = iface;
+    LL_PREPEND(iphdr, l2hdr);
+    return l2hdr;
+}
+
 /** @} */
-- 
GitLab