Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1070625
  • 博文数量: 252
  • 博客积分: 4561
  • 博客等级: 上校
  • 技术积分: 2833
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-15 08:23
文章分类

全部博文(252)

文章存档

2015年(2)

2014年(1)

2013年(1)

2012年(16)

2011年(42)

2010年(67)

2009年(87)

2008年(36)

分类:

2010-05-26 16:57:29

校验和是网络协议用来识别传输错误的冗余域。有些校验和不但能检测错误,还能自动修正某些类型的错误。
校验和的想法很简单。在传输一个数据 包之前,发送方计算出一个很小的、固定长度的域 (校验和)包含数据的某种散列。如果在传输过程中某几位数据被改变,很可能损坏的数据会产生一个不同的校验和。取决于你用来产生校验和使用的函数,校验和 提供不同级别的可靠性。IP协议采用的校验和是简单的一个包括求和取反码,这个方法太弱了,不能被认为是可靠的。对于更可靠的完整性检查,你必须依赖于 L2 CRC或者SSL/IPSec消息认证码。
不同的协议可以使用不同的校验和算法。IP协议校验和只覆盖IP头。大多数L4协议的校验和均覆盖头和数据。
看 起来在L2(比如,以太网)有校验和,L3(比如,IP)有另一个,L4(比如,TCP)还有一个的做法是冗余的,因为它们全都应用于数据的重叠部分,但 是检查是有价值的。错误不只在传输过程中发生,也会在层之间移动中发生。而且,每个协议负责保证他自己的正确传输,不能假设高或低的层完成这个任务。
举一个可能发生的复杂情况的例子,想象LAN1上的PC A通过Internet发送数据给LAN2上的PC B。假设LAN1中使用的L2协议使用校验和而LAN2上的不使用。那么最少一个高层提供某种形式的校验和来减小接受损坏数据的可能性是很重要的。
每 个协议的定义中都建议使用校验和,虽然它不是必须的。然而,必须承认的是一个好的相关协议的设计可以去掉一些不同层协议之间的重叠特性带来的开销。因为大 多数L2和L4协议提供校验和,在L3中也有校验和就不是严格必须的。正是由于这个原因,IPv6中去掉了这个校验和。
在IPv4中,IP校验和 是一个16位域覆盖整个IP头,包括选项。校验和最初由数据包源来计算,并在整个到目标的过程中一个跳跃一个跳跃的被更新以反映每个路由器带来的头部变 化。在更新校验和之前,每个跳跃首先必须检查包的完整性通过比较包中的和本地计算的校验和。如果完整性检查失败,包会被丢弃,但是不会产生ICMP:L4 协议会处理的(例如,使用在给定时间内没有应答就强制重发的定时器)。
这里是一些会触发更新校验和需求的情况:
减小TTL
路由器在转发数据包前必须减小包IP头中的TTL。由于IP校验和同样覆盖了那个域,原始的校验和就不再有效了。你将在第20章"ip_forward函数"节中看到TTL被ip_decrease_ttl减小,这个函数也处理校验和。
数据包破坏(包括NAT)
所有带来一个或多个IP头部域改变的特性均强制重新计算校验和。NAT大概是最著名的例子。
IP选项处理
由于选项是头的一部分,它们也被校验和覆盖。于是,每次它们被需要增加或者修改IP头(例如,时间戳的增加)的方式处理时,强制重新计算校验和。
分片
当一个数据包被分片时,每个分片有一个不同的头。大多数的域保持不变,但是与分片有关的域,比如偏移,就是不同的了。因此,校验和不得不重新计算。
由 于IP协议适用的校验和使用与TCP、UDP、ICMP相同的简单算法,它们共用一组通用的函数。也有一个为了IP校验和特殊优化的函数。按照IP校验和 算法的定义,头部被分成16位的字进行求和和取补码。图18-13显示了一个校验和计算的例子,为了简单只对两个16位字求和。Linux不对16位字求 和,它对32位甚至64位字求和,目的是更快速计算(这需要在求和和取反码之间增加一个额外的步骤;参看下一节中csum_fold的描述)。实现这个算 法的函数叫做ip_fast_csum,在大多数体系结构中直接用汇编语言。

图18-13. IP校验和计算

18.5.1. 校验和计算API
L3 (IP)校验和计算比L4校验和快的多,因为它只覆盖IP头。由于它是一个开销不大的操作,它经常在软件中被计算。
用 来计算校验和的通用函数组放在按体系结构文件include/asm-xxx/checksum.h中。(例如,对于i386平台,就是include /asm-i386/checksum.h。)每个协议直接使用正确的输入参数调用通用函数,或者定义一个包装调用通用函数。当改变一个以前校验正确的一 段数据比如IP头时,校验和算法允许一个协议简单更新校验和,而不是完全从头重新计算。
IP专用的函数原型在checksum.h,ip_fast_csum,在这里展示。函数需要IP头指针(iph)和它的长度(ihl)作为参数。后者由于IP选项可能会改变。返回值就是校验和。这个函数利用了IP头长度总是4字节的倍数这个事实来流水线某些处理。
static inline
unsigned short ip_fast_csum(unsigned char * iph, unsigned int ihl)

当计算要传送的包IP头校验和时,iphdr->check的值应该首先值为零,因为校验和不应该反映它自己。在这个算法中,由于它使用简单 求和,一个零值域会被有效的排除在校验和结果之外。这就是为什么在代码的不同部分,你会看到恰在调用ip_fast_csum之前将这个域清零的原因。
校 验和算法有一个很有趣的属性可能开始会让读包转发和接收的人困惑。如果校验和正确,转发或者接收代码在整个头运行算法(保持原始 iphdr->check域),将得到结果为零。如果你查看函数ip_rcv,你会看到这正是适用校验和验证输入包的方法。这种检查损坏的方法比更 直观的将iphdr->check 清零再重新计算的方法要快的多。
这里是用来计算或者更新IP校验和的主要函数:

