diff --git a/sys/include/net/gnrc/netif.h b/sys/include/net/gnrc/netif.h
index 236891c38cd179204b5336d450254b642bce758c..37eb8413e63bda44f06930c356b608b6b16797a1 100644
--- a/sys/include/net/gnrc/netif.h
+++ b/sys/include/net/gnrc/netif.h
@@ -46,6 +46,7 @@
 #endif
 #include "net/ndp.h"
 #include "net/netdev.h"
+#include "net/netopt.h"
 #include "rmutex.h"
 
 #ifdef __cplusplus
diff --git a/sys/include/net/gnrc/netif/internal.h b/sys/include/net/gnrc/netif/internal.h
index 22e31ed08c5c33be995000f5ec1ee478e056e59c..4a055343aa3262f0150d9238eb76ef992d9c5397 100644
--- a/sys/include/net/gnrc/netif/internal.h
+++ b/sys/include/net/gnrc/netif/internal.h
@@ -22,6 +22,7 @@
 #define NET_GNRC_NETIF_INTERNAL_H
 
 #include "net/gnrc/netif.h"
+#include "net/netopt.h"
 
 #ifdef MODULE_GNRC_IPV6_NIB
 #include "net/gnrc/ipv6/nib/conf.h"
@@ -403,7 +404,37 @@ static inline bool gnrc_netif_is_6lbr(const gnrc_netif_t *netif)
 #define gnrc_netif_is_6lbr(netif)               (false)
 #endif
 
+/**
+ * @name    Device type based function
+ *
+ * These functions' behavior is based around the gnrc_netif_t::device_type of
+ * an interface.
+ *
+ * @attention   Special care needs to be taken for those functions when porting
+ *              a new network device type or link-layer protocol: They might
+ *              need adaptions for your port
+ * @{
+ */
+/**
+ * @brief   Get the default link-layer address option for the given
+ *          gnrc_netif_t::device_type of a network interface
+ *
+ * @param[in] netif     The network interface to get the default link-layer
+ *                      address option for.
+ *
+ * @return  Either @ref NETOPT_ADDRESS or @ref NETOPT_ADDRESS_LONG.
+ */
+netopt_t gnrc_netif_get_l2addr_opt(const gnrc_netif_t *netif);
+
 #if defined(MODULE_GNRC_IPV6) || defined(DOXYGEN)
+/**
+ * @brief   Initialize IPv6 MTU and other packet length related members of
+ *          @ref gnrc_netif_t based on gnrc_netif_t::device_type
+ *
+ * @param[in,out] netif The network interface to initialize the MTU for.
+ */
+void gnrc_netif_ipv6_init_mtu(gnrc_netif_t *netif);
+
 /**
  * @brief   Converts a given hardware address to an IPv6 IID.
  *
@@ -484,11 +515,44 @@ static inline int gnrc_netif_ipv6_get_iid(gnrc_netif_t *netif, eui64_t *iid)
     (void)iid;
     return -ENOTSUP;
 }
+
+/**
+ * @brief   Derives the length of the link-layer address in an NDP link-layer
+ *          address option from that option's length field and the given device
+ *          type.
+ *
+ * @note    If an RFC exists that specifies how IPv6 operates over a link-layer,
+ *          this function usually implements the section "Unicast Address
+ *          Mapping".
+ *
+ * @see [RFC 4861, section 4.6.1](https://tools.ietf.org/html/rfc4861#section-4.6.1)
+ *
+ * @pre `netif->flags & GNRC_NETIF_FLAGS_HAS_L2ADDR`
+ *
+ * @attention   When `NDEBUG` is not defined, the node fails with an assertion
+ *              instead of returning `-ENOTSUP`
+ *
+ * @param[in] netif The network interface @p opt was received on within an NDP
+ *                  message.
+ * @param[in] opt   An NDP source/target link-layer address option.
+ *
+ * @return  Length of the link-layer address in @p opt on success
+ * @return  `-ENOTSUP`, when implementation does not know how to derive the
+ *          length of the link-layer address from @p opt's length field based
+ *          on gnrc_netif_t::device_type of @p netif.
+ * @return  `-EINVAL` if `opt->len` was an invalid value for the given
+ *          gnrc_netif_t::device_type of @p netif.
+ */
+int gnrc_netif_ndp_addr_len_from_l2ao(gnrc_netif_t *netif,
+                                      const ndp_opt_t *opt);
 #else   /* defined(MODULE_GNRC_IPV6) || defined(DOXYGEN) */
