diff --git a/Makefile.dep b/Makefile.dep
index 71c21be140b4af5c29236ad4d7a34f6556b978dd..8674023cac2cc31457276b01aa242846bee1355d 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -78,12 +78,16 @@ ifneq (,$(filter ng_ipv6_default,$(USEMODULE)))
   USEMODULE += ng_ipv6
   USEMODULE += ng_icmpv6
   USEMODULE += ng_ndp
+  USEMODULE += ng_ndp_internal
+  USEMODULE += ng_ndp_node
 endif
 
 ifneq (,$(filter ng_ipv6_router_default,$(USEMODULE)))
   USEMODULE += ng_ipv6_router
   USEMODULE += ng_icmpv6
   USEMODULE += ng_ndp
+  USEMODULE += ng_ndp_internal
+  USEMODULE += ng_ndp_node
 endif
 
 ifneq (,$(filter ng_ndp,$(USEMODULE)))
diff --git a/Makefile.include b/Makefile.include
index 1b72b3137729c834f7e28ea86d7708df2cde2709..013c80be18ad69b0909a99fd1916de24ad67bec2 100644
--- a/Makefile.include
+++ b/Makefile.include
@@ -130,6 +130,14 @@ endif
 include $(RIOTBASE)/Makefile.modules
 include $(RIOTBOARD)/$(BOARD)/Makefile.include
 include $(RIOTCPU)/$(CPU)/Makefile.include
+
+# get number of interfaces straight before resolving dependencies
+NG_NETIF_NUMOF ?= 1
+
+ifneq ($(NG_NETIF_NUMOF),1)
+  CFLAGS += -DNG_NETIF_NUMOF=$(NG_NETIF_NUMOF)
+endif
+
 include $(RIOTBASE)/Makefile.dep
 
 USEMODULE += $(filter-out $(DISABLE_MODULE), $(DEFAULT_MODULE))
diff --git a/sys/Makefile b/sys/Makefile
index 2e616b9f49962e04cde78dc74c6bb4a640e17233..d8c6383f203f16341d0a2130bcc3e3108f245423 100644
--- a/sys/Makefile
+++ b/sys/Makefile
@@ -49,6 +49,12 @@ endif
 ifneq (,$(filter ng_ndp,$(USEMODULE)))
     DIRS += net/network_layer/ng_ndp
 endif
+ifneq (,$(filter ng_ndp_internal,$(USEMODULE)))
+    DIRS += net/network_layer/ng_ndp/internal
+endif
+ifneq (,$(filter ng_ndp_node,$(USEMODULE)))
+    DIRS += net/network_layer/ng_ndp/node
+endif
 ifneq (,$(filter ng_netapi,$(USEMODULE)))
     DIRS += net/crosslayer/ng_netapi
 endif
diff --git a/sys/include/net/fib.h b/sys/include/net/fib.h
index 4bd2b0901b66177aba68144c75489344735373f2..4e2ed4a9d419658c4a9b058e68dc9cc9072e3a22 100644
--- a/sys/include/net/fib.h
+++ b/sys/include/net/fib.h
@@ -22,6 +22,7 @@
 #ifndef FIB_H_
 #define FIB_H_
 
+#include "kernel_types.h"
 #include "timex.h"
 
 #ifdef __cplusplus
diff --git a/sys/include/net/ng_ipv6/netif.h b/sys/include/net/ng_ipv6/netif.h
index fd9de92699346217e0491e7cd56ce751cf1d4b0c..720f9dcc419410158b244790b5f3211fd106fc50 100644
--- a/sys/include/net/ng_ipv6/netif.h
+++ b/sys/include/net/ng_ipv6/netif.h
@@ -253,6 +253,52 @@ void ng_ipv6_netif_remove(kernel_pid_t pid);
  */
 ng_ipv6_netif_t *ng_ipv6_netif_get(kernel_pid_t pid);
 
+/**
+ * @brief   Set interface to router mode.
+ *
+ * @details This sets/unsets the NG_IPV6_NETIF_FLAGS_ROUTER and initializes
+ *          or ceases router behavior for neighbor discovery.
+ *
+ * @param[in] The interface.
+ * @param[in] Status for the NG_IPV6_NETIF_FLAGS_ROUTER flag.
+ */
+static inline void ng_ipv6_netif_set_rtr(ng_ipv6_netif_t *netif, bool enable)
+{
+    (void)netif;    /* Don't do anything for non-routers */
+    (void)enable;
+}
+
+/**
+ * @brief   Set interface to router advertisement mode.
+ *
+ * @details If NG_IPV6_NETIF_FLAGS_ROUTER is set this sets/unsets the
+ *          NG_IPV6_NETIF_FLAGS_RTR_ADV and initializes or ceases router
+ *          advertising behavior for neighbor discovery.
+ *
+ * @param[in] The interface.
+ * @param[in] Status for the NG_IPV6_NETIF_FLAGS_RTR flag.
+ */
+static inline void ng_ipv6_netif_set_rtr_adv(ng_ipv6_netif_t *netif, bool enable)
+{
+    (void)netif;    /* Don't do anything for non-routers */
+    (void)enable;
+}
+
+/**
+ * @brief   Solicitates an advertisement of a neighboring router on this
+ *          interface.
+ *
+ * @param[in] netif The interface.
+ * @param[in] dst   The address of the neighboring router.
+ *                  May be NULL for @ref NG_IPV6_ADDR_ALL_ROUTERS_LINK_LOCAL.
+ */
+static inline void ng_ipv6_netif_sol_router(ng_ipv6_netif_t *netif,
+        ng_ipv6_addr_t *dst)
+{
+    (void)netif;    /* TODO */
+    (void)dst;
+}
+
 /**
  * @brief   Adds an address to an interface.
  *
diff --git a/sys/include/net/ng_ndp.h b/sys/include/net/ng_ndp.h
index 75e5eadd522921132ddaa0d01b5cf05704af6cf9..5d3a976cc66aeff4cfd7da40df3008bd48a4bbb2 100644
--- a/sys/include/net/ng_ndp.h
+++ b/sys/include/net/ng_ndp.h
@@ -27,6 +27,7 @@
 #include "net/ng_ipv6/nc.h"
 #include "net/ng_ipv6/netif.h"
 
+#include "net/ng_ndp/node.h"
 #include "net/ng_ndp/types.h"
 
 #ifndef NG_NDP_H_
@@ -167,27 +168,6 @@ void ng_ndp_netif_add(ng_ipv6_netif_t *iface);
  */
 void ng_ndp_netif_remove(ng_ipv6_netif_t *iface);
 
