深入理解Linux网络技术内幕——IPv4 报文的接收(转发与本地传递)

来源:互联网 发布:网络电影需要什么手续 编辑:程序博客网 时间:2024/05/16 19:55

我们知道,报文经过网卡驱动处理后,调用net_receive_skb传递给具体的协议处理函数,对于IPv4报文来说,其协议处理函数就是ip_rcv了,ip_rcv在进行一些健康检查等操作后,会调用ip_rcv_finish来处理报文。这也是IPv4协议对报文接收处理的开始。

我们先看下ip_rcv_finish源代码:

ip_rcv_finish:

//ip数据报文的主要处理程序(ip_rcv仅仅只是对ip数据报做一些健康性检查)//ip_rcv_finish 其实是进行路由表查询,,决定报文经过IP层处理后,是继续向上传递,还是进行转发,还是丢弃。//1.决定报文在本地传递或者转发,如果是转发还需要找到出口设备和下一跳节点//2.分析和处理一些选项static int ip_rcv_finish(struct sk_buff *skb){const struct iphdr *iph = ip_hdr(skb);struct rtable *rt;/* *Initialise the virtual path cache for the packet. It describe *how the packet travels inside Linux networking. *  刚开始没有进行路由表查询,所以还没有相应的路由表项:skb_dst(skb) == NULL。 *  则在路由表中查找ip_route_input(),关于内核的路由表 */if (skb_dst(skb) == NULL) {int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev); //这里面进行了一些初始化操作,比较重要,与ip报文接下来的走向有关if (unlikely(err)) {if (err == -EHOSTUNREACH)IP_INC_STATS_BH(dev_net(skb->dev),IPSTATS_MIB_INADDRERRORS);else if (err == -ENETUNREACH)IP_INC_STATS_BH(dev_net(skb->dev),IPSTATS_MIB_INNOROUTES);goto drop;}}#ifdef CONFIG_NET_CLS_ROUTE//更新traffic cotrol(qos层)所使用的统计数据if (unlikely(skb_dst(skb)->tclassid)) {struct ip_rt_acct *st = per_cpu_ptr(ip_rt_acct, smp_processor_id());u32 idx = skb_dst(skb)->tclassid;st[idx&0xFF].o_packets++;st[idx&0xFF].o_bytes += skb->len;st[(idx>>16)&0xFF].i_packets++;st[(idx>>16)&0xFF].i_bytes += skb->len;}#endifif (iph->ihl > 5 && ip_rcv_options(skb))goto drop;rt = skb_rtable(skb);  /* skb->dst包含路由信息。根据路由类型更新SNMP统计数据 */if (rt->rt_type == RTN_MULTICAST) {IP_UPD_PO_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INMCAST,skb->len);} else if (rt->rt_type == RTN_BROADCAST)IP_UPD_PO_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INBCAST,skb->len);/*      * dst_input实际上会调用skb->dst->input(skb).input函数会根据路由信息设置为合适的     * 函数指针,如果是递交到本地的则为ip_local_deliver,若是转发则为ip_forward.     * 暂时仅先考虑ip_local_deliver。     */return dst_input(skb);drop:kfree_skb(skb);return NET_RX_DROP;}


ip_route_input会进行路由表查询,该函数直接或间接决定了报文之后要往何处传递。是进行本地传递还是转发。

我们可以看到如果报文没有被drop掉,那么报文最终会被dst_input(skb)处理。dst_input(skb)实际上执行的是skb->dst->input(skb)。而这里的input函数其实就是由ip_route_input决定的。

对于应该本地传递的报文,input指针会指向ip_local_deliver。对于该转发的报文,input会指向ip_forward。

本地传递


