diff --git a/examples/set_bridge_vlan.rs b/examples/set_bridge_vlan.rs new file mode 100644 index 0000000..c5d520e --- /dev/null +++ b/examples/set_bridge_vlan.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT + +use futures_util::stream::TryStreamExt; +use rtnetlink::{ + new_connection, + packet_route::{ + link::{BridgeVlanInfoFlags, LinkAttribute, LinkExtentMask}, + AddressFamily, + }, + Handle, LinkBridge, LinkBridgeVlan, LinkDummy, LinkUnspec, +}; + +async fn create_bridge_and_get_index(handle: &Handle) -> Result { + handle + .link() + .add( + LinkBridge::new("my-bridge0") + .vlan_filtering(true) + .up() + .build(), + ) + .execute() + .await + .map_err(|e| format!("{e}"))?; + + let mut bridge_links = handle + .link() + .get() + .match_name("my-bridge0".to_string()) + .execute(); + if let Some(bridge_link) = + bridge_links.try_next().await.map_err(|e| format!("{e}"))? + { + Ok(bridge_link.header.index) + } else { + Err("failed to find my-bridge0".into()) + } +} + +async fn create_dummy_and_attach_to_bridge( + handle: &Handle, + bridge_index: u32, +) -> Result { + handle + .link() + .add(LinkDummy::new("my-dummy0").build()) + .execute() + .await?; + + handle + .link() + .set( + LinkUnspec::new_with_name("my-dummy0") + .controller(bridge_index) + .down() + .build(), + ) + .execute() + .await?; + + let mut dummy_links = handle + .link() + .get() + .match_name("my-dummy0".to_string()) + .execute(); + if let Some(dummy_link) = dummy_links.try_next().await? { + Ok(dummy_link.header.index) + } else { + panic!("failed to find my-dummy0") + } +} + +async fn set_bridge_vlan( + handle: &Handle, + port_index: u32, +) -> Result<(), rtnetlink::Error> { + let message = LinkBridgeVlan::new(port_index) + .vlan(10, BridgeVlanInfoFlags::Pvid) + .vlan_range_start(20, BridgeVlanInfoFlags::empty()) + .vlan_range_end(30, BridgeVlanInfoFlags::empty()) + .build(); + + handle.link().set(message).execute().await?; + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + let bridge_index = create_bridge_and_get_index(&handle).await?; + + let port_index = + create_dummy_and_attach_to_bridge(&handle, bridge_index).await?; + set_bridge_vlan(&handle, port_index).await?; + + let mut dump_link = handle + .link() + .get() + .set_filter_mask( + AddressFamily::Bridge, + vec![LinkExtentMask::BrvlanCompressed], + ) + .execute(); + while let Some(link_msg) = dump_link.try_next().await? { + // With set_filter_mask(), we cannot use match_name to filter due to + // linux kernel limitation. + if !link_msg.attributes.as_slice().iter().any( + |a| matches!(a, LinkAttribute::IfName(name) if name == "my-dummy0"), + ) { + continue; + } + for nla in link_msg.attributes { + if let LinkAttribute::AfSpecBridge(i) = &nla { + println!("{:?}", i); + return Ok(()); + } + } + } + Err("failed to find bridge vlan info for my-dummy0".into()) +} diff --git a/src/lib.rs b/src/lib.rs index 42fb0ce..009c9a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,10 +50,10 @@ pub use crate::{ handle::Handle, link::{ LinkAddRequest, LinkBond, LinkBondPort, LinkBridge, LinkBridgePort, - LinkDelPropRequest, LinkDelRequest, LinkDummy, LinkGetRequest, - LinkHandle, LinkMacSec, LinkMacVlan, LinkMacVtap, LinkMessageBuilder, - LinkNetkit, LinkSetRequest, LinkUnspec, LinkVeth, LinkVlan, LinkVrf, - LinkVxlan, LinkWireguard, LinkXfrm, QosMapping, + LinkBridgeVlan, LinkDelPropRequest, LinkDelRequest, LinkDummy, + LinkGetRequest, LinkHandle, LinkMacSec, LinkMacVlan, LinkMacVtap, + LinkMessageBuilder, LinkNetkit, LinkSetRequest, LinkUnspec, LinkVeth, + LinkVlan, LinkVrf, LinkVxlan, LinkWireguard, LinkXfrm, QosMapping, }, multicast::MulticastGroup, neighbour::{ diff --git a/src/link/bridge_vlan.rs b/src/link/bridge_vlan.rs new file mode 100644 index 0000000..a278dab --- /dev/null +++ b/src/link/bridge_vlan.rs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + packet_route::{ + link::{ + AfSpecBridge, BridgeVlanInfo, BridgeVlanInfoFlags, LinkAttribute, + }, + AddressFamily, + }, + LinkMessageBuilder, +}; + +#[derive(Debug)] +pub struct LinkBridgeVlan; + +impl LinkBridgeVlan { + pub fn new(port_index: u32) -> LinkMessageBuilder { + LinkMessageBuilder::::default() + .index(port_index) + .interface_family(AddressFamily::Bridge) + } +} + +impl LinkMessageBuilder { + /// Append arbitrary [LinkAttribute::AfSpecBridge] + pub fn append_af_spec(self, spec: AfSpecBridge) -> Self { + let mut ret = self; + + for attr in ret.extra_attriutes.iter_mut() { + if let LinkAttribute::AfSpecBridge(specs) = attr { + specs.push(spec); + return ret; + } + } + + ret.append_extra_attribute(LinkAttribute::AfSpecBridge(vec![spec])) + } + + pub fn vlan(self, vid: u16, flags: BridgeVlanInfoFlags) -> Self { + self.append_af_spec(AfSpecBridge::VlanInfo(BridgeVlanInfo { + vid, + flags, + })) + } + + /// Helper function by adding [BridgeVlanInfoFlags::RangeBegin] + /// automatically to `flags` + pub fn vlan_range_start( + self, + vid: u16, + flags: BridgeVlanInfoFlags, + ) -> Self { + self.vlan(vid, flags | BridgeVlanInfoFlags::RangeBegin) + } + + /// Helper function by adding [BridgeVlanInfoFlags::RangeEnd] + /// automatically to `flags` + pub fn vlan_range_end(self, vid: u16, flags: BridgeVlanInfoFlags) -> Self { + self.vlan(vid, flags | BridgeVlanInfoFlags::RangeEnd) + } +} diff --git a/src/link/builder.rs b/src/link/builder.rs index 04e7b1e..09d4235 100644 --- a/src/link/builder.rs +++ b/src/link/builder.rs @@ -2,9 +2,12 @@ use std::{marker::PhantomData, os::fd::RawFd}; -use crate::packet_route::link::{ - InfoData, InfoKind, InfoPortData, InfoPortKind, LinkAttribute, LinkFlags, - LinkHeader, LinkInfo, LinkMessage, +use crate::packet_route::{ + link::{ + InfoData, InfoKind, InfoPortData, InfoPortKind, LinkAttribute, + LinkFlags, LinkHeader, LinkInfo, LinkMessage, + }, + AddressFamily, }; /// Generic interface without interface type @@ -57,7 +60,7 @@ pub struct LinkMessageBuilder { pub(crate) info_data: Option, pub(crate) port_kind: Option, pub(crate) port_data: Option, - extra_attriutes: Vec, + pub(crate) extra_attriutes: Vec, _phantom: PhantomData, } @@ -164,6 +167,12 @@ impl LinkMessageBuilder { ret } + pub fn interface_family(self, family: AddressFamily) -> Self { + let mut ret = self; + ret.header.interface_family = family; + ret + } + /// Define the hardware address of the link when creating it (equivalent to /// `ip link add NAME address ADDRESS`) pub fn address(self, address: Vec) -> Self { diff --git a/src/link/mod.rs b/src/link/mod.rs index 5e4ae96..69df943 100644 --- a/src/link/mod.rs +++ b/src/link/mod.rs @@ -5,6 +5,8 @@ mod bond; mod bond_port; mod bridge; mod bridge_port; +#[cfg(any(target_os = "linux", target_os = "fuchsia", target_os = "android"))] +mod bridge_vlan; mod builder; mod del; mod dummy; @@ -52,3 +54,10 @@ pub use self::{ #[cfg(test)] mod test; + +#[cfg(any( + target_os = "linux", + target_os = "fuchsia", + target_os = "android" +))] +pub use self::bridge_vlan::LinkBridgeVlan;