gso: fix ip fragment support for gso packet 36/43336/3
authorMohsin Kazmi <[email protected]>
Thu, 26 Jun 2025 10:14:18 +0000 (10:14 +0000)
committerFlorin Coras <[email protected]>
Mon, 30 Jun 2025 20:24:39 +0000 (20:24 +0000)
Type: fix

GSO packets must be fragmented if the egress interface does not
support GSO offload and its MTU is smaller than the combined
size of the GSO chunk and protocol headers.

Signed-off-by: Mohsin Kazmi <[email protected]>
Change-Id: I94dff39c475a6ba31b1d8f4517e84be3ab506607

src/vnet/buffer.h
src/vnet/gso/node.c
src/vnet/ip/ip_frag.c
test/test_gso.py

index ae89915..c9005b7 100644 (file)
@@ -530,10 +530,15 @@ STATIC_ASSERT (sizeof (vnet_buffer_opaque2_t) ==
                 STRUCT_SIZE_OF (vlib_buffer_t, opaque2),
               "VNET buffer opaque2 meta-data too large for vlib_buffer");
 
-#define gso_mtu_sz(b) (vnet_buffer2(b)->gso_size + \
-                       vnet_buffer2(b)->gso_l4_hdr_sz + \
-                       vnet_buffer(b)->l4_hdr_offset - \
-                       vnet_buffer (b)->l3_hdr_offset)
+#define gso_mtu_tunnel_size(b)                                                \
+  ((vnet_buffer (b)->oflags & VNET_BUFFER_OFFLOAD_F_TNL_MASK) ?               \
+     vnet_buffer (b)->l3_hdr_offset - vnet_buffer2 (b)->outer_l3_hdr_offset : \
+     0)
+
+#define gso_mtu_sz(b)                                                         \
+  (vnet_buffer2 (b)->gso_size + vnet_buffer2 (b)->gso_l4_hdr_sz +             \
+   vnet_buffer (b)->l4_hdr_offset - vnet_buffer (b)->l3_hdr_offset +          \
+   gso_mtu_tunnel_size (b))
 
 format_function_t format_vnet_buffer_no_chain;
 format_function_t format_vnet_buffer;
index c4f4b74..1fabe44 100644 (file)
@@ -135,10 +135,10 @@ tso_segment_vxlan_tunnel_headers_fixup (vlib_main_t *vm, vlib_buffer_t *b)
       ip4->length = clib_host_to_net_u16 (
        b->current_length - (outer_l3_hdr_offset - b->current_data));
       ip4->checksum = ip4_header_checksum (ip4);
+      udp->length = clib_host_to_net_u16 (
+       b->current_length - (outer_l4_hdr_offset - b->current_data));
       if (vnet_buffer (b)->oflags & VNET_BUFFER_OFFLOAD_F_OUTER_UDP_CKSUM)
        {
-         udp->length = clib_host_to_net_u16 (
-           b->current_length - (outer_l4_hdr_offset - b->current_data));
          // udp checksum is 0, in udp tunnel
          udp->checksum = 0;
        }
