From 06d9fa77696a9fc72d34e18f6ce5482264e50300 Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Fri, 31 May 2024 12:23:40 -0500 Subject: [PATCH] Rebase to current develop (which includes reverting this) Interface status not being UP is no longer a blocker, it lists the status in the log to help with diagnosis. (It turns out that some valid configurations don't list UP at this time). --- src/rust/lqos_config/src/etc/mod.rs | 19 +++++ src/rust/lqos_config/src/lib.rs | 2 +- src/rust/lqosd/src/preflight_checks.rs | 109 ++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/src/rust/lqos_config/src/etc/mod.rs b/src/rust/lqos_config/src/etc/mod.rs index 0c5e48e9..347caff9 100644 --- a/src/rust/lqos_config/src/etc/mod.rs +++ b/src/rust/lqos_config/src/etc/mod.rs @@ -104,6 +104,24 @@ pub fn update_config(new_config: &Config) -> Result<(), LibreQoSConfigError> { Ok(()) } +/// Helper function that disables the XDP bridge in the LIVE, CACHED +/// configuration --- it does NOT save the changes to disk. This is +/// intended for use when the XDP bridge is disabled by pre-flight +/// because of a Linux bridge. +pub fn disable_xdp_bridge() -> Result<(), LibreQoSConfigError> { + let mut config = load_config()?; + let mut lock = CONFIG.lock().unwrap(); + + if let Some(bridge) = &mut config.bridge { + bridge.use_xdp_bridge = false; + } + + // Write the lock + *lock = Some(config); + + Ok(()) +} + #[derive(Debug, Error)] pub enum LibreQoSConfigError { #[error("Unable to read /etc/lqos.conf. See other errors for details.")] @@ -127,3 +145,4 @@ pub enum LibreQoSConfigError { #[error("Unable to serialize config")] SerializeError, } + diff --git a/src/rust/lqos_config/src/lib.rs b/src/rust/lqos_config/src/lib.rs index e7db6b9e..517c6cf3 100644 --- a/src/rust/lqos_config/src/lib.rs +++ b/src/rust/lqos_config/src/lib.rs @@ -13,7 +13,7 @@ mod program_control; mod shaped_devices; pub use authentication::{UserRole, WebUsers}; -pub use etc::{load_config, Config, enable_long_term_stats, Tunables, BridgeConfig, update_config}; +pub use etc::{load_config, Config, enable_long_term_stats, Tunables, BridgeConfig, update_config, disable_xdp_bridge}; pub use network_json::{NetworkJson, NetworkJsonNode, NetworkJsonTransport}; pub use program_control::load_libreqos; pub use shaped_devices::{ConfigShapedDevices, ShapedDevice}; diff --git a/src/rust/lqosd/src/preflight_checks.rs b/src/rust/lqosd/src/preflight_checks.rs index 453c2477..5b602e1a 100644 --- a/src/rust/lqosd/src/preflight_checks.rs +++ b/src/rust/lqosd/src/preflight_checks.rs @@ -1,7 +1,8 @@ -use std::path::Path; +use std::{path::Path, process::Command}; use anyhow::Result; use log::{error, info}; +use lqos_config::Config; use lqos_sys::interface_name_to_index; fn check_queues(interface: &str) -> Result<()> { @@ -42,6 +43,99 @@ fn check_queues(interface: &str) -> Result<()> { Ok(()) } +#[derive(Debug)] +pub struct IpLinkInterface { + pub name: String, + pub index: u32, + pub operstate: String, + pub link_type: String, + pub master: Option, +} + +pub fn get_interfaces_from_ip_link() -> Result> { + let output = Command::new("/sbin/ip") + .args(["-j", "link"]) + .output()?; + let output = String::from_utf8(output.stdout)?; + let output_json = serde_json::from_str::(&output)?; + + let mut interfaces = Vec::new(); + for interface in output_json.as_array().unwrap() { + let name = interface["ifname"].as_str().unwrap().to_string(); + let index = interface["ifindex"].as_u64().unwrap() as u32; + let operstate = interface["operstate"].as_str().unwrap().to_string(); + let link_type = interface["link_type"].as_str().unwrap().to_string(); + let master = interface["master"].as_str().map(|s| s.to_string()); + + interfaces.push(IpLinkInterface { + name, + index, + operstate, + link_type, + master, + }); + } + + Ok(interfaces) +} + +fn check_interface_status(config: &Config, interfaces: &[IpLinkInterface]) -> Result<()> { + if let Some(stick) = &config.single_interface { + if let Some(iface) = interfaces.iter().find(|i| i.name == stick.interface) { + info!("Interface {} is in status: {}", stick.interface, iface.operstate); + } + } else if let Some(bridge) = &config.bridge { + if let Some(iface) = interfaces.iter().find(|i| i.name == bridge.to_internet) { + info!("Interface {} is in status: {}", iface.name, iface.operstate); + } + if let Some(iface) = interfaces.iter().find(|i| i.name == bridge.to_network) { + info!("Interface {} is in status: {}", iface.name, iface.operstate); + } + } else { + error!("You MUST have either a single interface or a bridge defined in the configuration file."); + anyhow::bail!("You MUST have either a single interface or a bridge defined in the configuration file."); + } + + Ok(()) +} + +fn check_bridge_status(config: &Config, interfaces: &[IpLinkInterface]) -> Result<()> { + // On a stick mode is bridge-free + if config.on_a_stick_mode() { + return Ok(()); + } + + // Is the XDP bridge enabled? + if let Some(bridge) = &config.bridge { + if bridge.use_xdp_bridge { + for bridge_if in interfaces + .iter() + .filter(|bridge_if| bridge_if.link_type == "ether" && bridge_if.operstate == "UP") + { + // We found a bridge. Check member interfaces to check that it does NOT include any XDP + // bridge members. + let in_bridge: Vec<&IpLinkInterface> = interfaces + .iter() + .filter(|member_if| { + if let Some(master) = &member_if.master { + master == &bridge_if.name + } else { + false + } + }) + .filter(|member_if| member_if.name == config.internet_interface() || member_if.name == config.isp_interface()) + .collect(); + + if in_bridge.len() == 2 { + error!("Bridge ({}) contains both the internet and ISP interfaces, and you have the xdp_bridge enabled. This is not supported.", bridge_if.name); + anyhow::bail!("Bridge {} contains both the internet and ISP interfaces. This is not supported.", bridge_if.name); + } + } + } + } + Ok(()) +} + /// Runs a series of preflight checks to ensure that the configuration is sane pub fn preflight_checks() -> Result<()> { info!("Sanity checking configuration..."); @@ -78,6 +172,19 @@ pub fn preflight_checks() -> Result<()> { check_queues(&config.isp_interface())?; } + // Obtain the "IP link" output + let interfaces = get_interfaces_from_ip_link()?; + + // Are the interfaces up? + check_interface_status(&config, &interfaces)?; + + // Does the bridge system make sense? + if check_bridge_status(&config, &interfaces).is_err() { + log::warn!("Disabling XDP bridge"); + lqos_config::disable_xdp_bridge()?; + log::warn!("XDP bridge disabled in ACTIVE config. Please fix the configuration file."); + }; + info!("Sanity checks passed"); Ok(())