diff --git a/Makefile.dep b/Makefile.dep
index 0f1bdb16cbcdf7de38edf779ae500811cbdcbd2a..b533885381239ec3a14f84d2ab00187f68c3c2ef 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -86,7 +86,7 @@ endif
 ifneq (,$(filter gnrc_ipv6_router_default,$(USEMODULE)))
   USEMODULE += gnrc_ipv6_router
   USEMODULE += gnrc_icmpv6
-  USEMODULE += gnrc_ndp_node
+  USEMODULE += gnrc_ndp_router
 endif
 
 ifneq (,$(filter gnrc_ndp_host,$(USEMODULE)))
@@ -95,6 +95,12 @@ ifneq (,$(filter gnrc_ndp_host,$(USEMODULE)))
   USEMODULE += vtimer
 endif
 
+ifneq (,$(filter gnrc_ndp_router,$(USEMODULE)))
+  USEMODULE += gnrc_ndp_node
+  USEMODULE += random
+  USEMODULE += vtimer
+endif
+
 ifneq (,$(filter gnrc_ndp_node,$(USEMODULE)))
   USEMODULE += gnrc_ndp_internal
 endif
diff --git a/sys/include/net/gnrc/ndp.h b/sys/include/net/gnrc/ndp.h
index 87fbd39bc2ff9e01370e7d922c02562771f7ba1d..5315a2c4c5db6c084e681ae5efbea0fa6972741e 100644
--- a/sys/include/net/gnrc/ndp.h
+++ b/sys/include/net/gnrc/ndp.h
@@ -34,6 +34,7 @@
 
 #include "net/gnrc/ndp/host.h"
 #include "net/gnrc/ndp/internal.h"
+#include "net/gnrc/ndp/router.h"
 #include "net/gnrc/ndp/node.h"
 
 #ifdef __cplusplus