@@ -151,11 +151,11 @@ tso_segment_vxlan_tunnel_headers_fixup (vlib_main_t *vm, vlib_buffer_t *b)
     {
       ip6->payload_length = clib_host_to_net_u16 (
        b->current_length - (outer_l4_hdr_offset - b->current_data));
+      udp->length = ip6->payload_length;
 
       if (vnet_buffer (b)->oflags & VNET_BUFFER_OFFLOAD_F_OUTER_UDP_CKSUM)
        {
          int bogus;
-         udp->length = ip6->payload_length;
          // udp checksum is 0, in udp tunnel
          udp->checksum = 0;
          udp->checksum =
index 729aa67..aa7a031 100644 (file)
@@ -103,9 +103,10 @@ ip4_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u16 mtu,
 
   if (from_b->flags & VNET_BUFFER_F_OFFLOAD)
     {
-      ASSERT ((from_b->flags & VNET_BUFFER_F_GSO) == 0);
       vnet_calc_checksums_inline (vm, from_b, 1 /* is_v4 */, 0 /* is_v6 */);
       vnet_calc_outer_checksums_inline (vm, from_b);
+      /* Packet is going to be fragmented, so remove GSO flag */
+      from_b->flags &= ~VNET_BUFFER_F_GSO;
     }
 
   org_from_packet = vlib_buffer_get_current (from_b);
@@ -385,9 +386,10 @@ ip6_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u16 mtu,
 
   if (from_b->flags & VNET_BUFFER_F_OFFLOAD)
     {
-      ASSERT ((from_b->flags & VNET_BUFFER_F_GSO) == 0);
       vnet_calc_checksums_inline (vm, from_b, 0 /* is_v4 */, 1 /* is_v6 */);
       vnet_calc_outer_checksums_inline (vm, from_b);
+      /* Packet is going to be fragmented, so remove GSO flag */
+      from_b->flags &= ~VNET_BUFFER_F_GSO;
     }
 
   org_from_packet = vlib_buffer_get_current (from_b);
index c3822b0..63b42c9 100644 (file)
@@ -14,7 +14,7 @@ from scapy.packet import Raw
 from scapy.layers.l2 import GRE
 from scapy.layers.inet6 import IPv6, Ether, IP, ICMPv6PacketTooBig
 from scapy.layers.inet6 import ipv6nh, IPerror6
-from scapy.layers.inet import TCP, ICMP
+from scapy.layers.inet import TCP, ICMP, UDP, defragment
 from scapy.layers.vxlan import VXLAN
 from scapy.layers.ipsec import ESP
 
@@ -49,12 +49,16 @@ class TestGSO(VppTestCase):
     def setUpClass(self):
         super(TestGSO, self).setUpClass()
         res = self.create_pg_interfaces(range(2))
-        res_gso = self.create_pg_interfaces(range(2, 4), 1, 1460)
-        self.create_pg_interfaces(range(4, 5), 1, 8940)
+        res_gso1 = self.create_pg_interfaces(range(2, 3), 1, 1460)
+        res_gso2 = self.create_pg_interfaces(range(3, 4), 1, 1440)
+        self.pg_interfaces = self.create_pg_interfaces(range(4, 5), 1, 8940)
         self.pg_interfaces.append(res[0])
         self.pg_interfaces.append(res[1])
-        self.pg_interfaces.append(res_gso[0])
-        self.pg_interfaces.append(res_gso[1])
+        self.pg_interfaces.append(res_gso1[0])
+        self.pg_interfaces.append(res_gso2[0])
+        self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1500, 0, 0, 0])
+        self.vapi.sw_interface_set_mtu(self.pg2.sw_if_index, [1500, 0, 0, 0])
+        self.vapi.sw_interface_set_mtu(self.pg3.sw_if_index, [1500, 0, 0, 0])
 
     @classmethod
     def tearDownClass(self):
@@ -85,13 +89,34 @@ class TestGSO(VppTestCase):
             vni=self.single_tunnel_bd,
         )
 
