From 79f852c9f6eb9d49b288465ce8f0cada1ffa72a5 Mon Sep 17 00:00:00 2001 From: xujunjie-cover Date: Sat, 8 Nov 2025 11:54:50 +0800 Subject: [PATCH] feat(link): add support of creating ipvlan and ipvtap. feat: - add support of creating ipvlan and ipvtap - add examples. --- examples/create_ipvlan.rs | 75 ++++++++++++++++++++++++++++++++++++ examples/create_ipvtap.rs | 80 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 7 ++-- src/link/ip_vlan.rs | 77 +++++++++++++++++++++++++++++++++++++ src/link/ip_vtap.rs | 77 +++++++++++++++++++++++++++++++++++++ src/link/mod.rs | 4 ++ 6 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 examples/create_ipvlan.rs create mode 100644 examples/create_ipvtap.rs create mode 100644 src/link/ip_vlan.rs create mode 100644 src/link/ip_vtap.rs diff --git a/examples/create_ipvlan.rs b/examples/create_ipvlan.rs new file mode 100644 index 0000000..8b78ebb --- /dev/null +++ b/examples/create_ipvlan.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT + +use std::env; + +use futures_util::stream::TryStreamExt; +use rtnetlink::{ + new_connection, + packet_route::link::{IpVlanFlags, IpVlanMode}, + Error, Handle, LinkIpVlan, +}; + +#[tokio::main] +async fn main() -> Result<(), String> { + let args: Vec = env::args().collect(); + if args.len() != 3 { + usage(); + return Ok(()); + } + let link_name = &args[1]; + let mode_str = &args[2]; + let mode = match mode_str.as_str() { + "l2" => IpVlanMode::L2, + "l3" => IpVlanMode::L3, + "l3s" => IpVlanMode::L3S, + _ => { + usage(); + return Ok(()); + } + }; + + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + create_ipvlan(handle, link_name.to_string(), mode, IpVlanFlags::empty()) + .await + .map_err(|e| format!("{e}")) +} + +async fn create_ipvlan( + handle: Handle, + link_name: String, + mode: IpVlanMode, + flag: IpVlanFlags, +) -> Result<(), Error> { + let mut parent_links = + handle.link().get().match_name(link_name.clone()).execute(); + if let Some(parent) = parent_links.try_next().await? { + let builder = + LinkIpVlan::new("ipvlan_test", parent.header.index, mode, flag) + .up(); + let message = builder.build(); + let request = handle.link().add(message); + + request.execute().await? + } else { + println!("no link {link_name} found"); + } + Ok(()) +} + +fn usage() { + eprintln!( + "usage: + cargo run --example create_ipvlan -- +ipvlan_mode can be one of the following: + l2: L2 mode + l3: L3 mode + l3s: L3S mode +Note that you need to run this program as root. Instead of running cargo as root, +build the example normally: + cargo build --example create_ipvlan +Then find the binary in the target directory: + cd target/debug/examples ; sudo ./create_ipvlan " + ); +} diff --git a/examples/create_ipvtap.rs b/examples/create_ipvtap.rs new file mode 100644 index 0000000..4b9805f --- /dev/null +++ b/examples/create_ipvtap.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT + +use std::env; + +use futures_util::stream::TryStreamExt; +use rtnetlink::{ + new_connection, + packet_route::link::{IpVtapFlags, IpVtapMode}, + Error, Handle, LinkIpVtap, +}; + +#[tokio::main] +async fn main() -> Result<(), String> { + let args: Vec = env::args().collect(); + if args.len() != 3 { + usage(); + return Ok(()); + } + let link_name = &args[1]; + let mode_str = &args[2]; + let mode = match mode_str.as_str() { + "l2" => IpVtapMode::L2, + "l3" => IpVtapMode::L3, + "l3s" => IpVtapMode::L3S, + _ => { + usage(); + return Ok(()); + } + }; + + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + create_ipvtap(handle, link_name.to_string(), mode, IpVtapFlags::empty()) + .await + .map_err(|e| format!("{e}")) +} + +async fn create_ipvtap( + handle: Handle, + link_name: String, + mode: IpVtapMode, + flags: IpVtapFlags, +) -> Result<(), Error> { + let mut parent_links = + handle.link().get().match_name(link_name.clone()).execute(); + if let Some(parent) = parent_links.try_next().await? { + let builder = + LinkIpVtap::new("ipvtap_test", parent.header.index, mode, flags) + .up(); + let message = builder.build(); + let request = handle.link().add(message); + + request.execute().await? + } else { + println!("no link {link_name} found"); + } + Ok(()) +} + +fn usage() { + eprintln!( + "usage: + cargo run --example create_ipvtap -- + +ipvtap_mode can be one of the following: + l2: L2 mode + l3: L3 mode + l3s: L3S mode + +Note that you need to run this program as root. Instead of running cargo as root, +build the example normally: + + cargo build --example create_ipvtap + +Then find the binary in the target directory: + + cd target/debug/examples ; sudo ./create_ipvtap " + ); +} diff --git a/src/lib.rs b/src/lib.rs index 009c9a3..c935778 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,9 +51,10 @@ pub use crate::{ link::{ LinkAddRequest, LinkBond, LinkBondPort, LinkBridge, LinkBridgePort, LinkBridgeVlan, LinkDelPropRequest, LinkDelRequest, LinkDummy, - LinkGetRequest, LinkHandle, LinkMacSec, LinkMacVlan, LinkMacVtap, - LinkMessageBuilder, LinkNetkit, LinkSetRequest, LinkUnspec, LinkVeth, - LinkVlan, LinkVrf, LinkVxlan, LinkWireguard, LinkXfrm, QosMapping, + LinkGetRequest, LinkHandle, LinkIpVlan, LinkIpVtap, LinkMacSec, + LinkMacVlan, LinkMacVtap, LinkMessageBuilder, LinkNetkit, LinkSetRequest, + LinkUnspec, LinkVeth, LinkVlan, LinkVrf, LinkVxlan, LinkWireguard, + LinkXfrm, QosMapping, }, multicast::MulticastGroup, neighbour::{ diff --git a/src/link/ip_vlan.rs b/src/link/ip_vlan.rs new file mode 100644 index 0000000..7398069 --- /dev/null +++ b/src/link/ip_vlan.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + link::LinkMessageBuilder, + packet_route::link::{ + InfoData, InfoIpVlan, InfoKind, IpVlanFlags, IpVlanMode, + }, +}; + +/// Represent IP VLAN interface. +/// Example code on creating a IP VLAN interface +/// ```no_run +/// use rtnetlink::{new_connection, packet_route::link::{IpVlanFlags, IpVlanMode}, +/// LinkIpVlan}; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add( +/// LinkIpVlan::new("ipvlan100", 10, IpVlanMode::L2, IpVlanFlags::empty()) +/// .up() +/// .build(), +/// ) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkIpVlan; + +impl LinkIpVlan { + /// Wrapper of `LinkMessageBuilder::::new().link().mode()` + pub fn new( + name: &str, + base_iface_index: u32, + mode: IpVlanMode, + flags: IpVlanFlags, + ) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name) + .link(base_iface_index) + .mode(mode) + .flags(flags) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for IP VLAN + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::IpVlan) + .name(name.to_string()) + } + + pub fn append_info_data(mut self, info: InfoIpVlan) -> Self { + if let InfoData::IpVlan(infos) = self + .info_data + .get_or_insert_with(|| InfoData::IpVlan(Vec::new())) + { + infos.push(info); + } + self + } + + pub fn mode(self, mode: IpVlanMode) -> Self { + self.append_info_data(InfoIpVlan::Mode(mode)) + } + + pub fn flags(self, flags: IpVlanFlags) -> Self { + self.append_info_data(InfoIpVlan::Flags(flags)) + } +} diff --git a/src/link/ip_vtap.rs b/src/link/ip_vtap.rs new file mode 100644 index 0000000..5ce601f --- /dev/null +++ b/src/link/ip_vtap.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + link::LinkMessageBuilder, + packet_route::link::{ + InfoData, InfoIpVtap, InfoKind, IpVtapFlags, IpVtapMode, + }, +}; + +/// Represent IP VTAP interface. +/// Example code on creating a IP VTAP interface +/// ```no_run +/// use rtnetlink::{new_connection, packet_route::link::{IpVtapFlags, IpVtapMode}, +/// LinkIpVtap}; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add( +/// LinkIpVtap::new("ipvtap100", 10, IpVtapMode::L2, IpVtapFlags::empty()) +/// .up() +/// .build(), +/// ) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkIpVtap; + +impl LinkIpVtap { + /// Wrapper of `LinkMessageBuilder::::new().link().mode()` + pub fn new( + name: &str, + base_iface_index: u32, + mode: IpVtapMode, + flags: IpVtapFlags, + ) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name) + .link(base_iface_index) + .mode(mode) + .flags(flags) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for IP VLAN + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::IpVtap) + .name(name.to_string()) + } + + pub fn append_info_data(mut self, info: InfoIpVtap) -> Self { + if let InfoData::IpVtap(infos) = self + .info_data + .get_or_insert_with(|| InfoData::IpVtap(Vec::new())) + { + infos.push(info); + } + self + } + + pub fn mode(self, mode: IpVtapMode) -> Self { + self.append_info_data(InfoIpVtap::Mode(mode)) + } + + pub fn flags(self, flags: IpVtapFlags) -> Self { + self.append_info_data(InfoIpVtap::Flags(flags)) + } +} diff --git a/src/link/mod.rs b/src/link/mod.rs index 69df943..7864694 100644 --- a/src/link/mod.rs +++ b/src/link/mod.rs @@ -12,6 +12,8 @@ mod del; mod dummy; mod get; mod handle; +mod ip_vlan; +mod ip_vtap; mod mac_vlan; mod mac_vtap; mod macsec; @@ -37,6 +39,8 @@ pub use self::{ dummy::LinkDummy, get::LinkGetRequest, handle::LinkHandle, + ip_vlan::LinkIpVlan, + ip_vtap::LinkIpVtap, mac_vlan::LinkMacVlan, mac_vtap::LinkMacVtap, macsec::LinkMacSec,