Can specify MAC addresses for domains
This commit is contained in:
		@@ -43,3 +43,6 @@ pub const DISK_SIZE_MIN: usize = 100;
 | 
			
		||||
 | 
			
		||||
/// Disk size max (MB)
 | 
			
		||||
pub const DISK_SIZE_MAX: usize = 1000 * 1000 * 2;
 | 
			
		||||
 | 
			
		||||
/// Network mac address default prefix
 | 
			
		||||
pub const NET_MAC_ADDR_PREFIX: &str = "52:54:00";
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ struct StaticConfig {
 | 
			
		||||
    local_auth_enabled: bool,
 | 
			
		||||
    oidc_auth_enabled: bool,
 | 
			
		||||
    iso_mimetypes: &'static [&'static str],
 | 
			
		||||
    net_mac_prefix: &'static str,
 | 
			
		||||
    constraints: ServerConstraints,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -42,6 +43,7 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
 | 
			
		||||
        local_auth_enabled: *local_auth,
 | 
			
		||||
        oidc_auth_enabled: !AppConfig::get().disable_oidc,
 | 
			
		||||
        iso_mimetypes: &constants::ALLOWED_ISO_MIME_TYPES,
 | 
			
		||||
        net_mac_prefix: constants::NET_MAC_ADDR_PREFIX,
 | 
			
		||||
        constraints: ServerConstraints {
 | 
			
		||||
            iso_max_size: constants::ISO_MAX_SIZE,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -63,6 +63,13 @@ pub struct FeaturesXML {
 | 
			
		||||
#[serde(rename = "acpi")]
 | 
			
		||||
pub struct ACPIXML {}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "mac")]
 | 
			
		||||
pub struct NetMacAddress {
 | 
			
		||||
    #[serde(rename(serialize = "@address"))]
 | 
			
		||||
    pub address: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "source")]
 | 
			
		||||
pub struct NetIntSourceXML {
 | 
			
