nuttx/net/ipfrag/ipv4_frag.c
shichunma 682ce1db2f net/ipv4: Forward fragments before local reassembly
Allow non-local IPv4 fragments to reach the forwarding path before local fragment reassembly. This lets routers forward fragmented datagrams without requiring local reassembly state.

Keep NAT44 and IP filter builds on the existing reassembly path because those features can depend on L4 headers. Preserve the original fragment offset and MF flag when fragout must split an already-fragmented packet for a smaller egress MTU.

Signed-off-by: shichunma <shichunma@bestechnic.com>
2026-08-01 17:40:07 -03:00

460 lines
12 KiB
C

/****************************************************************************
* net/ipfrag/ipv4_frag.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_IPFRAG)
#include <sys/ioctl.h>
#include <stdint.h>
#include <stdlib.h>
#include <nuttx/debug.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <net/if.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 "netdev/netdev.h"
#include "inet/inet.h"
#include "utils/utils.h"
#include "ipfrag.h"
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static inline int32_t
ipv4_fragin_getinfo(FAR struct iob_s *iob,
FAR struct ip_fraglink_s *fraglink);
static uint32_t ipv4_fragin_reassemble(FAR struct ip_fragsnode_s *node);
static inline void
ipv4_fragout_buildipv4header(FAR struct ipv4_hdr_s *ref,
FAR struct ipv4_hdr_s *ipv4,
uint16_t len, uint16_t ipoff);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ipv4_fragin_getinfo
*
* Description:
* Populate fragment information from the input ipv4 packet data.
*
* Input Parameters:
* iob - An IPv4 fragment
* fraglink - node of the lower-level linked list, it maintains information
* of one fragment
*
* Returned Value:
* None
*
****************************************************************************/
static inline int32_t
ipv4_fragin_getinfo(FAR struct iob_s *iob,
FAR struct ip_fraglink_s *fraglink)
{
FAR struct ipv4_hdr_s *ipv4 = (FAR struct ipv4_hdr_s *)
(iob->io_data + iob->io_offset);
uint16_t offset;
fraglink->flink = NULL;
fraglink->fragsnode = NULL;
fraglink->isipv4 = true;
offset = (ipv4->ipoffset[0] << 8) + ipv4->ipoffset[1];
fraglink->morefrags = offset & IP_FLAG_MOREFRAGS;
fraglink->fragoff = ((offset & 0x1fff) << 3);
fraglink->fraglen = (ipv4->len[0] << 8) + ipv4->len[1] - IPv4_HDRLEN;
fraglink->ipid = (ipv4->ipid[0] << 8) + ipv4->ipid[1];
fraglink->frag = iob;
return OK;
}
/****************************************************************************
* Name: ipv4_fragin_reassemble
*
* Description:
* Reassemble all ipv4 fragments to build an IP frame.
*
* Input Parameters:
* node - node of the upper-level linked list, it maintains
* information about all fragments belonging to an IP datagram
*
* Returned Value:
* The length of the reassembled IP frame
*
****************************************************************************/
static uint32_t ipv4_fragin_reassemble(FAR struct ip_fragsnode_s *node)
{
FAR struct iob_s *head = NULL;
FAR struct ipv4_hdr_s *ipv4;
FAR struct ip_fraglink_s *fraglink;
/* Loop to walk through the fragment list and reassemble those fragments,
* the fraglink list was ordered by fragment offset value
*/
fraglink = node->frags;
node->frags = NULL;
while (fraglink != NULL)
{
FAR struct ip_fraglink_s *linknext;
FAR struct iob_s *iob = fraglink->frag;
if (fraglink->fragoff != 0)
{
uint16_t iphdrlen;
/* Get IPv4 header length from IPv4 header (it may carry some
* IPv4 options)
*/
ipv4 = (FAR struct ipv4_hdr_s *)(head->io_data + head->io_offset);
iphdrlen = (ipv4->vhl & IPv4_HLMASK) << 2;
/* Just modify the offset and length of all none zero fragments */
iob->io_offset += iphdrlen;
iob->io_len -= iphdrlen;
iob->io_pktlen -= iphdrlen;
/* Concatenate this iob to the reassembly chain */
iob_concat(head, iob);
}
else
{
/* Remember the head iob */
head = iob;
}
linknext = fraglink->flink;
kmm_free(fraglink);
fraglink = linknext;
}
/* Remember the reassembled outgoing IP frame */
node->outgoframe = head;
/* Get pointer of the new IPv4 header */
ipv4 = (FAR struct ipv4_hdr_s *)(head->io_data + head->io_offset);
/* Update the length value in the IP Header */
ipv4->len[0] = head->io_pktlen >> 8;
ipv4->len[1] = head->io_pktlen & 0xff;
/* Set ipoffset to zero */
ipv4->ipoffset[0] = 0;
ipv4->ipoffset[1] = 0;
/* Calculate IP checksum. */
ipv4->ipchksum = 0;
ipv4->ipchksum = ~(ipv4_chksum(ipv4));
return head->io_pktlen;
}
/****************************************************************************
* Name: ipv4_fragout_buildipv4header
*
* Description:
* Build IPv4 header for an IPv4 fragment.
*
* Input Parameters:
* ref - The reference IPv4 Header
* ipv4 - The pointer of the newly generated IPv4 Header
* len - Total Length of this IP frame
* ipoff - Fragment offset
*
* Returned Value:
* None
*
****************************************************************************/
static inline void
ipv4_fragout_buildipv4header(FAR struct ipv4_hdr_s *ref,
FAR struct ipv4_hdr_s *ipv4,
uint16_t len, uint16_t ipoff)
{
if (ref != ipv4)
{
uint32_t iphdrlen = (ref->vhl & IPv4_HLMASK) << 2;
memcpy(ipv4, ref, iphdrlen);
}
ipv4->len[0] = len >> 8;
ipv4->len[1] = len & 0xff;
ipv4->ipoffset[0] = ipoff >> 8;
ipv4->ipoffset[1] = ipoff & 0xff;
/* Calculate IP checksum. */
ipv4->ipchksum = 0;
ipv4->ipchksum = ~(ipv4_chksum(ipv4));
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ipv4_fragin
*
* Description:
* Handling incoming IPv4 and IPv6 fragment input, the input data
* (dev->d_iob) can be an I/O buffer chain
*
* Input Parameters:
* dev - The NIC device that the fragmented data comes from
*
* Returned Value:
* ENOMEM - No memory
* OK - The input fragment is processed as expected
*
****************************************************************************/
int32_t ipv4_fragin(FAR struct net_driver_s *dev)
{
FAR struct ip_fragsnode_s *node;
FAR struct ip_fraglink_s *fraginfo;
bool restartwdog;
if (dev->d_len != dev->d_iob->io_pktlen)
{
nerr("ERROR: Parameters error.\n");
return -EINVAL;
}
fraginfo = kmm_malloc(sizeof(struct ip_fraglink_s));
if (fraginfo == NULL)
{
nerr("ERROR: Failed to allocate buffer.\n");
return -ENOMEM;
}
/* Populate fragment information from input packet data */
ipv4_fragin_getinfo(dev->d_iob, fraginfo);
nxmutex_lock(&g_ipfrag_lock);
/* Need to restart reassembly worker if the original linked list is empty */
restartwdog = ip_fragin_enqueue(dev, fraginfo);
node = fraginfo->fragsnode;
if (node->verifyflag & IP_FRAGVERIFY_RECVDALLFRAGS)
{
/* Well, all fragments of an IP frame have been received, remove
* node from link list first, then reassemble and dispatch to the
* stack.
*/
ip_frag_remnode(node);
/* All fragments belonging to one IP frame have been separated
* from the fragment processing module, unlocks mutex as soon
* as possible
*/
nxmutex_unlock(&g_ipfrag_lock);
/* Reassemble fragments to one IP frame and set the resulting
* IP frame to dev->d_iob
*/
ipv4_fragin_reassemble(node);
netdev_iob_replace_l2(dev, node->outgoframe);
/* Free the memory of node */
kmm_free(node);
return ipv4_input(dev);
}
nxmutex_unlock(&g_ipfrag_lock);
if (restartwdog)
{
/* Restart the work queue for fragment processing */
ip_frag_startwdog();
}
return OK;
}
/****************************************************************************
* Name: ipv4_fragout
*
* Description:
* Execute the ipv4 fragment function. After this work is done, all
* fragments are maintained by dev->d_fragout. In order to reduce the
* cyclomatic complexity and facilitate maintenance, fragmentation is
* performed in two steps:
* 1. Reconstruct I/O Buffer according to MTU, which will reserve
* the space for the L3 header;
* 2. Fill the L3 header into the reserved space.
*
* Input Parameters:
* dev - The NIC device
* mtu - The MTU of current NIC
*
* Returned Value:
* 0 if success or a negative value if fail.
*
* Assumptions:
* Data length(dev->d_iob->io_pktlen) is grater than the MTU of
* current NIC
*
****************************************************************************/
int32_t ipv4_fragout(FAR struct net_driver_s *dev, uint16_t mtu)
{
uint32_t iter;
uint32_t nfrags;
uint16_t offset = 0;
uint16_t fragoff;
uint16_t morefrags;
uint16_t hdrlen;
FAR struct iob_s *frag = NULL;
FAR struct ipv4_hdr_s *ref = NULL;
struct iob_queue_s fragq =
{
NULL, NULL
};
/* Get the total length of L3 Header(if IPv4 options are present, then this
* length includes the size of all the IPv4 options)
*/
hdrlen = (IPv4BUF->vhl & IPv4_HLMASK) << 2;
/* Preserve the original fragment offset when splitting an
* already-fragmented packet for a smaller egress MTU.
*/
fragoff = (IPv4BUF->ipoffset[0] << 8) + IPv4BUF->ipoffset[1];
morefrags = fragoff & IP_FLAG_MOREFRAGS;
fragoff &= 0x1fff;
/* Reconstruct I/O Buffer according to MTU, which will reserve
* the space for the L3 header
*/
nfrags = ip_fragout_slice(dev->d_iob, PF_INET, mtu, hdrlen, &fragq);
netdev_iob_clear(dev);
/* No I/O Buffer is the only cause of failure */
if (nfrags == 0)
{
goto fail;
}
/* Fill the L3 header into the reserved space */
for (iter = 0; iter < nfrags; iter++)
{
frag = iob_remove_queue(&fragq);
if (iter == 0)
{
ref = (FAR struct ipv4_hdr_s *)(frag->io_data + frag->io_offset);
/* Update the IPv4 header of the first fragment */
ipv4_fragout_buildipv4header(ref, ref, frag->io_pktlen,
fragoff | IP_FLAG_MOREFRAGS);
}
else
{
uint16_t ipoff = fragoff + ((offset - iter * hdrlen) >> 3);
if (iter < nfrags - 1 || morefrags)
{
ipoff |= IP_FLAG_MOREFRAGS;
}
/* Refer to the zero fragment ipv4 header to construct the ipv4
* header of non-zero fragment
*/
ipv4_fragout_buildipv4header(ref,
(FAR struct ipv4_hdr_s *)(frag->io_data + frag->io_offset),
frag->io_pktlen, ipoff);
}
/* Enqueue this fragment to dev->d_fragout */
if (iob_tryadd_queue(frag, &dev->d_fragout) < 0)
{
goto fail;
}
offset += frag->io_pktlen;
}
#ifdef CONFIG_NET_STATISTICS
g_netstats.ipv4.sent += nfrags - 1;
#endif
netdev_txnotify_dev(dev, IPFRAG_POLL);
return OK;
fail:
netdev_iob_release(dev);
iob_free_chain(frag);
iob_free_queue(&fragq);
iob_free_queue(&dev->d_fragout);
return -ENOMEM;
}
#endif /* CONFIG_NET_IPv4 && CONFIG_NET_IPFRAG */