今天调试bug时, 忘了将原始的check_sum值reset,导致发包-抓包后发现。check-sum 错误。
来看一看check-sum:简单讲就是对要计算的数据,以16bit为单元进行累加,然后取反
在内核中构造数据包的时候,我们需要关注三个校验和:分别是sk_buf中的csum,ip_summed,ip头部中的check和udp或者tcp头部中的check
用于计算校验和的API:L3校验和的计算比L4的校验和要快得多,因为它只包含IP报头。校验和的API都在checksum.h中。
checksum在收包和发包时意义不一样
/* * @csum: Checksum (must include start/offset pair) * @csum_start: Offset from skb->head where checksumming should start * @csum_offset: Offset from csum_start where checksum should be stored * @ip_summed: Driver fed us an IP checksum */ struct sk_buff { union { __wsum csum; struct { __u16 csum_start; __u16 csum_offset; }; }; __u8 ip_summed:2,
csum_start: Offset from skb->head where checksumming should start csum_offset: Offset from csum_start where checksum should be store
/* Don't change this without changing skb_csum_unnecessary! */ #define CHECKSUM_NONE 0 #define CHECKSUM_UNNECESSARY 1 #define CHECKSUM_COMPLETE 2 #define CHECKSUM_PARTIAL 3
TCP收包时:
- CHECKSUM_UNNECESSARY
CHECKSUM_UNNECESSARY表示底层硬件或者协议栈已经计算了CSUM,也就是计算了tcp udp的伪头;所以TCP层在收到包后,发现skb->ip_summed为CHECKSUM_UNNECESSARY就不会再检查checksum;还有一种情况就是回环,因为在回环中错误发生的概率太低了,因此就不需要计算校验来节省cpu事件。
- CHECKSUM_NONE
csum中的校验和无效,需要L4层自己校验payload和伪头;可能有以下几种原因:设备不支持硬件校验和计算;设备计算了硬件校验和,但发现该数据帧已经损坏。部分驱动不会丢弃,而是将ip_summed设置为CHECKSUM_NONE,然后交给上层协议栈重新计算并处理这种错误。
- CHECKSUM_COMPLETE
网卡已经计算了L4层报头和payload的校验和,并且skb->csum已经被赋值,此时L4层的接收者只需要加伪头并验证校验结果。
1) 在L4层发现skb->ip_summed==CHECKSUM_UNNECESSARY,或者skb的csum_valid字段有效, 则放行该报文。skb->ip_summed==CHECKSUM_PARTIAL,但是checksum_start_offset存在,也放行。
2) 如果skb->ip_summed为CHECKSUM_COMPLETE,则把skb->csum加上伪头进行校验,成功则将skb->ip_summed设为CHECKSUM_UNNECESSARY,同时设置 skb->csum_valid=1 并 放行该数据包。
3) 如果skb->ip_summed是CHECKSUM_NONE且 skb->csum_bad已经置位,则不能放行-丢弃。
4) 如是为CHECKSUM_NONE且 csum_bad==0 ;则需要将数据报文的payload加上skb->csum进行checksum计算,成功将设为CHECKSUM_COMPLETE并放行,失败则丢弃。
skb->csum:存放硬件或者软件计算的payload的checksum不包括伪头,或者是只有伪头,但是是否有意义由skb->ip_summed的值决定,同时不同版本内核代码其值也不一样
int tcp_v4_rcv(struct sk_buff *skb) { ------------------------------ if (skb_checksum_init(skb, IPPROTO_TCP, inet_compute_pseudo)) goto csum_error; -------------------------- }
skb_checksum_init(skb, IPPROTO_TCP, inet_compute_pseudo) 函数实质上调用的是:__skb_checksum_validate(skb, IPPROTO_TCP, false, false, 0, inet_compute_pseudo)
__skb_checksum_validate(skb, IPPROTO_TCP, false, false, 0, inet_compute_pseudo) ({ \ __sum16 __ret = 0; \ skb->csum_valid = 0; \ if (__skb_checksum_validate_needed(skb, false, 0)) \ __ret = __skb_checksum_validate_complete(skb, \ complete, inet_compute_pseudo(skb, proto)); \ __ret; \ }) static inline void __skb_decr_checksum_unnecessary(struct sk_buff *skb) { if (skb->ip_summed == CHECKSUM_UNNECESSARY) { if (skb->csum_level == 0) skb->ip_summed = CHECKSUM_NONE; else skb->csum_level--; } } static inline int skb_csum_unnecessary(const struct sk_buff *skb) { return ((skb->ip_summed == CHECKSUM_UNNECESSARY) || skb->csum_valid || (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_start_offset(skb) >= 0)); } static inline bool __skb_checksum_validate_needed(struct sk_buff *skb, bool zero_okay, __sum16 check) { if (skb_csum_unnecessary(skb) || (zero_okay && !check)) { skb->csum_valid = 1; __skb_decr_checksum_unnecessary(skb); return false; } return true; } static inline __wsum inet_compute_pseudo(struct sk_buff *skb, int proto) { return csum_tcpudp_nofold(ip_hdr(skb)->saddr, ip_hdr(skb)->daddr, skb->len, proto, 0); } /* Validate (init) checksum based on checksum complete. * * Return values: * 0: checksum is validated or try to in skb_checksum_complete. In the latter * case the ip_summed will not be CHECKSUM_UNNECESSARY and the pseudo * checksum is stored in skb->csum for use in __skb_checksum_complete * non-zero: value of invalid checksum * */ static inline __sum16 __skb_checksum_validate_complete(struct sk_buff *skb, bool complete, __wsum psum) { if (skb->ip_summed == CHECKSUM_COMPLETE) { if (!csum_fold(csum_add(psum, skb->csum))) { skb->csum_valid = 1; return 0; } } else if (skb->csum_bad) { /* ip_summed == CHECKSUM_NONE in this case */ return (__force __sum16)1; } skb->csum = psum; if (complete || skb->len <= CHECKSUM_BREAK) { __sum16 csum; csum = __skb_checksum_complete(skb); skb->csum_valid = !csum; return csum; } return 0; } __sum16 __skb_checksum_complete(struct sk_buff *skb) { __wsum csum; __sum16 sum; csum = skb_checksum(skb, 0, skb->len, 0); /* skb->csum holds pseudo checksum */ sum = csum_fold(csum_add(skb->csum, csum)); if (likely(!sum)) { if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE) && !skb->csum_complete_sw) netdev_rx_csum_fault(skb->dev); } if (!skb_shared(skb)) { /* Save full packet checksum */ skb->csum = csum; skb->ip_summed = CHECKSUM_COMPLETE; skb->csum_complete_sw = 1; skb->csum_valid = !sum; } return sum; }