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:
Martine Lenders 2019-09-17 19:27:38 +02:00 committed by GitHub
commit 5631b698db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 836 additions and 18 deletions

View File

@ -45,6 +45,17 @@ extern "C" {
* @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
*
@ -76,6 +87,7 @@ extern "C" {
#ifndef GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US
#define GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US (10U * US_PER_SEC)
#endif
/** @} **/
/**

View File

@ -20,6 +20,7 @@
#ifndef NET_GNRC_IPV6_EXT_FRAG_H
#define NET_GNRC_IPV6_EXT_FRAG_H
#include <stdbool.h>
#include <stdint.h>
#include "clist.h"
@ -36,6 +37,21 @@ extern "C" {
*/
#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
* buffer
@ -47,6 +63,18 @@ typedef struct gnrc_ipv6_ext_frag_limits {
* fragment */
} 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
*/
@ -71,6 +99,26 @@ typedef struct {
*/
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
*

View File

@ -14,12 +14,18 @@
*/
#include <assert.h>
#include <stdbool.h>
#include "byteorder.h"
#include "net/ipv6/ext/frag.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/frag.h"
#include "net/gnrc/nettype.h"
#include "net/gnrc/pktbuf.h"
#include "random.h"
#include "sched.h"
#include "xtimer.h"
@ -28,12 +34,20 @@
#define ENABLE_DEBUG (0)
#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_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 };
/**
* @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 {
FRAG_LIMITS_NEW = 0, /**< limits are not present and do not overlap */
FRAG_LIMITS_DUPLICATE, /**< fragment limits are already present */
@ -46,11 +60,289 @@ void gnrc_ipv6_ext_frag_init(void)
#ifdef TEST_SUITES
memset(_rbuf, 0, sizeof(_rbuf));
#endif
_last_id = random_uint32();
for (unsigned i = 0; i < GNRC_IPV6_EXT_FRAG_LIMITS_POOL_SIZE; 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

View File

@ -73,6 +73,10 @@ static void _receive(gnrc_pktsnip_t *pkt);
* prep_hdr: prepare header for sending (call to _fill_ipv6_hdr()), otherwise
* assume it is already prepared */
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 */
static void *_event_loop(void *args);
@ -212,6 +216,14 @@ static void *_event_loop(void *args)
case GNRC_IPV6_EXT_FRAG_RBUF_GC:
gnrc_ipv6_ext_frag_rbuf_gc();
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 */
case GNRC_IPV6_NIB_SND_UC_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 */
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,
gnrc_netif_t *netif, ipv6_hdr_t *ipv6_hdr,
uint8_t netif_hdr_flags)
@ -457,6 +501,11 @@ static void _send_unicast(gnrc_pktsnip_t *pkt, bool prep_hdr,
netif_hdr_flags)) == NULL) {
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",
netif->pid);
/* 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,
bool prep_hdr,
gnrc_netif_t *netif,
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) {
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);
#ifdef MODULE_NETSTATS_IPV6
netif->ipv6.stats.tx_mcast_count++;
@ -528,12 +583,12 @@ static void _send_multicast(gnrc_pktsnip_t *pkt, bool prep_hdr,
return;
}
}
_send_multicast_over_iface(pkt, netif, netif_hdr_flags);
_send_multicast_over_iface(pkt, prep_hdr, netif, netif_hdr_flags);
}
}
else {
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 */
@ -547,7 +602,7 @@ static void _send_multicast(gnrc_pktsnip_t *pkt, bool 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 */
}

View File

@ -3,12 +3,17 @@ DEVELHELP := 1
include ../Makefile.tests_common
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 \
nucleo-f030r8 nucleo-f031k6 nucleo-f042k6 \
nucleo-f070rb nucleo-f072rb nucleo-f302r8 \
nucleo-f303k8 nucleo-f334r8 nucleo-l031k6 \
nucleo-l053r8 stm32f0discovery stm32l0538-disco \
telosb waspmote-pro wsn430-v1_3b wsn430-v1_4 z1
nucleo-l053r8 saml10-xpro saml11-xpro \
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
@ -16,15 +21,22 @@ CFLAGS += -DOUTPUT=TEXT
CFLAGS += -DTEST_SUITES="gnrc_ipv6_ext_frag"
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))
USEMODULE += netdev_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
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
USEMODULE += gnrc_ipv6_router_default
USEMODULE += gnrc_icmpv6_error
@ -42,8 +54,13 @@ USEMODULE += shell
USEMODULE += shell_commands
USEMODULE += ps
# native requires sudo for the `scapy` tests, but those are not executed for
# non-native boards
TEST_ON_CI_BLACKLIST += native
# The test requires some setup and to be run as root
# So it cannot currently be run
TEST_ON_CI_BLACKLIST += all
.PHONY: ethos
ethos:
$(Q)env -u CC -u CFLAGS make -C $(RIOTTOOLS)/ethos
include $(RIOTBASE)/Makefile.include

View File

@ -20,18 +20,31 @@
*/
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "byteorder.h"
#include "clist.h"
#include "embUnit.h"
#include "net/ipv6/addr.h"
#include "net/ipv6/ext/frag.h"
#include "net/protnum.h"
#include "net/gnrc.h"
#include "net/gnrc/ipv6/ext.h"
#include "net/gnrc/ipv6/ext/frag.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 "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, \
0xab, 0xcf, 0xde, 0xb8, 0x18, 0x48, 0xe3, 0x70, \
0x30, 0x1a, 0xba, 0x27, 0xa6, 0xa7, 0xce, 0xeb, \
@ -63,10 +76,18 @@
#define TEST_HL (64U)
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 const shell_command_t shell_commands[] = {
{ "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 }
};
@ -443,9 +464,174 @@ static void run_unittests(void)
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)
{
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();
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(&eth_netif->ipv6.addrs[i])) {
local_addr = &eth_netif->ipv6.addrs[i];
}
}
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
return 0;
}

View File

@ -14,10 +14,14 @@ import sys
import subprocess
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
RECV_BUFSIZE = 2 * 1500
TEST_SAMPLE = b"This is a test. Failure might sometimes be an option, but " \
b"not today. "
EXT_HDR_NH = {
IPv6ExtHdrFragment: 44,
}
@ -54,6 +58,13 @@ def stop_udp_server(child):
"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):
output = subprocess.check_output(cmd, *args, **kwargs).decode("utf-8")
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)
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):
tap = get_bridge(os.environ["TAP"])
@ -152,12 +322,45 @@ def testfunc(child):
print("." * int(child.match.group(1)), end="", flush=True)
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":
# ethos currently can't handle the larger, rapidly sent packets by the
# IPv6 fragmentation of the Linux Kernel
print("SUCCESS for unittests.")
print("Skipping interaction tests due to ethos bug.")
print("SUCCESS")
print("Skipping datagram reception tests due to ethos bug.")
return
# datagram reception tests
res = 1
count = 0
while res:
@ -197,4 +400,9 @@ def testfunc(child):
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))