/* *  Deliver IP Packets to the higher protocol layers. */int ip_local_deliver(struct sk_buff *skb){    /*       *  Reassemble IP fragments.     */    if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {        if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))            return 0;    }       return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,               ip_local_deliver_finish);}
我们知道,IPv4要将报文传送给上层协议(本地传递),那它需要对分段的报文进行重组,ip_defrag即完成报文重组。
然后由调用Netfilter决定是否调用ip_local_deliver_finish。
ip_local_deliver_finish
static int ip_local_deliver_finish(struct sk_buff *skb){    struct net *net = dev_net(skb->dev);    __skb_pull(skb, ip_hdrlen(skb));  /* 跳过IP头部 */    /* Point into the IP datagram, just past the header. */    /* 设置传输层头部位置 */    skb_reset_transport_header(skb);    rcu_read_lock();    {        int protocol = ip_hdr(skb)->protocol; //取出ip头中的协议.        int hash, raw;        const struct net_protocol *ipprot;    resubmit:        // 若是raw socket发送的,需要做相应的处理,clone数据包        raw = raw_local_deliver(skb, protocol); //得到raw socket, 如果不是raw socket,则返回0        hash = protocol & (MAX_INET_PROTOS - 1);  // 计算传输层协议处理结构在inet_protos数组hash表中的位置        ipprot = rcu_dereference(inet_protos[hash]); // 获取传输层协议处理指针        if (ipprot != NULL) {            int ret;                                                                                                                                                //主要是ipprot是否有被当前主机注册            if (!net_eq(net, &init_net) && !ipprot->netns_ok) { // 若获取到了对应传输层的处理结构                if (net_ratelimit())                    printk("%s: proto %d isn't netns-ready\n",                        __func__, protocol);                kfree_skb(skb);                goto out;            }            //判断ipsec,并进行相关处理.             if (!ipprot->no_policy) {                if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {                    kfree_skb(skb);                    goto out;                }                nf_reset(skb);            }            //调用handler,进入相应的4层协议的处理.            ret = ipprot->handler(skb);            if (ret < 0) {  // 处理数据包失败,再次尝试                protocol = -ret;                goto resubmit;            }            IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);// 添加数据包处理统计信息        } else {// 若没有找到相应传输层的处理函数          if (!raw) {                if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {                    IP_INC_STATS_BH(net, IPSTATS_MIB_INUNKNOWNPROTOS);                    icmp_send(skb, ICMP_DEST_UNREACH,                          ICMP_PROT_UNREACH, 0);                }            } else                IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);            kfree_skb(skb);        }    } out:    rcu_read_unlock();    return 0;}



转发

报文转发有下面几个步骤完成:

1. 处理IP选项

2. 确定封包可以被转发

3.递减封包头部的TTL字段,如果TTL字段为0,则丢弃该封包

4.根据路径相关MTU,在必要时处理分段

5.把封包传送至外出设备

IPv4协议中,报文的转发从ip_forward开始:

ip_forward

int ip_forward(struct sk_buff *skb){    struct iphdr *iph;  /* Our header */    struct rtable *rt;  /* Route we use */    struct ip_options * opt = &(IPCB(skb)->opt);    if (skb_warn_if_lro(skb))        goto drop;    if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))        goto drop;    if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb)) //处理Router_Alert选项(重要)        return NET_RX_SUCCESS;    //确定二层目的是发往本机的,这步检查是多余的,因为在二层接收的时候,不是二层地址不是本机的包已经被丢弃    //对于二层地址为本机的数据帧,skb->pkt_type 赋值为PACKET_HOST    if (skb->pkt_type != PACKET_HOST)//        goto drop;    skb_forward_csum(skb);//这里只是转发封包,因而不关系L4层检验和  skb_forward_csum(skb);//这里只是转发封包,因而不关系L4层检验和                                                                                                                                                                /*     *  According to the RFC, we must first decrease the TTL field. If     *  that reaches zero, we must reply an ICMP control message telling     *  that the packet's lifetime expired.     */    if (ip_hdr(skb)->ttl <= 1)        goto too_many_hops;    if (!xfrm4_route_forward(skb))        goto drop;    rt = skb_rtable(skb);    //如果报头含有strictroute选项,且选项中的下一跳与路由子系统的网关不同    //则表示选项失败,封包丢弃    if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)        goto sr_failed;    if (unlikely(skb->len > dst_mtu(&rt->u.dst) && !skb_is_gso(skb) &&             (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) {        IP_INC_STATS(dev_net(rt->u.dst.dev), IPSTATS_MIB_FRAGFAILS);        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,              htonl(dst_mtu(&rt->u.dst)));        goto drop;    }    /* We are about to mangle packet. Copy it! */    if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))        goto drop;    iph = ip_hdr(skb);    /* Decrease ttl after skb cow done */    ip_decrease_ttl(iph);    /*     *  We now generate an ICMP HOST REDIRECT giving the route     *  we calculated.     */    if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb_sec_path(skb))        ip_rt_send_redirect(skb);    skb->priority = rt_tos2priority(iph->tos);    return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,               ip_forward_finish);sr_failed:    /*     *  Strict routing permits no gatewaying     */     icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);     goto drop;too_many_hops:    /* Tell the sender its packet died... */    IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_INHDRERRORS);    icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);               drop:    kfree_skb(skb);    return NET_RX_DROP;}   
ip_forward检查结束,如果进入ip_forward_finish说明该封包已经可以真正的传给另一个系统了。转发真正工作在ip_forward_finish,中完成。


