Merge pull request #491 from LibreQoE/preflight-bridge-check

Preflight bridge check with relaxed interface status checks
This commit is contained in:
Herbert "TheBracket 2024-06-15 09:49:17 -05:00 committed by GitHub
commit 24f59e1d3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 128 additions and 2 deletions

View File

@ -117,6 +117,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.")]
@ -140,3 +158,4 @@ pub enum LibreQoSConfigError {
#[error("Unable to serialize config")]
SerializeError,
}

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,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<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) {
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(())