Linux内核Bridge网桥功能学习笔记
- 1. 背景介绍
在前两篇文章中,我们学习了Linux内核switchdev框架和实现代码,其中提到switchdev驱动的很大一部分是卸载内核中bridge网桥功能到交换芯片设备中。那么内核中的网桥功能具体是指什么,他和我们平常说的交换机又有什么区别,网桥有哪些应用,带着这些问题我研究了一下内核中的Bridge模块,当实际深入之后,发现它并没有想象的那么简单。
- 2. 网桥功能实战
网桥是什么,让我们通过在Linux主机上的一个实验来体会一下网桥的功能。
在聊聊 Linux 上软件实现的“交换机” – Bridge!这篇文章中,作者通过在Linux上创建一个小型的虚拟网络,来演示了网桥如何将不同的网络连接起来的。直接按照作者的配置命令就可以,但是在这个过程中我有了一些细微的调整,所以还是把我的完整命令记录下来,以便自己以后查阅。
网络拓扑图如下:

图片来自https://mp.weixin.qq.com/s/JnKz1fUgZmGdvfxOm2ehZg
配置命令如下:
# 配置netns1及相关veth接口
# 创建网络命名空间netns1
sudo ip netns add netns1
# 创建veth口veth1和veth1_p
sudo ip link add veth1 type veth peer name veth1_p
# 将veth1加入到netns1中
sudo ip link set veth1 netns netns1
# 为veth1配置ip地址
sudo ip netns exec netns1 ip addr add 192.168.0.101/24 dev veth1
# 将veth1口设置为up状态
sudo ip netns exec netns1 ip link set veth1 up
# 查看netns1下的link状态
sudo ip netns exec netns1 ip link list
# 查看netns1下的接口信息
sudo ip netns exec netns1 ifconfig
# 配置netns2及相关veth接口
# 创建网络命名空间netns2
sudo ip netns add netns2
# 创建veth口veth2和veth2_p
sudo ip link add veth2 type veth peer name veth2_p
# 将veth2加入到netns2中
sudo ip link set veth2 netns netns2
# 为veth2配置ip地址
sudo ip netns exec netns2 ip addr add 192.168.0.102/24 dev veth2
# 将veth2口设置为up状态
sudo ip netns exec netns2 ip link set veth2 up
# 查看netns2下的link状态
sudo ip netns exec netns2 ip link list
# 查看netns2下的接口信息
sudo ip netns exec netns2 ifconfig
# 配置bridge相关
# 创建bridge并命名为br0
sudo ip link add br0 type bridge
# 查看br0状态
ip link show br0
# 将vet1_p加入br0
sudo ip link set dev veth1_p master br0
# 将vet2_p加入br0
sudo ip link set dev veth2_p master br0
# 为br0配置ip地址
sudo ip addr add 192.168.0.100/24 dev br0
# 将bridge和veth设置为up,这样可以正常转发报文
# 将veth1_p设置为up状态
sudo ip link set veth1_p up
# 将veth2_p设置为up状态
sudo ip link set veth2_p up
# 将br0设置为up状态
sudo ip link set br0 up
# 查看bridge相关状态
# 显示br0状态
ip link show br0
# 显示br0状态
ip link show master br0
# 显示bridge统计信息
bridge -s link
# 显示brdige详细信息
bridge -d link
# 显示br0地址信息
ip addr show br0
完成上述操作后,就可以通过如下ping命令测试网络是否连通了。
thinkdancer@mypc:~$ sudo ip netns exec netns1 ping 192.168.0.102 -I veth1
PING 192.168.0.102 (192.168.0.102) from 192.168.0.101 veth1: 56(84) bytes of data.
64 bytes from 192.168.0.102: icmp_seq=1 ttl=64 time=0.058 ms
64 bytes from 192.168.0.102: icmp_seq=2 ttl=64 time=0.042 ms
64 bytes from 192.168.0.102: icmp_seq=3 ttl=64 time=0.042 ms
在以上实验操作中有几点需要注意:
- bridge和veth都要手动up,因为是虚拟设备,所以不会像物理设备那样连上网线自动up,不设置会导致报文不能正常转发,最终无法ping通,本文在配置的时候就漏掉了将br0设备配置为up,导致ping失败。
- 上面的配置和原文的主要差异是,废除了原文使用的brctl命令,而全部以ip或者bridge命令替代,这样整个配置过程使用的都是iproute2工具。
- ip link show master br0和ip link show br0两条命令的差异是前者多了一个master关键字,输出结果中可以看到br0包含的接口列表,而后者只能看到br0自己的接口状态,所以前者的输出结果更接近于brctl show的输出。
- ip命令的关键字很多,在使用的时候除了借助于搜索引擎外,还可以直接输入ip link help,ip addr help或者ip netns help查看更加详细的关键字说明。另外man ip link还可以看到ip link命令的关键字解释,man ip netns也可以看到ip netns命令关键字的解释,但是man ip addr就只能看到ip命令关键字的解释,效果和man ip是一样的。
- 3. 网桥功能原理分析
3.1 网桥模型
由于历史的原因,Linux内核中将网络设备中的交换机称为网桥Bridge,其实含义是一样的,就是实现不同接口之间的L2层报文转发。这里的接口可以是具有物理网卡的网口,也可以是veth, tun/tap等虚拟接口,而后者在有些场景具有更强大的连接功能。
网桥还具有三层交换机的功能,即网桥本身可以有L3层的转发能力。通过在bridge对应的接口上配置IP地址,可以实现L3层面的转发,这样可以将bridge和外部网络世界连通起来,而不仅仅是一个L2转发域。
当然,相对于传统交换机上的access, trunk等模型,bridge上并没有直接对应的概念,但是通过一些方法和技巧可以实现类似的效果。在这里我们主要关注bridge的主要功能,而不仅仅是传统物理交换机做对比,看看它在具体实践中的作用。
3.2 网桥报文收发包
对于Linux来说,大部分网络接口都是L3模式,也就是根据报文的IP地址查找路由表做转发,比如我们普通的物理网卡就是这种模式。当需要将接口加入网桥的时候,实际是将接口设置为L2模式,直接处理L2层报文。在内核中是在接口加入网桥的时候,通过在br_add_if中使用netdev_rx_handler_register注册网桥的处理函数br_handle_frame来实现的,br_handle_frame中会根据报文格式以及查找结果决定报文的处理结果,如果是需要L2处理的报文,比如转发到另一个接口或者上送bridge对应的L3口处理,则返回RX_HANDLER_CONSUMED,如果是不需要处理的表文,比如一些组播协议LLDP,则返回RX_HANDLER_PASS交由协议层继续处理。上面RX_HANDLER_CONSUMED的含义是已经被消耗,所以直接跳出了网络层核心处理函数__netif_receive_skb_core,也就意味着不被后续的ptype_base注册对应流程处理,比如0x0800对应的IPv4或者0x86DD对应的IPv6,但是因为在ptype_all之前,所以tcpdump还是可以正常抓取到报文。
br_handle_frame中在判断接口状态是fowarding或者learning的时候,会调用nf_hook_bridge_pre,而后者最终会调用br_handle_frame_finish处理报文,注意这里的forwarding是接口的状态,而不是报文的处理结果。在br_handle_frame_finish中,根据报文是组播还是单播查找对应的转发表,如果dst存在且flags中的BR_FDB_LOCAL置位,则表示是需要上送更高一层的协议栈,比如IP层,则交给br_pass_frame_up处理,如果dst存在但BR_FDB_LOCAL未置位,则表示需要做L2转发的表文,则交给br_forward处理。如果dst不存在则通过br_flood或者br_multicast_flood泛洪报文。br_pass_frame_up最终会通过netif_receive_skb交给上层协议栈继续处理。
3.3 网桥FDB
从上面的收发包流程可以看出,每个报文都需要通过查找FDB表来决定下一步的处理流程,而在查找的过程中也会同时学习报文的SMAC到FDB转发表中,这一点和传统的交换机是一样的,这个流程是在br_handle_frame_finish中调用br_fdb_update实现的。
fdb是brdige中的核心处理模块,涉及fdb数据结构维护,entry表项的添加和删除,静态表项的维护,动态表项的老化,fdb通过switchdev机制到硬件芯片中的卸载和反向通知机制。这部分和route中的fib其实是一样的思想,就是处理核心的转发表项和对应的接口资源信息。这里查找使用的key是:addr + vid,如果接口没有vlan,则vid = 1,否则使用接口或者报文的vlan。在进行key的查找之前,还会通过br_allowed_ingress进行入方向的报文过滤功能,在这里会根据接口允许的vlan以及报文是否有vlan来决定后续查找key中的vid是多少。当在fdb中查找到dst后会通过fdb_forward转发报文,而在这其中会通过br_handle_vlan对出方向的报文vlan做处理,以便支持tag/untag等不同的应用场景。
3.4 网桥VLAN
在传统交换机中,VLAN是用来隔离广播域的,同时在传统交换机中还有access, trunk的概念,用来将多个VLAN的聚合在一起。在bridge中也不例外,但是bridge中的vlan和口有关,一个bridge中可以包含多个VLAN口。在bridge的创建中你其实是看不到VLAN的,只能看到添加或者删除了哪些接口。当接口收到带有VLAN字段的报文时,会先经过VLAN模块处理,然后再走bridge转发流程,也就是说可以将eth0.100这样的接口加入到bridge中,效果是将VLAN剥掉后走L3转发或者走L2转发,具体哪种取决于报文的DMAC是不是本机的MAC地址。如果是L3转发则根据剥掉VLAN后的报文查找路由表,如果是L2转发则根据VLAN+DMAC查找FDB表。
参考资料