@@ -44,6 +45,8 @@ extern "C" {
 #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_RTR_ADV_RETRANS    (0x0213)    /**< Message type for periodic router advertisements */
+#define GNRC_NDP_MSG_RTR_ADV_DELAY      (0x0214)    /**< Message type for delayed router advertisements */
 #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 */
 
@@ -131,9 +134,43 @@ extern "C" {
  * @brief   Upper bound for randomised reachable time calculation.
  */
 #define GNRC_NDP_MAX_RAND               (15U)
+/** @} */
+
 /**
- * @}
+ * @name    Router constants
+ * @{
+ * @see     <a href="https://tools.ietf.org/html/rfc4861#section-10">
+ *              RFC 4861, section 10
+ *          </a>
+ */
+/**
+ * @brief   Initial router advertisement interval in seconds
+ */
+#define GNRC_NDP_MAX_INIT_RTR_ADV_INT   (16U)
+
+/**
+ * @brief   Maximum number of initial router advertisement transmissions
  */
+#define GNRC_NDP_MAX_INIT_RTR_ADV_NUMOF (3U)
+
+/**
+ * @brief   Maximum number of final router advertisement transmissions
+ */
+#define GNRC_NDP_MAX_FIN_RTR_ADV_NUMOF  (3U)
+
+/**
+ * @brief   Minimum delay in seconds between router advertisement
+ *          transmissions
+ */
+#define GNRC_NDP_MIN_RTR_ADV_DELAY      (3U)
+
+/**
+ * @brief   Upper bound for randomised delay in microseconds between router
+ *          solicitation reception and responding router advertisement
+ *          transmission.
+ */
+#define GNRC_NDP_MAX_RTR_ADV_DELAY      (500U * MS_IN_USEC)
+/** @} */
 
 /**
  * @brief   Handles received neighbor solicitations.
@@ -161,6 +198,32 @@ 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);
 
+#if (defined(MODULE_GNRC_NDP_ROUTER) || defined(MODULE_GNRC_SIXLOWPAN_ND_ROUTER))
+/**
+ * @brief   Handles received router solicitations.
+ *
+ * @param[in] iface         The receiving interface.
+ * @param[in] pkt           The received packet.
+ * @param[in] ipv6          The IPv6 header in @p pkt.
+ * @param[in] rtr_sol       The router solicitation in @p pkt.
+ * @param[in] icmpv6_size   The overall size of the router solicitation.
+ */
+void gnrc_ndp_rtr_sol_handle(kernel_pid_t iface, gnrc_pktsnip_t *pkt,
+                             ipv6_hdr_t *ipv6, ndp_rtr_sol_t *rtr_sol,
+                             size_t icmpv6_size);
+#else
+/**
+ * @brief   A host *must* silently discard all received router solicitations.
+ * @see     <a href="https://tools.ietf.org/html/rfc4861#section-6.2.6">
+ *              RFC 4861, section 6.2.6
+ *          </a>
+ *
+ * This macro is primarily an optimization to not go into the function defined
+ * above.
+ */
+#define gnrc_ndp_rtr_sol_handle(iface, pkt, ipv6, rtr_sol, size)
+#endif
+
 /**
  * @brief   Handles received router advertisements
  *
@@ -291,6 +354,65 @@ gnrc_pktsnip_t *gnrc_ndp_nbr_adv_build(uint8_t flags, ipv6_addr_t *tgt,
  */
 gnrc_pktsnip_t *gnrc_ndp_rtr_sol_build(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);
+
+#if (defined(MODULE_GNRC_NDP_ROUTER) || defined(MODULE_GNRC_SIXLOWPAN_ND_ROUTER))
+/**
+ * @brief   Builds a router advertisement message for sending.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4861#section-4.2">
+ *          RFC 4861, section 4.2
+ *      </a>
+ *
+ * @note    The source address for the packet MUST be the link-local address
+ *          of the interface.
+ *
+ * @param[in] cur_hl        Default hop limit for outgoing IP packets, 0 if
+ *                          unspecified by this router.
+ * @param[in] flags         Flags as defined above.
+ *                          @ref GNRC_NDP_RTR_ADV_FLAGS_M == 1 indicates, that the
+ *                          addresses are managed by DHCPv6,
+ *                          @ref GNRC_NDP_RTR_ADV_FLAGS_O == 1 indicates that other
+ *                          configuration information is available via DHCPv6.
+ * @param[in] ltime         Lifetime of the default router in seconds.
+ * @param[in] reach_time    Time in milliseconds a node should assume a neighbor
+ *                          reachable. 0 means unspecified by the router.
+ * @param[in] retrans_timer Time in milliseconds between retransmitted
+ *                          neighbor solicitations. 0 means unspecified by
+ *                          the router.
+ * @param[in] options       Options to append to the router advertisement.
+ *
+ * @return  The resulting ICMPv6 packet on success.
+ * @return  NULL, on failure.
+ */
+gnrc_pktsnip_t *gnrc_ndp_rtr_adv_build(uint8_t cur_hl, uint8_t flags, uint16_t ltime,
+                                       uint32_t reach_time, uint32_t retrans_timer,
+                                       gnrc_pktsnip_t *options);
+#else
+/**
+ * @brief   A host *must not* send router advertisements at any time (so why build them?)
+ * @see     <a href="https://tools.ietf.org/html/rfc4861#section-6.3.4">
+ *              RFC 4861, section 6.3.4
+ *          </a>
+ *
+ * This macro is primarily an optimization to not go into the function defined
+ * above.
+ */
+#define gnrc_ndp_rtr_adv_build(cur_hl, flags, ltime, reach_time, retrans_timer, options) (NULL)
+#endif
+
 /**
  * @brief   Builds a generic NDP option.
  *
@@ -346,6 +468,81 @@ gnrc_pktsnip_t *gnrc_ndp_opt_sl2a_build(const uint8_t *l2addr, uint8_t l2addr_le
 gnrc_pktsnip_t *gnrc_ndp_opt_tl2a_build(const uint8_t *l2addr, uint8_t l2addr_len,
                                         gnrc_pktsnip_t *next);
 
+#if (defined(MODULE_GNRC_NDP_ROUTER) || defined(MODULE_GNRC_SIXLOWPAN_ND_ROUTER))
+/**
+ * @brief   Builds the prefix information option.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4861#section-4.6.2">
+ *          RFC 4861, section 4.6.2
+ *      </a>
+ *
+ * @note    Must only be used with router advertisemnents. This is not checked
+ *          however, since nodes should silently ignore it in other NDP messages.
+ *
+ * @param[in] prefix_len    The length of @p prefix in bits. Must be between
+ *                          0 and 128.
+ * @param[in] flags         Flags as defined above.
+ *                          @ref GNRC_NDP_OPT_PI_FLAGS_L == 1 indicates, that
+ *                          @p prefix can be used for on-link determination,
+ *                          @ref GNRC_NDP_OPT_PI_FLAGS_A == 1 indicates, that
+ *                          @p prefix can be used for stateless address
+ *                          configuration.
+ * @param[in] valid_ltime   Length of time in seconds that @p prefix is valid.
+ *                          UINT32_MAX represents infinity.
+ * @param[in] pref_ltime    Length of time in seconds that addresses using
+ *                          @p prefix remain prefered. UINT32_MAX represents
+ *                          infinity.
+ * @param[in] prefix        An IPv6 address or a prefix of an IPv6 address.
+ * @param[in] next          More options in the packet. NULL, if there are none.
+ *
+ * @return  The packet snip list of options, on success
+ * @return  NULL, if packet buffer is full
+ */
+gnrc_pktsnip_t *gnrc_ndp_opt_pi_build(uint8_t prefix_len, uint8_t flags,
+                                      uint32_t valid_ltime, uint32_t pref_ltime,
+                                      ipv6_addr_t *prefix, gnrc_pktsnip_t *next);
+
+/**
+ * @brief   Builds the MTU option.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4861#section-4.6.4">
+ *          RFC 4861, section 4.6.4
+ *      </a>
+ *
+ * @note    Must only be used with router advertisemnents. This is not checked
+ *          however, since nodes should silently ignore it in other NDP messages.
+ *
+ * @param[in] mtu           The recommended MTU for the link.
+ * @param[in] next          More options in the packet. NULL, if there are none.
+ *
+ * @return  The packet snip list of options, on success
+ * @return  NULL, if packet buffer is full
+ */
+gnrc_pktsnip_t *gnrc_ndp_opt_mtu_build(uint32_t mtu, gnrc_pktsnip_t *next);
+#else
+/**
+ * @brief   A host *must not* send router advertisements at any time (so why build their options?)
+ * @see     <a href="https://tools.ietf.org/html/rfc4861#section-6.3.4">
+ *              RFC 4861, section 6.3.4
+ *          </a>
+ *
+ * This macro is primarily an optimization to not go into the function defined
+ * above.
+ */
+#define gnrc_ndp_opt_pi_build(prefix_len, flags, valid_ltime, pref_ltime, prefix, next) (NULL)
+
+/**
+ * @brief   A host *must not* send router advertisements at any time (so why build their options?)
+ * @see     <a href="https://tools.ietf.org/html/rfc4861#section-6.3.4">
+ *              RFC 4861, section 6.3.4
+ *          </a>
+ *
+ * This macro is primarily an optimization to not go into the function defined
+ * above.
+ */
+#define gnrc_ndp_opt_mtu_build(mtu, next)   (NULL)
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/sys/include/net/gnrc/ndp/internal.h b/sys/include/net/gnrc/ndp/internal.h
index 6765fcea89ee8170926d4df93be9339ec2f1bad7..90f8f7900a880bab82f072bd801b5272409384dd 100644
--- a/sys/include/net/gnrc/ndp/internal.h
+++ b/sys/include/net/gnrc/ndp/internal.h
@@ -99,6 +99,30 @@ void gnrc_ndp_internal_send_nbr_adv(kernel_pid_t iface, ipv6_addr_t *tgt, ipv6_a
  */
 void gnrc_ndp_internal_send_rtr_sol(kernel_pid_t iface, ipv6_addr_t *dst);
 
+#if (defined(MODULE_GNRC_NDP_ROUTER) || defined(MODULE_GNRC_SIXLOWPAN_ND_ROUTER))
+/**
+ * @brief   Handles received router solicitations.
+ *
+ * @param[in] iface         Interface to send over. May not be KERNEL_PID_UNDEF.
+ * @param[in] src           Source address for the router advertisement. May be NULL to be determined
+ *                          by source address selection (:: if no @p iface has no address).
+ * @param[in] dst           Destination address for router advertisement.
+ *                          @ref IPV6_ADDR_ALL_NODES_LINK_LOCAL if NULL.
+ * @param[in] fin           This is part of the router's final batch of router advertisements
+ *                          before ceising to be a router (set's router lifetime field to 0).
+ */
+void gnrc_ndp_internal_send_rtr_adv(kernel_pid_t iface, ipv6_addr_t *src,
+                                    ipv6_addr_t *dst, bool fin);
+#else
+/**
+ * @brief   A host *must not* send router advertisements at any time.
+ *
+ * This macro is primarily an optimization to not go into the function defined
+ * above.
+ */
+#define gnrc_ndp_internal_send_rtr_adv(iface, dst, fin)
+#endif
+
 /**
  * @brief   Handles a SL2A option.
  *
diff --git a/sys/include/net/gnrc/ndp/router.h b/sys/include/net/gnrc/ndp/router.h
new file mode 100644
index 0000000000000000000000000000000000000000..d82c9705fb83bee195cef31180dd357c6d48f5b6
--- /dev/null
+++ b/sys/include/net/gnrc/ndp/router.h
@@ -0,0 +1,88 @@
+/*
+ * 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_router Router-specific part of router discovery.
+ * @ingroup     net_gnrc_ndp
+ * @brief       Router-specific part for the router discovery in IPv6
+ *              neighbor discovery.
+ * @{
+ *
+ * @file
+ * @brief   Router-specific router discovery definitions
+ *
+ * @author  Martine Lenders <mlenders@inf.fu-berlin.de>
+ */
+#ifndef GNRC_NDP_ROUTER_H_
+#define GNRC_NDP_ROUTER_H_
+
+#include <stdbool.h>
+
+#include "kernel_types.h"
+#include "net/ipv6/hdr.h"
+#include "net/ndp.h"
+#include "net/gnrc/ipv6/nc.h"
+#include "timex.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Set @p iface to router mode.
+ *
+ * @details This sets/unsets the GNRC_IPV6_NETIF_FLAGS_ROUTER and
+ *          GNRC_IPV6_NETIF_FLAGS_RTR_ADV and initializes or ceases router
+ *          behavior for neighbor discovery.
+ *
+ * @param[in] iface     An IPv6 interface. Must not be NULL.
+ * @param[in] enable    Status for the GNRC_IPV6_NETIF_FLAGS_ROUTER and
+ *                      GNRC_IPV6_NETIF_FLAGS_RTR_ADV flags.
+ */
+void gnrc_ndp_router_set_router(gnrc_ipv6_netif_t *iface, bool enable);
+
+/**
+ * @brief   Set/Unset GNRC_IPV6_NETIF_FLAGS_RTR_ADV flag for @p iface.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4861#section-6.2.2">
+ *          RFC 4861, section 6.2.2
+ *      </a>
+ * @see <a href="https://tools.ietf.org/html/rfc4861#section-6.2.5">
+ *          RFC 4861, section 6.2.5
+ *      </a>
+ *
+ * @details GNRC_IPV6_NETIF_FLAGS_RTR_ADV and initializes or ceases
+ *          periodic router advertising behavior for neighbor discovery.
+ *
+ * @param[in] iface     An IPv6 interface. Must not be NULL.
+ * @param[in] enable    Status for the GNRC_IPV6_NETIF_FLAGS_RTR_ADV flags.
+ */
+void gnrc_ndp_router_set_rtr_adv(gnrc_ipv6_netif_t *iface, bool enable);
+
+/**
+ * @brief   Send an unsolicited router advertisement over @p iface
+ *          and reset the timer for the next one if necessary.
+ *
+ * @param[in] iface An IPv6 interface.
+ */
+void gnrc_ndp_router_retrans_rtr_adv(gnrc_ipv6_netif_t *iface);
+
+/**
+ * @brief   Send an solicited router advertisement to IPv6 address of
+ *          @p neighbor.
+ *
+ * @param[in] neighbor  A neighbor cache entry.
+ */
+void gnrc_ndp_router_send_rtr_adv(gnrc_ipv6_nc_t *neighbor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GNRC_NDP_ROUTER_H_ */
+/** @} */
diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile
index bee9e79773e86a8a007f45862cca5f2763c1e0e9..69439a9aa96f42887252774505b8f47cf8ce9159 100644
--- a/sys/net/gnrc/Makefile
+++ b/sys/net/gnrc/Makefile
@@ -31,6 +31,9 @@ endif
 ifneq (,$(filter gnrc_ndp_node,$(USEMODULE)))
     DIRS += network_layer/ndp/node
 endif
+ifneq (,$(filter gnrc_ndp_router,$(USEMODULE)))
+    DIRS += network_layer/ndp/router
+endif
 ifneq (,$(filter gnrc_netapi,$(USEMODULE)))
     DIRS += netapi
 endif
diff --git a/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c b/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c
index 1d7fa505e816efc2db9d201ec60c0e2ca63a541c..9aab1182d5f385266b1939e27a6d7d883d3ebad1 100644
--- a/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c
+++ b/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c
@@ -275,6 +275,70 @@ void gnrc_ndp_nbr_adv_handle(kernel_pid_t iface, gnrc_pktsnip_t *pkt,
     return;
 }
 
+#if (defined(MODULE_GNRC_NDP_ROUTER) || defined(MODULE_GNRC_SIXLOWPAN_ND_ROUTER))
+void gnrc_ndp_rtr_sol_handle(kernel_pid_t iface, gnrc_pktsnip_t *pkt,
+                             ipv6_hdr_t *ipv6, ndp_rtr_sol_t *rtr_sol,
+                             size_t icmpv6_size)
+{
+    gnrc_ipv6_netif_t *if_entry = gnrc_ipv6_netif_get(iface);
+
+    if (if_entry->flags & GNRC_IPV6_NETIF_FLAGS_ROUTER) {
+        int sicmpv6_size = (int)icmpv6_size, l2src_len = 0;
+        uint8_t l2src[GNRC_IPV6_NC_L2_ADDR_MAX];
+        uint16_t opt_offset = 0;
+        uint8_t *buf = (uint8_t *)(rtr_sol + 1);
+        /* check validity */
+        if ((ipv6->hl != 255) || (rtr_sol->code != 0) ||
+            (icmpv6_size < sizeof(ndp_rtr_sol_t))) {
+            DEBUG("ndp: router solicitation was invalid\n");
+            return;
+        }
+        sicmpv6_size -= sizeof(ndp_rtr_sol_t);
+        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_sol->type, opt,
+                                                                       l2src)) < 0) {
+                        /* -ENOTSUP can not happen */
+                        /* invalid source link-layer address option */
+                        return;
+                    }
+                    break;
+
+                default:
+                    /* silently discard all other options */
+                    break;
+            }
+
+            opt_offset += (opt->len * 8);
+            sicmpv6_size -= (opt->len * 8);
+        }
+        _stale_nc(iface, &ipv6->src, l2src, l2src_len);
+        /* send delayed */
+        if (if_entry->flags & GNRC_IPV6_NETIF_FLAGS_RTR_ADV) {
+            timex_t delay = timex_set(0, genrand_uint32_range(0, GNRC_NDP_MAX_RTR_ADV_DELAY));
+            vtimer_remove(&if_entry->rtr_adv_timer);
+            if (ipv6_addr_is_unspecified(&ipv6->src)) {
+                /* either multicast, if source unspecified */
+                vtimer_set_msg(&if_entry->rtr_adv_timer, delay, gnrc_ipv6_pid,
+                               GNRC_NDP_MSG_RTR_ADV_RETRANS, if_entry);
+            }
+            else {
+                /* or unicast, if source is known */
+                /* XXX: can't just use GNRC_NETAPI_MSG_TYPE_SND, since the next retransmission
+                 * must also be set. */
+                gnrc_ipv6_nc_t *nc_entry = gnrc_ipv6_nc_get(iface, &ipv6->src);
+                vtimer_set_msg(&if_entry->rtr_adv_timer, delay, gnrc_ipv6_pid,
+                               GNRC_NDP_MSG_RTR_ADV_DELAY, nc_entry);
+            }
+        }
+    }
+    /* otherwise ignore silently */
+}
+#endif
+
 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);
