Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add optional VLAN QOS specification
  • Loading branch information
jreppnow authored and cathay4t committed Jul 18, 2023
1 parent 7afe563 commit 78a58db
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 6 deletions.
135 changes: 135 additions & 0 deletions examples/create_vlan.rs
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: MIT

use std::{env, error::Error as StdError, str::FromStr};

use rtnetlink::{new_connection, QosMapping};

fn parse_mapping(parameter: &str) -> Result<QosMapping, Box<dyn StdError>> {
let (from, to) = parameter
.split_once(':')
.ok_or_else(|| "Failed to parse mapping..")?;

Ok(QosMapping {
from: u32::from_str(from)?,
to: u32::from_str(to)?,
})
}

const ARG_BASE: &'static str = "--base";
const ARG_NAME: &'static str = "--name";
const ARG_ID: &'static str = "--id";
const ARG_INGRESS_QOS: &'static str = "--ingress-qos-mapping";
const ARG_EGRESS_QOS: &'static str = "--egress-qos-mapping";

enum ParsingMode {
None,
Base,
Name,
Id,
Ingress,
Egress,
}

#[tokio::main]
async fn main() -> Result<(), String> {
let mut args: Vec<String> = 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<ParsingMode, String> {
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();
return 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 <base link index> --name <link name> --id <vlan id> [--ingress-qos-mapping <mapping as <integer>:<integer> ..>] [--egress-qos-mapping <mapping as <integer>:<integer> ..>]
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 <link_name>"
);
}
58 changes: 52 additions & 6 deletions src/link/add.rs
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: MIT

use std::net::{Ipv4Addr, Ipv6Addr};
use std::{
iter::empty,
net::{Ipv4Addr, Ipv6Addr},
};

use futures::stream::StreamExt;
use netlink_packet_core::{
Expand All @@ -11,7 +14,7 @@ use netlink_packet_core::{
use netlink_packet_route::{
link::nlas::{
Info, InfoBond, InfoData, InfoKind, InfoMacVlan, InfoMacVtap, InfoVlan,
InfoVxlan, InfoXfrmTun, Nla, VethInfo,
InfoVxlan, InfoXfrmTun, Nla, VethInfo, VlanQosMapping,
},
LinkMessage, RtnlMessage, IFF_UP,
};
Expand Down Expand Up @@ -523,6 +526,20 @@ pub struct LinkAddRequest {
replace: bool,
}

/// A quality-of-service mapping between the internal priority `from` to the
/// external vlan priority `to`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct QosMapping {
pub from: u32,
pub to: u32,
}

impl From<QosMapping> for VlanQosMapping {
fn from(QosMapping { from, to }: QosMapping) -> Self {
Self::Mapping { from, to }
}
}

impl LinkAddRequest {
pub(crate) fn new(handle: Handle) -> Self {
LinkAddRequest {
Expand Down Expand Up @@ -609,11 +626,40 @@ impl LinkAddRequest {
/// VLAN_ID`, but instead of specifying a link name (`LINK`), we specify
/// a link index.
pub fn vlan(self, name: String, index: u32, vlan_id: u16) -> Self {
self.vlan_with_qos(name, index, vlan_id, empty(), empty())
}

/// Create VLAN on a link with ingress and egress qos mappings.
/// This is equivalent to `ip link add link LINK name NAME type vlan id
/// VLAN_ID ingress-qos-mapping INGRESS_QOS egress-qos-mapping EGRESS_QOS`,
/// but instead of specifying a link name (`LINK`), we specify a link index.
pub fn vlan_with_qos<
I: IntoIterator<Item = QosMapping>,
E: IntoIterator<Item = QosMapping>,
>(
self,
name: String,
index: u32,
vlan_id: u16,
ingress_qos: I,
egress_qos: E,
) -> Self {
let mut info = vec![InfoVlan::Id(vlan_id)];

let ingress: Vec<_> =
ingress_qos.into_iter().map(VlanQosMapping::from).collect();
if !ingress.is_empty() {
info.push(InfoVlan::IngressQos(ingress));
}

let egress: Vec<_> =
egress_qos.into_iter().map(VlanQosMapping::from).collect();
if !egress.is_empty() {
info.push(InfoVlan::EgressQos(egress));
}

self.name(name)
.link_info(
InfoKind::Vlan,
Some(InfoData::Vlan(vec![InfoVlan::Id(vlan_id)])),
)
.link_info(InfoKind::Vlan, Some(InfoData::Vlan(info)))
.append_nla(Nla::Link(index))
.up()
}
Expand Down

0 comments on commit 78a58db

Please sign in to comment.