本文介紹一下網絡層中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; }