589 lines
14 KiB
C
589 lines
14 KiB
C
/*
|
|
* Copyright (c) 2016 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief 802.15.4 6LoWPAN fragment handler implementation
|
|
*/
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(net_ieee802154_6lo_fragment, CONFIG_NET_L2_IEEE802154_LOG_LEVEL);
|
|
|
|
#include "6lo.h"
|
|
#include "6lo_private.h"
|
|
#include "ieee802154_6lo_fragment.h"
|
|
#include "net_private.h"
|
|
|
|
#include <errno.h>
|
|
|
|
#include <zephyr/net/net_core.h>
|
|
#include <zephyr/net/net_if.h>
|
|
#include <zephyr/net/net_pkt.h>
|
|
#include <zephyr/net/net_stats.h>
|
|
#include <zephyr/net/udp.h>
|
|
|
|
#define NET_FRAG_DISPATCH_MASK 0xF8
|
|
#define NET_FRAG_OFFSET_POS (NET_6LO_FRAG_DATAGRAM_SIZE_LEN + NET_6LO_FRAG_DATAGRAM_OFFSET_LEN)
|
|
|
|
#define BUF_TIMEOUT K_MSEC(50)
|
|
|
|
#define FRAG_REASSEMBLY_TIMEOUT K_SECONDS(CONFIG_NET_L2_IEEE802154_REASSEMBLY_TIMEOUT)
|
|
#define REASS_CACHE_SIZE CONFIG_NET_L2_IEEE802154_FRAGMENT_REASS_CACHE_SIZE
|
|
|
|
/**
|
|
* Reassemble cache : Depends on cache size it used for reassemble
|
|
* IPv6 packets simultaneously.
|
|
*/
|
|
struct frag_cache {
|
|
struct k_work_delayable timer; /* Reassemble timer */
|
|
struct net_pkt *pkt; /* Reassemble packet */
|
|
uint16_t size; /* Datagram size */
|
|
uint16_t tag; /* Datagram tag */
|
|
bool used;
|
|
};
|
|
|
|
static struct frag_cache cache[REASS_CACHE_SIZE];
|
|
|
|
/**
|
|
* RFC 4944, section 5.3
|
|
* If an entire payload (e.g., IPv6) datagram fits within a single 802.15.4
|
|
* frame, it is unfragmented and the LoWPAN encapsulation should not contain
|
|
* a fragmentation header. If the datagram does not fit within a single
|
|
* IEEE 802.15.4 frame, it SHALL be broken into link fragments. As the
|
|
* fragment offset can only express multiples of eight bytes, all link
|
|
* fragments for a datagram except the last one MUST be multiples of eight
|
|
* bytes in length.
|
|
*
|
|
* RFC 7668, section 3 (IPv6 over Bluetooth Low Energy)
|
|
* Functionality is comprised of link-local IPv6 addresses and stateless
|
|
* IPv6 address autoconfiguration, Neighbor Discovery, and header compression
|
|
* Fragmentation features from 6LoWPAN standards are not used due to Bluetooth
|
|
* LE's link-layer fragmentation support.
|
|
*/
|
|
|
|
/**
|
|
* 1 2 3
|
|
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* |1 1 0 0 0| datagram_size | datagram_tag |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
*
|
|
* 1 2 3
|
|
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* |1 1 0 0 0| datagram_size | datagram_tag |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* |datagram_offset|
|
|
* +-+-+-+-+-+-+-+-+
|
|
*/
|
|
|
|
static inline void set_datagram_size(uint8_t *ptr, uint16_t size)
|
|
{
|
|
ptr[0] |= ((size & 0x7FF) >> 8);
|
|
ptr[1] = (uint8_t)size;
|
|
}
|
|
|
|
static inline void set_datagram_tag(uint8_t *ptr, uint16_t tag)
|
|
{
|
|
ptr[0] = tag >> 8;
|
|
ptr[1] = (uint8_t)tag;
|
|
}
|
|
|
|
static inline void set_up_frag_hdr(struct net_buf *frag, uint16_t size, uint8_t offset)
|
|
{
|
|
static uint16_t datagram_tag;
|
|
|
|
bool is_first_frag = !offset;
|
|
uint8_t pos = frag->len;
|
|
|
|
if (is_first_frag) {
|
|
datagram_tag++;
|
|
net_buf_add(frag, NET_6LO_FRAG1_HDR_LEN);
|
|
frag->data[pos] = NET_6LO_DISPATCH_FRAG1;
|
|
} else {
|
|
net_buf_add(frag, NET_6LO_FRAGN_HDR_LEN);
|
|
frag->data[pos] = NET_6LO_DISPATCH_FRAGN;
|
|
}
|
|
|
|
set_datagram_size(frag->data + pos, size);
|
|
pos += NET_6LO_FRAG_DATAGRAM_SIZE_LEN;
|
|
|
|
set_datagram_tag(frag->data + pos, datagram_tag);
|
|
pos += NET_6LO_FRAG_DATAGRAM_OFFSET_LEN;
|
|
|
|
if (!is_first_frag) {
|
|
frag->data[pos] = offset;
|
|
}
|
|
}
|
|
|
|
static inline uint8_t calc_payload_capacity(struct net_buf *frag)
|
|
{
|
|
uint8_t capacity = frag->size - frag->len;
|
|
|
|
return (capacity & 0xF8);
|
|
}
|
|
|
|
static inline uint8_t copy_data(struct ieee802154_6lo_fragment_ctx *ctx, struct net_buf *frame_buf,
|
|
uint8_t capacity)
|
|
{
|
|
uint8_t remainder = ctx->buf->len - (ctx->pos - ctx->buf->data);
|
|
uint8_t move = MIN(remainder, capacity);
|
|
|
|
memcpy(frame_buf->data + frame_buf->len, ctx->pos, move);
|
|
net_buf_add(frame_buf, move);
|
|
|
|
return move;
|
|
}
|
|
|
|
static inline void update_fragment_ctx(struct ieee802154_6lo_fragment_ctx *ctx, uint8_t moved)
|
|
{
|
|
uint8_t remainder = (ctx->buf->len - (ctx->pos - ctx->buf->data));
|
|
bool next_frag = moved == remainder;
|
|
|
|
if (next_frag) {
|
|
ctx->buf = ctx->buf->frags;
|
|
if (ctx->buf) {
|
|
ctx->pos = ctx->buf->data;
|
|
}
|
|
} else {
|
|
ctx->pos += moved;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ch : compressed (IPv6) header(s)
|
|
* fh : fragment header (dispatch + size + tag + [offset])
|
|
* p : payload (first fragment holds IPv6 hdr as payload)
|
|
* e : empty space
|
|
* ll : link layer
|
|
*
|
|
* Input frame_buf to ieee802154_6lo_fragment() looks like below
|
|
*
|
|
* | ll |
|
|
*
|
|
* After fragment creation, frame_buf will look like:
|
|
*
|
|
* | ll + fh + p + e |
|
|
*
|
|
* p being taken from current pkt buffer and position.
|
|
*
|
|
* Space in every fragment is because fragment payload should be multiple
|
|
* of 8 octets (we have predefined packets at compile time, data packet mtu
|
|
* is set already).
|
|
*
|
|
* If it's the first fragment being created, fh will not own any offset
|
|
* (so it will be 1 byte smaller)
|
|
*/
|
|
struct net_buf *ieee802154_6lo_fragment(struct ieee802154_6lo_fragment_ctx *ctx,
|
|
struct net_buf *frame_buf, bool iphc)
|
|
{
|
|
uint8_t capacity;
|
|
|
|
set_up_frag_hdr(frame_buf, ctx->pkt_size, ctx->offset);
|
|
|
|
capacity = calc_payload_capacity(frame_buf);
|
|
ctx->processed += capacity;
|
|
|
|
bool is_first_frag = !ctx->offset;
|
|
|
|
if (is_first_frag) {
|
|
/* First fragment needs to take into account 6lo */
|
|
if (iphc) {
|
|
capacity -= ctx->hdr_diff;
|
|
} else {
|
|
/* Adding IPv6 dispatch header */
|
|
capacity += 1U;
|
|
}
|
|
}
|
|
|
|
while (capacity && ctx->buf) {
|
|
uint8_t moved = copy_data(ctx, frame_buf, capacity);
|
|
|
|
update_fragment_ctx(ctx, moved);
|
|
capacity -= moved;
|
|
}
|
|
|
|
ctx->offset = ctx->processed >> 3;
|
|
|
|
return ctx->buf;
|
|
}
|
|
|
|
static inline uint8_t get_datagram_type(uint8_t *ptr)
|
|
{
|
|
return ptr[0] & NET_FRAG_DISPATCH_MASK;
|
|
}
|
|
|
|
static inline uint16_t get_datagram_size(uint8_t *ptr)
|
|
{
|
|
return ((ptr[0] & 0x1F) << 8) | ptr[1];
|
|
}
|
|
|
|
static inline uint16_t get_datagram_tag(uint8_t *ptr)
|
|
{
|
|
return (ptr[0] << 8) | ptr[1];
|
|
}
|
|
|
|
static void update_protocol_header_lengths(struct net_pkt *pkt, uint16_t size)
|
|
{
|
|
NET_PKT_DATA_ACCESS_DEFINE(ipv6_access, struct net_ipv6_hdr);
|
|
struct net_ipv6_hdr *ipv6;
|
|
|
|
ipv6 = (struct net_ipv6_hdr *)net_pkt_get_data(pkt, &ipv6_access);
|
|
if (!ipv6) {
|
|
NET_ERR("could not get IPv6 header");
|
|
return;
|
|
}
|
|
|
|
net_pkt_set_ip_hdr_len(pkt, NET_IPV6H_LEN);
|
|
ipv6->len = htons(size - NET_IPV6H_LEN);
|
|
|
|
net_pkt_set_data(pkt, &ipv6_access);
|
|
|
|
if (ipv6->nexthdr == IPPROTO_UDP) {
|
|
NET_PKT_DATA_ACCESS_DEFINE(udp_access, struct net_udp_hdr);
|
|
struct net_udp_hdr *udp;
|
|
|
|
udp = (struct net_udp_hdr *)net_pkt_get_data(pkt, &udp_access);
|
|
if (udp) {
|
|
udp->len = htons(size - NET_IPV6H_LEN);
|
|
net_pkt_set_data(pkt, &udp_access);
|
|
} else {
|
|
NET_ERR("could not get UDP header");
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void clear_reass_cache(uint16_t size, uint16_t tag)
|
|
{
|
|
uint8_t i;
|
|
|
|
for (i = 0U; i < REASS_CACHE_SIZE; i++) {
|
|
if (!(cache[i].size == size && cache[i].tag == tag)) {
|
|
continue;
|
|
}
|
|
|
|
if (cache[i].pkt) {
|
|
net_pkt_unref(cache[i].pkt);
|
|
}
|
|
|
|
cache[i].pkt = NULL;
|
|
cache[i].size = 0U;
|
|
cache[i].tag = 0U;
|
|
cache[i].used = false;
|
|
k_work_cancel_delayable(&cache[i].timer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If the reassembly not completed within reassembly timeout discard
|
|
* the whole packet.
|
|
*/
|
|
static void reass_timeout(struct k_work *work)
|
|
{
|
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
|
struct frag_cache *fcache = CONTAINER_OF(dwork, struct frag_cache, timer);
|
|
|
|
if (fcache->pkt) {
|
|
net_pkt_unref(fcache->pkt);
|
|
}
|
|
|
|
fcache->pkt = NULL;
|
|
fcache->size = 0U;
|
|
fcache->tag = 0U;
|
|
fcache->used = false;
|
|
}
|
|
|
|
/**
|
|
* Upon reception of first fragment with respective of size and tag
|
|
* create a new cache. If number of unused cache are out then
|
|
* discard the fragments.
|
|
*/
|
|
static inline struct frag_cache *set_reass_cache(struct net_pkt *pkt, uint16_t size, uint16_t tag)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < REASS_CACHE_SIZE; i++) {
|
|
if (cache[i].used) {
|
|
continue;
|
|
}
|
|
|
|
cache[i].pkt = pkt;
|
|
cache[i].size = size;
|
|
cache[i].tag = tag;
|
|
cache[i].used = true;
|
|
|
|
k_work_init_delayable(&cache[i].timer, reass_timeout);
|
|
k_work_reschedule(&cache[i].timer, FRAG_REASSEMBLY_TIMEOUT);
|
|
return &cache[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Return cache if it matches with size and tag of stored caches,
|
|
* otherwise return NULL.
|
|
*/
|
|
static inline struct frag_cache *get_reass_cache(uint16_t size, uint16_t tag)
|
|
{
|
|
uint8_t i;
|
|
|
|
for (i = 0U; i < REASS_CACHE_SIZE; i++) {
|
|
if (cache[i].used) {
|
|
if (cache[i].size == size && cache[i].tag == tag) {
|
|
return &cache[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline void fragment_append(struct net_pkt *pkt, struct net_buf *frag)
|
|
{
|
|
if (get_datagram_type(frag->data) == NET_6LO_DISPATCH_FRAG1) {
|
|
/* Always make sure first fragment is inserted first
|
|
* This will be useful for fragment_cached_pkt_len()
|
|
*/
|
|
frag->frags = pkt->buffer;
|
|
pkt->buffer = frag;
|
|
} else {
|
|
net_pkt_append_buffer(pkt, frag);
|
|
}
|
|
}
|
|
|
|
static inline size_t fragment_cached_pkt_len(struct net_pkt *pkt)
|
|
{
|
|
size_t len = 0U;
|
|
struct net_buf *frag;
|
|
int hdr_diff;
|
|
uint8_t *data;
|
|
|
|
frag = pkt->buffer;
|
|
while (frag) {
|
|
uint16_t frag_hdr_len = NET_6LO_FRAGN_HDR_LEN;
|
|
|
|
if (get_datagram_type(frag->data) == NET_6LO_DISPATCH_FRAG1) {
|
|
frag_hdr_len = NET_6LO_FRAG1_HDR_LEN;
|
|
}
|
|
|
|
len += frag->len - frag_hdr_len;
|
|
|
|
frag = frag->frags;
|
|
}
|
|
|
|
/* 6lo assumes that fragment header has been removed,
|
|
* and in our side we assume first buffer is always the first fragment.
|
|
*/
|
|
data = pkt->buffer->data;
|
|
pkt->buffer->data += NET_6LO_FRAG1_HDR_LEN;
|
|
|
|
hdr_diff = net_6lo_uncompress_hdr_diff(pkt);
|
|
|
|
pkt->buffer->data = data;
|
|
|
|
if (hdr_diff == INT_MAX) {
|
|
return 0;
|
|
}
|
|
|
|
return len + hdr_diff;
|
|
}
|
|
|
|
static inline uint16_t fragment_offset(struct net_buf *frag)
|
|
{
|
|
if (get_datagram_type(frag->data) == NET_6LO_DISPATCH_FRAG1) {
|
|
return 0;
|
|
}
|
|
|
|
return ((uint16_t)frag->data[NET_FRAG_OFFSET_POS] << 3);
|
|
}
|
|
|
|
static void fragment_move_back(struct net_pkt *pkt, struct net_buf *frag, struct net_buf *stop)
|
|
{
|
|
struct net_buf *prev, *current;
|
|
|
|
prev = NULL;
|
|
current = pkt->buffer;
|
|
|
|
while (current && current != stop) {
|
|
if (fragment_offset(frag) < fragment_offset(current)) {
|
|
if (prev) {
|
|
prev->frags = frag;
|
|
}
|
|
|
|
frag->frags = current;
|
|
break;
|
|
}
|
|
|
|
prev = current;
|
|
current = current->frags;
|
|
}
|
|
}
|
|
|
|
static inline void fragment_remove_headers(struct net_pkt *pkt)
|
|
{
|
|
struct net_buf *frag;
|
|
|
|
frag = pkt->buffer;
|
|
while (frag) {
|
|
uint16_t frag_hdr_len = NET_6LO_FRAGN_HDR_LEN;
|
|
|
|
if (get_datagram_type(frag->data) == NET_6LO_DISPATCH_FRAG1) {
|
|
frag_hdr_len = NET_6LO_FRAG1_HDR_LEN;
|
|
}
|
|
|
|
memmove(frag->data, frag->data + frag_hdr_len, frag->len - frag_hdr_len);
|
|
frag->len -= frag_hdr_len;
|
|
|
|
frag = frag->frags;
|
|
}
|
|
}
|
|
|
|
static inline void fragment_reconstruct_packet(struct net_pkt *pkt)
|
|
{
|
|
struct net_buf *prev, *current, *next;
|
|
|
|
prev = NULL;
|
|
current = pkt->buffer;
|
|
|
|
while (current) {
|
|
next = current->frags;
|
|
|
|
if (!prev || (fragment_offset(prev) > fragment_offset(current))) {
|
|
prev = current;
|
|
} else {
|
|
fragment_move_back(pkt, current, prev);
|
|
}
|
|
|
|
current = next;
|
|
}
|
|
|
|
/* Let's remove now useless fragmentation headers */
|
|
fragment_remove_headers(pkt);
|
|
}
|
|
|
|
static inline bool fragment_packet_valid(struct net_pkt *pkt)
|
|
{
|
|
return (get_datagram_type(pkt->buffer->data) == NET_6LO_DISPATCH_FRAG1);
|
|
}
|
|
|
|
/**
|
|
* Parse size and tag from the fragment, check if we have any cache
|
|
* related to it. If not create a new cache.
|
|
* Remove the fragmentation header and uncompress IPv6 and related headers.
|
|
* Cache Rx part of fragment along with data buf for the first fragment
|
|
* in the cache, remaining fragments just cache data fragment, unref
|
|
* RX pkt. So in both the cases caller can assume packet is consumed.
|
|
*/
|
|
static inline enum net_verdict fragment_add_to_cache(struct net_pkt *pkt)
|
|
{
|
|
bool first_frag = false;
|
|
struct frag_cache *fcache;
|
|
struct net_buf *frag;
|
|
uint16_t size;
|
|
uint16_t tag;
|
|
uint8_t type;
|
|
|
|
frag = pkt->buffer;
|
|
type = get_datagram_type(frag->data);
|
|
|
|
if ((type == NET_6LO_DISPATCH_FRAG1 && frag->len < NET_6LO_FRAG1_HDR_LEN) ||
|
|
(type == NET_6LO_DISPATCH_FRAGN && frag->len < NET_6LO_FRAGN_HDR_LEN)) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
/* Parse total size of packet */
|
|
size = get_datagram_size(frag->data);
|
|
|
|
/* Parse the datagram tag */
|
|
tag = get_datagram_tag(frag->data + NET_6LO_FRAG_DATAGRAM_SIZE_LEN);
|
|
|
|
/* If there are no fragments in the cache means this frag
|
|
* is the first one. So cache Rx pkt otherwise not.
|
|
*/
|
|
pkt->buffer = NULL;
|
|
|
|
fcache = get_reass_cache(size, tag);
|
|
if (!fcache) {
|
|
fcache = set_reass_cache(pkt, size, tag);
|
|
if (!fcache) {
|
|
NET_ERR("Could not get a cache entry");
|
|
pkt->buffer = frag;
|
|
return NET_DROP;
|
|
}
|
|
|
|
first_frag = true;
|
|
}
|
|
|
|
fragment_append(fcache->pkt, frag);
|
|
|
|
if (fragment_cached_pkt_len(fcache->pkt) == fcache->size) {
|
|
if (!first_frag) {
|
|
/* Assign buffer back to input packet. */
|
|
pkt->buffer = fcache->pkt->buffer;
|
|
fcache->pkt->buffer = NULL;
|
|
} else {
|
|
/* in case pkt == fcache->pkt, we don't want
|
|
* to unref it while clearing the cache.
|
|
*/
|
|
fcache->pkt = NULL;
|
|
}
|
|
|
|
clear_reass_cache(size, tag);
|
|
|
|
if (!fragment_packet_valid(pkt)) {
|
|
NET_ERR("Invalid fragmented packet");
|
|
return NET_DROP;
|
|
}
|
|
|
|
fragment_reconstruct_packet(pkt);
|
|
|
|
if (!net_6lo_uncompress(pkt)) {
|
|
NET_ERR("Could not uncompress. Bogus packet?");
|
|
return NET_DROP;
|
|
}
|
|
|
|
net_pkt_cursor_init(pkt);
|
|
|
|
update_protocol_header_lengths(pkt, size);
|
|
|
|
net_pkt_cursor_init(pkt);
|
|
|
|
NET_DBG("All fragments received and reassembled");
|
|
|
|
return NET_CONTINUE;
|
|
}
|
|
|
|
/* Unref Rx part of original packet */
|
|
if (!first_frag) {
|
|
net_pkt_unref(pkt);
|
|
}
|
|
|
|
return NET_OK;
|
|
}
|
|
|
|
enum net_verdict ieee802154_6lo_reassemble(struct net_pkt *pkt)
|
|
{
|
|
if (!pkt || !pkt->buffer) {
|
|
NET_ERR("Nothing to reassemble");
|
|
return NET_DROP;
|
|
}
|
|
|
|
if (get_datagram_type(pkt->buffer->data) >= NET_6LO_DISPATCH_FRAG1) {
|
|
return fragment_add_to_cache(pkt);
|
|
} else {
|
|
NET_DBG("No frag dispatch (%02x)", pkt->buffer->data[0]);
|
|
/* Received unfragmented packet, uncompress */
|
|
if (net_6lo_uncompress(pkt)) {
|
|
return NET_CONTINUE;
|
|
}
|
|
|
|
NET_ERR("Could not uncompress. Bogus packet?");
|
|
}
|
|
|
|
return NET_DROP;
|
|
}
|