// SPDX-License-Identifier: MIT use std::{env, error::Error as StdError, str::FromStr}; use rtnetlink::{new_connection, QosMapping}; fn parse_mapping(parameter: &str) -> Result> { let (from, to) = parameter .split_once(':') .ok_or("Failed to parse mapping..")?; Ok(QosMapping { from: u32::from_str(from)?, to: u32::from_str(to)?, }) } const ARG_BASE: &str = "--base"; const ARG_NAME: &str = "--name"; const ARG_ID: &str = "--id"; const ARG_INGRESS_QOS: &str = "--ingress-qos-mapping"; const ARG_EGRESS_QOS: &str = "--egress-qos-mapping"; enum ParsingMode { None, Base, Name, Id, Ingress, Egress, } #[tokio::main] async fn main() -> Result<(), String> { let mut args: Vec = env::args().collect(); let mut base_interface = None; let mut name = None; let mut id = None; let mut ingress = Vec::new(); let mut egress = Vec::new(); let mut mode = ParsingMode::None; for argument in args.drain(1..) { fn match_argument(argument: String) -> Result { match argument.to_lowercase().as_str() { ARG_BASE => Ok(ParsingMode::Base), ARG_NAME => Ok(ParsingMode::Name), ARG_ID => Ok(ParsingMode::Id), ARG_INGRESS_QOS => Ok(ParsingMode::Ingress), ARG_EGRESS_QOS => Ok(ParsingMode::Egress), other => { usage(); Err(format!("Unexpected argument: {other}")) } } } mode = match mode { ParsingMode::None => match_argument(argument)?, ParsingMode::Base => { base_interface = u32::from_str(&argument).ok(); ParsingMode::None } ParsingMode::Name => { name = Some(argument); ParsingMode::None } ParsingMode::Id => { id = u16::from_str(&argument).ok(); ParsingMode::None } mode @ ParsingMode::Ingress => match parse_mapping(&argument) { Ok(mapping) => { ingress.push(mapping); mode } Err(_) => match_argument(argument)?, }, mode @ ParsingMode::Egress => match parse_mapping(&argument) { Ok(mapping) => { egress.push(mapping); mode } Err(_) => match_argument(argument)?, }, } } let Some(base) = base_interface else { usage(); return Err( "Missing or invalid argument for base interface!".to_owned() ); }; let Some(name) = name else { usage(); return Err( "Missing or invalid argument for new interface name!".to_owned() ); }; let Some(id) = id else { usage(); return Err("Missing or invalid argument for vlan id!".to_owned()); }; let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); handle .link() .add() .vlan_with_qos(name, base, id, ingress, egress) .execute() .await .map_err(|err| format!("Netlink request failed: {err}")) } fn usage() { eprintln!( "usage: cargo run --example create_vlan -- --base --name --id [--ingress-qos-mapping : ..>] [--egress-qos-mapping : ..>] Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd netlink-ip ; cargo build --example create_vlan Then find the binary in the target directory: cd ../target/debug/example ; sudo ./create_vlan " ); }