-        self.ipip4 = VppIpIpTunInterface(
+        self.single_tunnel_bd2 = 20
+        self.vxlan3 = VppVxlanTunnel(
+            self,
+            src=self.pg1.local_ip4,
+            dst=self.pg1.remote_ip4,
+            vni=self.single_tunnel_bd2,
+        )
+        self.vxlan4 = VppVxlanTunnel(
+            self,
+            src=self.pg1.local_ip6,
+            dst=self.pg1.remote_ip6,
+            vni=self.single_tunnel_bd2,
+        )
+
+        self.ipip4_0 = VppIpIpTunInterface(
             self, self.pg0, self.pg0.local_ip4, self.pg0.remote_ip4
         )
-        self.ipip6 = VppIpIpTunInterface(
+        self.ipip6_0 = VppIpIpTunInterface(
             self, self.pg0, self.pg0.local_ip6, self.pg0.remote_ip6
         )
 
+        self.ipip4_1 = VppIpIpTunInterface(
+            self, self.pg1, self.pg1.local_ip4, self.pg1.remote_ip4
+        )
+        self.ipip6_1 = VppIpIpTunInterface(
+            self, self.pg1, self.pg1.local_ip6, self.pg1.remote_ip6
+        )
+
         self.gre4 = VppGreInterface(self, self.pg0.local_ip4, self.pg0.remote_ip4)
         self.gre6 = VppGreInterface(self, self.pg0.local_ip6, self.pg0.remote_ip6)
 
@@ -103,6 +128,13 @@ class TestGSO(VppTestCase):
                 i.unconfig_ip6()
                 i.admin_down()
 
+    def get_mtu(self, sw_if_index):
+        rv = self.vapi.sw_interface_dump(sw_if_index=sw_if_index)
+        for i in rv:
+            if i.sw_if_index == sw_if_index:
+                return i.mtu[0]
+        return 0
+
     def test_gso(self):
         """GSO test"""
         #
@@ -194,19 +226,19 @@ class TestGSO(VppTestCase):
         # ipv6
         #
         p61 = (
-            Ether(src=self.pg2.remote_mac, dst=self.pg2.local_mac)
-            / IPv6(src=self.pg2.remote_ip6, dst=self.pg3.remote_ip6)
+            Ether(src=self.pg3.remote_mac, dst=self.pg3.local_mac)
+            / IPv6(src=self.pg3.remote_ip6, dst=self.pg2.remote_ip6)
             / TCP(sport=1234, dport=1234)
             / Raw(b"\xa5" * 65200)
         )
 
-        rxs = self.send_and_expect(self.pg2, 100 * [p61], self.pg3, 100)
+        rxs = self.send_and_expect(self.pg3, 100 * [p61], self.pg2, 100)
 
         for rx in rxs:
-            self.assertEqual(rx[Ether].src, self.pg3.local_mac)
-            self.assertEqual(rx[Ether].dst, self.pg3.remote_mac)
-            self.assertEqual(rx[IPv6].src, self.pg2.remote_ip6)
-            self.assertEqual(rx[IPv6].dst, self.pg3.remote_ip6)
+            self.assertEqual(rx[Ether].src, self.pg2.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg2.remote_mac)
+            self.assertEqual(rx[IPv6].src, self.pg3.remote_ip6)
+            self.assertEqual(rx[IPv6].dst, self.pg2.remote_ip6)
             self.assertEqual(rx[IPv6].plen, 65220)  # 65200 + 20 (TCP)
             self.assertEqual(rx[TCP].sport, 1234)
             self.assertEqual(rx[TCP].dport, 1234)
@@ -290,6 +322,7 @@ class TestGSO(VppTestCase):
             self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
             self.assertEqual(rx[IP].src, self.pg2.remote_ip4)
             self.assertEqual(rx[IP].dst, self.pg1.remote_ip4)
+            self.assertTrue((rx[IP].flags == "MF") or (rx[IP].frag != 0))
             self.assert_ip_checksum_valid(rx)
             size += rx[IP].len - 20
         size -= 20 * 5  # TCP header
@@ -322,12 +355,11 @@ class TestGSO(VppTestCase):
             self.assertEqual(rx[IPerror6].plen - 20, 65200)
 
         #
-        # Send jumbo frame with gso enabled only on input interface with 9K MTU
-        # and DF bit is unset. GSO packet will be fragmented. MSS is 8960. GSO
+        # Send jumbo frame with gso enabled only on input interface with 9K MTU.
+        # GSO packet will be chunked. MSS is 8960. GSO
         # size will be min(MSS, 2048 - 14 - 20) vlib_buffer_t size
         #
         self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [9000, 0, 0, 0])
-        self.vapi.sw_interface_set_mtu(self.pg4.sw_if_index, [9000, 0, 0, 0])
         p44 = (
             Ether(src=self.pg4.remote_mac, dst=self.pg4.local_mac)
             / IP(src=self.pg4.remote_ip4, dst=self.pg1.remote_ip4)
@@ -372,6 +404,39 @@ class TestGSO(VppTestCase):
             size += payload_len
         self.assertEqual(size, 65200 * 5)
 
+        #
+        # Send jumbo frame with gso enabled only on input interface with 9K MTU.
+        # DF bit is unset. GSO packet will be fragmented.
+        #
+        self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [8000, 0, 0, 0])
+
+        rxs = self.send_and_expect(self.pg4, 5 * [p44], self.pg1, 165)
+        size = 0
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg4.remote_ip4)
+            self.assertEqual(rx[IP].dst, self.pg1.remote_ip4)
+            self.assert_ip_checksum_valid(rx)
+            self.assertTrue((rx[IP].flags == "MF") or (rx[IP].frag != 0))
+            size += rx[IP].len - 20  # len - 20 (IP4)
+        size -= 20 * 5  # TCP header
+        self.assertEqual(size, 65200 * 5)
+
+        rxs = self.send_and_expect_some(self.pg4, 5 * [p64], self.pg4, 5)
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg4.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg4.remote_mac)
+            self.assertEqual(rx[IPv6].src, self.pg4.local_ip6)
+            self.assertEqual(rx[IPv6].dst, self.pg4.remote_ip6)
+            self.assertEqual(rx[IPv6].plen, 1240)  # MTU - IPv6 header
+            self.assertEqual(ipv6nh[rx[IPv6].nh], "ICMPv6")
+            self.assertEqual(rx[ICMPv6PacketTooBig].mtu, 8000)
+            self.assertEqual(rx[IPerror6].src, self.pg4.remote_ip6)
+            self.assertEqual(rx[IPerror6].dst, self.pg1.remote_ip6)
+            self.assertEqual(rx[IPerror6].plen - 20, 65200)
+
+        self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1500, 0, 0, 0])
         self.vapi.feature_gso_enable_disable(
             sw_if_index=self.pg0.sw_if_index, enable_disable=0
         )