ip_forward_finish

static int ip_forward_finish(struct sk_buff *skb){    struct ip_options * opt = &(IPCB(skb)->opt);    IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);     //ip_forward已经处理了两个可能的选项,ROute_Alert 和 Strict Source Routing    //这里还需要处理其他选项(由ip_rcv_finish通过ip_options_compile进行初始化    if (unlikely(opt->optlen))        ip_forward_options(skb);           return dst_output(skb);                                                                                                                                 }


最后调用dst_ouput将封包传给设备驱动,转发出去,这里其实封包与我们从传输层传递下来要传给其他系统的封包,开始路径一致了。

/* Output packet to network from transport.  */static inline int dst_output(struct sk_buff *skb){    return skb_dst(skb)->output(skb);}   
output是虚拟函数,对于单播封包,会初始化为ip_output,对于多播封包,会初始化为ip_mc_output。这两函数也会处理分段,并在最后调用ip_finish_output.

ip_output

int ip_output(struct sk_buff *skb){    struct net_device *dev = skb_dst(skb)->dev;    IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);    skb->dev = dev;     skb->protocol = htons(ETH_P_IP);    return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,                 ip_finish_output,                !(IPCB(skb)->flags & IPSKB_REROUTED));}
ip_mc_output

int ip_mc_output(struct sk_buff *skb){    struct sock *sk = skb->sk;    struct rtable *rt = skb_rtable(skb);    struct net_device *dev = rt->u.dst.dev;    /*        *  If the indicated interface is up and running, send the packet.     */    IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);    skb->dev = dev;     skb->protocol = htons(ETH_P_IP);    /*        *  Multicasts are looped back for other local users     */    if (rt->rt_flags&RTCF_MULTICAST) {        if ((!sk || inet_sk(sk)->mc_loop)#ifdef CONFIG_IP_MROUTE        /* Small optimization: do not loopback not local frames,           which returned after forwarding; they will be  dropped           by ip_mr_input in any case.           Note, that local frames are looped back to be delivered           to local recipients.           This check is duplicated in ip_mr_input at the moment.         */            && ((rt->rt_flags&RTCF_LOCAL) || !(IPCB(skb)->flags&IPSKB_FORWARDED))#endif        ) {            struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);            if (newskb)                NF_HOOK(PF_INET, NF_INET_POST_ROUTING, newskb,                    NULL, newskb->dev,                    ip_dev_loopback_xmit);        }        /* Multicasts with ttl 0 must not go beyond the host */        if (ip_hdr(skb)->ttl == 0) {            kfree_skb(skb);            return 0;        }    }    if (rt->rt_flags&RTCF_BROADCAST) {        struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);        if (newskb)            NF_HOOK(PF_INET, NF_INET_POST_ROUTING, newskb, NULL,                newskb->dev, ip_dev_loopback_xmit);    }    return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, skb->dev,                ip_finish_output,                !(IPCB(skb)->flags & IPSKB_REROUTED));}   


ip_finish_output

static int ip_finish_output(struct sk_buff *skb){#if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)    /* Policy lookup after SNAT yielded a new policy */    if (skb_dst(skb)->xfrm != NULL) {        IPCB(skb)->flags |= IPSKB_REROUTED;        return dst_output(skb);    }#endif    if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))        return ip_fragment(skb, ip_finish_output2);//分段    else        return ip_finish_output2(skb);}
ip_finish_output会和邻居子系统进行衔接。细节看 ipv4 传输的博文。
























0 0
原创粉丝点击