• 【原创】Intel XL710网卡异常Reset问题复现


    问题背景

           业务服务器使用IntelXL710网卡,上线使用过程中网卡突然断链,Link状态为down,而且不可恢复,必须

    复位服务器才可以。但是过一段时间后,会再次出现同样的问题,而且在几个局点都出现了类似的问题。

      开始出现该问题时,根据以往经验,无非是光模块、光纤兼容问题,网卡硬件批次问题。但是随着出问题

    的设备增多,次数增多,开始觉得该问题没这么简单。

      先看看生产环境收集到的信息:

           网卡状态信息

           # ip a | grep eth4

      5: eth4: <NO-CARRIER,BROADCAST,MULTICAST,SLAVE,UP> mtu 1500 qdisc mq master bond5 state DOWN qlen 1000

      GZ-SN-OTT18:~ #

      GZ-SN-OTT18:~ # ethtool eth4

      Settings for eth4:

      Supported ports: [ ]

      Supported link modes:   1000baseT/Full

                      10000baseT/Full

      Supports auto-negotiation: Yes

      Advertised link modes:  1000baseT/Full

                           10000baseT/Full

      Advertised pause frame use: No

      Advertised auto-negotiation: Yes

      Speed: Unknown!

      Duplex: Unknown! (255)

      Port: Other

      PHYAD: 0

      Transceiver: external

      Auto-negotiation: off

      Supports Wake-on: g

      Wake-on: g

      Current message level: 0x0000000f (15)

                drv probe link timer

      Link detected: no

      内核日志信息

    Jul 20 11:28:38 JAedge5 kernel: [8395582.875067] i40e 0000:0a:00.2: TX driver issue detected, PF reset issued  ##网卡错误

    Jul 20 11:28:38 JAedge5 kernel: klogd 1.4.1, ---------- state change ----------

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453724] kworker/u:4: page allocation failure: order:5, mode:0x80d0  ##分配order=5的连续page失败

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453734] Pid: 21027, comm: kworker/u:4 Tainted: G ENX 3.0.101-0.47.52-default #1

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453738] Call Trace:

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453762]  dump_trace+0x75/0x300

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453777]  dump_stack+0x69/0x6f

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453790]  warn_alloc_failed+0xc6/0x170

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453802]  __alloc_pages_slowpath+0x561/0x7f0

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453812]  __alloc_pages_nodemask+0x1e9/0x200

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453823]  dma_generic_alloc_coherent+0xa6/0x160

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453837]  x86_swiotlb_alloc_coherent+0x28/0x80

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453875]  i40e_setup_tx_descriptors+0xf1/0x140 [i40e]

    Jul 20 11:28:38 JAedge5 kernel: [8395583.453971]  i40e_vsi_setup_tx_resources+0x2d/0x60 [i40e]

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454003]  i40e_vsi_open+0x27/0x1c0 [i40e]

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454046]  i40e_open+0x5b/0x90 [i40e]

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454078]  i40e_pf_unquiesce_all_vsi+0x30/0x50 [i40e]

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454114]  i40e_reset_and_rebuild+0x2f2/0x600 [i40e]

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454156]  i40e_reset_subtask+0xf8/0x100 [i40e]

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454189]  i40e_service_task+0x108/0x300 [i40e]

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454217]  process_one_work+0x16c/0x350

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454229]  worker_thread+0x17a/0x410

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454261]  kthread+0x96/0xa0

    Jul 20 11:28:38 JAedge5 kernel: [8395583.454272]  kernel_thread_helper+0x4/0x10

    Jul 20 11:28:39 JAedge5 kernel: [8395583.454280] Mem-Info:

    Jul 20 11:28:39 JAedge5 kernel: [8395583.454283] Node 0 DMA per-cpu:

    Jul 20 11:28:39 JAedge5 kernel: [8395583.454288] CPU    0: hi:    0, btch:   1 usd:   0

    Jul 20 11:28:39 JAedge5 kernel: [8395583.454292] CPU    1: hi:    0, btch:   1 usd:   0

    Jul 20 11:28:39 JAedge5 kernel: [8395583.454296] CPU    2: hi:    0, btch:   1 usd:   0

      

       

     

    内核异常日志分析

     

      从网卡和内核日志分析,这台设备eth4网卡是down的,是由于网卡出现PF reset打印,引起网卡

    重新创建Rx/Tx descriptor,但是由于内存不足(应该是需要的连续内存页不能满足),分配descriptor

    失败,导致网卡启动失败,最终引起网卡Link down状态。

      XL710 PF reset:

    i40e 0000:0a:00.2: TX driver issue detected, PF reset issued

    网口PF_RESET在驱动里是探测到MDD事件,即是 Malicious Driver Detection Event

    通过 I40E_GL_MDET_TX0x000E6480)获取。

      这个问题问过Intel技术支持,由于目前底层都被封装起来了,所以也不知道是什么类型的错误。

      XL710网卡内存分配失败

      page allocation failure: order:5, mode:0x80d0

      PF重启时,需要重新分配网卡descriptor资源,需要连续的内存页。当系统内存碎片严重时,会出现分配page失败,导致网口再也up不起来。

     

      网卡重建队列调用堆栈:

      i40e_reset_and_rebuild

       --> i40e_pf_unquiesce_all_vsi

            --> i40e_open

           --> i40e_vsi_open

             --> i40e_vsi_setup_tx_resources

                --> i40e_setup_tx_descriptors

                 --> x86_swiotlb_alloc_coherent

                   --> dma_generic_alloc_coherent

                     --> __alloc_pages_nodemask

                       --> __alloc_pages_slowpath

      void *dma_generic_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp)

       {

         void *ret, *ret_nocache;

         int order = get_order(size);

         gfp |= __GFP_ZERO;

         ret = (void *)__get_free_pages(gfp, order);

         if (!ret)

           return NULL;

         ....

       }

         在网上也搜索了相关的资料,有说是网卡自身问题,要把TCP segment offload关掉,生产环境也做了相关操作,

    确实也没有再出现PF reset的问题。这也只是临时规避措施。分配连续page失败是附带的问题,根本还是要找到网卡

    产生PF reset的原因。

    根本原因

        这里要感谢Intel技术工程师,出现这个问题后,反复和他们进行了沟通和交流,收集了很多信息,也联系了美国

    团队技术专家。最终,我们在Intel一篇技术文档中找到了问题的可疑原因:

       Intel specification update文档

    Intel ® Ethernet Controller X710/XXV710/XL710
    Specification Update
    Ethernet Networking Division (ND)

     Revision 3.3

      这是一个安全漏洞,是在2017年1月10号加入这个文档的。

    怎么理解和复现问题?

       从文档描述中,可疑肯定一点,这个问题与TSO相关,在生产环境关闭TSO后,也确实没有

    再次出现问题。但是,怎么理解文档描述的问题?又怎么复现这个问题,得到和生产环境一样的

    错误信息呢?

      先试着翻译一下:

      当一个TSO报文的第一个Tx descriptor包含报文头和数据载荷时,在MAX_BUFFS的异常检测中

    被计算两次。因此,如果第一个segment分布在8个descriptor中,就会报告一个MDD时间。然而,如果

    一个segement中超过8个descriptor时,它应该只产生一个MDD事件。

      这会导致虚假的MDD(Malicious Driver Detection)事件。

      软件驱动必须限制一个TSO报文的第一个段(segment)包含7个descriptor,而不是8个descriptor。

    这个现实已经在Intel 21.3驱动版本中实现。

    bug说明:

          从文档描述看,与发送TSO数据报文有关,而LInux协议栈发送TSO数据报文是通过skb数据结构传递到

    网卡驱动的,我们可以从skb入手,看看什么样的skb数据会导致整个问题。

      文档描述中的7个descriptor又是什么意思?什么样的skb数据才能算是7个descriptor?这要从I40E驱动

    里面寻找答案。

    I40E驱动如何计算descriptor个数

      PS:以I40E 1.4.25驱动源码为基础进行说明。

      I40E驱动发包函数为i40e_xmit_frame_ring,进入后调用i40e_xmit_descriptor_count计算descriptor是否充足,

    否则返回NETDEV_TX_BUSY。

      计算descriptor函数如下:

     1 #ifndef DIV_ROUND_UP
     2 #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
     3 #endif
     4 
     5 #define I40E_MAX_DATA_PER_TXD    8192
     6 
     7 /* Tx Descriptors needed, worst case */
     8 #define TXD_USE_COUNT(S) DIV_ROUND_UP((S), I40E_MAX_DATA_PER_TXD)
     9 
    10 
    11 #ifdef I40E_FCOE
    12 inline int i40e_xmit_descriptor_count(struct sk_buff *skb,
    13                       struct i40e_ring *tx_ring)
    14 #else
    15 static inline int i40e_xmit_descriptor_count(struct sk_buff *skb,
    16                          struct i40e_ring *tx_ring)
    17 #endif
    18 {
    19     unsigned int f;
    20     int count = 0;
    21 
    22     /* need: 1 descriptor per page * PAGE_SIZE/I40E_MAX_DATA_PER_TXD,
    23      *       + 1 desc for skb_head_len/I40E_MAX_DATA_PER_TXD,
    24      *       + 4 desc gap to avoid the cache line where head is,
    25      *       + 1 desc for context descriptor,
    26      * otherwise try next time
    27      */
    28     for (f = 0; f < skb_shinfo(skb)->nr_frags; f++)
    29         count += TXD_USE_COUNT(skb_shinfo(skb)->frags[f].size);
    30 
    31     count += TXD_USE_COUNT(skb_headlen(skb));
    32     if (i40e_maybe_stop_tx(tx_ring, count + 4 + 1)) {
    33         tx_ring->tx_stats.tx_busy++;
    34         return 0;
    35     }
    36     return count;
    37 }

      

      一个skb需要的TXD(Transmit Descriptor)计算分为两部分,一个是非线性区每个page需要的TXD计算,

    另一个是线性区数据需要的TXD计算。同时,要预留5个TXD,其中一个是context TXD,另外4个TXD是为避免

    head的cache line而保留的TXD间隔(gap)。

      每个TXD最大的数据长度是8192字节(I40E_MAX_DATA_PER_TXD)。

      注意:I40E_MAX_DATA_PER_TXD这个值在后来的高版本驱动中是12K。

      综上,bug说明中所说的一个segment分布在8个TXD中,即skb线性区数据占用一个TXD,skb frag占用7个TXD。

    这样,可以很容易构造使得I40E网卡产生MDD异常的SKB数据。

    复现代码 & skb结构

    构造特殊skb数据

     #define WIT_TCP_SEG_NUM   7
    #define WIT_TCP_DATA_LEN 4000
    #define WIT_TCP_SEG_LEN  150
    #define WIT_SKB_LINE_LEN 260

    #define WIT_MSS  1460
    #define WIT_SKB_CACHE_LENGTH 512

    /* 构造能够产生MDD事件的tcp skb */
    static int mdd_tcp_skb(struct sk_buff **skb)
    {
        /* 核心代码 */
        int skb_page_offset = 0;
        int pgrefcnt = 0;
        int page_index = 0;
        int rc = 0;
        struct page *linear_page=NULL;
        unsigned int headroom;
        unsigned int iphdr_length;
        unsigned int ethhdr_length;
        unsigned int tcphdr_length;
        unsigned int payload_length = 0;
        
        unsigned char * pdata=NULL;
        
        /* 分配页面,存放skb非线性区数据 */
        linear_page = alloc_pages(GFP_ATOMIC|__GFP_COMP, get_order(WIT_TCP_DATA_LEN));
        if (!linear_page)
        {
            rc = -ENOMEM;
            goto alloc_page_err;
        }
        /* 转化为线性虚拟地址,便于访问页面 */    
        linear_vaddr = page_address(linear_page);
        
        struct sk_buff *nskb=NULL;

        /*分配skb,并设置。*/
        /*
        skb->head = data;
        skb->data = data;
        skb_reset_tail_pointer(skb);
        skb->end = skb->tail + size;
        */
        nskb = __alloc_skb(WIT_SKB_CACHE_LENGTH, GFP_ATOMIC, 0, nid/* numa id */);
        
        /*需要重新分配skb,没有可复用的。*/
        if (unlikely(nskb==NULL))
        {
            BUG_ON(nskb==NULL);
            rc = -ENOMEM;
            goto alloc_skb_err;
        }
        
        /* nskb线性区保留空间大小,即将头部指针往后移动,然后从高层协议开始填充。*/
        /* skb_reserve operation result:
        skb->data += len;
        skb->tail += len;
        */
        headroom = 2;
        iphdr_length = sizeof(struct iphdr);
        ethhdr_length = sizeof(struct ethhdr);
        tcphdr_length = sizeof(struct tcphdr);
        skb_reserve(nskb, headroom + ethhdr_length + iphdr_length + tcphdr_length);
        
        /* 存放线性区数据 */
        /*
        unsigned char *tmp = skb_tail_pointer(skb);
        skb->tail += len;
        skb->len  += len;
        返回 tmp,即移动之前的skb->tail
        */
        pdata = __skb_put(nskb, WIT_SKB_LINE_LEN);
        memset(pdata, 0xAA, WIT_SKB_LINE_LEN);

        /* 指定一个page frag包含的数据长度 */
        payload_length = WIT_TCP_SEG_LEN;
        
        /*数据页面直接改用增加页面引用计数。*/
        for(page_index = 0; page_index < 8; page_index++)
        {
            skb_fill_page_desc(nskb, page_index, linear_page, skb_page_offset, payload_length);
            /* liangjs modi 2015-7-16, if rtphdr_length, second reference page, or it's first ref page, unnecssary */
            if(pgrefcnt > 0)
            {
                get_page(linear_page);
            }
            pgrefcnt++;
            skb_page_offset += payload_length;
            nskb->len += payload_length;
            nskb->data_len += payload_length;
            nskb->truesize += payload_length;
        }
        
        //填写包头 ether,ip and tcp header
        /* skb是从原始sk_buff,里面存放有 二层到四层 报文头 信息 */
        fill_mdd_skb_tcphdr(nskb);
        
        /* 计算 nskb中 tcp seg个数,tcp最大段长度(MSS), 以及GSO类型 */
        struct skb_shared_info *shinfo = skb_shinfo(nskb);
        
        shinfo->gso_segs = DIV_ROUND_UP(nskb->len, WIT_MSS);
        shinfo->gso_size = WIT_MSS;
        shinfo->gso_type = SKB_GSO_TCPV4; //sk->sk_gso_type;
        
        return rc;
        
    alloc_skb_err:
        if (linear_page)
        {
            /* liangjs note 2014-08-22: need not put_page   */
            printk("%s:%d: linear_page = %p ", __FUNCTION__, __LINE__, linear_page);
            __free_pages(linear_page, get_order(WIT_TCP_DATA_LEN));
        }
        
    alloc_page_err:
        *skb = NULL;
        return rc;
    }
        
    static inline int fill_mdd_skb_tcphdr(struct sk_buff *nskb)
    {
        struct ethhdr * pethhdr=NULL;
        struct iphdr * piphdr=NULL;
        struct tcphdr *ptcphdr=NULL;    
     
     #if 0
        struct tcphdr {
        __be16    source;
        __be16    dest;
        __be32    seq;
        __be32    ack_seq;
    #if defined(__LITTLE_ENDIAN_BITFIELD)
        __u16    res1:4,
            doff:4,
            fin:1,
            syn:1,
            rst:1,
            psh:1,
            ack:1,
            urg:1,
            ece:1,
            cwr:1;
    #elif defined(__BIG_ENDIAN_BITFIELD)
        __u16    doff:4,
            res1:4,
            cwr:1,
            ece:1,
            urg:1,
            ack:1,
            psh:1,
            rst:1,
            syn:1,
            fin:1;
    #else
    #error    "Adjust your <asm/byteorder.h> defines"
    #endif    
        __be16    window;
        __sum16    check;
        __be16    urg_ptr;
    };
     #endif

        /*调整UDP指针*/
        skb->protocol = __constant_htons(ETH_P_IP)
        ptcphdr = (struct tcphdr *)__skb_push(nskb, sizeof(struct tcphdr));
        skb_set_transport_header(nskb, 0);
        
        memset(ptcphdr, 0, sizeof(struct tcphdr));
        ptcphdr->source = htons(61369);
        ptcphdr->dest = htons(9527);
        ptcphdr->seq = 0x3b5f862d;
        ptcphdr->ack_seq = 0xe8f76aee;
        ptcphdr->doff = 5;
        ptcphdr->fin = 0;
        ptcphdr->syn = 0;
        ptcphdr->rst = 0;
        ptcphdr->psh = 0;
        ptcphdr->ack=1;
        #if 0
        ptcphdr->urg:1,
        ptcphdr->ece:1,
        ptcphdr->cwr:1;
        #endif
        ptcphdr->window = 0x0073;
        ptcphdr->check = 0;
        ptcphdr->urg_ptr = 0;

        /*ip头部*/
        piphdr = (struct iphdr * )__skb_push(nskb, sizeof(struct iphdr));
        //skb_copy_from_linear_data_offset(skb, skb_network_offset(skb),
        //    (unsigned char *)piphdr, sizeof(struct iphdr));
        piphdr->version = 4;    
        piphdr->protocol = 0x06;  /* TCP protocol */
        piphdr->saddr = 0x01010101;
        piphdr->daddr = 0x02020202;
        skb_set_network_header(nskb, 0);

        piphdr->ihl = sizeof(struct iphdr)/4;
        piphdr->tot_len = htons(nskb->len);
        piphdr->check = 0;
        piphdr->frag_off = htons(0x4000);
        piphdr->check = ip_fast_csum((unsigned char *)piphdr, piphdr->ihl);

        nskb->csum = 0;
        /* 这个赋值很重要,因为在I40E驱动的i40e_tso函数中,非CHECKSUM_PARTIAL,直接返回了 */
        nskb->ip_summed = CHECKSUM_PARTIAL;//CHECKSUM_UNNECESSARY;

        /*eth头部*/        
        pethhdr = (struct ethhdr * )__skb_push(nskb, sizeof(struct ethhdr));
        skb_set_mac_header(nskb, 0);
        //skb_copy_from_linear_data_offset(skb, 0,
        //    (unsigned char *)pethhdr, sizeof(struct ethhdr) );
        unsigned char dest[ETH_ALEN] = {0x98,0xf5,0x00,0x01,0x02,0x03};
        unsigned char src[ETH_ALEN] = {0x98,0xf5,0x00,0x01,0x02,0x04};
        memcpy(pethhdr->h_dest, dest, ETH_ALEN);
        memcp(pethhdr->h_source, src, ETH_ALEN);
        pethhdr->h_proto = 0x0800;
        
        nskb->mac_len = sizeof(struct ethhdr);
        nskb->dev = skb->dev;
        nskb->queue_mapping = skb->queue_mapping;
        nskb->protocol = skb->protocol;
        nskb->vlan_tci = skb->vlan_tci;
        nskb->pkt_type = skb->pkt_type;
        nskb->mark = 0;

        return 1;
    }

     测试复现

    测试用例1:
    #define WIT_TCP_SEG_NUM   7
    #define WIT_TCP_DATA_LEN 4000
    #define WIT_TCP_SEG_LEN  150
    #define WIT_SKB_LINE_LEN 400

    #define WIT_MSS  1450

    内核日志:
    Nov 15 20:32:18 linux kernel: [18363.480323] i40e 0000:0a:00.3: TX driver issue detected, PF reset issued


    测试用例2  :
    #define WIT_TCP_SEG_NUM   7
    #define WIT_TCP_DATA_LEN 4000
    #define WIT_TCP_SEG_LEN  150
    #define WIT_SKB_LINE_LEN 410

    #define WIT_MSS  1460

    内核日志:
    [70466.672761] i40e 0000:0a:00.3: TX driver issue detected, PF reset issued
    [70467.512354] bonding: bond0: link status up again after 0 ms for interface eth0.

    测试用例3 :
    #define WIT_TCP_SEG_NUM   8
    #define WIT_TCP_DATA_LEN 4000
    #define WIT_TCP_SEG_LEN  150
    #define WIT_SKB_LINE_LEN 260

    #define WIT_MSS  1460

    测试结果:
    no PF reset


    测试用例4  :
    #define WIT_TCP_SEG_NUM   7
    #define WIT_TCP_DATA_LEN 4000
    #define WIT_TCP_SEG_LEN  150
    #define WIT_SKB_LINE_LEN 260

    #define WIT_MSS  1460

    # dmesg -c
    [73345.238145] i40e 0000:0a:00.3: TX driver issue detected, PF reset issued
    [73345.847481] bonding: bond0: link status up again after 0 ms for interface eth0.


    更换驱动和固件:驱动:i40e-2.0.30  固件 5.05
    重复以上测试用例,没有再产生PF Reset错误。
     

    skb结构复现总结

    1. skb->data 线性区 有报文头 和 要发送的数据;

    2. 总共有7page frag

    3. 线性区数据 + page data_len <= mss_now(gso_size)

    后记

      软件也是人写的,是人都会犯错误,所以需要测试的介入,尽可能保证故障不外泄。

    针对Intel X710网卡的故障,个人愚见,如果进行分支覆盖测试和边界测试,可能也就

    不会出现这么严重的BUG了;或者驱动和固件进行整合测试,可能也会发现两边不一致的

    情况。软件的任何改动都需要标记和记录,都是测试的重点。

      出现该问题的时候,着实困惑了很久,排查过程中,甚至怀疑bond驱动和Linux系统BUG,

    或者交换机BUG,为此还专门咨询了在友商交换机部门工作的同学Σ( ° △ °|||)︴。虽然不是这几个

    问题,但也学习了相关的模块和技术,也算是一种收获吧。

      向Intel技术支持咨询时,总是建议我们升级驱动和固件,后来也确实是通过升级解决问题;

    从网上也搜到了相关的问题,关闭TSO也能规避问题。但是作为一位程序员总想搞清楚为什么

    会这样(其实有个领导也要求排查出问题),这样才能安心升级驱动呀,否则向局方说升级驱动

    能解决问题,如果又出相同问题,就是打自己的脸呀!

      还好,有Intel的相关勘误表,我们终于知道了原因,并且也可以复现出来。总算圆满解决。

      其实Intel有很多技术文档,包括勘误表,都在它的官方网站上,关键是怎么去查找。而且,

    文档中的描述和现象不是一致,而是另外一种说法,所以说要理解技术后面的意思非常重要。

      

    PS:您的支持是对博主最大的鼓励,感谢您的认真阅读。
            本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,

          且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    clion打断点不生效
    PHP加密解密
    细说MySQL表操作
    细说MySQL数据库操作
    终端(命令行)连接MySQL
    MySQL结构
    求1!+(1!+3!)+(1!+3!+5!)+...+(1!+3!+5!+7!+9!)的值
    react 生命周期
    React TS 组件 Demo
    react-redux 实现原理
  • 原文地址:https://www.cnblogs.com/smith9527/p/10522501.html
Copyright © 2020-2023  润新知