# 34.8.桥接

有时，将网络（如以太网段）划分为多个网络段是有用的，而无需创建 IP 子网并使用路由器将这些段连接在一起。一个将两个网络连接在一起的设备被称为“桥接”。

桥接通过学习其每个网络接口上的设备的 MAC 地址来工作。它仅在源和目标 MAC 地址位于不同网络时才转发流量。从许多方面来看，桥接就像是具有非常少端口的以太网交换机。可以配置具有多个网络接口的 FreeBSD 系统作为桥接设备。

桥接在以下情况下非常有用：

**连接网络**

桥接的基本操作是将两个或更多网络段连接在一起。有很多原因需要使用基于主机的桥接，而不是网络设备，例如电缆限制或防火墙问题。桥接还可以将运行在 hostap 模式的无线接口与有线网络连接，并作为接入点使用。

**过滤/流量整形防火墙**

当需要防火墙功能而不涉及路由或网络地址转换（NAT）时，可以使用桥接。

例如，一个小型公司通过 DSL 或 ISDN 连接到 ISP，ISP 提供了十三个公共 IP 地址，而公司网络中有十台计算机。在这种情况下，使用基于路由器的防火墙会遇到子网划分的问题。可以在没有任何 IP 地址问题的情况下配置基于桥接的防火墙。

**网络 Tap**

