netif_receive_skb简介

netif_receive_skb作用:

  • 把接收帧传给每个协议分流器
  • 把接收帧传给skb->protocol所关联的L3协议处理函数。
  • 负责L2必须的功能处理

如果某接收帧数据没有关联skb->protocol,而且L2没有处理该帧,kernel不知道如何处理该帧数据,会将起丢弃。

  • skb->protocol:一般由驱动接收程序赋值,从L2层设备驱动程序的角度看,就是用在下一个较高层的协议。如:IP、IPv6以及ARP等;值在include/linux/if_ether.h中。由于每种协议都有对应的处理函数,因此驱动程序使用该字段告诉上层程序对应协议。

netif_receive_skb() 是主要的接收数据处理函数。它总是成功。 缓冲区可能会在拥塞控制处理期间或被协议层丢弃。此函数只能从软中断上下文调用,并且应启用中断。

如果桥接代码或者入口流量控制没有处理该帧数据,则该帧会传给L3协议处理函数(通常每种协议只有一个处理函数,但是也可以注册多个)。

至此,接收部分已经完成。

L3对封包的处理:

  • 传递给接收应用层程序
  • 丢弃(如:防火墙过滤失败)
  • 转发(路由器中常见)

netif_receive_skb函数分析

下面是对入口帧处理函数netif_receive_skb走读。

netif_receive_skb函数

1
2
3
4
5
6
7
int netif_receive_skb(struct sk_buff *skb)
{
/* 跟踪调试 */
trace_netif_receive_skb_entry(skb);

return netif_receive_skb_internal(skb);
}

netif_receive_skb_internal函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static int netif_receive_skb_internal(struct sk_buff *skb)
{
/* 记录收包时间 */
net_timestamp_check(netdev_tstamp_prequeue, skb);

if (skb_defer_rx_timestamp(skb))
return NET_RX_SUCCESS;

/* rps机制,将报文在多个cpu之间做负载均衡以及提高报文处理的缓存命中率 */
#ifdef CONFIG_RPS
if (static_key_false(&rps_needed)) {
struct rps_dev_flow voidflow, *rflow = &voidflow;
int cpu, ret;

rcu_read_lock();

cpu = get_rps_cpu(skb->dev, skb, &rflow);

if (cpu >= 0) {
ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
rcu_read_unlock();
return ret;
}
rcu_read_unlock();
}
#endif
return __netif_receive_skb(skb);
}

__netif_receive_skb函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int __netif_receive_skb(struct sk_buff *skb)
{
int ret;

/* PF_MEMALLOC标志和内存管理有关系 */
if (sk_memalloc_socks() && skb_pfmemalloc(skb)) {
unsigned long pflags = current->flags;

current->flags |= PF_MEMALLOC;
ret = __netif_receive_skb_core(skb, true);
tsk_restore_flags(current, pflags, PF_MEMALLOC);
} else
ret = __netif_receive_skb_core(skb, false);

return ret;
}

PF_MEMALLOC标志的影响

  • 当进程进行页面分配时,可以忽略内存管理的内存水位进行分配,这是告诉内存管理系统,需要紧急内存,稍后会释放更多内存;
  • 如果忽略内存水位分配仍然失败,那么直接返回ENOMEM,而不是等待kswapd回收或者缩减内存;
  • 如果忽略内存水位分配仍然失败,那么直接返回ENOMEM,而不会调用OOM killer去杀死进程,释放内存。

__netif_receive_skb_core函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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;
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;

/* 重置network_header字段 */
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;

rcu_read_lock();

another_round:
skb->skb_iif = skb->dev->ifindex;

__this_cpu_inc(softnet_data.processed);

/* 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 unlock;
}

#ifdef CONFIG_NET_CLS_ACT
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
}
#endif

/* 如果PFMEMALLOC置位,此类报文不允许ptype_all处理 */
if (pfmemalloc)
goto skip_taps;

/* 适用于所有协议的特定于设备的数据包处理程序,paket_type.type 为 ETH_P_ALL,如tcpdump抓包 */
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}

list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) {
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 unlock;
ncls:
#endif

/* 不支持PFMEMALLOC置位 */
if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
goto drop;

/* 如果是vlan数据 */
if (skb_vlan_tag_present(skb)) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
if (vlan_do_receive(&skb))
goto another_round;
else if (unlikely(!skb))
goto unlock;
}

/* 如果注册了handle,如bridge */
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)) {

/* 被rx_handler处理,不用在处理 */
case RX_HANDLER_CONSUMED:
ret = NET_RX_SUCCESS;
goto unlock;

/* 重新走一轮,skb->dev被rx_handler修改了 */
case RX_HANDLER_ANOTHER:
goto another_round;

/* 精确传递:ptype->dev == skb->dev */
case RX_HANDLER_EXACT:
deliver_exact = true;

/* 什么都不做,就像没有调用rx_handler一样传递skb */
case RX_HANDLER_PASS:
break;
default:
BUG();
}
}

/* 还有VLAN,上面没有找到对应vlan的设备 */
if (unlikely(skb_vlan_tag_present(skb))) {

/* 如果获取到vid,则将pkt_type设置为PACKET_OTHERHOST
* PACKET_OTHERHOST:该帧目的地址不属于与该接口相匹配的地址,如果开启转发则转发,
* 否则丢弃。vlan_tci值为0。
*/
if (skb_vlan_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;
}

/* 获取L3协议类型,以下向L3提交 */
type = skb->protocol;

/* rx_handler没有设置精确处理 */
if (likely(!deliver_exact)) {
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
&ptype_base[ntohs(type) &
PTYPE_HASH_MASK]);
}

/* 特定于设备、特定于协议的数据包处理程序 */
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
&orig_dev->ptype_specific);

/* 入口记录的skb_dev发生改变 */
if (unlikely(skb->dev != orig_dev)) {
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
&skb->dev->ptype_specific);
}

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;
}

unlock:
rcu_read_unlock();
return ret;
}

由以上代码走读可以看出netif_receive_skb函数处理在__netif_receive_skb_core中。__netif_receive_skb_core函数主要做了以下内容:

  • 处理ptype_all上的函数,如tcpdump抓包
  • 处理vlan报文
  • 处理rx_handler,如bridge
  • 向L3提交skb