在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没有配置。

iptables查看转发状态 iptables icmp转发_IP

 

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函数的细节
}