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>
This commit is contained in:
shichunma 2026-08-01 17:33:52 +08:00
parent 8cec1d01db
commit 48dfff56ae
3 changed files with 71 additions and 19 deletions

View file

@ -84,6 +84,7 @@
#include <sys/ioctl.h>
#include <stdint.h>
#include <stdbool.h>
#include <nuttx/debug.h>
#include <string.h>
@ -166,6 +167,34 @@ static int ipv4_check_opt(FAR struct ipv4_hdr_s *ipv4)
}
#endif
/****************************************************************************
* Name: ipv4_fragin_or_drop
*
* Description:
* Reassemble an incoming IPv4 fragment for local processing or drop it if
* fragment reassembly is not available.
*
****************************************************************************/
static int ipv4_fragin_or_drop(FAR struct net_driver_s *dev)
{
#ifdef CONFIG_NET_IPFRAG
if (ipv4_fragin(dev) == OK)
{
return OK;
}
#endif
#ifdef CONFIG_NET_STATISTICS
g_netstats.ipv4.drop++;
g_netstats.ipv4.fragerr++;
#endif
nwarn("WARNING: IP fragment dropped\n");
dev->d_len = 0;
return OK;
}
/****************************************************************************
* Name: ipv4_in
*
@ -199,6 +228,7 @@ static int ipv4_in(FAR struct net_driver_s *dev)
FAR struct ipv4_hdr_s *ipv4 = IPv4BUF;
in_addr_t destipaddr;
uint16_t totlen;
bool isfrag;
int ret = OK;
/* Handle ARP on input then give the IPv4 packet to the network layer */
@ -273,22 +303,7 @@ static int ipv4_in(FAR struct net_driver_s *dev)
/* Check the fragment flag. */
if ((ipv4->ipoffset[0] & 0x3f) != 0 || ipv4->ipoffset[1] != 0)
{
#ifdef CONFIG_NET_IPFRAG
if (ipv4_fragin(dev) == OK)
{
return OK;
}
#endif
#ifdef CONFIG_NET_STATISTICS
g_netstats.ipv4.drop++;
g_netstats.ipv4.fragerr++;
#endif
nwarn("WARNING: IP fragment dropped\n");
goto drop;
}
isfrag = (ipv4->ipoffset[0] & 0x3f) != 0 || ipv4->ipoffset[1] != 0;
#ifdef CONFIG_DEBUG_FEATURES
if (ipv4_check_opt(ipv4) != OK)
@ -301,6 +316,13 @@ static int ipv4_in(FAR struct net_driver_s *dev)
}
#endif
#if defined(CONFIG_NET_NAT44) || defined(CONFIG_NET_IPFILTER)
if (isfrag)
{
return ipv4_fragin_or_drop(dev);
}
#endif
#ifdef CONFIG_NET_NAT44
/* Try NAT inbound, rule matching will be performed in NAT module. */
@ -334,6 +356,11 @@ static int ipv4_in(FAR struct net_driver_s *dev)
ipv4_forward_broadcast(dev, ipv4);
#endif
if (isfrag)
{
return ipv4_fragin_or_drop(dev);
}
#if defined(CONFIG_NET_BROADCAST) && defined(NET_UDP_HAVE_STACK)
if (ipv4->proto == IP_PROTO_UDP)
{
@ -406,6 +433,16 @@ static int ipv4_in(FAR struct net_driver_s *dev)
}
}
/* Forwarding has already had a chance to consume non-local unicast
* fragments. Any remaining fragments are for local input and must be
* reassembled before the transport layer sees them.
*/
if (isfrag)
{
return ipv4_fragin_or_drop(dev);
}
#ifdef CONFIG_NET_IPV4_CHECKSUMS
if (((dev->d_features & NETDEV_RX_CSUM) == 0)
&& (ipv4_chksum(IPv4BUF) != 0xffff))

View file

@ -77,6 +77,11 @@ static int ipv4_hdrsize(FAR struct ipv4_hdr_s *ipv4)
iphdrlen = (ipv4->vhl & IPv4_HLMASK) << 2;
if ((ipv4->ipoffset[0] & 0x3f) != 0 || ipv4->ipoffset[1] != 0)
{
return iphdrlen;
}
/* Size is also determined by the following protocol header, */
switch (ipv4->proto)

View file

@ -360,6 +360,8 @@ 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;
@ -374,6 +376,14 @@ int32_t ipv4_fragout(FAR struct net_driver_s *dev, uint16_t mtu)
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
*/
@ -401,13 +411,13 @@ int32_t ipv4_fragout(FAR struct net_driver_s *dev, uint16_t mtu)
/* Update the IPv4 header of the first fragment */
ipv4_fragout_buildipv4header(ref, ref, frag->io_pktlen,
IP_FLAG_MOREFRAGS);
fragoff | IP_FLAG_MOREFRAGS);
}
else
{
uint16_t ipoff = (offset - iter * hdrlen) >> 3;
uint16_t ipoff = fragoff + ((offset - iter * hdrlen) >> 3);
if (iter < nfrags - 1)
if (iter < nfrags - 1 || morefrags)
{
ipoff |= IP_FLAG_MOREFRAGS;
}