Linux bridge是Linux内核中虚拟网桥的一个实现,它与OpenvSwitch都实现交换机功能,但比后者简单一些,只实现了最简单的二层功能,而没有ovs下诸如QoS,OpenFlow,netconf等等复杂的功能。本文主要参考《深入理解Linux网络技术内幕》,更像一个读书笔记性质的小结。
虚拟网桥是定义在真实设备之上的一个抽象设备,当该真实设备发生状态变化,则Linux bridge会受到影响。netdevice通知链向内核注册br_device_event
回调函数,任何真实设备的变动会触发该回调函数。
下图是在有无网桥情况下收发包的示意图。在设备上传输数据是通过dev_queue_xmit
执行的,它会调用设备驱动函数hard_start_xmit
进行发包,中间有一些查找发包的过程。dev_queue_xmit
可以通过eth2接口进行发送,也可以调用br0进行发送,后者绑定了两个接口eth0和eth1。同理收包。下面将会具体进行介绍。
下图是主要的结构图。其中:
net_bridge_fdb_entry
为转发数据库的记录项,每个mac learning后的地址都会有个这样的记录。net_device
表示网络设备,比如bridge, router等等net_bridge
表示网桥net_bridge_port
表示网桥端口
左侧为网桥结构,port_list
指示网桥的端口list,每个list
为net_bridge_port
结构体,其dev
指向该端口所绑定的设备,br指向端口所属的网桥。网桥结构内含一个哈希表,存储学到的转发表项net_bridge_fdb_entry
,其dst指向网桥端口,age_list
表示老化时间。
网桥和网桥端口的创建与删除
网桥设备的建立和删除分别通过br_add_bridge
和br_del_bridge
,网桥端口的添加和删除是br_add_if
和br_del_if
。注意以上四个函数运行加Netlink routing
锁。br_add_bridge
新建网桥主要调用new_bridge_dev
完成创建:
- 分配一个
net_device
数据结构并初始化。 - 初始化私有数据结构。
- 初始化网桥优先权为默认值,32768(0x8000)。
- 初始化指定网桥ID,根路径开销以及根端口。
- 初始化老化时间,默认5min。
- 用
br_stp_timer_init
函数初始化每个网桥的定时器。
注意,网桥端口和NIC是一一对应的,以下显示了br_add_if
主要流程,图中dev_set_promiscuity
为设置混杂模式,该模式下可以捕获所有LAN网络数据,即使不是发给本机,而且当网桥转发帧时,也需要进入混杂模式。
转发数据库
该数据库嵌入在net_bridge
数据结构中,并被定义成一个hash表,对于网桥的任何端口上所学到的每个mac地址,会为其在该数据库中插入一个net_bridge_fdb_entry
数据结构实例。
转发数据库中得记录是通过mac地址来标识的。查询这个表包括用br_mac_hash
选出正确的hash表bucket,以及浏览bucket中得net_bridge_fdb_entry
实例列表,找出第一个和指定mac地址相匹配的数据项。
由于br_fdb_get
查询转发数据库可能把结果缓存起来,所以每个数据项都会指定一个引用计数。当查询成功时,br_fdb_get
会将其增1。当调用函数不再需要引用查询结果时,就应当用br_fdb_put
将其减1。当计数为0时,br_fdb_put
将会释放net_bridge_fdb_entry
。
在创建一个网桥端口时,br_add_if
通过br_fdb_insert
函数把被绑定的设备的mac地址加入到转发数据库。对于可以添加到转发数据库的数据项的数目目前没有限制,目前,这可能会让系统遭受DOS攻击。
收包主要过程
- 包从入口进入到真实设备到bridge,触发
netif_receive_skb
。若内核不支持桥接,handle_bridge
将被设为NULL,并且netif_receive_skb
会把入口帧交给上层协议去处理。若内核支持桥接,当bridge收到包,将会调用handle_bridge
进行入口帧处理。 handle_bridge
调用br_handle_frame_hook
处理该帧,当初始化桥接模块时,该函数指针指向br_handle_frame
。br_handle_frame
判断端口状态及STP状态,更新转发数据库,进行mac learning。注意,该函数跨越了NETFILTER中第一个钩子函数NF_BR_PRE_ROUTING
。如下图所示:
br_handle_frame
调用br_handle_frame_finish
判断报文类型:单播,广播,组播,并查转发表,以及决定是否交给本地协议栈,若是,调用br_pass_frame_up
。如下图所示:
br_pass_frame_up
越过另外一个NETFILTER钩子函数:NF_BR_LOCAL_IN
,然后调用br_pass_frame_up_finish
。br_pass_frame_up_finish
中,skb->dev
的值会被端口所值的网络设备替换,并且再次调用netif_receive_skb
。此时,handle_bridge
看到的设备已经不是bridge,因此会把该帧交给正确的协议处理函数。
参考
《深入理解Linux网络技术内幕》