简介:bridge-utils 1.0.4-rc3 源码包面向需要掌握 Linux 桥接管理的运维人员、虚拟化实践者与网络课程学习者,围绕 brctl 命令行工具解决网桥创建、端口纳管、STP 与 VLAN 配置等实际问题。压缩包仅 157KB,共 47 个文件,以 8 个 C 源文件和若干 Makefile.in、头文件为主体,配合 configure、install-sh 等构建脚本,以及 man 手册、FAQ、HOWTO、变更记录和 tests 目录下的压力与功能测试材料,规模轻量却覆盖从编译到验证的完整链路。已有 273 人学习或下载,说明其在桥接入门与实验环境中具有一定参考价值。读者可从中获得 brctl 及其 libbridge 库的实现脉络、命令行为说明和测试样例,便于对照源码理解 addbr、addif、show 等操作背后的桥接机制,也能为 KVM、Xen 等虚拟化场景中的网桥排错提供依据。
1. 一条命令看出二层不通:brctl还没到进博物馆的时候
排查过一次二层丢包的现场:三层地址、路由、防火墙规则全都翻过一遍,没有任何问题,最后brctl showstp br0一敲,两个挂载口的状态停在listening,STP 还在收敛,报文根本没机会转发。换作只看ip addr和ip route,这个信息是永远看不到的。bridge-utils-1.0.4-rc3.tar.gz就是这么一套源码包:体积不大,核心产物是brctl这个命令行工具和它依赖的libbridge静态库,负责创建、销毁 Linux 网桥,增删从接口,读写 STP 参数和 MAC 转发表。它在企业内网、嵌入式网关、虚拟化宿主机的二层组网里依然高频出现,尤其在只有最小根文件系统、没有 iproute2 的嵌入式环境里,brctl往往是唯一能配置网桥的东西。适合谁:要自己交叉编译网桥工具链的嵌入式工程师、需要读桥接内核行为的老系统维护者,以及想搞清楚bridge命令底层到底改了哪些 sysfs 节点的人。
2. 从源码包到可执行文件:configure 参数与工具链适配
拿到 tar.gz 之后第一件事不是解压,而是确认这台机器的内核到底支不支持网桥。brctl只是一个用户态外壳,真正干活的代码在内核的net/bridge目录里,用户态和内核态对不上,工具装得再干净也白搭。
2.1 包里到底装了什么
解压后大致能看到这么几层结构:顶层是 autoconf 生成的configure和Makefile.in;brctl/目录下是命令入口与子命令分发,brctl.c负责解析参数,brctl_cmd.c里是一个命令表,每条命令对应一个处理函数;libbridge/是真正和内核打交道的一层,封装了 ioctl 调用与 sysfs 读写;man/下是brctl.8手册页;doc/里偶尔带一点 STP 行为说明。整个包的大小在一两百 KB 量级,编译产物也只有几百 KB,塞进嵌入式根文件系统毫无压力。
关键点在于libbridge的接口方式。这一代版本走的是ioctl(SIOCBRADDBR)、SIOCBRADDIF、SIOCBRDELIF这类老的桥接 ioctl,同时辅以/sys/class/net/<bridge>/bridge/下的 sysfs 节点读取状态。所以它能不能跑,取决于三件事:内核编了CONFIG_BRIDGE、/sys已经挂载、调用进程有CAP_NET_ADMIN。这三条缺任何一条,brctl都会报出很含糊的错误,后面避坑章里会展开。
先做一次前置检查:
# 1. 确认内核配置里网桥是编进去还是模块 grep -E '^CONFIG_BRIDGE(=|_)' /boot/config-$(uname -r) 2>/dev/null || \ zcat /proc/config.gz 2>/dev/null | grep -E '^CONFIG_BRIDGE' # 2. 确认模块是否已加载,加载后 /sys/module/bridge 会存在 lsmod | grep -w bridge ls -d /sys/module/bridge 2>/dev/null # 3. 确认 sysfs 挂载点 mount | grep -w sysfs第一段命令是在找内核配置,CONFIG_BRIDGE=m说明是模块,需要modprobe bridge;CONFIG_BRIDGE=y说明编进内核,开机即用。第二段是运行期验证,/sys/module/bridge目录存在就代表模块已经在内核里了,注意模块可能被其他功能自动拉起,所以不要只看lsmod的输出。第三段容易被忽略——某些精简系统把 sysfs 挂在/sys之外甚至没挂,这种情况下brctl show会返回空表而不是报错,比报错更难查。
2.2 configure 的三个关键参数
源码包用的是标准 autoconf 流程,./configure --help能看到一长串选项。实际需要关心的只有三个:安装前缀、sbin 目录、man 目录。
tar -xzf bridge-utils-1.0.4-rc3.tar.gz cd bridge-utils-1.0.4-rc3 # --sbindir 决定 brctl 装到哪,必须是 root 默认 PATH 里的目录 ./configure \ --prefix=/usr \ --sbindir=/sbin \ --mandir=/usr/share/man make sudo make install--prefix=/usr是主机环境的常规选择;--sbindir=/sbin值得单独说一句——如果不指定,默认会装到${prefix}/sbin,也就是/usr/sbin,在一些老系统的sudo环境变量重置策略下会找不到命令,排查时会误以为是安装失败。--mandir只是决定手册页位置,嵌入式环境里可以直接砍掉,改成make install-sbin之类只装二进制。
编译完成后可以直接在源码目录里先跑一次,避免污染系统:
./brctl/brctl 2>&1 | head -5 # 正常输出应该是 Commands: 开头的用法列表这一步的意义在于区分“编译失败”和“安装失败”。老包在新工具链上翻车的情况不少,先确认二进制本身能起来,再去纠结安装路径。
2.3 新工具链上的编译适配
这个版本的源码年代较早,在 glibc 2.28 之后的环境里编译,常会碰到隐式函数声明告警甚至报错:major()/minor()/makedev()这几个宏在较新的头文件里已经被从<sys/types.h>挪到了<sys/sysmacros.h>。现象是编译输出里一串implicit declaration of function 'makedev',有的发行版默认把告警当错误就停在半路。
处理方式是打一个最小的补丁,在libbridge/libbridge_devif.c(或报错指向的那个文件)的 include 区补一行:
/* 旧源码在 glibc 2.28+ 上需要显式引入 sysmacros.h */ #include <sys/sysmacros.h>另一个常见问题是configure脚本本身太老,对某些新架构的host三元组识别不准,交叉编译时会退化成主机编译。判断依据很简单:make之后用file brctl/brctl看一眼产物架构,如果不是目标架构的 ELF,就说明交叉编译没生效,需要显式传--host=arm-linux-gnueabihf并确认CC环境变量指向交叉工具链。
交叉编译的完整命令大致长这样:
# 交叉编译示例,CC 必须显式指定,否则 configure 会退回主机 gcc CC=arm-linux-gnueabihf-gcc \ ./configure --host=arm-linux-gnueabihf --prefix=/usr --sbindir=/usr/sbin make file brctl/brctl # 必须显示 ARM 架构,否则说明 CC 没生效--host的作用是告诉 configure “我要为哪个平台构建”,它会据此切换编译器和链接器前缀;CC是兜底,防止 configure 内部的探测逻辑把编译器猜错。file那一行是硬性验收,别跳过,很多“交叉编译失败”最后都栽在没检查产物架构上。
3. 用brctl建第一个桥:内核开关、命令序列与参数表
工具编出来了,接下来是把它真正跑起来。Linux 网桥本质是一个虚拟的二层交换机:内核为它维护一张 MAC 转发表,收到帧时查表决定从哪个从接口发出,查不到就泛洪到除入端口外的所有从接口。brctl做的事情,就是创建这个虚拟设备、把物理或虚拟网卡挂上去、调整转发表和 STP 的行为参数。
3.1 内核侧的前置条件
在动手建桥之前,有几个开关必须先确认,否则命令会以非常隐晦的方式失败。
第一个是CONFIG_BRIDGE,前面已经查过了。第二个是CONFIG_BRIDGE_NETFILTER,它决定桥上的流量要不要走 netfilter 的 FORWARD 链。这个开关直接影响后面第 5 章里的一个经典翻车场景:桥建好了、口加上了、STP 也forwarding了,但对端就是不通,最后发现是桥上流量被 netfilter 的默认策略拦了。第三个是CONFIG_BRIDGE_VLAN_FILTERING,只有需要做桥内 VLAN 隔离时才用得上,这个版本的brctl对它的支持很有限,配 VLAN 过滤建议直接上 iproute2。
# 按需加载网桥模块,模块名固定为 bridge modprobe bridge # 检查 bridge netfilter 是否已加载,加载后有 br_netfilter 模块 lsmod | grep -E '^bridge|br_netfilter' # 查看内核是否把桥上流量交给了 netfilter sysctl net.bridge.bridge-nf-call-iptables 2>/dev/nullmodprobe bridge不会自动带上br_netfilter,两者是独立的模块。第三条sysctl只有在br_netfilter加载后才存在,返回空说明没加载,返回1说明桥上流量会被 netfilter 处理,这个值在第 5 章里会再见面。
还有一条权限问题:brctl的绝大多数子命令需要CAP_NET_ADMIN。在容器里跑这个命令,光有 root 不够,还得看容器的 capability 集,现象是addbr返回Operation not permitted,这里先记一笔。
3.2 建桥、加口、起接口的完整序列
下面这段命令是把两台物理口桥接成br0的最小可复现流程,顺序很重要,照着敲基本不会出问题。
# 1. 创建网桥设备,此时它还没有任何从接口 brctl addbr br0 # 2. 把两个物理口挂到桥上,addif 只加成员,不改接口自身的 up/down 状态 brctl addif br0 eth1 brctl addif br0 eth2 # 3. 把从接口和网桥本身都拉起来,这一步漏了后面全不通 ip link set eth1 up ip link set eth2 up ip link set br0 up # 4. 给网桥配三层地址(可选,纯二层转发时不需要) ip addr add 192.0.2.1/24 dev br0 # 5. 查看结果 brctl show第 2 步是最容易被误解的一步。brctl addif只负责把设备登记为网桥的从属口,它不会替你执行ip link set up。很多现场“加了口但不通”,就是因为从接口还处于DOWN状态——brctl show里能看到接口名字,但ip link里它是 down 的,两个视图对不上。
第 3 步的顺序建议是从接口先起来、网桥后起来。反过来的话,网桥 up 的瞬间从接口还是 down,STP 会先把它当成不可用端口,收敛要多等一个周期。
第 5 步的输出长这个样子:
bridge name bridge id STP enabled interfaces br0 8000.001122334455 no eth1 eth2bridge id由两部分拼成:前 4 位十六进制是网桥优先级,后 12 位是网桥的 MAC 地址。网桥的 MAC 默认取自第一个加入的从接口,所以这里显示的是eth1的地址,前面加8000表示优先级是默认的 32768。STP 默认关闭,这一栏显示no。
3.3 参数表与常用调优值
brctl的参数分两类:网桥级和端口级。下面这张表是实际运维里真正会动的几个,其余保持默认就好。
| 命令 | 作用对象 | 单位 | 默认值 | 什么时候改 |
|---|---|---|---|---|
setageing <br> <秒> | 网桥 | 秒 | 300 | MAC 表条目滞留过久、地址漂移导致泛洪 |
setfd <br> <秒> | 网桥 | 秒 | 15 | 缩短 STP 收敛时间,通常配合 hello/maxage 一起调 |
sethello <br> <秒> | 网桥 | 秒 | 2 | 链路频繁抖动时可适当加大,减少 BPDU 开销 |
setmaxage <br> <秒> | 网桥 | 秒 | 20 | 必须大于2×(hello+1),否则 STP 行为异常 |
setbridgeprio <br> <值> | 网桥 | 无 | 32768 | 多桥组网时指定根桥,值小的当根 |
setpathcost <br> <口> <值> | 端口 | 无 | 自动 | 手工干预 STP 选路,值小的优先 |
setportprio <br> <口> <值> | 端口 | 无 | 32 | 端口级选路微调,必须是 16 的倍数 |
setgcint <br> <秒> | 网桥 | 秒 | 无 | 调整转发数据库的垃圾回收周期 |
几个参数之间有硬约束,改之前先算一遍。setmaxage的值必须大于2 × (hello_time + 1),否则内核会拒绝写入或者让 STP 进入异常状态;setfd要和setmaxage配合,常规的收敛优化是把hello保持 2、maxage降到 6、fd降到 4,能显著缩短收敛窗口,代价是对链路抖动的容忍度下降。
# 打开 STP,多交换机环路场景必开,否则广播风暴分分钟打满 brctl stp br0 on # 缩短收敛窗口:hello 2s / maxage 6s / fd 4s / ageing 300s brctl sethello br0 2 brctl setmaxage br0 6 brctl setfd br0 4 brctl setageing br0 300 # 指定这台机器为根桥,优先级取最小值 brctl setbridgeprio br0 4096 # 回读确认,参数没写进去时这里会显示旧值 brctl showstp br0 | head -20最后那条回读命令是习惯问题,很多人改完参数就往下走了,结果实际值根本没生效——brctl有些参数在内核侧是有下限的,写一个超范围的值不会报错,但读回来还是旧值。showstp的输出里前 15 行左右是网桥级参数,bridge id、designated root、hello time、max age、forward delay、ageing time都在里面,逐项对一遍比什么都省事。
4. 虚拟化与容器场景的桥接组网:veth、tap 与转发链
物理口桥接是最简单的用法,真正让brctl保持生命力的场景是虚拟设备。容器和虚拟机都需要一个二层出口,而 Linux 网桥正好是那个“把虚拟网卡和物理网卡接在同一个广播域里”的粘合剂。
4.1 veth pair 与命名空间:容器网络的手工版
veth pair 是一对虚拟网卡,从一端发出去的帧会从另一端出来,行为完全对称。把其中一端塞进网络命名空间,另一端挂到网桥上,就等于给这个命名空间接了一根网线。容器运行时创建网络时,底层干的基本就是这件事。
# 1. 创建一对 veth,名字分别是 veth1 和 veth1-br ip link add veth1 type veth peer name veth1-br # 2. 把 veth1 移到命名空间 ns1 里(ns1 需预先创建) ip netns add ns1 ip link set veth1 netns ns1 # 3. 宿主侧这端挂到 br0 brctl addif br0 veth1-br ip link set veth1-br up # 4. 命名空间内部这端配置地址并拉起 ip netns exec ns1 ip link set veth1 up ip netns exec ns1 ip link set lo up ip netns exec ns1 ip addr add 192.0.2.10/24 dev veth1 # 5. 验证两个视图对得上 brctl show br0 ip netns exec ns1 ip link show veth1第 2 步的ip link set ... netns是把设备“搬走”而不是“复制”,执行完之后宿主侧的ip link里就看不到veth1了,第一次用很容易以为命令失败了。第 3 步挂到桥上之后,内核会自动给veth1-br设置混杂模式(或把它加入硬件过滤表),因为网桥需要接收目的 MAC 不是本机的帧。
第 4 步的lo那一行不是可有可无的,命名空间新建时回环口默认是 down 的,很多协议栈组件会因为 loopback 不可用而启动异常,排查时会往网络配置上跑偏。
第 5 步的核对是关键。brctl show br0里能看到veth1-br,说明成员关系对了;ip netns exec ns1 ip link show veth1里显示的state必须是UP,说明命名空间内那端起来了。两个视图有一个不对,二层就是不通,跟三层配置一点关系都没有。
4.2 tap 设备与虚拟机场景
虚拟机走的是 tap 设备,本质也是一个虚拟网口,只不过另一端接的是用户态的虚拟机进程而不是内核里的 veth peer。brctl在这个场景里承担的职责和上一节完全一样:把 tap 挂到桥上,让虚拟机看起来像是接在同一台交换机上。
# 1. 创建一个 tap 设备,mode tap 表示点对点、不带持久化 ip tuntap add dev tap0 mode tap # 2. 挂到网桥并拉起,顺序不能反 brctl addif br0 tap0 ip link set tap0 up # 3. 确认 tap 已在桥的从属列表里 brctl show br0mode tap和mode tun的区别值得记一下:tun 处理的是三层 IP 包,tap 处理的是二层以太帧。网桥只认二层,所以必须用 tap。如果误用了 tun,brctl addif会直接拒绝或者加进去之后完全不转发,现象是虚拟机发不出任何帧。
tap 设备的生命周期默认跟随打开它的进程,进程一退出设备就消失。如果需要在进程启动前先把 tap 建好并挂到桥上,可以加persist参数,但那属于另一个话题,这里点到为止。
4.3 让桥上的流量出得去:三层出口与转发
二层通了只是第一步,容器和虚拟机要访问外部网络,还得有网关和转发。
# 1. 给网桥配网关地址 ip addr add 192.0.2.1/24 dev br0 # 2. 打开 IPv4 转发,否则跨网段的包到了本机就被丢弃 sysctl -w net.ipv4.ip_forward=1 # 3. 持久化,重启后不丢 echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-bridge.conf sysctl -p /etc/sysctl.d/99-bridge.conf # 4. 命名空间内配默认路由指向网桥地址 ip netns exec ns1 ip route add default via 192.0.2.1第 2 步是硬性前提,net.ipv4.ip_forward为 0 时,即使路由表正确、防火墙全放行,包到了本机也会被静默丢弃,且不会有任何日志。这个问题在排查时属于典型的“黑匣子”级别,因为 ping 出去没回应,抓包却能看到请求到达。
第 4 步之后,如果命名空间里还是出不去,先别怀疑网桥,去检查宿主机的 netfilter FORWARD 链默认策略。很多发行版的默认策略是DROP,而桥接流量的处理路径又和普通三层转发不同,两件事叠在一起,现象就是“桥通、命名空间不通”。这个坑下一章展开讲。
5. 避坑与排查:五个让网桥不通的现场
下面这几条都是实际踩过、并且能稳定复现的。每条按“现象 → 原因 → 解决”写,遇到同类问题时按顺序对照就行。
5.1brctl addbr成功,但网桥立刻消失
现象:命令返回 0,没有任何报错,但紧接着brctl show里看不到新网桥,ip link show br0也报设备不存在。
原因:设备创建了但处于 down 状态,某些网络管理组件(系统的网络管理服务、容器运行时、云厂商的初始化脚本)会周期性地清理“无主的、未 up 的”网络设备。也可能是brctl走的是老 ioctl,而管理组件走的是 netlink,两边视图没有同步。
解决:创建之后立刻ip link set br0 up,不要留空窗期。排查时用ip monitor link盯着设备变化,如果看到br0出现之后几秒内又被删除,基本可以锁定是管理组件干的,要么调整它的策略,要么把网桥的创建放进它管理的配置里。
5.2brctl delbr报Device or resource busy
现象:想删掉一个网桥,delbr直接失败,内核日志里能看到can't delete bridge: device is in use之类的信息。
原因:网桥下面还有从接口没摘干净,或者网桥本身还是 up 状态。内核不允许删除一个还有成员或者还处于活动状态的网桥。
解决:先把成员逐个摘掉,再把网桥 down 掉,最后删。顺序写死,别凭感觉。
# 1. 先列出所有成员 brctl show br0 # 2. 逐个摘除,注意有些口已经因为设备消失而自动摘了,摘不到不用管 brctl delif br0 eth1 brctl delif br0 eth2 # 3. down 掉网桥再删 ip link set br0 down brctl delbr br0如果第 2 步报某个接口不存在,说明那个设备已经被移除了(比如命名空间被删),直接从列表里跳过即可,剩下的继续摘。全部摘干净之后第 3 步才会成功。
5.3 口挂上了,showstp永远停在listening
现象:brctl show里接口在,ip link里也是 UP,但brctl showstp br0里该端口状态一直是listening或blocking,流量完全不过。
原因:STP 收敛没完成。常见诱因有两个:一是从接口和网桥的 up 顺序不对,网桥先起、从接口后起,STP 会多做一轮状态机流转;二是两侧的 STP 参数不匹配,比如一边hello是 2 秒、另一边是 1 秒,BPDU 交互会被判成异常,端口长期停在非转发状态。
解决:先等一个完整的收敛周期(默认配置下 30 秒左右),状态还不变就去对参数。两侧的hello、maxage、fd三组值必须协商成一致,不一致时优先级高的那一方说了算,有时候表现为“一端以为通了、另一端还在 listening”。
# 网桥级参数回读 brctl showstp br0 | sed -n '1,20p' # 端口级状态,重点看 state 字段 brctl showstp br0 | grep -A6 'eth1'如果确认是纯二层直连、不存在环路,最干脆的做法是直接把 STP 关掉(brctl stp br0 off),省掉整个收敛过程。但这是有前提的:拓扑里不能有任何环路,否则广播风暴会在几秒内打满链路。
5.4 网桥像 hub 一样疯泛洪,showmacs学不到地址
现象:brctl showmacs br0输出只有本机地址和几条is local的条目,从接口的 MAC 一条都学不到,桥上流量全在泛洪。
原因:MAC 学习依赖入方向帧的源 MAC,如果流量根本没到网桥的转发路径,就无从学起。最常见的两种情况是:桥上流量被 netfilter 提前拦掉;或者从接口处于混杂模式但父设备被设了IFF_NO_ARP之类的特殊标志(某些虚拟设备会)。还有一种低概率情况,某个网卡在交换机侧配置了端口安全,源 MAC 被上游改写。
解决:先确认桥上流量是否真的进了转发路径。用sysctl net.bridge.bridge-nf-call-iptables看是不是 1,是 1 的话桥上流量会走 netfilter,如果 FORWARD 链有DROP规则,帧在到达转发逻辑前就没了。
# 临时关掉桥上流量的 netfilter 处理,验证问题是否出在这里 sysctl -w net.bridge.bridge-nf-call-iptables=0 sysctl -w net.bridge.bridge-nf-call-ip6tables=0 # 回看 MAC 表 brctl showmacs br0如果关掉之后地址立刻开始学习,问题就锁定了。不要长期这么关着——桥上流量不走 netfilter 等于放弃了这一层的访问控制,正确做法是调整 FORWARD 链规则,而不是绕过它。
5.5 参数写进去了,读回来还是旧值
现象:brctl setageing br0 60返回 0,但brctl show br0或者 sysfs 里读出来的还是 300。
原因:brctl的写操作走 ioctl,部分参数在内核侧有取值范围约束,超范围时内核直接忽略而不返回错误,用户态拿到的仍然是成功。这是这一代工具最让人抓狂的地方——静默失败,没有任何反馈。
解决:每次写完必须回读,养成条件反射。回读有两个口径,一个是brctl自己的输出,一个是 sysfs 节点的原始值,两者对不上时以 sysfs 为准。
# brctl 口径 brctl show br0 # sysfs 口径,ageing_time 单位是 1/100 秒,注意换算 cat /sys/class/net/br0/bridge/ageing_time cat /sys/class/net/br0/bridge/stp_state cat /sys/class/net/br0/bridge/forward_delaysysfs 里ageing_time的单位是百分之一秒,所以设了 60 秒读出来应该是6000,如果读到30000说明写入被忽略了。这个单位换算推翻过不少人,第一次见会以为是 bug。
6. 用 sysfs 做交叉验证:把showstp的读数读懂
brctl最大的短板是错误信息贫瘠,所以熟练之后我基本不再信任它的单一输出,而是拿 sysfs 做交叉对账。/sys/class/net/<bridge>/bridge/目录下是内核暴露的权威值,brctl读的其实也是这里,但它会做一层格式化,有些字段被吞掉了。
关键文件对照如下,排查时直接cat对应项比showstp的整段输出更快:
| sysfs 路径 | 含义 | 单位/取值 |
|---|---|---|
bridge/stp_state | STP 开关 | 0 关 / 1 开 |
bridge/forward_delay | 转发延迟 | 1/100 秒 |
bridge/hello_time | BPDU 间隔 | 1/100 秒 |
bridge/max_age | BPDU 最大老化 | 1/100 秒 |
bridge/ageing_time | MAC 表老化 | 1/100 秒 |
bridge/bridge_id | 网桥标识 | 优先级 + MAC |
bridge/root_id | 根桥标识 | 与 bridge_id 相同即为根桥 |
brif/<port>/state | 端口 STP 状态 | 0 禁用 1 监听 2 学习 3 转发 4 阻塞 |
brif/<port>/path_cost | 路径开销 | 整数 |
brif/<port>/priority | 端口优先级 | 整数 |
brif/<port>/state这个字段最有用,它直接把 STP 状态机暴露成数字。3是转发,其余都不过流量。1是监听、2是学习、4是阻塞,剩下的0是禁用——如果看到0,说明端口被显式关掉了,多半是管理组件干的,或者是 STP 判定这条链路有问题。
下面这段脚本是我常备的自查工具,一次把网桥、从接口、STP 状态全打出来,state不是3的直接标红:
#!/bin/bash # 网桥健康自查:遍历所有网桥及其从接口,输出 STP 状态与关键参数 for br in /sys/class/net/*/bridge; do brdev=$(basename $(dirname "$br")) stp=$(cat "$br/stp_state") fd=$(cat "$br/forward_delay") ageing=$(cat "$br/ageing_time") # forward_delay 与 ageing_time 单位均为 1/100 秒,除以 100 换算成秒 printf '== %s stp=%s fd=%ss ageing=%ss\n' \ "$brdev" "$stp" "$((fd/100))" "$((ageing/100))" for port in "$br"/brif/*; do [ -e "$port" ] || continue p=$(basename "$port") st=$(cat "$port/state") # 3 = forwarding,其余状态意味着该端口当前不转发数据帧 flag="" [ "$st" != "3" ] && flag=" <-- 非转发状态" printf ' port %-12s state=%s%s\n' "$p" "$st" "$flag" done done脚本本身没什么花活,价值在于把“非转发状态”这个判断固化成硬条件。人看showstp的输出容易疲劳,一长串listening、learning里混一个blocking,扫一眼根本看不出来;脚本直接比较数字,漏不掉。
两个细节需要提醒。第一,/sys/class/net/*/bridge这个 glob 只会匹配到网桥设备,普通网卡没有bridge子目录,所以不会误报。第二,brif/下的目录名可能因为接口名过长被截断,用basename取到的名字和ip link里看到的可能不完全一致,对不上时以brctl show的成员列表为准。
还有一个进阶用法值得单独提:把bridge/root_id和bridge/bridge_id做比对。两者相等说明本机就是根桥,不等说明拓扑里有别的桥优先级更高。在多台机器组网的场景里,这个比对能在几秒内定位“为什么流量绕远路”——如果某台本该当根桥的机器root_id指向了别人,说明它的优先级配高了,改setbridgeprio调小即可。
我自己的习惯是:任何一次桥接配置变更之后,不看brctl show的结论,而是先跑一遍上面这段脚本,再用brctl showmacs确认源地址学习正常,最后才去验证业务连通性。有过一次教训——参数改完brctl回显一切正常,实际上forward_delay被内核按最小值截断了,脚本里那行$((fd/100))一打出来才发现是 2 秒而不是预期的 4 秒,整个收敛窗口全被带偏。从那以后每次动桥接参数,我都强制走一遍“写 → sysfs 回读 → 脚本复核”这三步,再急也不跳。希望帮到你。
本文还有配套的精品资源,点击获取