深入立即Linux网络技术内幕学习笔记第十六章:桥接:Linux实现
网桥设备抽象:
对Linux而言,网桥是虚拟设备,要想传输或接收数据,需要将真实设备绑定到虚拟网桥上。
上图中,有几点需要注意:
LAN1和LAN2通过网桥连接在一起,子网都是一样的。
网桥连接到路由器上,这样LAN1,LAN2,LAN3可以通信。
从路由器角度看,在eth0上只有一个LAN。
因为Linux实现了桥接和路由,我们可以将这两种设备合并到一个Linux系统中。如图16-2a,网桥和路由器的网络连接是内核的事。
现在,内核需要处理下面两个问题:
在路由器层次上,虽然有三个接口(eth0,eth1,eth2),但内核只能看见两个子网。
内核只会在eth0和eth1间使用桥接,且认为这两个接口配置在相同的ip子网内。
当创建一个虚拟的网桥时,必须告诉内核它要绑定到哪些接口上。以上面的例子来说,我们需要建立一个虚拟网桥,假设名为br0,然后将eth0,eth1绑定到br0。由于eth0,eth1都是网桥接口,不需要配置ip。我们可以把路由器和网桥间的连接所具有的ip信息指派给网桥设备,结果如图16-2b。
如图16-4,可以给绑定到br0的eth0配置ip信息。这样,eth0可以接收要传给br0网桥的信息,也可以接收传给br0本身的信息。
默认情况下,被绑定的设备所接收的数据会分配给它绑定的网桥设备,但是,一个入口帧到底是要进行桥接还是路由(也就是交给br0还是eth0),可以通过ebtables配置(参见数据帧和BPDU一节)。
重要的数据结构:
mac_addr;//MAC地址
bridge_id;//网桥id
net_bridge_fdb_entry;//转发数据库的记录项
net_bridge_port;//网桥端口
net_bridge;//表示网桥。该结构会附加到net_device数据结构上。
br_config_bpdu;//入口配置BPDU的一些关键字段会复制到该结构中
上述数据结构都定义在net/bridge/br_private.h中,但br_config_bpdu定义在net/bridge/br_private_stp.h中
注意上图中,age_list已不再使用。
桥接程序的初始化:
桥接程序既可以集成在内核中,也可以编译成独立模块。初始化函数br_init和清理函数br_uninit定义在net/bridge/br.c。
初始化工作包括:
转发数据库初始化。
初始化函数指针br_ioctl_hook为处理ioctl命令的函数。
初始化函数指针br_handle_frame_hook,使其指向处理入口BPDU的函数。
用netdev_chain通知链注册一个回调函数。
若内核编译为支持Bridging-Firewalling时,br_netfilter_init就会在此时初始化Bridging-Firewalling。
清理函数所做的工作正好相反。
建立网桥设备和网桥端口函数:
网桥的建立和删除通过函数br_add_bridge和br_del_bridge进行。
端口的添加和删除通过函数br_add_if函数和br_del_if进行。
这四个函数定义在net/bridge/br_if.c
网桥设备建立:
网桥设备的建立和注册遵循第八章讲述的模型,区别在于,网桥需要在其私有区域内(即图16-6所展示的net_bridge结构)做一些额外的初始化。这个任务由new_bridge_dev函数完成:
分配一个net_device数据结构并初始化。
初始化私有数据结构net_bridge。
初始化网桥id,根路径开销(设为0),根端口(设为0,即没有根端口)。这样设置是因为网桥首次启动,会认为自己是根网桥。
设置老化时间,即转发数据中的记录项的有效时间。
用br_stp_timer_init初始化每个网桥定时器。
无论该网桥是否启动STP,都会对生成树的参数初始化。
网桥设备使用br_dev_setup函数对net_device结构中的一些通用字段初始化。
void br_dev_setup(struct net_device *dev)
{
memset(dev->dev_addr,0,ETH_ALEN);//网桥mac地址会被清除掉。因为这个地址将由br_stp_recaculate_bridge_id函数从其绑定的设备上配置的mac获取。基于通用的理由,set_mac_addr置为NULL。
dev->tx_queue_len = 0;//网桥设备默认没有实现队列机制,而是让被绑定的设备实现。管理员可以通过ipconfig或ip link来配置tx_queue_len。
dev->change_mtu = br_change_mtu;//网桥设备的MTU改变时,要确保新的mtu不会大于被绑定的设备中最小的mtu。这一点由br_change_mtu来确保。
dev->set_mac_addr = NULL;
dev->priv_flags = IFF_EBRIDGE;//必要时设置,使内核可以区分网桥设备和其他类型的设备。
dev->do_ioctl = br_dev_ioctl;//网桥上发出的ioctl命令由br_dev_ioctl函数处理。
dev->hard_start_xmit = br_dev_xmit;
dev->stop = br_dev_close;
dev->open = br_dev_open;
}
删除网桥设备:
删除网桥设备前要先将其关闭掉。删除时,br_del_bridge会调用del_br做如下工作:
删除网桥的全部端口。
用br_fdb_delete_by_port函数删除每个端口在转发数据库中所有相关的数据项。停止该端口的所有定时器,然后将 promiscurity计数器减一。
停止垃圾收集定时器br->gc_timer。
用br_sysfs_delbr函数删除/sys/class/net中的网桥目录
用unregister_netdevice函数在内核注销该设备。
给网桥添加端口:
在当前的桥接实现中,NIC和端口是一一对应的。(现在好像一个NIC可以虚拟出多个接口,都可以添加到网桥中??)
br_add_if函数可以给网桥添加端口,在添加之前会做一些检测,若下列任一条件满足则中断执行:
要添加的设备不是Etherner设备(或回环设备)。
若要添加端口的是网桥,则网桥端口必须指定到真实设备。(存疑??现在好像不是这样了)
该端口已经指派给一个设备了。(即dev->br_port不为NULL)。
上述检测通过后,就会:
给该网桥端口指派一个端口号
给该端口指派默认优先权
计算出端口id。
根据被绑定设的传输速率设置默认开销。传输速率的获取需要用到ethtool接口,且驱动程序支持ethtool接口。
指定BR_STATE_DISABLEED的初始状态。
将网桥端口连接到被绑定的设备和网桥设备。
该网桥相关的NIC会进入有dev_set_promiscurity函数设置为混杂模式。
删除网桥端口的过程就是把建立端口所做的事撤销掉。
启动和关闭网桥设备:
br_dev_open启动网桥的步骤如下:
br_feathers_recomputed将网桥设备的基本特征初始化为其绑定的设备所支持的功能的最小子集。
用netif_start_queue函数启动设备进行数据传输(见第十一章)。
用br_stp_enable_bridge启动网桥设备。
当网桥设备启动时,绑定到该设备的端口也会跟着启动。
启动和关闭网桥端口:
网桥端口用br_stp_enable和br_stp_disable_port来启动和关闭。
要启用网桥端口,必须满足下列所有条件:
被绑定的相关设备已经用管理手段启动。
被绑定的相关设备有载波检测。(参见第八章链路状态变更侦测)
相关的网桥设备已用管理手段启动。
若上述条件满足,端口会在下面这些情况满足:
当被关闭的网桥设备重新启动时,其所有关闭的端口就会启用。
当被绑定的设备检测到载波状态时,桥接程序会收到NETDEV_CHANGE通知信息。
当被关掉的绑定设备重新启动时,桥接程序会收到NETDEV_UP通知信息。
注意,网桥上没有载波检测,因为是虚拟设备。
当一个端口启动时,br_port_state_selection会对其初始化为指定状态。若该网桥没有运行STP,把端口指定为BR_STATE_FORWARDING。
改变网桥端口状态:
网桥端口状态不是atcive就是inactive,相关的状态是BR_STATE_FORWARDING。BR_STATE_BLOCKINGBR_STATE_BLOCKING可以立即被设置,但BR_STATE_FORWARDING需要经历listen,learning两个中间态。第十五章有介绍。相关函数是br_make_blocking和br_make_forwarding。
大蓝图:
下图是桥接程序用于处理入口帧和出口帧(包括数据帧和BPDU)的一些重要函数。
注意,无论是否开启STP,桥接程序都使用同一组核心程序。
转发数据库和查询:
用于转发数据库的所有函数都在net/bridge/br_fdb.c中。
该数据库嵌入在net_bridge数据结构中,并被定义成一个hash表。
查询转发数据库的函数有两个:fdb_find和_ _br_fdb_get。
外部子系统想查询转发数据,可以使用br_fdb_get,该函数是一个_ _br_fdb_get的包裹函数。但是,不能直接调用br_fdb_get。而是通过br_fdb_get_hook函数调用,这个函数在br_init中初始化为指向br_fdb_get的指针。
增加,更新及删除转发数据库的数据项:
br_add_if创建一个端口时,调用br_fdb_insert把被绑定设备的mac添加的转发数据库。
当端口相关联的本地设备改变其mac地址时,调用br_fdb_change_addr来改变转发数据库的记录项。
通过入口帧学习到的地址,调用br_fdb_update来添加到数据库,若该地址已经存在,需要更新相关的入口端口。
转发数据中的记录项由fdb_delete负责删除。该函数不会直接调用,而是通过br_fdb_cleanup和br_fdb_delete_by_port等这些包裹函数使用。
老化:
每个网桥实例都有一个垃圾收集定时器(gc_timer),定期扫描转发数据库以清理过期的数据项。
网桥实例初始化时,该定时器会在br_stp_timer_init中被初始化,之后br_stp_enable_bridge启动网桥,该定时器就会启动。
处理入口流量:
netif_receive_skb函数在把帧交给上层协议前,若内核支持桥接,会调用handle_bridge函数。当网桥端口上接收到帧,handle_bridge就会用br_handle_frame_hook(桥接模块初始化时,该hook指向br_handle_frame)处理该帧。
数据帧和BPDU:
只有处于BR_STATE_FORWARDING态的端口才可接受数据帧,而只要STP启动,任何启用的端口都可以接收BPDU。
关于图中ebtables的说明:
ebtables是一个架构,也能查看帧,提供一些Netfilter没有的额外功能。ebtables可以过滤和修改任何类型的帧。
就图中而言,涉及到了ebtables的两个功能:①定义规则,哪些流量走桥接,哪些流量走路由,也就是说,一块NIC既是网桥的一个端口,又分离L3地址。②可以修改mac地址,所以ebtables完成任务后检查目的mac地址。
入口数据帧的处理是br_handle_frame_finish完成的。
关于图16-13中入口帧传送至本地的原理见下图:
当该帧由NIC设备驱动程序收到时,skb->dev就被初始化为指向真实设备。然后。该帧经过网络堆栈到达br_pass_frame_up函数,再经过一个Netfilter钩子,然后调用br_pass_frame_up_finish。此时,skb->dev的值会被入口端口所属的网桥设备替换掉,并再次调用netif_receive_skb。这次,handle_bridge看到该设备不是被绑定的设备(根据br_port为NULL判断)。因此会把该帧交给正确的协议处理函数。
网桥设备上的传输:
网桥设备抽象层要求把一台网桥上的传输转变成一个或所有网桥端口上的传输。图16-11给出了相关的主要函数。网桥驱动(虚拟设备驱动)实现hard_start_xmit的函数是br_dev_xmit。
生成树协议STP:
第十五章了讲了STP如何运行,下面讨论如何处理入口BPDU,如何发出BPDU,如何处理定时器。
主要生成树函数:
br_become_root_bridge:把非根网桥变成根网桥。
br_is_root_bridge:判断网桥是不是根网桥。
br_should_become_designated_port:如果输入端口应获得指定角色,返回1.
br_designated_port_selection:遍历所有网桥端口,把应该成为指定角色的端口变成指定端口。
br_become_designated_port:把指定角色指派给一个端口。
br_is_designated_port:若输入端口是指定端口,返回1
br_is_designated_port_for_some_port:若给定网桥至少由一个端口是指定角色,返回1
br_supersedes_port_info:给定一个端口和该端口接收的输入配置BPDU,若这个BPDU比该端口所知的优先权向量更高级,返回1br_should_become_root_port:给定一个端口和当前根端口,比较优先级,若给定端口优先级更高,则返回1.
br_root_selection:给定一台网桥,选出根端口
br_configuration_update:给定一台网桥,测定根端口和指定端口,并返回它们的信息。
br_port_state_selection:给定一个网桥,该函数可以为每个端口选出正确的端口状态。
br_topology_change_detection:探测拓扑变化。
br_topology_change_acknowledge:发出一个设有TCA标识的BPDN作为对TCN的应该。
br_topology_change_acknowledged:停止定时器
br_record_config_information:给定一个网桥端口和一个输入配置BPDU,该函数会把BPDU的优先权向量记录再该端口的net_bridge_port数据结构中,然后重启消息生存期定时器。
br_record_config_timeout_values:该函数记录BPDU定时器配置信息。
br_get_port:给定一个网桥设备和一个端口号,该函数返回相关的net_bridge_port结构。
网桥ID和端口ID:
网桥id的mac地址部分和端口id的端口号部分由内核按下面的方式初始化:
网桥mac地址:从被绑定的设备上所配置的MAC地址中,选出最低的MAC地址。
端口号:从1到BR_MAX_PORTS的范围中第一个尚未被使用的号会被选中。
处理入口BPDU:
入口BPDU帧会传给br_stp_handle_bpdu。
传输BPDU:
配置更新和选择根网桥:
负责配置更新的函数是br_configuration_update,该函数会在下面的一些情况被调用:
调用br_configuration_update的函数 |
调用时机 |
br_received_config_bpdu |
在一个端口上收到一个更好的优先权向量的BPDU |
br_message_age_timer_expired |
端口知道的信息已经过期,该变化也能导致拓扑变化,从而改变根网桥 |
br_stp_disable_port |
网桥的一个端口关闭。若该端口是唯一能到达当前根网桥的,生成树就被割裂了,这会导致当前网桥所属的那部分选出新的根网桥。 |
br_stp_change_bridge_id |
网桥id的mac地址部分改变了,这种改变可能会改变根网桥 |
br_stp_set_bridge_priority |
网桥id的优先权部分已经被改变,这种改变可能会改变根网桥 |
br_stp_set_path_cost |
端口路径开销改变了 |
每次调用br_configuration_update后,总是接着调用br_port_state_selection。
定时器:
端口和网桥定时器可分别用br_stp_port_timer_init和br_stp_imer_init初始化。
STP网桥定时器处理函数:
定时器 |
处理函数 |
Hello |
br_hello_timer_expired |
Topology Change Notification |
br_tcn_timer_expired |
Topology Change |
br_topology_change_timer_expired |
STP端口定时器处理函数:
定时器 |
处理函数 |
Max Age |
br_message_age_timer_expired |
Forward Delay |
br_forward_delay_timer_expired |
Hold |
br_hold_timer_expired |
处理拓扑变化:
由上一章的拓扑变化一节知道哪些事件会导致拓扑变化,这些事件由下列函数进行探测:
br_make_blocking:当STP决定阻塞一个转发端口时会调用该函数。
br_forward_delay_timer_expired:处于BR_STATE_LEARNING状态的某个端口要转换到BR_STATE_FORDWARDING态时会调用该函数。
br_become_root_bridge:当非根网桥变成根网桥时调用。
br_received_tcn:收到TCN BPDU时调用。
netdevice 通知链:
由于虚拟网桥设备是定义在被绑定的真实设备之上的,所以当任一绑定设备状态改变时,网桥会收到通知(因为桥接程序初始化时会用netdevice通知链向内核注册br_device_event回调函数)
深入立即Linux网络技术内幕学习笔记第十六章:桥接:Linux实现相关推荐
- 深入理解Linux网络技术内幕学习笔记第十九章:因特网协议第四版(IPv4):Linux的原理和功能
本章主要介绍Linux支持IP的数据结构和基本活动,如入口IP包如何传递至IP接收函数,校验和如何验证,以及IP选项如何处理. 主要的IPv4数据结构: struct iphdr{ };//ip报头 ...
- [go学习笔记.第十六章.TCP编程] 3.项目-海量用户即时通讯系统-redis介入,用户登录,注册
1.实现功能-完成用户登录 在redis手动添加测试用户,并画出示意图以及说明注意事项(后续通过程序注册用户) 如:输入用户名和密码,如果在redis中存在并正确,则登录,否则退出系统,并给出相应提示 ...
- C++ Primer plus学习笔记-第十六章:string类和标准模板库
第十六章:string类和标准模板库 前言:这一章已经相当靠近全书的后面部分了:这一章我们会深入探讨一些技术上的细节,比如string的具体构造函数,比如适用于string类的几个函数,比如我们还会介 ...
- 深入理解linux网络技术内幕读书笔记(十)--帧的接收
Table of Contents 1 概述 1.1 帧接收的中断处理 2 设备的开启与关闭 3 队列 4 通知内核帧已接收:NAPI和netif_rx 4.1 NAPI简介 4.1.1 NAPI优点 ...
- 深入理解Linux网路技术内幕学习笔记第四章:通知链
第四章:通知链 内核很多子系统之间具有很强的依赖性,其中一个子系统侦测到的或者产生的事件,其他子系统可能都感兴趣,为了实现这种需求,Linux使用了通知链.通知链只在内核子系统之间使用. 通知链就是一 ...
- UNIX环境高级编程 学习笔记 第十六章 网络IPC:套接字
socket的设计目标之一:同样的接口既可以用于计算机间通信,也可以用于计算机内通信.socket接口可采用许多不同的网络协议进行通信,本章讨论限制在因特网事实上的通信标准:TCP/IP协议栈. 套接 ...
- Linux shell编程学习笔记-----第十六章
shell 脚本调试技术:trap命令 tee命令 调试钩子 和shell选项.前三者都需要修改shell脚本的源代码,后者不需要. trap 命令用于捕捉信号,shell脚本在执行时,会产生三个所 ...
- TCP/IP详解 卷1:协议 学习笔记 第十六章 BOOTP:引导程序协议
一个无盘系统在不知道自身IP地址情况下,进行系统引导时能通过RARP协议获取它的IP地址,使用RARP会有两个问题:(1)IP地址是返回的唯一结果:(2)RARP使用链路层广播,RARP请求不会被路由 ...
- 设计模式学习笔记(十六:桥接模式)
1.1概述 将抽象部分与它的实现部分分离,使他们都可以独立地变化.这就是桥接模式的定义. 抽象类或接口中可以定义若干个抽象方法,习惯上将抽象方法称作操作.抽象类或接口使程序的设计者忽略操作的细节,即不 ...
最新文章
- MySQL中的char和varchar类型
- 空间计量模型_Stata空间面板数据模型专题直播丨Stata空间计量3月远程直播
- c++ vector 一部分_C++ vector 使用注意事项
- 远程连接spark_spark内部原理篇之计算引擎和调度管理
- C#系列三《C#数据类型与变量三》
- docker port如何增加端口_docker部署redis实战
- java dumpstack_Java获取执行进程的dump文件及获取Java stack
- 【C++对象模型】第一章 关于对象
- oracle服务名连接慢,数据库 – Oracle SID和服务名称;连接问题
- Spring注解的使用和区别:@Component、@Service、@Repository、@Controller
- 【Python实例第20讲】手写数字识别问题的K-Means聚类
- python基础--集合
- plsql如何显示表结构图_如何用PLSQL导出数据库存表结构信息
- 如何高效率的使用Google搜索
- Linux查询IP失败
- 【美团】职级、薪酬、绩效全认知
- 四分树(UVa297紫书p160)
- 最新Chromedriver与Chrome版本对应参照表【附下载链接】
- ADOCE for ADO Programmers
- HBU训练营【动态规划DP】——兔子跳楼梯 (20分)
热门文章
- /bin/bash^M: bad interpreter 问题解决
- 新风系统风速推荐表_新风系统送风口风速怎么选择 新风系统送风口风速选择标准【详解】...
- R绘图实战|GSEA富集分析图
- 涡轮发动机图测试线2010开关测试机SW辊子输送机用抓手SW双头精雕机IGSFPC折弯设备、FPC软板折弯机SWZ4直流电机_三维图Z4-112-4-1_5.5KW_160-1抛光机三维图
- 程序员表白代码,爱心加玫瑰花
- 在ROS中兼容Python3和Python2
- 委内瑞拉通过加密货币使用法令
- 大学生体育运动网页设计模板代码 校园兵乓球网页作业成品 学校篮球网页制作模板 学生简单体育运动网站设计成品
- 基于ssm+mysql+jsp作业管理(在线学习)系统
- JAVA——链表的回文结构