use super::{
attr::AttrIter,
builder::MessageBuilder,
connection::Connection,
error::{Error, Result},
interface_ref::InterfaceRef,
message::{MessageIter, NLM_F_ACK, NLM_F_DUMP, NLM_F_REQUEST, NlMsgType},
protocol::Route,
types::link::{
BridgeVlanInfo, BrVlanMsg, IfInfoMsg, IflaAttr, br_state, bridge_af, bridge_vlan_flags,
bridge_vlan_tunnel, bridge_vlandb, bridge_vlandb_dump, bridge_vlandb_entry,
bridge_vlandb_gopts, rtext_filter,
},
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct BridgeVlanFlags {
pub pvid: bool,
pub untagged: bool,
}
impl BridgeVlanFlags {
pub fn from_raw(flags: u16) -> Self {
Self {
pvid: flags & bridge_vlan_flags::PVID != 0,
untagged: flags & bridge_vlan_flags::UNTAGGED != 0,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct BridgeVlanEntry {
pub(crate) ifindex: u32,
pub(crate) vid: u16,
pub(crate) flags: BridgeVlanFlags,
}
impl BridgeVlanEntry {
pub fn ifindex(&self) -> u32 {
self.ifindex
}
pub fn vid(&self) -> u16 {
self.vid
}
pub fn flags(&self) -> BridgeVlanFlags {
self.flags
}
pub fn is_pvid(&self) -> bool {
self.flags.pvid
}
pub fn is_untagged(&self) -> bool {
self.flags.untagged
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeVlanTunnelEntry {
pub ifindex: u32,
pub vid: u16,
pub tunnel_id: u32,
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct BridgeVlanTunnelBuilder {
dev: Option<InterfaceRef>,
vid: u16,
vid_end: Option<u16>,
tunnel_id: u32,
}
impl BridgeVlanTunnelBuilder {
pub const MAX_TUNNEL_ID: u32 = (1 << 24) - 1;
pub fn new(vid: u16, tunnel_id: u32) -> Self {
Self {
vid,
tunnel_id,
..Default::default()
}
}
pub fn dev(mut self, dev: impl Into<String>) -> Self {
self.dev = Some(InterfaceRef::Name(dev.into()));
self
}
pub fn ifindex(mut self, ifindex: u32) -> Self {
self.dev = Some(InterfaceRef::Index(ifindex));
self
}
pub fn device_ref(&self) -> Option<&InterfaceRef> {
self.dev.as_ref()
}
pub fn range(mut self, vid_end: u16) -> Self {
self.vid_end = Some(vid_end);
self
}
pub(crate) fn write_add(&self, builder: &mut MessageBuilder, ifindex: u32) -> Result<()> {
self.write_message(builder, NlMsgType::RTM_SETLINK, ifindex)
}
pub(crate) fn write_del(&self, builder: &mut MessageBuilder, ifindex: u32) -> Result<()> {
self.write_message(builder, NlMsgType::RTM_DELLINK, ifindex)
}
fn write_message(
&self,
builder: &mut MessageBuilder,
_msg_type: u16,
ifindex: u32,
) -> Result<()> {
if self.tunnel_id > Self::MAX_TUNNEL_ID {
return Err(Error::InvalidMessage(format!(
"tunnel_id {} exceeds maximum {}",
self.tunnel_id,
Self::MAX_TUNNEL_ID
)));
}
let ifinfo = IfInfoMsg::new()
.with_family(libc::AF_BRIDGE as u8)
.with_index(ifindex as i32);
builder.append(&ifinfo);
let af_spec = builder.nest_start(IflaAttr::AfSpec as u16);
if let Some(vid_end) = self.vid_end {
self.add_tunnel_entry(
builder,
self.vid,
self.tunnel_id,
bridge_vlan_flags::RANGE_BEGIN,
);
let tunnel_id_end = self.tunnel_id + (vid_end - self.vid) as u32;
if tunnel_id_end > Self::MAX_TUNNEL_ID {
return Err(Error::InvalidMessage(format!(
"tunnel_id range end {} exceeds maximum {}",
tunnel_id_end,
Self::MAX_TUNNEL_ID
)));
}
self.add_tunnel_entry(
builder,
vid_end,
tunnel_id_end,
bridge_vlan_flags::RANGE_END,
);
} else {
self.add_tunnel_entry(builder, self.vid, self.tunnel_id, 0);
}
builder.nest_end(af_spec);
Ok(())
}
fn add_tunnel_entry(&self, builder: &mut MessageBuilder, vid: u16, tunnel_id: u32, flags: u16) {
let tunnel_info = builder.nest_start(bridge_af::IFLA_BRIDGE_VLAN_TUNNEL_INFO);
builder.append_attr_u32(bridge_vlan_tunnel::IFLA_BRIDGE_VLAN_TUNNEL_ID, tunnel_id);
builder.append_attr_u16(bridge_vlan_tunnel::IFLA_BRIDGE_VLAN_TUNNEL_VID, vid);
if flags != 0 {
builder.append_attr_u16(bridge_vlan_tunnel::IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, flags);
}
builder.nest_end(tunnel_info);
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct BridgeVlanBuilder {
dev: Option<InterfaceRef>,
vid: u16,
vid_end: Option<u16>,
pvid: bool,
untagged: bool,
master: bool,
}
impl BridgeVlanBuilder {
pub fn new(vid: u16) -> Self {
Self {
vid,
..Default::default()
}
}
pub fn dev(mut self, dev: impl Into<String>) -> Self {
self.dev = Some(InterfaceRef::Name(dev.into()));
self
}
pub fn ifindex(mut self, ifindex: u32) -> Self {
self.dev = Some(InterfaceRef::Index(ifindex));
self
}
pub fn device_ref(&self) -> Option<&InterfaceRef> {
self.dev.as_ref()
}
pub fn range(mut self, vid_end: u16) -> Self {
self.vid_end = Some(vid_end);
self
}
pub fn pvid(mut self) -> Self {
self.pvid = true;
self
}
pub fn untagged(mut self) -> Self {
self.untagged = true;
self
}
pub fn master(mut self) -> Self {
self.master = true;
self
}
fn build_flags(&self) -> u16 {
let mut flags = 0u16;
if self.pvid {
flags |= bridge_vlan_flags::PVID;
}
if self.untagged {
flags |= bridge_vlan_flags::UNTAGGED;
}
if self.master {
flags |= bridge_vlan_flags::MASTER;
}
flags
}
pub(crate) fn write_add(&self, builder: &mut MessageBuilder, ifindex: u32) {
self.write_message(builder, ifindex);
}
pub(crate) fn write_del(&self, builder: &mut MessageBuilder, ifindex: u32) {
self.write_message(builder, ifindex);
}
fn write_message(&self, builder: &mut MessageBuilder, ifindex: u32) {
let ifinfo = IfInfoMsg::new()
.with_family(libc::AF_BRIDGE as u8)
.with_index(ifindex as i32);
builder.append(&ifinfo);
let af_spec = builder.nest_start(IflaAttr::AfSpec as u16);
if let Some(vid_end) = self.vid_end {
let mut begin_flags = self.build_flags();
begin_flags |= bridge_vlan_flags::RANGE_BEGIN;
let vlan_begin = BridgeVlanInfo::new(self.vid).with_flags(begin_flags);
builder.append_attr(bridge_af::IFLA_BRIDGE_VLAN_INFO, vlan_begin.as_bytes());
let mut end_flags = self.build_flags();
end_flags |= bridge_vlan_flags::RANGE_END;
let vlan_end = BridgeVlanInfo::new(vid_end).with_flags(end_flags);
builder.append_attr(bridge_af::IFLA_BRIDGE_VLAN_INFO, vlan_end.as_bytes());
} else {
let flags = self.build_flags();
let vlan_info = BridgeVlanInfo::new(self.vid).with_flags(flags);
builder.append_attr(bridge_af::IFLA_BRIDGE_VLAN_INFO, vlan_info.as_bytes());
}
builder.nest_end(af_spec);
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct BridgeVlanGlobalOptionsBuilder {
dev: Option<InterfaceRef>,
vid: u16,
vid_end: Option<u16>,
mcast_snooping: Option<bool>,
mcast_querier: Option<bool>,
mcast_igmp_version: Option<u8>,
mcast_mld_version: Option<u8>,
mcast_last_member_count: Option<u32>,
mcast_startup_query_count: Option<u32>,
mcast_last_member_interval: Option<u64>,
mcast_membership_interval: Option<u64>,
mcast_querier_interval: Option<u64>,
mcast_query_interval: Option<u64>,
mcast_query_response_interval: Option<u64>,
mcast_startup_query_interval: Option<u64>,
msti: Option<u16>,
}
impl BridgeVlanGlobalOptionsBuilder {
pub fn new(vid: u16) -> Self {
Self {
vid,
..Default::default()
}
}
pub fn dev(mut self, dev: impl Into<String>) -> Self {
self.dev = Some(InterfaceRef::Name(dev.into()));
self
}
pub fn ifindex(mut self, ifindex: u32) -> Self {
self.dev = Some(InterfaceRef::Index(ifindex));
self
}
pub fn device_ref(&self) -> Option<&InterfaceRef> {
self.dev.as_ref()
}
pub fn range(mut self, vid_end: u16) -> Self {
self.vid_end = Some(vid_end);
self
}
pub fn mcast_snooping(mut self, on: bool) -> Self {
self.mcast_snooping = Some(on);
self
}
pub fn mcast_querier(mut self, on: bool) -> Self {
self.mcast_querier = Some(on);
self
}
pub fn mcast_igmp_version(mut self, version: u8) -> Self {
self.mcast_igmp_version = Some(version);
self
}
pub fn mcast_mld_version(mut self, version: u8) -> Self {
self.mcast_mld_version = Some(version);
self
}
pub fn mcast_last_member_count(mut self, count: u32) -> Self {
self.mcast_last_member_count = Some(count);
self
}
pub fn mcast_startup_query_count(mut self, count: u32) -> Self {
self.mcast_startup_query_count = Some(count);
self
}
pub fn mcast_last_member_interval(mut self, centisecs: u64) -> Self {
self.mcast_last_member_interval = Some(centisecs);
self
}
pub fn mcast_membership_interval(mut self, centisecs: u64) -> Self {
self.mcast_membership_interval = Some(centisecs);
self
}
pub fn mcast_querier_interval(mut self, centisecs: u64) -> Self {
self.mcast_querier_interval = Some(centisecs);
self
}
pub fn mcast_query_interval(mut self, centisecs: u64) -> Self {
self.mcast_query_interval = Some(centisecs);
self
}
pub fn mcast_query_response_interval(mut self, centisecs: u64) -> Self {
self.mcast_query_response_interval = Some(centisecs);
self
}
pub fn mcast_startup_query_interval(mut self, centisecs: u64) -> Self {
self.mcast_startup_query_interval = Some(centisecs);
self
}
pub fn msti(mut self, msti: u16) -> Self {
self.msti = Some(msti);
self
}
pub(crate) fn write_set(&self, builder: &mut MessageBuilder, ifindex: u32) {
let msg = BrVlanMsg::new()
.with_family(libc::AF_BRIDGE as u8)
.with_index(ifindex);
builder.append(&msg);
let gopts = builder.nest_start(bridge_vlandb::GLOBAL_OPTIONS);
builder.append_attr_u16(bridge_vlandb_gopts::ID, self.vid);
if let Some(vid_end) = self.vid_end {
builder.append_attr_u16(bridge_vlandb_gopts::RANGE, vid_end);
}
if let Some(v) = self.mcast_snooping {
builder.append_attr_u8(bridge_vlandb_gopts::MCAST_SNOOPING, v as u8);
}
if let Some(v) = self.mcast_querier {
builder.append_attr_u8(bridge_vlandb_gopts::MCAST_QUERIER, v as u8);
}
if let Some(v) = self.mcast_igmp_version {
builder.append_attr_u8(bridge_vlandb_gopts::MCAST_IGMP_VERSION, v);
}
if let Some(v) = self.mcast_mld_version {
builder.append_attr_u8(bridge_vlandb_gopts::MCAST_MLD_VERSION, v);
}
if let Some(v) = self.mcast_last_member_count {
builder.append_attr_u32(bridge_vlandb_gopts::MCAST_LAST_MEMBER_CNT, v);
}
if let Some(v) = self.mcast_startup_query_count {
builder.append_attr_u32(bridge_vlandb_gopts::MCAST_STARTUP_QUERY_CNT, v);
}
if let Some(v) = self.mcast_last_member_interval {
builder.append_attr_u64(bridge_vlandb_gopts::MCAST_LAST_MEMBER_INTVL, v);
}
if let Some(v) = self.mcast_membership_interval {
builder.append_attr_u64(bridge_vlandb_gopts::MCAST_MEMBERSHIP_INTVL, v);
}
if let Some(v) = self.mcast_querier_interval {
builder.append_attr_u64(bridge_vlandb_gopts::MCAST_QUERIER_INTVL, v);
}
if let Some(v) = self.mcast_query_interval {
builder.append_attr_u64(bridge_vlandb_gopts::MCAST_QUERY_INTVL, v);
}
if let Some(v) = self.mcast_query_response_interval {
builder.append_attr_u64(bridge_vlandb_gopts::MCAST_QUERY_RESPONSE_INTVL, v);
}
if let Some(v) = self.mcast_startup_query_interval {
builder.append_attr_u64(bridge_vlandb_gopts::MCAST_STARTUP_QUERY_INTVL, v);
}
if let Some(v) = self.msti {
builder.append_attr_u16(bridge_vlandb_gopts::MSTI, v);
}
builder.nest_end(gopts);
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct BridgeVlanGlobalOptions {
pub(crate) ifindex: u32,
pub(crate) vid: u16,
pub(crate) vid_end: Option<u16>,
pub(crate) mcast_snooping: Option<bool>,
pub(crate) mcast_querier: Option<bool>,
pub(crate) mcast_igmp_version: Option<u8>,
pub(crate) mcast_mld_version: Option<u8>,
pub(crate) mcast_last_member_count: Option<u32>,
pub(crate) mcast_startup_query_count: Option<u32>,
pub(crate) mcast_last_member_interval: Option<u64>,
pub(crate) mcast_membership_interval: Option<u64>,
pub(crate) mcast_querier_interval: Option<u64>,
pub(crate) mcast_query_interval: Option<u64>,
pub(crate) mcast_query_response_interval: Option<u64>,
pub(crate) mcast_startup_query_interval: Option<u64>,
pub(crate) msti: Option<u16>,
}
impl BridgeVlanGlobalOptions {
pub fn ifindex(&self) -> u32 {
self.ifindex
}
pub fn vid(&self) -> u16 {
self.vid
}
pub fn vid_end(&self) -> Option<u16> {
self.vid_end
}
pub fn mcast_snooping(&self) -> Option<bool> {
self.mcast_snooping
}
pub fn mcast_querier(&self) -> Option<bool> {
self.mcast_querier
}
pub fn mcast_igmp_version(&self) -> Option<u8> {
self.mcast_igmp_version
}
pub fn mcast_mld_version(&self) -> Option<u8> {
self.mcast_mld_version
}
pub fn mcast_last_member_count(&self) -> Option<u32> {
self.mcast_last_member_count
}
pub fn mcast_startup_query_count(&self) -> Option<u32> {
self.mcast_startup_query_count
}
pub fn mcast_last_member_interval(&self) -> Option<u64> {
self.mcast_last_member_interval
}
pub fn mcast_membership_interval(&self) -> Option<u64> {
self.mcast_membership_interval
}
pub fn mcast_querier_interval(&self) -> Option<u64> {
self.mcast_querier_interval
}
pub fn mcast_query_interval(&self) -> Option<u64> {
self.mcast_query_interval
}
pub fn mcast_query_response_interval(&self) -> Option<u64> {
self.mcast_query_response_interval
}
pub fn mcast_startup_query_interval(&self) -> Option<u64> {
self.mcast_startup_query_interval
}
pub fn msti(&self) -> Option<u16> {
self.msti
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BridgeVlanState {
Disabled,
Listening,
Learning,
Forwarding,
Blocking,
}
impl BridgeVlanState {
pub fn to_raw(self) -> u8 {
match self {
Self::Disabled => br_state::DISABLED,
Self::Listening => br_state::LISTENING,
Self::Learning => br_state::LEARNING,
Self::Forwarding => br_state::FORWARDING,
Self::Blocking => br_state::BLOCKING,
}
}
pub fn from_raw(v: u8) -> Option<Self> {
match v {
br_state::DISABLED => Some(Self::Disabled),
br_state::LISTENING => Some(Self::Listening),
br_state::LEARNING => Some(Self::Learning),
br_state::FORWARDING => Some(Self::Forwarding),
br_state::BLOCKING => Some(Self::Blocking),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct BridgeVlanEntryOptionsBuilder {
dev: Option<InterfaceRef>,
vid: u16,
vid_end: Option<u16>,
state: Option<BridgeVlanState>,
mcast_router: Option<u8>,
mcast_max_groups: Option<u32>,
neigh_suppress: Option<bool>,
}
impl BridgeVlanEntryOptionsBuilder {
pub fn new(vid: u16) -> Self {
Self {
vid,
..Default::default()
}
}
pub fn dev(mut self, dev: impl Into<String>) -> Self {
self.dev = Some(InterfaceRef::Name(dev.into()));
self
}
pub fn ifindex(mut self, ifindex: u32) -> Self {
self.dev = Some(InterfaceRef::Index(ifindex));
self
}
pub fn device_ref(&self) -> Option<&InterfaceRef> {
self.dev.as_ref()
}
pub fn range(mut self, vid_end: u16) -> Self {
self.vid_end = Some(vid_end);
self
}
pub fn state(mut self, state: BridgeVlanState) -> Self {
self.state = Some(state);
self
}
pub fn mcast_router(mut self, mode: u8) -> Self {
self.mcast_router = Some(mode);
self
}
pub fn mcast_max_groups(mut self, max: u32) -> Self {
self.mcast_max_groups = Some(max);
self
}
pub fn neigh_suppress(mut self, on: bool) -> Self {
self.neigh_suppress = Some(on);
self
}
pub(crate) fn write_set(&self, builder: &mut MessageBuilder, ifindex: u32) {
let msg = BrVlanMsg::new()
.with_family(libc::AF_BRIDGE as u8)
.with_index(ifindex);
builder.append(&msg);
let entry = builder.nest_start(bridge_vlandb::ENTRY);
let info = BridgeVlanInfo::new(self.vid);
builder.append_attr(bridge_vlandb_entry::INFO, info.as_bytes());
if let Some(vid_end) = self.vid_end {
builder.append_attr_u16(bridge_vlandb_entry::RANGE, vid_end);
}
if let Some(state) = self.state {
builder.append_attr_u8(bridge_vlandb_entry::STATE, state.to_raw());
}
if let Some(mode) = self.mcast_router {
builder.append_attr_u8(bridge_vlandb_entry::MCAST_ROUTER, mode);
}
if let Some(max) = self.mcast_max_groups {
builder.append_attr_u32(bridge_vlandb_entry::MCAST_MAX_GROUPS, max);
}
if let Some(on) = self.neigh_suppress {
builder.append_attr_u8(bridge_vlandb_entry::NEIGH_SUPPRESS, on as u8);
}
builder.nest_end(entry);
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct BridgeVlanEntryOptions {
pub(crate) ifindex: u32,
pub(crate) vid: u16,
pub(crate) vid_end: Option<u16>,
pub(crate) state: Option<BridgeVlanState>,
pub(crate) mcast_router: Option<u8>,
pub(crate) mcast_n_groups: Option<u32>,
pub(crate) mcast_max_groups: Option<u32>,
pub(crate) neigh_suppress: Option<bool>,
}
impl BridgeVlanEntryOptions {
pub fn ifindex(&self) -> u32 {
self.ifindex
}
pub fn vid(&self) -> u16 {
self.vid
}
pub fn vid_end(&self) -> Option<u16> {
self.vid_end
}
pub fn state(&self) -> Option<BridgeVlanState> {
self.state
}
pub fn mcast_router(&self) -> Option<u8> {
self.mcast_router
}
pub fn mcast_n_groups(&self) -> Option<u32> {
self.mcast_n_groups
}
pub fn mcast_max_groups(&self) -> Option<u32> {
self.mcast_max_groups
}
pub fn neigh_suppress(&self) -> Option<bool> {
self.neigh_suppress
}
}
impl Connection<Route> {
async fn resolve_bridge_vlan_interface(&self, config: &BridgeVlanBuilder) -> Result<u32> {
match config.device_ref() {
Some(iface) => self.resolve_interface(iface).await,
None => Err(Error::InvalidMessage(
"device name or ifindex required".into(),
)),
}
}
async fn resolve_bridge_vlan_tunnel_interface(
&self,
config: &BridgeVlanTunnelBuilder,
) -> Result<u32> {
match config.device_ref() {
Some(iface) => self.resolve_interface(iface).await,
None => Err(Error::InvalidMessage(
"device name or ifindex required".into(),
)),
}
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_bridge_vlans"))]
pub async fn get_bridge_vlans(
&self,
dev: impl Into<InterfaceRef>,
) -> Result<Vec<BridgeVlanEntry>> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.get_bridge_vlans_by_index(ifindex).await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "get_bridge_vlans_by_index")
)]
pub async fn get_bridge_vlans_by_index(&self, ifindex: u32) -> Result<Vec<BridgeVlanEntry>> {
let mut builder = MessageBuilder::new(NlMsgType::RTM_GETLINK, NLM_F_REQUEST);
let ifinfo = IfInfoMsg::new()
.with_family(libc::AF_BRIDGE as u8)
.with_index(ifindex as i32);
builder.append(&ifinfo);
builder.append_attr_u32(IflaAttr::ExtMask as u16, rtext_filter::BRVLAN);
let response = self.send_request(builder).await?;
parse_vlan_entries(&response, ifindex)
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_bridge_vlans_all"))]
pub async fn get_bridge_vlans_all(
&self,
bridge: impl Into<InterfaceRef>,
) -> Result<Vec<BridgeVlanEntry>> {
let bridge_idx = self.resolve_interface(&bridge.into()).await?;
self.get_bridge_vlans_all_by_index(bridge_idx).await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "get_bridge_vlans_all_by_index")
)]
pub async fn get_bridge_vlans_all_by_index(
&self,
bridge_idx: u32,
) -> Result<Vec<BridgeVlanEntry>> {
let mut builder = MessageBuilder::new(NlMsgType::RTM_GETLINK, NLM_F_REQUEST | NLM_F_DUMP);
let ifinfo = IfInfoMsg::new().with_family(libc::AF_BRIDGE as u8);
builder.append(&ifinfo);
builder.append_attr_u32(IflaAttr::ExtMask as u16, rtext_filter::BRVLAN);
let responses = self.send_dump(builder).await?;
let mut entries = Vec::new();
for response in responses {
if let Ok(msg_entries) = parse_vlan_entries_from_dump(&response) {
for entry in msg_entries {
if entry.ifindex == bridge_idx {
entries.push(entry);
}
}
}
}
let links = self.get_links().await?;
let port_indices: Vec<u32> = links
.iter()
.filter(|l| l.master == Some(bridge_idx))
.map(|l| l.ifindex())
.collect();
let mut filtered = Vec::new();
for entry in entries {
if entry.ifindex == bridge_idx || port_indices.contains(&entry.ifindex) {
filtered.push(entry);
}
}
Ok(filtered)
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_bridge_vlan"))]
pub async fn add_bridge_vlan(&self, config: BridgeVlanBuilder) -> Result<()> {
let ifindex = self.resolve_bridge_vlan_interface(&config).await?;
let mut builder = MessageBuilder::new(NlMsgType::RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK);
config.write_add(&mut builder, ifindex);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("add_bridge_vlan"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_bridge_vlan"))]
pub async fn del_bridge_vlan(&self, dev: impl Into<InterfaceRef>, vid: u16) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.del_bridge_vlan_by_index(ifindex, vid).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_bridge_vlan_by_index"))]
pub async fn del_bridge_vlan_by_index(&self, ifindex: u32, vid: u16) -> Result<()> {
let config = BridgeVlanBuilder::new(vid).ifindex(ifindex);
let mut builder = MessageBuilder::new(NlMsgType::RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK);
config.write_del(&mut builder, ifindex);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("del_bridge_vlan"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_bridge_vlan_range"))]
pub async fn del_bridge_vlan_range(
&self,
dev: impl Into<InterfaceRef>,
vid_start: u16,
vid_end: u16,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
let config = BridgeVlanBuilder::new(vid_start)
.ifindex(ifindex)
.range(vid_end);
let mut builder = MessageBuilder::new(NlMsgType::RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK);
config.write_del(&mut builder, ifindex);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("del_bridge_vlan_range"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "set_bridge_pvid"))]
pub async fn set_bridge_pvid(&self, dev: impl Into<InterfaceRef>, vid: u16) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.add_bridge_vlan(
BridgeVlanBuilder::new(vid)
.ifindex(ifindex)
.pvid()
.untagged(),
)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "set_bridge_pvid_by_index"))]
pub async fn set_bridge_pvid_by_index(&self, ifindex: u32, vid: u16) -> Result<()> {
self.add_bridge_vlan(
BridgeVlanBuilder::new(vid)
.ifindex(ifindex)
.pvid()
.untagged(),
)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_bridge_vlan_tagged"))]
pub async fn add_bridge_vlan_tagged(
&self,
dev: impl Into<InterfaceRef>,
vid: u16,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.add_bridge_vlan(BridgeVlanBuilder::new(vid).ifindex(ifindex))
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_bridge_vlan_range"))]
pub async fn add_bridge_vlan_range(
&self,
dev: impl Into<InterfaceRef>,
vid_start: u16,
vid_end: u16,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.add_bridge_vlan(
BridgeVlanBuilder::new(vid_start)
.ifindex(ifindex)
.range(vid_end),
)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_vlan_tunnels"))]
pub async fn get_vlan_tunnels(
&self,
dev: impl Into<InterfaceRef>,
) -> Result<Vec<BridgeVlanTunnelEntry>> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.get_vlan_tunnels_by_index(ifindex).await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "get_vlan_tunnels_by_index")
)]
pub async fn get_vlan_tunnels_by_index(
&self,
ifindex: u32,
) -> Result<Vec<BridgeVlanTunnelEntry>> {
let mut builder = MessageBuilder::new(NlMsgType::RTM_GETLINK, NLM_F_REQUEST);
let ifinfo = IfInfoMsg::new()
.with_family(libc::AF_BRIDGE as u8)
.with_index(ifindex as i32);
builder.append(&ifinfo);
builder.append_attr_u32(IflaAttr::ExtMask as u16, rtext_filter::BRVLAN);
let response = self.send_request(builder).await?;
parse_tunnel_entries(&response, ifindex)
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_vlan_tunnel"))]
pub async fn add_vlan_tunnel(&self, config: BridgeVlanTunnelBuilder) -> Result<()> {
let ifindex = self.resolve_bridge_vlan_tunnel_interface(&config).await?;
let mut builder = MessageBuilder::new(NlMsgType::RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK);
config.write_add(&mut builder, ifindex)?;
self.send_ack(builder)
.await
.map_err(|e| e.with_context("add_vlan_tunnel"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_vlan_tunnel"))]
pub async fn del_vlan_tunnel(&self, dev: impl Into<InterfaceRef>, vid: u16) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.del_vlan_tunnel_by_index(ifindex, vid).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_vlan_tunnel_by_index"))]
pub async fn del_vlan_tunnel_by_index(&self, ifindex: u32, vid: u16) -> Result<()> {
let config = BridgeVlanTunnelBuilder::new(vid, 0).ifindex(ifindex);
let mut builder = MessageBuilder::new(NlMsgType::RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK);
config.write_del(&mut builder, ifindex)?;
self.send_ack(builder)
.await
.map_err(|e| e.with_context("del_vlan_tunnel"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_vlan_tunnel_range"))]
pub async fn del_vlan_tunnel_range(
&self,
dev: impl Into<InterfaceRef>,
vid_start: u16,
vid_end: u16,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
let config = BridgeVlanTunnelBuilder::new(vid_start, 0)
.ifindex(ifindex)
.range(vid_end);
let mut builder = MessageBuilder::new(NlMsgType::RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK);
config.write_del(&mut builder, ifindex)?;
self.send_ack(builder)
.await
.map_err(|e| e.with_context("del_vlan_tunnel_range"))
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "set_bridge_vlan_global_options")
)]
pub async fn set_bridge_vlan_global_options(
&self,
config: BridgeVlanGlobalOptionsBuilder,
) -> Result<()> {
let ifindex = match config.device_ref() {
Some(iface) => self.resolve_interface(iface).await?,
None => {
return Err(Error::InvalidMessage(
"device name or ifindex required".into(),
));
}
};
let mut builder = MessageBuilder::new(NlMsgType::RTM_NEWVLAN, NLM_F_REQUEST | NLM_F_ACK);
config.write_set(&mut builder, ifindex);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("set_bridge_vlan_global_options"))
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "get_bridge_vlan_global_options")
)]
pub async fn get_bridge_vlan_global_options(
&self,
bridge: impl Into<InterfaceRef>,
) -> Result<Vec<BridgeVlanGlobalOptions>> {
let ifindex = self.resolve_interface(&bridge.into()).await?;
self.get_bridge_vlan_global_options_by_index(ifindex).await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "get_bridge_vlan_global_options_by_index")
)]
pub async fn get_bridge_vlan_global_options_by_index(
&self,
ifindex: u32,
) -> Result<Vec<BridgeVlanGlobalOptions>> {
let mut builder = MessageBuilder::new(NlMsgType::RTM_GETVLAN, NLM_F_REQUEST | NLM_F_DUMP);
let msg = BrVlanMsg::new()
.with_family(libc::AF_BRIDGE as u8)
.with_index(ifindex);
builder.append(&msg);
builder.append_attr_u32(bridge_vlandb_dump::FLAGS, bridge_vlandb_dump::DUMPF_GLOBAL);
let responses = self
.send_dump(builder)
.await
.map_err(|e| e.with_context("get_bridge_vlan_global_options"))?;
let mut entries = Vec::new();
for response in responses {
parse_global_options_from_dump(&response, ifindex, &mut entries);
}
Ok(entries)
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "set_bridge_vlan_entry_options")
)]
pub async fn set_bridge_vlan_entry_options(
&self,
config: BridgeVlanEntryOptionsBuilder,
) -> Result<()> {
let ifindex = match config.device_ref() {
Some(iface) => self.resolve_interface(iface).await?,
None => {
return Err(Error::InvalidMessage(
"device name or ifindex required".into(),
));
}
};
let mut builder = MessageBuilder::new(NlMsgType::RTM_NEWVLAN, NLM_F_REQUEST | NLM_F_ACK);
config.write_set(&mut builder, ifindex);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("set_bridge_vlan_entry_options"))
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "get_bridge_vlan_entry_options")
)]
pub async fn get_bridge_vlan_entry_options(
&self,
dev: impl Into<InterfaceRef>,
) -> Result<Vec<BridgeVlanEntryOptions>> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.get_bridge_vlan_entry_options_by_index(ifindex).await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "get_bridge_vlan_entry_options_by_index")
)]
pub async fn get_bridge_vlan_entry_options_by_index(
&self,
ifindex: u32,
) -> Result<Vec<BridgeVlanEntryOptions>> {
let mut builder = MessageBuilder::new(NlMsgType::RTM_GETVLAN, NLM_F_REQUEST | NLM_F_DUMP);
let msg = BrVlanMsg::new()
.with_family(libc::AF_BRIDGE as u8)
.with_index(ifindex);
builder.append(&msg);
let responses = self
.send_dump(builder)
.await
.map_err(|e| e.with_context("get_bridge_vlan_entry_options"))?;
let mut entries = Vec::new();
for response in responses {
parse_entry_options_from_dump(&response, ifindex, &mut entries);
}
Ok(entries)
}
}
fn parse_vlan_entries(data: &[u8], ifindex: u32) -> Result<Vec<BridgeVlanEntry>> {
let mut entries = Vec::new();
if data.len() < IfInfoMsg::SIZE {
return Ok(entries);
}
let attrs_data = &data[IfInfoMsg::SIZE..];
for (attr_type, payload) in AttrIter::new(attrs_data) {
if attr_type == IflaAttr::AfSpec as u16 {
parse_af_spec_vlans(payload, ifindex, &mut entries);
}
}
Ok(entries)
}
fn parse_vlan_entries_from_dump(data: &[u8]) -> Result<Vec<BridgeVlanEntry>> {
let mut entries = Vec::new();
for msg_result in MessageIter::new(data) {
let (_header, payload) = match msg_result {
Ok(msg) => msg,
Err(_) => continue,
};
if payload.len() < IfInfoMsg::SIZE {
continue;
}
if let Ok(ifinfo) = IfInfoMsg::from_bytes(payload) {
let ifindex = ifinfo.ifi_index as u32;
let attrs_data = &payload[IfInfoMsg::SIZE..];
for (attr_type, attr_payload) in AttrIter::new(attrs_data) {
if attr_type == IflaAttr::AfSpec as u16 {
parse_af_spec_vlans(attr_payload, ifindex, &mut entries);
}
}
}
}
Ok(entries)
}
fn parse_af_spec_vlans(data: &[u8], ifindex: u32, entries: &mut Vec<BridgeVlanEntry>) {
let mut range_start: Option<(u16, u16)> = None;
for (attr_type, payload) in AttrIter::new(data) {
if attr_type == bridge_af::IFLA_BRIDGE_VLAN_INFO
&& let Some(vlan_info) = BridgeVlanInfo::from_bytes(payload)
{
let flags = BridgeVlanFlags::from_raw(vlan_info.flags);
if vlan_info.is_range_begin() {
if let Some((prev_vid, prev_flags)) = range_start.take() {
tracing::warn!(
ifindex,
prev_vid,
new_vid = vlan_info.vid,
"BRIDGE_VLAN_INFO RANGE_BEGIN before matching RANGE_END — emitting truncated range as single VLAN",
);
entries.push(BridgeVlanEntry {
ifindex,
vid: prev_vid,
flags: BridgeVlanFlags::from_raw(prev_flags),
});
}
range_start = Some((vlan_info.vid, vlan_info.flags));
} else if vlan_info.is_range_end() {
if let Some((start_vid, start_flags)) = range_start.take() {
let range_flags = BridgeVlanFlags::from_raw(start_flags);
for vid in start_vid..=vlan_info.vid {
entries.push(BridgeVlanEntry {
ifindex,
vid,
flags: range_flags,
});
}
}
} else {
entries.push(BridgeVlanEntry {
ifindex,
vid: vlan_info.vid,
flags,
});
}
}
}
if let Some((prev_vid, prev_flags)) = range_start {
tracing::warn!(
ifindex,
prev_vid,
"BRIDGE_VLAN_INFO RANGE_BEGIN with no matching RANGE_END at end of chain — emitting as single VLAN",
);
entries.push(BridgeVlanEntry {
ifindex,
vid: prev_vid,
flags: BridgeVlanFlags::from_raw(prev_flags),
});
}
}
fn parse_tunnel_entries(data: &[u8], ifindex: u32) -> Result<Vec<BridgeVlanTunnelEntry>> {
let mut entries = Vec::new();
if data.len() < IfInfoMsg::SIZE {
return Ok(entries);
}
let attrs_data = &data[IfInfoMsg::SIZE..];
for (attr_type, payload) in AttrIter::new(attrs_data) {
if attr_type == IflaAttr::AfSpec as u16 {
parse_af_spec_tunnels(payload, ifindex, &mut entries);
}
}
Ok(entries)
}
fn parse_af_spec_tunnels(data: &[u8], ifindex: u32, entries: &mut Vec<BridgeVlanTunnelEntry>) {
let mut range_start: Option<(u16, u32)> = None;
for (attr_type, payload) in AttrIter::new(data) {
if attr_type == bridge_af::IFLA_BRIDGE_VLAN_TUNNEL_INFO {
let mut vid: Option<u16> = None;
let mut tunnel_id: Option<u32> = None;
let mut flags: u16 = 0;
for (tunnel_attr, tunnel_payload) in AttrIter::new(payload) {
match tunnel_attr {
t if t == bridge_vlan_tunnel::IFLA_BRIDGE_VLAN_TUNNEL_ID
&& tunnel_payload.len() >= 4 =>
{
tunnel_id =
Some(u32::from_ne_bytes(tunnel_payload[..4].try_into().unwrap()));
}
t if t == bridge_vlan_tunnel::IFLA_BRIDGE_VLAN_TUNNEL_VID
&& tunnel_payload.len() >= 2 =>
{
vid = Some(u16::from_ne_bytes(tunnel_payload[..2].try_into().unwrap()));
}
t if t == bridge_vlan_tunnel::IFLA_BRIDGE_VLAN_TUNNEL_FLAGS
&& tunnel_payload.len() >= 2 =>
{
flags = u16::from_ne_bytes(tunnel_payload[..2].try_into().unwrap());
}
_ => {}
}
}
if let (Some(v), Some(t)) = (vid, tunnel_id) {
let is_range_begin = flags & bridge_vlan_flags::RANGE_BEGIN != 0;
let is_range_end = flags & bridge_vlan_flags::RANGE_END != 0;
if is_range_begin {
if let Some((prev_vid, prev_tunnel_id)) = range_start.take() {
tracing::warn!(
ifindex,
prev_vid,
new_vid = v,
"BRIDGE_VLAN_TUNNEL_INFO RANGE_BEGIN before matching RANGE_END — emitting truncated range as single mapping",
);
entries.push(BridgeVlanTunnelEntry {
ifindex,
vid: prev_vid,
tunnel_id: prev_tunnel_id,
});
}
range_start = Some((v, t));
} else if is_range_end {
if let Some((start_vid, start_tunnel_id)) = range_start.take() {
for i in 0..=(v - start_vid) {
entries.push(BridgeVlanTunnelEntry {
ifindex,
vid: start_vid + i,
tunnel_id: start_tunnel_id + i as u32,
});
}
}
} else {
entries.push(BridgeVlanTunnelEntry {
ifindex,
vid: v,
tunnel_id: t,
});
}
}
}
}
}
fn parse_global_options_from_dump(
data: &[u8],
fallback_ifindex: u32,
entries: &mut Vec<BridgeVlanGlobalOptions>,
) {
for msg_result in MessageIter::new(data) {
let Ok((_header, payload)) = msg_result else {
continue;
};
let ifindex = BrVlanMsg::from_bytes(payload)
.map(|m| m.ifindex)
.unwrap_or(fallback_ifindex);
if payload.len() < BrVlanMsg::SIZE {
continue;
}
let attrs = &payload[BrVlanMsg::SIZE..];
for (attr_type, attr_payload) in AttrIter::new(attrs) {
if attr_type == bridge_vlandb::GLOBAL_OPTIONS
&& let Some(opts) = parse_one_gopts(attr_payload, ifindex)
{
entries.push(opts);
}
}
}
}
fn parse_one_gopts(payload: &[u8], ifindex: u32) -> Option<BridgeVlanGlobalOptions> {
let mut opts = BridgeVlanGlobalOptions {
ifindex,
..Default::default()
};
let mut have_id = false;
for (attr, data) in AttrIter::new(payload) {
match attr {
t if t == bridge_vlandb_gopts::ID && data.len() >= 2 => {
opts.vid = u16::from_ne_bytes(data[..2].try_into().unwrap());
have_id = true;
}
t if t == bridge_vlandb_gopts::RANGE && data.len() >= 2 => {
opts.vid_end = Some(u16::from_ne_bytes(data[..2].try_into().unwrap()));
}
t if t == bridge_vlandb_gopts::MCAST_SNOOPING && !data.is_empty() => {
opts.mcast_snooping = Some(data[0] != 0);
}
t if t == bridge_vlandb_gopts::MCAST_QUERIER && !data.is_empty() => {
opts.mcast_querier = Some(data[0] != 0);
}
t if t == bridge_vlandb_gopts::MCAST_IGMP_VERSION && !data.is_empty() => {
opts.mcast_igmp_version = Some(data[0]);
}
t if t == bridge_vlandb_gopts::MCAST_MLD_VERSION && !data.is_empty() => {
opts.mcast_mld_version = Some(data[0]);
}
t if t == bridge_vlandb_gopts::MCAST_LAST_MEMBER_CNT && data.len() >= 4 => {
opts.mcast_last_member_count =
Some(u32::from_ne_bytes(data[..4].try_into().unwrap()));
}
t if t == bridge_vlandb_gopts::MCAST_STARTUP_QUERY_CNT && data.len() >= 4 => {
opts.mcast_startup_query_count =
Some(u32::from_ne_bytes(data[..4].try_into().unwrap()));
}
t if t == bridge_vlandb_gopts::MCAST_LAST_MEMBER_INTVL && data.len() >= 8 => {
opts.mcast_last_member_interval =
Some(u64::from_ne_bytes(data[..8].try_into().unwrap()));
}
t if t == bridge_vlandb_gopts::MCAST_MEMBERSHIP_INTVL && data.len() >= 8 => {
opts.mcast_membership_interval =
Some(u64::from_ne_bytes(data[..8].try_into().unwrap()));
}
t if t == bridge_vlandb_gopts::MCAST_QUERIER_INTVL && data.len() >= 8 => {
opts.mcast_querier_interval =
Some(u64::from_ne_bytes(data[..8].try_into().unwrap()));
}
t if t == bridge_vlandb_gopts::MCAST_QUERY_INTVL && data.len() >= 8 => {
opts.mcast_query_interval =
Some(u64::from_ne_bytes(data[..8].try_into().unwrap()));
}
t if t == bridge_vlandb_gopts::MCAST_QUERY_RESPONSE_INTVL && data.len() >= 8 => {
opts.mcast_query_response_interval =
Some(u64::from_ne_bytes(data[..8].try_into().unwrap()));
}
t if t == bridge_vlandb_gopts::MCAST_STARTUP_QUERY_INTVL && data.len() >= 8 => {
opts.mcast_startup_query_interval =
Some(u64::from_ne_bytes(data[..8].try_into().unwrap()));
}
t if t == bridge_vlandb_gopts::MSTI && data.len() >= 2 => {
opts.msti = Some(u16::from_ne_bytes(data[..2].try_into().unwrap()));
}
_ => {}
}
}
have_id.then_some(opts)
}
fn parse_entry_options_from_dump(
data: &[u8],
fallback_ifindex: u32,
entries: &mut Vec<BridgeVlanEntryOptions>,
) {
for msg_result in MessageIter::new(data) {
let Ok((_header, payload)) = msg_result else {
continue;
};
let ifindex = BrVlanMsg::from_bytes(payload)
.map(|m| m.ifindex)
.unwrap_or(fallback_ifindex);
if payload.len() < BrVlanMsg::SIZE {
continue;
}
let attrs = &payload[BrVlanMsg::SIZE..];
for (attr_type, attr_payload) in AttrIter::new(attrs) {
if attr_type == bridge_vlandb::ENTRY
&& let Some(opts) = parse_one_entry(attr_payload, ifindex)
{
entries.push(opts);
}
}
}
}
fn parse_one_entry(payload: &[u8], ifindex: u32) -> Option<BridgeVlanEntryOptions> {
let mut opts = BridgeVlanEntryOptions {
ifindex,
..Default::default()
};
let mut have_vid = false;
for (attr, data) in AttrIter::new(payload) {
match attr {
t if t == bridge_vlandb_entry::INFO => {
if let Some(info) = BridgeVlanInfo::from_bytes(data) {
opts.vid = info.vid;
have_vid = true;
}
}
t if t == bridge_vlandb_entry::RANGE && data.len() >= 2 => {
opts.vid_end = Some(u16::from_ne_bytes(data[..2].try_into().unwrap()));
}
t if t == bridge_vlandb_entry::STATE && !data.is_empty() => {
opts.state = BridgeVlanState::from_raw(data[0]);
}
t if t == bridge_vlandb_entry::MCAST_ROUTER && !data.is_empty() => {
opts.mcast_router = Some(data[0]);
}
t if t == bridge_vlandb_entry::MCAST_N_GROUPS && data.len() >= 4 => {
opts.mcast_n_groups = Some(u32::from_ne_bytes(data[..4].try_into().unwrap()));
}
t if t == bridge_vlandb_entry::MCAST_MAX_GROUPS && data.len() >= 4 => {
opts.mcast_max_groups = Some(u32::from_ne_bytes(data[..4].try_into().unwrap()));
}
t if t == bridge_vlandb_entry::NEIGH_SUPPRESS && !data.is_empty() => {
opts.neigh_suppress = Some(data[0] != 0);
}
_ => {}
}
}
have_vid.then_some(opts)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bridge_vlan_flags_from_raw() {
let flags =
BridgeVlanFlags::from_raw(bridge_vlan_flags::PVID | bridge_vlan_flags::UNTAGGED);
assert!(flags.pvid);
assert!(flags.untagged);
let flags = BridgeVlanFlags::from_raw(0);
assert!(!flags.pvid);
assert!(!flags.untagged);
let flags = BridgeVlanFlags::from_raw(bridge_vlan_flags::PVID);
assert!(flags.pvid);
assert!(!flags.untagged);
}
#[test]
fn test_bridge_vlan_entry_helpers() {
let entry = BridgeVlanEntry {
ifindex: 1,
vid: 100,
flags: BridgeVlanFlags {
pvid: true,
untagged: true,
},
};
assert!(entry.is_pvid());
assert!(entry.is_untagged());
let entry = BridgeVlanEntry {
ifindex: 1,
vid: 200,
flags: BridgeVlanFlags::default(),
};
assert!(!entry.is_pvid());
assert!(!entry.is_untagged());
}
#[test]
fn test_builder_default() {
let builder = BridgeVlanBuilder::new(100);
assert_eq!(builder.vid, 100);
assert!(!builder.pvid);
assert!(!builder.untagged);
assert!(!builder.master);
assert!(builder.vid_end.is_none());
}
#[test]
fn test_builder_chain() {
let builder = BridgeVlanBuilder::new(100)
.dev("eth0")
.pvid()
.untagged()
.master();
assert_eq!(builder.dev, Some(InterfaceRef::Name("eth0".to_string())));
assert!(builder.pvid);
assert!(builder.untagged);
assert!(builder.master);
}
#[test]
fn test_builder_range() {
let builder = BridgeVlanBuilder::new(100).dev("eth0").range(110);
assert_eq!(builder.vid, 100);
assert_eq!(builder.vid_end, Some(110));
}
#[test]
fn test_builder_ifindex() {
let builder = BridgeVlanBuilder::new(100).ifindex(5);
assert_eq!(builder.dev, Some(InterfaceRef::Index(5)));
}
#[test]
fn test_build_flags() {
let builder = BridgeVlanBuilder::new(100).pvid().untagged();
let flags = builder.build_flags();
assert_eq!(flags, bridge_vlan_flags::PVID | bridge_vlan_flags::UNTAGGED);
let builder = BridgeVlanBuilder::new(100).master();
let flags = builder.build_flags();
assert_eq!(flags, bridge_vlan_flags::MASTER);
}
#[test]
fn test_tunnel_builder_new() {
let builder = BridgeVlanTunnelBuilder::new(100, 10000);
assert_eq!(builder.vid, 100);
assert_eq!(builder.tunnel_id, 10000);
assert!(builder.dev.is_none());
assert!(builder.vid_end.is_none());
}
#[test]
fn test_tunnel_builder_chain() {
let builder = BridgeVlanTunnelBuilder::new(100, 10000)
.dev("vxlan0")
.range(110);
assert_eq!(builder.dev, Some(InterfaceRef::Name("vxlan0".to_string())));
assert_eq!(builder.vid, 100);
assert_eq!(builder.vid_end, Some(110));
assert_eq!(builder.tunnel_id, 10000);
}
#[test]
fn test_tunnel_builder_ifindex() {
let builder = BridgeVlanTunnelBuilder::new(100, 10000).ifindex(5);
assert_eq!(builder.dev, Some(InterfaceRef::Index(5)));
}
#[test]
fn test_tunnel_entry_equality() {
let entry1 = BridgeVlanTunnelEntry {
ifindex: 1,
vid: 100,
tunnel_id: 10000,
};
let entry2 = BridgeVlanTunnelEntry {
ifindex: 1,
vid: 100,
tunnel_id: 10000,
};
let entry3 = BridgeVlanTunnelEntry {
ifindex: 1,
vid: 100,
tunnel_id: 10001,
};
assert_eq!(entry1, entry2);
assert_ne!(entry1, entry3);
}
#[test]
fn test_tunnel_max_id() {
assert_eq!(BridgeVlanTunnelBuilder::MAX_TUNNEL_ID, 0xFFFFFF);
}
fn push_attr(buf: &mut Vec<u8>, atype: u16, payload: &[u8]) {
let len = 4 + payload.len();
buf.extend_from_slice(&(len as u16).to_ne_bytes());
buf.extend_from_slice(&atype.to_ne_bytes());
buf.extend_from_slice(payload);
while !buf.len().is_multiple_of(4) {
buf.push(0);
}
}
#[test]
fn br_vlan_msg_layout_is_8_bytes() {
assert_eq!(BrVlanMsg::SIZE, 8);
let msg = BrVlanMsg::new()
.with_family(libc::AF_BRIDGE as u8)
.with_index(0x0102_0304);
let bytes = msg.as_bytes();
assert_eq!(bytes.len(), 8);
assert_eq!(bytes[0], libc::AF_BRIDGE as u8); assert_eq!(&bytes[4..8], &0x0102_0304u32.to_ne_bytes()); let mut oversized = bytes.to_vec();
oversized.extend_from_slice(&[0xff; 4]);
let parsed = BrVlanMsg::from_bytes(&oversized).unwrap();
assert_eq!(parsed.ifindex, 0x0102_0304);
}
#[test]
fn gopts_attr_constants_match_kernel_uapi() {
assert_eq!(bridge_vlandb::ENTRY, 1);
assert_eq!(bridge_vlandb::GLOBAL_OPTIONS, 2);
assert_eq!(bridge_vlandb_dump::FLAGS, 1);
assert_eq!(bridge_vlandb_dump::DUMPF_GLOBAL, 1 << 1);
assert_eq!(bridge_vlandb_gopts::ID, 1);
assert_eq!(bridge_vlandb_gopts::RANGE, 2);
assert_eq!(bridge_vlandb_gopts::MCAST_SNOOPING, 3);
assert_eq!(bridge_vlandb_gopts::MCAST_IGMP_VERSION, 4);
assert_eq!(bridge_vlandb_gopts::MCAST_MLD_VERSION, 5);
assert_eq!(bridge_vlandb_gopts::MCAST_LAST_MEMBER_CNT, 6);
assert_eq!(bridge_vlandb_gopts::MCAST_STARTUP_QUERY_CNT, 7);
assert_eq!(bridge_vlandb_gopts::MCAST_LAST_MEMBER_INTVL, 8);
assert_eq!(bridge_vlandb_gopts::PAD, 9);
assert_eq!(bridge_vlandb_gopts::MCAST_MEMBERSHIP_INTVL, 10);
assert_eq!(bridge_vlandb_gopts::MCAST_QUERIER_INTVL, 11);
assert_eq!(bridge_vlandb_gopts::MCAST_QUERY_INTVL, 12);
assert_eq!(bridge_vlandb_gopts::MCAST_QUERY_RESPONSE_INTVL, 13);
assert_eq!(bridge_vlandb_gopts::MCAST_STARTUP_QUERY_INTVL, 14);
assert_eq!(bridge_vlandb_gopts::MCAST_QUERIER, 15);
assert_eq!(bridge_vlandb_gopts::MCAST_ROUTER_PORTS, 16);
assert_eq!(bridge_vlandb_gopts::MCAST_QUERIER_STATE, 17);
assert_eq!(bridge_vlandb_gopts::MSTI, 18);
}
#[test]
fn gopts_builder_wire_roundtrips() {
let mut builder = MessageBuilder::new(NlMsgType::RTM_NEWVLAN, 0);
BridgeVlanGlobalOptionsBuilder::new(100)
.ifindex(7)
.mcast_snooping(true)
.mcast_querier(false)
.mcast_igmp_version(3)
.mcast_mld_version(2)
.mcast_last_member_count(2)
.mcast_startup_query_count(2)
.mcast_last_member_interval(100)
.mcast_membership_interval(26000)
.mcast_querier_interval(25500)
.mcast_query_interval(12500)
.mcast_query_response_interval(1000)
.mcast_startup_query_interval(3125)
.msti(5)
.write_set(&mut builder, 7);
let bytes = builder.finish();
let mut entries = Vec::new();
parse_global_options_from_dump(&bytes, 0, &mut entries);
assert_eq!(entries.len(), 1);
let o = &entries[0];
assert_eq!(o.ifindex(), 7);
assert_eq!(o.vid(), 100);
assert_eq!(o.vid_end(), None);
assert_eq!(o.mcast_snooping(), Some(true));
assert_eq!(o.mcast_querier(), Some(false));
assert_eq!(o.mcast_igmp_version(), Some(3));
assert_eq!(o.mcast_mld_version(), Some(2));
assert_eq!(o.mcast_last_member_count(), Some(2));
assert_eq!(o.mcast_startup_query_count(), Some(2));
assert_eq!(o.mcast_last_member_interval(), Some(100));
assert_eq!(o.mcast_membership_interval(), Some(26000));
assert_eq!(o.mcast_querier_interval(), Some(25500));
assert_eq!(o.mcast_query_interval(), Some(12500));
assert_eq!(o.mcast_query_response_interval(), Some(1000));
assert_eq!(o.mcast_startup_query_interval(), Some(3125));
assert_eq!(o.msti(), Some(5));
}
#[test]
fn gopts_builder_emits_range_and_only_set_attrs() {
let mut builder = MessageBuilder::new(NlMsgType::RTM_NEWVLAN, 0);
BridgeVlanGlobalOptionsBuilder::new(200)
.ifindex(7)
.range(210)
.mcast_snooping(false)
.write_set(&mut builder, 7);
let bytes = builder.finish();
let mut entries = Vec::new();
parse_global_options_from_dump(&bytes, 0, &mut entries);
assert_eq!(entries.len(), 1);
let o = &entries[0];
assert_eq!(o.vid(), 200);
assert_eq!(o.vid_end(), Some(210));
assert_eq!(o.mcast_snooping(), Some(false));
assert_eq!(o.mcast_querier(), None);
assert_eq!(o.msti(), None);
}
#[test]
fn gopts_parse_skips_unknown_attrs() {
let mut nest = Vec::new();
push_attr(&mut nest, bridge_vlandb_gopts::ID, &100u16.to_ne_bytes());
push_attr(&mut nest, 0xFFFE, &[1, 2, 3, 4]);
push_attr(&mut nest, bridge_vlandb_gopts::MCAST_SNOOPING, &[1]);
let o = parse_one_gopts(&nest, 3).expect("ID present → Some");
assert_eq!(o.ifindex(), 3);
assert_eq!(o.vid(), 100);
assert_eq!(o.mcast_snooping(), Some(true));
}
#[test]
fn gopts_parse_without_id_is_none() {
let mut nest = Vec::new();
push_attr(&mut nest, bridge_vlandb_gopts::MCAST_SNOOPING, &[1]);
assert!(parse_one_gopts(&nest, 3).is_none());
}
#[test]
fn gopts_parse_arbitrary_bytes_never_panics() {
for len in 0..40usize {
let data: Vec<u8> = (0..len).map(|i| (i as u8).wrapping_mul(37)).collect();
let mut entries = Vec::new();
parse_global_options_from_dump(&data, 9, &mut entries);
let _ = parse_one_gopts(&data, 9);
}
let mut nest = Vec::new();
push_attr(&mut nest, bridge_vlandb_gopts::ID, &[0x01]); push_attr(&mut nest, bridge_vlandb_gopts::MCAST_QUERIER_INTVL, &[0, 0, 0]); assert!(parse_one_gopts(&nest, 1).is_none()); }
#[test]
fn entry_attr_and_state_constants_match_kernel_uapi() {
assert_eq!(bridge_vlandb_entry::INFO, 1);
assert_eq!(bridge_vlandb_entry::RANGE, 2);
assert_eq!(bridge_vlandb_entry::STATE, 3);
assert_eq!(bridge_vlandb_entry::TUNNEL_INFO, 4);
assert_eq!(bridge_vlandb_entry::STATS, 5);
assert_eq!(bridge_vlandb_entry::MCAST_ROUTER, 6);
assert_eq!(bridge_vlandb_entry::MCAST_N_GROUPS, 7);
assert_eq!(bridge_vlandb_entry::MCAST_MAX_GROUPS, 8);
assert_eq!(bridge_vlandb_entry::NEIGH_SUPPRESS, 9);
assert_eq!(br_state::DISABLED, 0);
assert_eq!(br_state::FORWARDING, 3);
assert_eq!(br_state::BLOCKING, 4);
}
#[test]
fn bridge_vlan_state_round_trips_all_variants() {
for s in [
BridgeVlanState::Disabled,
BridgeVlanState::Listening,
BridgeVlanState::Learning,
BridgeVlanState::Forwarding,
BridgeVlanState::Blocking,
] {
assert_eq!(BridgeVlanState::from_raw(s.to_raw()), Some(s));
}
assert_eq!(BridgeVlanState::from_raw(99), None);
}
#[test]
fn entry_builder_wire_roundtrips() {
let mut builder = MessageBuilder::new(NlMsgType::RTM_NEWVLAN, 0);
BridgeVlanEntryOptionsBuilder::new(100)
.ifindex(7)
.state(BridgeVlanState::Forwarding)
.mcast_router(2)
.mcast_max_groups(64)
.neigh_suppress(true)
.write_set(&mut builder, 7);
let bytes = builder.finish();
let mut entries = Vec::new();
parse_entry_options_from_dump(&bytes, 0, &mut entries);
assert_eq!(entries.len(), 1);
let o = &entries[0];
assert_eq!(o.ifindex(), 7);
assert_eq!(o.vid(), 100);
assert_eq!(o.vid_end(), None);
assert_eq!(o.state(), Some(BridgeVlanState::Forwarding));
assert_eq!(o.mcast_router(), Some(2));
assert_eq!(o.mcast_max_groups(), Some(64));
assert_eq!(o.neigh_suppress(), Some(true));
assert_eq!(o.mcast_n_groups(), None);
}
#[test]
fn entry_builder_emits_range_and_only_set_attrs() {
let mut builder = MessageBuilder::new(NlMsgType::RTM_NEWVLAN, 0);
BridgeVlanEntryOptionsBuilder::new(10)
.ifindex(7)
.range(20)
.state(BridgeVlanState::Blocking)
.write_set(&mut builder, 7);
let bytes = builder.finish();
let mut entries = Vec::new();
parse_entry_options_from_dump(&bytes, 0, &mut entries);
assert_eq!(entries.len(), 1);
let o = &entries[0];
assert_eq!(o.vid(), 10);
assert_eq!(o.vid_end(), Some(20));
assert_eq!(o.state(), Some(BridgeVlanState::Blocking));
assert_eq!(o.mcast_router(), None);
assert_eq!(o.neigh_suppress(), None);
}
#[test]
fn entry_parse_without_info_is_none() {
let mut nest = Vec::new();
push_attr(&mut nest, bridge_vlandb_entry::STATE, &[br_state::FORWARDING]);
assert!(parse_one_entry(&nest, 3).is_none());
}
#[test]
fn entry_parse_arbitrary_bytes_never_panics() {
for len in 0..40usize {
let data: Vec<u8> = (0..len).map(|i| (i as u8).wrapping_mul(53)).collect();
let mut entries = Vec::new();
parse_entry_options_from_dump(&data, 9, &mut entries);
let _ = parse_one_entry(&data, 9);
}
}
}