Linux网桥的实现
linux内核实现虚拟的网桥设备,并绑定若干个以太网口(目前网桥只支持以太网接口)。
交换机对收到的数据包,只能丢弃或转发。但是linux内核设备不一样,他可能本身就是数据包的目的地。所以除了丢弃转发,他还会发往协议层自己消化。
网桥是在数据链路层实现的,他的上面是邻居子系统和网络层。
linux网桥数据结构

虚拟网桥也是一个网络设备,基本结构体为net_device,net_device –> priv下存储了网桥数据结构的私有变量net_bridge。上图中一些重要的数据结构如下:
-
net_bridge:网桥私有数据
-
net_bridge_port网桥要绑定的以太网端口的结构体
-
net_bridge_fdb_entry:单播转发数据库条目
-
net_bridge_mdb_entry:组播转发数据库条目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
int br_add_bridge(struct net *net, const char *name)
{
struct net_device *dev;
int res;
dev = alloc_netdev(sizeof(struct net_bridge), name, NET_NAME_UNKNOWN,
br_dev_setup); //////
if (!dev)
return -ENOMEM;
dev_net_set(dev, net);
dev->rtnl_link_ops = &br_link_ops;
res = register_netdevice(dev);
if (res)
free_netdev(dev);
return res;
}
|
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
|
void br_dev_setup(struct net_device *dev)
{
struct net_bridge *br = netdev_priv(dev); /////////
eth_hw_addr_random(dev);
ether_setup(dev);
dev->netdev_ops = &br_netdev_ops;
dev->needs_free_netdev = true;
dev->ethtool_ops = &br_ethtool_ops;
SET_NETDEV_DEVTYPE(dev, &br_type);
dev->priv_flags = IFF_EBRIDGE | IFF_NO_QUEUE;
dev->features = COMMON_FEATURES | NETIF_F_LLTX | NETIF_F_NETNS_LOCAL |
NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_STAG_TX;
dev->hw_features = COMMON_FEATURES | NETIF_F_HW_VLAN_CTAG_TX |
NETIF_F_HW_VLAN_STAG_TX;
dev->vlan_features = COMMON_FEATURES;
br->dev = dev; //////////////////
spin_lock_init(&br->lock);
INIT_LIST_HEAD(&br->port_list); ////////////
INIT_HLIST_HEAD(&br->fdb_list); ////////////
INIT_HLIST_HEAD(&br->frame_type_list);
#if IS_ENABLED(CONFIG_BRIDGE_MRP)
INIT_HLIST_HEAD(&br->mrp_list);
#endif
#if IS_ENABLED(CONFIG_BRIDGE_CFM)
INIT_HLIST_HEAD(&br->mep_list);
#endif
spin_lock_init(&br->hash_lock);
br->bridge_id.prio[0] = 0x80;
br->bridge_id.prio[1] = 0x00;
ether_addr_copy(br->group_addr, eth_stp_addr);
br->stp_enabled = BR_NO_STP;
br->group_fwd_mask = BR_GROUPFWD_DEFAULT;
br->group_fwd_mask_required = BR_GROUPFWD_DEFAULT;
br->designated_root = br->bridge_id;
br->bridge_max_age = br->max_age = 20 * HZ;
br->bridge_hello_time = br->hello_time = 2 * HZ;
br->bridge_forward_delay = br->forward_delay = 15 * HZ;
br->bridge_ageing_time = br->ageing_time = BR_DEFAULT_AGEING_TIME;
dev->max_mtu = ETH_MAX_MTU;
br_netfilter_rtable_init(br);
br_stp_timer_init(br);
br_multicast_init(br);
INIT_DELAYED_WORK(&br->gc_work, br_fdb_cleanup);
}
|
1
2
3
4
|
static inline void *netdev_priv(const struct net_device *dev)
{
return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
}
|
简单来说,struct net_device 是桥的“外壳”或“接口”,而 struct net_bridge 是桥的“大脑”或“核心逻辑”。它们通过一个关键的指针相互关联。下面我们进行详细分解。
网桥私有数据:net_bridge
网桥相关信息保存在这里:
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
|
struct net_bridge {
spinlock_t lock;
spinlock_t hash_lock;
struct hlist_head frame_type_list;
struct net_device *dev; ///////
unsigned long options;
/* These fields are accessed on each packet */
#ifdef CONFIG_BRIDGE_VLAN_FILTERING
__be16 vlan_proto;
u16 default_pvid;
struct net_bridge_vlan_group __rcu *vlgrp;
#endif
struct rhashtable fdb_hash_tbl; /////
struct list_head port_list; /////
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
union {
struct rtable fake_rtable;
struct rt6_info fake_rt6_info;
};
#endif
u16 group_fwd_mask;
u16 group_fwd_mask_required;
/* STP */
bridge_id designated_root;
bridge_id bridge_id;
unsigned char topology_change;
unsigned char topology_change_detected;
u16 root_port;
unsigned long max_age;
unsigned long hello_time;
unsigned long forward_delay;
unsigned long ageing_time;
unsigned long bridge_max_age;
unsigned long bridge_hello_time;
unsigned long bridge_forward_delay;
unsigned long bridge_ageing_time;
u32 root_path_cost;
u8 group_addr[ETH_ALEN];
enum {
BR_NO_STP, /* no spanning tree */
BR_KERNEL_STP, /* old STP in kernel */
BR_USER_STP, /* new RSTP in userspace */
} stp_enabled;
struct net_bridge_mcast multicast_ctx;
#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
struct bridge_mcast_stats __percpu *mcast_stats;
u32 hash_max;
spinlock_t multicast_lock;
struct rhashtable mdb_hash_tbl; ////////
struct rhashtable sg_port_tbl;
struct hlist_head mcast_gc_list;
struct hlist_head mdb_list; //////
struct work_struct mcast_gc_work;
#endif
struct timer_list hello_timer;
struct timer_list tcn_timer;
struct timer_list topology_change_timer;
struct delayed_work gc_work;
struct kobject *ifobj;
u32 auto_cnt;
#ifdef CONFIG_NET_SWITCHDEV
/* Counter used to make sure that hardware domains get unique
* identifiers in case a bridge spans multiple switchdev instances.
*/
int last_hwdom;
/* Bit mask of hardware domain numbers in use */
unsigned long busy_hwdoms;
#endif
struct hlist_head fdb_list; /////
#if IS_ENABLED(CONFIG_BRIDGE_MRP)
struct hlist_head mrp_list;
#endif
#if IS_ENABLED(CONFIG_BRIDGE_CFM)
struct hlist_head mep_list;
#endif
};
|
网桥端口:net_bridge_port
一个网桥可以和若干以太网端口绑定,每个端口信息保存在net_bridge_port结构中,并被net_bridge{} –>port_list链接。
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
|
struct net_bridge_port {
struct net_bridge *br; ////
struct net_device *dev; ////
netdevice_tracker dev_tracker;
struct list_head list;
unsigned long flags;
#ifdef CONFIG_BRIDGE_VLAN_FILTERING
struct net_bridge_vlan_group __rcu *vlgrp;
#endif
struct net_bridge_port __rcu *backup_port;
/* STP */
u8 priority;
u8 state;
u16 port_no;
unsigned char topology_change_ack;
unsigned char config_pending;
port_id port_id;
port_id designated_port;
bridge_id designated_root;
bridge_id designated_bridge;
u32 path_cost;
u32 designated_cost;
unsigned long designated_age;
struct timer_list forward_delay_timer;
struct timer_list hold_timer;
struct timer_list message_age_timer;
struct kobject kobj;
struct rcu_head rcu;
struct net_bridge_mcast_port multicast_ctx;
#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
struct bridge_mcast_stats __percpu *mcast_stats;
u32 multicast_eht_hosts_limit;
u32 multicast_eht_hosts_cnt;
struct hlist_head mglist;
#endif
#ifdef CONFIG_SYSFS
char sysfs_name[IFNAMSIZ];
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
struct netpoll *np;
#endif
#ifdef CONFIG_NET_SWITCHDEV
/* Identifier used to group ports that share the same switchdev
* hardware domain.
*/
int hwdom;
int offload_count;
struct netdev_phys_item_id ppid;
#endif
u16 group_fwd_mask;
u16 backup_redirected_cnt;
struct bridge_stp_xstats stp_xstats;
};
|
单播转发数据库条目:net_bridge_fdb_entry
net_bridge中有两个字段与该结构体相关,fdb_hash_tbl与fdb_list,函数fdb_create()可以看到net_bridge_fdb_entry与这连个字段的关系。每一条新建的fdb表项会加入br->fdb_hash_tbl这个哈希表中,准确的说是br->fdb_hash_tbl->tbl,对应的键值是fdb表项的key字段(addr与vlan)。插入成功,则将该fdb表项的fdb_node插入br->fdb_hash_tbl中。
也就说fdb表项分别以哈希表与链表的形式存储在br中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct net_bridge_fdb_entry {
struct rhash_head rhnode;
struct net_bridge_port *dst;
struct net_bridge_fdb_key key;
struct hlist_node fdb_node;
unsigned long flags;
/* write-heavy members should not affect lookups */
unsigned long updated ____cacheline_aligned_in_smp;
unsigned long used;
struct rcu_head rcu;
};
|
组播转发数据库条目:net_bridge_mdb_entry
该条目描述一个多播组转发项,将多播形式的mac地址与一组端口对应,timer链表为这些端口的失效定时器链表,某一个端口定时器超时则将其移除
1
2
3
4
5
6
7
8
9
10
11
12
13
|
struct net_bridge_mdb_entry {
struct rhash_head rhnode;
struct net_bridge *br;
struct net_bridge_port_group __rcu *ports;
struct br_ip addr;
bool host_joined;
struct timer_list timer;
struct hlist_node mdb_node;
struct net_bridge_mcast_gc mcast_gc;
struct rcu_head rcu;
};
|
网桥设备对数据包处理
以收包为例。
网络中有数据包到来,当网卡接受到数据时,网卡向cpu发中断,cpu调用驱动程序,将数据包放到对应的cpu输入队列。接着唤醒软中断,调用netif_receive_skb函数,在这里判断数据包需要转到上层协议栈,还是向网桥接口处理。数据流向如下,这里默认网桥设备为非disable状态

