Chinaunix首页 | 论坛 | 博客

分类: LINUX

2014-03-29 12:48:05

/*
 *         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.
         * 当interface处于混杂模式,那么目的MAC地址是其它主机的数据包也会被网卡收上来,传递给ip_rcv函数.
         * 有两个问题:1、其它主机是什么意思?http://blog.csdn.net/nerdx/article/details/12450055,获知是非本机、非广播、非
         *     多播的数据帧。2、利用brctl命令可将主机配置成虚拟网桥,由此看来,经过网桥传输的数据包没有到IP层。
         */
        if (skb->pkt_type == PACKET_OTHERHOST)
                goto drop;




        IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len); //从IPSTATS_MIB_IN的字面意,可知是对接收的IP包信息的统计




        /*
         *  sk_buff结构有一个users成员,表示当前有users个模块在引用该sk_buff结构实体(注意是sk_buff本身,不包括它的数据缓冲
         *  区)。具体谁引用的不需要都明白,至少,当前IPv4模块在引用它. 
         *  
         *  为了避免修改sk_buff实例时影响到其它模块,调用skb_share_check函数。它可以说做了三件小事:检查users的值是否等于1;
         *  不等于1,则调用skb_clone整一个全新的sk_buff结构(不包括数据缓冲区); 最后,调用kfree_skb函数使得users-1。(当users
         *  的值=0时,内存会被释放.)
         *  问题:在利用netlink机制与用户态进行通信时,分别调用alloc_skb(不但分配sk_buff本身,还分配了数据缓冲区)、
         *        nlmsg_put(放置要发送的数据)、netlink_unicast(发送出去). 因为该skb是自己申请的,本以为在最后需要调用
         *        kfree_skb(skb)函数释放skb。结果程序崩溃了,原因由可知,
         *          netlink_unicast内部已经释放了该skb.
         *  NETfilter的回调函数是不需要释放skb的,即使返回了DROP也一样.
         */
        if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
                IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
                goto out;
        }


        /*
         * pskb_may_pull的实现还是挺复杂的,但是它的目的很简单:确保线性数据缓冲区中有sizeof(struct iphdr))个字节.
         * 这牵扯到了sk_buff中数据缓冲区中内容的移动,甚至有可能更换sk-buff的线性缓冲区(如果第二个参数的值大于len-skb_headle             * n + skb->tail - skb->end). skb中缓冲区内容的顺序是:线性缓冲区+skb_shinfo(skb)->frag+skb_shinfo(skb)->frag_list。
         * 相当于重新组织了skb的数据缓冲区.
         * 该函数的具体实现过程,参见http://blog.chinaunix.net/uid-22577711-id-3220155.html。
         * 注意:pskb_may_pull是会修改sk_buff和数据缓冲区的.
         */
        if (!pskb_may_pull(skb, sizeof(struct iphdr)))
                goto inhdr_error;
        iph = ip_hdr(skb);


        /*
         *        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)                 //基本检查
                goto inhdr_error;


        if (!pskb_may_pull(skb, iph->ihl*4))                 //确保线性缓冲区中有完整的IP报头
                goto inhdr_error;
        iph = ip_hdr(skb);


        /*
         * 对IP层校验和进行检查. 等于0是正常的. 否则直接丢弃掉该数据包.
         */
        if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
                goto inhdr_error;




        /*
         * skb->len < len : 不可能出现的状况。如果报文是被分段传输过来的, 那么len也只是代表了该分段的大小. 而skb也一定
         *                   包含了整个分段的大小.
         * len < (iph->ihl*4) :IP报文的tot_len值有问题
         * 
         */
        len = ntohs(iph->tot_len);
        if (skb->len < len) {
                IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
                goto drop;
        } else if (len < (iph->ihl*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).
         * 当传递给L2的载荷太小,数据帧大小小于发送的最小值。那么L2会有填充动作。所以IP报文的大小应该以iph->tot_len为准
         * 裁剪的时候也要考虑到是否有非线性缓冲区存在.
         */
        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 */
        memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));         //给cb缓冲区留下一块净土,以后用来存放ip options解析结果


        /* Must drop socket now because of tproxy. */
        skb_orphan(skb);        //make the @skb unowned, skb->destruct=NULL && skb->sk = NULL


        return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,
                       ip_rcv_finish);


inhdr_error:
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);         //统计IP报头有问题的包
drop:
        kfree_skb(skb);                 //users-1
out:
        return NET_RX_DROP;                //丢弃该报文
}

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