本文介绍一下网络层中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层进行处理了,没必要再在接收端进行分片报文的重组,另一个原因是因为要经过三层转发的IP分片报文没必要在这里重组,所以IP分片报文是否需要重组就直接由IP层进行决定了,没有交给GRO来处理。
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;
}
阅读(3232) | 评论(0) | 转发(1) |