Linux Bridge - Part 2

概览

在前一篇文章中,我描述了Linux 网桥(bridge)的配置,并展示了一个实验,其中使用Wireshark来分析流量。在本文中,我将讨论当创建一个网桥时会发生什么,以及Linux 网桥(bridge)的工作原理。

与网桥(bridge)相关的源代码可以在这里找到。

网桥设备

摘自《深入理解LINUX网络内幕》:

在Linux中,网桥(bridge)是一个虚拟设备。因此,除非你将其与一个或多个实际设备绑定,否则它无法接收或传输任何数据。

阅读上一篇文章后,有人问我,既然网桥(bridge)是一个第2层设备,为什么我们在运行ifconfig br0时会看到一个IP地址与之相关联呢?答案是,Linux的实现将网桥(bridge)和路由器(router)的功能结合在了一起。我们知道,网桥(bridge)有一个上行链路,它可以连接到路由器。而在Linux网桥(bridge)中,这种连接被内置于内核内部。这个路由器实际上就是网桥(bridge)所绑定的实际设备。我们稍后会详细讨论这部分内容。为了理解网桥(bridge),我们可以简单地将这个IP地址视为网桥(bridge)的默认网关。如果我们想要一个私有网桥(bridge)的话,这个IP地址并不是必需的。

注:在Linux网络模型中,网桥(bridge)虽然本质上是二层设备,但其设计允许它拥有IP地址并执行层三的功能,主要是因为Linux将网桥(bridge)与路由器的部分功能进行了融合。这样做的目的是为了提供更加灵活的网络配置能力。然而,对于纯粹的层二桥接需求,这个IP地址可以忽略,因为桥接器的主要任务是在同一广播域内的设备之间转发数据包。

网桥数据结构

net_bridge 结构体的定义可以在这里找到。

下面仅列出了一些重要的字段:

struct net_bridge
{
    spinlock_t          lock;
    struct list_head    port_list;
    struct net_device   *dev;

    spinlock_t          hash_lock;
    struct hlist_head   hash[BR_HASH_SIZE];
    bridge_id           bridge_id;
    ...
}

port_list 是桥接器所拥有的端口列表。每个桥接器最多可以拥有 BR_MAX_PORTS(1024)个端口。dev 是指向表示桥接设备的 net_device 结构的指针。hash 是一个具有 BR_HASH_SIZE(256)个条目的转发哈希表。以便于快速查找和转发数据包。

创建网桥设备

可以通过命令 brctl addbr br0 来创建一个网桥。最终,这会调用带有请求 SIOCBRADDBRioctl 函数。

通过strace跟踪创建网桥的系统调用:

# strace brctl addbr br0
execve("/sbin/brctl", ["brctl", "addbr", "br0"], [/* 17 vars */]) = 0
...
ioctl(3, SIOCBRADDBR, "br0")            = 0
...

如果我们搜索 SIOCBRADDBR 在源代码中的实现,我们会发现它是由 br_ioctl_deviceless_stub 函数处理的,同时处理的还有另外三个请求 - SIOCGIFBR, SIOCSIFBRSIOCBRDELBR

int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd, void __user
*uarg) {
    switch (cmd) {
    case SIOCGIFBR:
    case SIOCSIFBR:
        ...

    case SIOCBRADDBR:
    case SIOCBRDELBR:
        ...
    }
    ...
}

该函数在初始化函数 br_init 中进行注册。

int __init br_init(void) {
...
brioctl_set(br_ioctl_deviceless_stub);
...
}

br_ioctl_deviceless_stub 调用 br_add_bridge,后者分配一个表示网桥的 net_device 结构,并使用 register_netdev 将此设备添加到内核的接口列表中。

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);
    ...
    res = register_netdev(dev);
    ...
    return res;
}

alloc_netdev 被定义为一个宏,它是 alloc_netdev_mqs 的简化版本,而后者实际上是一种通用方法,用于分配 net_device 结构体,并非特指网桥。网桥作为私有数据存储在 net_device 结构中。(私有数据是指附加在 net_device 结构后的内存段。)alloc_netdev_mqs 所接受的回调函数用于设置这部分私有数据。在网桥的情况下,这个回调函数是 br_dev_setup

