diff --git a/virtweb_backend/src/constants.rs b/virtweb_backend/src/constants.rs index b19b740..8648a63 100644 --- a/virtweb_backend/src/constants.rs +++ b/virtweb_backend/src/constants.rs @@ -111,3 +111,6 @@ pub const API_TOKEN_RIGHT_PATH_MAX_LENGTH: usize = 255; /// Qemu image program path pub const QEMU_IMAGE_PROGRAM: &str = "/usr/bin/qemu-img"; + +/// IP program path +pub const IP_PROGRAM: &str = "/usr/sbin/ip"; diff --git a/virtweb_backend/src/controllers/server_controller.rs b/virtweb_backend/src/controllers/server_controller.rs index 1deda96..1e6ec15 100644 --- a/virtweb_backend/src/controllers/server_controller.rs +++ b/virtweb_backend/src/controllers/server_controller.rs @@ -188,3 +188,7 @@ pub async fn number_vcpus() -> HttpResult { pub async fn networks_list() -> HttpResult { Ok(HttpResponse::Ok().json(net_utils::net_list())) } + +pub async fn bridges_list() -> HttpResult { + Ok(HttpResponse::Ok().json(net_utils::bridges_list()?)) +} diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index 955b62c..6878bdd 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -48,6 +48,10 @@ async fn main() -> std::io::Result<()> { constants::QEMU_IMAGE_PROGRAM, "QEMU disk image utility is required to manipulate QCow2 files!", ); + exec_utils::check_program( + constants::IP_PROGRAM, + "ip is required to access bridges information!", + ); log::debug!("Create required directory, if missing"); files_utils::create_directory_if_missing(AppConfig::get().iso_storage_path()).unwrap(); @@ -137,6 +141,10 @@ async fn main() -> std::io::Result<()> { "/api/server/networks", web::get().to(server_controller::networks_list), ) + .route( + "/api/server/bridges", + web::get().to(server_controller::bridges_list), + ) // Auth controller .route( "/api/auth/local", diff --git a/virtweb_backend/src/utils/net_utils.rs b/virtweb_backend/src/utils/net_utils.rs index 6072073..8a55d04 100644 --- a/virtweb_backend/src/utils/net_utils.rs +++ b/virtweb_backend/src/utils/net_utils.rs @@ -1,6 +1,8 @@ +use crate::constants; use nix::sys::socket::{AddressFamily, SockaddrLike}; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::process::Command; use std::str::FromStr; use sysinfo::Networks; @@ -68,7 +70,7 @@ pub fn net_list() -> Vec { /// Get the list of available network interfaces associated with their IP address pub fn net_list_and_ips() -> anyhow::Result>> { - let addrs = nix::ifaddrs::getifaddrs().unwrap(); + let addrs = nix::ifaddrs::getifaddrs()?; let mut res = HashMap::new(); @@ -136,6 +138,31 @@ pub fn net_list_and_ips() -> anyhow::Result>> { Ok(res) } +#[derive(serde::Deserialize)] +struct IPBridgeInfo { + ifname: String, +} + +/// Get the list of bridge interfaces +pub fn bridges_list() -> anyhow::Result> { + let mut cmd = Command::new(constants::IP_PROGRAM); + cmd.args(["-json", "link", "show", "type", "bridge"]); + let output = cmd.output()?; + if !output.status.success() { + anyhow::bail!( + "{} failed, status: {}, stderr: {}", + constants::IP_PROGRAM, + output.status, + String::from_utf8_lossy(&output.stderr) + ); + } + + // Parse JSON result + let res: Vec = serde_json::from_str(&String::from_utf8_lossy(&output.stdout))?; + + Ok(res.iter().map(|ip| ip.ifname.clone()).collect()) +} + #[cfg(test)] mod tests { use crate::utils::net_utils::{ diff --git a/virtweb_docs/BRIDGE.md b/virtweb_docs/BRIDGE.md index 9071284..92fdd71 100644 --- a/virtweb_docs/BRIDGE.md +++ b/virtweb_docs/BRIDGE.md @@ -38,6 +38,9 @@ sudo netplan apply ```bash sudo brctl show + +# Or +ip link show type bridge ``` ## Reference