Add preflight checks that interface(s) are UP. Add a check that if XDP

bridging is enabled, there is no Linux bridge containing the listed
interfaces. If the interfaces are enabled, warnings are emitted and
the XDP bridge disabled to operate in Linux mode.
This commit is contained in:
Herbert Wolverson 2024-05-19 21:47:47 -05:00
parent 58f898e0f0
commit 417b545dc7
3 changed files with 130 additions and 2 deletions

View File

@ -50,6 +50,20 @@ pub fn load_config() -> Result<Config, LibreQoSConfigError> {
Ok(lock.as_ref().unwrap().clone())
}
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(())
}
/// Enables LTS reporting in the configuration file.
pub fn enable_long_term_stats(license_key: String) -> Result<(), LibreQoSConfigError> {
let mut config = load_config()?;

View File

@ -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};

View File

@ -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,106 @@ 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<String>,
}
pub fn get_interfaces_from_ip_link() -> Result<Vec<IpLinkInterface>> {
let output = Command::new("/sbin/ip")
.args(["-j", "link"])
.output()?;
let output = String::from_utf8(output.stdout)?;
let output_json = serde_json::from_str::<serde_json::Value>(&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) {
if iface.operstate != "UP" {
error!("Interface {} is not UP", stick.interface);
anyhow::bail!("Interface {} is not UP", stick.interface);
}
}
}
if let Some(bridge) = &config.bridge {
if let Some(iface) = interfaces.iter().find(|i| i.name == bridge.to_internet) {
if iface.operstate != "UP" {
error!("Interface {} is not UP", bridge.to_internet);
anyhow::bail!("Interface {} is not UP", bridge.to_internet);
}
}
if let Some(iface) = interfaces.iter().find(|i| i.name == bridge.to_network) {
if iface.operstate != "UP" {
error!("Interface {} is not UP", bridge.to_network);
anyhow::bail!("Interface {} is not UP", bridge.to_network);
}
}
}
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")
{
// 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. 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 +179,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(())