最近要做的东西跟网络相关,需要对于以ipv4协议格式承载的ipv6信息进行处理,对头部相关信息进行修改,这时,对于每一个数据包就要涉及到校验和的问题了。
一、ipv4、tcp、udp的校验和
IP/ICMP/IGMP/TCP/UDP等协议的校验和算法都是相同的,采用的都是将数据流视为16位整数流进行重复叠加计算。为了计算检验和,首先把检验和字段置为0。然后,对有效数据范围内中每个16位进行二进制反码求和,结果存在检验和字段中,如果数据长度为奇数则补一字节0。当收到数据后,同样对有效数据范围中每个16位数进行二进制反码的求和。由于接收方在计算过程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全0或全1(具体看实现了,本质一样) 。如果结果不是全0或全1,那么表示数据错误。
对于IPv4层中的校验和只包括IPv4头部分,不包括上层协议头和应用层数据,校验和是必须计算的。
对于ICMP/IGMP校验和计算范围为从ICMP/IGMP开始到数据结束,不包括IP头部分,校验和是必须计算的。
对于TCP及UDP层的检验和计算有点特殊,所计算的数据范围除了包括TCP/UDP头开始到数据结束外,还要包括一个IP伪头部分,所谓伪头,只有12字节数据,包括源地址(4字节)、目的地址(4字节)、协议(2字节,第一字节补0)和TCP/UDP包长(2字节)。TCP的校验和是必须的,而UDP的校验和是可选的,如果UDP中校验和字段为0,表示不进行校验和计算,因此对于UDP协议数据的修改后想偷懒的话直接将校验和设为0 就可以了。
下面通过一个具体的例子,来看一下在各层的检验和的计算方式
IPv4分组头的结构如下所示:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
其中的"Header Checksum"域即为头校验和部分。当要计算IPv4分组头校验和时,发送方先将其置为0,然后按16位逐一累加至IPv4分组头结束,累加和保存于一个32位的数值中。如果总的字节数为奇数,则最后一个字节单独相加。累加完毕将结果中高16位再加到低16位上,重复这一过程直到高16位为全0。下面用实际截获的IPv4分组(数据连路层DLC的包)来演示整个计算过程:
0x0000: 00 60 47 41 11 c9 00 09 6b 7a 5b 3b 08 00 45 00
0x0010: 00 1c 74 68 00 00 80 11 59 8f c0 a8 64 01 ab 46
0x0020: 9c e9 0f 3a 04 05 00 08 7f c5 00 00 00 00 00 00
0x0030: 00 00 00 00 00 00 00 00 00 00 00 00
在上面的16进制采样中,起始为Ethernet帧(DLC包)的开头。IPv4分组头从地址偏移量0x000e开始,第一个字节为0x45,最后一个字节为0xe9,即IPv4分组头到目标IP地址为止。根据以上的算法描述,我们可以作如下计算:
(1) 0x4500 + 0x001c + 0x7468 + 0x0000 + 0x8011 + 0x0000(累加和位置先置0) + 0xc0a8 + 0x6401 + 0xab46 + 0x9ce9 = 0x3a66d
(2) 0xa66d + 0x3 = 0xa670
(3) 0xffff - 0xa670 = 0x598f
注意在第一步我们用0x0000设置头校验和部分。可以看出这一分组头的校验和与收到的值完全一致。以上的过程仅用于发送方计算初始的校验和,实际中对于中间转发的路由器和最终接收方,可将收到的IPv4分组头校验和部分直接按同样算法相加,如果结果为0xffff,则校验正确。
对于TCP和UDP的数据报,其头部也包含16位的校验和,校验算法与IPv4分组头完全一致,但参与校验的数据不同。这时校验和不仅包含整个TCP/UDP数据报,还覆盖了一个虚头部。虚头部的定义如下:
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| source address |
+--------+--------+--------+--------+
| destination address |
+--------+--------+--------+--------+
| zero |protocol| TCP/UDP length |
+--------+--------+--------+--------+
其中有IP源地址,IP目的地址,协议号(TCP:6/UDP:17)及TCP或UDP数据报的总长度(头部+数据)。将虚头部加入校验的目的,是为了再次核对数据报是否到达正确的目的地,并防止IP欺骗攻击(spoofing)。上述报文在0x0018处的协议类型=十六进制11,即该报文是一个UDP报文,其长度存放在0x0027开始的两个字节(含源目端口地址4字节+UDP长度2字节+校验和2字节=8字节,以及UDP数据的长度:故本数据包UDP数据的长度实际为0字节),IP源目地址存放在0x0x1a到0x0x21共八个字节中,先将校验和0x002a处的两个字节置0,计算UDP包的校验和如下:
(1) 0xc0a8+0x6401(前为源IP)+0xab46+0x9ce9(前为目IP)+0x0011(即Zero和Protocol)+ 0x0008(UDP长度)+ 0x0f3a(源端口)+0x0405(目端口)+0x0008(UDP长度)+0x0000(校验和预置为0)+…(这里没有任何数据了:UDP数据的长度实际为0字节)=0x28038
(2) 0x28038=>0x8038+0x0002=0x803A
(3) 0xFFFF-0x803A=0x7FC5
计算结果和0x0028处的结果相同,注意UDP长度出现了两次。
二、ipv6、icmp6、igmp6、tcp、udp的检验和
在ipv4格式中的每个协议的定义中都建议使用校验和,虽然它不是必须的。然而,必须承认的是一个好的相关协议的设计可以去掉一些不同层协议之间的重叠特性带来的开销。因为大多数L2和L4协议提供校验和,在L3中也有校验和就不是严格必须的。正是由于这个原因,IPv6中去掉了这个校验和,也就是在ipv6协议的格式中的ip头部是没有检验和字段的。
这样的话,对于ip层的icmp6及igmp6的检验和计算方式则与ipv4不同,此时icmp6及igmp6的检验和计算方式更类似于上层的tcp及udp,需要构造伪首部,在伪首部字段内要包含ipv6的源地址、目的地址、协议类型及长度等字段。
在ipv6中检验和的计算通常是借助于函数csum_ipv6_magic及csum_partial两个函数,比如在我的试验中,在ip6的头部中改变了源地址,那么我同样需要重新构造icmp6头部的检验和字段。
首先,将icmp6头部的检验和字段置为0
然后,调用函数icmp6h->icmp6_cksum = csum_ipv6_magic( &ipv6h->saddr,&ipv6h->daddr,len,IPPROTO_ICMPV6,csum_partial((u8 *)icmp6h,len,0));
其中长度通过len = skb->len - sizeof(struct iphdr) - sizeof(struct ipv6hdr); //ipv4承载ipv6数据包的一种格式 ipv4头+ipv6头+icmp6头
如果在数据包改变的过程中长度发生变化,那么就必须先更新各头部的头部信息,再进行上述计算
对于tcp及udp在ipv4及ipv6协议中并没有进行什么改变,其检验和的构造方式仍然为通过构造伪头部实现,同样也能借助于上述函数,只不过改变具体的协议类型即可。
暂时只用到了icmp6头部的检验和字段,对于其内部实现也没有什么了解,以后会随着自己逐渐的了解,慢慢完善这一部分的学习,~O(∩_∩)O~
阅读(3857) | 评论(0) | 转发(0) |