From 865fd869e8e4d1c079368eb06a1d325f6f631053 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Thu, 12 Nov 2026 10:01:23 -0500 Subject: [PATCH] netlink: route table ids and bridge vlan on FreeBSD FreeBSD has no RT_TABLE_* in the libc crate and its tables are FIBs: the default FIB is 0 and the kernel reads the FIB from RTA_TABLE, so both the main and the unspec table id are 0 in the u8 header slot. Keep the Linux values from libc behind cfg. The bridge vlan message is AF_BRIDGE, which FreeBSD does not have. Reject the vlan option at validation on non-Linux and give set_vlan_id a stub there so the bridge driver does not need its own cfg. Signed-off-by: Michael Johnson --- src/network/bridge.rs | 6 ++++++ src/network/netlink_route.rs | 38 +++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/network/bridge.rs b/src/network/bridge.rs index 4f94e9f50..b6f596fdd 100644 --- a/src/network/bridge.rs +++ b/src/network/bridge.rs @@ -133,6 +133,12 @@ impl driver::NetworkDriver for Bridge<'_> { let no_default_route: bool = opts.no_default_route.unwrap_or(false); let vrf: Option = opts.vrf.clone(); let vlan: Option = opts.vlan; + #[cfg(not(target_os = "linux"))] + if vlan.is_some() { + return Err(NetavarkError::msg( + "the vlan option is not supported on this platform", + )); + } let host_interface_name = parse_option( &self.info.per_network_opts.options, diff --git a/src/network/netlink_route.rs b/src/network/netlink_route.rs index 75b51cd8c..71b09bd5d 100644 --- a/src/network/netlink_route.rs +++ b/src/network/netlink_route.rs @@ -13,16 +13,30 @@ use crate::{ }; use log::info; use netlink_packet_core::{NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL}; +#[cfg(target_os = "linux")] +use netlink_packet_route::link::{AfSpecBridge, BridgeVlanInfo}; use netlink_packet_route::{ address::AddressMessage, link::{ - AfSpecBridge, BridgeVlanInfo, BridgeVlanInfoFlags, InfoBridge, InfoData, InfoKind, - LinkAttribute, LinkFlags, LinkInfo, LinkMessage, + BridgeVlanInfoFlags, InfoBridge, InfoData, InfoKind, LinkAttribute, LinkFlags, LinkInfo, + LinkMessage, }, route::{RouteAddress, RouteMessage, RouteProtocol, RouteScope, RouteType}, AddressFamily, RouteNetlinkMessage, }; +// Route table ids. Linux keeps its routes in the main table (254). On FreeBSD +// a table is a FIB: the default FIB is 0 and the kernel takes the FIB from +// the RTA_TABLE attribute, so the u8 header slot is 0 for main and unspec. +#[cfg(target_os = "linux")] +const RT_TABLE_MAIN: u8 = libc::RT_TABLE_MAIN; +#[cfg(target_os = "linux")] +const RT_TABLE_UNSPEC: u8 = libc::RT_TABLE_UNSPEC; +#[cfg(target_os = "freebsd")] +const RT_TABLE_MAIN: u8 = 0; +#[cfg(target_os = "freebsd")] +const RT_TABLE_UNSPEC: u8 = 0; + #[derive(Clone)] pub struct CreateLinkOptions<'fd> { pub name: String, @@ -192,6 +206,7 @@ impl Socket { /// set the vlan id for an interface which is attached to the bridge with vlan_filtering /// Performs the equivalent of "bridge vlan add dev test vid [flags]" + #[cfg(target_os = "linux")] pub fn set_vlan_id( &mut self, link_id: u32, @@ -214,6 +229,19 @@ impl Socket { Ok(()) } + /// FreeBSD bridges have no netlink interface for vlan filtering. + #[cfg(not(target_os = "linux"))] + pub fn set_vlan_id( + &mut self, + _link_id: u32, + _vid: u16, + _flags: BridgeVlanInfoFlags, + ) -> NetavarkResult<()> { + Err(NetavarkError::msg( + "bridge vlan filtering is not supported on this platform", + )) + } + fn create_addr_msg(link_id: u32, addr: &ipnet::IpNet) -> AddressMessage { let mut msg = AddressMessage::default(); msg.header.index = link_id; @@ -276,7 +304,7 @@ impl Socket { fn create_route_msg(route: &Route) -> RouteMessage { let mut msg = RouteMessage::default(); - msg.header.table = libc::RT_TABLE_MAIN; + msg.header.table = RT_TABLE_MAIN; msg.header.protocol = RouteProtocol::Static; msg.header.scope = RouteScope::Universe; @@ -357,7 +385,7 @@ impl Socket { pub fn dump_routes(&mut self, table: Option) -> NetavarkResult> { let mut msg: RouteMessage = RouteMessage::default(); - msg.header.table = libc::RT_TABLE_UNSPEC; + msg.header.table = RT_TABLE_UNSPEC; msg.header.protocol = RouteProtocol::Unspec; msg.header.scope = RouteScope::Universe; msg.header.kind = RouteType::Unicast; @@ -365,7 +393,7 @@ impl Socket { msg.attributes .push(netlink_packet_route::route::RouteAttribute::Table( // default to the main table instead of dumping all tables - table.unwrap_or(libc::RT_TABLE_MAIN.into()), + table.unwrap_or(RT_TABLE_MAIN.into()), )); let results = self.make_netlink_request(RouteNetlinkMessage::GetRoute(msg), NLM_F_DUMP)?;