From f21ddb223b311e2a6ed23a1b55de8732c3d569a5 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Sat, 7 May 2022 10:33:21 +0800 Subject: [PATCH] bridge vlan info: expand VlanInfo of AfSpecBridge To simplify the efforts on parsing linux bridge vlan filter, this patch is introducing `struct BridgeVlanInfo` to hold VlanInfo of AfSpecBridge. This C struct is defined in `linux/if_bridge.h` as `struct bridge_vlan_info`. Example code included as `dump_packet_link_bridge_vlan.rs`. To try out of linux bridge VLAN filtering, you may: ``` sudo ip link add eth1 type veth peer name eth1.ep sudo ip link add br0 type bridge sudo ip link set eth1 master br0 sudo ip link set eth1 up sudo ip link set eth1.ep up sudo ip link set br0 up sudo bridge vlan add vid 10 pvid untagged dev eth1 sudo bridge vlan add vid 20-4094 dev eth1 sudo ip link set br0 type bridge vlan_filtering 1 cargo run --example dump_packet_link_bridge_vlan ``` The example code is manually tested on kernel 5.14.0-80.el9.x86_64 and 4.18.0-383.el8.x86_64. Signed-off-by: Gris Ge --- .../examples/dump_packet_link_bridge_vlan.rs | 82 +++++++++++++++++++ .../src/rtnl/link/nlas/af_spec_bridge.rs | 49 +++++++++-- 2 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 netlink-packet-route/examples/dump_packet_link_bridge_vlan.rs diff --git a/netlink-packet-route/examples/dump_packet_link_bridge_vlan.rs b/netlink-packet-route/examples/dump_packet_link_bridge_vlan.rs new file mode 100644 index 00000000..17642624 --- /dev/null +++ b/netlink-packet-route/examples/dump_packet_link_bridge_vlan.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_route::{ + nlas::link::Nla, + LinkMessage, + NetlinkHeader, + NetlinkMessage, + NetlinkPayload, + RtnlMessage, + AF_BRIDGE, + NLM_F_DUMP, + NLM_F_REQUEST, + RTEXT_FILTER_BRVLAN_COMPRESSED, +}; +use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; + +fn main() { + let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); + let _port_number = socket.bind_auto().unwrap().port_number(); + socket.connect(&SocketAddr::new(0, 0)).unwrap(); + + let mut message = LinkMessage::default(); + message.header.interface_family = AF_BRIDGE as u8; + message + .nlas + .push(Nla::ExtMask(RTEXT_FILTER_BRVLAN_COMPRESSED)); + let mut packet = NetlinkMessage { + header: NetlinkHeader::default(), + payload: NetlinkPayload::from(RtnlMessage::GetLink(message)), + }; + packet.header.flags = NLM_F_DUMP | NLM_F_REQUEST; + packet.header.sequence_number = 1; + packet.finalize(); + + let mut buf = vec![0; packet.header.length as usize]; + + // Before calling serialize, it is important to check that the buffer in which we're emitting is big + // enough for the packet, other `serialize()` panics. + assert!(buf.len() == packet.buffer_len()); + packet.serialize(&mut buf[..]); + + println!(">>> {:?}", packet); + socket.send(&buf[..], 0).unwrap(); + + let mut receive_buffer = vec![0; 4096]; + let mut offset = 0; + + // we set the NLM_F_DUMP flag so we expect a multipart rx_packet in response. + loop { + let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); + + loop { + let bytes = &receive_buffer[offset..]; + // Note that we're parsing a NetlinkBuffer<&&[u8]>, NOT a NetlinkBuffer<&[u8]> here. + // This is important because Parseable is only implemented for + // NetlinkBuffer<&'a T>, where T implements AsRef<[u8] + 'a. This is not + // particularly user friendly, but this is a low level library anyway. + // + // Note also that the same could be written more explicitely with: + // + // let rx_packet = + // as Parseable>::parse(NetlinkBuffer::new(&bytes)) + // .unwrap(); + // + let rx_packet: NetlinkMessage = + NetlinkMessage::deserialize(bytes).unwrap(); + + println!("<<< {:?}", rx_packet); + + if rx_packet.payload == NetlinkPayload::Done { + println!("Done!"); + return; + } + + offset += rx_packet.header.length as usize; + if offset == size || rx_packet.header.length == 0 { + offset = 0; + break; + } + } + } +} diff --git a/netlink-packet-route/src/rtnl/link/nlas/af_spec_bridge.rs b/netlink-packet-route/src/rtnl/link/nlas/af_spec_bridge.rs index 5a5e63c7..331afe87 100644 --- a/netlink-packet-route/src/rtnl/link/nlas/af_spec_bridge.rs +++ b/netlink-packet-route/src/rtnl/link/nlas/af_spec_bridge.rs @@ -1,4 +1,7 @@ // SPDX-License-Identifier: MIT + +use std::convert::TryFrom; + use anyhow::Context; use crate::{ @@ -14,7 +17,7 @@ use byteorder::{ByteOrder, NativeEndian}; #[derive(Clone, Eq, PartialEq, Debug)] pub enum AfSpecBridge { Flags(u16), - VlanInfo(Vec), + VlanInfo(BridgeVlanInfo), Other(DefaultNla), } @@ -22,7 +25,7 @@ impl nlas::Nla for AfSpecBridge { fn value_len(&self) -> usize { use self::AfSpecBridge::*; match *self { - VlanInfo(ref bytes) => bytes.len(), + VlanInfo(_) => 4, Flags(_) => 2, Other(ref nla) => nla.value_len(), } @@ -32,8 +35,8 @@ impl nlas::Nla for AfSpecBridge { use self::AfSpecBridge::*; match *self { Flags(value) => NativeEndian::write_u16(buffer, value), - VlanInfo(ref bytes) => { - (&mut buffer[..bytes.len()]).copy_from_slice(bytes.as_slice()); + VlanInfo(ref info) => { + (&mut buffer[..4]).copy_from_slice(<[u8; 4]>::from(info).as_slice()) } Other(ref nla) => nla.emit_value(buffer), } @@ -55,7 +58,9 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for AfSpecBridge { let payload = buf.value(); Ok(match buf.kind() { - IFLA_BRIDGE_VLAN_INFO => VlanInfo(payload.to_vec()), + IFLA_BRIDGE_VLAN_INFO => VlanInfo( + BridgeVlanInfo::try_from(payload).context("Invalid IFLA_BRIDGE_VLAN_INFO value")?, + ), IFLA_BRIDGE_FLAGS => { Flags(parse_u16(payload).context("invalid IFLA_BRIDGE_FLAGS value")?) } @@ -63,3 +68,37 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for AfSpecBridge { }) } } + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct BridgeVlanInfo { + pub flags: u16, + pub vid: u16, +} + +impl From<&BridgeVlanInfo> for [u8; 4] { + fn from(d: &BridgeVlanInfo) -> Self { + let mut ret = [0u8; 4]; + NativeEndian::write_u16(&mut ret[0..2], d.flags); + NativeEndian::write_u16(&mut ret[2..4], d.vid); + ret + } +} + +impl TryFrom<&[u8]> for BridgeVlanInfo { + type Error = DecodeError; + fn try_from(raw: &[u8]) -> Result { + if raw.len() == 4 { + Ok(Self { + flags: parse_u16(&raw[0..2]) + .context(format!("Invalid IFLA_BRIDGE_VLAN_INFO value: {:?}", raw))?, + vid: parse_u16(&raw[2..4]) + .context(format!("Invalid IFLA_BRIDGE_VLAN_INFO value: {:?}", raw))?, + }) + } else { + Err(DecodeError::from(format!( + "Invalid IFLA_BRIDGE_VLAN_INFO value, expecting [u8;4], but got {:?}", + raw + ))) + } + } +}