使用FreeBSD 15的新桥接功能
Using the new bridges of FreeBSD 15

原始链接: https://blog.feld.me/posts/2026/02/using-new-bridges-freebsd-15/

## FreeBSD 15 新的桥接与 VLAN 实现 FreeBSD 15 引入了经过改进的桥接系统,并提供原生 VLAN 支持,旨在提高性能并简化配置。新的实现行为更像硬件交换机,通过软弃用成员接口上的第三层地址来实现 – 此更改将在 FreeBSD 16.0 中强制执行。 此前,配置 VLAN 涉及为每个 VLAN 创建一个桥接,导致配置复杂,尤其是在使用大量 VLAN 或 jails 的情况下。新的系统允许使用单个桥接,其成员被标记用于特定的 VLAN,从而大大简化了设置。例如,需要多个桥接定义的复杂 `rc.conf` 现在可以简化为定义桥接上 VLAN 过滤的单行。 此更改会影响 VNET jails,需要更新脚本以创建稳定的 epair 接口并将它们连接到新的桥接。新的脚本可以自动化此过程,简化 jail 配置。 然而,与 `vm-bhyve` 等工具的集成仍在发展中。目前,用户可能需要手动创建 tap 接口并进行配置,绕过 `vm-bhyve` 的原生桥接支持。虽然可用,但这是一种临时解决方法,直到 `vm-bhyve` 完全支持新的桥接系统。总而言之,此次更新承诺为 FreeBSD 提供更简洁、更高效的网络体验。

## FreeBSD 在 Hacker News 上获得关注与讨论 最近 Hacker News 上与 FreeBSD 相关帖子激增,这源于几个月前发布的 FreeBSD 15。用户们分享和讨论了关于新特性(如 Linuxulator)和桥接技术改进的文章。 一个主要的讨论点围绕 FreeBSD 的容器化/虚拟化工具(jail, iojails, Sylve, Bastille, Bhyve),以及对更好、更一致文档的需求。一些用户建议为了清晰起见,回归 FreeBSD 手册。 FreeBSD 15 中新桥接技术的性能也存在争议,有用户声称由于数据包传输存在效率问题,并提倡使用 SR-IOV。另一些用户则认为,在 10GbE 速度下,内存带宽不是限制因素,暗示潜在问题可能存在于其他网络堆栈中。还有关于在桥接配置中禁用 TCP 分段卸载 (TSO) 和大接收卸载 (LRO) 的讨论,一些人认为这是过时的建议。总体而言,社区对 BSD 方面的进展表示热情。
相关文章

原文

FreeBSD 15 comes with a new bridging implementation which has native support for VLANs. They have also soft-deprecated the ability to have any layer 3 addresses on member interfaces which makes it behave like a real hardware switch. The net.link.bridge.member_ifaddrs sysctl controls this behavior and it will be removed in FreeBSD 16.0-RELEASE, same as if set to zero.

🚧 There are concerns about how this will work with router-on-a-stick style setups, but that's a future problem.

One of the primary benefits of the new implementation is that you can have a single bridge for everything and the packet processing has been optimized. Previously the switch performance would degrade as the number of member interfaces increased, but I'm not clear on how significant it really was. It didn't impact gigabit for me, but perhaps you'd see it if you were trying to get 10gbit line rate out of it.

Old Bridging

The old design when working with bridges and VLANs was like so:

  • Create a bridge for a specific VLAN
  • Create a VLAN interface off a physical interface
  • Attach the VLAN interface to the bridge
  • Now all your bridge members are able to communicate to that VLAN

As you can imagine this starts to get messy especially if you have a lot of VLANs to work with or try to deploy jails that are meant to communicate over multiple VLANs. An example from my old rc.conf looked something like this:

# Bhyve and VNET
ifconfig_ix1="up -tso4 -tso6 -vlanhwfilter -vlanmtu -vlanhwtso -vlanhwtag -vlanhwcsum -lro"

vlans_ix1="vlan2 vlan3 vlan128"
create_args_vlan2="vlan 2"
create_args_vlan3="vlan 3"
create_args_vlan128="vlan 128"

cloned_interfaces="bridge0 bridge1 bridge2"

ifconfig_bridge0_name="vlan2bridge"
ifconfig_vlan2bridge="addm vlan2 up"

ifconfig_bridge1_name="vlan3bridge"
ifconfig_vlan3bridge="addm vlan3 up"

ifconfig_bridge2_name="vlan128bridge"
ifconfig_vlan128bridge="addm vlan128 addm"

It's a lot of work for something that feels so simple if you're familiar with real network gear.

New Bridging

The new design allows you to have a single bridge and specify tagged and/or untagged VLANs for each bridge member. Here's what all of that condenses down to.

# Bhyve and VNET
ifconfig_ix1="up -tso4 -tso6 -vlanhwfilter -vlanmtu -vlanhwtso -vlanhwtag -vlanhwcsum -lro"