ip_compute_csum
计算校验和的通用函数。它简单地接收任意大小的缓冲区作为输入。

ip_fast_csum
给定IP头和长度,计算并返回IP校验和。可以用来验证一个输入包和计算外发包的校验和。
你可以认为ip_fast_csum是ip_compute_csum为IP头优化的变体。

ip_send_check
计算外发数据包的IP校验和。它仅是ip_fast_csum的一个包装,加上在处理前将iphdr->check 清零。

ip_decrease_ttl
当修改IP头中的一个域时,对IP校验和做增量更新比完全重新计算快的多。这个成为可能要感谢计算校验和所使用的简单算法。一个常见的例子就是转发一个数据包,于是要减小它的iphdr->ttl域。ip_decrease_ttl在ip_forward中被调用。
在前面提到的checksum.h文件中还有一些其它的通用支持例程file,但是它们大多由L4协议使用。例如:

skb_checkum
定义在net/core/skbuff.c中,它是一个被几个包装使用的通用校验和函数(包括前面列出的一些函数),主要由L4协议在特殊情况下使用。

csum_fold
将32位值的高16位折叠到低16位中,然后取反输出值。这个操作正常情况下是校验和计算的最后阶段。

csum_partial[_ xxx]
这一族函数计算校验和,没有csum_fold完成的最终折叠。L4协议可以调用 csum_partial 函数中的一个来计算L4数据的校验和,然后调用象csum_tcpudp_magic这样的函数对伪头(在后面的章节中介绍)计算校验和,最后对两个部分 和求和并且折叠结果。
csum_partial和它的一些变体在大多数体系结构中用汇编语言编写。

csum_block_add

csum_block_sub
分别加和减两个校验和。第一个对于增量计算一块数据的检验和很有用。第二个当一组数据从已经计算过校验和的数据中移除时可能会需要。许多其它函数内部使用这两个函数。

skb_checksum_help
这个函数有两种不同的行为,取决于传给它输入包还是输出包。
对于输入包,它使L4硬件校验和无效。
对 于输出包,它计算L4校验和。当外发设备硬件校验和能力不可用时(见第11章dev_queue_xmit),或者当L4硬件校验和无效并因此需要重新计 算时,使用它。一个校验和会被无效,比如,通过Netfilter的NAT操作,或者当IPsec协议组的变换协议通过在原始IP头和L4头之间插入附加 头破坏载荷时。同时注意,如果一个设备能用硬件计算L4校验和并保存到L4头中,它将导致修改L3载荷,这在后者被IPsec组摘要或者加密时是不可能 的,因为它将使数据无效。

csum_tcpudp_magic
计算TCP和UDP伪头的校验和(见图18-14)。
新的网卡能提供IP和L4校验和的硬件计 算。Linux利用了大多数现代网卡的L4硬件校验和能力,但是没有利用IP硬件校验和能力,因为额外的复杂度是不值得的。(例如,给定有限大小的IP头 软件计算已经足够快了)。硬件校验和只是CPU减轻负担的一个例子,可以让内核更快的处理数据包;大多数现代网卡也提供了一些L4(主要是TCP)负担减 轻。硬件校验和在第19章简要描述。
18.5.2. 改变L4校验和
TCP和UDP协议计算校验和覆盖头、载荷,以及周知的伪头,伪头基本上就是一块为了方便取自IP头的数据域(见图18-14)。换句话说,出现在IP头的一些信息最终统一到L4校验和中。注意,伪头只为了计算校验和而定义;它不存在在真实的数据包中。

图18-14. TCP和UDP计算校验和时使用的伪头

不幸的是,IP层有时为了NAT或者其他活动需要改变一些TCP和UDP在伪头中使用的IP头部域。IP级别的改变是L4校验和无效。如果校验和不 动,那么在IP层没有节点可以检测出任何错误,因为它们只校验IP头。然而,目标主机的TCP层将确信包已损坏。因此内核需要处理这种情况。
进一步,存在这样的常规情况,对于接收帧硬件计算的L4校验无效。下面是最常见的情况:

  1. 当一个输入L2帧包含为了达到最小大小的填充时,但是网卡在计算校验和时没聪明到去掉填充。在这种情况下,硬件校验和就不会匹配接收方L4层计算 的校验和。你会在第19章"处理输入包"节中看到,为了安全,ip_rcv函数总是在这种情况下使硬件校验和无效。在第四部分中,你将看到桥接代码做了相 似的事情。
  2. 当一个输入IP分片与前面接收到的分片重叠时。见第22章。
  3. 当一个输入IP包使用任何IPsec组协议时。在这种情况下,L4校验和不能由网卡正确计算,因为L4头和载荷或者被压缩、摘要,或者加密。例子见net/ipv4/esp4.c中esp_input。
  4. 校验和需要重新计算由于NAT或者一些类似的IP层干预。例如,见net/ipv4/netfilter/ip_nat_standalone.c中ip_nat_fn。

虽然名字可能被证明是让人糊涂的,但是skb->ip_summed域与L4校验和有关(更多细节见第19章)。它的值由IP层操作,当IP层知道有些东西使L4校验和无效时,比如属于伪头一部分的域的改变。
我不会介入本地生成包校验和是如何计算的细节。但是我们将在第21章"拷贝数据到分片:getfrag"节简要看到当创建分片时它是如何增量计算的。

阅读(884) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~