-#define gnrc_netif_ipv6_iid_from_addr(netif, addr, addr_len, iid) (-ENOTSUP)
-#define gnrc_netif_ipv6_iid_to_addr(netif, iid, addr)         (-ENOTSUP)
+#define gnrc_netif_ipv6_init_mtu(netif)                             (void)netif
+#define gnrc_netif_ipv6_iid_from_addr(netif, addr, addr_len, iid)   (-ENOTSUP)
+#define gnrc_netif_ipv6_iid_to_addr(netif, iid, addr)               (-ENOTSUP)
+#define gnrc_netif_ndp_addr_len_from_l2ao(netif, opt)               (-ENOTSUP)
 #define gnrc_netif_ipv6_get_iid(netif, iid)                         (-ENOTSUP)
 #endif  /* defined(MODULE_GNRC_IPV6) || defined(DOXYGEN) */
+/** @} */
 
 #ifdef __cplusplus
 }
diff --git a/sys/net/gnrc/netif/gnrc_netif.c b/sys/net/gnrc/netif/gnrc_netif.c
index cfab9795f77bdfb43f07d288b996fe4e669d0eb5..df0085d371ddc7abaae2ca9a14abdf8b39049096 100644
--- a/sys/net/gnrc/netif/gnrc_netif.c
+++ b/sys/net/gnrc/netif/gnrc_netif.c
@@ -1109,27 +1109,8 @@ static void _update_l2addr_from_dev(gnrc_netif_t *netif)
 {
     netdev_t *dev = netif->dev;
     int res;
-    netopt_t opt = NETOPT_ADDRESS;
+    netopt_t opt = gnrc_netif_get_l2addr_opt(netif);
 
-    switch (netif->device_type) {
-#if defined(MODULE_NETDEV_IEEE802154) || defined(MODULE_XBEE) \
-    || defined(MODULE_NORDIC_SOFTDEVICE_BLE)
-        case NETDEV_TYPE_BLE:
-        case NETDEV_TYPE_IEEE802154: {
-                uint16_t tmp;
-
-                res = dev->driver->get(dev, NETOPT_SRC_LEN, &tmp, sizeof(tmp));
-                assert(res == sizeof(tmp));
-                netif->l2addr_len = (uint8_t)tmp;
-                if (tmp == IEEE802154_LONG_ADDRESS_LEN) {
-                    opt = NETOPT_ADDRESS_LONG;
-                }
-            }
-            break;
-#endif
-        default:
-            break;
-    }
     res = dev->driver->get(dev, opt, netif->l2addr,
                            sizeof(netif->l2addr));
     if (res != -ENOTSUP) {
@@ -1155,60 +1136,7 @@ static void _init_from_device(gnrc_netif_t *netif)
     (void)res;
     assert(res == sizeof(tmp));
     netif->device_type = (uint8_t)tmp;
-    switch (netif->device_type) {
-#if defined(MODULE_NETDEV_IEEE802154) || defined(MODULE_NRFMIN) || \
-    defined(MODULE_XBEE) || defined(MODULE_ESP_NOW) || \
-    defined(MODULE_GNRC_SIXLOENC)
-        case NETDEV_TYPE_IEEE802154:
-        case NETDEV_TYPE_NRFMIN:
-#ifdef MODULE_GNRC_SIXLOWPAN_IPHC
-            netif->flags |= GNRC_NETIF_FLAGS_6LO_HC;
-#endif
-            /* intentionally falls through */
-        case NETDEV_TYPE_ESP_NOW:
-#ifdef MODULE_GNRC_IPV6
-            res = dev->driver->get(dev, NETOPT_MAX_PACKET_SIZE, &tmp, sizeof(tmp));
-            assert(res == sizeof(tmp));
-#ifdef MODULE_GNRC_SIXLOWPAN
-            netif->ipv6.mtu = IPV6_MIN_MTU;
-            netif->sixlo.max_frag_size = tmp;
-#else
-            netif->ipv6.mtu = tmp;
-#endif
-#endif
-            break;
-#endif  /* MODULE_NETDEV_IEEE802154 */
-#ifdef MODULE_NETDEV_ETH
-        case NETDEV_TYPE_ETHERNET:
-#ifdef MODULE_GNRC_IPV6
-            netif->ipv6.mtu = ETHERNET_DATA_LEN;
-#endif
-#if defined(MODULE_GNRC_SIXLOWPAN_IPHC) && defined(MODULE_GNRC_SIXLOENC)
-            netif->flags |= GNRC_NETIF_FLAGS_6LO_HC;
-#endif
-            break;
-#endif
-#ifdef MODULE_NORDIC_SOFTDEVICE_BLE
-        case NETDEV_TYPE_BLE:
-            netif->ipv6.mtu = IPV6_MIN_MTU;
-#ifdef MODULE_GNRC_SIXLOWPAN_IPHC
-            netif->flags |= GNRC_NETIF_FLAGS_6LO_HC;
-#endif
-            break;
-#endif
-        default:
-#ifdef MODULE_GNRC_IPV6
-            res = dev->driver->get(dev, NETOPT_MAX_PACKET_SIZE, &tmp, sizeof(tmp));
-            if (res < 0) {
-                /* assume maximum possible transition unit */
-                netif->ipv6.mtu = UINT16_MAX;
-            }
-            else {
-                netif->ipv6.mtu = tmp;
-            }
-#endif
-            break;
-    }
+    gnrc_netif_ipv6_init_mtu(netif);
     _update_l2addr_from_dev(netif);
 }
 
diff --git a/sys/net/gnrc/netif/gnrc_netif_device_type.c b/sys/net/gnrc/netif/gnrc_netif_device_type.c
index a44fdc3a3b1c54baf7a2adff9f2793e3b50b0396..031a0474dbebc1d114b78bca6b23c835ce493226 100644
--- a/sys/net/gnrc/netif/gnrc_netif_device_type.c
+++ b/sys/net/gnrc/netif/gnrc_netif_device_type.c
@@ -17,11 +17,111 @@
 #include <errno.h>
 
 #include "log.h"
+#ifdef MODULE_GNRC_IPV6
+#include "net/ipv6.h"
+#endif
 #include "net/gnrc/netif.h"
 #include "net/eui48.h"
+#include "net/ethernet.h"
 #include "net/ieee802154.h"
 
+netopt_t gnrc_netif_get_l2addr_opt(const gnrc_netif_t *netif)
+{
+    netopt_t res = NETOPT_ADDRESS;
+
+    switch (netif->device_type) {
+#if defined(MODULE_NETDEV_IEEE802154) || defined(MODULE_XBEE) || \
+    defined(MODULE_NORDIC_SOFTDEVICE_BLE)
+        case NETDEV_TYPE_IEEE802154:
+        case NETDEV_TYPE_BLE: {
+                netdev_t *dev = netif->dev;
+                int r;
+                uint16_t tmp;
+
+                r = dev->driver->get(dev, NETOPT_SRC_LEN, &tmp, sizeof(tmp));
+                assert(r == sizeof(tmp));
+                assert(r <= ((int)UINT8_MAX));
+                (void)r;
+                if (tmp == IEEE802154_LONG_ADDRESS_LEN) {
+                    res = NETOPT_ADDRESS_LONG;
+                }
+            }
+            break;
+#endif
+        default:
+            break;
+    }
+    return res;
+}
+
+#ifdef MODULE_GNRC_IPV6
+void gnrc_netif_ipv6_init_mtu(gnrc_netif_t *netif)
+{
+#ifdef MODULE_GNRC_IPV6
+    netdev_t *dev = netif->dev;
+    int res;
+    uint16_t tmp;
+
+    switch (netif->device_type) {
+#if defined(MODULE_NETDEV_IEEE802154) || defined(MODULE_NRFMIN) || \
+    defined(MODULE_XBEE) || defined(MODULE_ESP_NOW) || \
+    defined(MODULE_GNRC_SIXLOENC)
+        case NETDEV_TYPE_IEEE802154:
+        case NETDEV_TYPE_NRFMIN:
+#ifdef MODULE_GNRC_SIXLOWPAN_IPHC
+            netif->flags |= GNRC_NETIF_FLAGS_6LO_HC;
+#endif
+            /* intentionally falls through */
+        case NETDEV_TYPE_ESP_NOW:
+            res = dev->driver->get(dev, NETOPT_MAX_PACKET_SIZE,
+                                   &tmp, sizeof(tmp));
+            assert(res == sizeof(tmp));
+#ifdef MODULE_GNRC_SIXLOWPAN
+            netif->ipv6.mtu = IPV6_MIN_MTU;
+            netif->sixlo.max_frag_size = tmp;
+#else
+            netif->ipv6.mtu = tmp;
+#endif
+            break;
+#endif  /* defined(MODULE_NETDEV_IEEE802154) || defined(MODULE_NRFMIN) || \
+         * defined(MODULE_XBEE) || defined(MODULE_ESP_NOW) */
+#ifdef MODULE_NETDEV_ETH
+        case NETDEV_TYPE_ETHERNET:
 #ifdef MODULE_GNRC_IPV6
+            netif->ipv6.mtu = ETHERNET_DATA_LEN;
+#endif
+#if defined(MODULE_GNRC_SIXLOWPAN_IPHC) && defined(MODULE_GNRC_SIXLOENC)
+            netif->flags |= GNRC_NETIF_FLAGS_6LO_HC;
+#endif
+            break;
+#endif
+#ifdef MODULE_NORDIC_SOFTDEVICE_BLE
+        case NETDEV_TYPE_BLE:
+            netif->ipv6.mtu = IPV6_MIN_MTU;
+#ifdef MODULE_GNRC_SIXLOWPAN_IPHC
+            netif->flags |= GNRC_NETIF_FLAGS_6LO_HC;
+#endif
+            break;
+#endif
+        default:
+#ifdef DEVELHELP
+            LOG_DEBUG("gnrc_netif: getting MTU from device for interface %i\n",
+                      netif->pid);
+#endif
+            res = dev->driver->get(dev, NETOPT_MAX_PACKET_SIZE,
+                                   &tmp, sizeof(tmp));
+            if (res < 0) {
+                /* assume maximum possible transition unit */
+                netif->ipv6.mtu = UINT16_MAX;
+            }
+            else {
+                netif->ipv6.mtu = tmp;
+            }
+            break;
+    }
+#endif
+}
+
 #if defined(MODULE_CC110X) || defined(MODULE_NRFMIN)
 static void _create_iid_from_short(const uint8_t *addr, size_t addr_len,
                                    eui64_t *iid)
@@ -148,6 +248,56 @@ int gnrc_netif_ipv6_iid_to_addr(const gnrc_netif_t *netif, const eui64_t *iid,
     }
     return -ENOTSUP;
 }
