Hi TRex Team,
I wanted to highlight that the issue mentioned in my previous email seems to be originating from the TRex side and not from the Mellanox NIC. I have got a proposed fix using the AI tool on TRex version 3.06, and it is working successfully.
To provide a brief recap:
**Problem:** TRex's STLVmFixChecksumHw produces an incorrect inner UDP checksum for VXLAN-encapsulated IPv6 packets on Mellanox ConnectX-6 Lx NICs, due to the mlx5 PMD requiring explicit tunnel metadata in the mbuf, which TRex doesn't set.
**Proposed Fix:** Detect the Mellanox NIC at port initialization time (`g_mlx_nic` flag), and then at stream load time and runtime, auto-detect VXLAN tunnel and set `RTE_MBUF_F_TX_TUNNEL_VXLAN` along with the outer IP offload flags and IPv6 pseudo-header checksum. This adjustment ensures that the NIC computes the correct inner L4 checksum in hardware, while Intel NICs can bypass this path entirely.
Could you please take this proposed fix as reference and provide the fix in the latest TRex STL code.
git diff -c | cat
diff --git a/src/drivers/trex_driver_base.cpp b/src/drivers/trex_driver_base.cpp
index 775dfb3b2..11b227b12 100755
--- a/src/drivers/trex_driver_base.cpp
+++ b/src/drivers/trex_driver_base.cpp
@@ -62,7 +62,9 @@ port_cfg_t::port_cfg_t() {
tx_offloads.common_best_effort =
RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
- RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
+ RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+ RTE_ETH_TX_OFFLOAD_OUTER_IPV4_CKSUM |
+ RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO;
tx_offloads.common_required =
RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
diff --git a/src/main_dpdk.cpp b/src/main_dpdk.cpp
index 2a98f9231..ad4e3dadf 100644
--- a/src/main_dpdk.cpp
+++ b/src/main_dpdk.cpp
@@ -5932,6 +5932,11 @@ COLD_FUNC void CPhyEthIF::_conf_queues(uint16_t tx_qs,
// disable non-supported best-effort offloads
tx_offloads &= dev_info->tx_offload_capa;
+ /* Global variable for Mellanox NICs */
+ extern bool g_mlx_nic;
+ if (CTRexExtendedDriverDb::Ins()->get_driver_name().find("mlx") != std::string::npos) {
+ g_mlx_nic = true;
+ }
tx_offloads |= cfg.tx_offloads.common_required;
diff --git a/src/stx/stl/trex_stl_stream_vm.cpp b/src/stx/stl/trex_stl_stream_vm.cpp
index a44a251a9..7931f786e 100644
--- a/src/stx/stl/trex_stl_stream_vm.cpp
+++ b/src/stx/stl/trex_stl_stream_vm.cpp
@@ -33,6 +33,8 @@ limitations under the License.
#include "trex_stl_stream.h"
#include "trex_stl_stream_vm.h"
+/* Global flag: true if NIC is Mellanox (mlx4/mlx5) */
+bool g_mlx_nic = false;
/**
* provides some tools for the fast rand function
@@ -783,6 +785,9 @@ void StreamVm::build_program(){
StreamDPOpHwCsFix ipv_fix;
ipv_fix.m_l2_len = lpFix->m_l2_len;
ipv_fix.m_l3_len = l3_len;
+ ipv_fix.m_outer_l2_len = 0;
+ ipv_fix.m_outer_l3_len = 0;
+
ipv_fix.m_op = StreamDPVmInstructions::ditFIX_HW_CS;
if (packet_is_ipv4) {
@@ -800,21 +805,58 @@ void StreamVm::build_program(){
l4_header_size = 0;
}
}
- }else{
+ } else {
/* Ipv6*/
- /* in this case we need to scan the Ipv6 headers */
- /* TBD replace with parser of IPv6 function */
- if ( lpFix->m_l4_type==StreamVmInstructionFixHwChecksum::L4_TYPE_TCP ){
- ipv_fix.m_ol_flags = (RTE_MBUF_F_TX_IPV6 | RTE_MBUF_F_TX_TCP_CKSUM);
- l4_header_size = TCP_HEADER_LEN;
- }else{
- if ( lpFix->m_l4_type==StreamVmInstructionFixHwChecksum::L4_TYPE_UDP ){
- ipv_fix.m_ol_flags = (RTE_MBUF_F_TX_IPV6 | RTE_MBUF_F_TX_UDP_CKSUM);
+ /* Check if inner IPv6 is inside a VXLAN tunnel for Mellanox NIC */
+ extern bool g_mlx_nic;
+ bool is_ipv6_tunnel = false;
+ if (g_mlx_nic && lpFix->m_l2_len > ETH_HDR_LEN) {
+ /* Find outer L3: skip Ethernet + optional VLAN tags */
+ uint16_t outer_l2_len = ETH_HDR_LEN;
+ uint16_t ethertype = PKT_NTOHS(*((uint16_t*)(m_pkt + ETH_HDR_LEN - 2 /* ETHER_TYPE_LEN */)));
+ while (ethertype == 0x8100 || ethertype == 0x88A8 || ethertype == 0x9100) {
+ outer_l2_len += 4; /* VLAN_HDR_LEN */
+ ethertype = PKT_NTOHS(*((uint16_t*)(m_pkt + outer_l2_len - 2 /* ETHER_TYPE_LEN */)));
+ }
+ if (ethertype == 0x0800) {
+ IPHeader *outer = (IPHeader *)(m_pkt + outer_l2_len);
+ if (outer->getVersion() == 4 &&
+ outer->getNextProtocol() == IPHeader::Protocol::UDP) {
+ is_ipv6_tunnel = true;
+ ipv_fix.m_outer_l2_len = outer_l2_len;
+ ipv_fix.m_outer_l3_len = outer->getSize();
+ ipv_fix.m_l2_len = lpFix->m_l2_len - outer_l2_len - outer->getSize();
+ }
+ }
+ }
+
+ if (g_mlx_nic && is_ipv6_tunnel) {
+ /* VXLAN tunnel with inner IPv6 (Mellanox only) */
+ if ( lpFix->m_l4_type==StreamVmInstructionFixHwChecksum::L4_TYPE_TCP ){
+ ipv_fix.m_ol_flags = (RTE_MBUF_F_TX_IPV6 | RTE_MBUF_F_TX_TCP_CKSUM |
+ RTE_MBUF_F_TX_TUNNEL_VXLAN |
+ RTE_MBUF_F_TX_OUTER_IP_CKSUM | RTE_MBUF_F_TX_OUTER_IPV4);
+ l4_header_size = TCP_HEADER_LEN;
+ } else {
+ ipv_fix.m_ol_flags = (RTE_MBUF_F_TX_IPV6 | RTE_MBUF_F_TX_UDP_CKSUM |
+ RTE_MBUF_F_TX_TUNNEL_VXLAN |
+ RTE_MBUF_F_TX_OUTER_IP_CKSUM | RTE_MBUF_F_TX_OUTER_IPV4);
l4_header_size = UDP_HEADER_LEN;
- }else{
- std::stringstream ss;
- ss << "instruction id '" << ins_id << "' fix hw command offsets should be TCP or UDP for IPv6 ";
- err(ss.str());
+ }
+ } else {
+ /* Non-tunnel IPv6 (original) — covers all NICs and plain IPv6 on mlx */
+ if ( lpFix->m_l4_type==StreamVmInstructionFixHwChecksum::L4_TYPE_TCP ){
+ ipv_fix.m_ol_flags = (RTE_MBUF_F_TX_IPV6 | RTE_MBUF_F_TX_TCP_CKSUM);
+ l4_header_size = TCP_HEADER_LEN;
+ } else {
+ if ( lpFix->m_l4_type==StreamVmInstructionFixHwChecksum::L4_TYPE_UDP ){
+ ipv_fix.m_ol_flags = (RTE_MBUF_F_TX_IPV6 | RTE_MBUF_F_TX_UDP_CKSUM);
+ l4_header_size = UDP_HEADER_LEN;
+ } else {
+ std::stringstream ss;
+ ss << "instruction id '" << ins_id << "' fix hw command offsets should be TCP or UDP for IPv6 ";
+ err(ss.str());
+ }
}
}
}
diff --git a/src/stx/stl/trex_stl_stream_vm.h b/src/stx/stl/trex_stl_stream_vm.h
index 4d0f2307b..a4c998a5e 100644
--- a/src/stx/stl/trex_stl_stream_vm.h
+++ b/src/stx/stl/trex_stl_stream_vm.h
@@ -801,46 +801,73 @@ public:
} __attribute__((packed));
+extern bool g_mlx_nic;
+
/* fix checksum using hardware */
struct StreamDPOpHwCsFix {
uint8_t m_op;
uint16_t m_l2_len;
uint16_t m_l3_len;
- uint64_t m_ol_flags;
+ uint64_t m_ol_flags;
+ uint16_t m_outer_l2_len; /* 0 = non-tunnel, >0 = tunnel mode (IPv6 inner only) */
+ uint16_t m_outer_l3_len;
public:
void dump(FILE *fd,std::string opt);
void HOT_FUNC run(uint8_t * pkt_base,rte_mbuf_t * m){
- IPHeader * ipv4 = (IPHeader *)(pkt_base+m_l2_len);
union {
TCPHeader * tcp;
UDPHeader * udp;
} u;
- u.tcp = (TCPHeader*)(pkt_base+m_l2_len+m_l3_len);
- /* set the mbuf info */
- m->l2_len = m_l2_len;
- m->l3_len = m_l3_len;
- m->ol_flags |= m_ol_flags;
- if (m_ol_flags & RTE_MBUF_F_TX_IPV4 ){ /* splitting to 4 instructions didn't improve performance .. */
- ipv4->ClearCheckSum();
- if ((m_ol_flags & RTE_MBUF_F_TX_L4_MASK) == RTE_MBUF_F_TX_TCP_CKSUM ){
- u.tcp->setChecksumRaw(rte_ipv4_phdr_cksum((struct rte_ipv4_hdr *)ipv4,m_ol_flags));
- }else{
- u.udp->setChecksumRaw(rte_ipv4_phdr_cksum((struct rte_ipv4_hdr *)ipv4,m_ol_flags));
- }
-
- }else{
- if ((m_ol_flags & RTE_MBUF_F_TX_L4_MASK) == RTE_MBUF_F_TX_TCP_CKSUM ){
- u.tcp->setChecksumRaw(rte_ipv6_phdr_cksum((struct rte_ipv6_hdr *)ipv4,m_ol_flags));
- }else{
- u.udp->setChecksumRaw(rte_ipv6_phdr_cksum((struct rte_ipv6_hdr *)ipv4,m_ol_flags));
- }
- }
- }
-} __attribute__((packed));
-
-
+ if (m_outer_l2_len > 0 && g_mlx_nic) {
+ /* === TUNNEL MODE (VXLAN with IPv6 inner, Mellanox only) ===
+ * Use flat l2_len model with tunnel flags in ol_flags.
+ * l2_len = total offset to inner IPv6 header
+ * l3_len = inner IPv6 header length
+ * Tunnel flags in ol_flags enable mlx5 tunnel-aware TX.
+ */
+ uint16_t inner_l3_offset = m_outer_l2_len + m_outer_l3_len + m_l2_len;
+ uint8_t *inner_l3 = pkt_base + inner_l3_offset;
+ u.tcp = (TCPHeader *)(inner_l3 + m_l3_len);
+
+ m->l2_len = inner_l3_offset;
+ m->l3_len = m_l3_len;
+ m->l4_len = UDP_HEADER_LEN;
+ m->ol_flags |= m_ol_flags;
+
+ /* set inner IPv6 pseudo-header checksum for NIC to complete */
+ if ((m_ol_flags & RTE_MBUF_F_TX_L4_MASK) == RTE_MBUF_F_TX_TCP_CKSUM) {
+ u.tcp->setChecksumRaw(rte_ipv6_phdr_cksum((struct rte_ipv6_hdr *)inner_l3, m->ol_flags));
+ } else {
+ u.udp->setChecksumRaw(rte_ipv6_phdr_cksum((struct rte_ipv6_hdr *)inner_l3, m->ol_flags));
+ }
+ } else {
+ /* === NON-TUNNEL MODE (original logic for Intel NICs and non-tunnel packets) === */
+ IPHeader *ipv4 = (IPHeader *)(pkt_base + m_l2_len);
+ u.tcp = (TCPHeader *)(pkt_base + m_l2_len + m_l3_len);
+
+ m->l2_len = m_l2_len;
+ m->l3_len = m_l3_len;
+ m->ol_flags |= m_ol_flags;
+
+ if (m_ol_flags & RTE_MBUF_F_TX_IPV4) {
+ ipv4->ClearCheckSum();
+ if ((m_ol_flags & RTE_MBUF_F_TX_L4_MASK) == RTE_MBUF_F_TX_TCP_CKSUM) {
+ u.tcp->setChecksumRaw(rte_ipv4_phdr_cksum((struct rte_ipv4_hdr *)ipv4, m_ol_flags));
+ } else {
+ u.udp->setChecksumRaw(rte_ipv4_phdr_cksum((struct rte_ipv4_hdr *)ipv4, m_ol_flags));
+ }
+ } else {
+ if ((m_ol_flags & RTE_MBUF_F_TX_L4_MASK) == RTE_MBUF_F_TX_TCP_CKSUM) {
+ u.tcp->setChecksumRaw(rte_ipv6_phdr_cksum((struct rte_ipv6_hdr *)ipv4, m_ol_flags));
+ } else {
+ u.udp->setChecksumRaw(rte_ipv6_phdr_cksum((struct rte_ipv6_hdr *)ipv4, m_ol_flags));
+ }
+ }
+ }
+ }
+ } __attribute__((packed));
/* flow varible of Client command */
struct StreamDPFlowClient {
Regards ,
Dheeraj