diff --git a/Makefile.dep b/Makefile.dep index dd4dbcdfe4bd..1d5508bdf278 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -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 diff --git a/sys/include/net/gnrc/ipv6/ext.h b/sys/include/net/gnrc/ipv6/ext.h index 6524896d1985..ee66c48dfbfc 100644 --- a/sys/include/net/gnrc/ipv6/ext.h +++ b/sys/include/net/gnrc/ipv6/ext.h @@ -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. * diff --git a/sys/include/net/gnrc/ipv6/ext/frag.h b/sys/include/net/gnrc/ipv6/ext/frag.h new file mode 100644 index 000000000000..397e617c48ff --- /dev/null +++ b/sys/include/net/gnrc/ipv6/ext/frag.h @@ -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 + */ +#ifndef NET_GNRC_IPV6_EXT_FRAG_H +#define NET_GNRC_IPV6_EXT_FRAG_H + +#include + +#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 */ +/** @} */ diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile index 434f234ee359..d26036d5afae 100644 --- a/sys/net/gnrc/Makefile +++ b/sys/net/gnrc/Makefile @@ -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 diff --git a/sys/net/gnrc/network_layer/ipv6/ext/frag/Makefile b/sys/net/gnrc/network_layer/ipv6/ext/frag/Makefile new file mode 100644 index 000000000000..51f1ac0b6f5f --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/ext/frag/Makefile @@ -0,0 +1,3 @@ +MODULE := gnrc_ipv6_ext_frag + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/network_layer/ipv6/ext/frag/gnrc_ipv6_ext_frag.c b/sys/net/gnrc/network_layer/ipv6/ext/frag/gnrc_ipv6_ext_frag.c new file mode 100644 index 000000000000..ef1b7ea0dae0 --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/ext/frag/gnrc_ipv6_ext_frag.c @@ -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 + */ + +#include + +#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; +} + +/** @} */ diff --git a/sys/net/gnrc/network_layer/ipv6/ext/gnrc_ipv6_ext.c b/sys/net/gnrc/network_layer/ipv6/ext/gnrc_ipv6_ext.c index f7d23d9981db..2ac4c6b7cfb0 100644 --- a/sys/net/gnrc/network_layer/ipv6/ext/gnrc_ipv6_ext.c +++ b/sys/net/gnrc/network_layer/ipv6/ext/gnrc_ipv6_ext.c @@ -23,6 +23,7 @@ #include "net/gnrc/pktbuf.h" #include "net/gnrc/icmpv6/error.h" #include "net/gnrc/ipv6.h" +#include "net/gnrc/ipv6/ext/frag.h" #include "net/gnrc/ipv6/ext/rh.h" #include "net/gnrc/ipv6/ext.h" @@ -256,10 +257,12 @@ static gnrc_pktsnip_t *_demux(gnrc_pktsnip_t *pkt, unsigned protnum) break; #endif /* MODULE_GNRC_IPV6_EXT_RH */ - + case PROTNUM_IPV6_EXT_FRAG: +#ifdef MODULE_GNRC_IPV6_EXT_FRAG + return gnrc_ipv6_ext_frag_reass(pkt); +#endif /* MODULE_GNRC_IPV6_EXT_FRAG */ case PROTNUM_IPV6_EXT_HOPOPT: case PROTNUM_IPV6_EXT_DST: - case PROTNUM_IPV6_EXT_FRAG: case PROTNUM_IPV6_EXT_AH: case PROTNUM_IPV6_EXT_ESP: case PROTNUM_IPV6_EXT_MOB: diff --git a/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c b/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c index 06cc0fbbb9c5..886088b59735 100644 --- a/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c +++ b/sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c @@ -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: diff --git a/tests/gnrc_ipv6_ext_frag/Makefile b/tests/gnrc_ipv6_ext_frag/Makefile new file mode 100644 index 000000000000..68fcb1fc63b4 --- /dev/null +++ b/tests/gnrc_ipv6_ext_frag/Makefile @@ -0,0 +1,49 @@ +DEVELHELP := 1 +# name of your application +include ../Makefile.tests_common + +BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-leonardo \ + arduino-mega2560 arduino-nano arduino-uno chronos \ + i-nucleo-lrwan1 mega-xplained msb-430 msb-430h \ + nucleo-f030r8 nucleo-f031k6 nucleo-f042k6 \ + nucleo-f303k8 nucleo-f334r8 nucleo-l031k6 \ + nucleo-l053r8 stm32f0discovery stm32l0538-disco \ + telosb waspmote-pro wsn430-v1_3b wsn430-v1_4 z1 + +export TAP ?= tap0 + +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) + + USEMODULE += auto_init_gnrc_netif +endif +# Specify the mandatory networking modules for IPv6 +USEMODULE += gnrc_ipv6_router_default +USEMODULE += gnrc_icmpv6_error +USEMODULE += gnrc_pktdump +USEMODULE += gnrc_pktbuf_cmd +# IPv6 extension headers +USEMODULE += gnrc_ipv6_ext_frag +# UDP support for payload +USEMODULE += gnrc_udp +USEMODULE += od +# Add unittest framework +USEMODULE += embunit +# Add also the shell, some shell commands +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 + +include $(RIOTBASE)/Makefile.include diff --git a/tests/gnrc_ipv6_ext_frag/README.md b/tests/gnrc_ipv6_ext_frag/README.md new file mode 100644 index 000000000000..7ec3444b4af9 --- /dev/null +++ b/tests/gnrc_ipv6_ext_frag/README.md @@ -0,0 +1,36 @@ +# `gnrc_ipv6_ext_frag` test + +This test utilizes [scapy] to test the IPv6 Extension header parsing. + +It is intended to just test the fragmentation header handling and generation. +For other extension header types please provide a separate test application. + +To test, compile and flash the application to any board of your liking (since +`ethos` is used to communicate with non-native boards it really doesn't matter +as long as the application fits). + +``` +make flash +``` + +And run the tests using + +``` +sudo make test +``` + +Note that root privileges are required since `scapy` needs to construct Ethernet +frames to properly communicate over the TAP interface. + +The tests succeeds if you see the string `SUCCESS`. + +If any problems are encountered (i.e. if the test prints the sting `FAILED`), +set the echo parameter in the `run()` function at the bottom of the test script +(tests/01-run.py) to `True`. The test script will then offer a more detailed +output. + +It might be that due to `scapy`'s sniffer not picking up an expected packet +sometimes that the test application hangs for a while and then issues `FAILED`. +Just restart the test in that case. + +[scapy]: https://scapy.readthedocs.io/en/latest/ diff --git a/tests/gnrc_ipv6_ext_frag/main.c b/tests/gnrc_ipv6_ext_frag/main.c new file mode 100644 index 000000000000..a41fba7560eb --- /dev/null +++ b/tests/gnrc_ipv6_ext_frag/main.c @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Tests extension header handling of gnrc stack. + * + * @author Hauke Petersen + * @author Takuo Yonezawa + * + * @} + */ + +#include + +#include "byteorder.h" +#include "clist.h" +#include "embUnit.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 "shell.h" + +#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, \ + 0x4c, 0x8e, 0x64, 0xa1, 0x4d, 0x48, 0x19, 0x48 } +#define TEST_FRAG2 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x9f, 0x7e, 0xcb, 0x94, 0xe4, 0x63, 0xfa, 0xd9, \ + 0xb5, 0x5d, 0x75, 0x8a, 0xd5, 0xa7, 0x4d, 0xe9, \ + 0x22, 0xc2, 0x8a, 0xb9, 0x4e, 0x03, 0xe5, 0x3f } +#define TEST_FRAG3 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x6d, 0x07, 0x7c, 0xac, 0xad, 0x1f, 0x97, 0x1c, \ + 0x48, 0x96, 0x34, 0x46, 0xf9, 0xec, 0xbc } +#define TEST_PAYLOAD { 0xab, 0xcf, 0xde, 0xb8, 0x18, 0x48, 0xe3, 0x70, \ + 0x30, 0x1a, 0xba, 0x27, 0xa6, 0xa7, 0xce, 0xeb, \ + 0x4c, 0x8e, 0x64, 0xa1, 0x4d, 0x48, 0x19, 0x48, \ + 0x9f, 0x7e, 0xcb, 0x94, 0xe4, 0x63, 0xfa, 0xd9, \ + 0xb5, 0x5d, 0x75, 0x8a, 0xd5, 0xa7, 0x4d, 0xe9, \ + 0x22, 0xc2, 0x8a, 0xb9, 0x4e, 0x03, 0xe5, 0x3f, \ + 0x6d, 0x07, 0x7c, 0xac, 0xad, 0x1f, 0x97, 0x1c, \ + 0x48, 0x96, 0x34, 0x46, 0xf9, 0xec, 0xbc } +#define TEST_SRC { 0x20, 0x01, 0xdb, 0x82, 0xb5, 0xf9, 0xbe, 0x78, \ + 0xb1, 0x4d, 0xcd, 0xe8, 0xa9, 0x53, 0x54, 0xb1 } +#define TEST_DST { 0x20, 0x01, 0xdb, 0x89, 0xa3, 0x24, 0xfd, 0xab, \ + 0x29, 0x73, 0xde, 0xa4, 0xe4, 0xb1, 0xdb, 0xde } +#define TEST_ID (0x52dacb1) +#define TEST_FRAG1_OFFSET (0U) +#define TEST_FRAG2_OFFSET (24U) +#define TEST_FRAG3_OFFSET (48U) +#define TEST_PAYLOAD_LEN (21U) +#define TEST_HL (64U) + +extern int udp_cmd(int argc, char **argv); + +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 }, + { NULL, NULL, NULL } +}; + +static void tear_down_tests(void) +{ + gnrc_ipv6_ext_frag_init(); + gnrc_pktbuf_init(); +} + +static void test_ipv6_ext_frag_rbuf_get(void) +{ + static ipv6_hdr_t ipv6 = { .src = { .u8 = TEST_SRC }, + .dst = { .u8 = TEST_DST } }; + gnrc_ipv6_ext_frag_rbuf_t *rbuf = gnrc_ipv6_ext_frag_rbuf_get(&ipv6, + TEST_ID); + + TEST_ASSERT_NOT_NULL(rbuf); + TEST_ASSERT_EQUAL_INT(TEST_ID, rbuf->id); + TEST_ASSERT_MESSAGE(&ipv6 == rbuf->ipv6, "IPv6 header is not the same"); + + /* check that reassembly buffer never gets full */ + for (unsigned i = 1; i < (2 * GNRC_IPV6_EXT_FRAG_RBUF_SIZE); i++) { + rbuf = gnrc_ipv6_ext_frag_rbuf_get( + &ipv6, TEST_ID + i + ); + TEST_ASSERT_NOT_NULL(rbuf); + TEST_ASSERT_EQUAL_INT(TEST_ID + i, rbuf->id); + TEST_ASSERT_MESSAGE(&ipv6 == rbuf->ipv6, "IPv6 header is not the same"); + } +} + +static void test_ipv6_ext_frag_rbuf_free(void) +{ + static ipv6_hdr_t ipv6 = { .src = { .u8 = TEST_SRC }, + .dst = { .u8 = TEST_DST } }; + gnrc_pktsnip_t *pkt = gnrc_pktbuf_add(NULL, &ipv6, sizeof(ipv6), + GNRC_NETTYPE_IPV6); + gnrc_ipv6_ext_frag_rbuf_t *rbuf = gnrc_ipv6_ext_frag_rbuf_get(pkt->data, + TEST_ID); + + rbuf->pkt = pkt; + gnrc_ipv6_ext_frag_rbuf_free(rbuf); + TEST_ASSERT_NULL(rbuf->ipv6); + TEST_ASSERT_NULL(rbuf->limits.next); + TEST_ASSERT_EQUAL_INT(1, pkt->users); + gnrc_pktbuf_release(pkt); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_ipv6_ext_frag_rbuf_del(void) +{ + static ipv6_hdr_t ipv6 = { .src = { .u8 = TEST_SRC }, + .dst = { .u8 = TEST_DST } }; + gnrc_pktsnip_t *pkt = gnrc_pktbuf_add(NULL, &ipv6, sizeof(ipv6), + GNRC_NETTYPE_IPV6); + gnrc_ipv6_ext_frag_rbuf_t *rbuf = gnrc_ipv6_ext_frag_rbuf_get(pkt->data, + TEST_ID); + + rbuf->pkt = pkt; + gnrc_ipv6_ext_frag_rbuf_del(rbuf); + TEST_ASSERT_NULL(rbuf->pkt); + TEST_ASSERT_NULL(rbuf->ipv6); + TEST_ASSERT_NULL(rbuf->limits.next); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_ipv6_ext_frag_rbuf_gc(void) +{ + static ipv6_hdr_t ipv6 = { .src = { .u8 = TEST_SRC }, + .dst = { .u8 = TEST_DST } }; + gnrc_pktsnip_t *pkt = gnrc_pktbuf_add(NULL, &ipv6, sizeof(ipv6), + GNRC_NETTYPE_IPV6); + gnrc_ipv6_ext_frag_rbuf_t *rbuf = gnrc_ipv6_ext_frag_rbuf_get(pkt->data, + TEST_ID); + + rbuf->pkt = pkt; + gnrc_ipv6_ext_frag_rbuf_gc(); + TEST_ASSERT_NOT_NULL(rbuf->pkt); + TEST_ASSERT_MESSAGE(pkt->data == rbuf->ipv6, "IPv6 header is not the same"); + + rbuf->arrival -= GNRC_IPV6_EXT_FRAG_RBUF_TIMEOUT_US; + gnrc_ipv6_ext_frag_rbuf_gc(); + TEST_ASSERT_NULL(rbuf->pkt); + TEST_ASSERT_NULL(rbuf->ipv6); + TEST_ASSERT_NULL(rbuf->limits.next); +} + +static void test_ipv6_ext_frag_reass_in_order(void) +{ + static const ipv6_addr_t src = { .u8 = TEST_SRC }; + static const ipv6_addr_t dst = { .u8 = TEST_DST }; + static const uint8_t exp_payload[] = TEST_PAYLOAD; + static const uint8_t test_frag1[] = TEST_FRAG1; + static const uint8_t test_frag2[] = TEST_FRAG2; + static const uint8_t test_frag3[] = TEST_FRAG3; + gnrc_pktsnip_t *ipv6_snip = gnrc_ipv6_hdr_build(NULL, &src, &dst); + gnrc_pktsnip_t *pkt = gnrc_pktbuf_add(ipv6_snip, test_frag1, + sizeof(test_frag1), + GNRC_NETTYPE_UNDEF); + ipv6_hdr_t *ipv6 = ipv6_snip->data; + ipv6_ext_frag_t *frag = pkt->data; + gnrc_ipv6_ext_frag_rbuf_t *rbuf; + gnrc_ipv6_ext_frag_limits_t *ptr; + + ipv6->nh = PROTNUM_IPV6_EXT_FRAG; + ipv6->hl = TEST_HL; + ipv6->len = byteorder_htons(pkt->size); + frag->nh = PROTNUM_UDP; + frag->resv = 0U; + ipv6_ext_frag_set_offset(frag, TEST_FRAG1_OFFSET); + ipv6_ext_frag_set_more(frag); + frag->id = byteorder_htonl(TEST_ID); + + /* receive 1st fragment */ + TEST_ASSERT_NULL(gnrc_ipv6_ext_frag_reass(pkt)); + TEST_ASSERT_NOT_NULL((rbuf = gnrc_ipv6_ext_frag_rbuf_get(ipv6, TEST_ID))); + TEST_ASSERT_NOT_NULL(rbuf->pkt); + TEST_ASSERT_EQUAL_INT(sizeof(test_frag1) - sizeof(ipv6_ext_frag_t), + rbuf->pkt->size); + TEST_ASSERT_MESSAGE(ipv6 == rbuf->ipv6, "IPv6 header is not the same"); + TEST_ASSERT_EQUAL_INT(TEST_ID, rbuf->id); + TEST_ASSERT(!rbuf->last); + ptr = (gnrc_ipv6_ext_frag_limits_t *)rbuf->limits.next; + TEST_ASSERT_NOT_NULL(ptr); + ptr = ptr->next; + TEST_ASSERT_NOT_NULL(ptr); + TEST_ASSERT_EQUAL_INT(0, ptr->start); + TEST_ASSERT_EQUAL_INT(TEST_FRAG2_OFFSET / 8, ptr->end); + TEST_ASSERT(((clist_node_t *)ptr) == rbuf->limits.next); + TEST_ASSERT(memcmp(exp_payload, rbuf->pkt->data, rbuf->pkt->size) == 0); + + /* prepare 2nd fragment */ + ipv6_snip = gnrc_ipv6_hdr_build(NULL, &src, &dst); + pkt = gnrc_pktbuf_add(ipv6_snip, test_frag2, + sizeof(test_frag2), + GNRC_NETTYPE_UNDEF); + ipv6 = ipv6_snip->data; + frag = pkt->data; + + ipv6->nh = PROTNUM_IPV6_EXT_FRAG; + ipv6->hl = TEST_HL; + ipv6->len = byteorder_htons(pkt->size); + frag->nh = PROTNUM_UDP; + frag->resv = 0U; + ipv6_ext_frag_set_offset(frag, TEST_FRAG2_OFFSET); + ipv6_ext_frag_set_more(frag); + frag->id = byteorder_htonl(TEST_ID); + + /* receive 2nd fragment */ + TEST_ASSERT_NULL(gnrc_ipv6_ext_frag_reass(pkt)); + TEST_ASSERT_NOT_NULL(rbuf->pkt); + TEST_ASSERT_EQUAL_INT(sizeof(test_frag1) + sizeof(test_frag2) - + (2 * sizeof(ipv6_ext_frag_t)), + rbuf->pkt->size); + TEST_ASSERT_EQUAL_INT(TEST_ID, rbuf->id); + TEST_ASSERT(!rbuf->last); + ptr = (gnrc_ipv6_ext_frag_limits_t *)rbuf->limits.next; + TEST_ASSERT_NOT_NULL(ptr); + ptr = ptr->next; + TEST_ASSERT_NOT_NULL(ptr); + TEST_ASSERT_EQUAL_INT(0, ptr->start); + TEST_ASSERT_EQUAL_INT(TEST_FRAG2_OFFSET / 8, ptr->end); + TEST_ASSERT_NOT_NULL(ptr->next); + ptr = ptr->next; + TEST_ASSERT_NOT_NULL(ptr); + TEST_ASSERT_EQUAL_INT(TEST_FRAG2_OFFSET / 8, ptr->start); + TEST_ASSERT_EQUAL_INT(TEST_FRAG3_OFFSET / 8, ptr->end); + TEST_ASSERT(((clist_node_t *)ptr) == rbuf->limits.next); + TEST_ASSERT(memcmp(exp_payload, rbuf->pkt->data, rbuf->pkt->size) == 0); + + /* prepare 3rd fragment */ + ipv6_snip = gnrc_ipv6_hdr_build(NULL, &src, &dst); + pkt = gnrc_pktbuf_add(ipv6_snip, test_frag3, + sizeof(test_frag3), + GNRC_NETTYPE_UNDEF); + ipv6 = ipv6_snip->data; + frag = pkt->data; + + ipv6->nh = PROTNUM_IPV6_EXT_FRAG; + ipv6->hl = TEST_HL; + ipv6->len = byteorder_htons(pkt->size); + frag->nh = PROTNUM_UDP; + frag->resv = 0U; + ipv6_ext_frag_set_offset(frag, TEST_FRAG3_OFFSET); + frag->id = byteorder_htonl(TEST_ID); + + /* receive 3rd fragment */ + TEST_ASSERT_NOT_NULL((pkt = gnrc_ipv6_ext_frag_reass(pkt))); + /* reassembly buffer should be deleted */ + TEST_ASSERT_NULL(rbuf->ipv6); + TEST_ASSERT_EQUAL_INT(sizeof(exp_payload), pkt->size); + TEST_ASSERT(memcmp(exp_payload, pkt->data, pkt->size) == 0); + TEST_ASSERT_NOT_NULL(pkt->next); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_IPV6, pkt->next->type); + ipv6 = pkt->next->data; + TEST_ASSERT_EQUAL_INT(PROTNUM_UDP, ipv6->nh); + TEST_ASSERT_EQUAL_INT(pkt->size, byteorder_ntohs(ipv6->len)); + TEST_ASSERT_NULL(pkt->next->next); + gnrc_pktbuf_release(pkt); + /* and packet handled (and thus released) */ + gnrc_pktbuf_is_empty(); +} + +static void test_ipv6_ext_frag_reass_out_of_order(void) +{ + static const ipv6_addr_t src = { .u8 = TEST_SRC }; + static const ipv6_addr_t dst = { .u8 = TEST_DST }; + static const uint8_t exp_payload[] = TEST_PAYLOAD; + static const uint8_t test_frag1[] = TEST_FRAG1; + static const uint8_t test_frag2[] = TEST_FRAG2; + static const uint8_t test_frag3[] = TEST_FRAG3; + gnrc_pktsnip_t *ipv6_snip = gnrc_ipv6_hdr_build(NULL, &src, &dst); + gnrc_pktsnip_t *pkt = gnrc_pktbuf_add(ipv6_snip, test_frag3, + sizeof(test_frag3), + GNRC_NETTYPE_UNDEF); + ipv6_hdr_t *ipv6 = ipv6_snip->data; + ipv6_ext_frag_t *frag = pkt->data; + gnrc_ipv6_ext_frag_rbuf_t *rbuf; + gnrc_ipv6_ext_frag_limits_t *ptr; + + + ipv6->nh = PROTNUM_IPV6_EXT_FRAG; + ipv6->hl = TEST_HL; + ipv6->len = byteorder_htons(pkt->size); + frag->nh = PROTNUM_UDP; + frag->resv = 0U; + ipv6_ext_frag_set_offset(frag, TEST_FRAG3_OFFSET); + frag->id = byteorder_htonl(TEST_ID); + + /* receive 3rd fragment */ + TEST_ASSERT_NULL(gnrc_ipv6_ext_frag_reass(pkt)); + TEST_ASSERT_NOT_NULL((rbuf = gnrc_ipv6_ext_frag_rbuf_get(ipv6, TEST_ID))); + TEST_ASSERT_NOT_NULL(rbuf->pkt); + TEST_ASSERT_EQUAL_INT(sizeof(exp_payload), rbuf->pkt->size); + TEST_ASSERT_EQUAL_INT(TEST_ID, rbuf->id); + TEST_ASSERT(rbuf->last); + ptr = (gnrc_ipv6_ext_frag_limits_t *)rbuf->limits.next; + TEST_ASSERT_NOT_NULL(ptr); + ptr = ptr->next; + TEST_ASSERT_NOT_NULL(ptr); + TEST_ASSERT_EQUAL_INT(TEST_FRAG3_OFFSET / 8, ptr->start); + TEST_ASSERT_EQUAL_INT(sizeof(exp_payload) / 8, ptr->end); + TEST_ASSERT(((clist_node_t *)ptr) == rbuf->limits.next); + TEST_ASSERT(memcmp(&exp_payload[TEST_FRAG3_OFFSET], + (uint8_t *)rbuf->pkt->data + TEST_FRAG3_OFFSET, + rbuf->pkt->size - TEST_FRAG3_OFFSET) == 0); + + /* prepare 2nd fragment */ + ipv6_snip = gnrc_ipv6_hdr_build(NULL, &src, &dst); + pkt = gnrc_pktbuf_add(ipv6_snip, test_frag2, + sizeof(test_frag2), + GNRC_NETTYPE_UNDEF); + ipv6 = ipv6_snip->data; + frag = pkt->data; + + ipv6->nh = PROTNUM_IPV6_EXT_FRAG; + ipv6->hl = TEST_HL; + ipv6->len = byteorder_htons(pkt->size); + frag->nh = PROTNUM_UDP; + frag->resv = 0U; + ipv6_ext_frag_set_offset(frag, TEST_FRAG2_OFFSET); + ipv6_ext_frag_set_more(frag); + frag->id = byteorder_htonl(TEST_ID); + + /* receive 2nd fragment */ + TEST_ASSERT_NULL(gnrc_ipv6_ext_frag_reass(pkt)); + TEST_ASSERT_NOT_NULL(rbuf->pkt); + TEST_ASSERT_EQUAL_INT(sizeof(exp_payload), rbuf->pkt->size); + TEST_ASSERT(rbuf->last); + ptr = (gnrc_ipv6_ext_frag_limits_t *)rbuf->limits.next; + TEST_ASSERT_NOT_NULL(ptr); + ptr = ptr->next; + TEST_ASSERT_NOT_NULL(ptr); + TEST_ASSERT_EQUAL_INT(TEST_FRAG2_OFFSET / 8, ptr->start); + TEST_ASSERT_EQUAL_INT(TEST_FRAG3_OFFSET / 8, ptr->end); + ptr = ptr->next; + TEST_ASSERT_NOT_NULL(ptr); + TEST_ASSERT_EQUAL_INT(TEST_FRAG3_OFFSET / 8, ptr->start); + TEST_ASSERT_EQUAL_INT(sizeof(exp_payload) / 8, ptr->end); + TEST_ASSERT_NOT_NULL(ptr->next); + TEST_ASSERT(((clist_node_t *)ptr) == rbuf->limits.next); + TEST_ASSERT(memcmp(&exp_payload[TEST_FRAG2_OFFSET], + (uint8_t *)rbuf->pkt->data + TEST_FRAG2_OFFSET, + rbuf->pkt->size - TEST_FRAG2_OFFSET) == 0); + + /* prepare 1st fragment */ + ipv6_snip = gnrc_ipv6_hdr_build(NULL, &src, &dst); + pkt = gnrc_pktbuf_add(ipv6_snip, test_frag1, + sizeof(test_frag2), + GNRC_NETTYPE_UNDEF); + ipv6 = ipv6_snip->data; + frag = pkt->data; + + ipv6->nh = PROTNUM_IPV6_EXT_FRAG; + ipv6->hl = TEST_HL; + ipv6->len = byteorder_htons(pkt->size); + frag->nh = PROTNUM_UDP; + frag->resv = 0U; + ipv6_ext_frag_set_offset(frag, TEST_FRAG1_OFFSET); + ipv6_ext_frag_set_more(frag); + frag->id = byteorder_htonl(TEST_ID); + /* receive 1st fragment */ + TEST_ASSERT_NOT_NULL((pkt = gnrc_ipv6_ext_frag_reass(pkt))); + /* reassembly buffer should be deleted */ + TEST_ASSERT_NULL(rbuf->ipv6); + TEST_ASSERT_EQUAL_INT(sizeof(exp_payload), pkt->size); + TEST_ASSERT(memcmp(exp_payload, pkt->data, pkt->size) == 0); + TEST_ASSERT_NOT_NULL(pkt->next); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_IPV6, pkt->next->type); + ipv6 = pkt->next->data; + TEST_ASSERT_EQUAL_INT(PROTNUM_UDP, ipv6->nh); + TEST_ASSERT_EQUAL_INT(pkt->size, byteorder_ntohs(ipv6->len)); + TEST_ASSERT_NULL(pkt->next->next); + gnrc_pktbuf_release(pkt); + /* and packet handled (and thus released) */ + gnrc_pktbuf_is_empty(); +} + +static void test_ipv6_ext_frag_reass_one_frag(void) +{ + static const ipv6_addr_t src = { .u8 = TEST_SRC }; + static const ipv6_addr_t dst = { .u8 = TEST_DST }; + static const uint8_t exp_payload[] = TEST_PAYLOAD; + static const uint8_t test_frag1[] = TEST_FRAG1; + gnrc_pktsnip_t *ipv6_snip = gnrc_ipv6_hdr_build(NULL, &src, &dst); + gnrc_pktsnip_t *pkt = gnrc_pktbuf_add(ipv6_snip, test_frag1, + sizeof(test_frag1), + GNRC_NETTYPE_UNDEF); + ipv6_hdr_t *ipv6 = ipv6_snip->data; + ipv6_ext_frag_t *frag = pkt->data; + + ipv6->nh = PROTNUM_IPV6_EXT_FRAG; + ipv6->hl = TEST_HL; + ipv6->len = byteorder_htons(pkt->size); + frag->nh = PROTNUM_UDP; + frag->resv = 0U; + ipv6_ext_frag_set_offset(frag, TEST_FRAG1_OFFSET); + frag->id = byteorder_htonl(TEST_ID); + + /* receive 1st fragment */ + TEST_ASSERT_NOT_NULL((pkt = gnrc_ipv6_ext_frag_reass(pkt))); + /* reassembly buffer already consumed */ + TEST_ASSERT_EQUAL_INT(sizeof(test_frag1) - sizeof(ipv6_ext_frag_t), + pkt->size); + TEST_ASSERT(memcmp(exp_payload, pkt->data, pkt->size) == 0); + TEST_ASSERT_NOT_NULL(pkt->next); + TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_IPV6, pkt->next->type); + ipv6 = pkt->next->data; + TEST_ASSERT_EQUAL_INT(PROTNUM_UDP, ipv6->nh); + TEST_ASSERT_EQUAL_INT(pkt->size, byteorder_ntohs(ipv6->len)); + TEST_ASSERT_NULL(pkt->next->next); + gnrc_pktbuf_release(pkt); + /* and packet handled (and thus released) */ + gnrc_pktbuf_is_empty(); +} + +static void run_unittests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_ipv6_ext_frag_rbuf_get), + new_TestFixture(test_ipv6_ext_frag_rbuf_free), + new_TestFixture(test_ipv6_ext_frag_rbuf_del), + new_TestFixture(test_ipv6_ext_frag_rbuf_gc), + new_TestFixture(test_ipv6_ext_frag_reass_in_order), + new_TestFixture(test_ipv6_ext_frag_reass_out_of_order), + new_TestFixture(test_ipv6_ext_frag_reass_one_frag), + }; + + EMB_UNIT_TESTCALLER(ipv6_ext_frag_tests, NULL, tear_down_tests, fixtures); + TESTS_START(); + TESTS_RUN((Test *)&ipv6_ext_frag_tests); + TESTS_END(); +} + +int main(void) +{ + run_unittests(); + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + return 0; +} diff --git a/tests/gnrc_ipv6_ext_frag/tests/01-run.py b/tests/gnrc_ipv6_ext_frag/tests/01-run.py new file mode 100755 index 000000000000..cfde1c861589 --- /dev/null +++ b/tests/gnrc_ipv6_ext_frag/tests/01-run.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2018 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. + +import re +import os +import pexpect +import socket +import sys +import subprocess +import time + +from scapy.all import Ether, IPv6, IPv6ExtHdrFragment, sendp +from testrunner import run + + +EXT_HDR_NH = { + IPv6ExtHdrFragment: 44, + } + + +def pktbuf_empty(child): + child.sendline("pktbuf") + child.expect(r"packet buffer: first byte: (?P0x[0-9a-fA-F]+), " + r"last byte: 0x[0-9a-fA-F]+ \(size: (?P\d+)\)") + first_byte = child.match.group("first_byte") + size = child.match.group("size") + child.expect( + r"~ unused: {} \(next: (\(nil\)|0), size: {}\) ~".format( + first_byte, size)) + + +def pktbuf_size(child): + child.sendline("pktbuf") + child.expect(r"packet buffer: first byte: (?P0x[0-9a-fA-F]+), " + r"last byte: 0x[0-9a-fA-F]+ \(size: (?P\d+)\)") + size = child.match.group("size") + return int(size) + + +def start_udp_server(child, port): + child.sendline("udp server start {}".format(port)) + child.expect_exact("Success: started UDP server on port {}".format(port)) + + +def stop_udp_server(child): + child.sendline("udp server stop") + # either way: it is stopped + child.expect(["Success: stopped UDP server", + "Error: server was not running"]) + + +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(): + m = re.search(pattern, line) + if m is not None: + return m.group(res_group) + return None + + +def get_bridge(tap): + res = check_and_search_output( + ["bridge", "link"], + r"{}.+master\s+(?P[^\s]+)".format(tap), + "master" + ) + return tap if res is None else res + + +def get_host_lladdr(tap): + res = check_and_search_output( + ["ip", "addr", "show", "dev", tap, "scope", "link"], + r"inet6 (?P[0-9A-Fa-f:]+)/64", + "lladdr" + ) + if res is None: + raise AssertionError( + "Can't find host link-local address on interface {}".format(tap) + ) + else: + return res + + +def get_host_mtu(tap): + res = check_and_search_output( + ["ip", "link", "show", tap], + r"mtu (?P1500)", + "mtu" + ) + if res is None: + raise AssertionError( + "Can't find host link-local address on interface {}".format(tap) + ) + else: + return int(res) + + +def test_reass_successful_udp(child, iface, hw_dst, ll_dst, ll_src): + port = 1337 + mtu = get_host_mtu(iface) + byte_max = 0xff + payload_len = (byte_max * ((mtu // byte_max) + 1)) + if not (mtu % byte_max): + payload_len += 1 + start_udp_server(child, port) + with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as s: + res = socket.getaddrinfo("{}%{}".format(ll_src, iface), None) + s.bind(res[0][4]) + s.sendto(bytes(i for i in range(byte_max)) * (payload_len // byte_max), + (ll_dst, port)) + child.expect( + "~~ SNIP 0 - size: {} byte, type: NETTYPE_UNDEF \(\d+\)" + .format(payload_len) + ) + # 4 snips: payload, UDP header, IPv6 header, netif header + # (fragmentation header was removed) + child.expect( + "~~ PKT - 4 snips, total size: (\d+) byte" + ) + size = int(child.match.group(1)) + # 40 = IPv6 header length; 8 = UDP header length + # >= since netif header also has a length + assert size >= (payload_len + 40 + 8) + stop_udp_server(child) + pktbuf_empty(child) + + +def test_reass_too_short_header(child, iface, hw_dst, ll_dst, ll_src): + sendp(Ether(dst=hw_dst) / IPv6(dst=ll_dst, src=ll_src, + nh=EXT_HDR_NH[IPv6ExtHdrFragment]) / "\x11", + iface=iface, verbose=0) + pktbuf_empty(child) + + +def test_reass_offset_too_large(child, iface, hw_dst, ll_dst, ll_src): + size = pktbuf_size(child) + sendp(Ether(dst=hw_dst) / IPv6(dst=ll_dst, src=ll_src) / + IPv6ExtHdrFragment(offset=((size * 2) // 8)) / "x" * 128, + iface=iface, verbose=0) + pktbuf_empty(child) + + +def testfunc(child): + tap = get_bridge(os.environ["TAP"]) + + child.expect(r"OK \((\d+) tests\)") # wait for and check result of unittests + print("." * int(child.match.group(1)), end="", flush=True) + + lladdr_src = get_host_lladdr(tap) + 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.") + return + res = 1 + count = 0 + while res: + # check `ifconfig` and also get addresses from it until + # link-local address becomes valid + time.sleep(1) + child.sendline("ifconfig") + child.expect("HWaddr: (?P[A-Fa-f:0-9]+)") + hwaddr_dst = child.match.group("hwaddr").lower() + res = child.expect([ + r"(?Pfe80::[A-Fa-f:0-9]+)\s+scope:\s+local\s+VAL", + pexpect.TIMEOUT + ]) + count += 1 + if res and (count > 5): + raise pexpect.TIMEOUT("Link-local address did not become valid") + lladdr_dst = child.match.group("lladdr").lower() + + def run(func): + if child.logfile == sys.stdout: + func(child, tap, hwaddr_dst, lladdr_dst, lladdr_src) + else: + try: + func(child, tap, hwaddr_dst, lladdr_dst, 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 + + run(test_reass_successful_udp) + run(test_reass_too_short_header) + run(test_reass_offset_too_large) + print("SUCCESS") + + +if __name__ == "__main__": + sys.exit(run(testfunc, timeout=1, echo=False)) diff --git a/tests/gnrc_ipv6_ext_frag/udp.c b/tests/gnrc_ipv6_ext_frag/udp.c new file mode 100644 index 000000000000..2f823f170439 --- /dev/null +++ b/tests/gnrc_ipv6_ext_frag/udp.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2017 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. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * + * @author Martine Lenders + * @} + */ + +#include +#include + +#include "net/gnrc.h" +#include "net/gnrc/ipv6.h" +#include "net/gnrc/netif.h" +#include "net/gnrc/netif/hdr.h" +#include "net/gnrc/udp.h" +#include "net/gnrc/pktdump.h" +#include "timex.h" +#include "utlist.h" +#include "xtimer.h" + +static gnrc_netreg_entry_t server = GNRC_NETREG_ENTRY_INIT_PID(0, KERNEL_PID_UNDEF); + +static uint8_t send_count = 0; + +static void send(char *addr_str, char *port_str, char *data_len_str, unsigned int num, + unsigned int delay) +{ + int iface; + char *conversion_end; + uint16_t port; + ipv6_addr_t addr; + size_t data_len; + + /* get interface, if available */ + iface = ipv6_addr_split_iface(addr_str); + if ((iface < 0) && (gnrc_netif_numof() == 1)) { + iface = gnrc_netif_iter(NULL)->pid; + } + /* parse destination address */ + if (ipv6_addr_from_str(&addr, addr_str) == NULL) { + puts("Error: unable to parse destination address"); + return; + } + /* parse port */ + port = atoi(port_str); + if (port == 0) { + puts("Error: unable to parse destination port"); + return; + } + + data_len = strtoul(data_len_str, &conversion_end, 0); + if (*conversion_end != '\0') { + puts("Error: unable to parse data_len"); + return; + } + + for (unsigned int i = 0; i < num; i++) { + gnrc_pktsnip_t *payload, *udp, *ip; + /* allocate payload */ + payload = gnrc_pktbuf_add(NULL, NULL, data_len, GNRC_NETTYPE_UNDEF); + if (payload == NULL) { + puts("Error: unable to copy data to packet buffer"); + return; + } + memset(payload->data, send_count++, data_len); + /* allocate UDP header, set source port := destination port */ + udp = gnrc_udp_hdr_build(payload, port, port); + if (udp == NULL) { + puts("Error: unable to allocate UDP header"); + gnrc_pktbuf_release(payload); + return; + } + /* allocate IPv6 header */ + ip = gnrc_ipv6_hdr_build(udp, NULL, &addr); + if (ip == NULL) { + puts("Error: unable to allocate IPv6 header"); + gnrc_pktbuf_release(udp); + return; + } + /* add netif header, if interface was given */ + if (iface > 0) { + gnrc_pktsnip_t *netif = gnrc_netif_hdr_build(NULL, 0, NULL, 0); + + ((gnrc_netif_hdr_t *)netif->data)->if_pid = (kernel_pid_t)iface; + LL_PREPEND(ip, netif); + } + /* send packet */ + if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP, GNRC_NETREG_DEMUX_CTX_ALL, ip)) { + puts("Error: unable to locate UDP thread"); + gnrc_pktbuf_release(ip); + return; + } + /* access to `payload` was implicitly given up with the send operation above + * => use original variable for output */ + printf("Success: send %u byte to [%s]:%u\n", (unsigned)data_len, addr_str, + port); + xtimer_usleep(delay); + } +} + +static void start_server(char *port_str) +{ + uint16_t port; + + /* check if server is already running */ + if (server.target.pid != KERNEL_PID_UNDEF) { + printf("Error: server already running on port %" PRIu32 "\n", + server.demux_ctx); + return; + } + /* parse port */ + port = atoi(port_str); + if (port == 0) { + puts("Error: invalid port specified"); + return; + } + /* register server to receive messages from given port */ + gnrc_netreg_entry_init_pid(&server, port, gnrc_pktdump_pid); + gnrc_netreg_register(GNRC_NETTYPE_UDP, &server); + printf("Success: started UDP server on port %" PRIu16 "\n", port); +} + +static void stop_server(void) +{ + /* check if server is running at all */ + if (server.target.pid == KERNEL_PID_UNDEF) { + printf("Error: server was not running\n"); + return; + } + /* stop server */ + gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &server); + gnrc_netreg_entry_init_pid(&server, 0, KERNEL_PID_UNDEF); + puts("Success: stopped UDP server"); +} + +int udp_cmd(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: %s [send|server]\n", argv[0]); + return 1; + } + + if (strcmp(argv[1], "send") == 0) { + uint32_t num = 1; + uint32_t delay = 1000000LU; + if (argc < 5) { + printf("usage: %s send [ []]\n", + argv[0]); + return 1; + } + if (argc > 5) { + num = atoi(argv[5]); + } + if (argc > 6) { + delay = atoi(argv[6]); + } + send(argv[2], argv[3], argv[4], num, delay); + } + else if (strcmp(argv[1], "server") == 0) { + if (argc < 3) { + printf("usage: %s server [start|stop]\n", argv[0]); + return 1; + } + if (strcmp(argv[2], "start") == 0) { + if (argc < 4) { + printf("usage %s server start \n", argv[0]); + return 1; + } + start_server(argv[3]); + } + else if (strcmp(argv[2], "stop") == 0) { + stop_server(); + } + else { + puts("error: invalid command"); + } + } + else { + puts("error: invalid command"); + } + return 0; +}