struct net_device *alloc_netdev_mqs(..., void (*setup)(struct net_device *), ...) {
    struct net_device *dev;
    size_t alloc_size;
    ...
    alloc_size = sizeof(struct net_device);
    if (sizeof_priv) {
        /* ensure 32-byte alignment of private area */
        alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
        alloc_size += sizeof_priv;
    }
    /* ensure 32-byte alignment of whole construct */
    alloc_size += NETDEV_ALIGN - 1;
    p = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT);
    ...
    dev = PTR_ALIGN(p, NETDEV_ALIGN);
    ...
    setup(dev);
    ...
    return dev
}

br_dev_setup 设置 net_device 的私有数据区域。它将这一段内存转换为 net_bridge 类型,并初始化每一个字段。

void br_dev_setup(struct net_device *dev) {
    struct net_bridge *br = netdev_priv(dev);
    ...
    dev->priv_flags = IFF_EBRIDGE;
    br->dev = dev;
    spin_lock_init(&br->lock);
    INIT_LIST_HEAD(&br->port_list);
    spin_lock_init(&br->hash_lock);

    br->bridge_id.prio[0] = 0x80;
    br->bridge_id.prio[1] = 0x00;
    ...
}

总结一下,创建一个网桥的调用栈如下:

ioctl(3, SIOCBRADDBR, "br0")
 |- br_ioctl_deviceless_stub
     |- br_add_bridge
         |- alloc_netdev
             |- br_dev_setup
         |- register_netdev

经过这一步骤后,我们就有了一个 Linux 网桥设备。但是,此时还没有任何接口绑定到它上面,这意味着网桥还不能传输或接收任何数据。

添加接口

创建网桥之后,我们可以向它添加接口(端口)。尽管《深入理解LINUX网络内幕》一书中提到网桥必须绑定到一个“真实设备”,我认为这句话只对了一半。确实,一个网桥设备必须绑定到一个网络接口。然而,这并不一定意味着它必须绑定到一个“真实设备”,也就是说,它不必是实际物理网卡的接口。即使是 tap 接口,也可以绑定到网桥上使其正常工作。

用于绑定接口的命令是 brctl addif br0 tap0。这也被称为“从属(enslave)”,即我们让 tap0 成为 br0 的从属接口。

使用strace跟踪网桥添加端口的系统调用:

# strace brctl addif br0 tap0
execve("/sbin/brctl", ["brctl", "addif", "br0", "tap0"], [/* 17 vars */]) = 0
brk(NULL)                               = 0xf28000
...
ioctl(4, SIOCGIFINDEX, {ifr_name="tap0", }) = 0
close(4)                                = 0
ioctl(3, SIOCBRADDIF)                   = 0

它发出两个 ioctl 请求 - SIOCGIFINDEXSIOCBRADDIF。第一个用于查询 tap0 接口的索引,第二个用于将 tap0 接口添加到网桥。我们只关注第二个。

SIOCBRADDIFbr_dev_ioctl 函数处理。

int br_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) {
    struct net_bridge *br = netdev_priv(dev);

    switch (cmd) {
    ...
    case SIOCBRADDIF:
    case SIOCBRDELIF:
        return add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF);
    }
    ...
}

add_del_ifbr_add_ifbr_del_if 的包装器。在添加接口的情况下,会调用 br_add_if。以下是 br_add_if 中的一些重要步骤。

int br_add_if(struct net_bridge *br, struct net_device *dev) {
    struct net_bridge_port *p;
    ... (Validation)
    p = new_nbp(br, dev);
    ...
    err = netdev_rx_handler_register(dev, br_handle_frame, p);
    ...
    err = netdev_master_upper_dev_link(dev, br->dev);
    ...
    list_add_rcu(&p->list, &br->port_list);
    nbp_update_port_count(br);
    ...
    if (br_fdb_insert(br, p, dev->dev_addr, 0))
        netdev_err(dev, "failed insert local address bridge forwarding table\n");
    ...
    return err;
}

