Compare commits
	
		
			1 Commits
		
	
	
		
			3d4750de21
			...
			8d5c43a35b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8d5c43a35b | 
| @@ -108,9 +108,3 @@ pub const API_TOKEN_DESCRIPTION_MAX_LENGTH: usize = 30; | ||||
|  | ||||
| /// API token right path max length | ||||
| 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"; | ||||
|   | ||||
| @@ -188,7 +188,3 @@ 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()?)) | ||||
| } | ||||
|   | ||||
| @@ -80,9 +80,7 @@ pub struct NetMacAddress { | ||||
| #[serde(rename = "source")] | ||||
| pub struct NetIntSourceXML { | ||||
|     #[serde(rename = "@network")] | ||||
|     pub network: Option<String>, | ||||
|     #[serde(rename = "@bridge")] | ||||
|     pub bridge: Option<String>, | ||||
|     pub network: String, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||||
|   | ||||
| @@ -53,8 +53,7 @@ pub struct Network { | ||||
| #[serde(tag = "type")] | ||||
| pub enum NetworkType { | ||||
|     UserspaceSLIRPStack, | ||||
|     DefinedNetwork { network: String }, | ||||
|     Bridge { bridge: String }, | ||||
|     DefinedNetwork { network: String }, // TODO : complete network types | ||||
| } | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| @@ -241,18 +240,7 @@ impl VMInfo { | ||||
|                     mac, | ||||
|                     r#type: "network".to_string(), | ||||
|                     source: Some(NetIntSourceXML { | ||||
|                         network: Some(network.to_string()), | ||||
|                         bridge: None, | ||||
|                     }), | ||||
|                     model, | ||||
|                     filterref, | ||||
|                 }, | ||||
|                 NetworkType::Bridge { bridge } => DomainNetInterfaceXML { | ||||
|                     r#type: "bridge".to_string(), | ||||
|                     mac, | ||||
|                     source: Some(NetIntSourceXML { | ||||
|                         network: None, | ||||
|                         bridge: Some(bridge.to_string()), | ||||
|                         network: network.to_string(), | ||||
|                     }), | ||||
|                     model, | ||||
|                     filterref, | ||||
| @@ -480,34 +468,7 @@ impl VMInfo { | ||||
|                         r#type: match d.r#type.as_str() { | ||||
|                             "user" => NetworkType::UserspaceSLIRPStack, | ||||
|                             "network" => NetworkType::DefinedNetwork { | ||||
|                                 network: d | ||||
|                                     .source | ||||
|                                     .as_ref() | ||||
|                                     .unwrap() | ||||
|                                     .network | ||||
|                                     .as_deref() | ||||
|                                     .ok_or_else(|| { | ||||
|                                         LibVirtStructError::DomainExtraction( | ||||
|                                             "Missing source network for defined network!" | ||||
|                                                 .to_string(), | ||||
|                                         ) | ||||
|                                     })? | ||||
|                                     .to_string(), | ||||
|                             }, | ||||
|                             "bridge" => NetworkType::Bridge { | ||||
|                                 bridge: d | ||||
|                                     .source | ||||
|                                     .as_ref() | ||||
|                                     .unwrap() | ||||
|                                     .bridge | ||||
|                                     .as_deref() | ||||
|                                     .ok_or_else(|| { | ||||
|                                         LibVirtStructError::DomainExtraction( | ||||
|                                             "Missing bridge name for bridge connection!" | ||||
|                                                 .to_string(), | ||||
|                                         ) | ||||
|                                     })? | ||||
|                                     .to_string(), | ||||
|                                 network: d.source.as_ref().unwrap().network.to_string(), | ||||
|                             }, | ||||
|                             a => { | ||||
|                                 return Err(LibVirtStructError::DomainExtraction(format!( | ||||
|   | ||||
| @@ -28,7 +28,7 @@ use virtweb_backend::controllers::{ | ||||
| use virtweb_backend::libvirt_client::LibVirtClient; | ||||
| use virtweb_backend::middlewares::auth_middleware::AuthChecker; | ||||
| use virtweb_backend::nat::nat_conf_mode; | ||||
| use virtweb_backend::utils::{exec_utils, files_utils}; | ||||
| use virtweb_backend::utils::files_utils; | ||||
|  | ||||
| #[actix_web::main] | ||||
| async fn main() -> std::io::Result<()> { | ||||
| @@ -43,16 +43,6 @@ async fn main() -> std::io::Result<()> { | ||||
|     // Load additional config from file, if requested | ||||
|     AppConfig::parse_env_file().unwrap(); | ||||
|  | ||||
|     log::debug!("Checking for required programs"); | ||||
|     exec_utils::check_program( | ||||
|         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(); | ||||
|     files_utils::create_directory_if_missing(AppConfig::get().vnc_sockets_path()).unwrap(); | ||||
| @@ -141,10 +131,6 @@ 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", | ||||
|   | ||||
| @@ -1,10 +0,0 @@ | ||||
| use std::path::Path; | ||||
|  | ||||
| /// Check the existence of a required program | ||||
| pub fn check_program(name: &str, description: &str) { | ||||
|     let path = Path::new(name); | ||||
|  | ||||
|     if !path.exists() { | ||||
|         panic!("{name} does not exist! {description}"); | ||||
|     } | ||||
| } | ||||
| @@ -155,7 +155,7 @@ impl FileDisk { | ||||
|             } | ||||
|  | ||||
|             DiskFormat::QCow2 => { | ||||
|                 let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM); | ||||
|                 let mut cmd = Command::new("/usr/bin/qemu-img"); | ||||
|                 cmd.arg("create") | ||||
|                     .arg("-f") | ||||
|                     .arg("qcow2") | ||||
| @@ -189,13 +189,12 @@ struct QCowInfoOutput { | ||||
| /// Get QCow2 virtual size | ||||
| fn qcow_virt_size(path: &str) -> anyhow::Result<usize> { | ||||
|     // Run qemu-img | ||||
|     let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM); | ||||
|     let mut cmd = Command::new("qemu-img"); | ||||
|     cmd.args(["info", path, "--output", "json", "--force-share"]); | ||||
|     let output = cmd.output()?; | ||||
|     if !output.status.success() { | ||||
|         anyhow::bail!( | ||||
|             "{} info failed, status: {}, stderr: {}", | ||||
|             constants::QEMU_IMAGE_PROGRAM, | ||||
|             "qemu-img info failed, status: {}, stderr: {}", | ||||
|             output.status, | ||||
|             String::from_utf8_lossy(&output.stderr) | ||||
|         ); | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| pub mod exec_utils; | ||||
| pub mod file_disks_utils; | ||||
| pub mod files_utils; | ||||
| pub mod net_utils; | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| 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; | ||||
|  | ||||
| @@ -70,7 +68,7 @@ pub fn net_list() -> Vec<String> { | ||||
|  | ||||
| /// Get the list of available network interfaces associated with their IP address | ||||
| pub fn net_list_and_ips() -> anyhow::Result<HashMap<String, Vec<IpAddr>>> { | ||||
|     let addrs = nix::ifaddrs::getifaddrs()?; | ||||
|     let addrs = nix::ifaddrs::getifaddrs().unwrap(); | ||||
|  | ||||
|     let mut res = HashMap::new(); | ||||
|  | ||||
| @@ -138,31 +136,6 @@ pub fn net_list_and_ips() -> anyhow::Result<HashMap<String, Vec<IpAddr>>> { | ||||
|     Ok(res) | ||||
| } | ||||
|  | ||||
| #[derive(serde::Deserialize)] | ||||
| struct IPBridgeInfo { | ||||
|     ifname: String, | ||||
| } | ||||
|  | ||||
| /// Get the list of bridge interfaces | ||||
| pub fn bridges_list() -> anyhow::Result<Vec<String>> { | ||||
|     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<IPBridgeInfo> = 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::{ | ||||
|   | ||||
| @@ -1,47 +0,0 @@ | ||||
| # Bridges | ||||
|  | ||||
| Bridges can be used to connect virtual machines to networks. | ||||
|  | ||||
| ## Setup Bridge on Ubuntu | ||||
|  | ||||
| 1. Install dependencies: | ||||
|  | ||||
| ```bash | ||||
| sudo apt install bridge-utils | ||||
| ``` | ||||
|  | ||||
| 2. Adapt your netplan configuration to set the following: | ||||
|  | ||||
| ```yaml | ||||
| network: | ||||
|   version: 2 | ||||
|   renderer: networkd | ||||
|   ethernets: | ||||
|     enp2s0: | ||||
|       dhcp4: no | ||||
|   bridges: | ||||
|     br0: # Bridge name | ||||
|       dhcp4: yes | ||||
|       interfaces: | ||||
|          - enp2s0 # Set to your interface | ||||
| ``` | ||||
|  | ||||
|  | ||||
| 3. Apply netplan configuration: | ||||
|  | ||||
| ```bash | ||||
| sudo netplan apply | ||||
| ``` | ||||
|  | ||||
|  | ||||
| 4. Get the state and the list of bridges in the system: | ||||
|  | ||||
| ```bash | ||||
| sudo brctl show | ||||
|  | ||||
| # Or | ||||
| ip link show type bridge | ||||
| ``` | ||||
|  | ||||
| ## Reference | ||||
| [How to Configure Network Bridge in Ubuntu](https://www.tecmint.com/create-network-bridge-in-ubuntu/) | ||||
| @@ -217,16 +217,4 @@ export class ServerApi { | ||||
|       }) | ||||
|     ).data; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get host networks bridges list | ||||
|    */ | ||||
|   static async GetNetworksBridgesList(): Promise<string[]> { | ||||
|     return ( | ||||
|       await APIClient.exec({ | ||||
|         method: "GET", | ||||
|         uri: "/server/bridges", | ||||
|       }) | ||||
|     ).data; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -50,11 +50,7 @@ export interface VMNetInterfaceFilter { | ||||
|   parameters: VMNetInterfaceFilterParams[]; | ||||
| } | ||||
|  | ||||
| export type VMNetInterface = ( | ||||
|   | VMNetUserspaceSLIRPStack | ||||
|   | VMNetDefinedNetwork | ||||
|   | VMNetBridge | ||||
| ) & | ||||
| export type VMNetInterface = (VMNetUserspaceSLIRPStack | VMNetDefinedNetwork) & | ||||
|   VMNetInterfaceBase; | ||||
|  | ||||
| export interface VMNetInterfaceBase { | ||||
| @@ -71,11 +67,6 @@ export interface VMNetDefinedNetwork { | ||||
|   network: string; | ||||
| } | ||||
|  | ||||
| export interface VMNetBridge { | ||||
|   type: "Bridge"; | ||||
|   bridge: string; | ||||
| } | ||||
|  | ||||
| interface VMInfoInterface { | ||||
|   name: string; | ||||
|   uuid?: string; | ||||
|   | ||||
| @@ -29,7 +29,6 @@ export function VMNetworksList(p: { | ||||
|   onChange?: () => void; | ||||
|   editable: boolean; | ||||
|   networksList: NetworkInfo[]; | ||||
|   bridgesList: string[]; | ||||
|   networkFiltersList: NWFilter[]; | ||||
| }): React.ReactElement { | ||||
|   const addNew = () => { | ||||
| @@ -73,7 +72,6 @@ function NetworkInfoWidget(p: { | ||||
|   onChange?: () => void; | ||||
|   removeFromList: () => void; | ||||
|   networksList: NetworkInfo[]; | ||||
|   bridgesList: string[]; | ||||
|   networkFiltersList: NWFilter[]; | ||||
| }): React.ReactElement { | ||||
|   const confirm = useConfirm(); | ||||
| @@ -132,11 +130,6 @@ function NetworkInfoWidget(p: { | ||||
|                     value: "DefinedNetwork", | ||||
|                     description: "Attach to a defined network", | ||||
|                   }, | ||||
|                   { | ||||
|                     label: "Host bridge", | ||||
|                     value: "Bridge", | ||||
|                     description: "Attach to an host's bridge", | ||||
|                   }, | ||||
|                 ]} | ||||
|               /> | ||||
|             ) : ( | ||||
| @@ -156,53 +149,31 @@ function NetworkInfoWidget(p: { | ||||
|           }} | ||||
|         /> | ||||
|  | ||||
|         {/* Defined network selection */} | ||||
|         {p.network.type === "DefinedNetwork" && ( | ||||
|           <SelectInput | ||||
|             editable={p.editable} | ||||
|             label="Defined network" | ||||
|             options={p.networksList.map((n) => { | ||||
|               const chars = [n.forward_mode.toString()]; | ||||
|               if (n.ip_v4) chars.push("IPv4"); | ||||
|               if (n.ip_v6) chars.push("IPv6"); | ||||
|               if (n.description) chars.push(n.description); | ||||
|  | ||||
|               return { | ||||
|                 label: n.name, | ||||
|                 value: n.name, | ||||
|                 description: chars.join(" - "), | ||||
|               }; | ||||
|             })} | ||||
|             value={p.network.network} | ||||
|             onValueChange={(v) => { | ||||
|               if (p.network.type === "DefinedNetwork") | ||||
|                 p.network.network = v as any; | ||||
|               p.onChange?.(); | ||||
|             }} | ||||
|           /> | ||||
|         )} | ||||
|  | ||||
|         {/* Bridge selection */} | ||||
|         {p.network.type === "Bridge" && ( | ||||
|           <SelectInput | ||||
|             editable={p.editable} | ||||
|             label="Host bridge" | ||||
|             options={p.bridgesList.map((n) => { | ||||
|               return { | ||||
|                 label: n, | ||||
|                 value: n, | ||||
|               }; | ||||
|             })} | ||||
|             value={p.network.bridge} | ||||
|             onValueChange={(v) => { | ||||
|               if (p.network.type === "Bridge") p.network.bridge = v as any; | ||||
|               p.onChange?.(); | ||||
|             }} | ||||
|           /> | ||||
|         )} | ||||
|  | ||||
|         {p.network.type !== "UserspaceSLIRPStack" && ( | ||||
|           <> | ||||
|             <SelectInput | ||||
|               editable={p.editable} | ||||
|               label="Defined network" | ||||
|               options={p.networksList.map((n) => { | ||||
|                 const chars = [n.forward_mode.toString()]; | ||||
|                 if (n.ip_v4) chars.push("IPv4"); | ||||
|                 if (n.ip_v6) chars.push("IPv6"); | ||||
|                 if (n.description) chars.push(n.description); | ||||
|  | ||||
|                 return { | ||||
|                   label: n.name, | ||||
|                   value: n.name, | ||||
|                   description: chars.join(" - "), | ||||
|                 }; | ||||
|               })} | ||||
|               value={p.network.network} | ||||
|               onValueChange={(v) => { | ||||
|                 if (p.network.type === "DefinedNetwork") | ||||
|                   p.network.network = v as any; | ||||
|                 p.onChange?.(); | ||||
|               }} | ||||
|             /> | ||||
|  | ||||
|             {/* Network Filter */} | ||||
|             <NWFilterSelectInput | ||||
|               editable={p.editable} | ||||
|   | ||||
| @@ -725,11 +725,6 @@ export function TokenRightsEditor(p: { | ||||
|           right={{ verb: "GET", path: "/api/server/networks" }} | ||||
|           label="Get list of network cards" | ||||
|         /> | ||||
|         <RouteRight | ||||
|           {...p} | ||||
|           right={{ verb: "GET", path: "/api/server/bridges" }} | ||||
|           label="Get list of network bridges" | ||||
|         /> | ||||
|       </RightsSection> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -38,7 +38,6 @@ interface DetailsProps { | ||||
| export function VMDetails(p: DetailsProps): React.ReactElement { | ||||
|   const [groupsList, setGroupsList] = React.useState<string[] | undefined>(); | ||||
|   const [isoList, setIsoList] = React.useState<IsoFile[] | undefined>(); | ||||
|   const [bridgesList, setBridgesList] = React.useState<string[] | undefined>(); | ||||
|   const [vcpuCombinations, setVCPUCombinations] = React.useState< | ||||
|     number[] | undefined | ||||
|   >(); | ||||
| @@ -52,7 +51,6 @@ export function VMDetails(p: DetailsProps): React.ReactElement { | ||||
|   const load = async () => { | ||||
|     setGroupsList(await GroupApi.GetList()); | ||||
|     setIsoList(await IsoFilesApi.GetList()); | ||||
|     setBridgesList(await ServerApi.GetNetworksBridgesList()); | ||||
|     setVCPUCombinations(await ServerApi.NumberVCPUs()); | ||||
|     setNetworksList(await NetworkApi.GetList()); | ||||
|     setNetworkFiltersList(await NWFilterApi.GetList()); | ||||
| @@ -67,7 +65,6 @@ export function VMDetails(p: DetailsProps): React.ReactElement { | ||||
|         <VMDetailsInner | ||||
|           groupsList={groupsList!} | ||||
|           isoList={isoList!} | ||||
|           bridgesList={bridgesList!} | ||||
|           vcpuCombinations={vcpuCombinations!} | ||||
|           networksList={networksList!} | ||||
|           networkFiltersList={networkFiltersList!} | ||||
| @@ -90,7 +87,6 @@ enum VMTab { | ||||
| type DetailsInnerProps = DetailsProps & { | ||||
|   groupsList: string[]; | ||||
|   isoList: IsoFile[]; | ||||
|   bridgesList: string[]; | ||||
|   vcpuCombinations: number[]; | ||||
|   networksList: NetworkInfo[]; | ||||
|   networkFiltersList: NWFilter[]; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user