@@ -384,7 +449,6 @@ class TestGSO(VppTestCase):
     )
     def test_gso_vxlan(self):
         """GSO VXLAN test"""
-        self.logger.info(self.vapi.cli("sh int addr"))
         #
         # Send jumbo frame with gso enabled only on input interface and
         # create VXLAN VTEP on VPP pg0, and put vxlan_tunnel0 and pg2
@@ -555,9 +619,81 @@ class TestGSO(VppTestCase):
             sw_if_index=self.pg0.sw_if_index, enable_disable=0
         )
 
+        #
+        # IPv4/IPv4 - VXLAN - Fragmented test
+        # Send jumbo frame with gso enabled only on input interface and
+        # create VXLAN VTEP on VPP pg1, and put vxlan_tunnel and pg2
+        # into BD.
+        # Packets will be fragmented as
+        # gso_size (1460) + headers size (50 vxlan encap + 20 inner IPv4 + 20 TCP)
+        # is larger than MTU of the output interface (1500).
+        #
+
+        self.vxlan3.add_vpp_config()
+        self.vapi.sw_interface_set_l2_bridge(
+            rx_sw_if_index=self.vxlan3.sw_if_index, bd_id=self.single_tunnel_bd2
+        )
+        self.vapi.sw_interface_set_l2_bridge(
+            rx_sw_if_index=self.pg2.sw_if_index, bd_id=self.single_tunnel_bd2
+        )
+        self.vapi.feature_gso_enable_disable(
+            sw_if_index=self.pg1.sw_if_index, enable_disable=1
+        )
+
+        p67 = (
+            Ether(src=self.pg2.remote_mac, dst=self.pg2.local_mac)
+            / IP(src=self.pg2.remote_ip4, dst="172.16.3.3")
+            / TCP(sport=1234, dport=1234)
+            / Raw(b"\xa5" * 65200)
+        )
+
+        rxs = self.send_and_expect(self.pg2, 5 * [p67], self.pg1, 225)
+        size = 0
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg1.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg1.remote_ip4)
+            self.assert_ip_checksum_valid(rx)
+            self.assertTrue((rx[IP].flags == "MF") or (rx[IP].frag != 0))
+            size += rx[IP].len - 20  # outer IP len
+        size -= (
+            8 + 8 + 14 + 20 + 20
+        ) * 5  # UDP header + VXLAN header + Ethernet header + inner IP header + TCP header
+        self.assertEqual(size, 65200 * 5)
+
+        assembled_pkt = defragment(rxs)
+        for rx in assembled_pkt:
+            self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg1.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg1.remote_ip4)
+            self.assertEqual(
+                rx[IP].len, 65290
+            )  # 65200 + 50 (VXLAN encap) + 20 (IP) + 20 (TCP)
+            self.assertEqual(
+                rx[UDP].len, 65270
+            )  # 65200 + 50 (VXLAN encap) - 20 (outer IP) + 20 (IP) + 20 (TCP)
+            self.assert_ip_checksum_valid(rx)
+            self.assert_udp_checksum_valid(rx, ignore_zero_checksum=True)
+            self.assertEqual(rx[VXLAN].vni, 20)
+            inner = rx[VXLAN].payload
+            self.assertEqual(inner[Ether].src, self.pg2.remote_mac)
+            self.assertEqual(inner[Ether].dst, self.pg2.local_mac)
+            self.assertEqual(inner[IP].src, self.pg2.remote_ip4)
+            self.assertEqual(inner[IP].dst, "172.16.3.3")
+            self.assert_ip_checksum_valid(inner)
+            self.assert_tcp_checksum_valid(inner)
+            self.assertEqual(inner[IP].len - 20 - 20, 65200)
+
+        self.vxlan3.remove_vpp_config()
+
+        self.vapi.feature_gso_enable_disable(
+            sw_if_index=self.pg1.sw_if_index, enable_disable=0
+        )
+
     def test_gso_ipip(self):
         """GSO IPIP test"""