-/**
- * @brief   Get link-layer address and interface for next hop to destination
- *          IPv6 address.
- *
- * @param[out] l2addr           The link-layer for the next hop to @p dst.
- * @param[out] l2addr_len       Length of @p l2addr.
- * @param[in] iface             The interface to search the next hop on.
- *                              May be @ref KERNEL_PID_UNDEF if not specified.
- * @param[in] dst               An IPv6 address to search the next hop for.
- * @param[in] pkt               Packet to send to @p dst. Leave NULL if you
- *                              just want to get the addresses.
- *
- * @return  The PID of the interface, on success.
- * @return  -EHOSTUNREACH, if @p dst is not reachable.
- * @return  -ENOBUFS, if @p l2addr_len was smaller than the resulting @p l2addr
- *          would be long.
- */
-kernel_pid_t ng_ndp_next_hop_l2addr(uint8_t *l2addr, uint8_t *l2addr_len,
-                                    kernel_pid_t iface, ng_ipv6_addr_t *dst,
-                                    ng_pktsnip_t *pkt);
-
 /**
  * @brief   Builds a neighbor solicitation message for sending.
  *
diff --git a/sys/include/net/ng_ndp/internal.h b/sys/include/net/ng_ndp/internal.h
new file mode 100644
index 0000000000000000000000000000000000000000..165e9151b6aead75627dd26d7e5864168ff9767d
--- /dev/null
+++ b/sys/include/net/ng_ndp/internal.h
@@ -0,0 +1,124 @@
+/*
+ * 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_ng_ndp_internal Internal functions for neighbor discovery.
+ * @ingroup     net_ng_ndp
+ * @brief       Internal functions for neighbor discovery.
+ * @internal
+ * @note        Only for use with a neighbor discovery implementations.
+ * @{
+ *
+ * @file
+ * @brief   Internal neighbor discovery functions for neighbor discovery.
+ *
+ * @author  Martine Lenders <mlenders@inf.fu-berlin.de>
+ */
+#ifndef INTERNAL_H_
+#define INTERNAL_H_
+
+#include "net/ng_ipv6/addr.h"
+#include "net/ng_ndp/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Get best match from default router list.
+ *
+ * @internal
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4861#section-6.3.6">
+ *          RFC 4861, section 6.3.6
+ *      </a>
+ *
+ * @return  Address to a default router.
+ * @return  NULL, if the default router list is empty.
+ */
+ng_ipv6_addr_t *ng_ndp_internal_default_router(void);
+
+/**
+ * @brief   Sets state of a neighbor cache entry and triggers required actions.
+ *
+ * @internal
+ *
+ * @param[in] nc_entry  A neighbor cache entry.
+ * @param[in] state     The new state for the neighbor cache entry.
+ */
+void ng_ndp_internal_set_state(ng_ipv6_nc_t *nc_entry, uint8_t state);
+
+/**
+ * @brief   Send precompiled neighbor solicitation.
+ *
+ * @internal
+ *
+ * @param[in] iface Interface to send over. May not be KERNEL_PID_UNDEF.
+ * @param[in] tgt   Target address for the neighbor solicitation. May not be
+ *                  NULL.
+ * @param[in] dst   Destination address for neighbor solicitation. May not be
+ *                  NULL.
+ */
+void ng_ndp_internal_send_nbr_sol(kernel_pid_t iface, ng_ipv6_addr_t *tgt,
+                                  ng_ipv6_addr_t *dst);
+
+/**
+ * @brief   Send precompiled neighbor advertisement.
+ *
+ * @internal
+ *
+ * @param[in] iface         Interface to send over. May not be KERNEL_PID_UNDEF.
+ * @param[in] tgt           Target address for the neighbor solicitation. May
+ *                          not be NULL.
+ * @param[in] dst           Destination address for neighbor solicitation. May
+ *                          not be NULL.
+ * @param[in] supply_tl2a   Add target link-layer address option to neighbor
+ *                          advertisement if link-layer has addresses.
+ */
+void ng_ndp_internal_send_nbr_adv(kernel_pid_t iface, ng_ipv6_addr_t *tgt,
+                                  ng_ipv6_addr_t *dst, bool supply_tl2a);
+
+/**
+ * @brief   Handles a SL2A option.
+ *
+ * @param[in] iface         Interface the option was received on.
+ * @param[in] pkt           Packet the option was received in.
+ * @param[in] ipv6          IPv6 header of @p pkt
+ * @param[in] icmpv6_type   ICMPv6 type of the message carrying the option.
+ * @param[in] sl2a_opt      The SL2A option.
+ *
+ * @return  true, on success.
+ * @return  false, if SL2A was not valid.
+ */
+bool ng_ndp_internal_sl2a_opt_handle(kernel_pid_t iface, ng_pktsnip_t *pkt,
+                                     ng_ipv6_hdr_t *ipv6, uint8_t icmpv6_type,
+                                     ng_ndp_opt_t *sl2a_opt);
+
+/**
+ * @brief   Handles a TL2A option.
+ *
+ * @param[in] iface         Interface the option was received on.
+ * @param[in] pkt           Packet the option was received in.
+ * @param[in] ipv6          IPv6 header of @p pkt
+ * @param[in] icmpv6_type   ICMPv6 type of the message carrying the option.
+ * @param[in] tl2a_opt      The TL2A option.
+ * @param[out] l2addr       The L2 address carried in the TL2A option.
+ *
+ * @return  length of the L2 address, on success.
+ * @return  -EINVAL, if TL2A was not valid.
+ */
+int ng_ndp_internal_tl2a_opt_handle(ng_pktsnip_t *pkt, ng_ipv6_hdr_t *ipv6,
+                                    uint8_t icmpv6_type, ng_ndp_opt_t *tl2a_opt,
+                                    uint8_t *l2addr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* INTERNAL_H_ */
+/** @} */
diff --git a/sys/include/net/ng_ndp/node.h b/sys/include/net/ng_ndp/node.h
new file mode 100644
index 0000000000000000000000000000000000000000..d17c3a6e35a3a1c621ce50aeeabcb91e089242e6
--- /dev/null
+++ b/sys/include/net/ng_ndp/node.h
@@ -0,0 +1,53 @@
+/*
+ * 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_ng_ndp_node Neighbor discovery for pure IPv6 nodes
+ * @ingroup     net_ng_ndp
+ * @brief       Used for pure IPv6 nodes (without 6LoWPAN).
+ * @{
+ *
+ * @file
+ * @brief   IPv6-node neighbor discovery definitions
+ *
+ * @author  Martine Lenders <mlenders@inf.fu-berlin.de>
+ */
+#ifndef NG_NDP_NODE_H_
+#define NG_NDP_NODE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Get link-layer address and interface for next hop to destination
+ *          IPv6 address.
+ *
+ * @param[out] l2addr           The link-layer for the next hop to @p dst.
+ * @param[out] l2addr_len       Length of @p l2addr.
+ * @param[in] iface             The interface to search the next hop on.
+ *                              May be @ref KERNEL_PID_UNDEF if not specified.
+ * @param[in] dst               An IPv6 address to search the next hop for.
+ * @param[in] pkt               Packet to send to @p dst. Leave NULL if you
+ *                              just want to get the addresses.
+ *
+ * @return  The PID of the interface, on success.
+ * @return  -EHOSTUNREACH, if @p dst is not reachable.
+ * @return  -ENOBUFS, if @p l2addr_len was smaller than the resulting @p l2addr
+ *          would be long.
+ */
+kernel_pid_t ng_ndp_node_next_hop_l2addr(uint8_t *l2addr, uint8_t *l2addr_len,
+                                         kernel_pid_t iface, ng_ipv6_addr_t *dst,
+                                         ng_pktsnip_t *pkt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NG_NDP_NODE_H_ */
+/** @} */
diff --git a/sys/net/network_layer/ng_ipv6/ng_ipv6.c b/sys/net/network_layer/ng_ipv6/ng_ipv6.c
index 9f25fefc86c058d25d4bcb408240f5c4ba4cddb3..e7cd878b587cd503bb3df4a865bd12343328f9bd 100644
--- a/sys/net/network_layer/ng_ipv6/ng_ipv6.c
+++ b/sys/net/network_layer/ng_ipv6/ng_ipv6.c
@@ -450,6 +450,22 @@ static void _send_multicast(kernel_pid_t iface, ng_pktsnip_t *pkt,
 #endif  /* NG_NETIF_NUMOF */
 }
 
+static inline kernel_pid_t _next_hop_l2addr(uint8_t *l2addr, uint8_t *l2addr_len,
+                                            kernel_pid_t iface, ng_ipv6_addr_t *dst,
+                                            ng_pktsnip_t *pkt)
+{
+#ifdef MODULE_NG_NDP_NODE
+    return ng_ndp_node_next_hop_l2addr(l2addr, l2addr_len, iface, dst, pkt);
+#else
+    (void)l2addr;
+    (void)iface;
+    (void)dst;
+    (void)pkt;
+    *l2addr_len = 0;
+    return KERNEL_PID_UNDEF;
+#endif
+}
+
 static void _send(ng_pktsnip_t *pkt, bool prep_hdr)
 {
     kernel_pid_t iface = KERNEL_PID_UNDEF;
@@ -537,8 +553,7 @@ static void _send(ng_pktsnip_t *pkt, bool prep_hdr)
         uint8_t l2addr_len = NG_IPV6_NC_L2_ADDR_MAX;
         uint8_t l2addr[l2addr_len];
 
-        iface = ng_ndp_next_hop_l2addr(l2addr, &l2addr_len, iface, &hdr->dst,
-                                       pkt);
+        iface = _next_hop_l2addr(l2addr, &l2addr_len, iface, &hdr->dst, pkt);
 
         if (iface == KERNEL_PID_UNDEF) {
             DEBUG("ipv6: error determining next hop's link layer address\n");
diff --git a/sys/net/network_layer/ng_ndp/internal/Makefile b/sys/net/network_layer/ng_ndp/internal/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..7c18d557335705844cceb871845e9124f73e7fd4
--- /dev/null
+++ b/sys/net/network_layer/ng_ndp/internal/Makefile
@@ -0,0 +1,3 @@
+MODULE = ng_ndp_internal
+
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/network_layer/ng_ndp/internal/ng_ndp_internal.c b/sys/net/network_layer/ng_ndp/internal/ng_ndp_internal.c
new file mode 100644
index 0000000000000000000000000000000000000000..1b9349bd9952602934a40092815f313b350cf8af
--- /dev/null
+++ b/sys/net/network_layer/ng_ndp/internal/ng_ndp_internal.c
@@ -0,0 +1,449 @@
+/*
+ * 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 <stdlib.h>
+
+#include "net/ng_ipv6.h"
+#include "net/ng_ndp.h"
+#include "random.h"
+#include "timex.h"
+#include "vtimer.h"
+
+#include "net/ng_ndp/internal.h"
+
+#define ENABLE_DEBUG    (0)
+#include "debug.h"
+
+#if ENABLE_DEBUG
+static char addr_str[NG_IPV6_ADDR_MAX_STR_LEN];
+#endif
+
+static ng_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);
+
+/**
+ * @brief   Sends @ref NG_NETAPI_MSG_TYPE_SND delayed.
+ *
+ * @param[in] t         Timer for the delay.
+ * @param[in] interval  Delay interval.
+ * @param[in] pkt       Packet to send delayed.
+ */
+static inline void _send_delayed(vtimer_t *t, timex_t interval, ng_pktsnip_t *pkt)
+{
+    vtimer_remove(t);
+    vtimer_set_msg(t, interval, ng_ipv6_pid, NG_NETAPI_MSG_TYPE_SND, pkt);
+}
+
+
+ng_ipv6_addr_t *ng_ndp_internal_default_router(void)
+{
+    ng_ipv6_nc_t *router = ng_ipv6_nc_get_next_router(NULL);
+
+    /* first look if there is any reachable router */
+    while (router != NULL) {
+        if ((ng_ipv6_nc_get_state(router) != NG_IPV6_NC_STATE_INCOMPLETE) &&
+            (ng_ipv6_nc_get_state(router) != NG_IPV6_NC_STATE_UNREACHABLE)) {
+            _last_router = NULL;
+
+            return &router->ipv6_addr;
+        }
+
+        router = ng_ipv6_nc_get_next_router(router);
+    }
+
+    /* else take the first one, but keep round-robin in further selections */
+    router = ng_ipv6_nc_get_next_router(_last_router);
+
+    if (router == NULL) {   /* end of router list or there is none => wrap around */
+        router = ng_ipv6_nc_get_next_router(router);
+
+        if (router == NULL) {   /* still nothing found => no router in list */
+            return NULL;
+        }
+    }
+
+    _last_router = router;
+
+    return &router->ipv6_addr;
+}
+
+void ng_ndp_internal_set_state(ng_ipv6_nc_t *nc_entry, uint8_t state)
+{
+    ng_ipv6_netif_t *ipv6_iface;
+    timex_t t = { NG_NDP_FIRST_PROBE_DELAY, 0 };
+
+    nc_entry->flags &= ~NG_IPV6_NC_STATE_MASK;
+    nc_entry->flags |= state;
+
+    DEBUG("ndp internal: set %s state to ",
+          ng_ipv6_addr_to_str(addr_str, &nc_entry->ipv6_addr, sizeof(addr_str)));
+
+    switch (state) {
+        case NG_IPV6_NC_STATE_REACHABLE:
+            ipv6_iface = ng_ipv6_netif_get(nc_entry->iface);
+            DEBUG("REACHABLE (reachable time = %" PRIu32 ".%06" PRIu32 ")\n",
+                  ipv6_iface->reach_time.seconds,
+                  ipv6_iface->reach_time.microseconds);
+            t = ipv6_iface->reach_time;
+
+            /* we intentionally fall through here to set the desired timeout t */
+        case NG_IPV6_NC_STATE_DELAY:
+#if ENABLE_DEBUG
+            if (state == NG_IPV6_NC_STATE_DELAY) {
+                DEBUG("DELAY (probe with unicast NS in %u seconds)\n",
+                      NG_NDP_FIRST_PROBE_DELAY);
+            }
+#endif
+            vtimer_remove(&nc_entry->nbr_sol_timer);
+            vtimer_set_msg(&nc_entry->nbr_sol_timer, t, ng_ipv6_pid,
+                           NG_NDP_MSG_NC_STATE_TIMEOUT, nc_entry);
+            break;
+
+        case NG_IPV6_NC_STATE_PROBE:
+            ipv6_iface = ng_ipv6_netif_get(nc_entry->iface);
+
+            nc_entry->probes_remaining = NG_NDP_MAX_UC_NBR_SOL_NUMOF;
+            DEBUG("PROBE (probe with %" PRIu8 " unicast NS every %" PRIu32
+                  ".%06" PRIu32 " seconds)\n", nc_entry->probes_remaining,
+                  ipv6_iface->retrans_timer.seconds,
+                  ipv6_iface->retrans_timer.microseconds);
+
+            ng_ndp_internal_send_nbr_sol(nc_entry->iface, &nc_entry->ipv6_addr,
+                                         &nc_entry->ipv6_addr);
+
+            mutex_lock(&ipv6_iface->mutex);
+            vtimer_remove(&nc_entry->nbr_sol_timer);
+            vtimer_set_msg(&nc_entry->nbr_sol_timer,
+                           ipv6_iface->retrans_timer, ng_ipv6_pid,
+                           NG_NDP_MSG_NBR_SOL_RETRANS, nc_entry);
+            mutex_unlock(&ipv6_iface->mutex);
+            break;
+
+#ifdef ENABLE_DEBUG
+        case NG_IPV6_NC_STATE_STALE:
+            DEBUG("STALE (go into DELAY on next packet)\n");
+            break;
+#endif
+
+        default:
+            DEBUG("errorneous or unknown\n");
+            break;
+    }
+}
+
+void ng_ndp_internal_send_nbr_adv(kernel_pid_t iface, ng_ipv6_addr_t *tgt,
+                                  ng_ipv6_addr_t *dst, bool supply_tl2a)
+{
+    ng_pktsnip_t *hdr, *pkt = NULL;
+    uint8_t adv_flags = 0;
+
+    DEBUG("ndp internal: send neighbor advertisement (iface: %" PRIkernel_pid ", tgt: %s, ",
+          iface, ng_ipv6_addr_to_str(addr_str, tgt, sizeof(addr_str)));
+    DEBUG("dst: %s, supply_tl2a: %d)\n",
+          ng_ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)), supply_tl2a);
+
+    if (ng_ipv6_netif_get(iface)->flags & NG_IPV6_NETIF_FLAGS_ROUTER) {
+        adv_flags |= NG_NDP_NBR_ADV_FLAGS_R;
+    }
+
+    if (ng_ipv6_addr_is_unspecified(dst)) {
+        ng_ipv6_addr_set_all_nodes_multicast(dst,
+                                             NG_IPV6_ADDR_MCAST_SCP_LINK_LOCAL);
+    }
+    else {
+        adv_flags |= NG_NDP_NBR_ADV_FLAGS_S;
+    }
+
+    if (supply_tl2a) {
+        uint8_t l2src[8];
+        uint16_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);
+
+        if (l2src_len > 0) {
+            /* add target address link-layer address option */
+            pkt = ng_ndp_opt_tl2a_build(l2src, l2src_len, NULL);
+
+            if (pkt == NULL) {
+                DEBUG("ndp internal: error allocating Target Link-layer address option.\n");
+                ng_pktbuf_release(pkt);
+                return;
+            }
+        }
+    }
+
+    /* TODO: also check if the node provides proxy servies for tgt */
+    if ((pkt != NULL) && !ng_ipv6_netif_addr_is_non_unicast(tgt)) {
+        /* TL2A is not supplied and tgt is not anycast */
+        adv_flags |= NG_NDP_NBR_ADV_FLAGS_O;
+    }
+
+    hdr = ng_ndp_nbr_adv_build(adv_flags, tgt, pkt);
+
+    if (hdr == NULL) {
+        DEBUG("ndp internal: error allocating Neighbor advertisement.\n");
+        ng_pktbuf_release(pkt);
+        return;
+    }
+
+    pkt = hdr;
+    hdr = ng_ipv6_hdr_build(pkt, NULL, 0, (uint8_t *)dst,
+                            sizeof(ng_ipv6_addr_t));
+
+    if (hdr == NULL) {
+        DEBUG("ndp internal: error allocating IPv6 header.\n");
+        ng_pktbuf_release(pkt);
+        return;
+    }
+
+    ((ng_ipv6_hdr_t *)hdr->data)->hl = 255;
+
+    pkt = hdr;
+    /* add netif header for send interface specification */
+    hdr = ng_netif_hdr_build(NULL, 0, NULL, 0);
+
+    if (hdr == NULL) {
+        DEBUG("ndp internal: error allocating netif header.\n");
+        return;
+    }
+
+    ((ng_netif_hdr_t *)hdr->data)->if_pid = iface;
+
+    LL_PREPEND(pkt, hdr);
+
+    if (ng_ipv6_netif_addr_is_non_unicast(tgt)) {
+        /* avoid collision for anycast addresses
+         * (see https://tools.ietf.org/html/rfc4861#section-7.2.7) */
+        timex_t delay = { 0, genrand_uint32_range(0, NG_NDP_MAX_AC_TGT_DELAY * SEC_IN_USEC) };
+        timex_normalize(&delay);
+        ng_ipv6_nc_t *nc_entry = ng_ipv6_nc_get(iface, tgt);
+        DEBUG("ndp internal: delay neighbor advertisement for %" PRIu32 " sec.",
+              delay.seconds);
+
+        /* nc_entry must be set so no need to check it */
+        _send_delayed(&nc_entry->nbr_adv_timer, delay, pkt);
+    }
+    else {
+        ng_netapi_send(ng_ipv6_pid, pkt);
+    }
+}
+
+void ng_ndp_internal_send_nbr_sol(kernel_pid_t iface, ng_ipv6_addr_t *tgt,
+                                  ng_ipv6_addr_t *dst)
+{
+    ng_pktsnip_t *hdr, *pkt = NULL;
+    ng_ipv6_addr_t *src = NULL;
+    size_t src_len = 0;
+
+    DEBUG("ndp internal: send neighbor solicitation (iface: %" PRIkernel_pid ", tgt: %s, ",
+          iface, ng_ipv6_addr_to_str(addr_str, tgt, sizeof(addr_str)));
+    DEBUG("dst: %s)\n", ng_ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)));
+
+    /* check if there is a fitting source address to target */
+    if ((src = ng_ipv6_netif_find_best_src_addr(iface, tgt)) != NULL) {
+        uint8_t l2src[8];
+        uint16_t l2src_len;
+        src_len = sizeof(ng_ipv6_addr_t);
+        l2src_len = _get_l2src(l2src, sizeof(l2src), iface);
+
+        if (l2src_len > 0) {
+            /* add source address link-layer address option */
+            pkt = ng_ndp_opt_sl2a_build(l2src, l2src_len, NULL);
+
+            if (pkt == NULL) {
+                DEBUG("ndp internal: error allocating Source Link-layer address option.\n");
+                ng_pktbuf_release(pkt);
+                return;
+            }
+        }
+    }
+
+    hdr = ng_ndp_nbr_sol_build(tgt, pkt);
+
+    if (hdr == NULL) {
+        DEBUG("ndp internal: error allocating Neighbor solicitation.\n");
+        ng_pktbuf_release(pkt);
+        return;
+    }
+
+    pkt = hdr;
+    hdr = ng_ipv6_hdr_build(pkt, (uint8_t *)src, src_len, (uint8_t *)dst,
+                            sizeof(ng_ipv6_addr_t));
+
+    if (hdr == NULL) {
+        DEBUG("ndp internal: error allocating IPv6 header.\n");
+        ng_pktbuf_release(pkt);
+        return;
+    }
+
+    ((ng_ipv6_hdr_t *)hdr->data)->hl = 255;
+
+    pkt = hdr;
+    /* add netif header for send interface specification */
+    hdr = ng_netif_hdr_build(NULL, 0, NULL, 0);
+
+    if (hdr == NULL) {
+        DEBUG("ndp internal: error allocating netif header.\n");
+        return;
+    }
+
+    ((ng_netif_hdr_t *)hdr->data)->if_pid = iface;
+
+    LL_PREPEND(pkt, hdr);
+
+    ng_netapi_send(ng_ipv6_pid, pkt);
+}
+
+bool ng_ndp_internal_sl2a_opt_handle(kernel_pid_t iface, ng_pktsnip_t *pkt,
+                                     ng_ipv6_hdr_t *ipv6, uint8_t icmpv6_type,
+                                     ng_ndp_opt_t *sl2a_opt)
+{
+    ng_ipv6_nc_t *nc_entry = NULL;
+    uint8_t sl2a_len = 0;
+    uint8_t *sl2a = (uint8_t *)(sl2a_opt + 1);
+
+    if ((sl2a_opt->len == 0) || ng_ipv6_addr_is_unspecified(&ipv6->src)) {
+        DEBUG("ndp: invalid source link-layer address option received\n");
+        return false;
+    }
+
+    while (pkt) {
+        if (pkt->type == NG_NETTYPE_NETIF) {
+            ng_netif_hdr_t *hdr = pkt->data;
+            sl2a_len = hdr->src_l2addr_len;
+            break;
+        }
+        pkt = pkt->next;
+    }
+
+    if (sl2a_len == 0) {  /* in case there was no source address in l2 */
+        sl2a_len = (sl2a_opt->len / 8) - sizeof(ng_ndp_opt_t);
+
+        /* ignore all zeroes at the end for length */
+        for (; sl2a[sl2a_len - 1] == 0x00; sl2a_len--);
+    }
+
+    DEBUG("ndp: received SL2A (link-layer address: %s)\n",
+          ng_netif_addr_to_str(addr_str, sizeof(addr_str), sl2a, sl2a_len));
+
+    switch (icmpv6_type) {
+        case NG_ICMPV6_NBR_SOL:
+            nc_entry = ng_ipv6_nc_get(iface, &ipv6->src);
+
+            if (nc_entry != NULL) {
+                if ((sl2a_len != nc_entry->l2_addr_len) ||
+                    (memcmp(sl2a, nc_entry->l2_addr, sl2a_len) != 0)) {
+                    /* if entry exists but l2 address differs: set */
+                    nc_entry->l2_addr_len = sl2a_len;
+                    memcpy(nc_entry->l2_addr, sl2a, sl2a_len);
+
+                    ng_ndp_internal_set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
+                }
+            }
+            else {
+                ng_ipv6_nc_add(iface, &ipv6->src, sl2a, sl2a_len,
+                               NG_IPV6_NC_STATE_STALE);
+            }
+
+            return true;
+
+        default:    /* wrong encapsulating message: silently discard */
+            DEBUG("ndp: silently discard sl2a_opt for ICMPv6 message type %"
+                  PRIu8 "\n", icmpv6_type);
+            return true;
+    }
+}
+
+int ng_ndp_internal_tl2a_opt_handle(ng_pktsnip_t *pkt, ng_ipv6_hdr_t *ipv6,
+                                    uint8_t icmpv6_type, ng_ndp_opt_t *tl2a_opt,
+                                    uint8_t *l2addr)
+{
+    uint8_t tl2a_len = 0;
+    uint8_t *tl2a = (uint8_t *)(tl2a_opt + 1);
+
+    if ((tl2a_opt->len == 0) || ng_ipv6_addr_is_unspecified(&ipv6->src)) {
+        DEBUG("ndp: invalid target link-layer address option received\n");
+        return -EINVAL;
+    }
+
+    switch (icmpv6_type) {
+        case NG_ICMPV6_NBR_ADV:
+            while (pkt) {
+                if (pkt->type == NG_NETTYPE_NETIF) {
+                    ng_netif_hdr_t *hdr = pkt->data;
+                    tl2a_len = hdr->src_l2addr_len;
+                    break;
+                }
+                pkt = pkt->next;
+            }
+
+            if (tl2a_len == 0) {  /* in case there was no source address in l2 */
+                tl2a_len = (tl2a_opt->len / 8) - sizeof(ng_ndp_opt_t);
+
+                /* ignore all zeroes at the end for length */
+                for (; tl2a[tl2a_len - 1] == 0x00; tl2a_len--);
+            }
+
+            DEBUG("ndp: received TL2A (link-layer address: %s)\n",
+                  ng_netif_addr_to_str(addr_str, sizeof(addr_str), tl2a, tl2a_len));
+
+            memcpy(l2addr, tl2a, tl2a_len);
+
+            return (int)tl2a_len;
+
+        default:    /* wrong encapsulating message: silently discard */
+            DEBUG("ndp: silently discard tl2a_opt for ICMPv6 message type %"
+                  PRIu8 "\n", icmpv6_type);
+            return 0;
+    }
+}
+static uint16_t _get_l2src(uint8_t *l2src, size_t l2src_size, kernel_pid_t iface)
+{
+    bool try_long = false;
+    int res;
+    uint16_t l2src_len;
+    /* maximum address length that fits into a minimum length (8) S/TL2A option */
+    const uint16_t max_short_len = 6;
+
+    /* try getting source address */
+    if ((ng_netapi_get(iface, NETOPT_SRC_LEN, 0, &l2src_len,
+                       sizeof(l2src_len)) >= 0) &&
+        (l2src_len > max_short_len)) {
+        try_long = true;
+    }
+
+    if (try_long && ((res = ng_netapi_get(iface, NETOPT_ADDRESS_LONG, 0,
+                                          l2src, l2src_size)) > max_short_len)) {
+        l2src_len = (uint16_t)res;
+    }
+    else if ((res = ng_netapi_get(iface, NETOPT_ADDRESS, 0, l2src,
+                                  l2src_size)) >= 0) {
+        l2src_len = (uint16_t)res;
+    }
+    else {
+        DEBUG("ndp internal: no link-layer address found.\n");
+        l2src_len = 0;
+    }
+
+    return l2src_len;
+}
+
+/** @} */
diff --git a/sys/net/network_layer/ng_ndp/ng_ndp.c b/sys/net/network_layer/ng_ndp/ng_ndp.c
index b50c31756b47dc8b03c40bc279252ac3a7684b6e..a722b3eb7d6f02319d38514296b293652e086763 100644
--- a/sys/net/network_layer/ng_ndp/ng_ndp.c
+++ b/sys/net/network_layer/ng_ndp/ng_ndp.c
@@ -19,6 +19,7 @@
 #include <string.h>
 
 #include "byteorder.h"
