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

gnrc_ipv6_ext_frag: Initial import of IPv6 fragmentation

This commit is contained in:
Martine Lenders 2019-05-23 21:12:11 +02:00 committed by Martine S. Lenders
parent 018c15ae0c
commit c2c3216c16
4 changed files with 375 additions and 1 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"
@ -34,7 +35,22 @@ extern "C" {
/**
* @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
@ -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,16 @@ static bool _safe_fill_ipv6_hdr(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt,
}
/* functions for sending */
#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)