+
+int gnrc_netif_ndp_addr_len_from_l2ao(gnrc_netif_t *netif,
+                                      const ndp_opt_t *opt)
+{
+    assert(netif->flags & GNRC_NETIF_FLAGS_HAS_L2ADDR);
+    switch (netif->device_type) {
+#ifdef MODULE_CC110X
+        case NETDEV_TYPE_CC110X:
+            (void)opt;
+            return sizeof(uint8_t);
+#endif  /* MODULE_CC110X */
+#if defined(MODULE_NETDEV_ETH) || defined(MODULE_ESP_NOW)
+        case NETDEV_TYPE_ETHERNET:
+        case NETDEV_TYPE_ESP_NOW:
+            /* see https://tools.ietf.org/html/rfc2464#section-6*/
+            if (opt->len == 1U) {
+                return ETHERNET_ADDR_LEN;
+            }
+            else {
+                return -EINVAL;
+            }
+#endif  /* defined(MODULE_NETDEV_ETH) || defined(MODULE_ESP_NOW) */
+#ifdef MODULE_NRFMIN
+        case NETDEV_TYPE_NRFMIN:
+            (void)opt;
+            return sizeof(uint16_t);
+#endif  /* MODULE_NRFMIN */
+#if defined(MODULE_NETDEV_IEEE802154) || defined(MODULE_XBEE)
+        case NETDEV_TYPE_IEEE802154:
+            /* see https://tools.ietf.org/html/rfc4944#section-8 */
+            switch (opt->len) {
+                case 1U:
+                    return IEEE802154_SHORT_ADDRESS_LEN;
+                case 2U:
+                    return IEEE802154_LONG_ADDRESS_LEN;
+                default:
+                    return -EINVAL;
+            }
+#endif  /* defined(MODULE_NETDEV_IEEE802154) || defined(MODULE_XBEE) */
+        default:
+            (void)opt;
+#ifdef DEVELHELP
+            LOG_ERROR("gnrc_netif: can't get address length from NDP link-layer "
+                      "address option on interface %u\n", netif->pid);
+#endif
+            assert(false);
+            break;
+    }
+    return -ENOTSUP;
+}
 #endif /* MODULE_GNRC_IPV6 */
 
 /** @} */
diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c
index c2d37650a44f658cac1dd5ba853cbd35490ac1e5..ca0e2f0ff5ff8b5f8e3c8acd8744dbe55571dcc5 100644
--- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c
+++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c
@@ -30,18 +30,6 @@
 
 static char addr_str[IPV6_ADDR_MAX_STR_LEN];
 
-/**
- * @brief   Determines supposed link-layer address from interface and option
- *          length
- *
- * @param[in] netif A network interface.
- * @param[in] opt   A SL2AO or TL2AO.
- *
- * @return  The length of the L2 address carried in @p opt.
- */
-static inline unsigned _get_l2addr_len(gnrc_netif_t *netif,
-                                       const ndp_opt_t *opt);
-
 void _snd_ns(const ipv6_addr_t *tgt, gnrc_netif_t *netif,
              const ipv6_addr_t *src, const ipv6_addr_t *dst)
 {
@@ -104,10 +92,10 @@ void _handle_sl2ao(gnrc_netif_t *netif, const ipv6_hdr_t *ipv6,
 {
     assert(netif != NULL);
     _nib_onl_entry_t *nce = _nib_onl_get(&ipv6->src, netif->pid);
-    unsigned l2addr_len;
+    int l2addr_len;
 
-    l2addr_len = _get_l2addr_len(netif, sl2ao);
-    if (l2addr_len == 0U) {
+    l2addr_len = gnrc_netif_ndp_addr_len_from_l2ao(netif, sl2ao);
+    if (l2addr_len < 0) {
         DEBUG("nib: Unexpected SL2AO length. Ignoring SL2AO\n");
         return;
     }
@@ -174,43 +162,6 @@ void _handle_sl2ao(gnrc_netif_t *netif, const ipv6_hdr_t *ipv6,
     }
 }
 
-static inline unsigned _get_l2addr_len(gnrc_netif_t *netif,
-                                       const ndp_opt_t *opt)
-{
-    switch (netif->device_type) {
-#ifdef MODULE_CC110X
-        case NETDEV_TYPE_CC110X:
-            (void)opt;
-            return sizeof(uint8_t);
-#endif  /* MODULE_CC110X */
-#if defined(MODULE_NETDEV_ETH) || defined(MODULE_ESP_NOW)
-        case NETDEV_TYPE_ETHERNET:
-        case NETDEV_TYPE_ESP_NOW:
-            (void)opt;
-            return ETHERNET_ADDR_LEN;
-#endif  /* defined(MODULE_NETDEV_ETH) || defined(MODULE_ESP_NOW) */
-#ifdef MODULE_NRFMIN
-        case NETDEV_TYPE_NRFMIN:
-            (void)opt;
-            return sizeof(uint16_t);
-#endif  /* MODULE_NRFMIN */
-#if defined(MODULE_NETDEV_IEEE802154) || defined(MODULE_XBEE)
-        case NETDEV_TYPE_IEEE802154:
-            switch (opt->len) {
-                case 1U:
-                    return IEEE802154_SHORT_ADDRESS_LEN;
-                case 2U:
-                    return IEEE802154_LONG_ADDRESS_LEN;
-                default:
-                    return 0U;
-            }
-#endif  /* defined(MODULE_NETDEV_IEEE802154) || defined(MODULE_XBEE) */
-        default:
-            (void)opt;
-            return 0U;
-    }
-}
-
 #if GNRC_IPV6_NIB_CONF_ARSM
 /**
  * @brief   Calculates exponential back-off for retransmission timer for
@@ -379,13 +330,13 @@ void _probe_nbr(_nib_onl_entry_t *nbr, bool reset)
 void _handle_adv_l2(gnrc_netif_t *netif, _nib_onl_entry_t *nce,
                     const icmpv6_hdr_t *icmpv6, const ndp_opt_t *tl2ao)
 {
-    unsigned l2addr_len = 0;
+    int l2addr_len = 0;
 
     assert(nce != NULL);
     assert(netif != NULL);
     if (tl2ao != NULL) {
-        l2addr_len = _get_l2addr_len(netif, tl2ao);
-        if (l2addr_len == 0U) {
+        l2addr_len = gnrc_netif_ndp_addr_len_from_l2ao(netif, tl2ao);
+        if (l2addr_len < 0) {
             DEBUG("nib: Unexpected TL2AO length. Ignoring TL2AO\n");
             return;
         }