基本上,br_add_if 执行以下操作:

  1. 进行一系列验证,确保该设备可以被绑定到网桥下。部分规则包括: a) 不允许类似非以太网的设备;

    b) 已经被绑定的设备不允许再次绑定;

    c) 设备本身不能是网桥;

    d) 设备中不应存在 IFF_DONT_BRIDGE 标志;等等。

  2. 分配并初始化一个 net_bridge_port 结构体。稍后,该端口会被添加到网桥的 port_list 中。

  3. 为设备注册一个接收处理函数 br_handle_frame。发送到该设备的帧将由这个函数处理。我们后续会看到这个处理器具体做什么。

  4. 绑定设备,即让网桥成为这个设备的主控者。

  5. 将该设备的以太网地址作为本地条目添加到转发表中。

值得注意的是,在旧版本中,br_add_if 明确地将设备置于混杂模式。在 4.0 版本的内核中,这一点由 nbp_update_port_count 函数处理。

下图来自“Linux 网桥剖析”,展示了上述提及的函数之间的关系。

转发数据库

转发数据库存储了MAC地址与端口的映射关系。实现上,它使用了一个大小为 BR_HASH_SIZE(256)的哈希表(实际上是一个数组)作为转发数据库。数组中的每一项存储了一个单向链表的头指针(一个桶),该链表存储了所有哈希值落入该桶的MAC地址条目。参考上述 net_bridge 结构体中的 struct hlist_head hash[BR_HASH_SIZE]。MAC地址的哈希值由 br_mac_hash 计算得出。关于哈希算法的细节,我将略过不提。

表项结构

net_bridge_fdb_entry 是上述提到的链表的元素类型。

struct net_bridge_fdb_entry
{
    struct hlist_node       hlist;
    struct net_bridge_port  *dst;

    struct rcu_head         rcu;
    unsigned long           updated;
    unsigned long           used;
    mac_addr                addr;
    unsigned char           is_local:1,
                            is_static:1,
                            added_by_user:1,
                            added_by_external_learn:1;
    __u16                   vlan_id;
};

查找

由于实现方式采用了哈希表,查找过程与任何哈希表的查找相同。当网桥需要确定特定MAC地址的数据帧应转发到哪个端口时,它会在转发数据库中查找。因此,哈希表的键就是MAC地址。首先,它通过 br_mac_hash 获取MAC地址的哈希值,然后从该表项获取链表。接下来,它遍历整个链表,将MAC地址与每个元素进行比较,直到找到匹配的那一项。

更新条目

添加、更新和删除条目的操作都是典型的哈希表操作。我不会重复介绍它们是如何工作的,而是专注于这些操作何时发生。

当一个接口被添加到网桥上(即调用 br_add_if 时),会调用 br_fdb_insert 将被绑定设备的MAC地址插入到转发数据库中。 当本地端口上的设备更改其MAC地址(例如,通过命令 ifconfig eth0 hw ether 11:22:33:44:55:66),转发表中的条目会被更新。 当条目过期时,会删除该条目。通常情况下,如果一段时间内未使用某个条目,则该条目会过期。默认过期时间为5分钟,但这可以配置。定期调用 br_fdb_cleanup 来清理已过期的条目。

帧处理

入站数据首先由 netif_receive_skb 处理,这是一个通用的、与设备无关的函数。它会调用接收数据所在设备的 rx_handler。还记得在 br_add_if 中,我们为被绑定的设备注册了一个接收处理器 br_handle_frame 吗?在这里,这个处理器将被调用来处理设备上收到的数据。

br_handle_frame 首先进行一些基本检查,以确保这个帧是一个有效的以太网帧。然后它检查目的地是否为一个预留地址,这意味着这是一个控制帧。如果是的话,需要进行特殊处理。否则,它会调用 br_handle_frame_finish 来处理这个帧。

rx_handler_result_t br_handle_frame(struct sk_buff **pskb) {
    const unsigned char *dest = eth_hdr(skb)->h_dest;
    ... (Validation code)
    if (unlikely(is_link_local_ether_addr(dest))) {
        /*
         * See IEEE 802.1D Table 7-10 Reserved addresses
         *
         * Assignment               Value
         * Bridge Group Address     01-80-C2-00-00-00
         * (MAC Control) 802.3      01-80-C2-00-00-01
         * (Link Aggregation) 802.3 01-80-C2-00-00-02
         * 802.1X PAE address       01-80-C2-00-00-03
         *
         * 802.1AB LLDP         01-80-C2-00-00-0E
         *
         * Others reserved for future standardization
         */
        ... (Special processing for control frame)
    }

    ...
    NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
            br_handle_frame_finish);
    ...
}