+#include "net/fib.h"
 #include "net/ng_icmpv6.h"
 #include "net/ng_ipv6.h"
 #include "net/ng_ipv6/ext/rh.h"
@@ -28,8 +29,9 @@
 #include "thread.h"
 #include "vtimer.h"
 
+#include "net/ng_ndp/internal.h"
+
 #include "net/ng_ndp.h"
-#include "net/fib.h"
 
 #define ENABLE_DEBUG    (0)
 #include "debug.h"
@@ -41,43 +43,7 @@
 static char addr_str[NG_IPV6_ADDR_MAX_STR_LEN];
 #endif
 
-static ng_pktqueue_t _pkt_nodes[NG_IPV6_NC_SIZE * 2];
-static ng_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) */
-
 /* random helper function */
-static inline uint32_t _rand(uint32_t min, uint32_t max)
-{
-    return (genrand_uint32() % (max - min)) + min;
-}
-
-static bool _handle_sl2a_opt(kernel_pid_t iface, ng_pktsnip_t *pkt,
-                             ng_ipv6_hdr_t *ipv6, uint8_t icmpv6_type,
-                             ng_ndp_opt_t *sl2a_opt);
-static int _handle_tl2a_opt(ng_pktsnip_t *pkt, ng_ipv6_hdr_t *ipv6,
-                            uint8_t icmpv6_type, ng_ndp_opt_t *tl2a_opt,
-                            uint8_t *l2addr);
-
-/* send address resolution messages */
-static void _send_nbr_sol(kernel_pid_t iface, ng_ipv6_addr_t *tgt,
-                          ng_ipv6_addr_t *dst);
-static void _send_nbr_adv(kernel_pid_t iface, ng_ipv6_addr_t *tgt,
-                          ng_ipv6_addr_t *dst, bool supply_tl2a);
-
-static void _set_state(ng_ipv6_nc_t *nc_entry, uint8_t state);
-
-/* special netapi helper */
-static inline void _send_delayed(vtimer_t *t, timex_t interval, ng_pktsnip_t *pkt)
-{
-    vtimer_remove(t);
-    vtimer_set_msg(t, interval, ng_ipv6_pid, NG_NETAPI_MSG_TYPE_SND, pkt);
-}
-
-/* packet queue node allocation */
-static ng_pktqueue_t *_alloc_pkt_node(ng_pktsnip_t *pkt);
-
 void ng_ndp_nbr_sol_handle(kernel_pid_t iface, ng_pktsnip_t *pkt,
                            ng_ipv6_hdr_t *ipv6, ng_ndp_nbr_sol_t *nbr_sol,
                            size_t icmpv6_size)