桥接可以连接两个网络段，以便使用 [bpf(4)](https://man.freebsd.org/cgi/man.cgi?query=bpf\&sektion=4\&format=html) 和 [tcpdump(1)](https://man.freebsd.org/cgi/man.cgi?query=tcpdump\&sektion=1\&format=html) 在桥接接口上检查通过它们的所有以太网帧，或者通过将所有帧的副本发送到另一个称为 span 端口的接口来进行检查。

**2 层 VPN**

可以通过桥接网络将两个以太网网络通过 IP 链接连接起来，采用 EtherIP 隧道或基于 [tap(4)](https://man.freebsd.org/cgi/man.cgi?query=tap\&sektion=4\&format=html) 的解决方案，如 OpenVPN。

**2 层冗余**

可以通过多条链路将网络连接在一起，并使用生成树协议（STP）来阻止冗余路径。

本节描述了如何使用 [if\_bridge(4)](https://man.freebsd.org/cgi/man.cgi?query=if_bridge\&sektion=4\&format=html) 将 FreeBSD 系统配置为桥接设备。此外，还有一个 netgraph 桥接驱动程序，详细信息见 [ng\_bridge(4)](https://man.freebsd.org/cgi/man.cgi?query=ng_bridge\&sektion=4\&format=html)。

## 34.8.1. 启用桥接

在 FreeBSD 中，[if\_bridge(4)](https://man.freebsd.org/cgi/man.cgi?query=if_bridge\&sektion=4\&format=html) 是一个内核模块，当创建桥接接口时，系统会通过 [ifconfig(8)](https://man.freebsd.org/cgi/man.cgi?query=ifconfig\&sektion=8\&format=html) 自动加载它。也可以通过将 `device if_bridge` 添加到自定义内核配置文件中，来将桥接支持编译到自定义内核中。

桥接通过接口克隆的方式创建。要创建桥接接口，可以使用以下命令：

```sh
# ifconfig bridge create
bridge0
# ifconfig bridge0
bridge0: flags=8802<BROADCAST,SIMPLEX,MULTICAST> metric 0 mtu 1500
        ether 96:3d:4b:f1:79:7a
        id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
        maxage 20 holdcnt 6 proto rstp maxaddr 100 timeout 1200
        root id 00:00:00:00:00:00 priority 0 ifcost 0 port 0
```

当创建桥接接口时，它会自动分配一个随机生成的以太网地址。`maxaddr` 和 `timeout` 参数控制桥接将保留多少 MAC 地址在其转发表中，以及每个条目在最后一次出现后多少秒将被删除。其他参数则控制生成树协议（STP）的操作方式。

接下来，指定要添加到桥接中的网络接口。为了使桥接能够转发数据包，所有成员接口和桥接接口都需要处于启用状态：

```sh
# ifconfig bridge0 addm fxp0 addm fxp1 up
# ifconfig fxp0 up
# ifconfig fxp1 up
```

现在，桥接可以在 **fxp0** 和 **fxp1** 之间转发以太网帧。为了确保桥接在启动时自动创建，可以将以下内容添加到 **/etc/rc.conf** 文件中：

```sh
cloned_interfaces="bridge0"
ifconfig_bridge0="addm fxp0 addm fxp1 up"
ifconfig_fxp0="up"
ifconfig_fxp1="up"
```

如果桥接主机需要 IP 地址，请将其设置在桥接接口上，而不是成员接口上。可以使用静态 IP 地址或通过 DHCP 设置该地址。以下示例设置了一个静态 IP 地址：

```sh
# ifconfig bridge0 inet 192.168.0.1/24
```

也可以为桥接接口分配 IPv6 地址。为了使更改永久生效，可以将地址信息添加到 **/etc/rc.conf** 文件中。

> **注意**
>
> 启用数据包过滤时，桥接的数据包将分别在桥接接口的源接口和目标接口的入站与出站方向上通过过滤器。可以禁用任何一个阶段。当数据包流向重要时，最好在成员接口上而不是在桥接接口上进行防火墙设置。桥接有多个可配置的设置，用于传递非 IP 数据包和 IP 数据包，以及与 [ipfw(8)](https://man.freebsd.org/cgi/man.cgi?query=ipfw\&sektion=8\&format=html) 的第二层防火墙功能。有关更多信息，请参见 [if\_bridge(4)](https://man.freebsd.org/cgi/man.cgi?query=if_bridge\&sektion=4\&format=html)。
>
> 数据包过滤可以与任何与 [pfil(9)](https://man.freebsd.org/cgi/man.cgi?query=pfil\&sektion=9\&format=html) 框架连接的防火墙包一起使用。桥接还可以通过 [altq(4)](https://man.freebsd.org/cgi/man.cgi?query=altq\&sektion=4\&format=html) 或 [dummynet(4)](https://man.freebsd.org/cgi/man.cgi?query=dummynet\&sektion=4\&format=html) 作为流量整形器使用。

## 34.8.2. 启用生成树协议

为了确保以太网网络的正常运行，两个设备之间只能有一条活跃的路径。生成树协议（STP）用于检测环路，并将冗余链接置于阻塞状态。如果其中一条活跃的链路失败，STP 会计算出一个新的树形结构，并启用其中一条被阻塞的路径，以恢复网络中所有点的连接性。

快速生成树协议（RSTP 或 802.1w）与传统的 STP 兼容。RSTP 提供了更快的收敛速度，并与邻近的交换机交换信息，能够快速过渡到转发模式，同时避免产生环路。FreeBSD 支持 RSTP 和 STP 作为操作模式，默认使用 RSTP 模式。

可以使用 [ifconfig(8)](https://man.freebsd.org/cgi/man.cgi?query=ifconfig\&sektion=8\&format=html) 在成员接口上启用 STP。对于桥接接口 **fxp0** 和 **fxp1**，启用 STP 的命令如下：

```sh
# ifconfig bridge0 stp fxp0 stp fxp1
bridge0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        ether d6:cf:d5:a0:94:6d
        id 00:01:02:4b:d4:50 priority 32768 hellotime 2 fwddelay 15
        maxage 20 holdcnt 6 proto rstp maxaddr 100 timeout 1200
        root id 00:01:02:4b:d4:50 priority 32768 ifcost 0 port 0
        member: fxp0 flags=1c7<LEARNING,DISCOVER,STP,AUTOEDGE,PTP,AUTOPTP>
                port 3 priority 128 path cost 200000 proto rstp
                role designated state forwarding
        member: fxp1 flags=1c7<LEARNING,DISCOVER,STP,AUTOEDGE,PTP,AUTOPTP>
                port 4 priority 128 path cost 200000 proto rstp
                role designated state forwarding
```

这个桥接的生成树 ID 为 `00:01:02:4b:d4:50`，优先级为 `32768`。由于 `root id` 相同，表示这是生成树的根桥。

网络中另一个启用了 STP 的桥接如下所示：

```sh
bridge0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        ether 96:3d:4b:f1:79:7a
        id 00:13:d4:9a:06:7a priority 32768 hellotime 2 fwddelay 15
        maxage 20 holdcnt 6 proto rstp maxaddr 100 timeout 1200
        root id 00:01:02:4b:d4:50 priority 32768 ifcost 400000 port 4
        member: fxp0 flags=1c7<LEARNING,DISCOVER,STP,AUTOEDGE,PTP,AUTOPTP>
                port 4 priority 128 path cost 200000 proto rstp
                role root state forwarding
        member: fxp1 flags=1c7<LEARNING,DISCOVER,STP,AUTOEDGE,PTP,AUTOPTP>
                port 5 priority 128 path cost 200000 proto rstp
                role designated state forwarding
```

在这行 `root id 00:01:02:4b:d4:50 priority 32768 ifcost 400000 port 4` 中，显示了根桥的 ID 为 `00:01:02:4b:d4:50`，并且从该桥接到根桥的路径成本为 `400000`，该路径通过 `port 4`（即 **fxp0**）。

## 34.8.3. 桥接接口参数

桥接接口有几个 `ifconfig` 参数是独特的。本节总结了这些参数的一些常见用途。可用参数的完整列表请参见 [ifconfig(8)](https://man.freebsd.org/cgi/man.cgi?query=ifconfig\&sektion=8\&format=html)。

**private**

私有接口不会将任何流量转发到任何其他被指定为私有接口的端口。所有流量都会被无条件地阻止，因此不会转发任何以太网帧，包括 ARP 数据包。如果需要有选择地阻止流量，应使用防火墙。

**span**

镜像端口会将桥接收到的每一个以太网帧复制一份发送出去。可以为桥接配置多个镜像端口，但如果某个接口被指定为镜像端口，则不能同时作为普通的桥接端口使用。此功能最适合在另一台主机上通过桥接的镜像端口被动监视桥接网络。例如，要将所有帧的副本发送到名为 **fxp4** 的接口：

```sh
# ifconfig bridge0 span fxp4
```

**sticky**

如果一个桥接成员接口被标记为 sticky，那么动态学习到的地址条目会被当作静态条目处理，并存储在转发缓存中。即使该地址在另一个接口上被看到，sticky 条目也永远不会被从缓存中删除或替换。这提供了静态地址条目的好处，而无需预先填充转发表。客户端在桥接的某个段上学习到的地址不能跳转到其他段。

将 sticky 地址与 VLAN 结合使用的一个例子是：隔离客户网络，而不浪费 IP 地址空间。假设 `CustomerA` 在 `vlan100` 上，`CustomerB` 在 `vlan101` 上，且桥接有地址 `192.168.0.1`：

```sh
# ifconfig bridge0 addm vlan100 sticky vlan100 addm vlan101 sticky vlan101
# ifconfig bridge0 inet 192.168.0.1/24
```

在这个例子中，两个客户都将 `192.168.0.1` 作为默认网关。由于桥接缓存是 sticky 的，一个主机无法伪造另一个客户的 MAC 地址来截取他们的流量。

可以使用防火墙或如上所示的私有接口来阻止 VLAN 之间的任何通信：

```sh
# ifconfig bridge0 private vlan100 private vlan101
```

这样，客户完全相互隔离，并且可以在不进行子网划分的情况下，分配整个 `/24` 地址范围。

也可以限制每个接口后面的唯一源 MAC 地址数量。待达到限制，具有未知源地址的数据包将被丢弃，直到现有主机的缓存条目过期或被移除。

以下示例将 `CustomerA` 在 `vlan100` 上的以太网设备数量限制为 10：

```sh
# ifconfig bridge0 ifmaxaddr vlan100 10
```

桥接接口还支持监控模式，在这种模式下，数据包在 [bpf(4)](https://man.freebsd.org/cgi/man.cgi?query=bpf\&sektion=4\&format=html) 处理后会被丢弃，不会进一步处理或转发。这可以用于将两个或更多接口的输入多路复用到单个 [bpf(4)](https://man.freebsd.org/cgi/man.cgi?query=bpf\&sektion=4\&format=html) 流中。这对于重建网络 Tap 的流量非常有用，尤其是那些通过两个独立接口输出 RX/TX 信号的 Tap。例如，要将四个网络接口的输入作为一个流读取：

```sh
# ifconfig bridge0 addm fxp0 addm fxp1 addm fxp2 addm fxp3 monitor up
# tcpdump -i bridge0
```

## 34.8.4. SNMP 监控

桥接接口和 STP 参数可以通过 FreeBSD 基本系统中包含的 [bsnmpd(1)](https://man.freebsd.org/cgi/man.cgi?query=bsnmpd\&sektion=1\&format=html) 进行监控。导出的桥接 MIB 遵循 IETF 标准，因此可以使用任何 SNMP 客户端或监控软件包来检索数据。

要启用桥接的监控，请通过去掉 **/etc/snmpd.config** 文件中开头的 `#` 符号来取消注释这一行：

```sh
begemotSnmpdModulePath."bridge" = "/usr/lib/snmp_bridge.so"
```

可能还需要修改文件中的其他配置设置，例如社区名称和访问列表。更多信息请参见 [bsnmpd(1)](https://man.freebsd.org/cgi/man.cgi?query=bsnmpd\&sektion=1\&format=html) 和 [snmp\_bridge(3)](https://man.freebsd.org/cgi/man.cgi?query=snmp_bridge\&sektion=3\&format=html)。保存这些更改后，在 **/etc/rc.conf** 中添加以下行：

```sh
bsnmpd_enable="YES"
```

然后，启动 [bsnmpd(1)](https://man.freebsd.org/cgi/man.cgi?query=bsnmpd\&sektion=1\&format=html)：

```sh
# service bsnmpd start
```

以下示例使用 Net-SNMP 软件（[net-mgmt/net-snmp](https://cgit.freebsd.org/ports/tree/net-mgmt/net-snmp/)) 从客户端系统查询桥接。也可以使用 [net-mgmt/bsnmptools](https://cgit.freebsd.org/ports/tree/net-mgmt/bsnmptools/) 端口。在运行 Net-SNMP 的 SNMP 客户端中，将以下行添加到 **$HOME/.snmp/snmp.conf** 文件中，以导入桥接 MIB 定义：

```sh
mibdirs +/usr/share/snmp/mibs
mibs +BRIDGE-MIB:RSTP-MIB:BEGEMOT-MIB:BEGEMOT-BRIDGE-MIB
```

使用 IETF BRIDGE-MIB（RFC4188）监控单个桥接：

```sh
% snmpwalk -v 2c -c public bridge1.example.com mib-2.dot1dBridge
BRIDGE-MIB::dot1dBaseBridgeAddress.0 = STRING: 66:fb:9b:6e:5c:44
BRIDGE-MIB::dot1dBaseNumPorts.0 = INTEGER: 1 ports
BRIDGE-MIB::dot1dStpTimeSinceTopologyChange.0 = Timeticks: (189959) 0:31:39.59 centi-seconds
BRIDGE-MIB::dot1dStpTopChanges.0 = Counter32: 2
BRIDGE-MIB::dot1dStpDesignatedRoot.0 = Hex-STRING: 80 00 00 01 02 4B D4 50
...
BRIDGE-MIB::dot1dStpPortState.3 = INTEGER: forwarding(5)
BRIDGE-MIB::dot1dStpPortEnable.3 = INTEGER: enabled(1)
BRIDGE-MIB::dot1dStpPortPathCost.3 = INTEGER: 200000
BRIDGE-MIB::dot1dStpPortDesignatedRoot.3 = Hex-STRING: 80 00 00 01 02 4B D4 50
BRIDGE-MIB::dot1dStpPortDesignatedCost.3 = INTEGER: 0
BRIDGE-MIB::dot1dStpPortDesignatedBridge.3 = Hex-STRING: 80 00 00 01 02 4B D4 50
BRIDGE-MIB::dot1dStpPortDesignatedPort.3 = Hex-STRING: 03 80
BRIDGE-MIB::dot1dStpPortForwardTransitions.3 = Counter32: 1
RSTP-MIB::dot1dStpVersion.0 = INTEGER: rstp(2)
```

`dot1dStpTopChanges.0` 值为 2，表示 STP 桥接拓扑已变化两次。拓扑变化意味着网络中的一个或多个链路已改变或故障，计算了新的树。`dot1dStpTimeSinceTopologyChange.0` 值将显示此变化发生的时间。

要监控多个桥接接口，可以使用私有的 BEGEMOT-BRIDGE-MIB：

```sh
% snmpwalk -v 2c -c public bridge1.example.com
enterprises.fokus.begemot.begemotBridge
BEGEMOT-BRIDGE-MIB::begemotBridgeBaseName."bridge0" = STRING: bridge0
BEGEMOT-BRIDGE-MIB::begemotBridgeBaseName."bridge2" = STRING: bridge2
BEGEMOT-BRIDGE-MIB::begemotBridgeBaseAddress."bridge0" = STRING: e:ce:3b:5a:9e:13
BEGEMOT-BRIDGE-MIB::begemotBridgeBaseAddress."bridge2" = STRING: 12:5e:4d:74:d:fc
BEGEMOT-BRIDGE-MIB::begemotBridgeBaseNumPorts."bridge0" = INTEGER: 1
BEGEMOT-BRIDGE-MIB::begemotBridgeBaseNumPorts."bridge2" = INTEGER: 1
...
BEGEMOT-BRIDGE-MIB::begemotBridgeStpTimeSinceTopologyChange."bridge0" = Timeticks: (116927) 0:19:29.27 centi-seconds
BEGEMOT-BRIDGE-MIB::begemotBridgeStpTimeSinceTopologyChange."bridge2" = Timeticks: (82773) 0:13:47.73 centi-seconds
BEGEMOT-BRIDGE-MIB::begemotBridgeStpTopChanges."bridge0" = Counter32: 1
BEGEMOT-BRIDGE-MIB::begemotBridgeStpTopChanges."bridge2" = Counter32: 1
BEGEMOT-BRIDGE-MIB::begemotBridgeStpDesignatedRoot."bridge0" = Hex-STRING: 80 00 00 40 95 30 5E 31
BEGEMOT-BRIDGE-MIB::begemotBridgeStpDesignatedRoot."bridge2" = Hex-STRING: 80 00 00 50 8B B8 C6 A9
```

要通过 `mib-2.dot1dBridge` 子树更改正在监控的桥接接口：

```sh
% snmpset -v 2c -c private bridge1.example.com
BEGEMOT-BRIDGE-MIB::begemotBridgeDefaultBridgeIf.0 s bridge2
```
