IPV4--简单的三层处理流程
发现在工作的时候,如果先看书,看代码,再来总结,真是有点做重复功。因为总结的时候还是得看书,并且感觉还得一下下来。所以今后就一边看一边总结了,今后也能看清楚些了。
书上的前一章讲的是ipv4的一些概念,虽说自己以前看过tcp/ip协议方面的详细资料,但是现在看看还是收获不少。里面花了好多的笔墨来说分片的事,以前我确实没有意识到分片有这么大的影响,原来还有很多的考虑在里面,但是因为工作中在负责分片重组模块,所以对分片处理的性能降低还是有些切身体会的。对v4中分片机制弊端的考虑导致了v6中PMTU发现机制的出现(其实v4里面也有这种机制),下面说一下PMTU发现。
PMTU是指一条通信链路上允许传递的单个报文的最大值,也就是链路上每段网络的MTU的最低值。如果通信的双方都使用PMTU来封装IP报文就能避免分片,提高了效率。但是,路由的改变会使PMTU发生变化。PMTU发现机制利用了IP中DF标志位和ICMP,如果设置DF标志位,发送报文后没有收到表明报文过长的ICMP消息,那么表明此长度是小于等于PMTU的,但也许不是最佳值。Linux中如果使能了此机制并且和对端直接相连,那么初始值就是出接口的MTU;如果禁止了此机制,和对端不再同一个网段上,那么PMTU默认值是576--RFC791等规定的最小的网络MTU值。当然,用户也可以用ifconfig工具来配置这些。
Linux中对每一个目的ip,在routing cache entry中保存有其PMTU,这里为什么不对一条目的网段保存一个PMTU呢?可以考虑一下子网的情况,同一个网段上的主机并不一定就有一样的MTU值。上段提到了网络路由改变时,也可能要改变PMTU。这时如果主机收到了ICMP,表明上次的报文长度过大,那么主机尝试减少PMTU值,直到一个可接受的值,注意,这个值最小就是576。Linux的这种机制总是在减小PMTU,而不会去增大它,这也是目前的限制之一。
本章讲解linux中ipv4实现基础和一些特性。
1.
ip_rcv函数
ip_rcv函数是进入三层的第一个处理函数,也是体现netfilter中典型的两步走的一个实现。可以看到参数中的pt实际上没有被用到,应该只是为了保持协议模块注册机制的统一。
Ip_rcv只是执行一些基本的报文正确性检查,如长度,版本号,校验和等。其他的重要则完整地交给了ip_rcv_finish,这中间通过了netfilter的处理。
2.
ip_rcv_finish
ip_rcv_finish首先取得ip头的指针,然后检查skb->dst,如果此时还不知道此报文的目的地址,那么查询路由子系统得到dst。如果是上行的入报文,从前面的总结来看,此时是不知道dst的,所以这种场景下肯定会入路由子系统查询。如果一切正常,那么做一下用于流量管理的统计计数。随后就是一个重要的点:option的预处理。(注意这里选项的处理是在路由查询之前,而在报文出设备的情况下,顺序应该是相反的?)做完这些后,ip_rcv_finish的工作完成,通过dst_input(skb)将接力棒传递给了下一站:ip_local_deliver或者ip_forward。
3.
ip_options_compile
这里完成options的"预处理",所谓的预处理是指这里并不作实际的option处理工作,只是根据入报文的option,设置skb_buff中的一些字段,方便后续的处理,这些信息包括:option中有哪些选项,这些选项相对于ip头的偏移各自是多少,
处理这些选项是否需要更新校验和(isChanged指示)
。ip_rcv_finish调用ip_rcv_options,由后者调用ip_options_compile,下面以一种option来仔细看看ip_options_compile的处理过程。
首先,ip_options_compile的工作可以说只是收集一些信息--填充输入的opt结构,因为不同的option的处理分布在各处,这里统一做一个预处理是必要的。其次,ip_options_compile有两种被调用的环境:ip_rcv_finish,ip_options_get。上面说的就是前者,至于ip_options_get以后将会谈及。ip_rcv_finish调用ip_options_compile时,传入的skb非空,opt为NULL;而ip_options_get调用ip_options_compile时,则恰恰相反(书中说skb为空,但是从代码来看并非如此),这样,ip_options_compile就能判断出自己处在一种什么样的执行环境中了。
在ip_options_compile的开始处,会根据是何种环境,选择在适当的地方保存opt。
if (!opt) {
opt = &(IPCB(skb)->opt);/*opt为空,前一种环境-入报文,选择skb的cb域保存options的信息*/
iph = skb_network_header(skb);
opt->optlen = ((struct iphdr *)iph)->ihl*4 - sizeof(struct iphdr);
optptr = iph + sizeof(struct iphdr);/*optptr指向options的起始位置*/
opt->is_data = 0;
} else {
optptr = opt->is_data ? opt->__data :
(unsigned char *)&(ip_hdr(skb)[1]);/*注意,这里可以看出skb此时并不一定为空*/