br_handle_frame_finish 执行以下步骤:

  1. 学习源MAC地址并更新转发数据库。

  2. 如果目标地址是一个多播地址,那么调用 br_multicast_rcv 进行一些处理。

  3. 如何转发这个帧的决策取决于目标地址是广播地址、多播地址还是单播地址。规则如下:

注:如果接口处于混杂模式,那么无论目标地址是什么,这个帧都将被本地传递。而且,不一定需要开启混杂模式,因为在任何情况下,这个网桥处理器都会转发它。

下图来自“Linux 网桥剖析”,可能也有助于理解帧的处理过程。

结论

在这篇文章中,我们探讨了Linux网桥的实现方式,以及当我们配置网桥时所发生的事件,还有网桥如何处理帧的过程。这里有一个实验你可以尝试。首先,添加一个网桥。然后启动两个连接到这个网桥的虚拟机。通过命令 brctl showmacs br0 查看网桥的MAC地址表。看看两台虚拟机之间是否可以互相ping通。你还可以使用 tcpdump 或者 Wireshark 来捕获流量。回想一下每一步操作时发生了什么。

参考文献

[1] Benvenuti, Christian. 《深入理解LINUX网络内幕》. “O’Reilly Media, Inc.”, 2006.

[2] Linux网桥的剖析

[3] Linux网桥 - 其工作原理

Ref

https://hechao.li/2018/01/31/linux-bridge-part2/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/782341.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

给您介绍工控CAN总线

CAN是什么 CAN,全称Controller Area Network,即控制器局域网,是一种由Bosch公司在1983年开发的通信协议。它主要用于汽车和工业环境中的电子设备之间的通信。CAN协议定义了物理层和数据链路层的通信机制,使得不同的设备能够通过CA…

数据驱动的内容优化:Kompas.ai如何提升内容表现

在数字化营销时代,内容是企业与用户沟通的重要桥梁。然而,随着信息量的爆炸性增长,如何让内容在激烈的竞争中脱颖而出,成为每个营销人员面临的问题。数据驱动的内容优化策略,通过精准分析和科学决策,帮助品…

基于Java+SpringMvc+Vue技术的实验室管理系统设计与实现

博主介绍:硕士研究生,专注于信息化技术领域开发与管理,会使用java、标准c/c等开发语言,以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年,拥有近12年的管理工作经验,拥有较丰富的技术架…

基于Transformer的端到端的目标检测 | 读论文

本文正在参加 人工智能创作者扶持计划 提及到计算机视觉的目标检测,我们一般会最先想到卷积神经网络(CNN),因为这算是目标检测领域的开山之作了,在很长的一段时间里人们都折服于卷积神经网络在图像处理领域的优势&…

SQLite 嵌入式数据库

目录: 一、SQLite 简介二、SQLite 数据库安装1、安装方式一:2、安装方式二: 三、SQLite 的命令用法1、创建、打开、退出数据库:2、编辑数据库: 四、SQLite 的编程操作1、打开 / 创建数据库的 C 接口:2、操作…

欧拉函数.

性质1:质数n的欧拉函数为n-1. 性质2:如果p,q都是质数,那么ϕ ( p ∗ q ) ϕ ( p ) ∗ ϕ ( q ) ( p − 1 ) ∗ ( q − 1 ) 证明:p,2p....q*p都不与q*p互质,q同理,所以总的不互质个…

WPS+Python爬取百度之星排名

运行效果 手动拉取 https://www.matiji.net/exam/contest/contestdetail/146 如果手动查找,那么只能通过翻页的方式,每页10行(外加一行自己)。 爬取效果预览 本脚本爬取了个人排名和高校排名,可以借助WPS或MS Offi…

专业140+总分420+天津大学815信号与系统考研经验天大电子信息与通信工程,真题,大纲,参考书。

顺利上岸天津大学,专业课815信号与系统140,总分420,总结一些自己的复习经历,希望对于报考天大的同学有些许帮助,少走弯路,顺利上岸。专业课: 815信号与系统:指定教材吴大正&#xf…

