數據包接收系列 — 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);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章