报文从网卡接收经过软中断的处理,最终是要进协议栈的,__netif_receive_skb_core就是这个入口,这个函数中做了vlan的处理,抓包处理,ovs/bridge等二层转发处理和分发报文(arp_rcv, ip_rcv等)处理。
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
struct packet_type *ptype, *pt_prev;
rx_handler_func_t *rx_handler;
struct net_device *orig_dev;
struct net_device *null_or_dev;
bool deliver_exact = false;
int ret = NET_RX_DROP;
__be16 type;
net_timestamp_check(!netdev_tstamp_prequeue, skb);
trace_netif_receive_skb(skb);
orig_dev = skb->dev;
skb_reset_network_header(skb);
if (!skb_transport_header_was_set(skb))
skb_reset_transport_header(skb);
skb_reset_mac_len(skb);
pt_prev = NULL;
比如对于vlan报文处理,从父设备接收报文,经过vlan_do_receive找到vlan子接
口后,需要重新从此开始
another_round:
skb->skb_iif = skb->dev->ifindex;
__this_cpu_inc(softnet_data.processed);
vlan报文或者QinQ报文,调用skb_vlan_untag剥掉vlan头
if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
skb = skb_vlan_untag(skb);
if (unlikely(!skb))
goto out;
}
#ifdef CONFIG_NET_CLS_ACT
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
}
#endif
if (pfmemalloc)
goto skip_taps;
处理抓包程序,比如tcpdump。以下两种情况才会抓包:
a. ptype->dev 为空,即抓取所有设备的数据, 比如 tcpdump -i any
b. ptype->dev == skb->dev 指定了抓包设备,只有从这个设备收到的报文才会抓取,比如 tcpdump -i eth0。
如果是vlan报文的话,可以在vlan子接口的父设备 skb->dev 上抓到带vlan的报文。
后面经过vlan处理,找到真正的vlan子接口 vlan_dev,会重新走此,就可以在vlan
子接口上抓到不带vlan的报文。
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
skip_taps:
#ifdef CONFIG_NET_CLS_ACT
skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
ncls:
#endif
if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
goto drop;
处理vlan报文
if (vlan_tx_tag_present(skb)) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
此时 skb->dev 中的dev是vlan子接口的父设备,根据vlanid到dev查找
vlan_dev,并赋值给skb->dev,从 another_round 重新开始协议栈处理
if (vlan_do_receive(&skb))
goto another_round;
else if (unlikely(!skb))
goto out;
}
linux bridge, ovs, bond, macvlan等虚拟设备的处理
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
switch (rx_handler(&skb)) {
case RX_HANDLER_CONSUMED:
ret = NET_RX_SUCCESS;
goto out;
case RX_HANDLER_ANOTHER:
goto another_round;
case RX_HANDLER_EXACT:
deliver_exact = true;
case RX_HANDLER_PASS:
break;
default:
BUG();
}
}
if (unlikely(vlan_tx_tag_present(skb))) {
if (vlan_tx_tag_get_id(skb))
skb->pkt_type = PACKET_OTHERHOST;
/* Note: we might in the future use prio bits
* and set skb->priority like in vlan_do_receive()
* For the time being, just ignore Priority Code Point
*/
skb->vlan_tci = 0;
}
/* deliver only exact match when indicated */
null_or_dev = deliver_exact ? skb->dev : NULL;
根据 protocol 开始调用不同的hook函数,比如arp_rcv, ip_rcv等。
type = skb->protocol;
list_for_each_entry_rcu(ptype,
&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
if (ptype->type == type &&
(ptype->dev == null_or_dev || ptype->dev == skb->dev ||
ptype->dev == orig_dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev) {
if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
goto drop;
else
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} else {
drop:
atomic_long_inc(&skb->dev->rx_dropped);
kfree_skb(skb);
/* Jamal, now you will not able to escape explaining
* me how you were going to use this. :-)
*/
ret = NET_RX_DROP;
}
out:
return ret;
}
在__netif_receive_skb_core函数中,在for循环时会用到pt_prev,为什么要使用它呢?代码如下:
pt_prev=NULL;
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
想要理解pt_prev的存在,需要知道下面知识:
a. 分配skb时,users置为1
alloc_skb
atomic_set(&skb->users, 1);
b. 释放skb时,如果有多个模块引用skb,则users减一,如果users为1了,则真正释放skb
void kfree_skb(struct sk_buff *skb)
{
if (unlikely(!skb))
return;
//如果users为1说明只有一个模块引用这个skb,可以调用__kfree_skb安全释放skb
if (likely(atomic_read(&skb->users) == 1))
smp_rmb();
//否则,调用atomic_dec_and_test将users减一并判断新值是否为0.
//users为1已经在上面判断过,所以这个判断时users肯定是大于1的,即有多个模块引用skb
else if (likely(!atomic_dec_and_test(&skb->users)))
return;
trace_kfree_skb(skb, __builtin_return_address(0));
__kfree_skb(skb);
}
c. 调用deliver_skb上送时,users增加1,pt_prev->func执行完会调用kfree_skb,将users减1.
deliver_skb
if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
return -ENOMEM;
atomic_inc(&skb->users);
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
netif_receive_skb函数执行结束后,必须保证真正释放skb,否则就会造成内存泄漏
如果没有pt_prev的存在,循环结束后,得显试调用kfree_skb,代码如下:
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
//deliver_skb中会对users加1,在func函数中会调用kfree_skb再减1,
//所以deliver_skb执行完,users还是1
ret = deliver_skb(skb, pt_prev, orig_dev);
kfree_skb(skb); //显试调用,此时users为1,只有当前模块引用skb,所以就可以真正释放skb
假如ptype_all只有一个元素,则需要调用两次kfree_skb,第一次是在pt_prev->func中,第二次是上面的显试调用。
如果使用pt_prev,
pt_prev=NULL;
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} else {
kfree_skb(skb);
假如ptype_all只有一个元素,则只需要调用一次kfree_skb。
在for循环中,因为pt_prev为NULL,所以不会调用deliver_skb,
退出for循环后,if判断pt_prev不为空,再调用func,在func函数中调用kfree_skb时,就可以真正释放了。
假如ptype_all一个元素都没有,则会直接调用kfree_skb进行释放。
这种情况下,就是因为少调了一次deliver_skb。