DPDK 19.11下i40e网卡无法发送rte_gso_segment分片的TCP包
DPDK 19.11 GSO分片后i40e网卡无法发送问题
问题场景
在DPDK 19.11中实现GSO(通用分段卸载)功能,参考testpmd完成初始化与分片逻辑,相同代码在hinic网卡上可正常发送,但在i40e网卡上,经rte_gso_segment分片后的报文传递到i40e_xmit_pkts函数后无法发出。
相关代码实现
GSO上下文初始化代码
struct rte_gso_ctx gso_ctx; static int gso_ctx_setup(struct rte_gso_ctx *gso_ctx, int port_id) { uint32_t gso_types; char pool_name[64]; struct rte_mempool *mp; /* Mempool for GSO packets */ /* initialize GSO context */ gso_types = DEV_TX_OFFLOAD_TCP_TSO; snprintf(pool_name, sizeof(pool_name), "gso_%d", port_id); mp = rte_mempool_lookup((const char *)pool_name); if (!mp) { mp = rte_pktmbuf_pool_create(pool_name, 128 * 10, 4, 0, RTE_PKTMBUF_HEADROOM + 128, SOCKET_ID_ANY); if (!mp) { printf("failed to create mbuf pool for device %d\n",port_id); return -1; } } gso_ctx->direct_pool = mp; gso_ctx->indirect_pool = mp; gso_ctx->gso_types = gso_types; gso_ctx->gso_size = 1514; gso_ctx->flag = 0; return 0; }
分片与发送函数
#define GSO_MAX_PKT_BURST 128 uint32_t tx_xmit(struct rte_mbuf *pkt) { uint32_t sent_pkts = 0; struct rte_mbuf *gso_segments[GSO_MAX_PKT_BURST]; uint16_t nb_segments = 0; int ret; ret = rte_gso_segment(pkt, &gso_ctx, &gso_segments[nb_segments], GSO_MAX_PKT_BURST - nb_segments); if (ret > 0) { nb_segments += ret; } else { printf("unable to segment packet\n"); rte_pktmbuf_free(pkts[0]); // 此处存在错误,应为rte_pktmbuf_free(pkt) } rte_eth_tx_burst(port_id, queue_id, &gso_segments[0], nb_segments); }
输入MBUF设置代码
struct rte_mbuf *head = m; for (int i = 0; i < head->nb_segs; i++) { m->ol_flags = PKT_TX_IPV4 | PKT_TX_TCP_CKSUM | PKT_TX_IP_CKSUM | PKT_TX_TCP_SEG | DEV_TX_OFFLOAD_TCP_TSO; // I'm sure the TCP header and data are correct. m->data_len = 1514; m->pkt_len = 1514 + 1460 + 1460 ....; m->l2_len = 14; m->l3_len = 20; m->l4_len = 20; m = m->next; }
GDB调试的MBUF信息
Thread 4 "tcpip_thread_00" hit Breakpoint 1, i40e_xmit_pkts (tx_queue=0x1fff359c0, tx_pkts=0x7ffff5d1adc0, nb_pkts=3) at /root/rpmbuild/BUILD/dpdk-19.11/drivers/net/i40e/i40e_rxtx.c:994 994 { (gdb) p *tx_pkts[0] $1 = {cacheline0 = 0x1da918f00, buf_addr = 0x1da918f80, {buf_iova = 13330648960, buf_physaddr = 13330648960}, rearm_data = 0x1da918f10, data_off = 128, { refcnt_atomic = {cnt = 1}, refcnt = 1}, nb_segs = 2, port = 65535, ol_flags = 58546795155816448, rx_descriptor_fields1 = 0x1da918f20, {packet_type = 0, { l2_type = 0, l3_type = 0, l4_type = 0, tun_type = 0, { inner_esp_next_proto = 0 '\000', {inner_l2_type = 0 '\000', inner_l3_type = 0 '\000'}}, inner_l4_type = 0}}, pkt_len = 1514, data_len = 54, vlan_tci = 0, {hash = {rss = 0, fdir = {{{hash = 0, id = 0}, lo = 0}, hi = 0}, sched = {queue_id = 0, traffic_class = 0 '\000', color = 0 '\000', reserved = 0}, txadapter = {reserved1 = 0, reserved2 = 0, txq = 0}, usr = 0}}, vlan_tci_outer = 0, buf_len = 256, timestamp = 0, cacheline1 = 0x1da918f40, { userdata = 0x0, udata64 = 0}, pool = 0x1da9a8c00, next = 0x1da918d40, { tx_offload = 1313294, {l2_len = 14, l3_len = 20, l4_len = 20, tso_segsz = 0, outer_l3_len = 0, outer_l2_len = 0}}, priv_size = 0, timesync = 0, seqn = 0, shinfo = 0x0, dynfield1 = {0, 0}} (gdb) p *tx_pkts[0]->next $2 = {cacheline0 = 0x1da918d40, buf_addr = 0x1e0d33ac8, {buf_iova = 13435615944, buf_physaddr = 13435615944}, rearm_data = 0x1da918d50, data_off = 182, { refcnt_atomic = {cnt = 1}, refcnt = 1}, nb_segs = 1, port = 65535, ol_flags = 4670232813583204352, rx_descriptor_fields1 = 0x1da918d60, { packet_type = 0, {l2_type = 0, l3_type = 0, l4_type = 0, tun_type = 0, { inner_esp_next_proto = 0 '\000', {inner_l2_type = 0 '\000', inner_l3_type = 0 '\000'}}, inner_l4_type = 0}}, pkt_len = 1514, data_len = 1460, vlan_tci = 0, {hash = {rss = 0, fdir = {{{hash = 0, id = 0}, lo = 0}, hi = 0}, sched = {queue_id = 0, traffic_class = 0 '\000', color = 0 '\000', reserved = 0}, txadapter = {reserved1 = 0, reserved2 = 0, txq = 0}, usr = 0}}, vlan_tci_outer = 0, buf_len = 2176, timestamp = 0, cacheline1 = 0x1da918d80, {userdata = 0x0, udata64 = 0}, pool = 0x1da9a8c00, next = 0x0, {tx_offload = 1313294, {l2_len = 14, l3_len = 20, l4_len = 20, tso_segsz = 0, outer_l3_len = 0, outer_l2_len = 0}}, priv_size = 72, timesync = 0, seqn = 0, shinfo = 0x0, dynfield1 = {0, 0}}
问题分析与修复方案
核心问题点
- MBUF的port字段无效:调试信息显示
port字段为65535(RTE_MAX_ETHPORTS),i40e驱动依赖该字段做端口合法性校验,无效值会导致报文被丢弃。 - ol_flags值异常:输出的
ol_flags为超大无意义数值,是循环设置每个seg时的位操作错误(重复赋值或类型溢出);同时保留了DEV_TX_OFFLOAD_TCP_TSO标志——软件GSO已完成分片,无需硬件再执行TSO,该标志会与i40e驱动逻辑冲突。 - MBUF字段设置错误:
- 循环给每个seg设置
data_len=1514不符合实际,数据段的data_len应为1460(MTU减去L2/L3/L4头长度)。 - 仅需在head mbuf上设置
l2_len/l3_len/l4_len,无需给每个seg重复设置。
- 循环给每个seg设置
- tx_xmit函数的错误处理:分片失败时错误释放
pkts[0],实际应释放输入的pkt。
修复步骤
- 修正MBUF设置逻辑:
struct rte_mbuf *head = m; // 仅在head上设置ol_flags,移除硬件TSO标志 head->ol_flags = PKT_TX_IPV4 | PKT_TX_TCP_CKSUM | PKT_TX_IP_CKSUM | PKT_TX_TCP_SEG; head->pkt_len = 1514 + 1460 + 1460; // 总报文长度 head->data_len = 54; // 第一个seg的有效数据长度(L2+L3+L4头) head->l2_len = 14; head->l3_len = 20; head->l4_len = 20; head->port = port_id; // 设置正确的端口ID struct rte_mbuf *seg = head->next; while (seg) { seg->data_len = 1460; // 数据段长度 seg->pkt_len = head->pkt_len; // 每个seg的pkt_len为总长度 seg->l2_len = 0; seg->l3_len = 0; seg->l4_len = 0; seg->port = port_id; // 设置端口ID seg->ol_flags = 0; // 数据段无需设置ol_flags seg = seg->next; } - 修正tx_xmit的错误处理:
if (ret > 0) { nb_segments += ret; } else { printf("unable to segment packet\n"); rte_pktmbuf_free(pkt); // 释放输入的原始报文 } - 验证GSO上下文配置:确认
gso_size=1514符合当前端口MTU配置(需包含L2头),若端口MTU为1500,需保持gso_size=1500+14=1514一致。
内容的提问来源于stack exchange,提问作者J.Heng
相关产品推荐
相关产品推荐

