網絡層ipv4對GRO的處理 (linux網絡子系統學習 第九節)

本文介紹一下網絡層中IPv4協議對GRO支持。從第五節中我們知道,每個支持GRO功能的協議都要實現自己的接收和完成函數。


ipv4協議的定義如下:

file:// net/ipv4/af_inet.c
static struct packet_type ip_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_IP),
    .gro_receive = inet_gro_receive,
    .gro_complete = inet_gro_complete,
};


IPv4協議中對GRO的支持在網絡層並不對報文進行合併,IP分片報文會在IP層進行重組,沒有放在GRO中進行重組。個人理解是IPv4 分片包文的重組比較複雜,並且報文從鏈路層上來就直接送IP層進行處理了,沒必要再在接收端進行分片報文的重組。IPv4在IP層只是根據一定的規則進行報文的匹配,匹配後送傳輸層進行GRO的合併處理。因爲傳輸層的報文都會經過ip層進行處理,如果在接收端進行了重組後再送ip層處理,這樣就減少了ip層處理報文的個數,會對性能有一定的優化。


IPv4的GRO接收函數:
匹配規則:
1、兩個報文的源IP和目的IP地址相同
2、兩個報文的傳輸層協議相同
3、兩個報文的IP頭中tos字段相同


static struct sk_buff **inet_gro_receive(struct sk_buff **head,
                                         struct sk_buff *skb)
{
    const struct net_protocol *ops;
    struct sk_buff **pp = NULL;
    struct sk_buff *p;
    struct iphdr *iph;
    unsigned int hlen;
    unsigned int off;
    unsigned int id;
    int flush = 1;
    int proto;
    /*根據GRO的私有字段data_offset找到IP層要讀取數據的偏移量,
     *這時GRO要讀取的就是IP頭
     */
    off = skb_gro_offset(skb);
    hlen = off + sizeof(*iph);
                          
    /*根據偏移量找到IP頭*/
    iph = skb_gro_header_fast(skb, off);
    /*如果線性區內不包含IP頭,就把非線性區的IP頭部分拷貝到線性區,
     *方便以後的處理。如果skb是線性的,
     *NAPI_GRO_CB(skb)->frag0 爲NULL,
     *上邊根據偏移量找ip頭是找不到的,
     *這時可直接根據skb->data和偏移量找到IP頭。
     */
    if (skb_gro_header_hard(skb, hlen))
    {
        iph = skb_gro_header_slow(skb, hlen, off);
           if (unlikely(!iph))
                goto out;
    }
    /*取出報文的傳輸層協議類型*/
    proto = iph->protocol & (MAX_INET_PROTOS - 1);
    rcu_read_lock();
    ops = rcu_dereference(inet_protos[proto]);
   /*如果傳輸層不支持GRO,就直接返回不進行GRO處理*/
   if (!ops || !ops->gro_receive)
        goto out_unlock;
    if (*(u8 *)iph != 0x45)
        goto out_unlock;
    /*判斷一下報文的校驗和*/
     if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
       goto out_unlock;
    /*如果報文是分片報文,設置flush位,表示報文不需要進行GRO合併*/
    id = ntohl(*(u32 *)&iph->id);
    flush = (u16)((ntohl(*(u32 *)iph) ^ skb_gro_len(skb)) |
                                     (id ^ IP_DF));
    id >>= 16;
    /*循環gro_list上緩存的報文進行匹配,設置same 和 flush字段*/
    for (p = *head; p; p = p->next)
    {
        struct iphdr *iph2;
        /*如果same字段在鏈路層沒進行設置,
         *表示該報文網絡層不用進行匹配,直接跳過
         */
        if (!NAPI_GRO_CB(p)->same_flow)
            continue;
        iph2 = ip_hdr(p);
        /*如果不符合IPv4的匹配規則,就清除same位,
         *告訴傳輸層不再跟該報文進行匹配
         */
        if ((iph->protocol ^ iph2->protocol) |
            (iph->tos ^ iph2->tos) |
            (iph->saddr ^ iph2->saddr) |
            (iph->daddr ^ iph2->daddr))
        {
             NAPI_GRO_CB(p)->same_flow = 0;
             continue;
        }
        /*如果兩個報文ttl不相同或id 不連續,就設置flush位*/
        /* All fields must match except length and checksum. */
        NAPI_GRO_CB(p)->flush |=
             (iph->ttl ^ iph2->ttl) |
            ((u16)(ntohs(iph2->id) + NAPI_GRO_CB(p)->count) ^ id);
        NAPI_GRO_CB(p)->flush |= flush;
    }
    NAPI_GRO_CB(skb)->flush |= flush;
    /*設置傳輸層要讀取的GRO數據的偏移量,其實就是到傳輸層頭的偏移量*/
    skb_gro_pull(skb, sizeof(*iph));
    /*設置傳輸層頭指針*/
    skb_set_transport_header(skb, skb_gro_offset(skb));
    /*調用傳輸層的GRO接收函數*/
    pp = ops->gro_receive(head, skb);
out_unlock:
    rcu_read_unlock();
out:
    NAPI_GRO_CB(skb)->flush |= flush;
    return pp;
}

IP層的GRO完成處理函數:

static int inet_gro_complete(struct sk_buff *skb)
{
    const struct net_protocol *ops;
    struct iphdr *iph = ip_hdr(skb);
    int proto = iph->protocol & (MAX_INET_PROTOS - 1);
    int err = -ENOSYS;
    __be16 newlen = htons(skb->len - skb_network_offset(skb));
    /*重新計算並更新IP頭的校驗和*/
    csum_replace2(&iph->check, iph->tot_len, newlen);
    iph->tot_len = newlen;
    rcu_read_lock();
    /*找到傳輸層協議的GRO完成函數進行進一步的處理*/
    ops = rcu_dereference(inet_protos[proto]);
    if (WARN_ON(!ops || !ops->gro_complete))
        goto out_unlock;
    err = ops->gro_complete(skb);
out_unlock:
    rcu_read_unlock();
    return err;
}


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