Chinaunix首页 | 论坛 | 博客
  • 博客访问: 161572
  • 博文数量: 115
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2016-11-28 14:16
文章分类

全部博文(115)

文章存档

2017年(36)

2016年(79)

我的朋友

分类: LINUX

2017-03-12 17:07:27


* 函数调用关系图
ip层的函数调用关系如下图所示:(下图是 kernel-2.4的函数调用关系,仅供参考)



                                  该图仅供参考        
    


                  该图是《Understanding Linux Network Internals》的原图

* ip包的接收函数调用流程
* ip包接收函数调用流程(kernel-2.6.27)
ip_rcv()
   ->ip_rcv_finish()
      ->ip_route_input()
      ->dst_input()
         ->ip_local_deliver()      //本地数据包,调用传输层协议处理函数处理
         ->ip_forward()             // 非本地包,转发
         ->ip_mr_input()           //多播数据包转发

* 实现分析

* ip包接收主函数
该函数先对ip包做合法性,包括包长,校验和等做检查,若通过检查则查找路由表。
若找到属于自己的路由表项,则调用该表项相应的函数进行处理。

/*
 *  Main IP Receive routine.
 */
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
    struct iphdr *iph;
    u32 len;

    /* When the interface is in promisc. mode, drop all the crap
     * that it receives, do not try to analyse it.
     */
    if (skb->pkt_type == PACKET_OTHERHOST)  //不属于自己的ip包,丢弃
        goto drop;

    IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INRECEIVES);  // ip包计数统计

    // 若此时 skb->users != 1,说明skb被共享?
    // 若此时的skb->users !=1 说明有其他模块的指针,指向该skb,比如说sniffer等。
    // 这时要clone一个skb,并把skb的引用计数users修改成1,并设置cloned字段。
    if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {   
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
        goto out;
    }   

    // 检查ip包长度是否>=ip包的最小头长度
    if (!pskb_may_pull(skb, sizeof(struct iphdr))) 
        goto inhdr_error;

    iph = ip_hdr(skb);    //获取ip包,包头的指针

    /*  
     *  RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
     *
     *  Is the datagram acceptable?
     *
     *  1.  Length at least the size of an ip header
     *  2.  Version of 4
     *  3.  Checksums correctly. [Speed optimisation for later, skip loopback checksums]
     *  4.  Doesn't have a bogus length
     */


    if (iph->ihl < 5 || iph->version != 4)  //ip包的版本是否=4
        goto inhdr_error;

    if (!pskb_may_pull(skb, iph->ihl*4))    //再做一次检查
        goto inhdr_error;

    iph = ip_hdr(skb);            //重新获取一次ip包头指针,因为此时可能对ip头的为止做了调整

    if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))    //检查ip包的校验和是否正确
        goto inhdr_error;

    len = ntohs(iph->tot_len);    //检查ip包的长度是否合理
    if (skb->len < len) {      //skb中的len
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
        goto drop;
    } else if (len < (iph->ihl*4)) // 若ip->len的长度小于头部长度*4,说明数据包有错误
        goto inhdr_error;

    /* Our transport medium may have padded the buffer out. Now we know it
     * is IP we can trim to the true length of the frame.
     * Note this now means skb->len holds ntohs(iph->tot_len).
     */
    // 检查一下包的长度和checksum
    //若实际的长度和skb->len的长度不相等,则调用skb_trim对数据包进行调整 (为什么?依据?这个地方有点不太明白)
    if (pskb_trim_rcsum(skb, len)) {    
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
        goto drop;
    }

    /* Remove any debris in the socket control block */
    // 把skb->cb指向的空间清空
    memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));

    // 如运行到这里,说明ip包前期的各项检查都正确,
    // 此时调用NF_HOOK函数进入netfilter的NF_IP_PRE_ROUTING 挂载点,
    // 并遍历该挂载点的处理函数链表,检查该挂载点是否有其他的处理函数,比如防火墙(firewal),流量控制(QOS)等。
    // 若有处理函数,则调用这些函数进行处理,处理完成后,有调用ip_rcv_finish()
    return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,
               ip_rcv_finish);

    // ip包头有错误,进行错误统计
inhdr_error:
    IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
    // 丢弃该skb数据包
drop:
    kfree_skb(skb);
out:
    return NET_RX_DROP;
}


* 接收ip包的下半部分的实现
/*
* ip_rcv只是对ip包的基本字段做了一些检查,而ip_rcv_finish才真正的对ip包进行处理,主要实现一下功能:
* (1) 决定数据包是本地接收,还是进行包的转发。若是要进行转发,需要找到转发的出口设备和下一跳的地址。
* (2) 分解和处理一些ip选项,这里不会处理所有的选项。
*/ 
static int ip_rcv_finish(struct sk_buff *skb)
{
    // 获取ip包的头指针
    const struct iphdr *iph = ip_hdr(skb);
    struct rtable *rt; 
    
    /*
     *  Initialise the virtual path cache for the packet. It describes
     *  how the packet travels inside Linux networking.
     */
    // 查找ip包的路由项
    // 计算完成后,若找到路由项,会把找到的路由表项的指针保存到skb->rtable变量
    // 若没有找到相应的路由表,则直接丢弃ip包
    if (skb->dst == NULL) {
        int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
                     skb->dev);
        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
    if (unlikely(skb->dst->tclassid)) {
        struct ip_rt_acct *st = per_cpu_ptr(ip_rt_acct, smp_processor_id());
        u32 idx = skb->dst->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;
    }
#endif

    // 若ip头的长度大于5*32bits时, 说明该ip包有选项需要处理
  // iph->ihl长度表示ip头占32bit字段的数目,基本包头长度是5*32bit
    if (iph->ihl > 5 && ip_rcv_options(skb))
        goto drop;

    // 获取找到的路由表项指针
    rt = skb->rtable;

    // 多播路由计数统计
    if (rt->rt_type == RTN_MULTICAST)
        IP_INC_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INMCASTPKTS);
    // 广播路由计数统计
    else if (rt->rt_type == RTN_BROADCAST)
        IP_INC_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INBCASTPKTS);

    // 调用skb->dst->input(skb)函数做后期处理,该函数是在ip_route_input函数中设置的。
    return dst_input(skb);

drop:
    kfree_skb(skb);
    return NET_RX_DROP;
}




(待续...)


* 注
基于内核2.6.27


阅读(876) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~