具体流程:
1
2
3
4
5
6
7
8
9
10
|
1. netif_receive_skb
2. -->netif_receive_skb_internal /* 记录收包时间, rps机制,将报文在多个cpu之间做负载均衡以及提高报文处理的缓存命中率 */
3. -->__netif_receive_skb
4. -->__netif_receive_skb_core
5. --> skb_reset_network_header //重置network_header字段
6. --> skb_vlan_untag //802.1Q、802.1AD,剥除vxlan头
7. --> paket_type.func() //处理 ptype_all 上所有的 packet_type->func()
8. --> vlan_do_receive
9. --> dev->rx_handler () //实际执行函数br_handle_frame(),数据包逻辑分发,网桥处理转发就在这里进行
10. --> deliver_ptype_list_skb //向L3分发
|
__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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc,
struct packet_type **ppt_prev)
{
struct packet_type *ptype, *pt_prev;
rx_handler_func_t *rx_handler;
struct sk_buff *skb = *pskb;
struct net_device *orig_dev;
bool deliver_exact = false;
int ret = NET_RX_DROP;
__be16 type;
net_timestamp_check(!READ_ONCE(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;
another_round:
skb->skb_iif = skb->dev->ifindex;
__this_cpu_inc(softnet_data.processed);
if (static_branch_unlikely(&generic_xdp_needed_key)) {
int ret2;
migrate_disable();
ret2 = do_xdp_generic(rcu_dereference(skb->dev->xdp_prog), skb);
migrate_enable();
if (ret2 != XDP_PASS) {
ret = NET_RX_DROP;
goto out;
}
}
if (eth_type_vlan(skb->protocol)) {
skb = skb_vlan_untag(skb);
if (unlikely(!skb))
goto out;
}
if (skb_skip_tc_classify(skb))
goto skip_classify;
if (pfmemalloc)
goto skip_taps;
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_INGRESS
if (static_branch_unlikely(&ingress_needed_key)) {
bool another = false;
skb = sch_handle_ingress(skb, &pt_prev, &ret, orig_dev,
&another);
if (another)
goto another_round;
if (!skb)
goto out;
if (nf_ingress(skb, &pt_prev, &ret, orig_dev) < 0)
goto out;
}
#endif
skb_reset_redirect(skb);
skip_classify:
if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
goto drop;
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 out;
}
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;
break;
case RX_HANDLER_PASS:
break;
default:
BUG();
}
}
if (unlikely(skb_vlan_tag_present(skb)) && !netdev_uses_dsa(skb->dev)) {
check_vlan_id:
if (skb_vlan_tag_get_id(skb)) {
/* Vlan id is non 0 and vlan_do_receive() above couldn't
* find vlan device.
*/
skb->pkt_type = PACKET_OTHERHOST;
} else if (eth_type_vlan(skb->protocol)) {
/* Outer header is 802.1P with vlan 0, inner header is
* 802.1Q or 802.1AD and vlan_do_receive() above could
* not find vlan dev for vlan id 0.
*/
__vlan_hwaccel_clear_tag(skb);
skb = skb_vlan_untag(skb);
if (unlikely(!skb))
goto out;
if (vlan_do_receive(&skb))
/* After stripping off 802.1P header with vlan 0
* vlan dev is found for inner header.
*/
goto another_round;
else if (unlikely(!skb))
goto out;
else
/* We have stripped outer 802.1P vlan 0 header.
* But could not find vlan dev.
* check again for vlan id to set OTHERHOST.
*/
goto check_vlan_id;
}
/* 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
*/
__vlan_hwaccel_clear_tag(skb);
}
type = skb->protocol;
/* deliver only exact match when indicated */
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);
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_rx(skb, GFP_ATOMIC)))
goto drop;
*ppt_prev = pt_prev;
} else {
drop:
if (!deliver_exact)
atomic_long_inc(&skb->dev->rx_dropped);
else
atomic_long_inc(&skb->dev->rx_nohandler);
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:
/* The invariant here is that if *ppt_prev is not NULL
* then skb should also be non-NULL.
*
* Apparently *ppt_prev assignment above holds this invariant due to
* skb dereferencing near it.
*/
*pskb = skb;
return ret;
}
|
CONFIG_NET_INGRESS
是 Linux 内核的一个配置选项,用于启用或禁用网络设备的 Ingress 功能。启用 CONFIG_NET_INGRESS 配置选项后,Linux 内核将支持对网络设备的入口流量进行控制和处理。网络设备的 Ingress 功能可以用于实现以下功能:
Ø 数据包过滤:根据预先设置的过滤规则,判断数据包是否符合要求。如果数据包被过滤,可以根据策略进行丢弃或处理。
Ø 数据包处理:根据网络设备的配置,对数据包进行处理。例如,进行 QoS 处理、修改数据包头部、更改目标端口等操作。
Ø 数据包转发:根据策略和路由表,决定数据包的转发目的地,并将数据包发送到相应的网络接口。
上述功能通过函数sch_handle_ingress实现
对vlan的处理
判断skb协议是否为8021Q 8021AD,若是vlan包,则调用skb_vlan_untag()函数,该函数读出数据流中的vlan_id,并填写入skb->vlan_tci中,然后删除vlan_head,从而实现对上层的透明。注意这里的skb->vlan_tci标志仅是为了保存skb数据的vlan头信息,而skb中的数据是透明的以太网包.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static inline bool eth_type_vlan(__be16 ethertype)
{
switch (ethertype) {
case htons(ETH_P_8021Q):
case htons(ETH_P_8021AD):
return true;
default:
return false;
}
}
if (eth_type_vlan(skb->protocol)) {
skb = skb_vlan_untag(skb);
if (unlikely(!skb))
goto out;
}
|
vlan信息转移到skb结构中后,会获取数据包真实协议类型(三层协议类型),更新到skb的protocol字段,替换了之前的ETH_P_8021Q或者ETH_P_8021AD。将vlan信息存在skb字段后,调用vlan_do_receive,该函数由skb->vlan_tci得到该skb包所要发往的vlan_dev,并且重定向skb->dev为该vlan_dev,最后消除skb中的vlan_tci标志。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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 out;
}
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {
|
样之后vlan_do_receive()返回,不过此时的skb已经是一个普通的数据包了(实现了对上层的透明),且它看起来就像是由vlan_dev接收的数据包。
接下来是关键处理dev->rx_handler,这个函数在之前将网口添加到网桥设备的过程,调用br_add_if函数中定义
1
2
3
4
5
|
br_add_if ()
-->netdev_rx_handler_register
-->netdev_rx_handler_register(dev, br_get_rx_handler(dev), p); //
{rcu_assign_pointer(dev->rx_handler_data, p)
rcu_assign_pointer(dev->rx_handler, br_get_rx_handler)}
|
看到在netdev_rx_handler_register函数:
所以这里网桥操作skb包,主要在br_get_rx_handler函数中进行。
经过dev->rx_handler函数处理,返回值有四种类型:
Ø RX_HANDLER_CONSUMED:数据包已处理消化,无需进一步处理。(什么场景) 直接跳转到out标签退出。
Ø RX_HANDLER_ANOTHER:修改了skb->dev,再处理一次,比如数据包设备是网桥设备,但经过判断,需要经过网桥设备转发到另一个网桥关联的端口,这时就从bridge_dev转换到了port_dev
Ø RX_HANDLER_EXACT:精确指定要传递到ptype->dev == skb->dev(什么场景)
Ø RX_HANDLER_PASS:1.数据包类型是回环类型PACKET_LOOPBACK
后两种情况会继续往下执行,最终将数据包提交到L3层协议栈
网桥设备处理skb: br_handle_frame
从这里开始才是对数据包的处理分流转发,之前可以看作netif_receive_skb函数对数据包的预处理。br_get_rx_handler被设置为br_handle_frame,具体路径:net/bridge/br_input.c。
br_handle_frame函数根据数据包的类型,对数据的处理进行分流.
Ø 网桥处于disable状态:
1.数据包目的地址是网桥端口地址:将pkt_type设为PACKET_HOST
1
2
|
if (ether_addr_equal(p->br->dev->dev_addr, dest))
skb->pkt_type = PACKET_HOST;
|
2.否则,br_handle_local_finish函数设置到网桥预处理节点,该函数在网桥状态部位disable时,会学习mac与端口信息并记录fdb表中
1
2
3
4
5
|
if (NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING,
dev_net(skb->dev), NULL, skb, skb->dev, NULL,
br_handle_local_finish) == 1) {
return RX_HANDLER_PASS;
}
|
Ø 网桥处于learning或forwarding状态:
调用函数nf_hook_bridge_pre-> br_handle_frame_finish
br_handle_frame_finish
br_handle_frame_finish:决策将不同类别的数据包做不同的分发路径。具体处理逻辑如下图.
首先这里有一个比较重要的特性,arp proxy,接口如果是能了此功能,对于收到的arp request,通过查询本地的arp表构造arp reply报文回应。
根据目标地址skb->hdr->h_dest判断是单播、广播、多播的哪一种:
Ø 单播:调用br_fdb_find_rcu查找fdb表项, (1)若命中,调用br_forward()将数据包转发, (2)若表项为空,调用函数br_flood() br_flood(br, skb, pkt_type, local_rcv, false); 注意,如果发现目标地址是本地地址(判断方法目标表项dst->flags为BR_FDB_LOCAL),则直接调用br_pass_frame_up()发往本地,该函数会再次进入netif_receive_skb函数。
Ø 广播:由于广播目标地址包含本机,所以会再次调用br_pass_frame_up(),并调用br_flood()
Ø 多播:因为当目的mac地址是0x01开头时,既可以是igmp类型的多播协议控制报文,也可以是多播数据流报文。先调用br_multicast_rcv处理igmp类型数据包,多播数据表也通过该类型数据表维护。接着查看多播数据表项, (1)如果命中,调用br_multicast_flood(), (2)如果未命中也会调用br_flood()。 最后多播也会判断本机是否加入多播组,加入的话也会调用函数br_pass_frame_up()将数据发往本地。
这里看到单播,多播,广播都会调用br_flood()函数,分别在以下情况:
-
单播,fdb表项未命中
-
广播下都会调用
-
多播,mdb表未命中
不同情况,通过函数参数pkt_type进行的区分。
-
单播:pkt_type = BR_PKT_UNICAST
-
广播:pkt_type = BR_PKT_BROADCAST
-
多播:pkt_type = BR_PKT_MULTICAST
数据包发往本地:br_pass_frame_up
数据进入br_pass_frame_up,是打算经由Bridge设备,输入到本地Host的。数据包从网桥端口设备进入,经过网桥设备,然后再进入协议栈,其实是“两次经过net_device”,一次是端口设备,另一次是网桥设备。现在数据包离开网桥端口进入网桥设备,需要修改skb->dev字段。
1
2
|
indev = skb->dev;
skb->dev = brdev
|
递交的最后一步是经过NF_BR_LOCAL_IN钩子点,然后是我们熟悉的netif_receive_skb,只不过这次进入该函数的时候skb->dev已经被换成了Bridge设备。这可以理解为进入了Bridge设备的处理。它的skb->dev->rx_handler 为空,所以不会再次进入br_handler_frame,而是会进上层协议栈。
1
2
3
|
return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN,
dev_net(indev), NULL, skb, indev, NULL,
br_netif_receive_skb);
|
数据被修改skb->dev后再次进入netif_receive_skb,上次执行的netif_receive_skb因为rx_handler返回CONSUMED而结束。
数据包转发
-
单端口转发:br_forward
br_forward经过一系列的检查与前期准备,最终调用dev_queue_xmit(skb)将数据包放到网卡输出队列中发送出去。之后对应具体的网卡驱动函数。
-
多播:br_multicast_flood
桥的多播功能需要使能igmp snooping功能
转发端口获取:
- 从mdb中获取多播组的端口p1,
- 从br->router_list获取桥接端口p2
- p = p1>p2? p1: p2 (为什么这样比较)
- 若p=p1时,如果该端口支持多播对单播转发, 则以单播形式转发(也是调用br_forward,与普通多播貌似没有不同)
- 复制skb数据,调用br_forward从p端口转发数据
下面br_flood一样每次转发的端口都是上次遍历得到的端口,最后一次端口使用原始skb数据,这样减少一次clone操作
-
flood到各端口br_flood
如果没有查找到对应的表项(单播、组播),或者要进行广播模式,系统调用br_flood,向网桥的每个端口转发。br_flood函数遍历网桥下的每个端口,根据允许的flags条件,调用deliver_clone或__br_forward将sbk从该端口转发出去
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
|
void br_flood(struct net_bridge *br, struct sk_buff *skb,
enum br_pkt_type pkt_type, bool local_rcv, bool local_orig)
{
struct net_bridge_port *prev = NULL;
struct net_bridge_port *p;
list_for_each_entry_rcu(p, &br->port_list, list) {
/* Do not flood unicast traffic to ports that turn it off, nor
* other traffic if flood off, except for traffic we originate
*/
switch (pkt_type) {
case BR_PKT_UNICAST:
if (!(p->flags & BR_FLOOD))
continue;
break;
case BR_PKT_MULTICAST:
if (!(p->flags & BR_MCAST_FLOOD) && skb->dev != br->dev)
continue;
break;
case BR_PKT_BROADCAST:
if (!(p->flags & BR_BCAST_FLOOD) && skb->dev != br->dev)
continue;
break;
}
/* Do not flood to ports that enable proxy ARP */
if (p->flags & BR_PROXYARP)
continue;
if ((p->flags & (BR_PROXYARP_WIFI | BR_NEIGH_SUPPRESS)) &&
BR_INPUT_SKB_CB(skb)->proxyarp_replied)
continue;
prev = maybe_deliver(prev, p, skb, local_orig);
if (IS_ERR(prev))
goto out;
}
if (!prev)
goto out;
if (local_rcv)
deliver_clone(prev, skb, local_orig);
else
__br_forward(prev, skb, local_orig);
return;
out:
if (!local_rcv)
kfree_skb(skb);
}
|
在向每个端口转发时,都会复制一份新的skb数据。
maybe_deliver是deliver_clone的包装,每次遍历端口后,会在下一个遍历周期,将数 据从本次遍历端口发送,最后一个端口的数据则是直接使用原始的skb,这样可以少一次数据复制开销。
参考链接
linux网桥驱动与二层对数据包分发 - 知乎