@@ -297,7 +361,6 @@ void gnrc_ndp_rtr_adv_handle(kernel_pid_t iface, gnrc_pktsnip_t *pkt, ipv6_hdr_t
     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) ||
@@ -586,6 +649,58 @@ gnrc_pktsnip_t *gnrc_ndp_opt_tl2a_build(const uint8_t *l2addr, uint8_t l2addr_le
     return _opt_l2a_build(NDP_OPT_TL2A, l2addr, l2addr_len, next);
 }
 
+#if (defined(MODULE_GNRC_NDP_ROUTER) || defined(MODULE_GNRC_SIXLOWPAN_ND_ROUTER))
+gnrc_pktsnip_t *gnrc_ndp_rtr_adv_build(uint8_t cur_hl, uint8_t flags,
+                                       uint16_t ltime, uint32_t reach_time,
+                                       uint32_t retrans_timer, gnrc_pktsnip_t *options)
+{
+    gnrc_pktsnip_t *pkt;
+    DEBUG("ndp rtr: building router advertisement message\n");
+    pkt = gnrc_icmpv6_build(options, ICMPV6_RTR_ADV, 0, sizeof(ndp_rtr_adv_t));
+    if (pkt != NULL) {
+        ndp_rtr_adv_t *rtr_adv = pkt->data;
+        rtr_adv->cur_hl = cur_hl;
+        rtr_adv->flags = (flags & NDP_RTR_ADV_FLAGS_MASK);
+        rtr_adv->ltime = byteorder_htons(ltime);
+        rtr_adv->reach_time = byteorder_htonl(reach_time);
+        rtr_adv->retrans_timer = byteorder_htonl(retrans_timer);
+    }
+    return pkt;
+}
+
+gnrc_pktsnip_t *gnrc_ndp_opt_pi_build(uint8_t prefix_len, uint8_t flags,
+                                      uint32_t valid_ltime, uint32_t pref_ltime,
+                                      ipv6_addr_t *prefix, gnrc_pktsnip_t *next)
+{
+    gnrc_pktsnip_t *pkt = gnrc_ndp_opt_build(NDP_OPT_PI, sizeof(ndp_opt_pi_t),
+                                             next);
+    if (pkt != NULL) {
+        ndp_opt_pi_t *pi_opt = pkt->data;
+        pi_opt->prefix_len = prefix_len;
+        pi_opt->flags = (flags & NDP_OPT_PI_FLAGS_MASK);
+        pi_opt->valid_ltime = byteorder_htonl(valid_ltime);
+        pi_opt->pref_ltime = byteorder_htonl(pref_ltime);
+        pi_opt->resv.u32 = 0;
+        /* Bits beyond prefix_len MUST be 0 */
+        ipv6_addr_set_unspecified(&pi_opt->prefix);
+        ipv6_addr_init_prefix(&pi_opt->prefix, prefix, prefix_len);
+    }
+    return pkt;
+}
+
+gnrc_pktsnip_t *gnrc_ndp_opt_mtu_build(uint32_t mtu, gnrc_pktsnip_t *next)
+{
+    gnrc_pktsnip_t *pkt = gnrc_ndp_opt_build(NDP_OPT_MTU, sizeof(ndp_opt_mtu_t),
+                                             next);
+    if (pkt != NULL) {
+        ndp_opt_mtu_t *mtu_opt = pkt->data;
+        mtu_opt->resv.u16 = 0;
+        mtu_opt->mtu = byteorder_htonl(mtu);
+    }
+    return pkt;
+}
+#endif
+
 /**
  * @}
  */
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 e1af1a8f7f6170d9afd2345c364ec3d6edb0a060..9b68742da0dc8b6e360a13d0a5ed1904bad02c2a 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
@@ -311,6 +311,145 @@ void gnrc_ndp_internal_send_rtr_sol(kernel_pid_t iface, ipv6_addr_t *dst)
     gnrc_netapi_send(gnrc_ipv6_pid, hdr);
 }
 
