天天看點

Linux網絡子系統中鍊路層中GRO的處理

根據上篇博文的介紹,GRO需要支援GRO的每種協定都要實作自己的封包比對合并函數和合并完成函數。這裡我們先來看看鍊路層上

實作的自己的GRO函數。

鍊路層的接收比對函數 __napi_gro_receive(napi, skb):

該函數對封包進行比對,并不合并封包。

比對規則(必須同時滿足以下兩個條件):

1、兩個封包的接收dev必須相同。

2、兩個封包的以太頭必須相同。

static int __napi_gro_receive(struct napi_struct *napi,struct sk_buff *skb)

{

    struct sk_buff *p;

    for (p = napi->gro_list; p; p = p->next)

    {

        NAPI_GRO_CB(p)->same_flow = (p->dev == skb->dev)

                 && !compare_ether_header(skb_mac_header(p),

                     skb_gro_mac_header(skb));

        NAPI_GRO_CB(p)->flush = 0;

    }

    return dev_gro_receive(napi, skb);

}

int dev_gro_receive(struct napi_struct *napi,struct sk_buff *skb)

{

    struct sk_buff **pp = NULL;

    struct packet_type *ptype;

    __be16 type = skb->protocol;

    struct list_head *head = &ptype_base[ntohs(type) & PTYPE_HASH_MASK];

    int same_flow;

    int mac_len;

    int ret;

    if (!(skb->dev->features & NETIF_F_GRO))

    {

        goto normal;

    }

    if (skb_is_gso(skb) || skb_has_frags(skb))

    {

        goto normal;

    }

    rcu_read_lock();

    list_for_each_entry_rcu(ptype, head, list)

    {

        if (ptype->type != type || ptype->dev || !ptype->gro_receive)

            continue;

        skb_set_network_header(skb, skb_gro_offset(skb));

        mac_len = skb->network_header - skb->mac_header;

        skb->mac_len = mac_len;

        NAPI_GRO_CB(skb)->same_flow = 0;

        NAPI_GRO_CB(skb)->flush = 0;

        NAPI_GRO_CB(skb)->free = 0;

        pp = ptype->gro_receive(&napi->gro_list, skb);

        break;

    }

    rcu_read_unlock();

    if (&ptype->list == head)

    {

        goto normal;

    }

    same_flow = NAPI_GRO_CB(skb)->same_flow;

    ret = NAPI_GRO_CB(skb)->free ? GRO_MERGED_FREE :

                                   GRO_MERGED;

    if (pp)

    {

        struct sk_buff *nskb = *pp;

        *pp = nskb->next;

        nskb->next = NULL;

        napi_gro_complete(nskb);

        napi->gro_count--;

    }

    if (same_flow)

    {

        goto ok;

    }

    if (NAPI_GRO_CB(skb)->flush ||

        napi->gro_count >= MAX_GRO_SKBS)

    {

        goto normal;

    }

    napi->gro_count++;

    NAPI_GRO_CB(skb)->count = 1;

    skb_shinfo(skb)->gso_size = skb_gro_len(skb);

    skb->next = napi->gro_list;

    napi->gro_list = skb;

    ret = GRO_HELD;

pull:

    if (skb_headlen(skb) < skb_gro_offset(skb))

    {

        int grow = skb_gro_offset(skb) - skb_headlen(skb);

        BUG_ON(skb->end - skb->tail < grow);

        memcpy(skb_tail_pointer(skb),

               NAPI_GRO_CB(skb)->frag0, grow);

        skb->tail += grow;

        skb->data_len -= grow;

        skb_shinfo(skb)->frags[0].page_offset += grow;

        skb_shinfo(skb)->frags[0].size -= grow;

        if (unlikely(!skb_shinfo(skb)->frags[0].size))

        {

            put_page(skb_shinfo(skb)->frags[0].page);

            memmove(skb_shinfo(skb)->frags,

                    skb_shinfo(skb)->frags + 1,

                    (--skb_shinfo(skb)->nr_frags *

                     sizeof(skb_frag_t)));

         }

    }

ok:

    return ret;

normal:

    ret = GRO_NORMAL;

    goto pull;

}

鍊路層的GRO完成函數:

合并完成後的封包調用該函數來把封包送入協定棧。

static int napi_gro_complete(struct sk_buff *skb)

{

    struct packet_type *ptype;

    __be16 type = skb->protocol;

    struct list_head *head = &ptype_base[ntohs(type) & PTYPE_HASH_MASK];

    int err = -ENOENT;

    if (NAPI_GRO_CB(skb)->count == 1)

    {

        skb_shinfo(skb)->gso_size = 0;

        goto out;

    }

    rcu_read_lock();

    list_for_each_entry_rcu(ptype, head, list)

    {

        if (ptype->type != type || ptype->dev || !ptype->gro_complete)

            continue;

         err = ptype->gro_complete(skb);

        break;

    }

    rcu_read_unlock();

    if (err)

    {

        WARN_ON(&ptype->list == head);

        kfree_skb(skb);

        return NET_RX_SUCCESS;

    }

out:

    return netif_receive_skb(skb);

}

我們從上面分析看到,鍊路層處理完鍊路層上GRO的處理後,會再調用網絡層上對應得GRO處理。每一層協定自己負責自己的GRO處理。上次處理完後把處理結果返給下一層。最終由鍊路層來根據處理結果來把封包送給協定棧 。

下文我們分析一下IP層對GRO的處理實作。

繼續閱讀