Can specify MAC addresses for domains
This commit is contained in:
parent
6c56b62833
commit
924c972984
@ -43,3 +43,6 @@ pub const DISK_SIZE_MIN: usize = 100;
|
|||||||
|
|
||||||
/// Disk size max (MB)
|
/// Disk size max (MB)
|
||||||
pub const DISK_SIZE_MAX: usize = 1000 * 1000 * 2;
|
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,
|
local_auth_enabled: bool,
|
||||||
oidc_auth_enabled: bool,
|
oidc_auth_enabled: bool,
|
||||||
iso_mimetypes: &'static [&'static str],
|
iso_mimetypes: &'static [&'static str],
|
||||||
|
net_mac_prefix: &'static str,
|
||||||
constraints: ServerConstraints,
|
constraints: ServerConstraints,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
|||||||
local_auth_enabled: *local_auth,
|
local_auth_enabled: *local_auth,
|
||||||
oidc_auth_enabled: !AppConfig::get().disable_oidc,
|
oidc_auth_enabled: !AppConfig::get().disable_oidc,
|
||||||
iso_mimetypes: &constants::ALLOWED_ISO_MIME_TYPES,
|
iso_mimetypes: &constants::ALLOWED_ISO_MIME_TYPES,
|
||||||
|
net_mac_prefix: constants::NET_MAC_ADDR_PREFIX,
|
||||||
constraints: ServerConstraints {
|
constraints: ServerConstraints {
|
||||||
iso_max_size: constants::ISO_MAX_SIZE,
|
iso_max_size: constants::ISO_MAX_SIZE,
|
||||||
|
|
||||||
|
@ -63,6 +63,13 @@ pub struct FeaturesXML {
|
|||||||
#[serde(rename = "acpi")]
|
#[serde(rename = "acpi")]
|
||||||
pub struct ACPIXML {}
|
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)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename = "source")]
|
#[serde(rename = "source")]
|
||||||
pub struct NetIntSourceXML {
|
pub struct NetIntSourceXML {
|
||||||
@ -75,7 +82,7 @@ pub struct NetIntSourceXML {
|
|||||||
pub struct DomainNetInterfaceXML {
|
pub struct DomainNetInterfaceXML {
|
||||||
#[serde(rename(serialize = "@type"))]
|
#[serde(rename(serialize = "@type"))]
|
||||||
pub r#type: String,
|
pub r#type: String,
|
||||||
|
pub mac: NetMacAddress,
|
||||||
pub source: Option<NetIntSourceXML>,
|
pub source: Option<NetIntSourceXML>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ use crate::constants;
|
|||||||
use crate::libvirt_lib_structures::{
|
use crate::libvirt_lib_structures::{
|
||||||
DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
|
DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
|
||||||
DomainCPUTopology, DomainCPUXML, DomainInputXML, DomainMemoryXML, DomainNetInterfaceXML,
|
DomainCPUTopology, DomainCPUXML, DomainInputXML, DomainMemoryXML, DomainNetInterfaceXML,
|
||||||
DomainVCPUXML, DomainXML, FeaturesXML, GraphicsXML, NetIntSourceXML, NetworkDHCPRangeXML,
|
DomainVCPUXML, DomainXML, FeaturesXML, GraphicsXML, NetIntSourceXML, NetMacAddress,
|
||||||
NetworkDHCPXML, NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML, NetworkForwardXML,
|
NetworkDHCPRangeXML, NetworkDHCPXML, NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML,
|
||||||
NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML, TPMBackendXML, TPMDeviceXML, VideoModelXML,
|
NetworkForwardXML, NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML, TPMBackendXML,
|
||||||
VideoXML, XMLUuid, ACPIXML, OSXML,
|
TPMDeviceXML, VideoModelXML, VideoXML, XMLUuid, ACPIXML, OSXML,
|
||||||
};
|
};
|
||||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
|
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
|
||||||
use crate::utils::disks_utils::Disk;
|
use crate::utils::disks_utils::Disk;
|
||||||
@ -65,9 +65,16 @@ pub enum VMArchitecture {
|
|||||||
X86_64,
|
X86_64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Network {
|
||||||
|
mac: String,
|
||||||
|
#[serde(flatten)]
|
||||||
|
r#type: NetworkType,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum Network {
|
pub enum NetworkType {
|
||||||
UserspaceSLIRPStack,
|
UserspaceSLIRPStack,
|
||||||
DefinedNetwork { network: String }, // TODO : complete network types
|
DefinedNetwork { network: String }, // TODO : complete network types
|
||||||
}
|
}
|
||||||
@ -235,12 +242,14 @@ impl VMInfo {
|
|||||||
|
|
||||||
let mut networks = vec![];
|
let mut networks = vec![];
|
||||||
for n in self.networks {
|
for n in self.networks {
|
||||||
networks.push(match n {
|
networks.push(match n.r#type {
|
||||||
Network::UserspaceSLIRPStack => DomainNetInterfaceXML {
|
NetworkType::UserspaceSLIRPStack => DomainNetInterfaceXML {
|
||||||
|
mac: NetMacAddress { address: n.mac },
|
||||||
r#type: "user".to_string(),
|
r#type: "user".to_string(),
|
||||||
source: None,
|
source: None,
|
||||||
},
|
},
|
||||||
Network::DefinedNetwork { network } => DomainNetInterfaceXML {
|
NetworkType::DefinedNetwork { network } => DomainNetInterfaceXML {
|
||||||
|
mac: NetMacAddress { address: n.mac },
|
||||||
r#type: "network".to_string(),
|
r#type: "network".to_string(),
|
||||||
source: Some(NetIntSourceXML { network }),
|
source: Some(NetIntSourceXML { network }),
|
||||||
},
|
},
|
||||||
@ -382,14 +391,21 @@ impl VMInfo {
|
|||||||
.devices
|
.devices
|
||||||
.net_interfaces
|
.net_interfaces
|
||||||
.iter()
|
.iter()
|
||||||
.map(|d| match d.r#type.as_str() {
|
.map(|d| {
|
||||||
"user" => Ok(Network::UserspaceSLIRPStack),
|
Ok(Network {
|
||||||
"network" => Ok(Network::DefinedNetwork {
|
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(),
|
network: d.source.as_ref().unwrap().network.to_string(),
|
||||||
}),
|
},
|
||||||
a => Err(LibVirtStructError::DomainExtraction(format!(
|
a => {
|
||||||
|
return Err(LibVirtStructError::DomainExtraction(format!(
|
||||||
"Unknown network interface type: {a}! "
|
"Unknown network interface type: {a}! "
|
||||||
))),
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ export interface ServerConfig {
|
|||||||
local_auth_enabled: boolean;
|
local_auth_enabled: boolean;
|
||||||
oidc_auth_enabled: boolean;
|
oidc_auth_enabled: boolean;
|
||||||
iso_mimetypes: string[];
|
iso_mimetypes: string[];
|
||||||
|
net_mac_prefix: string;
|
||||||
constraints: ServerConstraints;
|
constraints: ServerConstraints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,10 +34,12 @@ export type VMNetInterface = VMNetUserspaceSLIRPStack | VMNetDefinedNetwork;
|
|||||||
|
|
||||||
export interface VMNetUserspaceSLIRPStack {
|
export interface VMNetUserspaceSLIRPStack {
|
||||||
type: "UserspaceSLIRPStack";
|
type: "UserspaceSLIRPStack";
|
||||||
|
mac: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VMNetDefinedNetwork {
|
export interface VMNetDefinedNetwork {
|
||||||
type: "DefinedNetwork";
|
type: "DefinedNetwork";
|
||||||
|
mac: string;
|
||||||
network: 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 { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
||||||
import { SelectInput } from "./SelectInput";
|
import { SelectInput } from "./SelectInput";
|
||||||
import { NetworkInfo } from "../../api/NetworksApi";
|
import { NetworkInfo } from "../../api/NetworksApi";
|
||||||
|
import { randomMacAddress } from "../../utils/RandUtils";
|
||||||
|
import { ServerApi } from "../../api/ServerApi";
|
||||||
|
import { MACInput } from "./MACInput";
|
||||||
|
|
||||||
export function VMNetworksList(p: {
|
export function VMNetworksList(p: {
|
||||||
vm: VMInfo;
|
vm: VMInfo;
|
||||||
@ -22,7 +25,10 @@ export function VMNetworksList(p: {
|
|||||||
networksList: NetworkInfo[];
|
networksList: NetworkInfo[];
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const addNew = () => {
|
const addNew = () => {
|
||||||
p.vm.networks.push({ type: "UserspaceSLIRPStack" });
|
p.vm.networks.push({
|
||||||
|
type: "UserspaceSLIRPStack",
|
||||||
|
mac: randomMacAddress(ServerApi.Config.net_mac_prefix),
|
||||||
|
});
|
||||||
p.onChange?.();
|
p.onChange?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -120,6 +126,16 @@ function NetworkInfoWidget(p: {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<div style={{ marginLeft: "70px" }}>
|
<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" && (
|
{p.network.type === "DefinedNetwork" && (
|
||||||
<SelectInput
|
<SelectInput
|
||||||
editable={p.editable}
|
editable={p.editable}
|
||||||
|
Loading…
Reference in New Issue
Block a user