+#if (defined(MODULE_GNRC_NDP_ROUTER) || defined(MODULE_GNRC_SIXLOWPAN_ND_ROUTER))
+static bool _pio_from_iface_addr(gnrc_pktsnip_t **res, gnrc_ipv6_netif_addr_t *addr,
+                                 gnrc_pktsnip_t *next)
+{
+    if (((addr->prefix_len - 1U) > 127U) && /* 0 < prefix_len < 128 */
+        !ipv6_addr_is_unspecified(&addr->addr) &&
+        !ipv6_addr_is_link_local(&addr->addr) &&
+        !gnrc_ipv6_netif_addr_is_non_unicast(&addr->addr)) {
+        DEBUG(" - PIO for %s/%" PRIu8 "\n", ipv6_addr_to_str(addr_str, &addr->addr,
+                                                             sizeof(addr_str)),
+              addr->prefix_len);
+        *res = gnrc_ndp_opt_pi_build(addr->prefix_len, (addr->flags &
+                                     (GNRC_IPV6_NETIF_ADDR_FLAGS_NDP_AUTO |
+                                      GNRC_IPV6_NETIF_ADDR_FLAGS_NDP_ON_LINK)),
+                                     addr->valid, addr->preferred, &addr->addr, next);
+        return true;
+    }
+    return false;
+}
+
+static gnrc_pktsnip_t *_add_pios(gnrc_ipv6_netif_t *ipv6_iface, gnrc_pktsnip_t *pkt)
+{
+    gnrc_pktsnip_t *tmp;
+    for (int i = 0; i < GNRC_IPV6_NETIF_ADDR_NUMOF; i++) {
+        if (_pio_from_iface_addr(&tmp, &ipv6_iface->addrs[i], pkt)) {
+            if (tmp != NULL) {
+                pkt = tmp;
+            }
+            else {
+                DEBUG("ndp rtr: error allocating PIO\n");
+                gnrc_pktbuf_release(pkt);
+                return NULL;
+            }
+        }
+    }
+    return pkt;
+}
+
+void gnrc_ndp_internal_send_rtr_adv(kernel_pid_t iface, ipv6_addr_t *src, ipv6_addr_t *dst,
+                                    bool fin)
+{
+    gnrc_pktsnip_t *hdr, *pkt = NULL;
+    ipv6_addr_t all_nodes = IPV6_ADDR_ALL_NODES_LINK_LOCAL;
+    gnrc_ipv6_netif_t *ipv6_iface = gnrc_ipv6_netif_get(iface);
+    uint32_t reach_time = 0, retrans_timer = 0;
+    uint16_t adv_ltime = 0;
+    uint8_t cur_hl = 0;
+
+    if (dst == NULL) {
+        dst = &all_nodes;
+    }
+    DEBUG("ndp internal: send router advertisement (iface: %" PRIkernel_pid ", dst: %s%s\n",
+          iface, ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)), fin ? ", final" : "");
+    mutex_lock(&ipv6_iface->mutex);
+    hdr = _add_pios(ipv6_iface, pkt);
+    if (hdr == NULL) {
+        /* pkt already released in _add_pios */
+        mutex_unlock(&ipv6_iface->mutex);
+        return;
+    }
+    pkt = hdr;
+    if (ipv6_iface->flags & GNRC_IPV6_NETIF_FLAGS_ADV_MTU) {
+        if ((hdr = gnrc_ndp_opt_mtu_build(ipv6_iface->mtu, pkt)) == NULL) {
+            DEBUG("ndp rtr: no space left in packet buffer\n");
+            mutex_unlock(&ipv6_iface->mutex);
+            gnrc_pktbuf_release(pkt);
+            return;
+        }
+        pkt = hdr;
+    }
+    if (src == NULL) {
+        /* get address from source selection algorithm */
+        src = gnrc_ipv6_netif_find_best_src_addr(iface, dst);
+    }
+    /* add SL2A for source address */
+    if (src != NULL) {
+        DEBUG(" - SL2A\n");
+        uint8_t l2src[8];
+        size_t l2src_len;
+        /* optimization note: MAY also be omitted to facilitate in-bound load balancing over
+         * replicated interfaces.
+         * source: https://tools.ietf.org/html/rfc4861#section-6.2.3 */
+        l2src_len = _get_l2src(iface, l2src, sizeof(l2src));
+        if (l2src_len > 0) {
+            /* add source address link-layer address option */
+            hdr = gnrc_ndp_opt_sl2a_build(l2src, l2src_len, NULL);
+
+            if (hdr == NULL) {
+                DEBUG("ndp internal: error allocating Source Link-layer address option.\n");
+                mutex_unlock(&ipv6_iface->mutex);
+                gnrc_pktbuf_release(pkt);
+                return;
+            }
+            pkt = hdr;
+        }
+    }
+    if (ipv6_iface->flags & GNRC_IPV6_NETIF_FLAGS_ADV_CUR_HL) {
+        cur_hl = ipv6_iface->cur_hl;
+    }
+    if (ipv6_iface->flags & GNRC_IPV6_NETIF_FLAGS_ADV_REACH_TIME) {
+        uint64_t tmp = timex_uint64(ipv6_iface->reach_time) / MS_IN_USEC;
+
+        if (tmp > (3600 * SEC_IN_MS)) { /* tmp > 1 hour */
+            tmp = (3600 * SEC_IN_MS);
+        }
+
+        reach_time = (uint32_t)tmp;
+    }
+    if (ipv6_iface->flags & GNRC_IPV6_NETIF_FLAGS_ADV_RETRANS_TIMER) {
+        uint64_t tmp = timex_uint64(ipv6_iface->retrans_timer) / MS_IN_USEC;
+        if (tmp > UINT32_MAX) {
+            tmp = UINT32_MAX;
+        }
+        retrans_timer = (uint32_t)tmp;
+    }
+    if (!fin) {
+        adv_ltime = ipv6_iface->adv_ltime;
+    }
+    mutex_unlock(&ipv6_iface->mutex);
+    hdr = gnrc_ndp_rtr_adv_build(cur_hl,
+                                 (ipv6_iface->flags & (GNRC_IPV6_NETIF_FLAGS_OTHER_CONF |
+                                                       GNRC_IPV6_NETIF_FLAGS_MANAGED)) >> 8,
+                                 adv_ltime, reach_time, retrans_timer, pkt);
+    if (hdr == NULL) {
+        DEBUG("ndp internal: error allocating router advertisement.\n");
+        gnrc_pktbuf_release(pkt);
+        return;
+    }
+    pkt = hdr;
+    hdr = _build_headers(iface, pkt, dst, src);
+    if (hdr == NULL) {
+        DEBUG("ndp internal: error adding lower-layer headers.\n");
+        gnrc_pktbuf_release(pkt);
+        return;
+    }
+    gnrc_netapi_send(gnrc_ipv6_pid, hdr);
+}
+#endif
+
 int gnrc_ndp_internal_sl2a_opt_handle(gnrc_pktsnip_t *pkt, ipv6_hdr_t *ipv6, uint8_t icmpv6_type,
                                       ndp_opt_t *sl2a_opt, uint8_t *l2src)
 {
@@ -335,6 +474,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_SOL:
         case ICMPV6_RTR_ADV:
         case ICMPV6_NBR_SOL:
             if (sl2a_len == 0) {  /* in case there was no source address in l2 */
@@ -403,7 +543,6 @@ bool gnrc_ndp_internal_mtu_opt_handle(kernel_pid_t iface, uint8_t icmpv6_type,
 {
     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;
diff --git a/sys/net/gnrc/network_layer/ndp/router/Makefile b/sys/net/gnrc/network_layer/ndp/router/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..541dc343692c18477294358051880d8477ec554d
--- /dev/null
+++ b/sys/net/gnrc/network_layer/ndp/router/Makefile
@@ -0,0 +1,3 @@
+MODULE = gnrc_ndp_router
+
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/gnrc/network_layer/ndp/router/gnrc_ndp_router.c b/sys/net/gnrc/network_layer/ndp/router/gnrc_ndp_router.c
new file mode 100644
index 0000000000000000000000000000000000000000..ac51e3d48ec8fa653306d145b9ef308c764ecc21
--- /dev/null
+++ b/sys/net/gnrc/network_layer/ndp/router/gnrc_ndp_router.c
@@ -0,0 +1,123 @@
+/*
+ * 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 "net/gnrc/ipv6.h"
+#include "net/gnrc/ndp.h"
+#include "net/gnrc/ndp/internal.h"
+#include "random.h"
+#include "timex.h"
+#include "vtimer.h"
+
+#include "net/gnrc/ndp/router.h"
+
+#define ENABLE_DEBUG (0)
+#include "debug.h"
+
+static void _send_rtr_adv(gnrc_ipv6_netif_t *iface, ipv6_addr_t *dst);
+
+void gnrc_ndp_router_set_router(gnrc_ipv6_netif_t *iface, bool enable)
+{
+    ipv6_addr_t all_routers = IPV6_ADDR_ALL_ROUTERS_LINK_LOCAL;
+    if (enable && !(iface->flags & GNRC_IPV6_NETIF_FLAGS_ROUTER)) {
+        gnrc_ipv6_netif_add_addr(iface->pid, &all_routers, 128,
+                                 GNRC_IPV6_NETIF_ADDR_FLAGS_NON_UNICAST);
+        mutex_lock(&iface->mutex);
+        iface->flags |= GNRC_IPV6_NETIF_FLAGS_ROUTER;
+        iface->max_adv_int = GNRC_IPV6_NETIF_DEFAULT_MAX_ADV_INT;
+        iface->min_adv_int = GNRC_IPV6_NETIF_DEFAULT_MIN_ADV_INT;
+        iface->adv_ltime = GNRC_IPV6_NETIF_DEFAULT_ROUTER_LTIME;
+        mutex_unlock(&iface->mutex);
+        gnrc_ndp_router_set_rtr_adv(iface, enable);
+    }
+    else if (!enable && (iface->flags & GNRC_IPV6_NETIF_FLAGS_ROUTER)) {
+        gnrc_ipv6_netif_remove_addr(iface->pid, &all_routers);
+        gnrc_ndp_router_set_rtr_adv(iface, enable);
+    }
+}
+
+void gnrc_ndp_router_set_rtr_adv(gnrc_ipv6_netif_t *iface, bool enable)
+{
+    if (enable && !(iface->flags & GNRC_IPV6_NETIF_FLAGS_RTR_ADV)) {
+        mutex_lock(&iface->mutex);
+        iface->flags |= GNRC_IPV6_NETIF_FLAGS_RTR_ADV;
+        iface->rtr_adv_count = GNRC_NDP_MAX_INIT_RTR_ADV_NUMOF;
+        mutex_unlock(&iface->mutex);
+        _send_rtr_adv(iface, NULL);
+    }
+    else if (!enable && (iface->flags & GNRC_IPV6_NETIF_FLAGS_RTR_ADV)) {
+        mutex_lock(&iface->mutex);
+        iface->rtr_adv_count = GNRC_NDP_MAX_FIN_RTR_ADV_NUMOF;
+        iface->flags &= ~GNRC_IPV6_NETIF_FLAGS_RTR_ADV;
+        iface->adv_ltime = 0;
+#ifdef MODULE_GNRC_NDP_HOST
+        iface->rtr_sol_count = GNRC_NDP_MAX_RTR_SOL_NUMOF;
+#endif
+        mutex_unlock(&iface->mutex);
+        _send_rtr_adv(iface, NULL);
+#ifdef MODULE_GNRC_NDP_HOST
+        gnrc_ndp_host_retrans_rtr_sol(iface);
+#endif
+    }
+}
+
+void gnrc_ndp_router_retrans_rtr_adv(gnrc_ipv6_netif_t *iface)
+{
+    _send_rtr_adv(iface, NULL);
+}
+
+void gnrc_ndp_router_send_rtr_adv(gnrc_ipv6_nc_t *neighbor)
+{
+    gnrc_ipv6_netif_t *iface = gnrc_ipv6_netif_get(neighbor->iface);
+    _send_rtr_adv(iface, &neighbor->ipv6_addr);
+}
+
+static void _send_rtr_adv(gnrc_ipv6_netif_t *iface, ipv6_addr_t *dst)
+{
+    bool fin;
+    uint32_t interval;
+
+    mutex_lock(&iface->mutex);
+    fin = (iface->adv_ltime == 0);
+    interval = genrand_uint32_range(iface->min_adv_int, iface->max_adv_int);
+    if (!fin && !((iface->flags | GNRC_IPV6_NETIF_FLAGS_ROUTER) &&
+                  (iface->flags | GNRC_IPV6_NETIF_FLAGS_RTR_ADV))) {
+        DEBUG("ndp rtr: interface %" PRIkernel_pid " is not an advertising interface\n",
+              iface->pid);
+        return;
+    }
+    if (iface->rtr_adv_count > 1) { /* regard for off-by-one error */
+        iface->rtr_adv_count--;
+        if (!fin && (interval > GNRC_NDP_MAX_INIT_RTR_ADV_INT)) {
+            interval = GNRC_NDP_MAX_INIT_RTR_ADV_INT;
+        }
+    }
+    if (!fin || (iface->rtr_adv_count > 1)) {   /* regard for off-by-one-error */
+        /* reset timer for next router advertisement */
+        vtimer_remove(&iface->rtr_adv_timer);
+        vtimer_set_msg(&iface->rtr_adv_timer, timex_set(interval, 0),
+                       gnrc_ipv6_pid, GNRC_NDP_MSG_RTR_ADV_RETRANS, iface);
+    }
+    mutex_unlock(&iface->mutex);
+    for (int i = 0; i < GNRC_IPV6_NETIF_ADDR_NUMOF; i++) {
+        ipv6_addr_t *src = &iface->addrs[i].addr;
+
+        if (!ipv6_addr_is_unspecified(src) && ipv6_addr_is_link_local(src) &&
+            !gnrc_ipv6_netif_addr_is_non_unicast(src)) {
+            /* send one for every link local address (ideally there is only one) */
+            gnrc_ndp_internal_send_rtr_adv(iface->pid, src, dst, fin);
+        }
+    }
+}
+
+/** @} */