-        self.logger.info(self.vapi.cli("sh int addr"))
         #
         # Send jumbo frame with gso enabled only on input interface and
         # create IPIP tunnel on VPP pg0.
@@ -569,11 +705,11 @@ class TestGSO(VppTestCase):
         #
         # enable ipip4
         #
-        self.ipip4.add_vpp_config()
+        self.ipip4_0.add_vpp_config()
 
         # Set interface up and enable IP on it
-        self.ipip4.admin_up()
-        self.ipip4.set_unnumbered(self.pg0.sw_if_index)
+        self.ipip4_0.admin_up()
+        self.ipip4_0.set_unnumbered(self.pg0.sw_if_index)
 
         # Add IPv4 routes via tunnel interface
         self.ip4_via_ip4_tunnel = VppIpRoute(
@@ -583,7 +719,7 @@ class TestGSO(VppTestCase):
             [
                 VppRoutePath(
                     "0.0.0.0",
-                    self.ipip4.sw_if_index,
+                    self.ipip4_0.sw_if_index,
                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP4,
                 )
             ],
@@ -627,7 +763,7 @@ class TestGSO(VppTestCase):
             [
                 VppRoutePath(
                     "::",
-                    self.ipip4.sw_if_index,
+                    self.ipip4_0.sw_if_index,
                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
                 )
             ],
@@ -671,7 +807,7 @@ class TestGSO(VppTestCase):
             sw_if_index=self.pg0.sw_if_index, enable_disable=0
         )
         self.vapi.feature_gso_enable_disable(
-            sw_if_index=self.ipip4.sw_if_index, enable_disable=1
+            sw_if_index=self.ipip4_0.sw_if_index, enable_disable=1
         )
 
         rxs = self.send_and_expect(self.pg2, 5 * [p47], self.pg0, 225)
@@ -698,11 +834,11 @@ class TestGSO(VppTestCase):
         # disable ipip4
         #
         self.vapi.feature_gso_enable_disable(
-            sw_if_index=self.ipip4.sw_if_index, enable_disable=0
+            sw_if_index=self.ipip4_0.sw_if_index, enable_disable=0
         )
         self.ip4_via_ip4_tunnel.remove_vpp_config()
         self.ip6_via_ip4_tunnel.remove_vpp_config()
-        self.ipip4.remove_vpp_config()
+        self.ipip4_0.remove_vpp_config()
 
         #
         # enable ipip6
