| /**************************************************************************** |
| * 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 extension 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 processing 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 */ |