@@ -119,7 +85,7 @@ void ng_ndp_nbr_sol_handle(kernel_pid_t iface, ng_pktsnip_t *pkt,
 
         switch (opt->type) {
             case NG_NDP_OPT_SL2A:
-                if (!_handle_sl2a_opt(iface, pkt, ipv6, nbr_sol->type, opt)) {
+                if (!ng_ndp_internal_sl2a_opt_handle(iface, pkt, ipv6, nbr_sol->type, opt)) {
                     /* invalid source link-layer address option */
                     return;
                 }
@@ -135,8 +101,8 @@ void ng_ndp_nbr_sol_handle(kernel_pid_t iface, ng_pktsnip_t *pkt,
         sicmpv6_size -= (opt->len * 8);
     }
 
-    _send_nbr_adv(iface, tgt, &ipv6->src,
-                  ng_ipv6_addr_is_multicast(&ipv6->dst));
+    ng_ndp_internal_send_nbr_adv(iface, tgt, &ipv6->src,
+                                 ng_ipv6_addr_is_multicast(&ipv6->dst));
 
     return;
 }
@@ -190,8 +156,7 @@ void ng_ndp_nbr_adv_handle(kernel_pid_t iface, ng_pktsnip_t *pkt,
 
         switch (opt->type) {
             case NG_NDP_OPT_TL2A:
-                if ((l2tgt_len = _handle_tl2a_opt(pkt, ipv6, nbr_adv->type, opt,
-                                                  l2tgt)) < 0) {
+                if ((l2tgt_len = ng_ndp_internal_tl2a_opt_handle(pkt, ipv6, nbr_adv->type, opt, l2tgt)) < 0) {
                     /* invalid target link-layer address option */
                     return;
                 }
@@ -227,10 +192,10 @@ void ng_ndp_nbr_adv_handle(kernel_pid_t iface, ng_pktsnip_t *pkt,
         memcpy(nc_entry->l2_addr, l2tgt, l2tgt_len);
 
         if (nbr_adv->flags & NG_NDP_NBR_ADV_FLAGS_S) {
-            _set_state(nc_entry, NG_IPV6_NC_STATE_REACHABLE);
+            ng_ndp_internal_set_state(nc_entry, NG_IPV6_NC_STATE_REACHABLE);
         }
         else {
-            _set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
+            ng_ndp_internal_set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
         }
 
         if (nbr_adv->flags & NG_NDP_NBR_ADV_FLAGS_R) {
@@ -270,10 +235,10 @@ void ng_ndp_nbr_adv_handle(kernel_pid_t iface, ng_pktsnip_t *pkt,
             }
 
             if (nbr_adv->flags & NG_NDP_NBR_ADV_FLAGS_S) {
-                _set_state(nc_entry, NG_IPV6_NC_STATE_REACHABLE);
+                ng_ndp_internal_set_state(nc_entry, NG_IPV6_NC_STATE_REACHABLE);
             }
             else if (l2tgt_changed && (l2tgt_len != 0)) {
-                _set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
+                ng_ndp_internal_set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
             }
 
             if (nbr_adv->flags & NG_NDP_NBR_ADV_FLAGS_R) {
@@ -286,7 +251,7 @@ void ng_ndp_nbr_adv_handle(kernel_pid_t iface, ng_pktsnip_t *pkt,
         }
         else if (l2tgt_changed) {
             if (ng_ipv6_nc_get_state(nc_entry) == NG_IPV6_NC_STATE_REACHABLE) {
-                _set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
+                ng_ndp_internal_set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
 
             }
         }
@@ -322,7 +287,7 @@ void ng_ndp_retrans_nbr_sol(ng_ipv6_nc_t *nc_entry)
                 size_t ifnum = ng_netif_get(ifs);
 
                 for (size_t i = 0; i < ifnum; i++) {
-                    _send_nbr_sol(ifs[i], &nc_entry->ipv6_addr, &dst);
+                    ng_ndp_internal_send_nbr_sol(ifs[i], &nc_entry->ipv6_addr, &dst);
                 }
 
                 vtimer_remove(&nc_entry->nbr_sol_timer);
@@ -332,7 +297,7 @@ void ng_ndp_retrans_nbr_sol(ng_ipv6_nc_t *nc_entry)
             else {
                 ng_ipv6_netif_t *ipv6_iface = ng_ipv6_netif_get(nc_entry->iface);
 
-                _send_nbr_sol(nc_entry->iface, &nc_entry->ipv6_addr, &dst);
+                ng_ndp_internal_send_nbr_sol(nc_entry->iface, &nc_entry->ipv6_addr, &dst);
 
                 mutex_lock(&ipv6_iface->mutex);
                 vtimer_remove(&nc_entry->nbr_sol_timer);
@@ -359,11 +324,11 @@ void ng_ndp_state_timeout(ng_ipv6_nc_t *nc_entry)
 {
     switch (ng_ipv6_nc_get_state(nc_entry)) {
         case NG_IPV6_NC_STATE_REACHABLE:
-            _set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
+            ng_ndp_internal_set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
             break;
 
         case NG_IPV6_NC_STATE_DELAY:
-            _set_state(nc_entry, NG_IPV6_NC_STATE_PROBE);
+            ng_ndp_internal_set_state(nc_entry, NG_IPV6_NC_STATE_PROBE);
             break;
 
         default:
@@ -373,7 +338,7 @@ void ng_ndp_state_timeout(ng_ipv6_nc_t *nc_entry)
 
 void ng_ndp_netif_add(ng_ipv6_netif_t *iface)
 {
-    uint32_t reach_time = _rand(NG_NDP_MIN_RAND, NG_NDP_MAX_RAND);
+    uint32_t reach_time = genrand_uint32_range(NG_NDP_MIN_RAND, NG_NDP_MAX_RAND);
 
     /* set default values */
     mutex_lock(&iface->mutex);
@@ -392,173 +357,6 @@ void ng_ndp_netif_remove(ng_ipv6_netif_t *iface)
     (void) iface;
 }
 
-static ng_ipv6_addr_t *_default_router(void)
-{
-    ng_ipv6_nc_t *router = ng_ipv6_nc_get_next_router(NULL);
-
-    /* first look if there is any reachable router */
-    while (router != NULL) {
-        if ((ng_ipv6_nc_get_state(router) != NG_IPV6_NC_STATE_INCOMPLETE) &&
-            (ng_ipv6_nc_get_state(router) != NG_IPV6_NC_STATE_UNREACHABLE)) {
-            _last_router = NULL;
-
-            return &router->ipv6_addr;
-        }
-
-        router = ng_ipv6_nc_get_next_router(router);
-    }
-
-    /* else take the first one, but keep round-robin in further selections */
-    router = ng_ipv6_nc_get_next_router(_last_router);
-
-    if (router == NULL) {   /* end of router list or there is none => wrap around */
-        router = ng_ipv6_nc_get_next_router(router);
-
-        if (router == NULL) {   /* still nothing found => no router in list */
-            return NULL;
-        }
-    }
-
-    _last_router = router;
-
-    return &router->ipv6_addr;
-}
-
-kernel_pid_t ng_ndp_next_hop_l2addr(uint8_t *l2addr, uint8_t *l2addr_len,
-                                    kernel_pid_t iface, ng_ipv6_addr_t *dst,
-                                    ng_pktsnip_t *pkt)
-{
-    ng_ipv6_addr_t *next_hop_ip = NULL, *prefix = NULL;
-
-#ifdef MODULE_NG_IPV6_EXT_RH
-    next_hop_ip = ng_ipv6_ext_rh_next_hop(hdr);
-#endif
-#ifdef MODULE_FIB
-    size_t next_hop_size = sizeof(ng_ipv6_addr_t);
-    uint32_t next_hop_flags = 0;
-    ng_ipv6_addr_t next_hop_actual; /* FIB copies address into this variable */
-
-    if ((next_hop_ip == NULL) &&
-        (fib_get_next_hop(&iface, next_hop_actual.u8, &next_hop_size,
-                          &next_hop_flags, (uint8_t *)dst,
-                          sizeof(ng_ipv6_addr_t), 0) >= 0) &&
-        (next_hop_size == sizeof(ng_ipv6_addr_t))) {
-        next_hop_ip = &next_hop_actual;
-    }
-
-#endif
-
-    if (next_hop_ip == NULL) {            /* no route to host */
-        if (iface == KERNEL_PID_UNDEF) {
-            /* ng_ipv6_netif_t doubles as prefix list */
-            iface = ng_ipv6_netif_find_by_prefix(&prefix, dst);
-        }
-        else {
-            /* ng_ipv6_netif_t doubles as prefix list */
-            prefix = ng_ipv6_netif_match_prefix(iface, dst);
-        }
-
-        if ((prefix != NULL) &&             /* prefix is on-link */
-            (ng_ipv6_netif_addr_get(prefix)->flags &
-             NG_IPV6_NETIF_ADDR_FLAGS_NDP_ON_LINK)) {
-            next_hop_ip = dst;
-#ifdef MODULE_FIB
-            /* We don't care if FIB is full, this is just for efficiency
-             * for later sends */
-            fib_add_entry(iface, (uint8_t *)dst, sizeof(ng_ipv6_addr_t), 0,
-                          (uint8_t *)next_hop_ip, sizeof(ng_ipv6_addr_t), 0,
-                          FIB_LIFETIME_NO_EXPIRE);
-#endif
-        }
-    }
-
-    if (next_hop_ip == NULL) {
-        next_hop_ip = _default_router();
-#ifdef MODULE_FIB
-        /* We don't care if FIB is full, this is just for efficiency for later
-         * sends */
-        fib_add_entry(iface, (uint8_t *)dst, sizeof(ng_ipv6_addr_t), 0,
-                      (uint8_t *)next_hop_ip, sizeof(ng_ipv6_addr_t), 0,
-                      FIB_LIFETIME_NO_EXPIRE);
-#endif
-    }
-
-    if (next_hop_ip != NULL) {
-        ng_ipv6_nc_t *nc_entry = ng_ipv6_nc_get(iface, next_hop_ip);
-
-        if ((nc_entry != NULL) && ng_ipv6_nc_is_reachable(nc_entry)) {
-            DEBUG("ndp: found reachable neighbor (%s => ",
-                  ng_ipv6_addr_to_str(addr_str, &nc_entry->ipv6_addr, sizeof(addr_str)));
-            DEBUG("%s)\n",
-                  ng_netif_addr_to_str(addr_str, sizeof(addr_str),
-                                       nc_entry->l2_addr, nc_entry->l2_addr_len));
-
-            if (ng_ipv6_nc_get_state(nc_entry) == NG_IPV6_NC_STATE_STALE) {
-                _set_state(nc_entry, NG_IPV6_NC_STATE_DELAY);
-            }
-
-            memcpy(l2addr, nc_entry->l2_addr, nc_entry->l2_addr_len);
-            *l2addr_len = nc_entry->l2_addr_len;
-            /* TODO: unreachability check */
-            return nc_entry->iface;
-        }
-        else if (nc_entry == NULL) {
-            ng_pktqueue_t *pkt_node;
-            ng_ipv6_addr_t dst_sol;
-
-            nc_entry = ng_ipv6_nc_add(iface, next_hop_ip, NULL, 0,
-                                      NG_IPV6_NC_STATE_INCOMPLETE << NG_IPV6_NC_STATE_POS);
-
-            if (nc_entry == NULL) {
-                DEBUG("ndp: could not create neighbor cache entry\n");
-                return KERNEL_PID_UNDEF;
-            }
-
-            pkt_node = _alloc_pkt_node(pkt);
-
-            if (pkt_node == NULL) {
-                DEBUG("ndp: could not add packet to packet queue\n");
-            }
-            else {
-                /* prevent packet from being released by IPv6 */
-                ng_pktbuf_hold(pkt_node->pkt, 1);
-                ng_pktqueue_add(&nc_entry->pkts, pkt_node);
-            }
-
-            /* address resolution */
-            ng_ipv6_addr_set_solicited_nodes(&dst_sol, next_hop_ip);
-
-            if (iface == KERNEL_PID_UNDEF) {
-                timex_t t = { 0, NG_NDP_RETRANS_TIMER };
-                kernel_pid_t ifs[NG_NETIF_NUMOF];
-                size_t ifnum = ng_netif_get(ifs);
-
-                for (size_t i = 0; i < ifnum; i++) {
-                    _send_nbr_sol(ifs[i], next_hop_ip, &dst_sol);
-                }
-
-                vtimer_remove(&nc_entry->nbr_sol_timer);
-                vtimer_set_msg(&nc_entry->nbr_sol_timer, t, ng_ipv6_pid,
-                               NG_NDP_MSG_NBR_SOL_RETRANS, nc_entry);
-            }
-            else {
-                ng_ipv6_netif_t *ipv6_iface = ng_ipv6_netif_get(iface);
-
-                _send_nbr_sol(iface, next_hop_ip, &dst_sol);
-
-                mutex_lock(&ipv6_iface->mutex);
-                vtimer_remove(&nc_entry->nbr_sol_timer);
-                vtimer_set_msg(&nc_entry->nbr_sol_timer,
-                               ipv6_iface->retrans_timer, ng_ipv6_pid,
-                               NG_NDP_MSG_NBR_SOL_RETRANS, nc_entry);
-                mutex_unlock(&ipv6_iface->mutex);
-            }
-        }
-    }
-
-    return KERNEL_PID_UNDEF;
-}
-
 ng_pktsnip_t *ng_ndp_nbr_sol_build(ng_ipv6_addr_t *tgt, ng_pktsnip_t *options)
 {
     ng_pktsnip_t *pkt;
@@ -631,199 +429,6 @@ ng_pktsnip_t *ng_ndp_opt_build(uint8_t type, size_t size, ng_pktsnip_t *next)
     return pkt;
 }
 
-static uint16_t _get_l2src(uint8_t *l2src, size_t l2src_size, kernel_pid_t iface)
-{
-    bool try_long = false;
-    int res;
-    uint16_t l2src_len;
-    /* maximum address length that fits into a minimum length (8) S/TL2A option */
-    const uint16_t max_short_len = 6;
-
-    /* try getting source address */
-    if ((ng_netapi_get(iface, NETOPT_SRC_LEN, 0, &l2src_len,
-                       sizeof(l2src_len)) >= 0) &&
-        (l2src_len > max_short_len)) {
-        try_long = true;
-    }
-
-    if (try_long && ((res = ng_netapi_get(iface, NETOPT_ADDRESS_LONG, 0,
-                                          l2src, l2src_size)) > max_short_len)) {
-        l2src_len = (uint16_t)res;
-    }
-    else if ((res = ng_netapi_get(iface, NETOPT_ADDRESS, 0, l2src,
-                                  l2src_size)) >= 0) {
-        l2src_len = (uint16_t)res;
-    }
-    else {
-        DEBUG("ndp: no link-layer address found.\n");
-        l2src_len = 0;
-    }
-
-    return l2src_len;
-}
-
-static void _send_nbr_sol(kernel_pid_t iface, ng_ipv6_addr_t *tgt,
-                          ng_ipv6_addr_t *dst)
-{
-    ng_pktsnip_t *hdr, *pkt = NULL;
-    ng_ipv6_addr_t *src = NULL;
-    size_t src_len = 0;
-
-    DEBUG("ndp: send neighbor solicitation (iface: %" PRIkernel_pid ", tgt: %s, ",
-          iface, ng_ipv6_addr_to_str(addr_str, tgt, sizeof(addr_str)));
-    DEBUG("dst: %s)\n", ng_ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)));
-
-    /* check if there is a fitting source address to target */
-    if ((src = ng_ipv6_netif_find_best_src_addr(iface, tgt)) != NULL) {
-        uint8_t l2src[8];
-        uint16_t l2src_len;
-        src_len = sizeof(ng_ipv6_addr_t);
-        l2src_len = _get_l2src(l2src, sizeof(l2src), iface);
-
-        if (l2src_len > 0) {
-            /* add source address link-layer address option */
-            pkt = ng_ndp_opt_sl2a_build(l2src, l2src_len, NULL);
-
-            if (pkt == NULL) {
-                DEBUG("ndp: error allocating Source Link-layer address option.\n");
-                ng_pktbuf_release(pkt);
-                return;
-            }
-        }
-    }
-
-    hdr = ng_ndp_nbr_sol_build(tgt, pkt);
-
-    if (hdr == NULL) {
-        DEBUG("ndp: error allocating Neighbor solicitation.\n");
-        ng_pktbuf_release(pkt);
-        return;
-    }
-
-    pkt = hdr;
-    hdr = ng_ipv6_hdr_build(pkt, (uint8_t *)src, src_len, (uint8_t *)dst,
-                            sizeof(ng_ipv6_addr_t));
-
-    if (hdr == NULL) {
-        DEBUG("ndp: error allocating IPv6 header.\n");
-        ng_pktbuf_release(pkt);
-        return;
-    }
-
-    ((ng_ipv6_hdr_t *)hdr->data)->hl = 255;
-
-    pkt = hdr;
-    /* add netif header for send interface specification */
-    hdr = ng_netif_hdr_build(NULL, 0, NULL, 0);
-
-    if (hdr == NULL) {
-        DEBUG("ndp: error allocating netif header.\n");
-        return;
-    }
-
-    ((ng_netif_hdr_t *)hdr->data)->if_pid = iface;
-
-    LL_PREPEND(pkt, hdr);
-
-    ng_netapi_send(ng_ipv6_pid, pkt);
-}
-
-static void _send_nbr_adv(kernel_pid_t iface, ng_ipv6_addr_t *tgt,
-                          ng_ipv6_addr_t *dst, bool supply_tl2a)
-{
-    ng_pktsnip_t *hdr, *pkt = NULL;
-    uint8_t adv_flags = 0;
-
-    DEBUG("ndp: send neighbor advertisement (iface: %" PRIkernel_pid ", tgt: %s, ",
-          iface, ng_ipv6_addr_to_str(addr_str, tgt, sizeof(addr_str)));
-    DEBUG("dst: %s, supply_tl2a: %d)\n",
-          ng_ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)), supply_tl2a);
-
-    if (ng_ipv6_netif_get(iface)->flags & NG_IPV6_NETIF_FLAGS_ROUTER) {
-        adv_flags |= NG_NDP_NBR_ADV_FLAGS_R;
-    }
-
-    if (ng_ipv6_addr_is_unspecified(dst)) {
-        ng_ipv6_addr_set_all_nodes_multicast(dst,
-                                             NG_IPV6_ADDR_MCAST_SCP_LINK_LOCAL);
-    }
-    else {
-        adv_flags |= NG_NDP_NBR_ADV_FLAGS_S;
-    }
-
-    if (supply_tl2a) {
-        uint8_t l2src[8];
-        uint16_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);
-
-        if (l2src_len > 0) {
-            /* add target address link-layer address option */
-            pkt = ng_ndp_opt_tl2a_build(l2src, l2src_len, NULL);
-
-            if (pkt == NULL) {
-                DEBUG("ndp: error allocating Target Link-layer address option.\n");
-                ng_pktbuf_release(pkt);
-                return;
-            }
-        }
-    }
-
-    /* TODO: also check if the node provides proxy servies for tgt */
-    if ((pkt != NULL) && !ng_ipv6_netif_addr_is_non_unicast(tgt)) {
-        /* TL2A is not supplied and tgt is not anycast */
-        adv_flags |= NG_NDP_NBR_ADV_FLAGS_O;
-    }
-
-    hdr = ng_ndp_nbr_adv_build(adv_flags, tgt, pkt);
-
-    if (hdr == NULL) {
-        DEBUG("ndp: error allocating Neighbor advertisement.\n");
-        ng_pktbuf_release(pkt);
-        return;
-    }
-
-    pkt = hdr;
-    hdr = ng_ipv6_hdr_build(pkt, NULL, 0, (uint8_t *)dst,
-                            sizeof(ng_ipv6_addr_t));
-
-    if (hdr == NULL) {
-        DEBUG("ndp: error allocating IPv6 header.\n");
-        ng_pktbuf_release(pkt);
-        return;
-    }
-
-    ((ng_ipv6_hdr_t *)hdr->data)->hl = 255;
-
-    pkt = hdr;
-    /* add netif header for send interface specification */
-    hdr = ng_netif_hdr_build(NULL, 0, NULL, 0);
-
-    if (hdr == NULL) {
-        DEBUG("ndp: error allocating netif header.\n");
-        return;
-    }
-
-    ((ng_netif_hdr_t *)hdr->data)->if_pid = iface;
-
-    LL_PREPEND(pkt, hdr);
-
-    if (ng_ipv6_netif_addr_is_non_unicast(tgt)) {
-        /* avoid collision for anycast addresses
-         * (see https://tools.ietf.org/html/rfc4861#section-7.2.7) */
-        timex_t delay = { _rand(0, NG_NDP_MAX_AC_TGT_DELAY), 0 };
-        ng_ipv6_nc_t *nc_entry = ng_ipv6_nc_get(iface, tgt);
-        DEBUG("ndp: delay neighbor advertisement for %" PRIu32 " sec.",
-              delay.seconds);
-
-        /* nc_entry must be set so no need to check it */
-        _send_delayed(&nc_entry->nbr_adv_timer, delay, pkt);
-    }
-    else {
-        ng_netapi_send(ng_ipv6_pid, pkt);
-    }
-}
-
 static inline ng_pktsnip_t *_opt_l2a_build(uint8_t type, const uint8_t *l2addr,
                                            uint8_t l2addr_len, ng_pktsnip_t *next)
 {
@@ -858,188 +463,6 @@ ng_pktsnip_t *ng_ndp_opt_tl2a_build(const uint8_t *l2addr, uint8_t l2addr_len,
     return _opt_l2a_build(NG_NDP_OPT_TL2A, l2addr, l2addr_len, next);
 }
 
-/* internal functions */
-/* packet queue node allocation */
-static ng_pktqueue_t *_alloc_pkt_node(ng_pktsnip_t *pkt)
-{
-    for (size_t i = 0; i < sizeof(_pkt_nodes) / sizeof(ng_pktqueue_t); i++) {
-        if ((_pkt_nodes[i].pkt == NULL) && (_pkt_nodes[i].next == NULL)) {
-            _pkt_nodes[i].pkt = pkt;
-
-            return &(_pkt_nodes[i]);
-        }
-    }
-
-    return NULL;
-}
-
-static bool _handle_sl2a_opt(kernel_pid_t iface, ng_pktsnip_t *pkt,
-                             ng_ipv6_hdr_t *ipv6, uint8_t icmpv6_type,
-                             ng_ndp_opt_t *sl2a_opt)
-{
-    ng_ipv6_nc_t *nc_entry = NULL;
-    uint8_t sl2a_len = 0;
-    uint8_t *sl2a = (uint8_t *)(sl2a_opt + 1);
-
-    if ((sl2a_opt->len == 0) || ng_ipv6_addr_is_unspecified(&ipv6->src)) {
-        DEBUG("ndp: invalid source link-layer address option received\n");
-        return false;
-    }
-
-    while (pkt) {
-        if (pkt->type == NG_NETTYPE_NETIF) {
-            ng_netif_hdr_t *hdr = pkt->data;
-            sl2a_len = hdr->src_l2addr_len;
-            break;
-        }
-        pkt = pkt->next;
-    }
-
-    if (sl2a_len == 0) {  /* in case there was no source address in l2 */
-        sl2a_len = (sl2a_opt->len / 8) - sizeof(ng_ndp_opt_t);
-
-        /* ignore all zeroes at the end for length */
-        for (; sl2a[sl2a_len - 1] == 0x00; sl2a_len--);
-    }
-
-    DEBUG("ndp: received SL2A (link-layer address: %s)\n",
-          ng_netif_addr_to_str(addr_str, sizeof(addr_str), sl2a, sl2a_len));
-
-    switch (icmpv6_type) {
-        case NG_ICMPV6_NBR_SOL:
-            nc_entry = ng_ipv6_nc_get(iface, &ipv6->src);
-
-            if (nc_entry != NULL) {
-                if ((sl2a_len != nc_entry->l2_addr_len) ||
-                    (memcmp(sl2a, nc_entry->l2_addr, sl2a_len) != 0)) {
-                    /* if entry exists but l2 address differs: set */
-                    nc_entry->l2_addr_len = sl2a_len;
-                    memcpy(nc_entry->l2_addr, sl2a, sl2a_len);
-
-                    _set_state(nc_entry, NG_IPV6_NC_STATE_STALE);
-                }
-            }
-            else {
-                ng_ipv6_nc_add(iface, &ipv6->src, sl2a, sl2a_len,
-                               NG_IPV6_NC_STATE_STALE);
-            }
-
-            return true;
-
-        default:    /* wrong encapsulating message: silently discard */
-            DEBUG("ndp: silently discard sl2a_opt for ICMPv6 message type %"
-                  PRIu8 "\n", icmpv6_type);
-            return true;
-    }
-}
-
-static int _handle_tl2a_opt(ng_pktsnip_t *pkt, ng_ipv6_hdr_t *ipv6,
-                            uint8_t icmpv6_type, ng_ndp_opt_t *tl2a_opt,
-                            uint8_t *l2addr)
-{
-    uint8_t tl2a_len = 0;
-    uint8_t *tl2a = (uint8_t *)(tl2a_opt + 1);
-
-    if ((tl2a_opt->len == 0) || ng_ipv6_addr_is_unspecified(&ipv6->src)) {
-        DEBUG("ndp: invalid target link-layer address option received\n");
-        return -EINVAL;
-    }
-
-    switch (icmpv6_type) {
-        case NG_ICMPV6_NBR_ADV:
-            while (pkt) {
-                if (pkt->type == NG_NETTYPE_NETIF) {
-                    ng_netif_hdr_t *hdr = pkt->data;
-                    tl2a_len = hdr->src_l2addr_len;
-                    break;
-                }
-                pkt = pkt->next;
-            }
-
-            if (tl2a_len == 0) {  /* in case there was no source address in l2 */
-                tl2a_len = (tl2a_opt->len / 8) - sizeof(ng_ndp_opt_t);
-
-                /* ignore all zeroes at the end for length */
-                for (; tl2a[tl2a_len - 1] == 0x00; tl2a_len--);
-            }
-
-            DEBUG("ndp: received TL2A (link-layer address: %s)\n",
-                  ng_netif_addr_to_str(addr_str, sizeof(addr_str), tl2a, tl2a_len));
-
-            memcpy(l2addr, tl2a, tl2a_len);
-
-            return (int)tl2a_len;
-
-        default:    /* wrong encapsulating message: silently discard */
-            DEBUG("ndp: silently discard tl2a_opt for ICMPv6 message type %"
-                  PRIu8 "\n", icmpv6_type);
-            return 0;
-    }
-}
-
-static void _set_state(ng_ipv6_nc_t *nc_entry, uint8_t state)
-{
-    ng_ipv6_netif_t *ipv6_iface;
-    timex_t t = { NG_NDP_FIRST_PROBE_DELAY, 0 };
-
-    nc_entry->flags &= ~NG_IPV6_NC_STATE_MASK;
-    nc_entry->flags |= state;
-
-    DEBUG("ndp: set %s state to ",
-          ng_ipv6_addr_to_str(addr_str, &nc_entry->ipv6_addr, sizeof(addr_str)));
-
-    switch (state) {
-        case NG_IPV6_NC_STATE_REACHABLE:
-            ipv6_iface = ng_ipv6_netif_get(nc_entry->iface);
-            DEBUG("REACHABLE (reachable time = %" PRIu32 ".%06" PRIu32 ")\n",
-                  ipv6_iface->reach_time.seconds,
-                  ipv6_iface->reach_time.microseconds);
-            t = ipv6_iface->reach_time;
-            /* we intentionally fall through here to set the desired timeout t */
-        case NG_IPV6_NC_STATE_DELAY:
-#if ENABLE_DEBUG
-            if (state == NG_IPV6_NC_STATE_DELAY) {
-                DEBUG("DELAY (probe with unicast NS in %u seconds)\n",
-                      NG_NDP_FIRST_PROBE_DELAY);
-            }
-#endif
-            vtimer_remove(&nc_entry->nbr_sol_timer);
-            vtimer_set_msg(&nc_entry->nbr_sol_timer, t, ng_ipv6_pid,
-                           NG_NDP_MSG_NC_STATE_TIMEOUT, nc_entry);
-            break;
-
-        case NG_IPV6_NC_STATE_PROBE:
-            ipv6_iface = ng_ipv6_netif_get(nc_entry->iface);
-
-            nc_entry->probes_remaining = NG_NDP_MAX_UC_NBR_SOL_NUMOF;
-            DEBUG("PROBE (probe with %" PRIu8 " unicast NS every %" PRIu32
-                  ".%06" PRIu32 " seconds)\n", nc_entry->probes_remaining,
-                  ipv6_iface->retrans_timer.seconds,
-                  ipv6_iface->retrans_timer.microseconds);
-
-            _send_nbr_sol(nc_entry->iface, &nc_entry->ipv6_addr,
-                          &nc_entry->ipv6_addr);
-
-            mutex_lock(&ipv6_iface->mutex);
-            vtimer_remove(&nc_entry->nbr_sol_timer);
-            vtimer_set_msg(&nc_entry->nbr_sol_timer,
-                           ipv6_iface->retrans_timer, ng_ipv6_pid,
-                           NG_NDP_MSG_NBR_SOL_RETRANS, nc_entry);
-            mutex_unlock(&ipv6_iface->mutex);
-            break;
-
-#ifdef ENABLE_DEBUG
-        case NG_IPV6_NC_STATE_STALE:
-            DEBUG("STALE (go into DELAY on next packet)\n");
-            break;
-#endif
-
-        default:
-            DEBUG("errorneous or unknown\n");
-            break;
-    }
-}
-
 /**
  * @}
  */
diff --git a/sys/net/network_layer/ng_ndp/node/Makefile b/sys/net/network_layer/ng_ndp/node/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..abdb17d251c8b6e7f36c9914eb6e47913cbef50d
--- /dev/null
+++ b/sys/net/network_layer/ng_ndp/node/Makefile
@@ -0,0 +1,3 @@
+MODULE = ng_ndp_node
+
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/network_layer/ng_ndp/node/ng_ndp_node.c b/sys/net/network_layer/ng_ndp/node/ng_ndp_node.c
new file mode 100644
index 0000000000000000000000000000000000000000..1de413b19a1a9b24ded496ce45fa6d61f53ad5ea
--- /dev/null
+++ b/sys/net/network_layer/ng_ndp/node/ng_ndp_node.c
@@ -0,0 +1,194 @@
+/*
+ * 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 <stdint.h>
+#include <stdlib.h>
+
+#include "kernel_types.h"
+#include "net/fib.h"
+#include "net/ng_ipv6.h"
+#include "net/ng_ndp.h"
+#include "net/ng_pkt.h"
+#include "net/ng_pktqueue.h"
+
+#include "net/ng_ndp/internal.h"
+
+#include "net/ng_ndp/node.h"
+
+#define ENABLE_DEBUG    (0)
+#include "debug.h"
+
+#if ENABLE_DEBUG
+static char addr_str[NG_IPV6_ADDR_MAX_STR_LEN];
+#endif
+
+static ng_pktqueue_t _pkt_nodes[NG_IPV6_NC_SIZE * 2];
+
+/**
+ * @brief   Allocates a node for the packet queue.
+ *
+ * @param[in] pkt   Packet to add to the packet queue.
+ *
+ * @return  A packet queue node.
+ */
+static ng_pktqueue_t *_alloc_pkt_node(ng_pktsnip_t *pkt)
+{
+    for (size_t i = 0; i < sizeof(_pkt_nodes) / sizeof(ng_pktqueue_t); i++) {
+        if ((_pkt_nodes[i].pkt == NULL) && (_pkt_nodes[i].next == NULL)) {
+            _pkt_nodes[i].pkt = pkt;
+
+            return &(_pkt_nodes[i]);
+        }
+    }
+
+    return NULL;
+}
+
+kernel_pid_t ng_ndp_node_next_hop_l2addr(uint8_t *l2addr, uint8_t *l2addr_len,
+                                         kernel_pid_t iface, ng_ipv6_addr_t *dst,
+                                         ng_pktsnip_t *pkt)
+{
+    ng_ipv6_addr_t *next_hop_ip = NULL, *prefix = NULL;
+
+#ifdef MODULE_NG_IPV6_EXT_RH
+    next_hop_ip = ng_ipv6_ext_rh_next_hop(hdr);
+#endif
+#ifdef MODULE_FIB
+    size_t next_hop_size;
+    uint32_t next_hop_flags = 0;
+    ng_ipv6_addr_t next_hop_actual; /* FIB copies address into this variable */
+
+    if ((next_hop_ip == NULL) &&
+        (fib_get_next_hop(&iface, next_hop_actual.u8, &next_hop_size,
+                          &next_hop_flags, (uint8_t *)dst,
+                          sizeof(ng_ipv6_addr_t), 0) >= 0) &&
+        (next_hop_size == sizeof(ng_ipv6_addr_t))) {
+        next_hop_ip = &next_hop_actual;
+    }
+
+#endif
+
+    if ((next_hop_ip == NULL)) {            /* no route to host */
+        if (iface == KERNEL_PID_UNDEF) {
+            /* ng_ipv6_netif_t doubles as prefix list */
+            iface = ng_ipv6_netif_find_by_prefix(&prefix, dst);
+        }
+        else {
+            /* ng_ipv6_netif_t doubles as prefix list */
+            prefix = ng_ipv6_netif_match_prefix(iface, dst);
+        }
+
+        if ((prefix != NULL) &&             /* prefix is on-link */
+            (ng_ipv6_netif_addr_get(prefix)->flags &
+             NG_IPV6_NETIF_ADDR_FLAGS_NDP_ON_LINK)) {
+            next_hop_ip = dst;
+#ifdef MODULE_FIB
+            /* We don't care if FIB is full, this is just for efficiency
+             * for later sends */
+            fib_add_entry(iface, (uint8_t *)dst, sizeof(ng_ipv6_addr_t), 0,
+                          (uint8_t *)next_hop_ip, sizeof(ng_ipv6_addr_t), 0,
+                          FIB_LIFETIME_NO_EXPIRE);
+#endif
+        }
+    }
+
+    if (next_hop_ip == NULL) {
+        next_hop_ip = ng_ndp_internal_default_router();
+#ifdef MODULE_FIB
+        /* We don't care if FIB is full, this is just for efficiency for later
+         * sends */
+        fib_add_entry(iface, (uint8_t *)dst, sizeof(ng_ipv6_addr_t), 0,
+                      (uint8_t *)next_hop_ip, sizeof(ng_ipv6_addr_t), 0,
+                      FIB_LIFETIME_NO_EXPIRE);
+#endif
+    }
+
+    if (next_hop_ip != NULL) {
+        ng_ipv6_nc_t *nc_entry = ng_ipv6_nc_get(iface, next_hop_ip);
+
+        if ((nc_entry != NULL) && ng_ipv6_nc_is_reachable(nc_entry)) {
+            DEBUG("ndp node: found reachable neighbor (%s => ",
+                  ng_ipv6_addr_to_str(addr_str, &nc_entry->ipv6_addr, sizeof(addr_str)));
+            DEBUG("%s)\n",
+                  ng_netif_addr_to_str(addr_str, sizeof(addr_str),
+                                       nc_entry->l2_addr, nc_entry->l2_addr_len));
+
+            if (ng_ipv6_nc_get_state(nc_entry) == NG_IPV6_NC_STATE_STALE) {
+                ng_ndp_internal_set_state(nc_entry, NG_IPV6_NC_STATE_DELAY);
+            }
+
+            memcpy(l2addr, nc_entry->l2_addr, nc_entry->l2_addr_len);
+            *l2addr_len = nc_entry->l2_addr_len;
+            /* TODO: unreachability check */
+            return nc_entry->iface;
+        }
+        else if (nc_entry == NULL) {
+            ng_pktqueue_t *pkt_node;
+            ng_ipv6_addr_t dst_sol;
+
+            nc_entry = ng_ipv6_nc_add(iface, next_hop_ip, NULL, 0,
+                                      NG_IPV6_NC_STATE_INCOMPLETE << NG_IPV6_NC_STATE_POS);
+
+            if (nc_entry == NULL) {
+                DEBUG("ndp node: could not create neighbor cache entry\n");
+                return KERNEL_PID_UNDEF;
+            }
+
+            pkt_node = _alloc_pkt_node(pkt);
+
+            if (pkt_node == NULL) {
+                DEBUG("ndp node: could not add packet to packet queue\n");
+            }
+            else {
+                /* prevent packet from being released by IPv6 */
+                ng_pktbuf_hold(pkt_node->pkt, 1);
+                ng_pktqueue_add(&nc_entry->pkts, pkt_node);
+            }
+
+            /* address resolution */
+            ng_ipv6_addr_set_solicited_nodes(&dst_sol, next_hop_ip);
+
+            if (iface == KERNEL_PID_UNDEF) {
+                timex_t t = { 0, NG_NDP_RETRANS_TIMER };
+                kernel_pid_t ifs[NG_NETIF_NUMOF];
+                size_t ifnum = ng_netif_get(ifs);
+
+                for (size_t i = 0; i < ifnum; i++) {
+                    ng_ndp_internal_send_nbr_sol(ifs[i], next_hop_ip, &dst_sol);
+                }
+
+                vtimer_remove(&nc_entry->nbr_sol_timer);
+                vtimer_set_msg(&nc_entry->nbr_sol_timer, t, ng_ipv6_pid,
+                               NG_NDP_MSG_NBR_SOL_RETRANS, nc_entry);
+            }
+            else {
+                ng_ipv6_netif_t *ipv6_iface = ng_ipv6_netif_get(iface);
+
+                ng_ndp_internal_send_nbr_sol(iface, next_hop_ip, &dst_sol);
+
+                mutex_lock(&ipv6_iface->mutex);
+                vtimer_remove(&nc_entry->nbr_sol_timer);
+                vtimer_set_msg(&nc_entry->nbr_sol_timer,
+                               ipv6_iface->retrans_timer, ng_ipv6_pid,
+                               NG_NDP_MSG_NBR_SOL_RETRANS, nc_entry);
+                mutex_unlock(&ipv6_iface->mutex);
+            }
+        }
+    }
+
+    return KERNEL_PID_UNDEF;
+}
+
+
+/** @} */