今天调试bug时, 忘了将原始的check_sum值reset,导致发包-抓包后发现。check-sum 错误。

来看一看check-sum:简单讲就是对要计算的数据,以16bit为单元进行累加,然后取反

checksum在收包和发包时意义不一样

TCP收包时:

/*
* @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,

skb->ip_summed一般的取值如下 ;skb->csum:存放硬件或者软件计算的payload的checksum不包括伪头

/* 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
  • CHECKSUM_UNNECESSARY

    CHECKSUM_UNNECESSARY表示底层硬件已经计算了CSUM;所以TCP层在收到包后,发现skb->ip_summedCHECKSUM_UNNECESSARY就不会再检查checksum:

  • CHECKSUM_NONE

    csum中的校验和无效,可能有以下几种原因:设备不支持硬件校验和计算;设备计算了硬件校验和,但发现该数据帧已经损坏。部分驱动不会丢弃,而是将ip_summed设置为CHECKSUM_NONE,然后交给上层协议栈重新计算并处理这种错误。

  • CHECKSUM_COMPLETE

  网卡已经计算了L4层报头和payload的校验和,并且skb->csum已经被赋值,此时L4层的接收者只需要加伪头并验证校验结果。

TCP发包时:

skb->ip_summed用于L4校验和的状态,以通知底层网卡是否还需要处理校验和;此时ip_summed可以被设置的值有下面两种

  •  CHECKSUM_NONE

  CHECKSUM_NONE表示协议栈已经计算了校验和,设备不需要做任何事情

  • CHECKSUM_PARTIAL

     CHECKSUM_PARTIAL表示使用硬件checksum ,协议栈已经计算L4层的伪头的校验和

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

  1. TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。
  2. TCP的校验和是必需的,而UDP的校验和是可选的。
  3. TCP和UDP计算校验和时,都要加上一个12字节的伪首部。

  伪首部共有12字节,包含如下信息:源IP地址、目的IP地址、保留字节(置0)、传输层协议号(TCP是6)、TCP报文长度(报头+数据)。伪首部是为了增加TCP校验和的检错能力:如检查TCP报文是否收错了(目的IP地址)、传输层协议是否选对了(传输层协议号)等。

RFC 793的TCP校验和定义

  The checksum field is the 16 bit one's complement of the one's complement sum of all 16-bit words in the header and text. If a segment contains an odd number of header and text octets to be checksummed, the last octet is padded on the right with zeros to form a 16-bit word for checksum purposes. The pad is not transmitted as part of the segment. While computingthe checksum, the checksum field itself is replaced with zeros.

  把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。把TCP报头中的校验和字段置为0;

  校验和的计算与顺序无关, 可以从数据块开始计算, 也可以从未尾开始向前计算

RFC 1071的IP校验和定义

1. Adjacent octets to be checksummed are paired to form 16-bit integers, and the 1's complement sum of these 16-bit integers is formed.

2. To generate a checksum, the checksum field itself is cleared, the 16-bit 1's complement sum is computed over the octets concerned, and the 1's complement of this sum is placed in the checksum field.

3. To check a checksum, the 1's complement sum is computed over the same set of octets, including the checksum field. If the result is all 1 bits (-0 in 1's complement arithmetic), the check succeeds.

内核协议栈中:

为了提高计算效率, TCP包的校验和并不一次算出,而是采用32位部分累加和(sk->csum)进行增量计算.
csum_partial()用来计算数据块的32位部分累加和, 累加和可以用csum_fold()折叠为16位校验和.csum_partial_copy_nocheck()可在拷贝用户数据的同时计算出它的部分累加和.
为了加快执行速度, csum_partial()将8个32位字分为一组用分立的指令进行32位累加,这样可加长循环体中指令长度, 提高CPU指令流水线的效率.
 

TCP包接收校验的初始化
static __sum16 tcp_v4_checksum_init(struct sk_buff *skb)
{
const struct iphdr *iph = ip_hdr(skb);
//如果TCP包本身的校验已经完成
if (skb->ip_summed == CHECKSUM_COMPLETE) {
if (!tcp_v4_check(skb->len, iph->saddr, iph->daddr, skb->csum)) { //附加伪头进行校验
skb->ip_summed = CHECKSUM_UNNECESSARY;
return 0;
}
}
//生成包含伪头的累加和
skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr, skb->len, IPPROTO_TCP, 0);
if (skb->len <= 76) {
return __skb_checksum_complete(skb); //计算数据部分校验和
}
return 0;
}
附加伪头进行校验
static inline __sum16 tcp_v4_check(int len, __be32 saddr, __be32 daddr, __wsum base)
{
return csum_tcpudp_magic(saddr, daddr, len, IPPROTO_TCP, base);
}
static inline __sum16 csum_tcpudp_magic(__be32 saddr, __be32 daddr, unsigned short len, unsigned short proto, __wsum sum)
{
return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum));
}
生成包含伪头的累加和(源,目的,长度,协议号)
static inline __wsum csum_tcpudp_nofold(__be32 saddr, __be32 daddr, unsigned short len, unsigned short proto, __wsum sum)
{
__asm__(
"addl %1, %0 ;\n" //addl 加法
"adcl %2, %0 ;\n" //adcl 带进位的加法
"adcl %3, %0 ;\n"
"adcl $0, %0 ;\n" //如果有进位,进行累加
: "=r" (sum)
: "g" (daddr), "g"(saddr), "g"((len + proto) << 8), "0"(sum)
); return sum;
}
将32位累加和折叠成16位校验和
static inline __sum16 csum_fold(__wsum sum)
{
__asm__(
"addl %1, %0 ;\n"
"adcl $0xffff, %0 ;\n"
: "=r" (sum)
: "r" ((__force u32)sum << 16), "0" ((__force u32)sum & 0xffff0000)
);
return (__force __sum16)(~(__force u32)sum >> 16);
}
基于伪头累加和,完成全包校验
static __inline__ int tcp_checksum_complete(struct sk_buff *skb)
{
return skb->ip_summed != CHECKSUM_UNNECESSARY && __tcp_checksum_complete(skb);
}
__sum16 __skb_checksum_complete(struct sk_buff *skb)
{
return __skb_checksum_complete_head(skb, skb->len);
}
__sum16 __skb_checksum_complete_head(struct sk_buff *skb, int len)
{
__sum16 sum; 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;
}
__wsum skb_checksum(const struct sk_buff *skb, int offset, int len, __wsum csum)
{
int start = skb_headlen(skb);
int i, copy = start - offset;
int pos = 0; /* Checksum header. */
if (copy > 0) {
if (copy > len)
copy = len; csum = csum_partial(skb->data + offset, copy, csum);
if ((len -= copy) == 0)
return csum; offset += copy;
pos = copy;
}
......
}
计算32位中间累加和
unsigned int csum_partial(const unsigned char * buff, int len, unsigned int sum)
{
//arch/x86/lib/checksum_32.S 汇编文件
}
基于TCP用户数据的中间累加和, 生成TCP包校验码
void tcp_v4_send_check(struct sock *sk, int len, struct sk_buff *skb)
{
struct inet_sock *inet = inet_sk(sk);
struct tcphdr *th = tcp_hdr(skb); if (skb->ip_summed == CHECKSUM_PARTIAL) {
th->check = ~tcp_v4_check(len, inet->saddr, inet->daddr, 0); //附加伪头进行校验
skb->csum_start = skb_transport_header(skb) - skb->head;
skb->csum_offset = offsetof(struct tcphdr, check);
} else {
//完整的tcp校验和计算方法
th->check = tcp_v4_check(len, inet->saddr, inet->daddr, csum_partial((char *)th, th->doff << 2, skb->csum));
}
}
在拷贝用户数据时同时计算累加和
unsigned int csum_partial_copy_nocheck(const char *src, char *dst, int len, int sum)
{
return csum_partial_copy_generic(src, dst, len, sum, NULL, NULL); // arch/x86/lib/checksum_32.S
}
ip头校验和计算
static inline __sum16 ip_fast_csum(const void *iph, unsigned int ihl)
{
unsigned int sum; __asm__ __volatile__(
"movl (%1), %0 ;\n"
"subl $4, %2 ;\n"
"jbe 2f ;\n"
"addl 4(%1), %0 ;\n" //sum = sum + *(iph+4)
"adcl 8(%1), %0 ;\n" //sum = sum + *(iph+8) + carry
"adcl 12(%1), %0 ;\n" //sum = sum + *(iph+12) + carry
"1: adcl 16(%1), %0 ;\n" //sum = sum + *(iph+16) + carry
"lea 4(%1), %1 ;\n" //iph = iph + 4
"decl %2 ;\n"
"jne 1b ;\n"
"adcl $0, %0 ;\n"
"movl %0, %2 ;\n"
"shrl $16, %0 ;\n"
"addw %w2, %w0 ;\n"
"adcl $0, %0 ;\n"
"notl %0 ;\n"
"2: ;\n"
/* Since the input registers which are loaded with iph and ihl are modified, we must also specify them as outputs,
or gcc will assume they contain their original values. */
: "=r" (sum), "=r" (iph), "=r" (ihl)
: "1" (iph), "2" (ihl)
: "memory"
);
return (__force __sum16)sum;
}
递减ip->ttl,更新校验和
static inline int ip_decrease_ttl(struct iphdr *iph)
{
u32 check = (__force u32)iph->check;
check += (__force u32)htons(0x0100);
iph->check = (__force __sum16)(check + (check>=0xFFFF));
return --iph->ttl;
}
static inline __wsum csum_add(__wsum csum, __wsum addend)
{
u32 res = (__force u32)csum;
res += (__force u32)addend;
return (__force __wsum)(res + (res < (__force u32)addend));
} static inline __wsum csum_sub(__wsum csum, __wsum addend)
{
return csum_add(csum, ~addend);
} static inline __wsum csum_block_add(__wsum csum, __wsum csum2, int offset)
{
u32 sum = (__force u32)csum2;
if (offset & 1)
sum = ((sum & 0xFF00FF)<<8) + ((sum>>8) & 0xFF00FF);
return csum_add(csum, (__force __wsum)sum);
}
static inline __wsum csum_block_sub(__wsum csum, __wsum csum2, int offset)
{
u32 sum = (__force u32)csum2;
if (offset & 1)
sum = ((sum & 0xFF00FF)<<8) + ((sum>>8) & 0xFF00FF);
return csum_sub(csum, (__force __wsum)sum);
}
[/函数实现]
转载:https://www.cnblogs.com/super-king/p/3284884.html