		||||
@@ -75,7 +82,7 @@ pub struct NetIntSourceXML {
 | 
			
		||||
pub struct DomainNetInterfaceXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
 | 
			
		||||
    pub mac: NetMacAddress,
 | 
			
		||||
    pub source: Option<NetIntSourceXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,10 @@ use crate::constants;
 | 
			
		||||
use crate::libvirt_lib_structures::{
 | 
			
		||||
    DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
 | 
			
		||||
    DomainCPUTopology, DomainCPUXML, DomainInputXML, DomainMemoryXML, DomainNetInterfaceXML,
 | 
			
		||||
    DomainVCPUXML, DomainXML, FeaturesXML, GraphicsXML, NetIntSourceXML, NetworkDHCPRangeXML,
 | 
			
		||||
    NetworkDHCPXML, NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML, NetworkForwardXML,
 | 
			
		||||
    NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML, TPMBackendXML, TPMDeviceXML, VideoModelXML,
 | 
			
		||||
    VideoXML, XMLUuid, ACPIXML, OSXML,
 | 
			
		||||
    DomainVCPUXML, DomainXML, FeaturesXML, GraphicsXML, NetIntSourceXML, NetMacAddress,
 | 
			
		||||
    NetworkDHCPRangeXML, NetworkDHCPXML, NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML,
 | 
			
		||||
    NetworkForwardXML, NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML, TPMBackendXML,
 | 
			
		||||
    TPMDeviceXML, VideoModelXML, VideoXML, XMLUuid, ACPIXML, OSXML,
 | 
			
		||||
};
 | 
			
		||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
 | 
			
		||||
use crate::utils::disks_utils::Disk;
 | 
			
		||||
@@ -65,9 +65,16 @@ pub enum VMArchitecture {
 | 
			
		||||
    X86_64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub struct Network {
 | 
			
		||||
    mac: String,
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    r#type: NetworkType,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(tag = "type")]
 | 
			
		||||
pub enum Network {
 | 
			
		||||
pub enum NetworkType {
 | 
			
		||||
    UserspaceSLIRPStack,
 | 
			
		||||
    DefinedNetwork { network: String }, // TODO : complete network types
 | 
			
		||||
}
 | 
			
		||||
@@ -235,12 +242,14 @@ impl VMInfo {
 | 
			
		||||
 | 
			
		||||
        let mut networks = vec![];
 | 
			
		||||
        for n in self.networks {
 | 
			
		||||
            networks.push(match n {
 | 
			
		||||
                Network::UserspaceSLIRPStack => DomainNetInterfaceXML {
 | 
			
		||||
            networks.push(match n.r#type {
 | 
			
		||||
                NetworkType::UserspaceSLIRPStack => DomainNetInterfaceXML {
 | 
			
		||||
                    mac: NetMacAddress { address: n.mac },
 | 
			
		||||
                    r#type: "user".to_string(),
 | 
			
		||||
                    source: None,
 | 
			
		||||
                },
 | 
			
		||||
                Network::DefinedNetwork { network } => DomainNetInterfaceXML {
 | 
			
		||||
                NetworkType::DefinedNetwork { network } => DomainNetInterfaceXML {
 | 
			
		||||
                    mac: NetMacAddress { address: n.mac },
 | 
			
		||||
                    r#type: "network".to_string(),
 | 
			
		||||
                    source: Some(NetIntSourceXML { network }),
 | 
			
		||||
                },
 | 
			
		||||
@@ -382,14 +391,21 @@ impl VMInfo {
 | 
			
		||||
                .devices
 | 
			
		||||
                .net_interfaces
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|d| match d.r#type.as_str() {
 | 
			
		||||
                    "user" => Ok(Network::UserspaceSLIRPStack),
 | 
			
		||||
                    "network" => Ok(Network::DefinedNetwork {
 | 
			
		||||
                        network: d.source.as_ref().unwrap().network.to_string(),
 | 
			
		||||
                    }),
 | 
			
		||||
                    a => Err(LibVirtStructError::DomainExtraction(format!(
 | 
			
		||||
                        "Unknown network interface type: {a}! "
 | 
			
		||||
                    ))),
 | 
			
		||||
                .map(|d| {
 | 
			
		||||
                    Ok(Network {
 | 
			
		||||
                        mac: d.mac.address.to_string(),
 | 
			
		||||
                        r#type: match d.r#type.as_str() {
 | 
			
		||||
                            "user" => NetworkType::UserspaceSLIRPStack,
 | 
			
		||||
                            "network" => NetworkType::DefinedNetwork {
 | 
			
		||||
                                network: d.source.as_ref().unwrap().network.to_string(),
 | 
			
		||||
                            },
 | 
			
		||||
                            a => {
 | 
			
		||||
                                return Err(LibVirtStructError::DomainExtraction(format!(
 | 
			
		||||
                                    "Unknown network interface type: {a}! "
 | 
			
		||||
                                )));
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                    })
 | 
			
		||||
                })
 | 
			
		||||
                .collect::<Result<Vec<_>, _>>()?,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ export interface ServerConfig {
 | 
			
		||||
  local_auth_enabled: boolean;
 | 
			
		||||
  oidc_auth_enabled: boolean;
 | 
			
		||||
  iso_mimetypes: string[];
 | 
			
		||||
  net_mac_prefix: string;
 | 
			
		||||
  constraints: ServerConstraints;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,10 +34,12 @@ export type VMNetInterface = VMNetUserspaceSLIRPStack | VMNetDefinedNetwork;
 | 
			
		||||
 | 
			
		||||
export interface VMNetUserspaceSLIRPStack {
 | 
			
		||||
  type: "UserspaceSLIRPStack";
 | 
			
		||||
  mac: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface VMNetDefinedNetwork {
 | 
			
		||||
  type: "DefinedNetwork";
 | 
			
		||||
  mac: string;
 | 
			
		||||
  network: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								virtweb_frontend/src/utils/RandUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								virtweb_frontend/src/utils/RandUtils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Generate a random MAC address
 | 
			
		||||
 */
 | 
			
		||||
export function randomMacAddress(prefix: string | undefined): string {
 | 
			
		||||
  let mac = "XX:XX:XX:XX:XX:XX";
 | 
			
		||||
  mac = prefix + mac.slice(prefix?.length);
 | 
			
		||||
 | 
			
		||||
  return mac.replace(/X/g, () =>
 | 
			
		||||
    "0123456789abcdef".charAt(Math.floor(Math.random() * 16))
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								virtweb_frontend/src/widgets/forms/MACInput.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								virtweb_frontend/src/widgets/forms/MACInput.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
import { TextInput } from "./TextInput";
 | 
			
		||||
 | 
			
		||||
export function MACInput(p: {
 | 
			
		||||
  label: string;
 | 
			
		||||
  editable: boolean;
 | 
			
		||||
  value?: string;
 | 
			
		||||
  onValueChange?: (newVal: string | undefined) => void;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const { onValueChange, ...props } = p;
 | 
			
		||||
  return (
 | 
			
		||||
    <TextInput
 | 
			
		||||
      onValueChange={(v) => {
 | 
			
		||||
        onValueChange?.(sanitizeMacAddress(v));
 | 
			
		||||
      }}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function sanitizeMacAddress(s: string | undefined): string | undefined {
 | 
			
		||||
  if (s === "" || s === undefined) return s;
 | 
			
		||||
 | 
			
		||||
  const split = s.split(":");
 | 
			
		||||
  if (split.length > 6) split.splice(6);
 | 
			
		||||
 | 
			
		||||
  let needAnotherIteration = false;
 | 
			
		||||
 | 
			
		||||
  const res = split
 | 
			
		||||
    .map((e) => {
 | 
			
		||||
      if (e === "") return e;
 | 
			
		||||
 | 
			
		||||
      const num = parseInt(e, 16);
 | 
			
		||||
      if (isNaN(num)) return "0";
 | 
			
		||||
 | 
			
		||||
      let s = num.toString(16).padStart(2, "0");
 | 
			
		||||
      if (num > 0xff) {
 | 
			
		||||
        needAnotherIteration = true;
 | 
			
		||||
        return s.slice(0, 2) + ":" + s.slice(2);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return s;
 | 
			
		||||
    })
 | 
			
		||||
    .join(":");
 | 
			
		||||
 | 
			
		||||
  return needAnotherIteration ? sanitizeMacAddress(res) : res;
 | 
			
		||||
}
 | 
			
		||||
@@ -14,6 +14,9 @@ import { VMInfo, VMNetInterface } from "../../api/VMApi";
 | 
			
		||||
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
 | 
			
		||||
import { SelectInput } from "./SelectInput";
 | 
			
		||||
import { NetworkInfo } from "../../api/NetworksApi";
 | 
			
		||||
import { randomMacAddress } from "../../utils/RandUtils";
 | 
			
		||||
import { ServerApi } from "../../api/ServerApi";
 | 
			
		||||
import { MACInput } from "./MACInput";
 | 
			
		||||
 | 
			
		||||
export function VMNetworksList(p: {
 | 
			
		||||
  vm: VMInfo;
 | 
			
		||||
@@ -22,7 +25,10 @@ export function VMNetworksList(p: {
 | 
			
		||||
  networksList: NetworkInfo[];
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const addNew = () => {
 | 
			
		||||
    p.vm.networks.push({ type: "UserspaceSLIRPStack" });
 | 
			
		||||
    p.vm.networks.push({
 | 
			
		||||
      type: "UserspaceSLIRPStack",
 | 
			
		||||
      mac: randomMacAddress(ServerApi.Config.net_mac_prefix),
 | 
			
		||||
    });
 | 
			
		||||
    p.onChange?.();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@@ -120,6 +126,16 @@ function NetworkInfoWidget(p: {
 | 
			
		||||
        />
 | 
			
		||||
      </ListItem>
 | 
			
		||||
      <div style={{ marginLeft: "70px" }}>
 | 
			
		||||
        <MACInput
 | 
			
		||||
          editable={p.editable}
 | 
			
		||||
          label="MAC Address"
 | 
			
		||||
          value={p.network.mac}
 | 
			
		||||
          onValueChange={(v) => {
 | 
			
		||||
            p.network.mac = v!;
 | 
			
		||||
            p.onChange?.();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {p.network.type === "DefinedNetwork" && (
 | 
			
		||||
          <SelectInput
 | 
			
		||||
            editable={p.editable}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user