dpdk 收发包问题案例:使用不匹配的收发包函数触发的不收包问题定位

问题现象

业务程序使用 x710 网卡,收发一万多个巨帧包后就无法正常收包,查看网卡收发包统计发现 imissed 字段一直增加,问题必现。

环境信息

  1. dpdk 版本
    dpdk-16.04
  2. 网卡 pci 信息
    24:00.0 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 02)
    24:00.1 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 02)
    
  3. 接口统计信息
    第一次查看:
         rx_bytes: 124163766000
         rx_packets: 13772588
         rx_no_buffer_count: 0
         rx_errors: 0
         rx_missed_errors:13755556
         tx_bytes: 33291648
         tx_packets: 16224
         tx_broadcast: 0
         tx_multicast: 0
         tx_dropped: 0
         tx_errors: 0
    
    第二次查看:
         rx_bytes: 124176852000
         rx_packets: 13774042
         rx_no_buffer_count: 0
         rx_errors: 0
         rx_missed_errors:13757010
         tx_bytes: 33291648
         tx_packets: 16224
         tx_broadcast: 0
         tx_multicast: 0
         tx_dropped: 0
         tx_errors: 0
    
    从上述信息看出,tx_packets 没有增长,rx_packet 与 rx_missed 同时在增长,rx_no_buffer_count 为 0 表示没有出现 mbuf 泄露,同时确认网络流量非常少,rx_packets 的数据减掉 rx_missed_errors 的数据大约在一万5千左右,判断问题为程序无法收包。
  4. 收包 mempool 的 mbuf_size 为 2048

debug 过程记录

1. 查看接口收发包函数

收包函数:i40e_recv_scattered_pkts
发包函数:i40e_xmit_pkts_vec

2. 查看接口 rte_eth_devices[port]->data 中的重要字段