https://hustcat.github.io/checksum-in-kernel/

https://www.kernel.org/doc/Documentation/networking/checksum-offloads.txt

https://w180112.pixnet.net/blog/post/200083785

http://blog.chinaunix.net/uid-25518484-id-5709671.html

IP/TCP/UDP checsum的更多相关文章

  1. Socket(套接字) IP TCP UDP HTTP

    Socket(套接字) 阮老师的微博 (转)什么是套接字(Socket)? 应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题.多个TCP连接或多个应用程序进 ...

  2. IP,TCP,UDP Checksum校验

    IP数据报的校验: IP数据报只需要对数据头进行校验,步骤如下: 将接收到的数据的checksum字段设置为0 把需要校验的字段的所有位划分为16位(2字节)的字 把所有16位的字相加,如果遇到进位, ...

  3. 以太网,IP,TCP,UDP数据包分析【转】

    原文地址:http://www.cnblogs.com/feitian629/archive/2012/11/16/2774065.html 1.ISO开放系统有以下几层: 7 应用层 6 表示层 5 ...

  4. 以太网,IP,TCP,UDP数据包分析(此文言简意赅,一遍看不懂的话,耐心的看个10遍就懂了,感谢作者无私奉献)

    1.ISO开放系统有以下几层: 7 应用层 6 表示层 5 会话层 4 传输层 3 网络层 2 数据链路层 1 物理层 2.TCP/IP 网络协议栈分为应用层(Application).传输层(Tra ...

  5. 《TCP/IP - TCP/UDP》

    一:概述 - 由于 IP 的传输是无状态的,IP 提供尽力服务,但并不保证数据可以到达主机. - 所以,数据的完整性需要更上层的 传输层来保证.TCP和UDP 均属于 传输层. 二:UDP - 特点 ...

  6. 以太 ip tcp udp 三次握手的理解

    以太帧: 1.前导码(7字节):使接收器建立比特同步. 2.起始定界符SFD(1字节):指示一帧的开始. 3.目的地址DA(6字节):指出要接收该帧的工作站. 4.源地址SA(6字节):指示发送该帧的 ...

  7. 以太网,IP,TCP,UDP数据包分析

    http://www.cnblogs.com/feitian629/archive/2012/11/16/2774065.html 网络层的IP 协议是构成Internet 的基础.IP 协议不保证传 ...

  8. Ethernet IP TCP UDP 协议头部格式

    The Ethernet header structure is shown in the illustration below: 以太网头部14 bytes Destination Source L ...

  9. 【网络编程】TCP/IP、UDP、网络概…

    计算机刚刚发明出来的时候,两台计算机之间是无法通信的,为了使计算机之间能够进行数据的交流,制定了OSI(Open SystemInterconnection)开放系统互联模型,而TCP/IP(我们所使 ...

随机推荐

  1. 文件上传-阿里云OSS-存储文件

    JS上传文件到阿里云OSS OSS支持流式写入和读出.特别适合视频等大文件的边写边读业务场景. 注意在OSS的控制台:跨域管理中设置允许的方法 <script> var client = ...

  2. 基于python实现顺序存储的栈

    """ 栈 sstack.py 栈模型的顺序存储 重点代码 思路总结: 1.列表是顺序存储,但功能多,不符合栈的模型特征 2.利用列表,将其封装,提供接口方法 " ...

  3. Linux系统快速搭建LAMP

    LAMP是 Linux + Apache + MySQL + PHP 的环境要求,即web服务器. 1.前置条件: (1)搭建Linux系统(参考博客:https://www.cnblogs.com/ ...

  4. selenium 浏览器最大化

    from time import sleep from selenium import webdriver from selenium.webdriver.chrome.options import ...

  5. dom4j api 详解【转】

    1.DOM4J简介 DOM4J是 dom4j.org 出品的一个开源 XML 解析包.DOM4J应用于 Java 平台,采用了 Java 集合框架并完全支持 DOM,SAX 和 JAXP. DOM4J ...

  6. python 微信小程序自动化

    微信小程序自动化 https://www.cnblogs.com/yyoba/python27 - FautoTesthttps://www.cnblogs.com/yyoba/p/9973731.h ...

  7. Vue中封装axios组件实例

    首先要创建一个网络模块network文件夹  里面要写封装好的几个组件 在config.js里面这样写 在index.js要这样写 core.js文件里面内容如下 然后要在main.js文件里面要设置 ...

  8. docker gitlab搭建

    1,官方查找gitlab docker镜像 2,pull镜像 3,run docker run -d -p 443:443 -p 10080:80 -p 22:22 --name gitlab --r ...

  9. Linux的top命令及交换分区

    TOP命令关键指标 %MEM,在内存中的占用率 %CPU,使用率,如果两核,最大可到200% TIME+, 占用cpu的总时间/s SHR,分享内存 RES, 常驻内存,进程当前使用的内存大小,不包括 ...

  10. 【转】Event Driven Programming

    FROM: http://lazyfoo.net/tutorials/SDL/03_event_driven_programming/index.php Event Driven Programmin ...