Backport the TC_HANDLE parser to main.

Replace C code with native Rust, including unit tests to find the
edge-cases.
This commit is contained in:
Herbert Wolverson 2023-03-20 17:40:03 +00:00
parent 6fe9748ea4
commit beead5a303
6 changed files with 30 additions and 82 deletions

1
src/rust/Cargo.lock generated
View File

@ -1327,7 +1327,6 @@ name = "lqos_bus"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode", "bincode",
"cc",
"criterion", "criterion",
"log", "log",
"lqos_config", "lqos_config",

View File

@ -17,9 +17,6 @@ tokio = { version = "1", features = [ "rt", "macros", "net", "io-util", "time" ]
log = "0" log = "0"
nix = "0" nix = "0"
[build-dependencies]
cc = "1.0"
[dev-dependencies] [dev-dependencies]
criterion = { version = "0", features = [ "html_reports", "async_tokio"] } criterion = { version = "0", features = [ "html_reports", "async_tokio"] }

View File

@ -1,3 +0,0 @@
fn main() {
cc::Build::new().file("src/tc_handle_parser.c").compile("tc_handle_parse.o");
}

View File

@ -1,6 +1,6 @@
use log::error; use log::error;
use lqos_utils::hex_string::read_hex_string;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ffi::CString;
use thiserror::Error; use thiserror::Error;
/// Provides consistent handling of TC handle types. /// Provides consistent handling of TC handle types.
@ -9,20 +9,9 @@ use thiserror::Error;
)] )]
pub struct TcHandle(u32); pub struct TcHandle(u32);
#[allow(non_camel_case_types)]
type __u32 = ::std::os::raw::c_uint;
#[allow(dead_code)]
const TC_H_ROOT: u32 = 4294967295; const TC_H_ROOT: u32 = 4294967295;
#[allow(dead_code)]
const TC_H_UNSPEC: u32 = 0; const TC_H_UNSPEC: u32 = 0;
extern "C" {
pub fn get_tc_classid(
h: *mut __u32,
str_: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int;
}
impl TcHandle { impl TcHandle {
/// Returns the TC handle as two values, indicating major and minor /// Returns the TC handle as two values, indicating major and minor
/// TC handle values. /// TC handle values.
@ -36,23 +25,26 @@ impl TcHandle {
/// Build a TC handle from a string. This is actually a complicated /// Build a TC handle from a string. This is actually a complicated
/// operation, since it has to handle "root" and other strings as well /// operation, since it has to handle "root" and other strings as well
/// as simple "1:2" mappings. Calls a C function to handle this gracefully. /// as simple "1:2" mappings. Calls a C function to handle this gracefully.
pub fn from_string<S: ToString>( pub fn from_string(
handle: S, handle: &str,
) -> Result<Self, TcHandleParseError> { ) -> Result<Self, TcHandleParseError> {
let mut tc_handle: __u32 = 0; let handle = handle.trim();
let str = CString::new(handle.to_string()); match handle {
if str.is_err() { "root" => Ok(Self(TC_H_ROOT)),
error!("Unable to convert {} to a C-String.", handle.to_string()); "none" => Ok(Self(TC_H_UNSPEC)),
return Err(TcHandleParseError::CString); _ => {
} if !handle.contains(':') {
let str = str.unwrap(); error!("Unable to parse TC handle {handle}. Must contain a colon.");
let handle_pointer: *mut __u32 = &mut tc_handle; return Err(TcHandleParseError::InvalidInput(handle.to_string()));
let result = unsafe { get_tc_classid(handle_pointer, str.as_ptr()) }; }
if result != 0 { let parts: Vec<&str> = handle.split(':').collect();
error!("Unable to parse {} as a valid TC handle", handle.to_string()); let major = read_hex_string(parts[0]).map_err(|_| TcHandleParseError::InvalidInput(handle.to_string()))?;
Err(TcHandleParseError::InvalidInput) let minor = read_hex_string(parts[1]).map_err(|_| TcHandleParseError::InvalidInput(handle.to_string()))?;
} else { if major >= (1<<16) || minor >= (1<<16) {
Ok(Self(tc_handle)) return Err(TcHandleParseError::InvalidInput(handle.to_string()));
}
Ok(Self((major << 16) | minor))
}
} }
} }
@ -86,7 +78,7 @@ pub enum TcHandleParseError {
)] )]
CString, CString,
#[error("Invalid input")] #[error("Invalid input")]
InvalidInput, InvalidInput(String),
} }
#[cfg(test)] #[cfg(test)]
@ -151,4 +143,10 @@ mod test {
} }
} }
} }
#[test]
fn blank_minor() {
let tc = TcHandle::from_string("7FFF:").unwrap();
assert_eq!(tc.to_string().to_uppercase(), "7FFF:0");
}
} }

View File

@ -1,46 +0,0 @@
// Imported from https://github.com/thebracket/cpumap-pping/blob/master/src/xdp_iphash_to_cpu_cmdline.c
// Because it uses strtoul and is based on the TC source, including it directly
// seemed like the path of least resistance.
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <linux/types.h>
#include <linux/pkt_sched.h> /* TC macros */
/* Handle classid parsing based on iproute source */
int get_tc_classid(__u32 *h, const char *str)
{
__u32 major, minor;
char *p;
major = TC_H_ROOT;
if (strcmp(str, "root") == 0)
goto ok;
major = TC_H_UNSPEC;
if (strcmp(str, "none") == 0)
goto ok;
major = strtoul(str, &p, 16);
if (p == str) {
major = 0;
if (*p != ':')
return -1;
}
if (*p == ':') {
if (major >= (1<<16))
return -1;
major <<= 16;
str = p+1;
minor = strtoul(str, &p, 16);
if (*p != 0)
return -1;
if (minor >= (1<<16))
return -1;
major |= minor;
} else if (*p != 0)
return -1;
ok:
*h = major;
return 0;
}

View File

@ -19,6 +19,9 @@ use thiserror::Error;
/// assert_eq!(read_hex_string("0x12AD").unwrap(), 4781); /// assert_eq!(read_hex_string("0x12AD").unwrap(), 4781);
/// ``` /// ```
pub fn read_hex_string(s: &str) -> Result<u32, HexParseError> { pub fn read_hex_string(s: &str) -> Result<u32, HexParseError> {
if s.is_empty() {
return Ok(0);
}
let result = u32::from_str_radix(&s.replace("0x", ""), 16); let result = u32::from_str_radix(&s.replace("0x", ""), 16);
match result { match result {
Ok(data) => Ok(data), Ok(data) => Ok(data),