@@ -710,11 +846,11 @@ class TestGSO(VppTestCase):
         self.vapi.feature_gso_enable_disable(
             sw_if_index=self.pg0.sw_if_index, enable_disable=1
         )
-        self.ipip6.add_vpp_config()
+        self.ipip6_0.add_vpp_config()
 
         # Set interface up and enable IP on it
-        self.ipip6.admin_up()
-        self.ipip6.set_unnumbered(self.pg0.sw_if_index)
+        self.ipip6_0.admin_up()
+        self.ipip6_0.set_unnumbered(self.pg0.sw_if_index)
 
         # Add IPv4 routes via tunnel interface
         self.ip4_via_ip6_tunnel = VppIpRoute(
@@ -724,7 +860,7 @@ class TestGSO(VppTestCase):
             [
                 VppRoutePath(
                     "0.0.0.0",
-                    self.ipip6.sw_if_index,
+                    self.ipip6_0.sw_if_index,
                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP4,
                 )
             ],
@@ -767,7 +903,7 @@ class TestGSO(VppTestCase):
             [
                 VppRoutePath(
                     "::",
-                    self.ipip6.sw_if_index,
+                    self.ipip6_0.sw_if_index,
                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
                 )
             ],
@@ -807,12 +943,135 @@ class TestGSO(VppTestCase):
         #
         self.ip4_via_ip6_tunnel.remove_vpp_config()
         self.ip6_via_ip6_tunnel.remove_vpp_config()
-        self.ipip6.remove_vpp_config()
+        self.ipip6_0.remove_vpp_config()
 
         self.vapi.feature_gso_enable_disable(
             sw_if_index=self.pg0.sw_if_index, enable_disable=0
         )
 
+        #
+        # IPIP - Fragmented test
+        # enable ipip4
+        #
+        self.vapi.feature_gso_enable_disable(
+            sw_if_index=self.pg1.sw_if_index, enable_disable=1
+        )
+        self.ipip4_1.add_vpp_config()
+
+        # Set interface up and enable IP on it
+        self.ipip4_1.admin_up()
+        self.ipip4_1.set_unnumbered(self.pg1.sw_if_index)
+
+        # Add IPv4 routes via tunnel interface
+        self.ip4_via_ip4_tunnel_1 = VppIpRoute(
+            self,
+            "172.16.10.0",
+            24,
+            [
+                VppRoutePath(
+                    "0.0.0.0",
+                    self.ipip4_1.sw_if_index,
+                    proto=FibPathProto.FIB_PATH_NH_PROTO_IP4,
+                )
+            ],
+        )
+        self.ip4_via_ip4_tunnel_1.add_vpp_config()
+
+        p47_1 = (
+            Ether(src=self.pg2.remote_mac, dst=self.pg2.local_mac)
+            / IP(src=self.pg2.remote_ip4, dst="172.16.10.3")
+            / TCP(sport=1234, dport=1234)
+            / Raw(b"\xa5" * 65200)
+        )
+
+        rxs = self.send_and_expect(self.pg2, 5 * [p47_1], self.pg1, 225)
+        size = 0
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg1.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg1.remote_ip4)
+            self.assert_ip_checksum_valid(rx)
+            self.assertTrue((rx[IP].flags == "MF") or (rx[IP].frag != 0))
+            size += rx[IP].len - 20  # outer IP len
+        size -= (20 + 20) * 5  # inner IP header + TCP header
+        self.assertEqual(size, 65200 * 5)
+
+        assembled_pkt = defragment(rxs)
+        for rx in assembled_pkt:
+            self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg1.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg1.remote_ip4)
+            self.assertEqual(
+                rx[IP].len, 65260
+            )  # 65200 + 20 (outer IP) + 20 (inner IP) + 20 (TCP)
+            self.assert_ip_checksum_valid(rx)
+            inner = rx[IP].payload
+            self.assertEqual(inner[IP].src, self.pg2.remote_ip4)
+            self.assertEqual(inner[IP].dst, "172.16.10.3")
+            self.assert_ip_checksum_valid(inner)
+            self.assert_tcp_checksum_valid(inner)
+            self.assertEqual(inner[IP].len - 20 - 20, 65200)
+
+        self.ip4_via_ip4_tunnel_1.remove_vpp_config()
+        self.ipip4_1.remove_vpp_config()
+
+        self.vapi.feature_gso_enable_disable(
+            sw_if_index=self.pg1.sw_if_index, enable_disable=0
+        )
+
+        #
+        # enable ipip6
+        #
+        self.vapi.feature_gso_enable_disable(
+            sw_if_index=self.pg1.sw_if_index, enable_disable=1
+        )
+        self.ipip6_1.add_vpp_config()
+        # Set interface up and enable IP on it
+        self.ipip6_1.admin_up()
+        self.ipip6_1.set_unnumbered(self.pg1.sw_if_index)
+        # Add IPv6 routes via tunnel interface
+        self.ip6_via_ip6_tunnel_1 = VppIpRoute(
+            self,
+            "fd01:10::",
+            64,
+            [
+                VppRoutePath(
+                    "::",
+                    self.ipip6_1.sw_if_index,
+                    proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
+                )
+            ],
+        )
+        self.ip6_via_ip6_tunnel_1.add_vpp_config()
+
+        p68_1 = (
+            Ether(src=self.pg3.remote_mac, dst=self.pg3.local_mac)
+            / IPv6(src=self.pg3.remote_ip6, dst="fd01:10::3")
+            / TCP(sport=1234, dport=1234)
+            / Raw(b"\xa5" * 65200)
+        )
+
+        rxs = self.send_and_expect(self.pg3, 5 * [p68_1], self.pg1, 230)
+        size = 0
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+            self.assertEqual(rx[IPv6].src, self.pg1.local_ip6)
+            self.assertEqual(rx[IPv6].dst, self.pg1.remote_ip6)
+            self.assertEqual(ipv6nh[rx[IPv6].nh], "Fragment Header")
+            size += rx[IPv6].plen - 8  # remove fragment header size
+        size -= (40 + 20) * 5  # inner IPv6 header + TCP header
+        self.assertEqual(size, 65200 * 5)
+
+        self.ip6_via_ip6_tunnel_1.remove_vpp_config()
+        self.ipip6_1.remove_vpp_config()
+
+        self.vapi.feature_gso_enable_disable(
+            sw_if_index=self.pg1.sw_if_index, enable_disable=0
+        )
+
     def test_gso_gre(self):
         """GSO GRE test"""
         #
