1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

gnrc_ipv6_ext_frag: Initial import of IPv6 reassembly

This commit is contained in:
Martine Lenders 2019-05-23 16:29:19 +02:00
parent 409e04267f
commit f4b8ba32f3
7 changed files with 612 additions and 0 deletions

View File

@ -264,6 +264,11 @@ ifneq (,$(filter gnrc_rpl_srh,$(USEMODULE)))
USEMODULE += gnrc_ipv6_ext_rh
endif
ifneq (,$(filter gnrc_ipv6_ext_frag,$(USEMODULE)))
USEMODULE += gnrc_ipv6_ext
USEMODULE += xtimer
endif
ifneq (,$(filter gnrc_ipv6_ext_rh,$(USEMODULE)))
USEMODULE += gnrc_ipv6_ext
endif

View File

@ -29,6 +29,7 @@
#include "net/gnrc/pkt.h"
#include "net/ipv6/ext.h"
#include "timex.h"
#ifdef MODULE_GNRC_IPV6_EXT_RH
#include "net/gnrc/ipv6/ext/rh.h"
@ -38,6 +39,45 @@
extern "C" {
#endif
/**
* @defgroup net_gnrc_ipv6_ext_conf IPv6 extension header compile configurations
* @ingroup net_gnrc_ipv6_ext
* @ingroup config
* @{
*/
/**
* @brief IPv6 fragmentation reassembly buffer size
*
* This limits the total amount of datagrams that can be reassembled at the same time.
*
* @note Only applicable with [gnrc_ipv6_ext_frag](@ref net_gnrc_ipv6_ext_frag) module
*/
#ifndef GNRC_IPV6_EXT_FRAG_RBUF_SIZE
#define GNRC_IPV6_EXT_FRAG_RBUF_SIZE (1U)
#endif
/**
* @brief The number of total allocatable @ref gnrc_ipv6_ext_frag_limits_t objects
*
* This is the maximum number of receivable fragments, shared between all
* fragmented datagrams
*
* @note Only applicable with [gnrc_ipv6_ext_frag](@ref net_gnrc_ipv6_ext_frag) module
*/
#ifndef GNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE
#define GNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE (GNRC_IPV6_EXT_FRAG_RBUF_SIZE * 2U)
#endif
/**
* @brief Timeout for IPv6 fragmentation reassembly buffer entries in microseconds
*
* @note Only applicable with [gnrc_ipv6_ext_frag](@ref net_gnrc_ipv6_ext_frag) module
*/
#ifndef GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US
#define GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US (10U * US_PER_SEC)
#endif
/** @} **/
/**
* @brief Builds an extension header for sending.
*

View File

@ -0,0 +1,147 @@
/*
* Copyright (C) 2019 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup net_gnrc_ipv6_ext_frag Support for IPv6 fragmentation extension
* @ingroup net_gnrc_ipv6_ext
* @brief GNRC implementation of IPv6 fragmentation extension
* @{
*
* @file
* @brief GNRC fragmentation extension definitions
*
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#ifndef NET_GNRC_IPV6_EXT_FRAG_H
#define NET_GNRC_IPV6_EXT_FRAG_H
#include <stdint.h>
#include "clist.h"
#include "net/gnrc/pkt.h"
#include "net/gnrc/pktbuf.h"
#include "net/ipv6/hdr.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Message type to time reassembly buffer garbage collection
*/
#define GNRC_IPV6_EXT_FRAG_RBUF_GC (0xfe00U)
/**
* @brief Data type to describe limits of a single fragment in the reassembly
* buffer
*/
typedef struct gnrc_ipv6_ext_frag_limits {
struct gnrc_ipv6_ext_frag_limits *next; /**< limits of next fragment */
uint16_t start; /**< the start (= offset) of the fragment */
uint16_t end; /**< the exclusive end (= offset + length) of the
* fragment */
} gnrc_ipv6_ext_frag_limits_t;
/**
* @brief A reassembly buffer entry
*/
typedef struct {
gnrc_pktsnip_t *pkt; /**< the (partly) reassembled packet */
ipv6_hdr_t *ipv6; /**< the IPv6 header of gnrc_ipv6_ext_frag_rbuf_t::pkt */
/**
* @brief The limits of the fragments in the reassembled packet
*
* @note Members of this list can be cast to gnrc_ipv6_ext_frag_limits_t.
*/
clist_node_t limits;
uint32_t id; /**< the identification from the fragment headers */
uint32_t arrival; /**< arrival time of last received fragment */
uint16_t pkt_len; /**< length of gnrc_ipv6_ext_frag_rbuf_t::pkt */
uint8_t last; /**< received last fragment */
} gnrc_ipv6_ext_frag_rbuf_t;
/**
* @brief Initializes IPv6 fragmentation and reassembly
* @internal
*/
void gnrc_ipv6_ext_frag_init(void);
/**
* @brief Reassemble fragmented IPv6 packet
*
* @param[in] pkt A fragment of the IPv6 packet to be reassembled containing
* the fragment header in the first snip.
*
* @return The reassembled packet when @p pkt completed the reassembly
* @return NULL, when there are still fragments missing or an error occured
* during reassembly
*/
gnrc_pktsnip_t *gnrc_ipv6_ext_frag_reass(gnrc_pktsnip_t *pkt);
/**
* @name Reassembly buffer operations
* @{
*/
/**
* @brief Get a reassembly buffer by the identifying parameters
*
* @internal
* @see [RFC 8200, section 4.5](https://tools.ietf.org/html/rfc8200#section-4.5)
*
* @param[in] hdr IPv6 header to get source and destination address from.
* @param[in] id The identification from the fragment header.
*
* @return A reassembly buffer matching @p id ipv6_hdr_t::src and ipv6_hdr::dst
* of @p hdr or first free reassembly buffer. Will never be NULL, as
* in the case of the reassembly buffer being full, the entry with the
* lowest gnrc_ipv6_ext_frag_rbuf_t::arrival (serial-number-like) is
* removed.
*/
gnrc_ipv6_ext_frag_rbuf_t *gnrc_ipv6_ext_frag_rbuf_get(ipv6_hdr_t *ipv6,
uint32_t id);
/**
* @brief Frees a reassembly buffer entry (but does not release its
* gnrc_ipv6_ext_frag_rbuf_t::pkt)
*
* @param[in] rbuf A reassembly buffer entry.
*/
void gnrc_ipv6_ext_frag_rbuf_free(gnrc_ipv6_ext_frag_rbuf_t *rbuf);
/**
* @brief Delete a reassembly buffer entry (and release its
* gnrc_ipv6_ext_frag_rbuf_t::pkt)
*
* @note May be used by the IPv6 thread to remove a timed out reassembly
* buffer entry.
*
* @param[in] rbuf A reassembly buffer entry.
*/
static inline void gnrc_ipv6_ext_frag_rbuf_del(gnrc_ipv6_ext_frag_rbuf_t *rbuf)
{
gnrc_pktbuf_release(rbuf->pkt);
rbuf->pkt = NULL;
gnrc_ipv6_ext_frag_rbuf_free(rbuf);
}
/**
* @brief Garbage-collect reassembly buffer
*
* This calls @ref gnrc_ipv6_ext_frag_rbuf_del() for all reassembly buffer
* entries for which * gnrc_ipv6_ext_frag_rbuf_t::arrival is
* @ref GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US in the past.
*/
void gnrc_ipv6_ext_frag_rbuf_gc(void);
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* NET_GNRC_IPV6_EXT_FRAG_H */
/** @} */

View File

@ -13,6 +13,9 @@ endif
ifneq (,$(filter gnrc_ipv6_ext,$(USEMODULE)))
DIRS += network_layer/ipv6/ext
endif
ifneq (,$(filter gnrc_ipv6_ext_frag,$(USEMODULE)))
DIRS += network_layer/ipv6/ext/frag
endif
ifneq (,$(filter gnrc_ipv6_ext_rh,$(USEMODULE)))
DIRS += network_layer/ipv6/ext/rh
endif

View File

@ -0,0 +1,3 @@
MODULE := gnrc_ipv6_ext_frag
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,401 @@
/*
* Copyright (C) 2019 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include <assert.h>
#include "byteorder.h"
#include "net/ipv6/ext/frag.h"
#include "net/ipv6/addr.h"
#include "net/gnrc/ipv6/ext.h"
#include "net/gnrc/pktbuf.h"
#include "sched.h"
#include "xtimer.h"
#include "net/gnrc/ipv6/ext/frag.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
static gnrc_ipv6_ext_frag_rbuf_t _rbuf[GNRC_IPV6_EXT_FRAG_RBUF_SIZE];
static gnrc_ipv6_ext_frag_limits_t _limits_pool[GNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE];
static clist_node_t _free_limits;
static xtimer_t _gc_xtimer;
static msg_t _gc_msg = { .type = GNRC_IPV6_EXT_FRAG_RBUF_GC };
typedef enum {
FRAG_LIMITS_NEW = 0, /**< limits are not present and do not overlap */
FRAG_LIMITS_DUPLICATE, /**< fragment limits are already present */
FRAG_LIMITS_OVERLAP, /**< limits overlap */
FRAG_LIMITS_FULL, /**< no free gnrc_ipv6_ext_frag_limits_t object */
} _limits_res_t;
void gnrc_ipv6_ext_frag_init(void)
{
#ifdef TEST_SUITES
memset(_rbuf, 0, sizeof(_rbuf));
#endif
for (unsigned i = 0; i < GNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE; i++) {
clist_rpush(&_free_limits, (clist_node_t *)&_limits_pool[i]);
}
}
/*
* ===============
* IPv6 reassembly
* ===============
*/
/**
* @brief Initializes a reassembly buffer entry
*
* @param[in] rbuf A reassembly buffer entry.
* @param[in] ipv6 The IPv6 header for the reassembly buffer entry.
* @param[in] id The identification from the fragment header.
*/
static inline void _init_rbuf(gnrc_ipv6_ext_frag_rbuf_t *rbuf, ipv6_hdr_t *ipv6,
uint32_t id);
/**
* @brief Checks if given fragment limits overlap with fragment limits already
* in a given reassembly buffer entry
*
* If no overlap exists the new limits are added to @p rbuf.
*
* @param[in, out] rbuf A reassembly buffer entry.
* @param[in] offset A fragment offset.
* @param[in] pkt_len The length of the packet.
*
* @return see _limits_res_t.
*/
static _limits_res_t _overlaps(gnrc_ipv6_ext_frag_rbuf_t *rbuf,
unsigned offset, unsigned pkt_len);
/**
* @brief Sets the next header field of a header.
*
* @pre `hdr_snip->type` $\in$ {GNRC_NETTYPE_IPV6, GNRC_NETTYPE_IPV6_EXT}
*
* @param[in] hdr_snip A header
* @param[in] nh A protocol number
*/
static inline void _set_nh(gnrc_pktsnip_t *hdr_snip, uint8_t nh);
/**
* @brief Checks if a fragmented packet is completely reassembled.
*
* @param[in] rbuf A reassembly buffer entry.
*
* @return The reassembled packet on if it is completed.
* @return NULL, if the packet is not completely reassembled yet
*/
static gnrc_pktsnip_t *_completed(gnrc_ipv6_ext_frag_rbuf_t *rbuf);
gnrc_pktsnip_t *gnrc_ipv6_ext_frag_reass(gnrc_pktsnip_t *pkt)
{
gnrc_ipv6_ext_frag_rbuf_t *rbuf;
gnrc_pktsnip_t *fh_snip, *ipv6_snip;
ipv6_hdr_t *ipv6;
ipv6_ext_frag_t *fh;
unsigned offset;
uint8_t nh;
fh_snip = gnrc_pktbuf_mark(pkt, sizeof(ipv6_ext_frag_t),
GNRC_NETTYPE_IPV6_EXT);
if (fh_snip == NULL) {
DEBUG("ipv6_ext_frag: unable to mark fragmentation header\n");
goto error_release;
}
fh = fh_snip->data;
/* search IPv6 header */
ipv6_snip = gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_IPV6);
assert(ipv6_snip != NULL);
ipv6 = ipv6_snip->data;
rbuf = gnrc_ipv6_ext_frag_rbuf_get(ipv6, byteorder_ntohl(fh->id));
if (rbuf == NULL) {
DEBUG("ipv6_ext_frag: reassembly buffer full\n");
goto error_release;
}
rbuf->arrival = xtimer_now_usec();
xtimer_set_msg(&_gc_xtimer, GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US, &_gc_msg,
sched_active_pid);
nh = fh->nh;
offset = ipv6_ext_frag_get_offset(fh);
switch (_overlaps(rbuf, offset, pkt->size)) {
case FRAG_LIMITS_NEW:
break;
case FRAG_LIMITS_DUPLICATE:
gnrc_pktbuf_release(pkt);
return NULL;
case FRAG_LIMITS_OVERLAP:
DEBUG("ipv6_ext_frag: fragment overlaps with existing fragments\n");
/* intentionally falls through */
case FRAG_LIMITS_FULL:
default:
DEBUG("ipv6_ext_frag: can't store fragment limits\n");
goto error_exit;
}
if (offset > 0) {
size_t size_until = offset + pkt->size;
/* use IPv6 header in reassembly buffer from here on */
ipv6 = rbuf->ipv6;
/* subsequent fragment */
if (!ipv6_ext_frag_more(fh)) {
/* last fragment; add to rbuf->pkt_len */
rbuf->last++;
rbuf->pkt_len += size_until;
}
/* not divisible by 8 */
else if ((pkt->size & 0x7)) {
DEBUG("ipv6_ext_frag: fragment length not divisible by 8");
goto error_exit;
}
if (rbuf->pkt == NULL) {
rbuf->pkt = gnrc_pktbuf_add(fh_snip->next, NULL, size_until,
GNRC_NETTYPE_UNDEF);
if (rbuf->pkt == NULL) {
DEBUG("ipv6_ext_frag: unable to create space for reassembled "
"packet\n");
goto error_exit;
}
}
else if (rbuf->pkt->size < size_until) {
if (gnrc_pktbuf_realloc_data(rbuf->pkt, size_until) != 0) {
DEBUG("ipv6_ext_frag: unable to allocate space for reassembled "
"packet\n");
goto error_exit;
}
}
memcpy(((uint8_t *)rbuf->pkt->data) + offset, pkt->data, pkt->size);
/* we don't need the rest anymore */
gnrc_pktbuf_release(pkt);
return _completed(rbuf);
}
else if (!ipv6_ext_frag_more(fh)) {
/* first fragment but actually not fragmented */
_set_nh(fh_snip->next, nh);
gnrc_pktbuf_remove_snip(pkt, fh_snip);
gnrc_ipv6_ext_frag_rbuf_del(rbuf);
ipv6->len = byteorder_htons(byteorder_ntohs(ipv6->len) -
sizeof(ipv6_ext_frag_t));
return pkt;
}
else {
/* first fragment */
uint16_t ipv6_len = byteorder_ntohs(ipv6->len);
/* not divisible by 8*/
if ((pkt->size & 0x7)) {
DEBUG("ipv6_ext_frag: fragment length not divisible by 8");
goto error_exit;
}
_set_nh(fh_snip->next, nh);
gnrc_pktbuf_remove_snip(pkt, fh_snip);
/* TODO: RFC 8200 says "- 8"; determine if `sizeof(ipv6_ext_frag_t)` is
* really needed*/
rbuf->pkt_len += ipv6_len - pkt->size - sizeof(ipv6_ext_frag_t);
if (rbuf->pkt != NULL) {
/* first fragment but not first arriving */
memcpy(rbuf->pkt->data, pkt->data, pkt->size);
rbuf->pkt->next = pkt->next;
rbuf->pkt->type = pkt->type;
/* payload was copied to reassembly buffer so remove it */
gnrc_pktbuf_remove_snip(pkt, pkt);
rbuf->ipv6 = ipv6;
return _completed(rbuf);
}
else {
/* first fragment but first arriving */
rbuf->pkt = pkt;
}
}
return NULL;
error_exit:
gnrc_ipv6_ext_frag_rbuf_del(rbuf);
error_release:
gnrc_pktbuf_release(pkt);
return NULL;
}
gnrc_ipv6_ext_frag_rbuf_t *gnrc_ipv6_ext_frag_rbuf_get(ipv6_hdr_t *ipv6,
uint32_t id)
{
gnrc_ipv6_ext_frag_rbuf_t *res = NULL, *oldest = NULL;
for (unsigned i = 0; i < GNRC_IPV6_EXT_FRAG_RBUF_SIZE; i++) {
gnrc_ipv6_ext_frag_rbuf_t *tmp = &_rbuf[i];
if (tmp->ipv6 != NULL) {
if ((tmp->id == id) &&
ipv6_addr_equal(&tmp->ipv6->src, &ipv6->src) &&
ipv6_addr_equal(&tmp->ipv6->dst, &ipv6->dst)) {
return tmp;
}
}
else if (res == NULL) {
res = tmp;
_init_rbuf(res, ipv6, id);
}
if ((oldest == NULL) ||
/* xtimer_now_usec() overflows every ~1.2 hours */
((tmp->arrival - oldest->arrival) < (UINT32_MAX / 2))) {
oldest = tmp;
}
}
if (res == NULL) {
assert(oldest != NULL); /* reassembly buffer is full, so there needs
* to be an oldest entry */
DEBUG("ipv6_ext_frag: dropping oldest entry\n");
gnrc_ipv6_ext_frag_rbuf_del(oldest);
res = oldest;
_init_rbuf(res, ipv6, id);
}
return res;
}
void gnrc_ipv6_ext_frag_rbuf_free(gnrc_ipv6_ext_frag_rbuf_t *rbuf)
{
rbuf->ipv6 = NULL;
while (rbuf->limits.next != NULL) {
clist_node_t *tmp = clist_lpop(&rbuf->limits);
clist_rpush(&_free_limits, tmp);
}
}
void gnrc_ipv6_ext_frag_rbuf_gc(void)
{
uint32_t now = xtimer_now_usec();
for (unsigned i = 0; i < GNRC_IPV6_EXT_FRAG_RBUF_SIZE; i++) {
gnrc_ipv6_ext_frag_rbuf_t *rbuf = &_rbuf[i];
if ((now - rbuf->arrival) > GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US) {
gnrc_ipv6_ext_frag_rbuf_del(rbuf);
}
}
}
typedef struct {
uint16_t start;
uint16_t end;
} _check_limits_t;
static inline void _init_rbuf(gnrc_ipv6_ext_frag_rbuf_t *rbuf, ipv6_hdr_t *ipv6,
uint32_t id)
{
rbuf->ipv6 = ipv6;
rbuf->id = id;
rbuf->pkt_len = 0;
rbuf->last = 0;
}
static int _check_overlap(clist_node_t *node, void *arg)
{
_check_limits_t *limits = arg;
gnrc_ipv6_ext_frag_limits_t *cur = (gnrc_ipv6_ext_frag_limits_t *)node;
return ((cur->start < limits->end) && (limits->start < cur->end));
}
static int _limits_cmp(clist_node_t *a, clist_node_t *b)
{
gnrc_ipv6_ext_frag_limits_t *al = (gnrc_ipv6_ext_frag_limits_t *)a;
gnrc_ipv6_ext_frag_limits_t *bl = (gnrc_ipv6_ext_frag_limits_t *)b;
return (int)al->start - (int)bl->start;
}
static _limits_res_t _overlaps(gnrc_ipv6_ext_frag_rbuf_t *rbuf,
unsigned offset, unsigned pkt_len)
{
_check_limits_t limits = { .start = offset >> 3U,
.end = (offset + pkt_len) >> 3U };
gnrc_ipv6_ext_frag_limits_t *res;
if (limits.start == limits.end) {
/* might happen with last fragment */
limits.end++;
}
res = (gnrc_ipv6_ext_frag_limits_t *)clist_foreach(&rbuf->limits,
_check_overlap,
&limits);
if (res == NULL) {
res = (gnrc_ipv6_ext_frag_limits_t *)clist_lpop(&_free_limits);
if (res != NULL) {
res->start = limits.start;
res->end = limits.end;
clist_rpush(&rbuf->limits, (clist_node_t *)res);
clist_sort(&rbuf->limits, _limits_cmp);
return FRAG_LIMITS_NEW;
}
else {
return FRAG_LIMITS_FULL;
}
}
else if ((res->start == limits.start) && (res->end == limits.end)) {
return FRAG_LIMITS_DUPLICATE;
}
else {
return FRAG_LIMITS_NEW;
}
}
static inline void _set_nh(gnrc_pktsnip_t *hdr_snip, uint8_t nh)
{
switch (hdr_snip->type) {
case GNRC_NETTYPE_IPV6: {
ipv6_hdr_t *hdr = hdr_snip->data;
hdr->nh = nh;
break;
}
case GNRC_NETTYPE_IPV6_EXT: {
ipv6_ext_t *hdr = hdr_snip->data;
hdr->nh = nh;
break;
}
default:
/* should not happen */
assert(false);
break;
}
}
static gnrc_pktsnip_t *_completed(gnrc_ipv6_ext_frag_rbuf_t *rbuf)
{
assert(rbuf->limits.next != NULL); /* this function is only called when
* at least one fragment was already
* added */
/* clist: first element is second element ;-) (from next of head) */
gnrc_ipv6_ext_frag_limits_t *ptr =
(gnrc_ipv6_ext_frag_limits_t *)rbuf->limits.next->next;
if (rbuf->last && (ptr->start == 0)) {
gnrc_pktsnip_t *res = NULL;
/* last and first fragment were received, so check if everything
* in-between is there */
do {
gnrc_ipv6_ext_frag_limits_t *next = ptr->next;
if (ptr->end < next->start) {
return NULL;
}
ptr = next;
} while (((clist_node_t *)ptr) != rbuf->limits.next);
res = rbuf->pkt;
/* rewrite length */
rbuf->ipv6->len = byteorder_htons(rbuf->pkt_len);
rbuf->pkt = NULL;
gnrc_ipv6_ext_frag_rbuf_free(rbuf);
return res;
}
return NULL;
}
/** @} */

