IP層的GSO/GRO定義在ip_packet_offload結構體中。
static struct packet_offload ip_packet_offload __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.callbacks = {
.gso_segment = inet_gso_segment, //gso分段函數
.gro_receive = inet_gro_receive, //gro收包函數
.gro_complete = inet_gro_complete,
},
};
inet_gso_segment函數
static struct sk_buff *inet_gso_segment(struct sk_buff *skb,
netdev_features_t features)
{
struct sk_buff *segs = ERR_PTR(-EINVAL);
const struct net_offload *ops;
unsigned int offset = 0;
bool udpfrag, encap;
struct iphdr *iph;
int proto;
int nhoff;
int ihl;
int id;
if (unlikely(skb_shinfo(skb)->gso_type &
~(SKB_GSO_TCPV4 |
SKB_GSO_UDP |
SKB_GSO_DODGY |
SKB_GSO_TCP_ECN |
SKB_GSO_GRE |
SKB_GSO_GRE_CSUM |
SKB_GSO_IPIP |
SKB_GSO_SIT |
SKB_GSO_TCPV6 |
SKB_GSO_UDP_TUNNEL |
SKB_GSO_UDP_TUNNEL_CSUM |
SKB_GSO_TUNNEL_REMCSUM |
0)))
goto out;
skb_reset_network_header(skb);
nhoff = skb_network_header(skb) - skb_mac_header(skb); //根據network header和mac header得到IP頭相對MAC的偏移
if (unlikely(!pskb_may_pull(skb, sizeof(*iph)))) //檢測skb是否可以移動到L4頭?
goto out;
iph = ip_hdr(skb);
ihl = iph->ihl * 4; //得到IP包頭的實際長度,基於此可以得到L4的首地址
if (ihl < sizeof(*iph))
goto out;
id = ntohs(iph->id);
proto = iph->protocol; //L4層協議類型
/* Warning: after this point, iph might be no longer valid */
if (unlikely(!pskb_may_pull(skb, ihl))) //檢測skb是否可以移動到L4頭?
goto out;
__skb_pull(skb, ihl); //報文data指針移動到傳輸層
encap = SKB_GSO_CB(skb)->encap_level > 0;
if (encap)
features &= skb->dev->hw_enc_features; //如果encap,那麼feature與hw_enc_features取交集
SKB_GSO_CB(skb)->encap_level += ihl; //用來標示是否爲內層報文
skb_reset_transport_header(skb); //設置transport header值
segs = ERR_PTR(-EPROTONOSUPPORT);
if (skb->encapsulation &&
skb_shinfo(skb)->gso_type & (SKB_GSO_SIT|SKB_GSO_IPIP))
udpfrag = proto == IPPROTO_UDP && encap;
else
udpfrag = proto == IPPROTO_UDP && !skb->encapsulation; //vxlan封裝報文走此分支,此時udpfrag爲false
ops = rcu_dereference(inet_offloads[proto]);
if (likely(ops && ops->callbacks.gso_segment))
segs = ops->callbacks.gso_segment(skb, features); //UDP或TCP的分段函數
if (IS_ERR_OR_NULL(segs))
goto out;
skb = segs;
do {
iph = (struct iphdr *)(skb_mac_header(skb) + nhoff); //根據分段報文的mac header 和 IP偏移
if (udpfrag) { //ip分片報文
iph->id = htons(id);
iph->frag_off = htons(offset >> 3); //設置ip頭的frag_off值
if (skb->next)
iph->frag_off |= htons(IP_MF); //後面還有報文,需要設置more frag標記
offset += skb->len - nhoff - ihl; //計算offset值,下一個報文需要使用
} else {
iph->id = htons(id++); //每個報文爲完整的IP報文
}
iph->tot_len = htons(skb->len - nhoff);
ip_send_check(iph); //計算ip頭 csum值
if (encap) //如果encap值非空,說明當前處於內層報文中,所以需要設置inner heaer值
skb_reset_inner_headers(skb);
skb->network_header = (u8 *)iph - skb->head; //設置network header
} while ((skb = skb->next));
out:
return segs;
}