网络层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;
}


你可能感兴趣的:(linux,linux协议栈,GRO)