Merge pull request #11623 from miri64/gnrc_ipv6_ext/feat/ipv6-frag
gnrc_ipv6_ext_frag: Initial import of IPv6 fragmentation
This commit is contained in:
commit
5631b698db
@ -45,6 +45,17 @@ extern "C" {
|
|||||||
* @ingroup config
|
* @ingroup config
|
||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* @brief IPv6 fragmentation send buffer size
|
||||||
|
*
|
||||||
|
* This limits the total amount of datagrams that can be fragmented at the same time.
|
||||||
|
*
|
||||||
|
* @note Only applicable with [gnrc_ipv6_ext_frag](@ref net_gnrc_ipv6_ext_frag) module
|
||||||
|
*/
|
||||||
|
#ifndef GNRC_IPV6_EXT_FRAG_SEND_SIZE
|
||||||
|
#define GNRC_IPV6_EXT_FRAG_SEND_SIZE (1U)
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief IPv6 fragmentation reassembly buffer size
|
* @brief IPv6 fragmentation reassembly buffer size
|
||||||
*
|
*
|
||||||
@ -76,6 +87,7 @@ extern "C" {
|
|||||||
#ifndef GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US
|
#ifndef GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US
|
||||||
#define GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US (10U * US_PER_SEC)
|
#define GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US (10U * US_PER_SEC)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** @} **/
|
/** @} **/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
#ifndef NET_GNRC_IPV6_EXT_FRAG_H
|
#ifndef NET_GNRC_IPV6_EXT_FRAG_H
|
||||||
#define NET_GNRC_IPV6_EXT_FRAG_H
|
#define NET_GNRC_IPV6_EXT_FRAG_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "clist.h"
|
#include "clist.h"
|
||||||
@ -34,7 +35,22 @@ extern "C" {
|
|||||||
/**
|
/**
|
||||||
* @brief Message type to time reassembly buffer garbage collection
|
* @brief Message type to time reassembly buffer garbage collection
|
||||||
*/
|
*/
|
||||||
#define GNRC_IPV6_EXT_FRAG_RBUF_GC (0xfe00U)
|
#define GNRC_IPV6_EXT_FRAG_RBUF_GC (0xfe00U)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Message type to continue fragmenting a datagram from a given
|
||||||
|
* fragmentation send buffer
|
||||||
|
*
|
||||||
|
* Expected type: @ref gnrc_ipv6_ext_frag_send_t
|
||||||
|
*/
|
||||||
|
#define GNRC_IPV6_EXT_FRAG_CONTINUE (0xfe01U)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Message type to send a fragment of an IPv6 datagram.
|
||||||
|
*
|
||||||
|
* Expected type: @ref gnrc_pktsnip_t
|
||||||
|
*/
|
||||||
|
#define GNRC_IPV6_EXT_FRAG_SEND (0xfe02U)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Data type to describe limits of a single fragment in the reassembly
|
* @brief Data type to describe limits of a single fragment in the reassembly
|
||||||
@ -47,6 +63,18 @@ typedef struct gnrc_ipv6_ext_frag_limits {
|
|||||||
* fragment */
|
* fragment */
|
||||||
} gnrc_ipv6_ext_frag_limits_t;
|
} gnrc_ipv6_ext_frag_limits_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fragmentation send buffer type
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
gnrc_pktsnip_t *pkt; /**< the IPv6 packet to fragment */
|
||||||
|
gnrc_pktsnip_t *per_frag; /**< per fragment headers */
|
||||||
|
uint32_t id; /**< the identification for the fragment header */
|
||||||
|
uint16_t path_mtu; /**< path MTU to destination of
|
||||||
|
* gnrc_ipv6_ext_frag_send_t::pkt */
|
||||||
|
uint16_t offset; /**< current fragmentation offset */
|
||||||
|
} gnrc_ipv6_ext_frag_send_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A reassembly buffer entry
|
* @brief A reassembly buffer entry
|
||||||
*/
|
*/
|
||||||
@ -71,6 +99,26 @@ typedef struct {
|
|||||||
*/
|
*/
|
||||||
void gnrc_ipv6_ext_frag_init(void);
|
void gnrc_ipv6_ext_frag_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send an IPv6 packet fragmented
|
||||||
|
*
|
||||||
|
* @param[in] pkt The IPv6 packet. The packet must have an already
|
||||||
|
* prepared @ref GNRC_NETTYPE_NETIF snip as its first
|
||||||
|
* snip. The packet must contain at least an IPv6 header
|
||||||
|
* and any number of IPv6 extension headers after that.
|
||||||
|
* @param[in] path_mtu Path MTU to destination of IPv6 packet.
|
||||||
|
*/
|
||||||
|
void gnrc_ipv6_ext_frag_send_pkt(gnrc_pktsnip_t *pkt, unsigned path_mtu);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief (Continue to) fragment packet already in fragmentation send buffer
|
||||||
|
*
|
||||||
|
* @pre `snd_buf != NULL`
|
||||||
|
*
|
||||||
|
* @param[in,out] snd_buf A fragmentation send buffer entry. May not be NULL.
|
||||||
|
*/
|
||||||
|
void gnrc_ipv6_ext_frag_send(gnrc_ipv6_ext_frag_send_t *snd_buf);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reassemble fragmented IPv6 packet
|
* @brief Reassemble fragmented IPv6 packet
|
||||||
*
|
*
|
||||||
|
|||||||
@ -14,12 +14,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "byteorder.h"
|
#include "byteorder.h"
|
||||||
#include "net/ipv6/ext/frag.h"
|
#include "net/ipv6/ext/frag.h"
|
||||||
#include "net/ipv6/addr.h"
|
#include "net/ipv6/addr.h"
|
||||||
|
#include "net/ipv6/hdr.h"
|
||||||
|
#include "net/gnrc/ipv6.h"
|
||||||
#include "net/gnrc/ipv6/ext.h"
|
#include "net/gnrc/ipv6/ext.h"
|
||||||
|
#include "net/gnrc/ipv6/ext/frag.h"
|
||||||
|
#include "net/gnrc/nettype.h"
|
||||||
#include "net/gnrc/pktbuf.h"
|
#include "net/gnrc/pktbuf.h"
|
||||||
|
#include "random.h"
|
||||||
#include "sched.h"
|
#include "sched.h"
|
||||||
#include "xtimer.h"
|
#include "xtimer.h"
|
||||||
|
|
||||||
@ -28,12 +34,20 @@
|
|||||||
#define ENABLE_DEBUG (0)
|
#define ENABLE_DEBUG (0)
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
|
||||||
|
static gnrc_ipv6_ext_frag_send_t _snd_bufs[GNRC_IPV6_EXT_FRAG_SEND_SIZE];
|
||||||
static gnrc_ipv6_ext_frag_rbuf_t _rbuf[GNRC_IPV6_EXT_FRAG_RBUF_SIZE];
|
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 gnrc_ipv6_ext_frag_limits_t _limits_pool[GNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE];
|
||||||
static clist_node_t _free_limits;
|
static clist_node_t _free_limits;
|
||||||
static xtimer_t _gc_xtimer;
|
static xtimer_t _gc_xtimer;
|
||||||
static msg_t _gc_msg = { .type = GNRC_IPV6_EXT_FRAG_RBUF_GC };
|
static msg_t _gc_msg = { .type = GNRC_IPV6_EXT_FRAG_RBUF_GC };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Implement better mechanism as described in
|
||||||
|
* https://tools.ietf.org/html/rfc7739 (for minimal approach
|
||||||
|
* destination cache is required)
|
||||||
|
*/
|
||||||
|
static uint32_t _last_id;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
FRAG_LIMITS_NEW = 0, /**< limits are not present and do not overlap */
|
FRAG_LIMITS_NEW = 0, /**< limits are not present and do not overlap */
|
||||||
FRAG_LIMITS_DUPLICATE, /**< fragment limits are already present */
|
FRAG_LIMITS_DUPLICATE, /**< fragment limits are already present */
|
||||||
@ -46,11 +60,289 @@ void gnrc_ipv6_ext_frag_init(void)
|
|||||||
#ifdef TEST_SUITES
|
#ifdef TEST_SUITES
|
||||||
memset(_rbuf, 0, sizeof(_rbuf));
|
memset(_rbuf, 0, sizeof(_rbuf));
|
||||||
#endif
|
#endif
|
||||||
|
_last_id = random_uint32();
|
||||||
for (unsigned i = 0; i < GNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE; i++) {
|
for (unsigned i = 0; i < GNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE; i++) {
|
||||||
clist_rpush(&_free_limits, (clist_node_t *)&_limits_pool[i]);
|
clist_rpush(&_free_limits, (clist_node_t *)&_limits_pool[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ==================
|
||||||
|
* IPv6 fragmentation
|
||||||
|
* ==================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocates a fragmentation send buffer entry from pool
|
||||||
|
*
|
||||||
|
* @return A free fragmentation send buffer entry.
|
||||||
|
*/
|
||||||
|
static gnrc_ipv6_ext_frag_send_t *_snd_buf_alloc(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes a fragmentation send buffer and releases the stored
|
||||||
|
* datagrams and fragments.
|
||||||
|
*
|
||||||
|
* @param[in] snd_buf A fragmentation send buffer entry
|
||||||
|
*/
|
||||||
|
static void _snd_buf_free(gnrc_ipv6_ext_frag_send_t *snd_buf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes a fragmentation send buffer without releasing the stored
|
||||||
|
* datagrams and fragments.
|
||||||
|
*
|
||||||
|
* @param[in] snd_buf A fragmentation send buffer entry
|
||||||
|
*/
|
||||||
|
static void _snd_buf_del(gnrc_ipv6_ext_frag_send_t *snd_buf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dermines the last Per-Fragment extension header of a datagram.
|
||||||
|
*
|
||||||
|
* @see [RFC 8200, section 4.5](https://tools.ietf.org/html/rfc8200#section-4.5)
|
||||||
|
* for definition of _Per-Fragment extension header_
|
||||||
|
*
|
||||||
|
* @param[in] pkt An IPv6 datagram
|
||||||
|
*
|
||||||
|
* @return The last Per-Fragment extension header in @p pkt.
|
||||||
|
* @return NULL, unexpected error. Should never be reached.
|
||||||
|
*/
|
||||||
|
static gnrc_pktsnip_t *_determine_last_per_frag(gnrc_pktsnip_t *pkt);
|
||||||
|
|
||||||
|
void gnrc_ipv6_ext_frag_send_pkt(gnrc_pktsnip_t *pkt, unsigned path_mtu)
|
||||||
|
{
|
||||||
|
gnrc_ipv6_ext_frag_send_t *snd_buf = _snd_buf_alloc();
|
||||||
|
gnrc_pktsnip_t *last_per_frag;
|
||||||
|
|
||||||
|
assert(pkt->type == GNRC_NETTYPE_NETIF);
|
||||||
|
if (snd_buf == NULL) {
|
||||||
|
DEBUG("ipv6_ext_frag: can not allocate fragmentation send buffer\n");
|
||||||
|
gnrc_pktbuf_release_error(pkt, ENOMEM);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last_per_frag = _determine_last_per_frag(pkt);
|
||||||
|
snd_buf->per_frag = pkt;
|
||||||
|
snd_buf->pkt = last_per_frag->next;
|
||||||
|
/* separate per-fragment headers from rest */
|
||||||
|
last_per_frag->next = NULL;
|
||||||
|
snd_buf->id = _last_id;
|
||||||
|
_last_id += random_uint32_range(1, 64);
|
||||||
|
snd_buf->path_mtu = path_mtu;
|
||||||
|
snd_buf->offset = 0;
|
||||||
|
gnrc_ipv6_ext_frag_send(snd_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gnrc_ipv6_ext_frag_send(gnrc_ipv6_ext_frag_send_t *snd_buf)
|
||||||
|
{
|
||||||
|
assert(snd_buf != NULL);
|
||||||
|
gnrc_pktsnip_t *last = NULL, *ptr, *to_send = NULL;
|
||||||
|
ipv6_ext_frag_t *frag_hdr;
|
||||||
|
uint8_t *nh = NULL;
|
||||||
|
network_uint16_t *len = NULL;
|
||||||
|
msg_t msg;
|
||||||
|
/* see if fragment to send fits into the path MTU */
|
||||||
|
bool last_fragment = (snd_buf->path_mtu >
|
||||||
|
(gnrc_pkt_len(snd_buf->per_frag->next) +
|
||||||
|
sizeof(ipv6_ext_frag_t) +
|
||||||
|
gnrc_pkt_len(snd_buf->pkt)));
|
||||||
|
uint16_t remaining = snd_buf->path_mtu & 0xfff8; /* lower multiple of 8 */
|
||||||
|
|
||||||
|
/* prepare fragment for sending */
|
||||||
|
ptr = snd_buf->per_frag;
|
||||||
|
if (!last_fragment) {
|
||||||
|
/* this won't be the last fragment
|
||||||
|
* => we need to duplicate the per-fragment headers */
|
||||||
|
gnrc_pktbuf_hold(ptr, 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* prevent duplicate release of per_frag */
|
||||||
|
snd_buf->per_frag = NULL;
|
||||||
|
}
|
||||||
|
/* first add per-fragment headers */
|
||||||
|
while (ptr) {
|
||||||
|
gnrc_pktsnip_t *tmp = gnrc_pktbuf_start_write(ptr);
|
||||||
|
if (tmp == NULL) {
|
||||||
|
DEBUG("ipv6_ext_frag: packet buffer full, canceling fragmentation\n");
|
||||||
|
if (ptr->users > 1) {
|
||||||
|
/* we are not the last fragment, so we need to also release
|
||||||
|
* our hold on the snips we did not duplicate so far
|
||||||
|
* and all also release all the snips we did duplicated so far
|
||||||
|
*/
|
||||||
|
if (to_send != NULL) {
|
||||||
|
gnrc_pktbuf_release(to_send);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gnrc_pktbuf_release(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_snd_buf_free(snd_buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ptr = tmp;
|
||||||
|
if (to_send == NULL) {
|
||||||
|
to_send = ptr;
|
||||||
|
}
|
||||||
|
switch (ptr->type) {
|
||||||
|
case GNRC_NETTYPE_IPV6: {
|
||||||
|
ipv6_hdr_t *hdr = ptr->data;
|
||||||
|
nh = &hdr->nh;
|
||||||
|
len = &hdr->len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GNRC_NETTYPE_IPV6_EXT: {
|
||||||
|
ipv6_ext_t *hdr = ptr->data;
|
||||||
|
nh = &hdr->nh;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ptr->type != GNRC_NETTYPE_NETIF) {
|
||||||
|
remaining -= ptr->size;
|
||||||
|
}
|
||||||
|
if (last) {
|
||||||
|
last->next = ptr;
|
||||||
|
}
|
||||||
|
last = ptr;
|
||||||
|
ptr = ptr->next;
|
||||||
|
}
|
||||||
|
assert(nh != NULL);
|
||||||
|
/* then the fragment header */
|
||||||
|
ptr = gnrc_ipv6_ext_build(last, last->next, *nh, sizeof(ipv6_ext_frag_t));
|
||||||
|
if (ptr == NULL) {
|
||||||
|
DEBUG("ipv6_ext_frag: unable to create fragmentation header\n");
|
||||||
|
gnrc_pktbuf_release(to_send);
|
||||||
|
_snd_buf_free(snd_buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
remaining -= sizeof(ipv6_ext_frag_t);
|
||||||
|
frag_hdr = ptr->data;
|
||||||
|
ipv6_ext_frag_set_offset(frag_hdr, snd_buf->offset);
|
||||||
|
if (!last_fragment) {
|
||||||
|
ipv6_ext_frag_set_more(frag_hdr);
|
||||||
|
}
|
||||||
|
frag_hdr->id = byteorder_htonl(snd_buf->id);
|
||||||
|
*nh = PROTNUM_IPV6_EXT_FRAG;
|
||||||
|
last = ptr;
|
||||||
|
/* then the rest */
|
||||||
|
while (remaining && snd_buf->pkt) {
|
||||||
|
if (last_fragment ||
|
||||||
|
(snd_buf->pkt->size <= remaining)) {
|
||||||
|
ptr = snd_buf->pkt;
|
||||||
|
snd_buf->pkt = ptr->next;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ptr = gnrc_pktbuf_mark(snd_buf->pkt, remaining,
|
||||||
|
GNRC_NETTYPE_UNDEF);
|
||||||
|
if (ptr == NULL) {
|
||||||
|
DEBUG("ipv6_ext_frag: packet buffer full, canceling fragmentation\n");
|
||||||
|
gnrc_pktbuf_release(to_send);
|
||||||
|
_snd_buf_free(snd_buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(snd_buf->pkt->next == ptr); /* we just created it with mark */
|
||||||
|
snd_buf->pkt->next = snd_buf->pkt->next->next;
|
||||||
|
}
|
||||||
|
ptr->next = NULL;
|
||||||
|
last->next = ptr;
|
||||||
|
last = ptr;
|
||||||
|
remaining -= ptr->size;
|
||||||
|
snd_buf->offset += ptr->size;
|
||||||
|
}
|
||||||
|
assert(len != NULL);
|
||||||
|
/* adapt IPv6 header length field */
|
||||||
|
*len = byteorder_htons(gnrc_pkt_len(to_send->next->next));
|
||||||
|
/* tell gnrc_ipv6 to send the above prepared fragment */
|
||||||
|
msg.type = GNRC_IPV6_EXT_FRAG_SEND;
|
||||||
|
msg.content.ptr = to_send;
|
||||||
|
msg_try_send(&msg, gnrc_ipv6_pid);
|
||||||
|
if (last_fragment) {
|
||||||
|
/* last fragment => we don't need the send buffer anymore.
|
||||||
|
* But as we just sent it to gnrc_ipv6 we still need the packet
|
||||||
|
* allocated, so not _snd_buf_free()! */
|
||||||
|
_snd_buf_del(snd_buf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* tell gnrc_ipv6 to continue fragmenting the datagram in snd_buf
|
||||||
|
* later */
|
||||||
|
msg.type = GNRC_IPV6_EXT_FRAG_CONTINUE;
|
||||||
|
msg.content.ptr = snd_buf;
|
||||||
|
msg_try_send(&msg, gnrc_ipv6_pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gnrc_ipv6_ext_frag_send_t *_snd_buf_alloc(void)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < GNRC_IPV6_EXT_FRAG_SEND_SIZE; i++) {
|
||||||
|
gnrc_ipv6_ext_frag_send_t *snd_buf = &_snd_bufs[i];
|
||||||
|
if (snd_buf->pkt == NULL) {
|
||||||
|
return snd_buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _snd_buf_del(gnrc_ipv6_ext_frag_send_t *snd_buf)
|
||||||
|
{
|
||||||
|
snd_buf->per_frag = NULL;
|
||||||
|
snd_buf->pkt = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _snd_buf_free(gnrc_ipv6_ext_frag_send_t *snd_buf)
|
||||||
|
{
|
||||||
|
if (snd_buf->per_frag) {
|
||||||
|
gnrc_pktbuf_release(snd_buf->per_frag);
|
||||||
|
}
|
||||||
|
if (snd_buf->pkt) {
|
||||||
|
gnrc_pktbuf_release(snd_buf->pkt);
|
||||||
|
}
|
||||||
|
_snd_buf_del(snd_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gnrc_pktsnip_t *_determine_last_per_frag(gnrc_pktsnip_t *ptr)
|
||||||
|
{
|
||||||
|
gnrc_pktsnip_t *last_per_frag = NULL;
|
||||||
|
unsigned nh = PROTNUM_RESERVED;
|
||||||
|
|
||||||
|
/* ignore NETIF header */
|
||||||
|
ptr = ptr->next;
|
||||||
|
while (ptr) {
|
||||||
|
switch (ptr->type) {
|
||||||
|
case GNRC_NETTYPE_IPV6: {
|
||||||
|
ipv6_hdr_t *hdr = ptr->data;
|
||||||
|
last_per_frag = ptr;
|
||||||
|
nh = hdr->nh;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GNRC_NETTYPE_IPV6_EXT: {
|
||||||
|
ipv6_ext_t *hdr = ptr->data;
|
||||||
|
switch (nh) {
|
||||||
|
/* "[...] that is, all headers up to and including the
|
||||||
|
* Routing header if present, else the Hop-by-Hop Options
|
||||||
|
* header if present, [...]"
|
||||||
|
* (IPv6 header comes before Hop-by-Hop Options comes before
|
||||||
|
* Routing header, so an override to keep the quoted
|
||||||
|
* priorities is ensured) */
|
||||||
|
case PROTNUM_IPV6_EXT_HOPOPT:
|
||||||
|
case PROTNUM_IPV6_EXT_RH:
|
||||||
|
last_per_frag = ptr;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nh = hdr->nh;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assert(last_per_frag != NULL);
|
||||||
|
return last_per_frag;
|
||||||
|
}
|
||||||
|
ptr = ptr->next;
|
||||||
|
}
|
||||||
|
/* should not be reached */
|
||||||
|
assert(false);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ===============
|
* ===============
|
||||||
* IPv6 reassembly
|
* IPv6 reassembly
|
||||||
|
|||||||
@ -73,6 +73,10 @@ static void _receive(gnrc_pktsnip_t *pkt);
|
|||||||
* prep_hdr: prepare header for sending (call to _fill_ipv6_hdr()), otherwise
|
* prep_hdr: prepare header for sending (call to _fill_ipv6_hdr()), otherwise
|
||||||
* assume it is already prepared */
|
* assume it is already prepared */
|
||||||
static void _send(gnrc_pktsnip_t *pkt, bool prep_hdr);
|
static void _send(gnrc_pktsnip_t *pkt, bool prep_hdr);
|
||||||
|
|
||||||
|
#ifdef MODULE_GNRC_IPV6_EXT_FRAG
|
||||||
|
static void _send_by_netif_hdr(gnrc_pktsnip_t *pkt);
|
||||||
|
#endif /* MODULE_GNRC_IPV6_EXT_FRAG */
|
||||||
/* Main event loop for IPv6 */
|
/* Main event loop for IPv6 */
|
||||||
static void *_event_loop(void *args);
|
static void *_event_loop(void *args);
|
||||||
|
|
||||||
@ -212,6 +216,14 @@ static void *_event_loop(void *args)
|
|||||||
case GNRC_IPV6_EXT_FRAG_RBUF_GC:
|
case GNRC_IPV6_EXT_FRAG_RBUF_GC:
|
||||||
gnrc_ipv6_ext_frag_rbuf_gc();
|
gnrc_ipv6_ext_frag_rbuf_gc();
|
||||||
break;
|
break;
|
||||||
|
case GNRC_IPV6_EXT_FRAG_CONTINUE:
|
||||||
|
DEBUG("ipv6: continue fragmenting packet\n");
|
||||||
|
gnrc_ipv6_ext_frag_send(msg.content.ptr);
|
||||||
|
break;
|
||||||
|
case GNRC_IPV6_EXT_FRAG_SEND:
|
||||||
|
DEBUG("ipv6: send fragment\n");
|
||||||
|
_send_by_netif_hdr(msg.content.ptr);
|
||||||
|
break;
|
||||||
#endif /* MODULE_GNRC_IPV6_EXT_FRAG */
|
#endif /* MODULE_GNRC_IPV6_EXT_FRAG */
|
||||||
case GNRC_IPV6_NIB_SND_UC_NS:
|
case GNRC_IPV6_NIB_SND_UC_NS:
|
||||||
case GNRC_IPV6_NIB_SND_MC_NS:
|
case GNRC_IPV6_NIB_SND_MC_NS:
|
||||||
@ -435,6 +447,38 @@ static bool _safe_fill_ipv6_hdr(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* functions for sending */
|
/* functions for sending */
|
||||||
|
static bool _fragment_pkt_if_needed(gnrc_pktsnip_t *pkt,
|
||||||
|
gnrc_netif_t *netif,
|
||||||
|
bool from_me)
|
||||||
|
{
|
||||||
|
#ifdef MODULE_GNRC_IPV6_EXT_FRAG
|
||||||
|
/* TODO: get path MTU when PMTU discovery is implemented */
|
||||||
|
unsigned path_mtu = netif->ipv6.mtu;
|
||||||
|
|
||||||
|
if (from_me && (gnrc_pkt_len(pkt->next) > path_mtu)) {
|
||||||
|
gnrc_netif_hdr_t *hdr = pkt->data;
|
||||||
|
hdr->if_pid = netif->pid;
|
||||||
|
gnrc_ipv6_ext_frag_send_pkt(pkt, path_mtu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#else /* MODULE_GNRC_IPV6_EXT_FRAG */
|
||||||
|
(void)pkt;
|
||||||
|
(void)netif;
|
||||||
|
(void)from_me;
|
||||||
|
#endif /* MODULE_GNRC_IPV6_EXT_FRAG */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef MODULE_GNRC_IPV6_EXT_FRAG
|
||||||
|
static void _send_by_netif_hdr(gnrc_pktsnip_t *pkt)
|
||||||
|
{
|
||||||
|
assert(pkt->type == GNRC_NETTYPE_NETIF);
|
||||||
|
gnrc_netif_t *netif = gnrc_netif_hdr_get_netif(pkt->data);
|
||||||
|
|
||||||
|
_send_to_iface(netif, pkt);
|
||||||
|
}
|
||||||
|
#endif /* MODULE_GNRC_IPV6_EXT_FRAG */
|
||||||
|
|
||||||
static void _send_unicast(gnrc_pktsnip_t *pkt, bool prep_hdr,
|
static void _send_unicast(gnrc_pktsnip_t *pkt, bool prep_hdr,
|
||||||
gnrc_netif_t *netif, ipv6_hdr_t *ipv6_hdr,
|
gnrc_netif_t *netif, ipv6_hdr_t *ipv6_hdr,
|
||||||
uint8_t netif_hdr_flags)
|
uint8_t netif_hdr_flags)
|
||||||
@ -457,6 +501,11 @@ static void _send_unicast(gnrc_pktsnip_t *pkt, bool prep_hdr,
|
|||||||
netif_hdr_flags)) == NULL) {
|
netif_hdr_flags)) == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
/* prep_hdr => The packet is from me */
|
||||||
|
if (_fragment_pkt_if_needed(pkt, netif, prep_hdr)) {
|
||||||
|
DEBUG("ipv6: packet is fragmented\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
DEBUG("ipv6: send unicast over interface %" PRIkernel_pid "\n",
|
DEBUG("ipv6: send unicast over interface %" PRIkernel_pid "\n",
|
||||||
netif->pid);
|
netif->pid);
|
||||||
/* and send to interface */
|
/* and send to interface */
|
||||||
@ -468,6 +517,7 @@ static void _send_unicast(gnrc_pktsnip_t *pkt, bool prep_hdr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline void _send_multicast_over_iface(gnrc_pktsnip_t *pkt,
|
static inline void _send_multicast_over_iface(gnrc_pktsnip_t *pkt,
|
||||||
|
bool prep_hdr,
|
||||||
gnrc_netif_t *netif,
|
gnrc_netif_t *netif,
|
||||||
uint8_t netif_hdr_flags)
|
uint8_t netif_hdr_flags)
|
||||||
{
|
{
|
||||||
@ -476,6 +526,11 @@ static inline void _send_multicast_over_iface(gnrc_pktsnip_t *pkt,
|
|||||||
GNRC_NETIF_HDR_FLAGS_MULTICAST)) == NULL) {
|
GNRC_NETIF_HDR_FLAGS_MULTICAST)) == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
/* prep_hdr => The packet is from me */
|
||||||
|
if (_fragment_pkt_if_needed(pkt, netif, prep_hdr)) {
|
||||||
|
DEBUG("ipv6: packet is fragmented\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
DEBUG("ipv6: send multicast over interface %" PRIkernel_pid "\n", netif->pid);
|
DEBUG("ipv6: send multicast over interface %" PRIkernel_pid "\n", netif->pid);
|
||||||
#ifdef MODULE_NETSTATS_IPV6
|
#ifdef MODULE_NETSTATS_IPV6
|
||||||
netif->ipv6.stats.tx_mcast_count++;
|
netif->ipv6.stats.tx_mcast_count++;
|
||||||
@ -528,12 +583,12 @@ static void _send_multicast(gnrc_pktsnip_t *pkt, bool prep_hdr,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_send_multicast_over_iface(pkt, netif, netif_hdr_flags);
|
_send_multicast_over_iface(pkt, prep_hdr, netif, netif_hdr_flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (_safe_fill_ipv6_hdr(netif, pkt, prep_hdr)) {
|
if (_safe_fill_ipv6_hdr(netif, pkt, prep_hdr)) {
|
||||||
_send_multicast_over_iface(pkt, netif, netif_hdr_flags);
|
_send_multicast_over_iface(pkt, prep_hdr, netif, netif_hdr_flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else /* GNRC_NETIF_NUMOF */
|
#else /* GNRC_NETIF_NUMOF */
|
||||||
@ -547,7 +602,7 @@ static void _send_multicast(gnrc_pktsnip_t *pkt, bool prep_hdr,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_safe_fill_ipv6_hdr(netif, pkt, prep_hdr)) {
|
if (_safe_fill_ipv6_hdr(netif, pkt, prep_hdr)) {
|
||||||
_send_multicast_over_iface(pkt, netif, netif_hdr_flags);
|
_send_multicast_over_iface(pkt, prep_hdr, netif, netif_hdr_flags);
|
||||||
}
|
}
|
||||||
#endif /* GNRC_NETIF_NUMOF */
|
#endif /* GNRC_NETIF_NUMOF */
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,17 @@ DEVELHELP := 1
|
|||||||
include ../Makefile.tests_common
|
include ../Makefile.tests_common
|
||||||
|
|
||||||
BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-leonardo \
|
BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-leonardo \
|
||||||
arduino-mega2560 arduino-nano arduino-uno chronos \
|
arduino-mega2560 arduino-nano arduino-uno \
|
||||||
|
blackpill bluepill hifive1 hifive1b \
|
||||||
i-nucleo-lrwan1 mega-xplained msb-430 msb-430h \
|
i-nucleo-lrwan1 mega-xplained msb-430 msb-430h \
|
||||||
nucleo-f030r8 nucleo-f031k6 nucleo-f042k6 \
|
nucleo-f030r8 nucleo-f031k6 nucleo-f042k6 \
|
||||||
|
nucleo-f070rb nucleo-f072rb nucleo-f302r8 \
|
||||||
nucleo-f303k8 nucleo-f334r8 nucleo-l031k6 \
|
nucleo-f303k8 nucleo-f334r8 nucleo-l031k6 \
|
||||||
nucleo-l053r8 stm32f0discovery stm32l0538-disco \
|
nucleo-l053r8 saml10-xpro saml11-xpro \
|
||||||
telosb waspmote-pro wsn430-v1_3b wsn430-v1_4 z1
|
stm32f0discovery stm32l0538-disco telosb \
|
||||||
|
waspmote-pro wsn430-v1_3b wsn430-v1_4 z1
|
||||||
|
# chronos, hamilton, ruuvitag, and thingy52 boards don't support ethos
|
||||||
|
BOARD_BLACKLIST := chronos hamilton ruuvitag thingy52
|
||||||
|
|
||||||
export TAP ?= tap0
|
export TAP ?= tap0
|
||||||
|
|
||||||
@ -16,15 +21,22 @@ CFLAGS += -DOUTPUT=TEXT
|
|||||||
CFLAGS += -DTEST_SUITES="gnrc_ipv6_ext_frag"
|
CFLAGS += -DTEST_SUITES="gnrc_ipv6_ext_frag"
|
||||||
CFLAGS += -DGNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE=3
|
CFLAGS += -DGNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE=3
|
||||||
|
|
||||||
# use Ethernet as link-layer protocol for native
|
|
||||||
# The only current general option for non-native boards, ethos, performs poorly
|
|
||||||
# with the rapidly sent, large packets sent by the Linux kernel.
|
|
||||||
ifeq (native,$(BOARD))
|
ifeq (native,$(BOARD))
|
||||||
USEMODULE += netdev_tap
|
USEMODULE += netdev_tap
|
||||||
TERMFLAGS ?= $(TAP)
|
TERMFLAGS ?= $(TAP)
|
||||||
|
else
|
||||||
|
USEMODULE += stdio_ethos
|
||||||
|
|
||||||
USEMODULE += auto_init_gnrc_netif
|
ETHOS_BAUDRATE ?= 115200
|
||||||
|
CFLAGS += -DETHOS_BAUDRATE=$(ETHOS_BAUDRATE)
|
||||||
|
TERMDEPS += ethos
|
||||||
|
TERMPROG ?= sudo $(RIOTTOOLS)/ethos/ethos
|
||||||
|
TERMFLAGS ?= $(TAP) $(PORT) $(ETHOS_BAUDRATE)
|
||||||
endif
|
endif
|
||||||
|
USEMODULE += auto_init_gnrc_netif
|
||||||
|
# add dummy interface to test forwarding to smaller MTU
|
||||||
|
USEMODULE += netdev_test
|
||||||
|
GNRC_NETIF_NUMOF := 2
|
||||||
# Specify the mandatory networking modules for IPv6
|
# Specify the mandatory networking modules for IPv6
|
||||||
USEMODULE += gnrc_ipv6_router_default
|
USEMODULE += gnrc_ipv6_router_default
|
||||||
USEMODULE += gnrc_icmpv6_error
|
USEMODULE += gnrc_icmpv6_error
|
||||||
@ -42,8 +54,13 @@ USEMODULE += shell
|
|||||||
USEMODULE += shell_commands
|
USEMODULE += shell_commands
|
||||||
USEMODULE += ps
|
USEMODULE += ps
|
||||||
|
|
||||||
# native requires sudo for the `scapy` tests, but those are not executed for
|
# The test requires some setup and to be run as root
|
||||||
# non-native boards
|
# So it cannot currently be run
|
||||||
TEST_ON_CI_BLACKLIST += native
|
TEST_ON_CI_BLACKLIST += all
|
||||||
|
|
||||||
|
.PHONY: ethos
|
||||||
|
|
||||||
|
ethos:
|
||||||
|
$(Q)env -u CC -u CFLAGS make -C $(RIOTTOOLS)/ethos
|
||||||
|
|
||||||
include $(RIOTBASE)/Makefile.include
|
include $(RIOTBASE)/Makefile.include
|
||||||
|
|||||||
@ -20,18 +20,31 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "byteorder.h"
|
#include "byteorder.h"
|
||||||
#include "clist.h"
|
#include "clist.h"
|
||||||
#include "embUnit.h"
|
#include "embUnit.h"
|
||||||
|
#include "net/ipv6/addr.h"
|
||||||
#include "net/ipv6/ext/frag.h"
|
#include "net/ipv6/ext/frag.h"
|
||||||
#include "net/protnum.h"
|
#include "net/protnum.h"
|
||||||
#include "net/gnrc.h"
|
#include "net/gnrc.h"
|
||||||
#include "net/gnrc/ipv6/ext.h"
|
#include "net/gnrc/ipv6/ext.h"
|
||||||
#include "net/gnrc/ipv6/ext/frag.h"
|
#include "net/gnrc/ipv6/ext/frag.h"
|
||||||
#include "net/gnrc/ipv6/hdr.h"
|
#include "net/gnrc/ipv6/hdr.h"
|
||||||
|
#include "net/gnrc/ipv6/nib.h"
|
||||||
|
#include "net/gnrc/netif/raw.h"
|
||||||
|
#include "net/gnrc/udp.h"
|
||||||
|
#include "net/netdev_test.h"
|
||||||
|
#include "od.h"
|
||||||
|
#include "random.h"
|
||||||
#include "shell.h"
|
#include "shell.h"
|
||||||
|
#include "xtimer.h"
|
||||||
|
|
||||||
|
#define TEST_SAMPLE "This is a test. Failure might sometimes be an " \
|
||||||
|
"option, but not today. "
|
||||||
|
#define TEST_PORT (20908U)
|
||||||
#define TEST_FRAG1 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
|
#define TEST_FRAG1 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
|
||||||
0xab, 0xcf, 0xde, 0xb8, 0x18, 0x48, 0xe3, 0x70, \
|
0xab, 0xcf, 0xde, 0xb8, 0x18, 0x48, 0xe3, 0x70, \
|
||||||
0x30, 0x1a, 0xba, 0x27, 0xa6, 0xa7, 0xce, 0xeb, \
|
0x30, 0x1a, 0xba, 0x27, 0xa6, 0xa7, 0xce, 0xeb, \
|
||||||
@ -63,10 +76,18 @@
|
|||||||
#define TEST_HL (64U)
|
#define TEST_HL (64U)
|
||||||
|
|
||||||
extern int udp_cmd(int argc, char **argv);
|
extern int udp_cmd(int argc, char **argv);
|
||||||
|
/* shell_test_cmd is used to test weird snip configurations,
|
||||||
|
* the rest can just use udp_cmd */
|
||||||
|
static int shell_test_cmd(int argc, char **argv);
|
||||||
|
|
||||||
|
static netdev_test_t mock_netdev;
|
||||||
|
static gnrc_netif_t *eth_netif, *mock_netif;
|
||||||
|
static ipv6_addr_t *local_addr;
|
||||||
|
static char mock_netif_stack[THREAD_STACKSIZE_DEFAULT];
|
||||||
static char line_buf[SHELL_DEFAULT_BUFSIZE];
|
static char line_buf[SHELL_DEFAULT_BUFSIZE];
|
||||||
static const shell_command_t shell_commands[] = {
|
static const shell_command_t shell_commands[] = {
|
||||||
{ "udp", "send data over UDP and listen on UDP ports", udp_cmd },
|
{ "udp", "send data over UDP and listen on UDP ports", udp_cmd },
|
||||||
|
{ "test", "sends data according to a specified numeric test", shell_test_cmd },
|
||||||
{ NULL, NULL, NULL }
|
{ NULL, NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -443,9 +464,174 @@ static void run_unittests(void)
|
|||||||
TESTS_END();
|
TESTS_END();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gnrc_pktsnip_t *_build_udp_packet(const ipv6_addr_t *dst,
|
||||||
|
unsigned payload_size,
|
||||||
|
gnrc_pktsnip_t *payload)
|
||||||
|
{
|
||||||
|
udp_hdr_t *udp_hdr;
|
||||||
|
ipv6_hdr_t *ipv6_hdr;
|
||||||
|
gnrc_netif_hdr_t *netif_hdr;
|
||||||
|
gnrc_pktsnip_t *hdr;
|
||||||
|
|
||||||
|
if (payload == NULL) {
|
||||||
|
uint8_t *data;
|
||||||
|
|
||||||
|
payload = gnrc_pktbuf_add(NULL, NULL, payload_size, GNRC_NETTYPE_UNDEF);
|
||||||
|
if (payload == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
data = payload->data;
|
||||||
|
while (payload_size) {
|
||||||
|
unsigned test_sample_len = sizeof(TEST_SAMPLE) - 1;
|
||||||
|
|
||||||
|
if (test_sample_len > payload_size) {
|
||||||
|
test_sample_len = payload_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data, TEST_SAMPLE, test_sample_len);
|
||||||
|
data += test_sample_len;
|
||||||
|
payload_size -= test_sample_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hdr = gnrc_udp_hdr_build(payload, TEST_PORT, TEST_PORT);
|
||||||
|
if (hdr == NULL) {
|
||||||
|
gnrc_pktbuf_release(payload);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
udp_hdr = hdr->data;
|
||||||
|
udp_hdr->length = byteorder_htons(gnrc_pkt_len(hdr));
|
||||||
|
payload = hdr;
|
||||||
|
hdr = gnrc_ipv6_hdr_build(payload, local_addr, dst);
|
||||||
|
if (hdr == NULL) {
|
||||||
|
gnrc_pktbuf_release(payload);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ipv6_hdr = hdr->data;
|
||||||
|
ipv6_hdr->len = byteorder_htons(gnrc_pkt_len(payload));
|
||||||
|
ipv6_hdr->nh = PROTNUM_UDP;
|
||||||
|
ipv6_hdr->hl = GNRC_NETIF_DEFAULT_HL;
|
||||||
|
gnrc_udp_calc_csum(payload, hdr);
|
||||||
|
payload = hdr;
|
||||||
|
hdr = gnrc_netif_hdr_build(NULL, 0, NULL, 0);
|
||||||
|
if (hdr == NULL) {
|
||||||
|
gnrc_pktbuf_release(payload);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
netif_hdr = hdr->data;
|
||||||
|
netif_hdr->if_pid = eth_netif->pid;
|
||||||
|
netif_hdr->flags |= GNRC_NETIF_HDR_FLAGS_MULTICAST;
|
||||||
|
hdr->next = payload;
|
||||||
|
return hdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_ipv6_ext_frag_send_pkt_single_frag(const ipv6_addr_t *dst)
|
||||||
|
{
|
||||||
|
gnrc_pktsnip_t *pkt;
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(local_addr);
|
||||||
|
pkt = _build_udp_packet(dst, sizeof(TEST_SAMPLE) - 1, NULL);
|
||||||
|
TEST_ASSERT_NOT_NULL(pkt);
|
||||||
|
gnrc_ipv6_ext_frag_send_pkt(pkt, eth_netif->ipv6.mtu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_ipv6_ext_frag_payload_snips_not_divisible_of_8(const ipv6_addr_t *dst)
|
||||||
|
{
|
||||||
|
gnrc_pktsnip_t *pkt, *payload = NULL;
|
||||||
|
unsigned payload_size = 0;
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(local_addr);
|
||||||
|
/* TEST_SAMPLE's string length is not a multiple of 8*/
|
||||||
|
TEST_ASSERT((sizeof(TEST_SAMPLE) - 1) & 0x7);
|
||||||
|
|
||||||
|
while (payload_size <= eth_netif->ipv6.mtu) {
|
||||||
|
pkt = gnrc_pktbuf_add(payload, TEST_SAMPLE, sizeof(TEST_SAMPLE) - 1,
|
||||||
|
GNRC_NETTYPE_UNDEF);
|
||||||
|
TEST_ASSERT_NOT_NULL(pkt);
|
||||||
|
payload_size += pkt->size;
|
||||||
|
payload = pkt;
|
||||||
|
}
|
||||||
|
pkt = _build_udp_packet(dst, 0, payload);
|
||||||
|
TEST_ASSERT_NOT_NULL(pkt);
|
||||||
|
gnrc_ipv6_ext_frag_send_pkt(pkt, eth_netif->ipv6.mtu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int shell_test_cmd(int argc, char **argv)
|
||||||
|
{
|
||||||
|
static ipv6_addr_t dst;
|
||||||
|
static void (* const _shell_tests[])(const ipv6_addr_t *) = {
|
||||||
|
test_ipv6_ext_frag_send_pkt_single_frag,
|
||||||
|
test_ipv6_ext_frag_payload_snips_not_divisible_of_8,
|
||||||
|
};
|
||||||
|
int test_num;
|
||||||
|
|
||||||
|
if ((argc < 3) || (ipv6_addr_from_str(&dst, argv[1]) == NULL)) {
|
||||||
|
puts("usage: test <dst_addr> [<num>]");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
test_num = atoi(argv[2]);
|
||||||
|
if ((unsigned)test_num >= ARRAY_SIZE(_shell_tests)) {
|
||||||
|
printf("<num> must be between 0 and %u\n",
|
||||||
|
(unsigned)ARRAY_SIZE(_shell_tests) - 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Running test %d\n", test_num);
|
||||||
|
_shell_tests[test_num](&dst);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: test if forwarded packet is not fragmented */
|
||||||
|
|
||||||
|
static int mock_get_device_type(netdev_t *dev, void *value, size_t max_len)
|
||||||
|
{
|
||||||
|
(void)dev;
|
||||||
|
assert(max_len == sizeof(uint16_t));
|
||||||
|
*((uint16_t *)value) = NETDEV_TYPE_TEST;
|
||||||
|
return sizeof(uint16_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mock_get_max_packet_size(netdev_t *dev, void *value, size_t max_len)
|
||||||
|
{
|
||||||
|
(void)dev;
|
||||||
|
assert(max_len == sizeof(uint16_t));
|
||||||
|
assert(eth_netif != NULL);
|
||||||
|
*((uint16_t *)value) = eth_netif->ipv6.mtu - 8;
|
||||||
|
return sizeof(uint16_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mock_send(netdev_t *dev, const iolist_t *iolist)
|
||||||
|
{
|
||||||
|
(void)dev;
|
||||||
|
int res = 0;
|
||||||
|
while(iolist != NULL) {
|
||||||
|
od_hex_dump(iolist->iol_base, iolist->iol_len,
|
||||||
|
OD_WIDTH_DEFAULT);
|
||||||
|
res += iolist->iol_len;
|
||||||
|
iolist = iolist->iol_next;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
|
eth_netif = gnrc_netif_iter(NULL);
|
||||||
|
/* create mock netif to test forwarding too large fragments */
|
||||||
|
netdev_test_setup(&mock_netdev, 0);
|
||||||
|
netdev_test_set_get_cb(&mock_netdev, NETOPT_DEVICE_TYPE,
|
||||||
|
mock_get_device_type);
|
||||||
|
netdev_test_set_get_cb(&mock_netdev, NETOPT_MAX_PDU_SIZE,
|
||||||
|
mock_get_max_packet_size);
|
||||||
|
netdev_test_set_send_cb(&mock_netdev, mock_send);
|
||||||
|
mock_netif = gnrc_netif_raw_create(mock_netif_stack,
|
||||||
|
sizeof(mock_netif_stack),
|
||||||
|
GNRC_NETIF_PRIO, "mock_netif",
|
||||||
|
(netdev_t *)&mock_netdev);
|
||||||
run_unittests();
|
run_unittests();
|
||||||
|
printf("Sending UDP test packets to port %u\n", TEST_PORT);
|
||||||
|
for (unsigned i = 0; i < GNRC_NETIF_IPV6_ADDRS_NUMOF; i++) {
|
||||||
|
if (ipv6_addr_is_link_local(ð_netif->ipv6.addrs[i])) {
|
||||||
|
local_addr = ð_netif->ipv6.addrs[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
|
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,10 +14,14 @@ import sys
|
|||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from scapy.all import Ether, IPv6, IPv6ExtHdrFragment, sendp
|
from scapy.all import Ether, ICMPv6PacketTooBig, IPv6, IPv6ExtHdrFragment, \
|
||||||
|
UDP, raw, sendp, srp1
|
||||||
from testrunner import run
|
from testrunner import run
|
||||||
|
|
||||||
|
|
||||||
|
RECV_BUFSIZE = 2 * 1500
|
||||||
|
TEST_SAMPLE = b"This is a test. Failure might sometimes be an option, but " \
|
||||||
|
b"not today. "
|
||||||
EXT_HDR_NH = {
|
EXT_HDR_NH = {
|
||||||
IPv6ExtHdrFragment: 44,
|
IPv6ExtHdrFragment: 44,
|
||||||
}
|
}
|
||||||
@ -54,6 +58,13 @@ def stop_udp_server(child):
|
|||||||
"Error: server was not running"])
|
"Error: server was not running"])
|
||||||
|
|
||||||
|
|
||||||
|
def udp_send(child, addr, port, length, num=1, delay=1000000):
|
||||||
|
child.sendline("udp send {addr}%6 {port} {length} {num} {delay}"
|
||||||
|
.format(**vars()))
|
||||||
|
child.expect("Success: send {length} byte to \[[0-9a-f:]+\]:{port}"
|
||||||
|
.format(**vars()))
|
||||||
|
|
||||||
|
|
||||||
def check_and_search_output(cmd, pattern, res_group, *args, **kwargs):
|
def check_and_search_output(cmd, pattern, res_group, *args, **kwargs):
|
||||||
output = subprocess.check_output(cmd, *args, **kwargs).decode("utf-8")
|
output = subprocess.check_output(cmd, *args, **kwargs).decode("utf-8")
|
||||||
for line in output.splitlines():
|
for line in output.splitlines():
|
||||||
@ -145,6 +156,165 @@ def test_reass_offset_too_large(child, iface, hw_dst, ll_dst, ll_src):
|
|||||||
pktbuf_empty(child)
|
pktbuf_empty(child)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv6_ext_frag_shell_test_0(child, s, iface, ll_dst):
|
||||||
|
child.sendline("test {} 0".format(ll_dst))
|
||||||
|
data, _ = s.recvfrom(RECV_BUFSIZE)
|
||||||
|
assert data == TEST_SAMPLE
|
||||||
|
pktbuf_empty(child)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv6_ext_frag_shell_test_1(child, s, iface, ll_dst):
|
||||||
|
child.sendline("test {} 1".format(ll_dst))
|
||||||
|
data, _ = s.recvfrom(RECV_BUFSIZE)
|
||||||
|
offset = 0
|
||||||
|
while (offset < len(data)):
|
||||||
|
assert data[offset:(offset + len(TEST_SAMPLE))] == TEST_SAMPLE
|
||||||
|
offset += len(TEST_SAMPLE)
|
||||||
|
pktbuf_empty(child)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv6_ext_frag_send_success(child, s, iface, ll_dst):
|
||||||
|
length = get_host_mtu(iface)
|
||||||
|
port = s.getsockname()[1]
|
||||||
|
udp_send(child, ll_dst, port, length)
|
||||||
|
data, _ = s.recvfrom(length)
|
||||||
|
assert len(data) == length
|
||||||
|
pktbuf_empty(child)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv6_ext_frag_send_last_fragment_filled(child, s, iface, ll_dst):
|
||||||
|
# every fragment has an IPv6 header and a fragmentation header so subtract
|
||||||
|
# them
|
||||||
|
mtu = get_host_mtu(iface) - len(IPv6() / IPv6ExtHdrFragment())
|
||||||
|
# first fragment has UDP header (so subtract it) and is rounded down to
|
||||||
|
# the nearest multiple of 8
|
||||||
|
length = (mtu - len(UDP())) & 0xfff8
|
||||||
|
# second fragment fills the whole available MTU
|
||||||
|
length += mtu
|
||||||
|
port = s.getsockname()[1]
|
||||||
|
udp_send(child, ll_dst, port, length)
|
||||||
|
data, _ = s.recvfrom(length)
|
||||||
|
assert len(data) == length
|
||||||
|
pktbuf_empty(child)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv6_ext_frag_send_last_fragment_only_one_byte(child, s,
|
||||||
|
iface, ll_dst):
|
||||||
|
mtu = get_host_mtu(iface)
|
||||||
|
# subtract IPv6 and UDP header as they are not part of the UDP payload
|
||||||
|
length = (mtu - len(IPv6() / UDP()))
|
||||||
|
length += 1
|
||||||
|
port = s.getsockname()[1]
|
||||||
|
udp_send(child, ll_dst, port, length)
|
||||||
|
data, _ = s.recvfrom(length)
|
||||||
|
assert len(data) == length
|
||||||
|
pktbuf_empty(child)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv6_ext_frag_send_full_pktbuf(child, s, iface, ll_dst):
|
||||||
|
length = pktbuf_size(child)
|
||||||
|
# remove some slack for meta-data and header and 1 addition fragment header
|
||||||
|
length -= (len(IPv6() / IPv6ExtHdrFragment() / UDP()) +
|
||||||
|
(len(IPv6() / IPv6ExtHdrFragment())) + 96)
|
||||||
|
port = s.getsockname()[1]
|
||||||
|
# trigger neighbor discovery so it doesn't fill the packet buffer
|
||||||
|
udp_send(child, ll_dst, port, 1)
|
||||||
|
data, _ = s.recvfrom(1)
|
||||||
|
last_nd = time.time()
|
||||||
|
count = 0
|
||||||
|
while True:
|
||||||
|
if (time.time() - last_nd) > 5:
|
||||||
|
# trigger neighbor discovery so it doesn't fill the packet buffer
|
||||||
|
udp_send(child, ll_dst, port, 1)
|
||||||
|
data, _ = s.recvfrom(1)
|
||||||
|
last_nd = time.time()
|
||||||
|
udp_send(child, ll_dst, port, length)
|
||||||
|
count += 1
|
||||||
|
try:
|
||||||
|
data, _ = s.recvfrom(length)
|
||||||
|
except socket.timeout:
|
||||||
|
# 8 is the alignment unit of the packet buffer
|
||||||
|
# and 20 the size of a packet snip, so take next multiple of 8 to
|
||||||
|
# 28
|
||||||
|
length -= 24
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
finally:
|
||||||
|
pktbuf_empty(child)
|
||||||
|
assert(count > 1)
|
||||||
|
|
||||||
|
|
||||||
|
def _fwd_setup(child, ll_dst, g_src, g_dst):
|
||||||
|
# check if interface is configured properly
|
||||||
|
child.sendline("ifconfig 7")
|
||||||
|
child.expect(r"MTU:(\d+)")
|
||||||
|
mtu = int(child.match.group(1))
|
||||||
|
# configure routes
|
||||||
|
child.sendline("nib route add 7 {}/128 fe80::1".format(g_dst))
|
||||||
|
child.sendline("nib route add 6 {}/128 {}".format(g_src, ll_dst))
|
||||||
|
child.sendline("nib route")
|
||||||
|
child.expect(r"{}/128 via fe80::1 dev #7".format(g_dst))
|
||||||
|
child.expect(r"{}/128 via {} dev #6".format(g_src, ll_dst))
|
||||||
|
child.sendline("nib neigh add 7 fe80::1")
|
||||||
|
child.sendline("nib neigh")
|
||||||
|
child.expect(r"fe80::1 dev #7 lladdr\s+-")
|
||||||
|
# get TAP MAC address
|
||||||
|
child.sendline("ifconfig 6")
|
||||||
|
child.expect("HWaddr: ([0-9A-F:]+)")
|
||||||
|
hwaddr = child.match.group(1)
|
||||||
|
# consume MTU for later calls of `ifconfig 7`
|
||||||
|
child.expect(r"MTU:(\d+)")
|
||||||
|
return mtu, hwaddr
|
||||||
|
|
||||||
|
|
||||||
|
def _fwd_teardown(child):
|
||||||
|
# remove route
|
||||||
|
child.sendline("nib neigh del 7 fe80::1")
|
||||||
|
child.sendline("nib route del 7 affe::/64")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv6_ext_frag_fwd_success(child, s, iface, ll_dst):
|
||||||
|
mtu, dst_mac = _fwd_setup(child, ll_dst, "beef::1", "affe::1")
|
||||||
|
payload_fit = mtu - len(IPv6() / IPv6ExtHdrFragment() / UDP())
|
||||||
|
pkt = Ether(dst=dst_mac) / IPv6(src="beef::1", dst="affe::1") / \
|
||||||
|
IPv6ExtHdrFragment(m=True, id=0x477384a9) / \
|
||||||
|
UDP(sport=1337, dport=1337) / ("x" * payload_fit)
|
||||||
|
# fill missing fields
|
||||||
|
pkt = Ether(raw(pkt))
|
||||||
|
sendp(pkt, verbose=0, iface=iface)
|
||||||
|
# check hexdump of mock device
|
||||||
|
ipv6 = pkt[IPv6]
|
||||||
|
ipv6.hlim -= 1 # the packet will have passed a hop
|
||||||
|
# segment packet as GNRC does
|
||||||
|
segments = [bytes(ipv6)[:40], bytes(ipv6.payload)]
|
||||||
|
for seg in segments:
|
||||||
|
addr = 0
|
||||||
|
for i in range(0, len(seg), 16):
|
||||||
|
bs = seg[i:i+16]
|
||||||
|
exp_str = ("{:08X}" + (" {:02X}") * len(bs)).format(addr, *bs)
|
||||||
|
child.expect_exact(exp_str)
|
||||||
|
addr += 16
|
||||||
|
_fwd_teardown(child)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv6_ext_frag_fwd_too_big(child, s, iface, ll_dst):
|
||||||
|
mtu, dst_mac = _fwd_setup(child, ll_dst, "beef::1", "affe::1")
|
||||||
|
assert(get_host_mtu(iface) > mtu)
|
||||||
|
payload_fit = get_host_mtu(iface) - len(IPv6() / IPv6ExtHdrFragment() /
|
||||||
|
UDP())
|
||||||
|
pkt = srp1(Ether(dst=dst_mac) / IPv6(src="beef::1", dst="affe::1") /
|
||||||
|
IPv6ExtHdrFragment(m=True, id=0x477384a9) /
|
||||||
|
UDP(sport=1337, dport=1337) / ("x" * payload_fit),
|
||||||
|
timeout=2, verbose=0, iface=iface)
|
||||||
|
# packet should not be fragmented further but an ICMPv6 error should be
|
||||||
|
# returned instead
|
||||||
|
assert(pkt is not None)
|
||||||
|
assert(ICMPv6PacketTooBig in pkt)
|
||||||
|
assert(IPv6ExtHdrFragment in pkt)
|
||||||
|
assert(pkt[IPv6ExtHdrFragment].id == 0x477384a9)
|
||||||
|
_fwd_teardown(child)
|
||||||
|
|
||||||
|
|
||||||
def testfunc(child):
|
def testfunc(child):
|
||||||
tap = get_bridge(os.environ["TAP"])
|
tap = get_bridge(os.environ["TAP"])
|
||||||
|
|
||||||
@ -152,12 +322,45 @@ def testfunc(child):
|
|||||||
print("." * int(child.match.group(1)), end="", flush=True)
|
print("." * int(child.match.group(1)), end="", flush=True)
|
||||||
|
|
||||||
lladdr_src = get_host_lladdr(tap)
|
lladdr_src = get_host_lladdr(tap)
|
||||||
|
|
||||||
|
def run_sock_test(func, s):
|
||||||
|
if child.logfile == sys.stdout:
|
||||||
|
func(child, s, tap, lladdr_src)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
func(child, s, tap, lladdr_src)
|
||||||
|
print(".", end="", flush=True)
|
||||||
|
except PermissionError:
|
||||||
|
print("\n\x1b[1;33mSkipping {} because of missing "
|
||||||
|
"privileges\x1b[0m".format(func.__name__))
|
||||||
|
except Exception as e:
|
||||||
|
print("FAILED")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
child.expect(r"Sending UDP test packets to port (\d+)")
|
||||||
|
|
||||||
|
port = int(child.match.group(1))
|
||||||
|
with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as s:
|
||||||
|
res = socket.getaddrinfo("{}%{}".format(lladdr_src, tap), port)
|
||||||
|
s.bind(res[0][4])
|
||||||
|
s.settimeout(.3)
|
||||||
|
run_sock_test(test_ipv6_ext_frag_shell_test_0, s)
|
||||||
|
run_sock_test(test_ipv6_ext_frag_shell_test_1, s)
|
||||||
|
run_sock_test(test_ipv6_ext_frag_send_success, s)
|
||||||
|
run_sock_test(test_ipv6_ext_frag_send_last_fragment_filled, s)
|
||||||
|
run_sock_test(test_ipv6_ext_frag_send_last_fragment_only_one_byte, s)
|
||||||
|
run_sock_test(test_ipv6_ext_frag_send_full_pktbuf, s)
|
||||||
|
run_sock_test(test_ipv6_ext_frag_fwd_success, s)
|
||||||
|
run_sock_test(test_ipv6_ext_frag_fwd_too_big, s)
|
||||||
|
|
||||||
if os.environ.get("BOARD", "") != "native":
|
if os.environ.get("BOARD", "") != "native":
|
||||||
# ethos currently can't handle the larger, rapidly sent packets by the
|
# ethos currently can't handle the larger, rapidly sent packets by the
|
||||||
# IPv6 fragmentation of the Linux Kernel
|
# IPv6 fragmentation of the Linux Kernel
|
||||||
print("SUCCESS for unittests.")
|
print("SUCCESS")
|
||||||
print("Skipping interaction tests due to ethos bug.")
|
print("Skipping datagram reception tests due to ethos bug.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# datagram reception tests
|
||||||
res = 1
|
res = 1
|
||||||
count = 0
|
count = 0
|
||||||
while res:
|
while res:
|
||||||
@ -197,4 +400,9 @@ def testfunc(child):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(run(testfunc, timeout=1, echo=False))
|
if os.geteuid() != 0:
|
||||||
|
print("\x1b[1;31mThis test requires root privileges.\n"
|
||||||
|
"It's constructing and sending Ethernet frames.\x1b[0m\n",
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
sys.exit(run(testfunc, timeout=2, echo=False))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user