View File

@ -32,6 +32,10 @@
#include "net/gnrc/ipv6/whitelist.h"
#include "net/gnrc/ipv6/blacklist.h"
#ifdef MODULE_GNRC_IPV6_EXT_FRAG
#include "net/gnrc/ipv6/ext/frag.h"
#endif
#include "net/gnrc/ipv6.h"
#define ENABLE_DEBUG (0)
@ -171,6 +175,10 @@ static void *_event_loop(void *args)
(void)args;
msg_init_queue(msg_q, GNRC_IPV6_MSG_QUEUE_SIZE);
/* initialize fragmentation data-structures */
#ifdef MODULE_GNRC_IPV6_EXT_FRAG
gnrc_ipv6_ext_frag_init();
#endif /* MODULE_GNRC_IPV6_EXT_FRAG */
/* register interest in all IPv6 packets */
gnrc_netreg_register(GNRC_NETTYPE_IPV6, &me_reg);
@ -200,6 +208,11 @@ static void *_event_loop(void *args)
msg_reply(&msg, &reply);
break;
#ifdef MODULE_GNRC_IPV6_EXT_FRAG
case GNRC_IPV6_EXT_FRAG_RBUF_GC:
gnrc_ipv6_ext_frag_rbuf_gc();
break;
#endif /* MODULE_GNRC_IPV6_EXT_FRAG */
case GNRC_IPV6_NIB_SND_UC_NS:
case GNRC_IPV6_NIB_SND_MC_NS:
case GNRC_IPV6_NIB_SND_NA: