当前位置:   article > 正文

Linux 内核IP和UDP检验和计算_ip option字段计算checksum

ip option字段计算checksum

IP checksum

a.     接收报文

struct iphdr *iph = ip_hdr(skb);

if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))

goto checksum_error;

 

b.     发送报文

ip_send_check(iph);

{

     iph->check = 0

     iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);

}

 

·       UDP checksum

 

a.     网卡设备属性

 

#define NETIF_F_IP_CSUM     2   /* 基于IPv4L4checksum. */  

#define NETIF_F_NO_CSUM     4   /* 设备可靠不需要L4checksum. loopack. */  

#define NETIF_F_HW_CSUM     8   /* 基于所有协议的L4checksum*/  

#define NETIF_F_IPV6_CSUM   16  /* 基于IPv6L4checksum*/  

 

通过ethtool可以查看网卡是否支持硬件checksum

 

ethtool -k eth0
Offload parameters for eth0:
Cannot get device rx csum settings: Operation not supported
Cannot get device udp large send offload settings: Operation not supported
rx-checksumming: off
tx-checksumming: on
scatter-gather: on
tcp segmentation offload: off
udp fragmentation offload: off
generic segmentation offload: off

 

tx-checksumming: on  表明支持发送hardware checksum

 

b.     linux UDP checksum数据结构

union {

            __wsum                       csum;

            struct {

                        __u16  csum_start;

                        __u16  csum_offset;

            };

};

1 skb->csumskb->ip_summed这两个域也是与4层校验相关的,这两个域的含义依赖于skb表示的是一个输入包还是一个输出包。

2 当网卡设备能提供硬件checksum并且作为输出包的时候,表示为skb-> csum_startskb-> csum_offset
csum_start: Offset from skb->head where checksumming should start

csum_offset: Offset from csum_start where checksum should be stored

 

  1.  当数据包是一个输入包时

skb->ip_summed表示的是四层校验的状态,下面的几个宏定义表示了设备驱动传递给4层的一些信息。

#define CHECKSUM_NONE 0  

#define CHECKSUM_UNNECESSARY 1  

#define CHECKSUM_COMPLETE 2  

 

skb->csum:存放硬件或者软件计算的payloadchecksum不包括伪头,但是是否有意义由skb->ip_summed的值决定。


CHECKSUM_NONE表示csum域中的校验值是无意义的,需要L4层自己校验payload和伪头。有可能是硬件检验出错或者硬件没有校验功能,协议栈软件更改如pskb_trim_rcsum函数。

CHECKSUM_UNNECESSARY表示网卡或者协议栈已经计算和验证了L4层的头和校验值。也就是计算了tcp udp的伪头。还有一种情况就是回环,因为在回环中错误发生的概率太低了,因此就不需要计算校验来节省cpu事件。

CHECKSUM_COMPLETE表示网卡已经计算了L4payload的校验,并且csum已经被赋值,此时L4层的接收者只需要加伪头并验证校验结果。

 

1 L4层发现如果udp->check位段被设为0,那么skb->ip_summed直接设为CHECKSUM_UNNECESSARY,放行该报文。

2)   如果skb->ip_summedCHECKSUM_COMPLETE,则把skb->csum加上伪头进行校验,成功则将skb->ip_summed设为CHECKSUM_UNNECESSARY 放行该数据包。

3)   通过上述后skb->ip_summed还不是CHECKSUM_UNNECESSARY,那么重新计算伪头赋给skb->csum

4)    将还不是CHECKSUM_UNNECESSARY的数据报文的payload加上skb->csum进行checksum计算,成功将设为CHECKSUM_UNNECESSARY并放行,失败则丢弃。

 

udp4_csum_init(skb, uh, proto)

{

            const struct iphdr *iph = ip_hdr(skb);

            if (uh->check == 0) {

                        skb->ip_summed = CHECKSUM_UNNECESSARY;

            } else if (skb->ip_summed == CHECKSUM_COMPLETE) {

                        if (!csum_tcpudp_magic(iph->saddr, iph->daddr, skb->len,

                                                      proto, skb->csum))

                                    skb->ip_summed = CHECKSUM_UNNECESSARY;

            }

            if (!skb_csum_unnecessary(skb))

                        skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr,

                                                                   skb->len, proto, 0);

}

 

if (udp_lib_checksum_complete(skb))

                        goto csum_error;

 

static inline int udp_lib_checksum_complete(struct sk_buff *skb)

{

            return !skb_csum_unnecessary(skb) &&

                        {

sum = csum_fold(skb_checksum(skb, 0, len, skb->csum));

                                    if (likely(!sum)) {

                                         if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE))

                                         netdev_rx_csum_fault(skb->dev);

                                         skb->ip_summed = CHECKSUM_UNNECESSARY;

                                    }

                                        return sum;

 }

}

 

  1. 当数据包是输出包时

skb->csum表示为csum_startcsum_offset,它表示硬件网卡存放将要计算的校验值的地址,和最后填充的便宜。这个域在输出包时使用,只在校验值在硬件计算的情况下才对于网卡真正有意义。硬件checksum功能只能用于非分片报文。
而此时ip_summed可以被设置的值有下面两种:

#define CHECKSUM_NONE       0  

#define CHECKSUM_PARTIAL 3

 

CHECKSUM_NONE 表示协议栈计算好了校验值,设备不需要做任何事。CHECKSUM_PARTIAL表示协议栈算好了伪头需要硬件计算payload checksum

 

1)对于UDP socket开启了UDP_CSUM_NOXMIT /* UDP csum disabled */

 uh->check = 0

skb->ip_summed = CHECKSUM_NONE;

 

2)软件udp checksum

struct iphdr *iph = ip_hdr(skb);

struct udphdr *uh = udp_hdr(skb);

uh->check = 0;                                     

skb->csum = csum_partial(skb_transport_header (skb), skb->len, 0);//skb->data指向传输层头

uh->check = csum_tcpudp_magic(iph->saddr, iph->daddr, skb->len, iph->protocol, skb->csum);

skb->ip_summed = CHECKSUM_NONE;

//Todo: scatter and gather

 

3)  硬件checksum: 只能是ip报文长度小于mtu的数据报(没有分片的报文)

CHECKSUM_PARTIAL表示使用硬件checksum L4层的伪头的校验已经完毕,并且已经加入uh->check字段中,此时只需要设备计算整个头4层头的校验值。

(对于支持scatter and gather的报文必须要传输层头在线性空间才能使用硬件checksum功能)

uh->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr, skb->len, IPPROTO_UDP, 0);

skb->csum_start = skb_transport_header (skb) - skb->head;

skb->csum_offset = offsetof(struct udphdr, check);

skb->ip_summed = CHECKSUM_PARTIAL;

 

d

最后在dev_queue_xmit发送的时候发现设备不支持硬件checksum就会进行软件计算

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,

                                    struct netdev_queue *txq)

{

/* If packet is not checksummed and device does not

 * support checksumming for this protocol, complete

* checksumming here.

*/

if (skb->ip_summed == CHECKSUM_PARTIAL) {

                                                skb_set_transport_header(skb,

                                                            skb_checksum_start_offset(skb));

                                                if (!(features & NETIF_F_ALL_CSUM) &&

                                                     skb_checksum_help(skb))

                                                            goto out_kfree_skb;

                                    }

}

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/385990
推荐阅读
相关标签
  

闽ICP备14008679号