在ip_rcv_finish函数的尾端,如果目的地址和本地接口不同,内核必须把封包转发至适当的主机,若目的地址是本地地址,内核必须把封包准备好,以便高层使用。本章主要介绍转发和本地传递是怎么完成的。
转发的工作由ip_forward函数和ip_forward_finish完成。此时,ip_rcv_finish里已经调用了ip_route_input,所以skb_buff中已经包含了转发封包所需要的所有信息。转发由下列步骤组成:
处理IP选项。
确定封包可以被转发。
递减IP报头的TTL字段。
根据路径相关MTU,必要时处理分片工作。
把封包传送至外出设备。
如果封包无法转发会发送ICMP,或者被转发了,但使用的是次佳路由,因而触发了重导项事件,发送ICMP来通知重导项事件。
和IPsec交互也是转发工作的一部分,由xfrm4_xxx函数实现,这些函数是进入IPsec基础架构的钩子。本书不会讨论IPsec,就是说后续讨论假设IPsec没有配置。
ip_forward函数:
原型:int ip_forward(struct sk_buff *skb);
如果报头中发现了Router Alert选项,则封包现在就会被处理。处理函数ip_call_ra_chain会先重组整个ip封包,然后把封包传给那些Raw套接字(这些套接字设置了IP_ROUTER_ALERT)。然后ip_forward直接返回。
如果报头中没有Router Alert选项,或者存在但没有感兴趣的程序在运行,ip_forward会继续下去:
if(IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb) )
return NET_RX_SUCCESS;
if(skb->pkt_type != PACKET_HOST)
goto drop;
skb->ip_summed = CHECKSUM_NONE;//转发封包,不涉及L4,所以用CHECKSUM_NONE指出当前校验和无误,若有些处理工作修改了IP报头,TCP报头或有效载荷,那么在传输前,内核就会重算校验和。
真实的转发流程是由递减TTL字段开始的:
if(iph->ttl <= 1)
goto too_many_hops;
如果IP报头包含一个Strict Source Route选项,且下个跳点和路由子系统找到的不一样,则Source Routing选项失败了,丢弃该封包,发送一个ICMP报文给传送者。
rt = (struct rtable *)skb->dst;
if(opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
goto sr_failed;
多数健康检查做完后,此函数会更新报头,所以需要拷贝缓冲区。
if(skb_cow(skb,LL_RESERVED_SAPCE(rt->u.dst.dev)+rt->udst.header_len))
goto drop;
现在,TTL会由ip_decrease_ttl递减,并且该函数还会更新ip校验和:
ip_decrease_ttl(iph);
如果有比请求的还要更好的下一跳点存在,原来的主机就会收到ICMP REDIRECT 消息(前提是原来的主机没有请求源路由之时??这里怎么理解?)。
if(rt->rt_flags & RTCF_DOREDIRECT && !opt->ssr)
ip_rt_send_redirect(skb);
这里,priority字段是使用ip头的TOS字段设定的,由Traffic Control(Qos层)使用。
skb->priority = rt_tos2priority(iph->tos);
最后,ip_forward函数会要求Netfilter执行ip_forward_finish(如果没有禁止转发的规则)
return NF_HOOK(PF_INET,NF_IP_FORWARD,skb,skb->dev,rt->u.dst.dev,ip_forward_finish);
ip_forward_finish函数:
到了这一步,说明封包已经通过所有会使其停止的检查,而且真的已经就绪,可以传输到另一个系统了。
先前在ip_forward函数中已经处理过Route Alert和Strict Source Routing选项了(还有其他选项,只是列举了这两个例子而已)。ip_forward_finish会调用ip_forward_options来处理那些选项所需的最后工作。最后,ip_forward_finish函数会调用dst_output函数将封包传递出去。
dst_output函数:
无论是本地产生还是转发,所有传输都会通过dst_output上路。此时,IP报头已经完成,内含传输所需信息以及本地系统负责添加的其他任何信息。dst_output函数会调用虚拟函数output,分段也是在该函数内部处理的。最后,会调用ip_finish_output来衔接邻居子系统,只有当Netfilter放行时,ip_finish_output才会被调用,否则丢弃封包。
static inline int dst_out(struct sk_buff *skb)
{
int err;
for(; ;){
err = skb->dst->output(&skb);
if(likely(err = 0))
return err;
if(unlikely(err != NET_XMIT_BYPASS))
return err;
}
}
本地传递:
第三十五章会说明转发(路由)引擎如何得知本地主机是封包的地址。当封包抵达目的主机时,ip_rcv_finish开端调用ip_route_input,就会把skb->dst->input初始化为ip_local_deliver。此外,Netfilter有最后的权力决定do_something函数是否可以调用相应的do_something_finish函数。
int ip_local_deliver(struct sk_buff *skb)
{
if(skb->nh.iph->frag_off & htons(IP_MF | IP_OFFSET)){
skb = ip_defrag(skb,IP_DEFRAG_LOCAL_DELIVER);//转发几乎不需要重组,而本地传递必须重组整个ip封包
if(!skb)
return 0;
}
return NF_HOOK(PF_INET,NF_IP_LOCAL_IN,skb,skb->dev,rt->u.dst.dev,ip_local_deliver_finish); //第二十四章会谈论ip_local_deliver_finish函数的细节
}