From 48dfff56aea9811693a870ac751400bf40279c41 Mon Sep 17 00:00:00 2001 From: shichunma Date: Sat, 1 Aug 2026 17:33:52 +0800 Subject: [PATCH 1/3] 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 --- net/devif/ipv4_input.c | 69 +++++++++++++++++++++++++++--------- net/ipforward/ipv4_forward.c | 5 +++ net/ipfrag/ipv4_frag.c | 16 +++++++-- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/net/devif/ipv4_input.c b/net/devif/ipv4_input.c index 77ea651499b..eb880ad2e90 100644 --- a/net/devif/ipv4_input.c +++ b/net/devif/ipv4_input.c @@ -84,6 +84,7 @@ #include #include +#include #include #include @@ -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)) diff --git a/net/ipforward/ipv4_forward.c b/net/ipforward/ipv4_forward.c index 5bd509042e7..24e2d256b24 100644 --- a/net/ipforward/ipv4_forward.c +++ b/net/ipforward/ipv4_forward.c @@ -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) diff --git a/net/ipfrag/ipv4_frag.c b/net/ipfrag/ipv4_frag.c index b5628f28a78..314ec3f15f6 100644 --- a/net/ipfrag/ipv4_frag.c +++ b/net/ipfrag/ipv4_frag.c @@ -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; } From 6dfb82dc4a7264b440fb4680f395ceeb8a853761 Mon Sep 17 00:00:00 2001 From: shichunma Date: Sat, 1 Aug 2026 21:54:32 +0800 Subject: [PATCH 2/3] net/nat: Reassemble IPv6 fragments before NAT66 NAT66 needs transport headers to create or match address and port mappings. Consume IPv6 fragments through local reassembly before NAT66 processing so forwarded fragmented traffic is not translated fragment-by-fragment without L4 context. Also dispatch inbound NAT66 using the parsed IPv6 next-header value, matching the outbound path and allowing packets with extension headers before the transport header to be translated. Signed-off-by: shichunma --- net/devif/ipv6_input.c | 51 +++++++++++++++++++++++++++++++----------- net/nat/ipv6_nat.c | 2 +- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/net/devif/ipv6_input.c b/net/devif/ipv6_input.c index dc1c04360de..7236d320a10 100644 --- a/net/devif/ipv6_input.c +++ b/net/devif/ipv6_input.c @@ -151,6 +151,36 @@ static bool check_destipaddr(FAR struct net_driver_s *dev, return false; } +#if defined(CONFIG_NET_IPFRAG) || defined(CONFIG_NET_NAT66) +/**************************************************************************** + * Name: ipv6_fragin_or_drop + * + * Description: + * Reassemble an incoming IPv6 fragment for NAT or local processing or drop + * it if fragment reassembly is not available. + * + ****************************************************************************/ + +static int ipv6_fragin_or_drop(FAR struct net_driver_s *dev) +{ +#ifdef CONFIG_NET_IPFRAG + if (ipv6_fragin(dev) == OK) + { + return OK; + } +#endif + +#ifdef CONFIG_NET_STATISTICS + g_netstats.ipv6.drop++; + g_netstats.ipv6.fragerr++; +#endif + nwarn("WARNING: IPv6 fragment dropped\n"); + + dev->d_len = 0; + return OK; +} +#endif + /**************************************************************************** * Name: ipv6_in * @@ -195,7 +225,7 @@ static int ipv6_in(FAR struct net_driver_s *dev) #ifdef CONFIG_NET_IPFORWARD int ret; #endif -#ifdef CONFIG_NET_IPFRAG +#if defined(CONFIG_NET_IPFRAG) || defined(CONFIG_NET_NAT66) bool isfrag = false; #endif @@ -287,7 +317,7 @@ static int ipv6_in(FAR struct net_driver_s *dev) if (nxthdr == NEXT_FRAGMENT_EH) { extlen = EXTHDR_FRAG_LEN; -#ifdef CONFIG_NET_IPFRAG +#if defined(CONFIG_NET_IPFRAG) || defined(CONFIG_NET_NAT66) isfrag = true; #endif } @@ -302,6 +332,11 @@ static int ipv6_in(FAR struct net_driver_s *dev) } #ifdef CONFIG_NET_NAT66 + if (isfrag) + { + return ipv6_fragin_or_drop(dev); + } + /* Try NAT inbound, rule matching will be performed in NAT module. */ ipv6_nat_inbound(dev, ipv6); @@ -410,17 +445,7 @@ static int ipv6_in(FAR struct net_driver_s *dev) #ifdef CONFIG_NET_IPFRAG if (isfrag) { - if (ipv6_fragin(dev) == OK) - { - return OK; - } - else - { -#ifdef CONFIG_NET_STATISTICS - g_netstats.ipv6.fragerr++; -#endif - goto drop; - } + return ipv6_fragin_or_drop(dev); } #endif diff --git a/net/nat/ipv6_nat.c b/net/nat/ipv6_nat.c index 1c0f16ec955..4a40d2cc4be 100644 --- a/net/nat/ipv6_nat.c +++ b/net/nat/ipv6_nat.c @@ -540,7 +540,7 @@ ipv6_nat_inbound_internal(FAR struct ipv6_hdr_s *ipv6, uint8_t proto; FAR void *l4hdr = net_ipv6_payload(ipv6, &proto); - switch (ipv6->proto) + switch (proto) { #ifdef CONFIG_NET_TCP case IP_PROTO_TCP: From 21b8e01a0f20f1824516240bc9ecc9962ea7b108 Mon Sep 17 00:00:00 2001 From: shichunma Date: Sat, 1 Aug 2026 22:09:51 +0800 Subject: [PATCH 3/3] net/ipfilter: Reassemble IPv6 fragments before filtering IPv6 input currently lets fragmented packets continue into the IP filter path before reassembly. Forwarded IP filter rules can inspect L4 fields, but non-first IPv6 fragments do not carry the transport header. Consume IPv6 fragments through the existing reassembly/drop helper before NAT66 or IP filter processing. This keeps plain IPv6 forwarding unchanged when neither L4-dependent feature is enabled, while avoiding filtering incomplete fragments. Signed-off-by: shichunma --- net/devif/ipv6_input.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/net/devif/ipv6_input.c b/net/devif/ipv6_input.c index 7236d320a10..3400fdebd79 100644 --- a/net/devif/ipv6_input.c +++ b/net/devif/ipv6_input.c @@ -151,7 +151,7 @@ static bool check_destipaddr(FAR struct net_driver_s *dev, return false; } -#if defined(CONFIG_NET_IPFRAG) || defined(CONFIG_NET_NAT66) +#if defined(CONFIG_NET_IPFRAG) || defined(CONFIG_NET_NAT66) || defined(CONFIG_NET_IPFILTER) /**************************************************************************** * Name: ipv6_fragin_or_drop * @@ -225,7 +225,7 @@ static int ipv6_in(FAR struct net_driver_s *dev) #ifdef CONFIG_NET_IPFORWARD int ret; #endif -#if defined(CONFIG_NET_IPFRAG) || defined(CONFIG_NET_NAT66) +#if defined(CONFIG_NET_IPFRAG) || defined(CONFIG_NET_NAT66) || defined(CONFIG_NET_IPFILTER) bool isfrag = false; #endif @@ -317,7 +317,7 @@ static int ipv6_in(FAR struct net_driver_s *dev) if (nxthdr == NEXT_FRAGMENT_EH) { extlen = EXTHDR_FRAG_LEN; -#if defined(CONFIG_NET_IPFRAG) || defined(CONFIG_NET_NAT66) +#if defined(CONFIG_NET_IPFRAG) || defined(CONFIG_NET_NAT66) || defined(CONFIG_NET_IPFILTER) isfrag = true; #endif } @@ -331,12 +331,14 @@ static int ipv6_in(FAR struct net_driver_s *dev) nxthdr = exthdr->nxthdr; } -#ifdef CONFIG_NET_NAT66 +#if defined(CONFIG_NET_NAT66) || defined(CONFIG_NET_IPFILTER) if (isfrag) { return ipv6_fragin_or_drop(dev); } +#endif +#ifdef CONFIG_NET_NAT66 /* Try NAT inbound, rule matching will be performed in NAT module. */ ipv6_nat_inbound(dev, ipv6);