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:
parent
018c15ae0c
commit
c2c3216c16
@ -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
|
||||
|
||||
/** @} **/
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user