您的位置:首页 > 运维架构 > Linux

【Linux4.1.12源码分析】二层报文发送之报文GSO分段(IP层)

2016-10-09 22:48 489 查看
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息