缺失行处理(R和python)

R(complete.cases) rm(listls()) # 创建一个包含缺失值的数据框 # df <- data.frame( # x c(1, 2, NA, 4), # y c(NA, 2, 3, 4), # z c(1, NA, 3, 3) # ) # # # 使用complete.cases函数筛选包含缺失值的数据行 # missing_rows <- !complete.cases(df) # # # …

Vue2前端实现数据可视化大屏全局自适应 Vue实现所有页面自适应 Vue实现自适应所有屏幕

Vue自适应所有屏幕大小,目前页面自适应,尤其是数据可视化大屏的自适应更是案例很多 今天就记录一下使用Vue全局自适应各种屏幕大小的功能 在Vue.js中创建一个数据大屏,并使其能够自适应不同屏幕大小,通常涉及到布局的响应式设计、CSS媒体查询、以及利用Vue的事件系统来处理…

C++面向对象的常见面试题目(一)

1. 面向对象的三大特征 &#xff08;1&#xff09;封装&#xff1a;隐藏对象的内部状态&#xff0c;只暴露必要的接口。 #include <iostream> #include <string>// 定义一个简单的类 Person class Person { private: // 私有成员&#xff0c;外部不可直接访问std…

通俗易懂的信道复用技术详解:频分、时分、波分与码分复用

在现代通信网络中&#xff0c;信道复用技术 扮演着至关重要的角色。今天&#xff0c;我们将用通俗易懂的语言来讲解几种常见的信道复用技术&#xff1a;频分复用、时分复用、波分复用 和 码分复用。这篇文章特别适合基础小白&#xff0c;希望能帮助你快速理解这些概念。 一、频…

Bean的管理

1.主动获取Bean spring项目在需要时&#xff0c;会自动从IOC容器中获取需要的Bean 我们也可以自己主动的得到Bean对象 &#xff08;1&#xff09;获取bean对象&#xff0c;首先获取SpringIOC对象 private ApplicationContext applicationContext //IOC容器对象 (2 )方法…

[算法] 优先算法(四):滑动窗口(下)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

Springboot 敏感词过滤

参考&#xff1a;网站是怎么屏蔽脏话的呢&#xff1a;简单学会SpringBoot项目敏感词、违规词过滤方案_springboot 项目关键词过滤-CSDN博客 【敏感词过滤】_wx60d2a462203aa的技术博客_51CTO博客 1、添加依赖 <dependency><groupId>com.github.houbb</groupI…

模型训练之数据集

我们知道人工智能的四大要素&#xff1a;数据、算法、算力、场景。我们训练模型离不开数据 目标 一、数据集划分 定义 数据集&#xff1a;训练集是一组训练数据。 样本&#xff1a;一组数据中一个数据 特征&#xff1a;反映样本在某方面的表现、属性或性质事项 训练集&#…

输入Rviz打不开,显示could not contact Ros master at[..],retrying

直接输入rviz会报错无法打开 解决方法&#xff1a; 先输入roscore&#xff0c;再用ctrlaltt打开新终端&#xff0c;在新终端输入rviz/rosrun rviz rviz即可

深度学习3 基于规则的决策树模型

1.决策树是一种归纳学习算法&#xff0c;从一些没有规则、没有顺序、杂乱无章的数据中&#xff0c;推理出决 策模型。不管是什么算法的决策树&#xff0c;都是一种对实例进行分类的树形结构。决策树有三个要素&#xff1a;节点(Node)、分支(Branches)和结果(Leaf)。 训练决策树…

二、Spring

二、Spring 1、Spring简介 1.1、Spring概述 官网地址&#xff1a;https://spring.io/ Spring 是最受欢迎的企业级 Java 应用程序开发框架&#xff0c;数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。 Spring 框架是一个开源的 Jav…

VMware Workstation Pro 17.5.2 + license key

Workstation Pro是专为Windows操作系统设计的功能强大的虚拟化软件平台,它允许用户在其计算机上创建和运行虚拟机,这使他们能够同时与多个操作系统、应用程序和开发环境一起工作。 Workstation Pro的主要特点之一是其易用性,程序提供了直观的界面,允许用户轻松创建、配置和…