cloned_interfaces="bridge0"
ifconfig_bridge0="vlanfilter addm ix1 tagged 2,3,128"

That's it. Much simpler, isn't it? Please don't over look the vlanfilter flag on the bridge. If you are missing this you can add members to the bridge with untagged VLANs but you'll get this error when trying to add tagged VLANs:

ifconfig: BRDGSIFVLANSET ix1: Invalid argument (extended error VLAN filtering not enabled)

VNET Jails

VNET jails posed a problem for me because I was relying on the "unofficial" /usr/share/examples/jails/jib script which can handle creating epair(4) devices for the jails. I'd copy this script into my PATH, make it executable, and then I could use it. A simplified example of this jail configuration looks like this:

webserver {
    exec.prestart += "jib addm $name vlan2";
    exec.poststop = "jib destroy $name";
    vnet;
    vnet.interface = "e0b_$name";
}

This would handle creating a stable name of epair(4) devices and attach it to the right bridge. e.g., "vlan2" becomes "vlan2bridge" in the jib script.

The jib script is old and has some features that I don't believe are necessary anymore. One feature tries to ensure your MAC addreses are stable, but this seems to be a native kernel feature of epair(4) now:

$ sysctl -d net.link.epair.ether_gen_addr
net.link.epair.ether_gen_addr: Generate MAC with FreeBSD OUI using ether_gen_addr(9)

And ether_gen_addr(9) says:

By default, ether_gen_addr attempts to generate a stable MAC address
using the hostid of the jail that the ifp is being added to. During
early boot, the hostid may not be set on machines that haven't yet pop-
ulated /etc/hostid, or on machines that do not use loader(8).

Great, we seem to be covered there. But I still need a way to make stable epair device names for the jails and attach them to the bridge the new way, so I wrote a script for that:

#!/bin/sh
# /scripts/vnetif

set -eu

ENAME=$1
BRIDGE=$2
VLAN=$3

NEW_EPAIR=$(ifconfig -D epair create -vlanhwfilter up)
EPAIR_NUM=$(echo ${NEW_EPAIR##epair} | tr -d '[a]')

# Do not even want ipv6 link local on here, layer3 not allowed
# anymore on bridge members
ifconfig ${NEW_EPAIR} inet6 ifdisabled -auto_linklocal -accept_rtadv no_radr

ifconfig epair${EPAIR_NUM}a name e0a_${ENAME}
ifconfig epair${EPAIR_NUM}b name e0b_${ENAME}
ifconfig ${BRIDGE} addm e0a_${ENAME} untagged ${VLAN}

This is not well designed but it gets the job done.

Note, on the epair device I had to set -vlanhwfilter -- another hidden requirement!

Now that this is sorted, my VNET jail config looks like this:

webserver {
    exec.prestart += "/scripts/vnetif $name bridge0 2";
    vnet;
    vnet.interface = "e0b_$name";
}

It works! The VNET interface gets added to the bridge with the correct VLAN membership. It even seems to start the jail faster now...

Notice I don't have an exec.poststop anymore. This seems to not be required as the epair interface is automatically destroyed when the jail is torn down now.

Bhyve VMs

The Bhyve VM solution is not finalized for me right now. I'm using the vm-bhyve tooling to manage my VMs, but it has no support for this new bridging+VLAN functionality. I've resorted to defining a manually managed switch in the configuration and pre-creating tap interfaces for each VM. Technically I could use cloned_interfaces in /etc/rc.conf and add them all to the bridge when the bridge is created, but as the number of VMs grow the configuration for this is getting really long and messy. I've opted to sidestep it for the moment and create all the tap interfaces in /etc/rc.local, and then the VM config looks like:

loader="grub"
cpu="2"
memory="4G"
network0_type="virtio-net"
network0_switch="bridge0" # the predefined manual bridge config in /vm-bhyve/.config/system.conf
network0_device="tap133" # my precreated tap interface
disk0_type="nvme"
disk0_name="disk0.img"
uuid="2109c019-9fd0-11f0-9ab1-d05099db8057"
network0_mac="58:9c:fc:0f:74:60" # seems to be ineffective now
disk1_type="ahci-cd"
disk1_name="seed.iso"
disk1_dev="file"

So if you want a stable MAC address, you need to set it where you create the tap interface.

For the record, that /vm-bhyve/.config/system.conf looks like:

switch_list="bridge0"
type_bridge0="manual"
bridge_bridge0="bridge0"

and my /etc/rc.local has entries like:

# for VM such-and-such
ifconfig tap129 create up
ifconfig bridge0 addm tap129 untagged 2

It's not elegant, but it works well enough while I think of a better solution. When I have one I'll update this blog post. Hopefully this should be able to get you up and running with the new bridge+VLAN design in FreeBSD 15.0-RELEASE.

联系我们 contact @ memedata.com