@@ -1019,9 +1278,9 @@ class TestGSO(VppTestCase):
         #
         # enable ipip4
         #
-        self.ipip4.add_vpp_config()
+        self.ipip4_0.add_vpp_config()
         self.vapi.feature_gso_enable_disable(
-            sw_if_index=self.ipip4.sw_if_index, enable_disable=1
+            sw_if_index=self.ipip4_0.sw_if_index, enable_disable=1
         )
 
         # Add IPv4 routes via tunnel interface
@@ -1032,7 +1291,7 @@ class TestGSO(VppTestCase):
             [
                 VppRoutePath(
                     "0.0.0.0",
-                    self.ipip4.sw_if_index,
+                    self.ipip4_0.sw_if_index,
                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP4,
                 )
             ],
@@ -1042,7 +1301,7 @@ class TestGSO(VppTestCase):
         # IPSec config
         self.ipv4_params = IPsecIPv4Params()
         self.encryption_type = ESP
-        config_tun_params(self.ipv4_params, self.encryption_type, self.ipip4)
+        config_tun_params(self.ipv4_params, self.encryption_type, self.ipip4_0)
 
         self.tun_sa_in_v4 = VppIpsecSA(
             self,
@@ -1069,14 +1328,14 @@ class TestGSO(VppTestCase):
         self.tun_sa_out_v4.add_vpp_config()
 
         self.tun_protect_v4 = VppIpsecTunProtect(
-            self, self.ipip4, self.tun_sa_out_v4, [self.tun_sa_in_v4]
+            self, self.ipip4_0, self.tun_sa_out_v4, [self.tun_sa_in_v4]
         )
 
         self.tun_protect_v4.add_vpp_config()
 
         # Set interface up and enable IP on it
-        self.ipip4.admin_up()
-        self.ipip4.set_unnumbered(self.pg0.sw_if_index)
+        self.ipip4_0.admin_up()
+        self.ipip4_0.set_unnumbered(self.pg0.sw_if_index)
 
         #
         # IPv4/IPv4 - IPSEC
@@ -1110,7 +1369,7 @@ class TestGSO(VppTestCase):
             [
                 VppRoutePath(
                     "::",
-                    self.ipip4.sw_if_index,
+                    self.ipip4_0.sw_if_index,
                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
                 )
             ],
@@ -1149,20 +1408,20 @@ class TestGSO(VppTestCase):
         #
         # disable ipip4
         #
-        self.vapi.feature_gso_enable_disable(self.ipip4.sw_if_index, enable_disable=0)
+        self.vapi.feature_gso_enable_disable(self.ipip4_0.sw_if_index, enable_disable=0)
         self.ip4_via_ip4_tunnel.remove_vpp_config()
         self.ip6_via_ip4_tunnel.remove_vpp_config()
-        self.ipip4.remove_vpp_config()
+        self.ipip4_0.remove_vpp_config()
 
         #
         # enable ipip6
         #
-        self.ipip6.add_vpp_config()
-        self.vapi.feature_gso_enable_disable(self.ipip6.sw_if_index, enable_disable=1)
+        self.ipip6_0.add_vpp_config()
+        self.vapi.feature_gso_enable_disable(self.ipip6_0.sw_if_index, enable_disable=1)
 
         # Set interface up and enable IP on it
-        self.ipip6.admin_up()
-        self.ipip6.set_unnumbered(self.pg0.sw_if_index)
+        self.ipip6_0.admin_up()
+        self.ipip6_0.set_unnumbered(self.pg0.sw_if_index)
 
         # Add IPv4 routes via tunnel interface
         self.ip4_via_ip6_tunnel = VppIpRoute(
@@ -1172,7 +1431,7 @@ class TestGSO(VppTestCase):
             [
                 VppRoutePath(
                     "0.0.0.0",
-                    self.ipip6.sw_if_index,
+                    self.ipip6_0.sw_if_index,
                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP4,
                 )
             ],
@@ -1182,7 +1441,7 @@ class TestGSO(VppTestCase):
         # IPSec config
         self.ipv6_params = IPsecIPv6Params()
         self.encryption_type = ESP
-        config_tun_params(self.ipv6_params, self.encryption_type, self.ipip6)
+        config_tun_params(self.ipv6_params, self.encryption_type, self.ipip6_0)
         self.tun_sa_in_v6 = VppIpsecSA(
             self,
             self.ipv6_params.scapy_tun_sa_id,
@@ -1208,7 +1467,7 @@ class TestGSO(VppTestCase):
         self.tun_sa_out_v6.add_vpp_config()
 
         self.tun_protect_v6 = VppIpsecTunProtect(
-            self, self.ipip6, self.tun_sa_out_v6, [self.tun_sa_in_v6]
+            self, self.ipip6_0, self.tun_sa_out_v6, [self.tun_sa_in_v6]
         )
 
         self.tun_protect_v6.add_vpp_config()
@@ -1245,7 +1504,7 @@ class TestGSO(VppTestCase):
             [
                 VppRoutePath(
                     "::",
-                    self.ipip6.sw_if_index,
+                    self.ipip6_0.sw_if_index,
                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
                 )
             ],
@@ -1287,7 +1546,7 @@ class TestGSO(VppTestCase):
         #
         self.ip4_via_ip6_tunnel.remove_vpp_config()
         self.ip6_via_ip6_tunnel.remove_vpp_config()
-        self.ipip6.remove_vpp_config()
+        self.ipip6_0.remove_vpp_config()
 
         self.vapi.feature_gso_enable_disable(self.pg0.sw_if_index, enable_disable=0)