• 数据包接收系列 — IP协议处理流程(一)


    本文主要内容:在接收数据包时,IP协议的处理流程。

    内核版本:2.6.37

    Author:zhangskd @ csdn blog 

    IP报头

    IP报头:

    struct iphdr {
    #if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8 ihl:4,
             version:4;
    #elif defined(__BIG_ENDIAN_BITFIELD)
        __u8 version:4, /* 协议版本,IPv4为4 */
             ihl:4; /* 首部长度,不包括选项为5,表示20字节 */
    #else
    #error "Please fix <asm/byteorder.h>"
    #endif
    
        __u8 tos; /* TOS服务类型,6位DSCP,2为ECN */
        __be16 tot_len; /* IP包总长度,最大为65535 */
        __be16 id; /* 标识符,同一个IP包的不同分片具有相同的标识符 */
        __be16 frag_off; /* 3个标志位,13位偏移 */
        __u8 ttl; /* 存活时间,一般为64跳 */
        __u8 protocol; /* L4协议值 */
        __sum16 check; /* 报头校验和,不包含载荷 */
        __be32 saddr; /* 源IP */
        __be32 daddr; /* 目的IP */
    }; 

    ip_rcv

    调用ip_rcv()时skb中的一些变量:

    ip_rcv()是IP层的入口,主要做了:

    丢弃L2目的地址不是本机的数据包(这说明网卡处于混杂模式,嗅探器会处理这些包)。

    检查skb的引用计数,如果大于1,说明其它地方也在使用此skb,则克隆一个skb返回;否则直接返回原来的skb。

    数据包合法性检查:

    data room必须大于IP报头长度。

    IP报头长度至少是20,类型为IPv4。

    data room至少能容纳IP报头(包括IP选项)。

    检查IP报头校验和是否正确。

    数据包没被截断(skb->len >= 报总长),报总长不小于20。

    如果L2有进行填充(以太网帧最小长度为64),则把IP包裁剪成原大小,去除填充。此时如果接收的NIC

    已计算出校验和,则让其失效,让L4自己重新计算。

    最后,调用netfilter的NF_INET_PRE_ROUTING的钩子函数,如果此数据包被钩子函数放行,则调用

    ip_rcv_finish()继续处理。

    /* Main IP Receive routinue. */
    
    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.
         * 当数据帧的L2目的地址和接收接口的地址不同时,skb->pkt_type就被设成PACKET_OTHERHOST。
         * 网卡本身会丢弃这些包,除非设成混杂模式。嗅探器自会处理这种包,IP层无需理会。
         */
        if (skb->pkt_type == PACKET_OTHERHOST)
            goto drop;
    
        IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);
    
        /* 如果此skb的引用计数大于1,说明在其它地方也被使用,则克隆一个skb返回。
         * 否则直接返回原来的skb。
         */
        if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
            IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
            goto out;
        }
    
        /* 确保data room >= IP报头 */
        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
         */
    
        /* IP报头长度至少是20,类型为IPv4 */
        if (iph->ihl < 5 || iph->version != 4)
            goto inhdr_error;
    
        /* data room至少能容纳IP报头(包括IP选项) */
        if (! pskb_may_pull(skb, iph->ihl * 4))
            goto inhdr_error;
    
        iph = ip_hdr(skb);
    
        /* 检查IP报头校验和是否正确 */
        if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
            goto inhdr_error;
    
        len = ntohs(iph->tot_len); /* IP报文总长度 */
    
        /* L2为了满足最小帧的长度可能会进行填充,所以skb->len >= len。
         * Ethernet数据帧的最小帧长度为64字节。
         */
        if (skb->len < len) { /* 数据包被截断了 */
            IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
        } 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有进行填充,则把IP包裁剪成原大小。
         * 如果接收的NIC已计算出校验和,则让其失效,让L4自己重新计算。
         */
        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));
    
        /* Must drop socket now because of tproxy. */
        skb_orphan(skb);
    
        /* 调用netfilter的NF_INET_PRE_ROUTING钩子 */
        return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
                       ip_rcv_finish);
    
    inhdr_error:
       IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
    
    drop:
        kfree_skb(skb);
    
    out:
        return NET_RX_DROP;
    }

    如果skb的引用计数大于1,说明在其它地方也被使用,则克隆一个skb返回,否则直接返回原来的skb。

    /**
     * skb_shard_check - check if buffer is shard and if so clone it
     * @skb: buffer to check
     * @pri: priority for memory allocation
     * 
     * If the buffer is shared the buffer is cloned and the old copy drops a
     * reference. A new clone with a single reference is returned.
     * If the buffer is not shared the original buffer is returned. When being called
     * from interrupt status or with spinlocks held pri must be GFP_ATOMIC.
     * NULL is returned on a memory allocation failure.
     */
    
    static inline struct sk_buff *skb_shared_check(struct sk_buff *skb, gfp_t pri)
    {
        /* 不能睡眠,否则调用might_sleep()打印栈的回溯信息 */
        might_sleep_if(pri & __GFP_WAIT); 
    
        if (skb_shared(skb)) { /* skb->users是否为1 */
            struct sk_buff *nskb = skb_clone(skb, pri);
            kfree_skb(skb);
            skb = nskb;
        }
    
        return skb;
    }
    /**
     * skb_orphan - orphan a buffer
     * @skb: buffer to orphan
     * If a buffer currently has an owner then we call the owner's destructor
     * function and make the @skb unowned. The buffer continues to exist
     * but is no longer charged to its former owner.
     */
    static inline void skb_orphan(struct sk_buff *skb)
    {
        if (skb->destructor)
            skb->destructor(skb);
    
        skb->destructor = NULL;
        skb->sk = NULL;
    }
    

      

    ip_rcv_finish

    ip_rcv_finish()主要做了:

    查找路由,决定要把数据包发送到哪,赋值skb_dst()->input(),发往本地为ip_local_deliver,转发为ip_forward()。

    更新Traffic Control (Qos)层的统计数据。

    处理IP选项,检查选项是否正确,然后将选项存储在IPCB(skb)->opt中。

    最后执行skb_dst()->input(),要么发往四层,要么进行转发,取决于IP的目的地址。

    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 describes how the packet travels inside linux networking.
         */
        if (skb_dst(skb) == NULL) {
    
            /* 查找路由,决定要把包送往哪里 */
            int err = ip_route_input_noref(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);
    
            if (unlikely(err)) {
                if (err == -EHOSTUNREACH) /* no route to host,主机不可达 */
                    IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INADDRERRORS);
                else if (err == -ENETUNREACH) /* Network is unreachable,网络不可达 */
                    IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INNOROUTES);
                else if (err == -EXDEV) /* Cross-device link */
                    NET_INC_STATS_BH(dev_net(skb->dev), LINUX_MIB_IPRPFILTER);
    
                goto drop; /* 目的地不可达,丢弃 */
            }
        }
    
    /* 更新Traffic Control (Qos)层的统计数据 */
    #ifdef CONFIG_NET_CLS_ROUTE
        if (unlikely(skb_dst(skb)->tclassid)) {
            struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
            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;
        }
    #endif
    
        /* 处理IP选项,调用ip_options_compile()来检查选项是否正确,然后将选项存储
         * 在IPCB(skb)->opt中。
         */
        if (iph->ihl > 5 && ip_rcv_options(skb))
            goto drop;
    
        rt = skb_rtable(skb);
        if (rt->rt_type == RTN_MULTICAST) {
            IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST, skb->len);
        } else if (rt->rt_type == RTN_BROADCAST)
            IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST, skb->len);
    
        /* skb_dst(skb)->input()在ip_route_input_noref()中被赋值,要么是ip_local_deliver(),
        * 要么是ip_forward(),取决于数据包的目的地址。
        */
        return dst_input(skb);
    
    drop:
        kfree_skb(skb);
        return NET_RX_DROP;
    }
    
    /* Input packet from network to transport. */
    static inline int dst_input(struct sk_buff *skb)
    {
        return skb_dst(skb)->input(skb);
    }
    
  • 相关阅读:
    [原]将工程由VC6迁移到VS2005
    [原]DirectDraw视频播放要点
    [原]代码优化学习笔记
    [原]Linux文件交换
    [原]计划
    [原]写在2006年的最后一天
    [原]技术发展规划
    FindBugs的安装和使用
    VirtualBox常用命令
    eclipse中统计代码行数
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333289.html
Copyright © 2020-2023  润新知