记录一次checksum校验失败导致丢包的问题

开始之前先附上网络模型的图,此次问题的重点出在运输层、网络层、数据链路层。
协议栈应用:lwip_1.4.1
记录一次checksum校验失败导致丢包的问题_第1张图片
问题现象:在udp send数据包大小不超过MTU值时,数据包发送且接收方接收成功;当udp send数据包大小超过MTU值时,数据包用wireshark抓包发现发送成功,但是接收方未接收成功。

分析流程:
1.查看了应用层的数据,确认无误,于是问题指向了udp自身包头一块。

2.在udp自身包头中确认MAC、IP地址、port等信息无误,然而又因为只会在协议栈IP层自动拆包后会出现接收方未接收成功的情况,于是问题又指向了IP层的TTL值与checksum值。
记录一次checksum校验失败导致丢包的问题_第2张图片
在这里插入代码片
普通的 IP 头部长度为20 个字节,不包含IP 选项字段。
版本号(Version)字段标明了IP 协议的版本号,目前的协议版本号为4。下一代IP 协议的版本号为6。
报文长度指 IP 包头部长度,占4 位。
8 位的服务类型(TOS,Type of Service)字段包括一个3 位的优先权字段(COS,Class of Service),4 位TOS 字段和1 位未用位。4 位TOS 分别代表最小时延、最大吞吐量、最高可靠性和最小费用。
总长度(Total length)是整个IP 数据报长度,包括数据部分。
标识符(Identification)字段唯一地标识主机发送的每一份数据报。通常每发送一份报文它的值就会加1.
生存时间(TTL,Time to Live)字段设置了数据包可以经过的路由器数目。一旦经过一个路由器,TTL 值就会减1,当该字段值为0 时,数据包将被丢弃。
协议字段确定在数据包内传送的上层协议,和端口号类似,IP 协议用协议号区分上层协议。TCP 协议的协议号为6,UDP 协议的协议号为17。
报头校验和(Head checksum)字段计算IP 头部的校验和,检查报文头部的完整性。源IP 地址和目的IP 地址字段标识数据包的源端设备和目的端设备。

3.手动计算wireshark中的checksum值,发现checksum值是正确的,计算方法在另外一篇博文中:
https://blog.csdn.net/zhaozhiyuan111/article/details/97640966
发现程式中的TTL默认为255(在lwip配置文件lwipopt.h中),修改为64,无进展,卡死。

4.上网查到wireshark可以自动校验checksum,默认不打开。
设置方式:编辑----首选项----协议----udp----勾选Validata the UDP checksum if possible
记录一次checksum校验失败导致丢包的问题_第3张图片
5.结果发现是在传输层中的checksum出错,并非是网络层的checksum出错。
记录一次checksum校验失败导致丢包的问题_第4张图片
6.在lwipopt.h中打开了传输层与网络层的checksum定义CHECKSUM_GEN_IP和CHECKSUM_GEN_UDP(意味着发送时采用软件计算checksum):

//*****************************************************************************
//
// ---------- checksum options ----------
//
//*****************************************************************************
//#define CHECKSUM_GEN_IP                    0
#define CHECKSUM_GEN_ICMP              0
//#define CHECKSUM_GEN_UDP                   0
#define CHECKSUM_GEN_TCP                0
#define CHECKSUM_CHECK_IP               0
#define CHECKSUM_CHECK_UDP              0  
#define CHECKSUM_CHECK_TCP              0

兴高采烈的去lwip udp.c中的相应位置:
udpchksum = inet_chksum_pseudo(q, src_ip, dst_ip, IP_PROTO_UDP, q->tot_len);
去修改,发现怎么改抓包看到的checksum值都不变。

    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP packet length %"U16_F"\n", q->tot_len));
    udphdr->len = htons(q->tot_len);
    /* calculate checksum */
#if CHECKSUM_GEN_UDP
    if ((pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
      u16_t udpchksum = 0;
#if LWIP_CHECKSUM_ON_COPY
      if (have_chksum) {
        u32_t acc;
        udpchksum = inet_chksum_pseudo_partial(q, src_ip, dst_ip, IP_PROTO_UDP,
          q->tot_len, UDP_HLEN);
        acc = udpchksum + (u16_t)~(chksum);
        udpchksum = FOLD_U32T(acc);
      } else
#endif /* LWIP_CHECKSUM_ON_COPY */
      {
        udpchksum = inet_chksum_pseudo(q, src_ip, dst_ip, IP_PROTO_UDP, q->tot_len);
      }

      /* chksum zero must become 0xffff, as zero means 'no checksum' */
      if (udpchksum == 0x0000) {
        udpchksum = 0xffff;
      }
      udphdr->chksum = udpchksum;
    }
#endif /* CHECKSUM_GEN_UDP */
    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP checksum 0x%04"X16_F"\n", udphdr->chksum));
    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: ip_output_if (,,,,IP_PROTO_UDP,)\n"));
    /* output to IP */
    NETIF_SET_HWADDRHINT(netif, &pcb->addr_hint);
    err = ip_output_if(q, src_ip, dst_ip, pcb->ttl, pcb->tos, IP_PROTO_UDP, netif);
    NETIF_SET_HWADDRHINT(netif, NULL);

7.问题指向了checksum是否是由硬件校验生成,导致软件修改无果。
最终发现确实如此,在数据包的实际传输tivaif_transmit函数中,配置了网卡的状态为DES0_TX_CTRL_IP_ALL_CKHSUMS,修改为DES0_TX_CTRL_NO_CHKSUM,问题完美解决。

在这里插入代码片
总结:udp在发送数据时会因为包头中的checksum值不对而丢包,一般为了提高CPU的工作效率,都会默认为checksum的校验方式为硬件校验,导致在超过MTU值限制时,硬件网卡自动生成的checksum值出错,改为软件校验可以解决此问题。

你可能感兴趣的:(物联网)