1287 lines
34 KiB
C
1287 lines
34 KiB
C
/****************************************************************************
|
|
* net/ipfrag/ipfrag.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
#if (defined(CONFIG_NET_IPv4) || defined(CONFIG_NET_IPv6)) && \
|
|
defined(CONFIG_NET_IPFRAG)
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <debug.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <netinet/in.h>
|
|
#include <net/if.h>
|
|
|
|
#include <nuttx/nuttx.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/net/netconfig.h>
|
|
#include <nuttx/net/netdev.h>
|
|
#include <nuttx/net/netstats.h>
|
|
#include <nuttx/net/ip.h>
|
|
#include <nuttx/net/ipv6ext.h>
|
|
|
|
#include "netdev/netdev.h"
|
|
#include "inet/inet.h"
|
|
#include "icmp/icmp.h"
|
|
#include "icmpv6/icmpv6.h"
|
|
#include "ipfrag.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define GOTO_IF(expression, to) \
|
|
if (expression) \
|
|
{ \
|
|
goto to; \
|
|
}
|
|
|
|
#define UPDATE_IOB(iob, off, len) \
|
|
do \
|
|
{ \
|
|
(iob)->io_offset = (off); \
|
|
(iob)->io_len = (len); \
|
|
(iob)->io_pktlen = (len); \
|
|
} while (0);
|
|
|
|
/* Defined the minimal timeout interval to avoid triggering timeout timer
|
|
* too frequently, default: 0.5 seconds.
|
|
*/
|
|
|
|
#define REASSEMBLY_TIMEOUT_MINIMAL 5
|
|
|
|
#if CONFIG_NET_IPFRAG_REASS_MAXAGE < REASSEMBLY_TIMEOUT_MINIMAL
|
|
# define REASSEMBLY_TIMEOUT REASSEMBLY_TIMEOUT_MINIMAL
|
|
#else
|
|
# define REASSEMBLY_TIMEOUT CONFIG_NET_IPFRAG_REASS_MAXAGE
|
|
#endif
|
|
|
|
#define REASSEMBLY_TIMEOUT_MINIMALTICKS DSEC2TICK(REASSEMBLY_TIMEOUT_MINIMAL)
|
|
#define REASSEMBLY_TIMEOUT_TICKS DSEC2TICK(REASSEMBLY_TIMEOUT)
|
|
|
|
#define IPFRAGWORK LPWORK
|
|
|
|
/* Helper macro to count I/O buffer count for a given I/O buffer chain */
|
|
|
|
#define IOBUF_CNT(ptr) (((ptr)->io_pktlen + CONFIG_IOB_BUFSIZE - 1)/ \
|
|
CONFIG_IOB_BUFSIZE)
|
|
|
|
/* The maximum I/O buffer occupied by fragment reassembly cache */
|
|
|
|
#define REASSEMBLY_MAXOCCUPYIOB CONFIG_IOB_NBUFFERS / 5
|
|
|
|
/* Deciding whether to fragment outgoing packets which target is to ourself */
|
|
|
|
#define LOOPBACK_IPFRAME_NOFRAGMENT 0
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/* A timeout timer used to start a worker which is used to check
|
|
* whether the assembly time of those fragments within one node is expired,
|
|
* if so, free all resources of this node.
|
|
*/
|
|
|
|
static struct wdog_s g_wdfragtimeout;
|
|
|
|
/* Reassembly timeout work */
|
|
|
|
static struct work_s g_wkfragtimeout;
|
|
|
|
/* Remember the number of I/O buffers currently in reassembly cache */
|
|
|
|
static uint8_t g_bufoccupy;
|
|
|
|
/* Queue header definition, it links all fragments of all NICs by ascending
|
|
* ipid.
|
|
*/
|
|
|
|
static sq_queue_t g_assemblyhead_ipid;
|
|
|
|
/* Queue header definition, which connects all fragments of all NICs in order
|
|
* of addition time.
|
|
*/
|
|
|
|
static sq_queue_t g_assemblyhead_time;
|
|
|
|
/****************************************************************************
|
|
* Public Data
|
|
****************************************************************************/
|
|
|
|
/* Only one thread can access g_assemblyhead_ipid and g_assemblyhead_time
|
|
* at a time.
|
|
*/
|
|
|
|
mutex_t g_ipfrag_lock = NXMUTEX_INITIALIZER;
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static void ip_fragin_timerout_expiry(wdparm_t arg);
|
|
static void ip_fragin_timerwork(FAR void *arg);
|
|
static inline FAR struct ip_fraglink_s *
|
|
ip_fragin_freelink(FAR struct ip_fraglink_s *fraglink);
|
|
static void ip_fragin_check(FAR struct ip_fragsnode_s *fragsnode);
|
|
static void ip_fragin_cachemonitor(FAR struct ip_fragsnode_s *curnode);
|
|
static inline FAR struct iob_s *
|
|
ip_fragout_allocfragbuf(FAR struct iob_queue_s *fragq);
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: ip_fragin_timerout_expiry
|
|
*
|
|
* Description:
|
|
* Schedule the timeout checking and handling on the low priority work
|
|
* queue.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void ip_fragin_timerout_expiry(wdparm_t arg)
|
|
{
|
|
if (g_wkfragtimeout.worker == NULL)
|
|
{
|
|
work_queue(IPFRAGWORK, &g_wkfragtimeout, ip_fragin_timerwork, NULL, 0);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_fragin_timerwork
|
|
*
|
|
* Description:
|
|
* The really work of fragment timeout checking and handling.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void ip_fragin_timerwork(FAR void *arg)
|
|
{
|
|
clock_t curtick = clock_systime_ticks();
|
|
sclock_t interval = 0;
|
|
FAR sq_entry_t *entry;
|
|
FAR sq_entry_t *entrynext;
|
|
FAR struct ip_fragsnode_s *node;
|
|
|
|
ninfo("Start reassembly work queue\n");
|
|
|
|
nxmutex_lock(&g_ipfrag_lock);
|
|
|
|
/* Walk through the list, check the timetout and calculate the next timer
|
|
* interval
|
|
*/
|
|
|
|
entry = sq_peek(&g_assemblyhead_time);
|
|
while (entry != NULL)
|
|
{
|
|
entrynext = sq_next(entry);
|
|
|
|
node = (FAR struct ip_fragsnode_s *)
|
|
container_of(entry, FAR struct ip_fragsnode_s, flinkat);
|
|
|
|
/* Check for timeout, be careful with the calculation formula,
|
|
* the tick counter may overflow
|
|
*/
|
|
|
|
interval = curtick - node->tick;
|
|
|
|
if (interval >= REASSEMBLY_TIMEOUT_TICKS)
|
|
{
|
|
/* If this timeout expires, the partially-reassembled datagram
|
|
* MUST be discarded and an ICMP Time Exceeded message sent to
|
|
* the source host (if fragment zero has been received).
|
|
*/
|
|
|
|
ninfo("Reassembly timeout occurs!");
|
|
#if defined(CONFIG_NET_ICMP) && !defined(CONFIG_NET_ICMP_NO_STACK)
|
|
if ((node->verifyflag & IP_FRAGVERIFY_RECVDZEROFRAG) != 0)
|
|
{
|
|
FAR struct net_driver_s *dev = node->dev;
|
|
|
|
net_lock();
|
|
|
|
netdev_iob_replace(dev, node->frags->frag);
|
|
node->frags->frag = NULL;
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
if (node->frags->isipv4)
|
|
{
|
|
icmp_reply(dev, ICMP_TIME_EXCEEDED,
|
|
ICMP_EXC_FRAGTIME);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (!node->frags->isipv4)
|
|
{
|
|
icmpv6_reply(dev, ICMPv6_PACKET_TIME_EXCEEDED,
|
|
ICMPV6_EXC_FRAGTIME, 0);
|
|
}
|
|
#endif
|
|
|
|
if (iob_tryadd_queue(dev->d_iob, &dev->d_fragout) == 0)
|
|
{
|
|
netdev_iob_clear(dev);
|
|
|
|
/* Send ICMP Time Exceeded message via dev->d_fragout
|
|
* queue
|
|
*/
|
|
|
|
ninfo("Send Time Exceeded ICMP%s Message to source "
|
|
"host\n", node->frags->isipv4 ? "v4" : "v6");
|
|
netdev_txnotify_dev(dev);
|
|
}
|
|
|
|
net_unlock();
|
|
}
|
|
#endif
|
|
|
|
/* Remove fragments of this node */
|
|
|
|
if (node->frags != NULL)
|
|
{
|
|
FAR struct ip_fraglink_s *fraglink = node->frags;
|
|
|
|
while (fraglink)
|
|
{
|
|
fraglink = ip_fragin_freelink(fraglink);
|
|
}
|
|
}
|
|
|
|
/* Remove node from single-list and free node memory */
|
|
|
|
ip_frag_remnode(node);
|
|
kmm_free(node);
|
|
}
|
|
else
|
|
{
|
|
/* Because fragment nodes have been sorted to g_assemblyhead_time
|
|
* according to the added time, so enter here, we can get the
|
|
* 'interval' of the earliest time node that has not timed out.
|
|
* There is no need to continue the loop here, and use time
|
|
* REASSEMBLY_TIMEOUT_TICKS - 'interval' as the input for the next
|
|
* Timer starting.
|
|
*/
|
|
|
|
break;
|
|
}
|
|
|
|
entry = entrynext;
|
|
}
|
|
|
|
/* Be sure to start the timer, if there are nodes in the linked list */
|
|
|
|
if (sq_peek(&g_assemblyhead_time) != NULL)
|
|
{
|
|
clock_t delay = REASSEMBLY_TIMEOUT_MINIMALTICKS;
|
|
|
|
/* The interval for the next timer is REASSEMBLY_TIMEOUT_TICKS -
|
|
* interval, if it is less than the minimum timeout interval,
|
|
* fix it to REASSEMBLY_TIMEOUT_MINIMALTICKS
|
|
*/
|
|
|
|
if (delay < REASSEMBLY_TIMEOUT_TICKS - interval)
|
|
{
|
|
delay = REASSEMBLY_TIMEOUT_TICKS - interval;
|
|
}
|
|
|
|
ninfo("Reschedule reassembly work queue\n");
|
|
wd_start(&g_wdfragtimeout, delay, ip_fragin_timerout_expiry,
|
|
(wdparm_t)NULL);
|
|
}
|
|
else
|
|
{
|
|
ninfo("Stop reassembly work queue\n");
|
|
}
|
|
|
|
nxmutex_unlock(&g_ipfrag_lock);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_fragin_freelink
|
|
*
|
|
* Description:
|
|
* Free the I/O buffer and ip_fraglink_s buffer at the head of a
|
|
* ip_fraglink_s chain.
|
|
*
|
|
* Input Parameters:
|
|
* fraglink - node of the lower-level linked list, it maintains
|
|
* information of one fragment
|
|
*
|
|
* Returned Value:
|
|
* The link to the next ip_fraglink_s buffer in the chain.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline FAR struct ip_fraglink_s *
|
|
ip_fragin_freelink(FAR struct ip_fraglink_s *fraglink)
|
|
{
|
|
FAR struct ip_fraglink_s *next = fraglink->flink;
|
|
|
|
if (fraglink->frag != NULL)
|
|
{
|
|
iob_free_chain(fraglink->frag);
|
|
}
|
|
|
|
kmm_free(fraglink);
|
|
|
|
return next;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_fragin_check
|
|
*
|
|
* Description:
|
|
* Check whether the zero fragment has been received or all fragments have
|
|
* been received.
|
|
*
|
|
* Input Parameters:
|
|
* fragsnode - node of the upper-level linked list, it maintains
|
|
* information bout all fragments belonging to an IP datagram
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void ip_fragin_check(FAR struct ip_fragsnode_s *fragsnode)
|
|
{
|
|
uint16_t formerlen = 0;
|
|
FAR struct ip_fraglink_s *entry;
|
|
|
|
if (fragsnode->verifyflag & IP_FRAGVERIFY_RECVDTAILFRAG)
|
|
{
|
|
entry = fragsnode->frags;
|
|
while (entry != NULL)
|
|
{
|
|
if (entry->morefrags)
|
|
{
|
|
formerlen += entry->fraglen;
|
|
}
|
|
else
|
|
{
|
|
/* Only the last entry has a 0 morefrags flag */
|
|
|
|
if (entry->fragoff == formerlen)
|
|
{
|
|
fragsnode->verifyflag |= IP_FRAGVERIFY_RECVDALLFRAGS;
|
|
}
|
|
}
|
|
|
|
entry = entry->flink;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_fragin_cachemonitor
|
|
*
|
|
* Description:
|
|
* Check the reassembly cache buffer size, if it exceeds the configured
|
|
* threshold, some I/O buffers need to be freed
|
|
*
|
|
* Input Parameters:
|
|
* curnode - node of the upper-level linked list, it maintains information
|
|
* about all fragments belonging to an IP datagram
|
|
*
|
|
* Returned Value:
|
|
* none
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void ip_fragin_cachemonitor(FAR struct ip_fragsnode_s *curnode)
|
|
{
|
|
uint32_t cleancnt = 0;
|
|
uint32_t bufcnt;
|
|
FAR sq_entry_t *entry;
|
|
FAR sq_entry_t *entrynext;
|
|
FAR struct ip_fragsnode_s *node;
|
|
|
|
/* Start cache cleaning if g_bufoccupy exceeds the cache threshold */
|
|
|
|
if (g_bufoccupy > REASSEMBLY_MAXOCCUPYIOB)
|
|
{
|
|
cleancnt = g_bufoccupy - REASSEMBLY_MAXOCCUPYIOB;
|
|
entry = sq_peek(&g_assemblyhead_time);
|
|
|
|
while (entry != NULL && cleancnt > 0)
|
|
{
|
|
entrynext = sq_next(entry);
|
|
|
|
node = (FAR struct ip_fragsnode_s *)
|
|
container_of(entry, FAR struct ip_fragsnode_s, flinkat);
|
|
|
|
/* Skip specified node */
|
|
|
|
if (node != curnode)
|
|
{
|
|
/* Remove fragments of this node */
|
|
|
|
if (node->frags != NULL)
|
|
{
|
|
FAR struct ip_fraglink_s *fraglink = node->frags;
|
|
|
|
while (fraglink != NULL)
|
|
{
|
|
fraglink = ip_fragin_freelink(fraglink);
|
|
}
|
|
}
|
|
|
|
/* Remove node from single-list and free node memory */
|
|
|
|
bufcnt = ip_frag_remnode(node);
|
|
kmm_free(node);
|
|
|
|
cleancnt = cleancnt > bufcnt ? cleancnt - bufcnt : 0;
|
|
}
|
|
|
|
entry = entrynext;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_fragout_allocfragbuf
|
|
*
|
|
* Description:
|
|
* Prepare one I/O buffer and enqueue it to a specified queue
|
|
*
|
|
* Input Parameters:
|
|
* fragq - the queue head
|
|
*
|
|
* Returned Value:
|
|
* The pointer to I/O buffer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline FAR struct iob_s *
|
|
ip_fragout_allocfragbuf(FAR struct iob_queue_s *fragq)
|
|
{
|
|
FAR struct iob_s *iob;
|
|
|
|
iob = iob_tryalloc(false);
|
|
if (iob != NULL)
|
|
{
|
|
if (iob_tryadd_queue(iob, fragq) < 0)
|
|
{
|
|
iob_free(iob);
|
|
iob = NULL;
|
|
}
|
|
}
|
|
|
|
return iob;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: ip_frag_remnode
|
|
*
|
|
* Description:
|
|
* free ip_fragsnode_s
|
|
*
|
|
* Input Parameters:
|
|
* node - node of the upper-level linked list, it maintains
|
|
* information about all fragments belonging to an IP datagram
|
|
*
|
|
* Returned Value:
|
|
* I/O buffer count of this node
|
|
*
|
|
****************************************************************************/
|
|
|
|
uint32_t ip_frag_remnode(FAR struct ip_fragsnode_s *node)
|
|
{
|
|
g_bufoccupy -= node->bufcnt;
|
|
ASSERT(g_bufoccupy < CONFIG_IOB_NBUFFERS);
|
|
|
|
sq_rem((FAR sq_entry_t *)node, &g_assemblyhead_ipid);
|
|
sq_rem((FAR sq_entry_t *)&node->flinkat, &g_assemblyhead_time);
|
|
|
|
return node->bufcnt;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_fragin_enqueue
|
|
*
|
|
* Description:
|
|
* Enqueue one fragment.
|
|
* All fragments belonging to one IP frame are organized in a linked list
|
|
* form, that is a ip_fragsnode_s node. All ip_fragsnode_s nodes are also
|
|
* organized in an upper-level linked list.
|
|
*
|
|
* Input Parameters:
|
|
* dev - NIC Device instance
|
|
* curfraglink - node of the lower-level linked list, it maintains
|
|
* information of one fragment
|
|
*
|
|
* Returned Value:
|
|
* Whether queue is empty before enqueue the new node
|
|
*
|
|
****************************************************************************/
|
|
|
|
bool ip_fragin_enqueue(FAR struct net_driver_s *dev,
|
|
FAR struct ip_fraglink_s *curfraglink)
|
|
{
|
|
FAR struct ip_fragsnode_s *node;
|
|
FAR sq_entry_t *entry;
|
|
FAR sq_entry_t *entrylast = NULL;
|
|
bool empty;
|
|
|
|
/* The linked list is ordered by IP ID value, walk through it and try to
|
|
* find a node that has the same IP ID value, otherwise need to create a
|
|
* new node and insert it into the linked list.
|
|
*/
|
|
|
|
entry = sq_peek(&g_assemblyhead_ipid);
|
|
empty = (entry == NULL) ? true : false;
|
|
|
|
while (entry != NULL)
|
|
{
|
|
node = (struct ip_fragsnode_s *)entry;
|
|
|
|
if (dev == node->dev && curfraglink->ipid <= node->ipid)
|
|
{
|
|
break;
|
|
}
|
|
|
|
entrylast = entry;
|
|
entry = sq_next(entry);
|
|
}
|
|
|
|
node = (FAR struct ip_fragsnode_s *)entry;
|
|
|
|
if (node != NULL && curfraglink->ipid == node->ipid)
|
|
{
|
|
FAR struct ip_fraglink_s *fraglink;
|
|
FAR struct ip_fraglink_s *lastlink = NULL;
|
|
|
|
/* Found a previously created ip_fragsnode_s, insert this new
|
|
* ip_fraglink_s to the subchain of this node.
|
|
*/
|
|
|
|
fraglink = node->frags;
|
|
|
|
/* An ip_fragsnode_s must have an ip_fraglink_s because we allocate a
|
|
* new ip_fraglink_s when caching a new ip_fraglink_s with a new IP ID
|
|
*/
|
|
|
|
while (fraglink != NULL)
|
|
{
|
|
/* The fragment list is ordered by fragment offset value */
|
|
|
|
if (curfraglink->fragoff <= fraglink->fragoff)
|
|
{
|
|
break;
|
|
}
|
|
|
|
lastlink = fraglink;
|
|
fraglink = fraglink->flink;
|
|
}
|
|
|
|
if (fraglink == NULL)
|
|
{
|
|
/* This fragment offset is greater than the previous fragments,
|
|
* added to the last position
|
|
*/
|
|
|
|
lastlink->flink = curfraglink;
|
|
|
|
/* Remember I/O buffer count */
|
|
|
|
node->bufcnt += IOBUF_CNT(curfraglink->frag);
|
|
g_bufoccupy += IOBUF_CNT(curfraglink->frag);
|
|
}
|
|
else if (curfraglink->fragoff == fraglink->fragoff)
|
|
{
|
|
/* Fragments with same offset value contain the same data, use the
|
|
* more recently arrived copy. Refer to RFC791, Section3.2, Page29.
|
|
* Replace and removed the old packet from the fragment list
|
|
*/
|
|
|
|
curfraglink->flink = fraglink->flink;
|
|
if (lastlink == NULL)
|
|
{
|
|
node->frags = curfraglink;
|
|
}
|
|
else
|
|
{
|
|
lastlink->flink = curfraglink;
|
|
}
|
|
|
|
iob_free_chain(fraglink->frag);
|
|
kmm_free(fraglink);
|
|
}
|
|
else
|
|
{
|
|
/* Insert into the fragment list */
|
|
|
|
if (lastlink == NULL)
|
|
{
|
|
/* Insert before the first node */
|
|
|
|
curfraglink->flink = node->frags;
|
|
node->frags = curfraglink;
|
|
}
|
|
else
|
|
{
|
|
/* Insert this node after lastlink */
|
|
|
|
curfraglink->flink = lastlink->flink;
|
|
lastlink->flink = curfraglink;
|
|
}
|
|
|
|
/* Remember I/O buffer count */
|
|
|
|
node->bufcnt += IOBUF_CNT(curfraglink->frag);
|
|
g_bufoccupy += IOBUF_CNT(curfraglink->frag);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* It's a new IP ID fragment, malloc a new node and insert it into the
|
|
* linked list
|
|
*/
|
|
|
|
node = kmm_malloc(sizeof(struct ip_fragsnode_s));
|
|
if (node == NULL)
|
|
{
|
|
nerr("ERROR: Failed to allocate buffer.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
node->flink = NULL;
|
|
node->flinkat = NULL;
|
|
node->dev = dev;
|
|
node->ipid = curfraglink->ipid;
|
|
node->frags = curfraglink;
|
|
node->tick = clock_systime_ticks();
|
|
node->bufcnt = IOBUF_CNT(curfraglink->frag);
|
|
g_bufoccupy += IOBUF_CNT(curfraglink->frag);
|
|
node->verifyflag = 0;
|
|
node->outgoframe = NULL;
|
|
|
|
/* Insert this new node into linked list identified by
|
|
* g_assemblyhead_ipid with correct position
|
|
*/
|
|
|
|
if (sq_peek(&g_assemblyhead_ipid) == NULL || entrylast == NULL)
|
|
{
|
|
sq_addfirst((FAR sq_entry_t *)node, &g_assemblyhead_ipid);
|
|
}
|
|
else
|
|
{
|
|
sq_addafter(entrylast, (FAR sq_entry_t *)node,
|
|
&g_assemblyhead_ipid);
|
|
}
|
|
|
|
/* Add this new node to the tail of linked list identified by
|
|
* g_assemblyhead_time
|
|
*/
|
|
|
|
sq_addlast((FAR sq_entry_t *)&node->flinkat, &g_assemblyhead_time);
|
|
}
|
|
|
|
if (curfraglink->fragoff == 0)
|
|
{
|
|
/* Have received the zero fragment */
|
|
|
|
node->verifyflag |= IP_FRAGVERIFY_RECVDZEROFRAG;
|
|
}
|
|
else if (!curfraglink->morefrags)
|
|
{
|
|
/* Have received the tail fragment */
|
|
|
|
node->verifyflag |= IP_FRAGVERIFY_RECVDTAILFRAG;
|
|
}
|
|
|
|
/* For indexing convenience */
|
|
|
|
curfraglink->fragsnode = node;
|
|
|
|
/* Check receiving status */
|
|
|
|
ip_fragin_check(node);
|
|
|
|
/* Buffer is take away, clear original pointers in NIC */
|
|
|
|
netdev_iob_clear(dev);
|
|
|
|
/* Perform cache cleaning when reassembly cache size exceeds the configured
|
|
* threshold
|
|
*/
|
|
|
|
ip_fragin_cachemonitor(node);
|
|
|
|
return empty;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_fragout_slice
|
|
*
|
|
* Description:
|
|
* According to the MTU of a given NIC, split the original data into
|
|
* multiple data pieces, and the space for filling the L3 header is
|
|
* reserved at the forefront of each piece. Each piece is stored in
|
|
* independent I/O buffer(s) and eventually forms an I/O buffer queue.
|
|
* Note:
|
|
* 1.About the 'piece' above
|
|
* 1).If MTU < CONFIG_IOB_BUFSIZE, a piece consists of an I/O buffer;
|
|
* 2).If MTU >= CONFIG_IOB_BUFSIZE, a piece consists of multiple I/O
|
|
* buffers.
|
|
* 2.This function split and gathers the incoming data into outgoing
|
|
* I/O buffers according to the MTU, but is not responsible for
|
|
* building the L3 header related to the fragmentation.
|
|
*
|
|
* Input Parameters:
|
|
* iob - The data comes from
|
|
* domain - PF_INET or PF_INET6
|
|
* mtu - MTU of current NIC
|
|
* unfraglen - The starting position to fragmentation processing
|
|
* fragq - Those output slices
|
|
*
|
|
* Returned Value:
|
|
* Number of fragments
|
|
*
|
|
* Assumptions:
|
|
* Data length(iob->io_pktlen) is grater than the MTU of current NIC
|
|
*
|
|
****************************************************************************/
|
|
|
|
int32_t ip_fragout_slice(FAR struct iob_s *iob, uint8_t domain, uint16_t mtu,
|
|
uint16_t unfraglen, FAR struct iob_queue_s *fragq)
|
|
{
|
|
FAR uint8_t *leftstart;
|
|
uint16_t leftlen = 0;
|
|
uint16_t ncopy;
|
|
uint16_t navail;
|
|
uint32_t nfrags = 0;
|
|
bool expand = false;
|
|
FAR struct iob_s *orig = NULL;
|
|
FAR struct iob_s *reorg = NULL;
|
|
FAR struct iob_s *head = NULL;
|
|
|
|
if (iob == NULL || fragq == NULL)
|
|
{
|
|
nerr("ERROR: Invalid parameters! iob: %p, fragq: %p\n", iob, fragq);
|
|
return 0;
|
|
}
|
|
|
|
ASSERT(iob->io_pktlen > mtu);
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
if (domain == PF_INET)
|
|
{
|
|
uint16_t nreside;
|
|
|
|
/* Fragmentation requires that the data length after the IP header
|
|
* must be a multiple of 8
|
|
*/
|
|
|
|
mtu = ((mtu - IPv4_HDRLEN) & ~0x7) + IPv4_HDRLEN;
|
|
|
|
/* Remember the number of resident bytes */
|
|
|
|
nreside = mtu;
|
|
|
|
/* For IPv4, fragmented frames and non-fragmented frames have the
|
|
* same length L3 header. So process it as follows:
|
|
* the zero fragment use the original I/O buffer and reorganize
|
|
* the non-zero fragments (copy to new I/O buffers), space for the
|
|
* L3 IP header must be reserved for all fragments
|
|
*/
|
|
|
|
head = iob;
|
|
while (iob != NULL && nreside > iob->io_len)
|
|
{
|
|
nreside -= iob->io_len;
|
|
iob = iob->io_flink;
|
|
}
|
|
|
|
leftstart = iob->io_data + iob->io_offset + nreside;
|
|
leftlen = iob->io_len - nreside;
|
|
|
|
orig = iob->io_flink;
|
|
if (orig != NULL)
|
|
{
|
|
orig->io_pktlen = head->io_pktlen - (mtu + leftlen);
|
|
iob->io_flink = NULL;
|
|
}
|
|
|
|
head->io_pktlen = mtu;
|
|
iob->io_len = nreside;
|
|
|
|
if (iob_tryadd_queue(head, fragq) < 0)
|
|
{
|
|
goto allocfail;
|
|
}
|
|
|
|
head = NULL;
|
|
nfrags++;
|
|
|
|
if (leftlen == 0 && orig != NULL)
|
|
{
|
|
reorg = ip_fragout_allocfragbuf(fragq);
|
|
GOTO_IF(reorg == NULL, allocfail);
|
|
|
|
nfrags++;
|
|
|
|
/* This is a new fragment buffer, reserve L2&L3 header space
|
|
* in the front of this buffer
|
|
*/
|
|
|
|
UPDATE_IOB(reorg, CONFIG_NET_LL_GUARDSIZE, unfraglen);
|
|
}
|
|
else
|
|
{
|
|
/* If the MTU is relatively small, the remaining data of the first
|
|
* I/O buffer may need to be fragmented multiple times.
|
|
* For IPv4, the first I/O Buffer is reused, which have reserved
|
|
* the L4 header space, the following fragmentation flow is only
|
|
* for non-zero fragments, so following flow does not need to
|
|
* consider the L4 header
|
|
*/
|
|
|
|
while (leftlen > 0)
|
|
{
|
|
reorg = ip_fragout_allocfragbuf(fragq);
|
|
GOTO_IF(reorg == NULL, allocfail);
|
|
|
|
nfrags++;
|
|
|
|
if (leftlen + unfraglen > mtu)
|
|
{
|
|
ncopy = mtu - unfraglen;
|
|
}
|
|
else
|
|
{
|
|
ncopy = leftlen;
|
|
}
|
|
|
|
/* This is a new fragment buffer, reserve L2&L3 header space
|
|
* in the front of this buffer
|
|
*/
|
|
|
|
UPDATE_IOB(reorg, CONFIG_NET_LL_GUARDSIZE, unfraglen);
|
|
|
|
/* Then copy L4 data */
|
|
|
|
GOTO_IF(iob_trycopyin(reorg, leftstart, ncopy,
|
|
reorg->io_pktlen, false) < 0, allocfail);
|
|
|
|
leftstart += ncopy;
|
|
leftlen -= ncopy;
|
|
}
|
|
}
|
|
}
|
|
#ifdef CONFIG_NET_IPv6
|
|
else
|
|
#endif
|
|
#endif
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (domain == PF_INET6)
|
|
{
|
|
unfraglen += EXTHDR_FRAG_LEN;
|
|
|
|
/* Fragmentation requires the length field to be a multiples of 8,
|
|
* and the length of the IPv6 basic header and all extended headers
|
|
* is a multiples of 8, so here directly fix the MTU to 8-byte
|
|
* alignment.
|
|
*/
|
|
|
|
mtu = mtu & ~0x7;
|
|
|
|
/* For IPv6 fragment, a fragment header needs to be inserted before
|
|
* the l4 header, so all data must be reorganized, a space for IPv6
|
|
* header and fragment external header is reserved before l4 header
|
|
* for all fragments
|
|
*/
|
|
|
|
reorg = ip_fragout_allocfragbuf(fragq);
|
|
GOTO_IF(reorg == NULL, allocfail);
|
|
|
|
nfrags++;
|
|
|
|
/* Reserve L3 header space */
|
|
|
|
UPDATE_IOB(reorg, CONFIG_NET_LL_GUARDSIZE, unfraglen);
|
|
|
|
/* Copy L3 header(include unfragmentable extention header if present)
|
|
* from original I/O buffer
|
|
*/
|
|
|
|
orig = iob;
|
|
memcpy(reorg->io_data + reorg->io_offset,
|
|
orig->io_data + orig->io_offset, unfraglen - EXTHDR_FRAG_LEN);
|
|
iob_trimhead(orig, unfraglen - EXTHDR_FRAG_LEN);
|
|
}
|
|
#endif
|
|
|
|
/* Copy data from original I/O buffer chain 'orig' to new reorganized
|
|
* I/O buffer chain 'reorg'
|
|
*/
|
|
|
|
while (orig != NULL)
|
|
{
|
|
leftstart = orig->io_data + orig->io_offset;
|
|
leftlen = orig->io_len;
|
|
|
|
/* For each I/O buffer data of the 'orig' chain */
|
|
|
|
while (leftlen > 0)
|
|
{
|
|
/* Calculate target area size */
|
|
|
|
navail = mtu - reorg->io_pktlen;
|
|
|
|
if (navail > 0)
|
|
{
|
|
if (leftlen > navail)
|
|
{
|
|
/* Target area is too small, need expand the destination
|
|
* chain
|
|
*/
|
|
|
|
expand = true;
|
|
ncopy = navail;
|
|
}
|
|
else
|
|
{
|
|
ncopy = leftlen;
|
|
}
|
|
|
|
if (iob_trycopyin(reorg, leftstart, ncopy,
|
|
reorg->io_pktlen, false) < 0)
|
|
{
|
|
goto allocfail;
|
|
}
|
|
|
|
leftlen -= ncopy;
|
|
leftstart += ncopy;
|
|
}
|
|
else
|
|
{
|
|
expand = true;
|
|
}
|
|
|
|
if (expand)
|
|
{
|
|
reorg = ip_fragout_allocfragbuf(fragq);
|
|
GOTO_IF(reorg == NULL, allocfail);
|
|
|
|
nfrags++;
|
|
|
|
/* This is a new fragment buffer, reserve L2&L3 header space
|
|
* in the front of this buffer
|
|
*/
|
|
|
|
UPDATE_IOB(reorg, CONFIG_NET_LL_GUARDSIZE, unfraglen);
|
|
|
|
expand = false;
|
|
}
|
|
}
|
|
|
|
orig = iob_free(orig);
|
|
}
|
|
|
|
return nfrags;
|
|
|
|
allocfail:
|
|
nerr("ERROR: Fragout fail! No I/O buffer available!");
|
|
iob_free_chain(head);
|
|
iob_free_chain(orig);
|
|
iob_free_chain(reorg);
|
|
iob_free_queue(fragq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_frag_startwdog
|
|
*
|
|
* Description:
|
|
* Start the reassembly timeout timer
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void ip_frag_startwdog(void)
|
|
{
|
|
if (g_wdfragtimeout.func == NULL)
|
|
{
|
|
wd_start(&g_wdfragtimeout, REASSEMBLY_TIMEOUT_TICKS,
|
|
ip_fragin_timerout_expiry, (wdparm_t)NULL);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_frag_uninit
|
|
*
|
|
* Description:
|
|
* Uninitialize the fragment processing module.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
int32_t ip_frag_uninit(void)
|
|
{
|
|
FAR struct net_driver_s *dev;
|
|
|
|
ninfo("Uninitialize frag proccessing module\n");
|
|
|
|
/* Stop work queue */
|
|
|
|
if (!work_available(&g_wkfragtimeout))
|
|
{
|
|
ninfo("Cancel reassembly work queue\n");
|
|
work_cancel(IPFRAGWORK, &g_wkfragtimeout);
|
|
}
|
|
|
|
/* Release frag processing resources of each NIC */
|
|
|
|
net_lock();
|
|
for (dev = g_netdevices; dev; dev = dev->flink)
|
|
{
|
|
/* Is the interface in the "up" state? */
|
|
|
|
if ((dev->d_flags & IFF_UP) != 0)
|
|
{
|
|
ip_frag_stop(dev);
|
|
}
|
|
}
|
|
|
|
net_unlock();
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_frag_stop
|
|
*
|
|
* Description:
|
|
* Stop the fragment process function for the specified NIC.
|
|
*
|
|
* Input Parameters:
|
|
* dev - NIC Device instance which will be bring down
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void ip_frag_stop(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR sq_entry_t *entry = NULL;
|
|
FAR sq_entry_t *entrynext;
|
|
|
|
ninfo("Stop frag processing for NIC:%p\n", dev);
|
|
|
|
nxmutex_lock(&g_ipfrag_lock);
|
|
|
|
entry = sq_peek(&g_assemblyhead_ipid);
|
|
|
|
/* Drop those unassembled incoming fragments belonging to this NIC */
|
|
|
|
while (entry != NULL)
|
|
{
|
|
FAR struct ip_fragsnode_s *node = (FAR struct ip_fragsnode_s *)entry;
|
|
entrynext = sq_next(entry);
|
|
|
|
if (dev == node->dev)
|
|
{
|
|
if (node->frags != NULL)
|
|
{
|
|
FAR struct ip_fraglink_s *fraglink = node->frags;
|
|
|
|
while (fraglink != NULL)
|
|
{
|
|
fraglink = ip_fragin_freelink(fraglink);
|
|
}
|
|
}
|
|
|
|
ip_frag_remnode(node);
|
|
kmm_free(entry);
|
|
}
|
|
|
|
entry = entrynext;
|
|
}
|
|
|
|
nxmutex_unlock(&g_ipfrag_lock);
|
|
|
|
/* Drop those unsent outgoing fragments belonging to this NIC */
|
|
|
|
iob_free_queue(&dev->d_fragout);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_frag_remallfrags
|
|
*
|
|
* Description:
|
|
* Release all I/O Buffers used by fragment processing module when
|
|
* I/O Buffer resources are exhausted.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void ip_frag_remallfrags(void)
|
|
{
|
|
FAR sq_entry_t *entry = NULL;
|
|
FAR sq_entry_t *entrynext;
|
|
FAR struct net_driver_s *dev;
|
|
|
|
nxmutex_lock(&g_ipfrag_lock);
|
|
|
|
entry = sq_peek(&g_assemblyhead_ipid);
|
|
|
|
/* Drop all unassembled incoming fragments */
|
|
|
|
while (entry != NULL)
|
|
{
|
|
FAR struct ip_fragsnode_s *node = (FAR struct ip_fragsnode_s *)entry;
|
|
entrynext = sq_next(entry);
|
|
|
|
if (node->frags != NULL)
|
|
{
|
|
FAR struct ip_fraglink_s *fraglink = node->frags;
|
|
|
|
while (fraglink != NULL)
|
|
{
|
|
fraglink = ip_fragin_freelink(fraglink);
|
|
}
|
|
}
|
|
|
|
/* Because nodes managed by the two queues are the same,
|
|
* and g_assemblyhead_ipid will be cleared after this loop ends,
|
|
* so only reset g_assemblyhead_time is needed after this loop ends
|
|
*/
|
|
|
|
sq_rem(entry, &g_assemblyhead_ipid);
|
|
kmm_free(entry);
|
|
|
|
entry = entrynext;
|
|
}
|
|
|
|
sq_init(&g_assemblyhead_time);
|
|
g_bufoccupy = 0;
|
|
|
|
nxmutex_unlock(&g_ipfrag_lock);
|
|
|
|
/* Drop all unsent outgoing fragments */
|
|
|
|
net_lock();
|
|
for (dev = g_netdevices; dev; dev = dev->flink)
|
|
{
|
|
/* Is the interface in the "up" state? */
|
|
|
|
if ((dev->d_flags & IFF_UP) != 0)
|
|
{
|
|
iob_free_queue(&dev->d_fragout);
|
|
}
|
|
}
|
|
|
|
net_unlock();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ip_fragout
|
|
*
|
|
* Description:
|
|
* Fragout processing
|
|
*
|
|
* Input Parameters:
|
|
* dev - The NIC device
|
|
*
|
|
* Returned Value:
|
|
* A non-negative value is returned on success; negative value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int32_t ip_fragout(FAR struct net_driver_s *dev)
|
|
{
|
|
uint16_t mtu = devif_get_mtu(dev);
|
|
|
|
if (dev->d_iob == NULL || dev->d_len == 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dev->d_iob->io_pktlen <= mtu)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
#ifdef CONFIG_NET_6LOWPAN
|
|
if (dev->d_lltype == NET_LL_IEEE802154 ||
|
|
dev->d_lltype == NET_LL_PKTRADIO)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
if (devif_is_loopback(dev))
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
ninfo("pkt size: %d, MTU: %d\n", dev->d_iob->io_pktlen, mtu);
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
if (IFF_IS_IPv4(dev->d_flags))
|
|
{
|
|
return ipv4_fragout(dev, mtu);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (IFF_IS_IPv6(dev->d_flags))
|
|
{
|
|
return ipv6_fragout(dev, mtu);
|
|
}
|
|
#endif
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
#endif /* (CONFIG_NET_IPv4 || CONFIG_NET_IPv6) && CONFIG_NET_IPFRAG */
|