From faffa52a7dad3fb05c1b5c314a522b25e4cffaef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Wed, 13 Dec 2023 10:02:53 +0100 Subject: [PATCH] link: add bridge_port MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add parsing support for the port data (IFLA_INFO_PORT_DATA) attributes of the "bridge" port kind (IFLA_INFO_PORT_KIND). Signed-off-by: Íñigo Huguet --- src/link/link_info/bridge_port.rs | 622 ++++++++++++++++++++++++++++++ src/link/link_info/info_port.rs | 44 ++- src/link/link_info/mod.rs | 4 + src/link/mod.rs | 14 +- src/link/tests/bridge.rs | 115 +++++- 5 files changed, 771 insertions(+), 28 deletions(-) create mode 100644 src/link/link_info/bridge_port.rs diff --git a/src/link/link_info/bridge_port.rs b/src/link/link_info/bridge_port.rs new file mode 100644 index 00000000..d121fe58 --- /dev/null +++ b/src/link/link_info/bridge_port.rs @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: MIT +use anyhow::Context; +use byteorder::{ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer}, + parsers::{parse_mac, parse_u16, parse_u32, parse_u64, parse_u8}, + traits::Parseable, + DecodeError, +}; + +const IFLA_BRPORT_STATE: u16 = 1; +const IFLA_BRPORT_PRIORITY: u16 = 2; +const IFLA_BRPORT_COST: u16 = 3; +const IFLA_BRPORT_MODE: u16 = 4; +const IFLA_BRPORT_GUARD: u16 = 5; +const IFLA_BRPORT_PROTECT: u16 = 6; +const IFLA_BRPORT_FAST_LEAVE: u16 = 7; +const IFLA_BRPORT_LEARNING: u16 = 8; +const IFLA_BRPORT_UNICAST_FLOOD: u16 = 9; +const IFLA_BRPORT_PROXYARP: u16 = 10; +// only used in one driver, we don't know if its type is stable: +// const IFLA_BRPORT_LEARNING_SYNC: u16 = 11; +const IFLA_BRPORT_PROXYARP_WIFI: u16 = 12; +const IFLA_BRPORT_ROOT_ID: u16 = 13; +const IFLA_BRPORT_BRIDGE_ID: u16 = 14; +const IFLA_BRPORT_DESIGNATED_PORT: u16 = 15; +const IFLA_BRPORT_DESIGNATED_COST: u16 = 16; +const IFLA_BRPORT_ID: u16 = 17; +const IFLA_BRPORT_NO: u16 = 18; +const IFLA_BRPORT_TOPOLOGY_CHANGE_ACK: u16 = 19; +const IFLA_BRPORT_CONFIG_PENDING: u16 = 20; +const IFLA_BRPORT_MESSAGE_AGE_TIMER: u16 = 21; +const IFLA_BRPORT_FORWARD_DELAY_TIMER: u16 = 22; +const IFLA_BRPORT_HOLD_TIMER: u16 = 23; +const IFLA_BRPORT_FLUSH: u16 = 24; +const IFLA_BRPORT_MULTICAST_ROUTER: u16 = 25; +// const IFLA_BRPORT_PAD: u16 = 26; +const IFLA_BRPORT_MCAST_FLOOD: u16 = 27; +const IFLA_BRPORT_MCAST_TO_UCAST: u16 = 28; +const IFLA_BRPORT_VLAN_TUNNEL: u16 = 29; +const IFLA_BRPORT_BCAST_FLOOD: u16 = 30; +const IFLA_BRPORT_GROUP_FWD_MASK: u16 = 31; +const IFLA_BRPORT_NEIGH_SUPPRESS: u16 = 32; +const IFLA_BRPORT_ISOLATED: u16 = 33; +const IFLA_BRPORT_BACKUP_PORT: u16 = 34; +const IFLA_BRPORT_MRP_RING_OPEN: u16 = 35; +const IFLA_BRPORT_MRP_IN_OPEN: u16 = 36; +const IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT: u16 = 37; +const IFLA_BRPORT_MCAST_EHT_HOSTS_CNT: u16 = 38; +const IFLA_BRPORT_LOCKED: u16 = 39; +const IFLA_BRPORT_MAB: u16 = 40; +const IFLA_BRPORT_MCAST_N_GROUPS: u16 = 41; +const IFLA_BRPORT_MCAST_MAX_GROUPS: u16 = 42; +const IFLA_BRPORT_NEIGH_VLAN_SUPPRESS: u16 = 43; +const IFLA_BRPORT_BACKUP_NHID: u16 = 44; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum InfoBridgePort { + State(BridgePortState), + Priority(u16), + Cost(u32), + HairpinMode(bool), + Guard(bool), + Protect(bool), + FastLeave(bool), + Learning(bool), + UnicastFlood(bool), + ProxyARP(bool), + ProxyARPWifi(bool), + RootId((u16, [u8; 6])), + BridgeId((u16, [u8; 6])), + DesignatedPort(u16), + DesignatedCost(u16), + PortId(u16), + PortNumber(u16), + TopologyChangeAck(bool), + ConfigPending(bool), + MessageAgeTimer(u64), + ForwardDelayTimer(u64), + HoldTimer(u64), + Flush, + MulticastRouter(BridgePortMulticastRouter), + MulticastFlood(bool), + MulticastToUnicast(bool), + VlanTunnel(bool), + BroadcastFlood(bool), + GroupFwdMask(u16), + NeighSupress(bool), + Isolated(bool), + BackupPort(u32), + MrpRingOpen(bool), + MrpInOpen(bool), + MulticastEhtHostsLimit(u32), + MulticastEhtHostsCnt(u32), + Locked(bool), + Mab(bool), + MulticastNGroups(u32), + MulticastMaxGroups(u32), + NeighVlanSupress(bool), + BackupNextHopId(u32), + Other(DefaultNla), +} + +impl Nla for InfoBridgePort { + fn value_len(&self) -> usize { + match self { + InfoBridgePort::Flush => 0, + InfoBridgePort::State(_) + | InfoBridgePort::HairpinMode(_) + | InfoBridgePort::Guard(_) + | InfoBridgePort::Protect(_) + | InfoBridgePort::FastLeave(_) + | InfoBridgePort::Learning(_) + | InfoBridgePort::UnicastFlood(_) + | InfoBridgePort::ProxyARP(_) + | InfoBridgePort::ProxyARPWifi(_) + | InfoBridgePort::TopologyChangeAck(_) + | InfoBridgePort::ConfigPending(_) + | InfoBridgePort::MulticastRouter(_) + | InfoBridgePort::MulticastFlood(_) + | InfoBridgePort::MulticastToUnicast(_) + | InfoBridgePort::VlanTunnel(_) + | InfoBridgePort::BroadcastFlood(_) + | InfoBridgePort::NeighSupress(_) + | InfoBridgePort::Isolated(_) + | InfoBridgePort::MrpRingOpen(_) + | InfoBridgePort::MrpInOpen(_) + | InfoBridgePort::Locked(_) + | InfoBridgePort::Mab(_) + | InfoBridgePort::NeighVlanSupress(_) => 1, + InfoBridgePort::Priority(_) + | InfoBridgePort::DesignatedPort(_) + | InfoBridgePort::DesignatedCost(_) + | InfoBridgePort::PortId(_) + | InfoBridgePort::PortNumber(_) + | InfoBridgePort::GroupFwdMask(_) => 2, + InfoBridgePort::Cost(_) + | InfoBridgePort::BackupPort(_) + | InfoBridgePort::MulticastEhtHostsLimit(_) + | InfoBridgePort::MulticastEhtHostsCnt(_) + | InfoBridgePort::MulticastNGroups(_) + | InfoBridgePort::MulticastMaxGroups(_) + | InfoBridgePort::BackupNextHopId(_) => 4, + InfoBridgePort::RootId(_) + | InfoBridgePort::BridgeId(_) + | InfoBridgePort::MessageAgeTimer(_) + | InfoBridgePort::ForwardDelayTimer(_) + | InfoBridgePort::HoldTimer(_) => 8, + InfoBridgePort::Other(nla) => nla.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + InfoBridgePort::Flush => (), + InfoBridgePort::HairpinMode(value) + | InfoBridgePort::Guard(value) + | InfoBridgePort::Protect(value) + | InfoBridgePort::FastLeave(value) + | InfoBridgePort::Learning(value) + | InfoBridgePort::UnicastFlood(value) + | InfoBridgePort::ProxyARP(value) + | InfoBridgePort::TopologyChangeAck(value) + | InfoBridgePort::ConfigPending(value) + | InfoBridgePort::ProxyARPWifi(value) + | InfoBridgePort::MulticastFlood(value) + | InfoBridgePort::MulticastToUnicast(value) + | InfoBridgePort::VlanTunnel(value) + | InfoBridgePort::BroadcastFlood(value) + | InfoBridgePort::NeighSupress(value) + | InfoBridgePort::Isolated(value) + | InfoBridgePort::MrpRingOpen(value) + | InfoBridgePort::MrpInOpen(value) + | InfoBridgePort::Locked(value) + | InfoBridgePort::Mab(value) + | InfoBridgePort::NeighVlanSupress(value) => { + buffer[0] = if *value { 1 } else { 0 } + } + InfoBridgePort::Priority(value) + | InfoBridgePort::DesignatedPort(value) + | InfoBridgePort::DesignatedCost(value) + | InfoBridgePort::PortId(value) + | InfoBridgePort::PortNumber(value) + | InfoBridgePort::GroupFwdMask(value) => { + NativeEndian::write_u16(buffer, *value) + } + InfoBridgePort::Cost(value) + | InfoBridgePort::BackupPort(value) + | InfoBridgePort::MulticastEhtHostsLimit(value) + | InfoBridgePort::MulticastEhtHostsCnt(value) + | InfoBridgePort::MulticastNGroups(value) + | InfoBridgePort::MulticastMaxGroups(value) + | InfoBridgePort::BackupNextHopId(value) => { + NativeEndian::write_u32(buffer, *value) + } + InfoBridgePort::MessageAgeTimer(value) + | InfoBridgePort::ForwardDelayTimer(value) + | InfoBridgePort::HoldTimer(value) => { + NativeEndian::write_u64(buffer, *value) + } + InfoBridgePort::RootId((prio, addr)) + | InfoBridgePort::BridgeId((prio, addr)) => { + NativeEndian::write_u16(buffer, *prio); + buffer[2..].copy_from_slice(&addr[..]); + } + InfoBridgePort::State(state) => buffer[0] = (*state).into(), + InfoBridgePort::MulticastRouter(mcast_router) => { + buffer[0] = (*mcast_router).into() + } + InfoBridgePort::Other(nla) => nla.emit_value(buffer), + } + } + + fn kind(&self) -> u16 { + match self { + InfoBridgePort::State(_) => IFLA_BRPORT_STATE, + InfoBridgePort::Priority(_) => IFLA_BRPORT_PRIORITY, + InfoBridgePort::Cost(_) => IFLA_BRPORT_COST, + InfoBridgePort::HairpinMode(_) => IFLA_BRPORT_MODE, + InfoBridgePort::Guard(_) => IFLA_BRPORT_GUARD, + InfoBridgePort::Protect(_) => IFLA_BRPORT_PROTECT, + InfoBridgePort::FastLeave(_) => IFLA_BRPORT_FAST_LEAVE, + InfoBridgePort::Learning(_) => IFLA_BRPORT_LEARNING, + InfoBridgePort::UnicastFlood(_) => IFLA_BRPORT_UNICAST_FLOOD, + InfoBridgePort::ProxyARP(_) => IFLA_BRPORT_PROXYARP, + InfoBridgePort::ProxyARPWifi(_) => IFLA_BRPORT_PROXYARP_WIFI, + InfoBridgePort::RootId(_) => IFLA_BRPORT_ROOT_ID, + InfoBridgePort::BridgeId(_) => IFLA_BRPORT_BRIDGE_ID, + InfoBridgePort::DesignatedPort(_) => IFLA_BRPORT_DESIGNATED_PORT, + InfoBridgePort::DesignatedCost(_) => IFLA_BRPORT_DESIGNATED_COST, + InfoBridgePort::PortId(_) => IFLA_BRPORT_ID, + InfoBridgePort::PortNumber(_) => IFLA_BRPORT_NO, + InfoBridgePort::TopologyChangeAck(_) => { + IFLA_BRPORT_TOPOLOGY_CHANGE_ACK + } + InfoBridgePort::ConfigPending(_) => IFLA_BRPORT_CONFIG_PENDING, + InfoBridgePort::MessageAgeTimer(_) => IFLA_BRPORT_MESSAGE_AGE_TIMER, + InfoBridgePort::ForwardDelayTimer(_) => { + IFLA_BRPORT_FORWARD_DELAY_TIMER + } + InfoBridgePort::HoldTimer(_) => IFLA_BRPORT_HOLD_TIMER, + InfoBridgePort::Flush => IFLA_BRPORT_FLUSH, + InfoBridgePort::MulticastRouter(_) => IFLA_BRPORT_MULTICAST_ROUTER, + InfoBridgePort::MulticastFlood(_) => IFLA_BRPORT_MCAST_FLOOD, + InfoBridgePort::MulticastToUnicast(_) => IFLA_BRPORT_MCAST_TO_UCAST, + InfoBridgePort::VlanTunnel(_) => IFLA_BRPORT_VLAN_TUNNEL, + InfoBridgePort::BroadcastFlood(_) => IFLA_BRPORT_BCAST_FLOOD, + InfoBridgePort::GroupFwdMask(_) => IFLA_BRPORT_GROUP_FWD_MASK, + InfoBridgePort::NeighSupress(_) => IFLA_BRPORT_NEIGH_SUPPRESS, + InfoBridgePort::Isolated(_) => IFLA_BRPORT_ISOLATED, + InfoBridgePort::BackupPort(_) => IFLA_BRPORT_BACKUP_PORT, + InfoBridgePort::MrpRingOpen(_) => IFLA_BRPORT_MRP_RING_OPEN, + InfoBridgePort::MrpInOpen(_) => IFLA_BRPORT_MRP_IN_OPEN, + InfoBridgePort::MulticastEhtHostsLimit(_) => { + IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT + } + InfoBridgePort::MulticastEhtHostsCnt(_) => { + IFLA_BRPORT_MCAST_EHT_HOSTS_CNT + } + InfoBridgePort::Locked(_) => IFLA_BRPORT_LOCKED, + InfoBridgePort::Mab(_) => IFLA_BRPORT_MAB, + InfoBridgePort::MulticastNGroups(_) => IFLA_BRPORT_MCAST_N_GROUPS, + InfoBridgePort::MulticastMaxGroups(_) => { + IFLA_BRPORT_MCAST_MAX_GROUPS + } + InfoBridgePort::NeighVlanSupress(_) => { + IFLA_BRPORT_NEIGH_VLAN_SUPPRESS + } + InfoBridgePort::BackupNextHopId(_) => IFLA_BRPORT_BACKUP_NHID, + InfoBridgePort::Other(nla) => nla.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for InfoBridgePort +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + + Ok(match buf.kind() { + IFLA_BRPORT_STATE => InfoBridgePort::State( + parse_u8(payload) + .with_context(|| { + format!("invalid IFLA_BRPORT_STATE {payload:?}") + })? + .into(), + ), + IFLA_BRPORT_PRIORITY => { + InfoBridgePort::Priority(parse_u16(payload).with_context( + || format!("invalid IFLA_BRPORT_PRIORITY {payload:?}"), + )?) + } + IFLA_BRPORT_COST => { + InfoBridgePort::Cost(parse_u32(payload).with_context(|| { + format!("invalid IFLA_BRPORT_COST {payload:?}") + })?) + } + IFLA_BRPORT_MODE => InfoBridgePort::HairpinMode( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_MODE {payload:?}") + })? > 0, + ), + IFLA_BRPORT_GUARD => InfoBridgePort::Guard( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_GUARD {payload:?}") + })? > 0, + ), + IFLA_BRPORT_PROTECT => InfoBridgePort::Protect( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_PROTECT {payload:?}") + })? > 0, + ), + IFLA_BRPORT_FAST_LEAVE => InfoBridgePort::FastLeave( + parse_u8(payload).with_context(|| { + format!( + "invalid IFLA_BRPORT_FAST_LEAVE {payload:?}" + ) + })? > 0, + ), + IFLA_BRPORT_LEARNING => InfoBridgePort::Learning( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_LEARNING {payload:?}") + })? > 0, + ), + IFLA_BRPORT_UNICAST_FLOOD => InfoBridgePort::UnicastFlood( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_UNICAST_FLOOD {payload:?}") + })? > 0, + ), + IFLA_BRPORT_PROXYARP => InfoBridgePort::ProxyARP( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_PROXYARP {payload:?}") + })? > 0, + ), + IFLA_BRPORT_PROXYARP_WIFI => InfoBridgePort::ProxyARPWifi( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_PROXYARP_WIFI {payload:?}") + })? > 0, + ), + IFLA_BRPORT_ROOT_ID => { + if payload.len() != 8 { + return Err(format!( + "invalid IFLA_BRPORT_ROOT_ID {payload:?}" + ) + .into()); + } + let prio = NativeEndian::read_u16(&payload[0..2]); + let addr = parse_mac(&payload[2..]).with_context(|| { + format!("invalid IFLA_BRPORT_ROOT_ID {payload:?}") + })?; + InfoBridgePort::RootId((prio, addr)) + } + IFLA_BRPORT_BRIDGE_ID => { + if payload.len() != 8 { + return Err(format!( + "invalid IFLA_BRPORT_BRIDGE_ID {payload:?}" + ) + .into()); + } + let prio = NativeEndian::read_u16(&payload[0..2]); + let addr = parse_mac(&payload[2..]).with_context(|| { + format!("invalid IFLA_BRPORT_BRIDGE_ID {payload:?}") + })?; + InfoBridgePort::BridgeId((prio, addr)) + } + IFLA_BRPORT_DESIGNATED_PORT => InfoBridgePort::DesignatedPort( + parse_u16(payload).with_context(|| { + format!("invalid IFLA_BRPORT_DESIGNATED_PORT {payload:?}") + })?, + ), + IFLA_BRPORT_DESIGNATED_COST => InfoBridgePort::DesignatedCost( + parse_u16(payload).with_context(|| { + format!("invalid IFLA_BRPORT_DESIGNATED_COST {payload:?}") + })?, + ), + IFLA_BRPORT_ID => { + InfoBridgePort::PortId(parse_u16(payload).with_context(|| { + format!("invalid IFLA_BRPORT_ID {payload:?}") + })?) + } + IFLA_BRPORT_NO => { + InfoBridgePort::PortNumber(parse_u16(payload).with_context(|| { + format!("invalid IFLA_BRPORT_NO {payload:?}") + })?) + } + IFLA_BRPORT_TOPOLOGY_CHANGE_ACK => { + InfoBridgePort::TopologyChangeAck( + parse_u8(payload).with_context(|| { + format!( + "invalid IFLA_BRPORT_TOPOLOGY_CHANGE_ACK {payload:?}" + ) + })? > 0, + ) + } + IFLA_BRPORT_CONFIG_PENDING => InfoBridgePort::ConfigPending( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_CONFIG_PENDING {payload:?}") + })? > 0, + ), + IFLA_BRPORT_MESSAGE_AGE_TIMER => InfoBridgePort::MessageAgeTimer( + parse_u64(payload).with_context(|| { + format!("invalid IFLA_BRPORT_MESSAGE_AGE_TIMER {payload:?}") + })?, + ), + IFLA_BRPORT_FORWARD_DELAY_TIMER => { + InfoBridgePort::ForwardDelayTimer( + parse_u64(payload).with_context(|| { + format!( + "invalid IFLA_BRPORT_FORWARD_DELAY_TIMER {payload:?}" + ) + })?, + ) + } + IFLA_BRPORT_HOLD_TIMER => { + InfoBridgePort::HoldTimer(parse_u64(payload).with_context( + || format!("invalid IFLA_BRPORT_HOLD_TIMER {payload:?}"), + )?) + } + IFLA_BRPORT_FLUSH => InfoBridgePort::Flush, + IFLA_BRPORT_MULTICAST_ROUTER => InfoBridgePort::MulticastRouter( + parse_u8(payload) + .with_context(|| { + format!( + "invalid IFLA_BRPORT_MULTICAST_ROUTER {payload:?}" + ) + })? + .into(), + ), + IFLA_BRPORT_MCAST_FLOOD => InfoBridgePort::MulticastFlood( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_MCAST_FLOOD {payload:?}") + })? > 0, + ), + IFLA_BRPORT_MCAST_TO_UCAST => InfoBridgePort::MulticastToUnicast( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_MCAST_TO_UCAST {payload:?}") + })? > 0, + ), + IFLA_BRPORT_VLAN_TUNNEL => InfoBridgePort::VlanTunnel( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_VLAN_TUNNEL {payload:?}") + })? > 0, + ), + IFLA_BRPORT_BCAST_FLOOD => InfoBridgePort::BroadcastFlood( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_BCAST_FLOOD {payload:?}") + })? > 0, + ), + IFLA_BRPORT_GROUP_FWD_MASK => InfoBridgePort::GroupFwdMask( + parse_u16(payload).with_context(|| { + format!("invalid IFLA_BRPORT_GROUP_FWD_MASK {payload:?}") + })?, + ), + IFLA_BRPORT_NEIGH_SUPPRESS => InfoBridgePort::NeighSupress( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_NEIGH_SUPPRESS {payload:?}") + })? > 0, + ), + IFLA_BRPORT_ISOLATED => InfoBridgePort::Isolated( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_ISOLATED {payload:?}") + })? > 0, + ), + IFLA_BRPORT_BACKUP_PORT => { + InfoBridgePort::BackupPort(parse_u32(payload).with_context( + || format!("invalid IFLA_BRPORT_BACKUP_PORT {payload:?}"), + )?) + } + IFLA_BRPORT_MRP_RING_OPEN => InfoBridgePort::MrpRingOpen( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_MRP_RING_OPEN {payload:?}") + })? > 0, + ), + IFLA_BRPORT_MRP_IN_OPEN => InfoBridgePort::MrpInOpen( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_MRP_IN_OPEN {payload:?}") + })? > 0, + ), + IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT => { + InfoBridgePort::MulticastEhtHostsLimit( + parse_u32(payload).with_context(|| { + format!( + "invalid IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT {payload:?}" + ) + })?, + ) + } + IFLA_BRPORT_MCAST_EHT_HOSTS_CNT => { + InfoBridgePort::MulticastEhtHostsCnt( + parse_u32(payload).with_context(|| { + format!( + "invalid IFLA_BRPORT_MCAST_EHT_HOSTS_CNT {payload:?}" + ) + })? + ) + } + IFLA_BRPORT_LOCKED => InfoBridgePort::Locked( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_LOCKED {payload:?}") + })? > 0, + ), + IFLA_BRPORT_MAB => InfoBridgePort::Mab( + parse_u8(payload).with_context(|| { + format!("invalid IFLA_BRPORT_MAB {payload:?}") + })? > 0, + ), + IFLA_BRPORT_MCAST_N_GROUPS => InfoBridgePort::MulticastNGroups( + parse_u32(payload).with_context(|| { + format!("invalid IFLA_BRPORT_MCAST_N_GROUPS {payload:?}") + })?, + ), + IFLA_BRPORT_MCAST_MAX_GROUPS => InfoBridgePort::MulticastMaxGroups( + parse_u32(payload).with_context(|| { + format!("invalid IFLA_BRPORT_MCAST_MAX_GROUPS {payload:?}") + })?, + ), + IFLA_BRPORT_NEIGH_VLAN_SUPPRESS => InfoBridgePort::NeighVlanSupress( + parse_u8(payload).with_context(|| { + format!( + "invalid IFLA_BRPORT_NEIGH_VLAN_SUPPRESS {payload:?}" + ) + })? > 0, + ), + IFLA_BRPORT_BACKUP_NHID => InfoBridgePort::BackupNextHopId( + parse_u32(payload).with_context(|| { + format!("invalid IFLA_BRPORT_BACKUP_NHID {payload:?}") + })?, + ), + kind => InfoBridgePort::Other( + DefaultNla::parse(buf).with_context(|| { + format!( + "failed to parse bridge port NLA of type '{kind}' into DefaultNla" + ) + })?, + ), + }) + } +} + +const BR_STATE_DISABLED: u8 = 0; +const BR_STATE_LISTENING: u8 = 1; +const BR_STATE_LEARNING: u8 = 2; +const BR_STATE_FORWARDING: u8 = 3; +const BR_STATE_BLOCKING: u8 = 4; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[non_exhaustive] +pub enum BridgePortState { + Disabled, + Listening, + Learning, + Forwarding, + Blocking, + Other(u8), +} + +impl From for BridgePortState { + fn from(value: u8) -> Self { + match value { + BR_STATE_DISABLED => BridgePortState::Disabled, + BR_STATE_LISTENING => BridgePortState::Listening, + BR_STATE_LEARNING => BridgePortState::Learning, + BR_STATE_FORWARDING => BridgePortState::Forwarding, + BR_STATE_BLOCKING => BridgePortState::Blocking, + _ => BridgePortState::Other(value), + } + } +} + +impl From for u8 { + fn from(value: BridgePortState) -> Self { + match value { + BridgePortState::Disabled => BR_STATE_DISABLED, + BridgePortState::Listening => BR_STATE_LISTENING, + BridgePortState::Learning => BR_STATE_LEARNING, + BridgePortState::Forwarding => BR_STATE_FORWARDING, + BridgePortState::Blocking => BR_STATE_BLOCKING, + BridgePortState::Other(v) => v, + } + } +} + +const MDB_RTR_TYPE_DISABLED: u8 = 0; +const MDB_RTR_TYPE_TEMP_QUERY: u8 = 1; +const MDB_RTR_TYPE_PERM: u8 = 2; +const MDB_RTR_TYPE_TEMP: u8 = 3; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[non_exhaustive] +pub enum BridgePortMulticastRouter { + Disabled, + TempQuery, + Perm, + Temp, + Other(u8), +} + +impl From for BridgePortMulticastRouter { + fn from(value: u8) -> Self { + match value { + MDB_RTR_TYPE_DISABLED => BridgePortMulticastRouter::Disabled, + MDB_RTR_TYPE_TEMP_QUERY => BridgePortMulticastRouter::TempQuery, + MDB_RTR_TYPE_PERM => BridgePortMulticastRouter::Perm, + MDB_RTR_TYPE_TEMP => BridgePortMulticastRouter::Temp, + _ => BridgePortMulticastRouter::Other(value), + } + } +} + +impl From for u8 { + fn from(value: BridgePortMulticastRouter) -> Self { + match value { + BridgePortMulticastRouter::Disabled => MDB_RTR_TYPE_DISABLED, + BridgePortMulticastRouter::TempQuery => MDB_RTR_TYPE_TEMP_QUERY, + BridgePortMulticastRouter::Perm => MDB_RTR_TYPE_PERM, + BridgePortMulticastRouter::Temp => MDB_RTR_TYPE_TEMP, + BridgePortMulticastRouter::Other(v) => v, + } + } +} diff --git a/src/link/link_info/info_port.rs b/src/link/link_info/info_port.rs index 24d53fa0..67f3f966 100644 --- a/src/link/link_info/info_port.rs +++ b/src/link/link_info/info_port.rs @@ -7,9 +7,10 @@ use netlink_packet_utils::{ DecodeError, Emitable, Parseable, }; -use super::super::InfoBondPort; +use super::super::{InfoBondPort, InfoBridgePort}; const BOND: &str = "bond"; +const BRIDGE: &str = "bridge"; const IFLA_INFO_PORT_KIND: u16 = 4; const IFLA_INFO_PORT_DATA: u16 = 5; @@ -18,6 +19,7 @@ const IFLA_INFO_PORT_DATA: u16 = 5; #[non_exhaustive] pub enum InfoPortKind { Bond, + Bridge, Other(String), } @@ -28,6 +30,7 @@ impl std::fmt::Display for InfoPortKind { "{}", match self { Self::Bond => BOND, + Self::Bridge => BRIDGE, Self::Other(s) => s.as_str(), } ) @@ -38,6 +41,7 @@ impl Nla for InfoPortKind { fn value_len(&self) -> usize { let len = match self { Self::Bond => BOND.len(), + Self::Bridge => BRIDGE.len(), Self::Other(s) => s.len(), }; len + 1 @@ -46,6 +50,7 @@ impl Nla for InfoPortKind { fn emit_value(&self, buffer: &mut [u8]) { let s = match self { Self::Bond => BOND, + Self::Bridge => BRIDGE, Self::Other(s) => s.as_str(), }; buffer[..s.len()].copy_from_slice(s.as_bytes()); @@ -70,6 +75,7 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoPortKind { .context("invalid IFLA_INFO_PORT_KIND value")?; Ok(match s.as_str() { BOND => Self::Bond, + BRIDGE => Self::Bridge, _ => Self::Other(s), }) } @@ -79,6 +85,7 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoPortKind { #[non_exhaustive] pub enum InfoPortData { BondPort(Vec), + BridgePort(Vec), Other(Vec), } @@ -86,6 +93,7 @@ impl Nla for InfoPortData { fn value_len(&self) -> usize { match self { Self::BondPort(nlas) => nlas.as_slice().buffer_len(), + Self::BridgePort(nlas) => nlas.as_slice().buffer_len(), Self::Other(bytes) => bytes.len(), } } @@ -93,6 +101,7 @@ impl Nla for InfoPortData { fn emit_value(&self, buffer: &mut [u8]) { match self { Self::BondPort(nlas) => nlas.as_slice().emit(buffer), + Self::BridgePort(nlas) => nlas.as_slice().emit(buffer), Self::Other(bytes) => buffer.copy_from_slice(bytes), } } @@ -107,23 +116,20 @@ impl InfoPortData { payload: &[u8], kind: InfoPortKind, ) -> Result { - Ok(match kind { - InfoPortKind::Bond => { - let mut v = Vec::new(); - for nla in NlasIterator::new(payload) { - let nla = &nla.context(format!( - "failed to parse IFLA_INFO_PORT_DATA \ - (IFLA_INFO_PORT_KIND is '{kind}')" - ))?; - let parsed = InfoBondPort::parse(nla).context(format!( - "failed to parse IFLA_INFO_PORT_DATA \ - (IFLA_INFO_PORT_KIND is '{kind}')" - ))?; - v.push(parsed); - } - InfoPortData::BondPort(v) - } - InfoPortKind::Other(_) => InfoPortData::Other(payload.to_vec()), - }) + let port_data = match kind { + InfoPortKind::Bond => NlasIterator::new(payload) + .map(|nla| nla.and_then(|nla| InfoBondPort::parse(&nla))) + .collect::, _>>() + .map(InfoPortData::BondPort), + InfoPortKind::Bridge => NlasIterator::new(payload) + .map(|nla| nla.and_then(|nla| InfoBridgePort::parse(&nla))) + .collect::, _>>() + .map(InfoPortData::BridgePort), + InfoPortKind::Other(_) => Ok(InfoPortData::Other(payload.to_vec())), + }; + + Ok(port_data.context(format!( + "failed to parse IFLA_INFO_PORT_DATA (IFLA_INFO_PORT_KIND is '{kind}')" + ))?) } } diff --git a/src/link/link_info/mod.rs b/src/link/link_info/mod.rs index b7a09f6a..c32bac73 100644 --- a/src/link/link_info/mod.rs +++ b/src/link/link_info/mod.rs @@ -3,6 +3,7 @@ mod bond; mod bond_port; mod bridge; +mod bridge_port; mod gre; mod gre6; mod gre_tap; @@ -29,6 +30,9 @@ mod xstats; pub use self::bond::{BondAdInfo, InfoBond}; pub use self::bond_port::{BondPortState, InfoBondPort, MiiStatus}; pub use self::bridge::{BridgeQuerierState, InfoBridge}; +pub use self::bridge_port::{ + BridgePortMulticastRouter, BridgePortState, InfoBridgePort, +}; pub use self::gre::InfoGreTun; pub use self::gre6::InfoGreTun6; pub use self::gre_tap::InfoGreTap; diff --git a/src/link/mod.rs b/src/link/mod.rs index 0641f8f2..18e91936 100644 --- a/src/link/mod.rs +++ b/src/link/mod.rs @@ -38,13 +38,13 @@ pub use self::ext_mask::LinkExtentMask; pub use self::header::{LinkHeader, LinkMessageBuffer}; pub use self::link_flag::LinkFlag; pub use self::link_info::{ - BondAdInfo, BondPortState, BridgeQuerierState, HsrProtocol, InfoBond, - InfoBondPort, InfoBridge, InfoData, InfoGreTap, InfoGreTap6, InfoGreTun, - InfoGreTun6, InfoGtp, InfoHsr, InfoIpVlan, InfoIpoib, InfoKind, InfoMacSec, - InfoMacVlan, InfoMacVtap, InfoPortData, InfoPortKind, InfoSitTun, InfoTun, - InfoVeth, InfoVlan, InfoVrf, InfoVti, InfoVxlan, InfoXfrm, LinkInfo, - LinkXstats, MacSecCipherId, MacSecOffload, MacSecValidate, MiiStatus, - VlanQosMapping, + BondAdInfo, BondPortState, BridgePortMulticastRouter, BridgePortState, + BridgeQuerierState, HsrProtocol, InfoBond, InfoBondPort, InfoBridge, + InfoBridgePort, InfoData, InfoGreTap, InfoGreTap6, InfoGreTun, InfoGreTun6, + InfoGtp, InfoHsr, InfoIpVlan, InfoIpoib, InfoKind, InfoMacSec, InfoMacVlan, + InfoMacVtap, InfoPortData, InfoPortKind, InfoSitTun, InfoTun, InfoVeth, + InfoVlan, InfoVrf, InfoVti, InfoVxlan, InfoXfrm, LinkInfo, LinkXstats, + MacSecCipherId, MacSecOffload, MacSecValidate, MiiStatus, VlanQosMapping, }; pub use self::link_layer_type::LinkLayerType; pub use self::link_state::State; diff --git a/src/link/tests/bridge.rs b/src/link/tests/bridge.rs index 69d3c472..24122edf 100644 --- a/src/link/tests/bridge.rs +++ b/src/link/tests/bridge.rs @@ -7,8 +7,9 @@ use netlink_packet_utils::{ use crate::link::{ af_spec::VecAfSpecBridge, AfSpecBridge, AfSpecInet, AfSpecInet6, - AfSpecUnspec, BridgeVlanInfo, Inet6CacheInfo, Inet6DevConf, Inet6IfaceFlag, - Inet6IfaceFlags, InetDevConf, InfoBridge, InfoData, InfoKind, + AfSpecUnspec, BridgePortMulticastRouter, BridgePortState, BridgeVlanInfo, + Inet6CacheInfo, Inet6DevConf, Inet6IfaceFlag, Inet6IfaceFlags, InetDevConf, + InfoBridge, InfoBridgePort, InfoData, InfoKind, InfoPortData, InfoPortKind, LinkAttribute, LinkFlag, LinkHeader, LinkInfo, LinkLayerType, LinkMessage, LinkMessageBuffer, LinkXdp, Map, State, Stats, Stats64, XdpAttached, }; @@ -449,3 +450,113 @@ fn test_af_spec_bridge() { expected ); } + +#[test] +fn test_bridge_port_link_info() { + let raw = vec![ + 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x12, 0x00, 0x09, 0x00, 0x01, 0x00, + 0x76, 0x65, 0x74, 0x68, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x04, 0x00, + 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x00, 0x00, 0x48, 0x01, 0x05, 0x00, + 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x1c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x1b, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x1e, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x80, 0x00, 0x52, 0x54, + 0x00, 0xde, 0x0d, 0x2e, 0x0c, 0x00, 0x0e, 0x00, 0x80, 0x00, 0x52, 0x54, + 0x00, 0xde, 0x0d, 0x2e, 0x06, 0x00, 0x0f, 0x00, 0x01, 0x80, 0x00, 0x00, + 0x06, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x11, 0x00, + 0x01, 0x80, 0x00, 0x00, 0x06, 0x00, 0x12, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x21, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x15, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x16, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x17, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x19, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x25, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x08, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x29, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = LinkMessage { + header: LinkHeader { + interface_family: AddressFamily::Unspec, + index: 7, + link_layer_type: LinkLayerType::Ether, + flags: vec![LinkFlag::Broadcast, LinkFlag::Multicast], + change_mask: vec![], + }, + attributes: vec![LinkAttribute::LinkInfo(vec![ + LinkInfo::Kind(InfoKind::Veth), + LinkInfo::PortKind(InfoPortKind::Bridge), + LinkInfo::PortData(InfoPortData::BridgePort(vec![ + InfoBridgePort::State(BridgePortState::Disabled), + InfoBridgePort::Priority(32), + InfoBridgePort::Cost(2), + InfoBridgePort::HairpinMode(false), + InfoBridgePort::Guard(false), + InfoBridgePort::Protect(false), + InfoBridgePort::FastLeave(false), + InfoBridgePort::MulticastToUnicast(false), + InfoBridgePort::Learning(true), + InfoBridgePort::UnicastFlood(true), + InfoBridgePort::MulticastFlood(true), + InfoBridgePort::BroadcastFlood(true), + InfoBridgePort::ProxyARP(false), + InfoBridgePort::ProxyARPWifi(false), + InfoBridgePort::RootId(( + 0x0080, + [0x52, 0x54, 0x00, 0xde, 0x0d, 0x2e], + )), + InfoBridgePort::BridgeId(( + 0x0080, + [0x52, 0x54, 0x00, 0xde, 0x0d, 0x2e], + )), + InfoBridgePort::DesignatedPort(32769), + InfoBridgePort::DesignatedCost(0), + InfoBridgePort::PortId(32769), + InfoBridgePort::PortNumber(1), + InfoBridgePort::TopologyChangeAck(false), + InfoBridgePort::ConfigPending(false), + InfoBridgePort::VlanTunnel(false), + InfoBridgePort::GroupFwdMask(0), + InfoBridgePort::NeighSupress(false), + InfoBridgePort::MrpRingOpen(false), + InfoBridgePort::MrpInOpen(false), + InfoBridgePort::Isolated(false), + InfoBridgePort::Locked(false), + InfoBridgePort::Mab(false), + InfoBridgePort::MessageAgeTimer(0), + InfoBridgePort::ForwardDelayTimer(0), + InfoBridgePort::HoldTimer(0), + InfoBridgePort::MulticastRouter( + BridgePortMulticastRouter::TempQuery, + ), + InfoBridgePort::MulticastEhtHostsLimit(512), + InfoBridgePort::MulticastEhtHostsCnt(0), + InfoBridgePort::MulticastNGroups(0), + InfoBridgePort::MulticastMaxGroups(0), + ])), + ])], + }; + + assert_eq!( + expected, + LinkMessage::parse(&LinkMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +}