kernel 校验和实现
kernel 校验和实现
Kernel checksum implementation ) TCP包的错误检测使用16位累加和校验. 除了TCP包本身,
TCP校验数据块还包括源IP地址,目的IP地址, TCP包长度, TCP协议号组成的12字节伪头标. ) 校验和为16位字补码和, 数据块长度为奇数时, 数据块末尾添零处理.
校验和的计算与顺序无关, 可以从数据块开始计算, 也可以从未尾开始向前计算. ) 为了提高计算效率, TCP包的校验和并不一次算出,
而是采用32位部分累加和(sk->csum)进行增量计算.
csum_partial()用来计算数据块的32位部分累加和, 累加和可以用csum_fold()折叠为16位校验和.
csum_partial_copy_nocheck()可在拷贝用户数据的同时计算出它的部分累加和. ) 为了加快执行速度, csum_partial()将8个32位字分为一组用分立的指令进行32位累加,
这样可加长循环体中指令长度, 提高CPU指令流水线的效率. ) 并不是所有的TCP包都必须校验, skb->ip_summed用来控制校验操作.
对于loopback设备的收发包, 其skb->ip_summed设为HECKSUM_UNNECESSARY, 忽略校验过程.
如果 skb->ip_summed == CHECKSUM_HW 说明TCP包本身的校验已经由硬件链路层完成,新kernel(2.6.)已经不使用这个选项。 下面我们介绍具体实现,基于kernel-2.6., x86 architecture. [函数实现]
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 ;
}
}
//生成包含伪头的累加和
skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr, skb->len, IPPROTO_TCP, );
if (skb->len <= ) {
return __skb_checksum_complete(skb); //计算数据部分校验和
}
return ;
}
附加伪头进行校验
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) << ), ""(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 << ), "" ((__force u32)sum & 0xffff0000)
);
return (__force __sum16)(~(__force u32)sum >> );
}
基于伪头累加和,完成全包校验
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, , 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 = ; /* Checksum header. */
if (copy > ) {
if (copy > len)
copy = len; csum = csum_partial(skb->data + offset, copy, csum);
if ((len -= copy) == )
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, ); //附加伪头进行校验
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 << , 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)
: "" (iph), "" (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 & )
sum = ((sum & 0xFF00FF)<<) + ((sum>>) & 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 & )
sum = ((sum & 0xFF00FF)<<) + ((sum>>) & 0xFF00FF);
return csum_sub(csum, (__force __wsum)sum);
}
[/函数实现]
kernel 校验和实现的更多相关文章
- MIT JOS学习笔记02:kernel 01(2016.10.28)
未经许可谢绝以任何形式对本文内容进行转载! 在文章开头不得不说的是,因为这部分的代码需要仔细理清的东西太多,所以导致这篇分析显得很啰嗦,还请谅解. 我们在上一篇文章已经分析了Boot Loader的功 ...
- TCP校验和的原理和实现
http://blog.csdn.net/zhangskd/article/details/11770647 分类: Linux TCP/IP Linux Kernel 2013-09-24 ...
- TCP Socket Establish;UDP Send Package Process In Kernel Sourcecode Learning
目录 . 引言 . TCP握手流程 . TCP connect() API原理 . TCP listen() API原理 . UDP交互过程 . UDP send() API原理 . UDP bind ...
- Nand ECC校验和纠错原理及2.6.27内核ECC代码分析
ECC的全称是Error Checking and Correction,是一种用于Nand的差错检测和修正算法.如果操作时序和电路稳定性不存在问题的话,NAND Flash出错的时候一般不会造成整个 ...
- 一步步学习操作系统(2)——在STM32上实现一个可动态加载kernel的"my-boot"
如果要做嵌入式Linux,我们首先要在板子上烧写的往往不是kernel,而是u-boot,这时需要烧写工具帮忙.当u-boot烧写成功后,我们就可以用u-boot附带的网络功能来烧写kernel了.每 ...
- 解决wireshark抓包校验和和分片显示异常
问题描述: 在使用wireshark抓取报文时,发现从10.81.2.92发过来的报文绝大部分标记为异常报文(开启IPv4和TCP checksum) 分析如下报文,发现http报文(即tcp pay ...
- 深入linux kernel内核配置选项
============================================================================== 深入linux kernel内核配置选项 ...
- virtio_net设备的校验和问题
我们来看一个virtio_net设备的校验和配置: [root@10 ~]# ethtool -K eth0 tx-checksumming on //caq:大写的K用来调整feature [roo ...
- Linux 内核概述 - Linux Kernel
Linux 内核学习笔记整理. Unix unix 已有40历史,但计算机科学家仍认为其是现存操作系统中最大和最优秀的系统,它已成为一种传奇的存在,历经时间的考验却依然声名不坠. 1973 年,在用 ...
随机推荐
- MVC小系列(二十)【给Action提供HttpStatusCodeResult】
主要用到: HttpStatusCodeResult 和HttpStatusCode 的http返回状态 比如: /// <summary> /// 使用异步模式 /// </sum ...
- 查看SQL server 2008 R2 的Service Package 版本号(同样适用于SQL Server 2005)
在SQL Server 中新建一个查询(new Query),然后输入下面的SQL脚本,即可看到当前的数据库的Service Package (补丁包)的版本号 select serverproper ...
- 学习笔记_过滤器应用(粗粒度权限控制(拦截是否登录、拦截用户名admin权限))
RBAC ->基于角色的权限控制 l tb_user l tb_role l tb_userrole l tb_menu(增.删.改.查) l tb_rolemenu 1 说明 我们给 ...
- 深入理解Java的接口和抽象类 _摘抄
http://www.cnblogs.com/dolphin0520/p/3811437.html 原文 深入理解Java的接口和抽象类 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可 ...
- Glibc和GCC,ARM-LINUX-GCC的关系
看到有些贴子/blog上提到「Glibc编译器」,这是个错误的用语.Glibc不是编译器,Glibc不是编译器,Glibc不是编译器.重要的事情说三遍.GCC才是编译器.
- Java中ArrayList源码分析
一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保 ...
- AOP学习过程中遇到的问题汇总
jdk版本问题: 在spring较低的版本中,仅支持jdk1.5到1.7版本,由于我本机安装的是jdk1.8,所以在调试的时候就会提示jdk版本要高于1.5.于是换成spring4.0,在co ...
- Ubuntu 之旅—— 调整扩展屏分辨率
打开终端输入 xrandr 得到如下信息 Screen 0: minimum 320 x 200, current 2390 x 768, maximum 8192 x 8192 LVDS conne ...
- 安装交叉编译arm-linux-gcc环境
设置好交叉编译的执行文件路径贴加到环境变量PATH 设置如下 export PATH=$PATH:/XXX/XXX/bin /etc/profile /~/.bashrc source /etc/p ...
- SQL VIEW(视图)
1,视图包含行和列,就像一个真实的表. 2,视图中的字段就是来自一个或多个数据库中的真实的表中的字段. 3,我们可以向视图添加 SQL 函数.WHERE 以及 JOIN 语句,我们也可以提交数据,就像 ...