(gdb) print *rte_eth_devices[0]->data
$4 = {
  name = "24:0.0", '\000' <repeats 25 times>,
  rx_queues = 0x600025f12940,
  tx_queues = 0x600025f128c0,
  nb_rx_queues = 8,
  nb_tx_queues = 8,
  ..................................
  dev_private = 0x60000019d880,
  dev_link = {
    link_speed = 10000,
    link_duplex = 1,
    link_autoneg = 1,
    link_status = 1
  },
  dev_conf = {
    link_speeds = 0,
    rxmode = {
      mq_mode = ETH_MQ_RX_RSS,
      max_rx_pkt_len = 9728,
      split_hdr_size = 0,
      header_split = 0,
      hw_ip_checksum = 1,
      hw_vlan_filter = 0,
      hw_vlan_strip = 0,
      hw_vlan_extend = 0,
      jumbo_frame = 1,
      hw_strip_crc = 0,
      enable_scatter = 0,
      enable_lro = 0,
      enable_hash_offload = 0
    },
    txmode = {
      mq_mode = ETH_MQ_TX_NONE,
      pvid = 0,
      hw_vlan_reject_tagged = 0 '\000',
      hw_vlan_reject_untagged = 0 '\000',
      hw_vlan_insert_pvid = 0 '\000'
    },
  .......................................
  rx_mbuf_alloc_failed = 0,
  .................................
  port_id = 0 '\000',
  promiscuous = 1 '\001',
  scattered_rx = 1 '\001',
  all_multicast = 0 '\000',
  dev_started = 1 '\001',

从 data 结构能够确认如下信息:

  1. 接口混淆模式开启
  2. 接口正常 up 且 link 状态为 up
  3. 接口收发包队列配置正常
  4. 网卡 rx_mode 中使能了 hw_ip_checksum、jumbo_frame,并将支持的最大收包大小 max_rx_pkt_len 设置为了 9728。

3. 查看收包队列重要字段

(gdb) print *(struct i40e_rx_queue *)rte_eth_devices[0]->data->rx_queues[0]
$8 = {
  mp = 0x60002d1f9ec0,
  rx_ring = 0x60002624ee80,
  rx_ring_phys_addr = 937750144,
  sw_ring = 0x60002624dd40,
  nb_rx_desc = 512,
  rx_free_thresh = 32,
  rx_tail = 412,
  ...............................
  rx_free_trigger = 31,
  ...............................
  rxrearm_nb = 0,
  rxrearm_start = 0,
  mbuf_initializer = 0,
  port_id = 0 '\000',
  crc_len = 4 '\004',
  queue_id = 0,
  reg_idx = 1,
  drop_en = 0 '\000',
  qrx_tail = 0x600064128004 "\230\001",
  vsi = 0x60000011bb40,
  rx_buf_len = 2048,
  rx_hdr_len = 0,
  max_pkt_len = 9728,
  .........................

rx_ring 与 sw_ring 配置正常,描述符大小为 512 个,其它字段未见明显异常,同时注意到 rx_buf_len 设置大小为 2048,max_pkt_len 设置为 9728,这种配置时网卡会使用多个 mbuf 保存巨帧,驱动的收包函数会使用 xxx_recv_scattered_xxx 类型。

查看 rx_tail 指向的 rx 描述符数据:

(gdb) print /x ((struct i40e_rx_queue *)rte_eth_devices[1]->data->rx_queues[0])->rx_ring[412]
$55 = {
  read = {
    pkt_addr = 0xe8e3e8e300000000,
    hdr_addr = 0x2000600003019,
    rsvd1 = 0x0,
    rsvd2 = 0x0
  },
  wb = {
    qword0 = {
      lo_dword = {
        mirr_fcoe = {
          mirroring_status = 0x0,
          fcoe_ctx_id = 0x0
        },
        l2tag1 = 0x0
      },
      hi_dword = {
        rss = 0xe8e3e8e3,
        fcoe_param = 0xe8e3e8e3,
        fd_id = 0xe8e3e8e3
      }
    },
    qword1 = {
      status_error_len = 0x2000600003019
    },
    .....................................
  }
}

上述信息中 pkt_addr 的地址明显不太正常。

提问环节

  1. 使用的驱动高版本是否有更新?

    无更新,收包函数代码完全一致。

  2. debug 观察到的疑点
    rx_tail 指向的下一个要处理的收包描述符上的报文地址异常,分析驱动代码,不应该存在这种情况,说明可能是网卡填充描述符出现了异常。

  3. pcie 侧是否存在问题?
    lspci -nvv 查看异常接口状态,与正常接口对比,没有发现异常点,暂且排除。

尝试异常恢复

做了如下测试:

  1. 接口 down、up 不能恢复正常
  2. 重启引擎后仍旧收了几万个包后不能收包
    如果是 pcie 异常,重启引擎让驱动重新初始化也不能恢复,上述测试进一步排除了 pcie 的问题,怀疑可能是网卡已经处于某种异常工作状态。

l2fwd 对照测试发现的问题

考虑到可能有其它因素干扰且问题必现,我判断如果是驱动侧的问题,使用 l2fwd 理论上也应该能够复现出问题。于是进行了如下测试:

在配置 rx_mode 开启 hw_ip_checksum 与 jumbo_frame 并设置 max_rx_pkt_len 为 9728 的条件下,进行了如下对比实验:

  1. 测试创建 mbuf dataroom 为 9728 + headroom 大小的 mempool
    正常收发包。

  2. 创建 mbuf dataroom 为 2048 + headroom 大小的 mempool
    收发几百个包后就无法收包。

  3. 在 2 的基础上,将 l2fwd 修改为只收包不发包的工作模式,能够持续收包,表明问题出在发包上

进行了上述测试后,问题缩小到 l2fwd 收发包使用的 mempool 创建的 mbuf 结构中的 dataroom 大小上,可这个大小是怎样影响到收发包过程的呢?

dpdk 中处理巨帧的两种工作模式

  1. 巨帧由一个 mbuf 的 dataroom 保存,这种方式称为 singlesegs,表明由一个 mbuf 装载报文
  2. 巨帧拆成多个 mbuf,拼成 mbuf 链的形式保存,这种方式可称为 multisegs 模式,表明由多个 mbuf 组成的链表共同装载报文

xxx_recv_scattered_xxx 这种收包函数支持 multisegs 模式,它需要使用匹配的支持 multisegs 模式的发包函数,否则会出现发包异常。
那网卡如何知道要用哪种模式?
其实是驱动侧在上层调用 xxx_dev_start 接口时,会将每个收包队列的 rx_buf_len 写入到网卡寄存器中,网卡寄存器检查此大小与 mtu 的关系就能够判断使用哪种工作模式处理报文。

当 rx_buf_len 大于等于 mtu 的时候,使用 singlesegs,当 rx_buf_len 小于 mtu 的时候使用 multisegs 模式。同时驱动侧也会做类似的检查来使用匹配的收包函数,但是发包函数的选择却没有使用这些条件,这就可能存在误用的情况。

哪个 i40e 的发包函数支持 multisegs 模式?

写到这里,回顾下 debug 记录中查看接口收发包函数内容:

  1. 收包函数:i40e_recv_scattered_pkts
  2. 发包函数:i40e_xmit_pkts_vec

结合队列信息中 rx_buf_len 的值为 2048,而 max_rx_pkt_len 为 9728,此时会使用 multisegs 模式来收包。阅读代码确定只有 i40e_xmit_pkts 发包函数支持 multisegs 模式,i40e_xmit_pkts_vec 发包函数不支持 multisegs,这两个收发包函数不匹配,就会触发发包异常导致网卡工作异常进而无法收包。

那为什么 l2fwd 会使用 i40e_xmit_pkts_vec 发包函数呢?

分析代码有如下结论:

  1. l2fwd 的 tx_queue_setup 调用中 tx_conf 配置为 NULL
  2. rte_eth_tx_queue_setup 函数中判断到 tx_conf 为空则调用驱动的 dev_infos_get 接口获取默认的 txconf 配置,然后调用底层驱动中的 tx_queue_setup 函数
  3. i40e 驱动中默认的 txconf 配置中将 txq_flags 配置为 ETH_TXQ_FLAGS_NOMULTSEGS |
    ETH_TXQ_FLAGS_NOOFFLOADS
  4. i40e 驱动设置发包函数的时候判断 txq_flags 开启了 ETH_TXQ_FLAGS_NOMULTSEGS |
    ETH_TXQ_FLAGS_NOOFFLOADS 则使用不支持 multsegs 的发包函数

我确定只需要在 tx_queue_setup 的时候传递个 tx_conf 避免使用默认值就能够让 l2fwd 使用支持 multsegs 模式的发包函数,为此对 l2fwd 代码进行如下修改:

Index: main.c
===================================================================
--- main.c
+++ main.c
@@ -114,10 +114,11 @@
        .rxmode = {
                .split_hdr_size = 0,
                .header_split   = 0, /**< Header Split disabled */
-               .hw_ip_checksum = 0, /**< IP checksum offload disabled */
+               .hw_ip_checksum = 1, /**< IP checksum offload disabled */
                .hw_vlan_filter = 0, /**< VLAN filtering disabled */
-               .jumbo_frame    = 0, /**< Jumbo Frame Support disabled */
+               .jumbo_frame    = 1, /**< Jumbo Frame Support disabled */
                .hw_strip_crc   = 0, /**< CRC stripped by hardware */
+               .max_rx_pkt_len = 9728,
        },
        .txmode = {
                .mq_mode = ETH_MQ_TX_NONE,
@@ -225,12 +226,6 @@
                        BURST_TX_DRAIN_US;
        struct rte_eth_dev_tx_buffer *buffer;

        prev_tsc = 0;
        timer_tsc = 0;

@@ -668,9 +661,13 @@

                /* init one TX queue on each port */
                fflush(stdout);
+               struct rte_eth_txconf txconf;
+               memset(&txconf, 0x00, sizeof(txconf));
+               txconf.txq_flags = 0;
+
                ret = rte_eth_tx_queue_setup(portid, 0, nb_txd,
                                rte_eth_dev_socket_id(portid),
-                               NULL);
+                               &txconf);
                if (ret < 0)
                        rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup:err=%d, port=%u\n",
                                ret, (unsigned) portid);

修改完成后重新编译并运行 l2fwd,此时收发包正常,使用 perf 观测到热点函数调用如下:

  54.11%  l2fwd             [.] l2fwd_launch_one_lcore
  45.22%  l2fwd             [.] i40e_recv_scattered_pkts
  .......................................................
   0.02%  l2fwd             [.] i40e_xmit_pkts

收包函数使用 i40e_recv_scattered_pkts,发包函数为 i40e_xmit_pkts,这两个收发包函数均支持 multisegs,问题得到解决。按照这个思路排查业务代码,发现业务代码调用 tx_queue_setup 时传递的 tx_conf 参数配置了 ETH_TXQ_FLAGS_NOMULTSEGS | ETH_TXQ_FLAGS_NOOFFLOADS,针对 x710 网卡去掉相应的配置,重新测试问题得到解决。

延伸

写到这里我觉得驱动的这种实现不合理,它不应该将默认的 txq_flags 设置为某个特定的值,高版本应该会修改,查看 dpdk git log 发现在如下 commit 中移除了 tx_conf 的相关配置:

commit 7d1daae8c7d28ae5b2b7fe054d1dc507edb405a9
Author: Qi Zhang <qi.z.zhang@intel.com>
Date:   Wed May 2 11:56:33 2018 +0800

这个 commit 的提交信息如下:

Since we move to new offload APIs, txq_flags is no long needed.
This patch remove the dependence on that.

这个修改并不是为了解决本文描述的问题,只是为了使用新的 offloads 结构而去除了 txq_flags 的配置,而新的 offloads 结构的默认值为 0,就不存在本文描述的问题。

总结

写到最后回顾下发现其实问题在 debug 过程查看的第一个信息中就已经能够明确了,只是由于自己对底层原理不够清楚才忽略了这个信息,这让我想到“要在不疑处存疑”这句名言。有时候自己判断某些信息没有疑问不过是快思考迅速给出的轻松结论,问题可能就在其中隐含,这点需要不断的训练来改善。

想想如果我对 debug 记录中的每处信息都了如指掌,那这类问题的解决速度将会大大加快,这可能是自己向领域专家前进的道路。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值