1 // Copyright © 2019 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 use std::collections::{BTreeSet, HashMap}; 7 use std::path::PathBuf; 8 use std::result; 9 use std::str::FromStr; 10 11 use clap::ArgMatches; 12 use option_parser::{ 13 ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, Tuple, 14 }; 15 use serde::{Deserialize, Serialize}; 16 use thiserror::Error; 17 use virtio_bindings::virtio_blk::VIRTIO_BLK_ID_BYTES; 18 use virtio_devices::block::MINIMUM_BLOCK_QUEUE_SIZE; 19 use virtio_devices::vhost_user::VIRTIO_FS_TAG_LEN; 20 use virtio_devices::{RateLimiterConfig, TokenBucketConfig}; 21 22 use crate::landlock::LandlockAccess; 23 use crate::vm_config::*; 24 25 const MAX_NUM_PCI_SEGMENTS: u16 = 96; 26 const MAX_IOMMU_ADDRESS_WIDTH_BITS: u8 = 64; 27 28 /// Errors associated with VM configuration parameters. 29 #[derive(Debug, Error)] 30 pub enum Error { 31 /// Filesystem tag is missing 32 #[error("Error parsing --fs: tag missing")] 33 ParseFsTagMissing, 34 /// Filesystem tag is too long 35 #[error("Error parsing --fs: max tag length is {VIRTIO_FS_TAG_LEN}")] 36 ParseFsTagTooLong, 37 /// Filesystem socket is missing 38 #[error("Error parsing --fs: socket missing")] 39 ParseFsSockMissing, 40 /// Missing persistent memory file parameter. 41 #[error("Error parsing --pmem: file missing")] 42 ParsePmemFileMissing, 43 /// Missing vsock socket path parameter. 44 #[error("Error parsing --vsock: socket missing")] 45 ParseVsockSockMissing, 46 /// Missing vsock cid parameter. 47 #[error("Error parsing --vsock: cid missing")] 48 ParseVsockCidMissing, 49 /// Missing restore source_url parameter. 50 #[error("Error parsing --restore: source_url missing")] 51 ParseRestoreSourceUrlMissing, 52 /// Error parsing CPU options 53 #[error("Error parsing --cpus")] 54 ParseCpus(#[source] OptionParserError), 55 /// Invalid CPU features 56 #[error("Invalid feature in --cpus features list: {0}")] 57 InvalidCpuFeatures(String), 58 /// Error parsing memory options 59 #[error("Error parsing --memory")] 60 ParseMemory(#[source] OptionParserError), 61 /// Error parsing memory zone options 62 #[error("Error parsing --memory-zone")] 63 ParseMemoryZone(#[source] OptionParserError), 64 /// Missing 'id' from memory zone 65 #[error("Error parsing --memory-zone: id missing")] 66 ParseMemoryZoneIdMissing, 67 /// Error parsing rate-limiter group options 68 #[error("Error parsing --rate-limit-group")] 69 ParseRateLimiterGroup(#[source] OptionParserError), 70 /// Error parsing disk options 71 #[error("Error parsing --disk")] 72 ParseDisk(#[source] OptionParserError), 73 /// Error parsing network options 74 #[error("Error parsing --net")] 75 ParseNetwork(#[source] OptionParserError), 76 /// Error parsing RNG options 77 #[error("Error parsing --rng")] 78 ParseRng(#[source] OptionParserError), 79 /// Error parsing balloon options 80 #[error("Error parsing --balloon")] 81 ParseBalloon(#[source] OptionParserError), 82 /// Error parsing filesystem parameters 83 #[error("Error parsing --fs")] 84 ParseFileSystem(#[source] OptionParserError), 85 /// Error parsing persistent memory parameters 86 #[error("Error parsing --pmem")] 87 ParsePersistentMemory(#[source] OptionParserError), 88 /// Failed parsing console 89 #[error("Error parsing --console")] 90 ParseConsole(#[source] OptionParserError), 91 #[cfg(target_arch = "x86_64")] 92 /// Failed parsing debug-console 93 #[error("Error parsing --debug-console")] 94 ParseDebugConsole(#[source] OptionParserError), 95 /// No mode given for console 96 #[error("Error parsing --console: invalid console mode given")] 97 ParseConsoleInvalidModeGiven, 98 /// Failed parsing device parameters 99 #[error("Error parsing --device")] 100 ParseDevice(#[source] OptionParserError), 101 /// Missing path from device, 102 #[error("Error parsing --device: path missing")] 103 ParseDevicePathMissing, 104 /// Failed parsing vsock parameters 105 #[error("Error parsing --vsock")] 106 ParseVsock(#[source] OptionParserError), 107 /// Failed parsing restore parameters 108 #[error("Error parsing --restore")] 109 ParseRestore(#[source] OptionParserError), 110 /// Failed parsing SGX EPC parameters 111 #[cfg(target_arch = "x86_64")] 112 #[error("Error parsing --sgx-epc")] 113 ParseSgxEpc(#[source] OptionParserError), 114 /// Missing 'id' from SGX EPC section 115 #[cfg(target_arch = "x86_64")] 116 #[error("Error parsing --sgx-epc: id missing")] 117 ParseSgxEpcIdMissing, 118 /// Failed parsing NUMA parameters 119 #[error("Error parsing --numa")] 120 ParseNuma(#[source] OptionParserError), 121 /// Failed validating configuration 122 #[error("Error validating configuration")] 123 Validation(#[source] ValidationError), 124 #[cfg(feature = "sev_snp")] 125 #[error("Error parsing --sev_snp")] 126 /// Failed parsing SEV-SNP config 127 ParseSevSnp(#[source] OptionParserError), 128 #[cfg(feature = "tdx")] 129 #[error("Error parsing --tdx")] 130 /// Failed parsing TDX config 131 ParseTdx(#[source] OptionParserError), 132 #[cfg(feature = "tdx")] 133 #[error("TDX firmware missing")] 134 /// No TDX firmware 135 FirmwarePathMissing, 136 /// Failed parsing userspace device 137 #[error("Error parsing --user-device")] 138 ParseUserDevice(#[source] OptionParserError), 139 /// Missing socket for userspace device 140 #[error("Error parsing --user-device: socket missing")] 141 ParseUserDeviceSocketMissing, 142 /// Error parsing pci segment options 143 #[error("Error parsing --pci-segment")] 144 ParsePciSegment(#[source] OptionParserError), 145 /// Failed parsing platform parameters 146 #[error("Error parsing --platform")] 147 ParsePlatform(#[source] OptionParserError), 148 /// Failed parsing vDPA device 149 #[error("Error parsing --vdpa")] 150 ParseVdpa(#[source] OptionParserError), 151 /// Missing path for vDPA device 152 #[error("Error parsing --vdpa: path missing")] 153 ParseVdpaPathMissing, 154 /// Failed parsing TPM device 155 #[error("Error parsing --tpm")] 156 ParseTpm(#[source] OptionParserError), 157 /// Missing path for TPM device 158 #[error("Error parsing --tpm: path missing")] 159 ParseTpmPathMissing, 160 /// Error parsing Landlock rules 161 #[error("Error parsing --landlock-rules")] 162 ParseLandlockRules(#[source] OptionParserError), 163 /// Missing fields in Landlock rules 164 #[error("Error parsing --landlock-rules: path/access field missing")] 165 ParseLandlockMissingFields, 166 } 167 168 #[derive(Debug, PartialEq, Eq, Error)] 169 pub enum ValidationError { 170 /// No kernel specified 171 #[error("No kernel specified")] 172 KernelMissing, 173 /// Missing file value for console 174 #[error("Path missing when using file console mode")] 175 ConsoleFileMissing, 176 /// Missing socket path for console 177 #[error("Path missing when using socket console mode")] 178 ConsoleSocketPathMissing, 179 /// Max is less than boot 180 #[error("Max CPUs lower than boot CPUs")] 181 CpusMaxLowerThanBoot, 182 /// Missing file value for debug-console 183 #[cfg(target_arch = "x86_64")] 184 #[error("Path missing when using file mode for debug console")] 185 DebugconFileMissing, 186 /// Both socket and path specified 187 #[error("Disk path and vhost socket both provided")] 188 DiskSocketAndPath, 189 /// Using vhost user requires shared memory 190 #[error("Using vhost-user requires using shared memory or huge pages")] 191 VhostUserRequiresSharedMemory, 192 /// No socket provided for vhost_use 193 #[error("No socket provided when using vhost-user")] 194 VhostUserMissingSocket, 195 /// Trying to use IOMMU without PCI 196 #[error("Using an IOMMU without PCI support is unsupported")] 197 IommuUnsupported, 198 /// Trying to use VFIO without PCI 199 #[error("Using VFIO without PCI support is unsupported")] 200 VfioUnsupported, 201 /// CPU topology count doesn't match max 202 #[error("Product of CPU topology parts does not match maximum vCPU")] 203 CpuTopologyCount, 204 /// One part of the CPU topology was zero 205 #[error("No part of the CPU topology can be zero")] 206 CpuTopologyZeroPart, 207 #[cfg(target_arch = "aarch64")] 208 /// Dies per package must be 1 209 #[error("Dies per package must be 1")] 210 CpuTopologyDiesPerPackage, 211 /// Virtio needs a min of 2 queues 212 #[error("Number of queues to virtio_net less than 2")] 213 VnetQueueLowerThan2, 214 /// The input queue number for virtio_net must match the number of input fds 215 #[error("Number of queues to virtio_net does not match the number of input FDs")] 216 VnetQueueFdMismatch, 217 /// Using reserved fd 218 #[error("Reserved fd number (<= 2)")] 219 VnetReservedFd, 220 /// Hardware checksum offload is disabled. 221 #[error("\"offload_tso\" and \"offload_ufo\" depend on \"offload_csum\"")] 222 NoHardwareChecksumOffload, 223 /// Hugepages not turned on 224 #[error("Huge page size specified but huge pages not enabled")] 225 HugePageSizeWithoutHugePages, 226 /// Huge page size is not power of 2 227 #[error("Huge page size is not power of 2: {0}")] 228 InvalidHugePageSize(u64), 229 /// CPU Hotplug is not permitted with TDX 230 #[cfg(feature = "tdx")] 231 #[error("CPU hotplug is not permitted with TDX")] 232 TdxNoCpuHotplug, 233 /// Missing firmware for TDX 234 #[cfg(feature = "tdx")] 235 #[error("No TDX firmware specified")] 236 TdxFirmwareMissing, 237 /// Insufficient vCPUs for queues 238 #[error("Number of vCPUs is insufficient for number of queues")] 239 TooManyQueues, 240 /// Invalid queue size 241 #[error("Queue size is smaller than {MINIMUM_BLOCK_QUEUE_SIZE}: {0}")] 242 InvalidQueueSize(u16), 243 /// Need shared memory for vfio-user 244 #[error("Using user devices requires using shared memory or huge pages")] 245 UserDevicesRequireSharedMemory, 246 /// VSOCK Context Identifier has a special meaning, unsuitable for a VM. 247 #[error("{0} is a special VSOCK CID")] 248 VsockSpecialCid(u32), 249 /// Memory zone is reused across NUMA nodes 250 #[error("Memory zone: {0} belongs to multiple NUMA nodes: {1} and {2}")] 251 MemoryZoneReused(String, u32, u32), 252 /// Invalid number of PCI segments 253 #[error("Number of PCI segments ({0}) not in range of 1 to {MAX_NUM_PCI_SEGMENTS}")] 254 InvalidNumPciSegments(u16), 255 /// Invalid PCI segment id 256 #[error("Invalid PCI segment id: {0}")] 257 InvalidPciSegment(u16), 258 /// Invalid PCI segment aperture weight 259 #[error("Invalid PCI segment aperture weight: {0}")] 260 InvalidPciSegmentApertureWeight(u32), 261 /// Invalid IOMMU address width in bits 262 #[error("IOMMU address width in bits ({0}) should be less than or equal to {MAX_IOMMU_ADDRESS_WIDTH_BITS}")] 263 InvalidIommuAddressWidthBits(u8), 264 /// Balloon too big 265 #[error("Ballon size ({0}) greater than RAM ({1})")] 266 BalloonLargerThanRam(u64, u64), 267 /// On a IOMMU segment but not behind IOMMU 268 #[error("Device is on an IOMMU PCI segment ({0}) but not placed behind IOMMU")] 269 OnIommuSegment(u16), 270 // On a IOMMU segment but IOMMU not supported 271 #[error( 272 "Device is on an IOMMU PCI segment ({0}) but does not support being placed behind IOMMU" 273 )] 274 IommuNotSupportedOnSegment(u16), 275 // Identifier is not unique 276 #[error("Identifier {0} is not unique")] 277 IdentifierNotUnique(String), 278 /// Invalid identifier 279 #[error("Identifier {0} is not invalid")] 280 InvalidIdentifier(String), 281 /// Placing the device behind a virtual IOMMU is not supported 282 #[error("Device does not support being placed behind IOMMU")] 283 IommuNotSupported, 284 /// Duplicated device path (device added twice) 285 #[error("Duplicated device path: {0}")] 286 DuplicateDevicePath(String), 287 /// Provided MTU is lower than what the VIRTIO specification expects 288 #[error("Provided MTU {0} is lower than 1280 (expected by VIRTIO specification)")] 289 InvalidMtu(u16), 290 /// PCI segment is reused across NUMA nodes 291 #[error("PCI segment: {0} belongs to multiple NUMA nodes {1} and {2}")] 292 PciSegmentReused(u16, u32, u32), 293 /// Default PCI segment is assigned to NUMA node other than 0. 294 #[error("Default PCI segment assigned to non-zero NUMA node {0}")] 295 DefaultPciSegmentInvalidNode(u32), 296 /// Invalid rate-limiter group 297 #[error("Invalid rate-limiter group")] 298 InvalidRateLimiterGroup, 299 /// The specified I/O port was invalid. It should be provided in hex, such as `0xe9`. 300 #[cfg(target_arch = "x86_64")] 301 #[error("The IO port was not properly provided in hex or a `0x` prefix is missing: {0}")] 302 InvalidIoPortHex(String), 303 #[cfg(feature = "sev_snp")] 304 #[error("Invalid host data format")] 305 InvalidHostData, 306 /// Restore expects all net ids that have fds 307 #[error("Net id {0} is associated with FDs and is required")] 308 RestoreMissingRequiredNetId(String), 309 /// Number of FDs passed during Restore are incorrect to the NetConfig 310 #[error("Number of Net FDs passed for '{0}' during Restore: {1}. Expected: {2}")] 311 RestoreNetFdCountMismatch(String, usize, usize), 312 /// Path provided in landlock-rules doesn't exist 313 #[error("Path {0:?} provided in landlock-rules does not exist")] 314 LandlockPathDoesNotExist(PathBuf), 315 /// Access provided in landlock-rules in invalid 316 #[error("access provided in landlock-rules in invalid")] 317 InvalidLandlockAccess(String), 318 /// Invalid block device serial length 319 #[error("Block device serial length ({0}) exceeds maximum allowed length ({1})")] 320 InvalidSerialLength(usize, usize), 321 } 322 323 type ValidationResult<T> = std::result::Result<T, ValidationError>; 324 325 pub fn add_to_config<T>(items: &mut Option<Vec<T>>, item: T) { 326 if let Some(items) = items { 327 items.push(item); 328 } else { 329 *items = Some(vec![item]); 330 } 331 } 332 333 pub type Result<T> = result::Result<T, Error>; 334 335 pub struct VmParams<'a> { 336 pub cpus: &'a str, 337 pub memory: &'a str, 338 pub memory_zones: Option<Vec<&'a str>>, 339 pub firmware: Option<&'a str>, 340 pub kernel: Option<&'a str>, 341 pub initramfs: Option<&'a str>, 342 pub cmdline: Option<&'a str>, 343 pub rate_limit_groups: Option<Vec<&'a str>>, 344 pub disks: Option<Vec<&'a str>>, 345 pub net: Option<Vec<&'a str>>, 346 pub rng: &'a str, 347 pub balloon: Option<&'a str>, 348 pub fs: Option<Vec<&'a str>>, 349 pub pmem: Option<Vec<&'a str>>, 350 pub serial: &'a str, 351 pub console: &'a str, 352 #[cfg(target_arch = "x86_64")] 353 pub debug_console: &'a str, 354 pub devices: Option<Vec<&'a str>>, 355 pub user_devices: Option<Vec<&'a str>>, 356 pub vdpa: Option<Vec<&'a str>>, 357 pub vsock: Option<&'a str>, 358 #[cfg(feature = "pvmemcontrol")] 359 pub pvmemcontrol: bool, 360 pub pvpanic: bool, 361 #[cfg(target_arch = "x86_64")] 362 pub sgx_epc: Option<Vec<&'a str>>, 363 pub numa: Option<Vec<&'a str>>, 364 pub watchdog: bool, 365 #[cfg(feature = "guest_debug")] 366 pub gdb: bool, 367 pub pci_segments: Option<Vec<&'a str>>, 368 pub platform: Option<&'a str>, 369 pub tpm: Option<&'a str>, 370 #[cfg(feature = "igvm")] 371 pub igvm: Option<&'a str>, 372 #[cfg(feature = "sev_snp")] 373 pub host_data: Option<&'a str>, 374 pub landlock_enable: bool, 375 pub landlock_rules: Option<Vec<&'a str>>, 376 } 377 378 impl<'a> VmParams<'a> { 379 pub fn from_arg_matches(args: &'a ArgMatches) -> Self { 380 // These .unwrap()s cannot fail as there is a default value defined 381 let cpus = args.get_one::<String>("cpus").unwrap(); 382 let memory = args.get_one::<String>("memory").unwrap(); 383 let memory_zones: Option<Vec<&str>> = args 384 .get_many::<String>("memory-zone") 385 .map(|x| x.map(|y| y as &str).collect()); 386 let rng = args.get_one::<String>("rng").unwrap(); 387 let serial = args.get_one::<String>("serial").unwrap(); 388 let firmware = args.get_one::<String>("firmware").map(|x| x as &str); 389 let kernel = args.get_one::<String>("kernel").map(|x| x as &str); 390 let initramfs = args.get_one::<String>("initramfs").map(|x| x as &str); 391 let cmdline = args.get_one::<String>("cmdline").map(|x| x as &str); 392 let rate_limit_groups: Option<Vec<&str>> = args 393 .get_many::<String>("rate-limit-group") 394 .map(|x| x.map(|y| y as &str).collect()); 395 let disks: Option<Vec<&str>> = args 396 .get_many::<String>("disk") 397 .map(|x| x.map(|y| y as &str).collect()); 398 let net: Option<Vec<&str>> = args 399 .get_many::<String>("net") 400 .map(|x| x.map(|y| y as &str).collect()); 401 let console = args.get_one::<String>("console").unwrap(); 402 #[cfg(target_arch = "x86_64")] 403 let debug_console = args.get_one::<String>("debug-console").unwrap().as_str(); 404 let balloon = args.get_one::<String>("balloon").map(|x| x as &str); 405 let fs: Option<Vec<&str>> = args 406 .get_many::<String>("fs") 407 .map(|x| x.map(|y| y as &str).collect()); 408 let pmem: Option<Vec<&str>> = args 409 .get_many::<String>("pmem") 410 .map(|x| x.map(|y| y as &str).collect()); 411 let devices: Option<Vec<&str>> = args 412 .get_many::<String>("device") 413 .map(|x| x.map(|y| y as &str).collect()); 414 let user_devices: Option<Vec<&str>> = args 415 .get_many::<String>("user-device") 416 .map(|x| x.map(|y| y as &str).collect()); 417 let vdpa: Option<Vec<&str>> = args 418 .get_many::<String>("vdpa") 419 .map(|x| x.map(|y| y as &str).collect()); 420 let vsock: Option<&str> = args.get_one::<String>("vsock").map(|x| x as &str); 421 #[cfg(feature = "pvmemcontrol")] 422 let pvmemcontrol = args.get_flag("pvmemcontrol"); 423 let pvpanic = args.get_flag("pvpanic"); 424 #[cfg(target_arch = "x86_64")] 425 let sgx_epc: Option<Vec<&str>> = args 426 .get_many::<String>("sgx-epc") 427 .map(|x| x.map(|y| y as &str).collect()); 428 let numa: Option<Vec<&str>> = args 429 .get_many::<String>("numa") 430 .map(|x| x.map(|y| y as &str).collect()); 431 let watchdog = args.get_flag("watchdog"); 432 let pci_segments: Option<Vec<&str>> = args 433 .get_many::<String>("pci-segment") 434 .map(|x| x.map(|y| y as &str).collect()); 435 let platform = args.get_one::<String>("platform").map(|x| x as &str); 436 #[cfg(feature = "guest_debug")] 437 let gdb = args.contains_id("gdb"); 438 let tpm: Option<&str> = args.get_one::<String>("tpm").map(|x| x as &str); 439 #[cfg(feature = "igvm")] 440 let igvm = args.get_one::<String>("igvm").map(|x| x as &str); 441 #[cfg(feature = "sev_snp")] 442 let host_data = args.get_one::<String>("host-data").map(|x| x as &str); 443 let landlock_enable = args.get_flag("landlock"); 444 let landlock_rules: Option<Vec<&str>> = args 445 .get_many::<String>("landlock-rules") 446 .map(|x| x.map(|y| y as &str).collect()); 447 448 VmParams { 449 cpus, 450 memory, 451 memory_zones, 452 firmware, 453 kernel, 454 initramfs, 455 cmdline, 456 rate_limit_groups, 457 disks, 458 net, 459 rng, 460 balloon, 461 fs, 462 pmem, 463 serial, 464 console, 465 #[cfg(target_arch = "x86_64")] 466 debug_console, 467 devices, 468 user_devices, 469 vdpa, 470 vsock, 471 #[cfg(feature = "pvmemcontrol")] 472 pvmemcontrol, 473 pvpanic, 474 #[cfg(target_arch = "x86_64")] 475 sgx_epc, 476 numa, 477 watchdog, 478 #[cfg(feature = "guest_debug")] 479 gdb, 480 pci_segments, 481 platform, 482 tpm, 483 #[cfg(feature = "igvm")] 484 igvm, 485 #[cfg(feature = "sev_snp")] 486 host_data, 487 landlock_enable, 488 landlock_rules, 489 } 490 } 491 } 492 493 #[derive(Debug)] 494 pub enum ParseHotplugMethodError { 495 InvalidValue(String), 496 } 497 498 impl FromStr for HotplugMethod { 499 type Err = ParseHotplugMethodError; 500 501 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 502 match s.to_lowercase().as_str() { 503 "acpi" => Ok(HotplugMethod::Acpi), 504 "virtio-mem" => Ok(HotplugMethod::VirtioMem), 505 _ => Err(ParseHotplugMethodError::InvalidValue(s.to_owned())), 506 } 507 } 508 } 509 510 pub enum CpuTopologyParseError { 511 InvalidValue(String), 512 } 513 514 impl FromStr for CpuTopology { 515 type Err = CpuTopologyParseError; 516 517 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 518 let parts: Vec<&str> = s.split(':').collect(); 519 520 if parts.len() != 4 { 521 return Err(Self::Err::InvalidValue(s.to_owned())); 522 } 523 524 let t = CpuTopology { 525 threads_per_core: parts[0] 526 .parse() 527 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 528 cores_per_die: parts[1] 529 .parse() 530 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 531 dies_per_package: parts[2] 532 .parse() 533 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 534 packages: parts[3] 535 .parse() 536 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 537 }; 538 539 Ok(t) 540 } 541 } 542 543 impl CpusConfig { 544 pub fn parse(cpus: &str) -> Result<Self> { 545 let mut parser = OptionParser::new(); 546 parser 547 .add("boot") 548 .add("max") 549 .add("topology") 550 .add("kvm_hyperv") 551 .add("max_phys_bits") 552 .add("affinity") 553 .add("features"); 554 parser.parse(cpus).map_err(Error::ParseCpus)?; 555 556 let boot_vcpus: u8 = parser 557 .convert("boot") 558 .map_err(Error::ParseCpus)? 559 .unwrap_or(DEFAULT_VCPUS); 560 let max_vcpus: u8 = parser 561 .convert("max") 562 .map_err(Error::ParseCpus)? 563 .unwrap_or(boot_vcpus); 564 let topology = parser.convert("topology").map_err(Error::ParseCpus)?; 565 let kvm_hyperv = parser 566 .convert::<Toggle>("kvm_hyperv") 567 .map_err(Error::ParseCpus)? 568 .unwrap_or(Toggle(false)) 569 .0; 570 let max_phys_bits = parser 571 .convert::<u8>("max_phys_bits") 572 .map_err(Error::ParseCpus)? 573 .unwrap_or(DEFAULT_MAX_PHYS_BITS); 574 let affinity = parser 575 .convert::<Tuple<u8, Vec<usize>>>("affinity") 576 .map_err(Error::ParseCpus)? 577 .map(|v| { 578 v.0.iter() 579 .map(|(e1, e2)| CpuAffinity { 580 vcpu: *e1, 581 host_cpus: e2.clone(), 582 }) 583 .collect() 584 }); 585 let features_list = parser 586 .convert::<StringList>("features") 587 .map_err(Error::ParseCpus)? 588 .unwrap_or_default(); 589 // Some ugliness here as the features being checked might be disabled 590 // at compile time causing the below allow and the need to specify the 591 // ref type in the match. 592 // The issue will go away once kvm_hyperv is moved under the features 593 // list as it will always be checked for. 594 #[allow(unused_mut)] 595 let mut features = CpuFeatures::default(); 596 for s in features_list.0 { 597 match <std::string::String as AsRef<str>>::as_ref(&s) { 598 #[cfg(target_arch = "x86_64")] 599 "amx" => { 600 features.amx = true; 601 Ok(()) 602 } 603 _ => Err(Error::InvalidCpuFeatures(s)), 604 }?; 605 } 606 607 Ok(CpusConfig { 608 boot_vcpus, 609 max_vcpus, 610 topology, 611 kvm_hyperv, 612 max_phys_bits, 613 affinity, 614 features, 615 }) 616 } 617 } 618 619 impl PciSegmentConfig { 620 pub const SYNTAX: &'static str = "PCI Segment parameters \ 621 \"pci_segment=<segment_id>,mmio32_aperture_weight=<scale>,mmio64_aperture_weight=<scale>\""; 622 623 pub fn parse(disk: &str) -> Result<Self> { 624 let mut parser = OptionParser::new(); 625 parser 626 .add("mmio32_aperture_weight") 627 .add("mmio64_aperture_weight") 628 .add("pci_segment"); 629 parser.parse(disk).map_err(Error::ParsePciSegment)?; 630 631 let pci_segment = parser 632 .convert("pci_segment") 633 .map_err(Error::ParsePciSegment)? 634 .unwrap_or_default(); 635 let mmio32_aperture_weight = parser 636 .convert("mmio32_aperture_weight") 637 .map_err(Error::ParsePciSegment)? 638 .unwrap_or(DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT); 639 let mmio64_aperture_weight = parser 640 .convert("mmio64_aperture_weight") 641 .map_err(Error::ParsePciSegment)? 642 .unwrap_or(DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT); 643 644 Ok(PciSegmentConfig { 645 pci_segment, 646 mmio32_aperture_weight, 647 mmio64_aperture_weight, 648 }) 649 } 650 651 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 652 let num_pci_segments = match &vm_config.platform { 653 Some(platform_config) => platform_config.num_pci_segments, 654 None => 1, 655 }; 656 657 if self.pci_segment >= num_pci_segments { 658 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 659 } 660 661 if self.mmio32_aperture_weight == 0 { 662 return Err(ValidationError::InvalidPciSegmentApertureWeight( 663 self.mmio32_aperture_weight, 664 )); 665 } 666 667 if self.mmio64_aperture_weight == 0 { 668 return Err(ValidationError::InvalidPciSegmentApertureWeight( 669 self.mmio64_aperture_weight, 670 )); 671 } 672 673 Ok(()) 674 } 675 } 676 677 impl PlatformConfig { 678 pub fn parse(platform: &str) -> Result<Self> { 679 let mut parser = OptionParser::new(); 680 parser 681 .add("num_pci_segments") 682 .add("iommu_segments") 683 .add("iommu_address_width") 684 .add("serial_number") 685 .add("uuid") 686 .add("oem_strings"); 687 #[cfg(feature = "tdx")] 688 parser.add("tdx"); 689 #[cfg(feature = "sev_snp")] 690 parser.add("sev_snp"); 691 parser.parse(platform).map_err(Error::ParsePlatform)?; 692 693 let num_pci_segments: u16 = parser 694 .convert("num_pci_segments") 695 .map_err(Error::ParsePlatform)? 696 .unwrap_or(DEFAULT_NUM_PCI_SEGMENTS); 697 let iommu_segments = parser 698 .convert::<IntegerList>("iommu_segments") 699 .map_err(Error::ParsePlatform)? 700 .map(|v| v.0.iter().map(|e| *e as u16).collect()); 701 let iommu_address_width_bits: u8 = parser 702 .convert("iommu_address_width") 703 .map_err(Error::ParsePlatform)? 704 .unwrap_or(MAX_IOMMU_ADDRESS_WIDTH_BITS); 705 let serial_number = parser 706 .convert("serial_number") 707 .map_err(Error::ParsePlatform)?; 708 let uuid = parser.convert("uuid").map_err(Error::ParsePlatform)?; 709 let oem_strings = parser 710 .convert::<StringList>("oem_strings") 711 .map_err(Error::ParsePlatform)? 712 .map(|v| v.0); 713 #[cfg(feature = "tdx")] 714 let tdx = parser 715 .convert::<Toggle>("tdx") 716 .map_err(Error::ParsePlatform)? 717 .unwrap_or(Toggle(false)) 718 .0; 719 #[cfg(feature = "sev_snp")] 720 let sev_snp = parser 721 .convert::<Toggle>("sev_snp") 722 .map_err(Error::ParsePlatform)? 723 .unwrap_or(Toggle(false)) 724 .0; 725 Ok(PlatformConfig { 726 num_pci_segments, 727 iommu_segments, 728 iommu_address_width_bits, 729 serial_number, 730 uuid, 731 oem_strings, 732 #[cfg(feature = "tdx")] 733 tdx, 734 #[cfg(feature = "sev_snp")] 735 sev_snp, 736 }) 737 } 738 739 pub fn validate(&self) -> ValidationResult<()> { 740 if self.num_pci_segments == 0 || self.num_pci_segments > MAX_NUM_PCI_SEGMENTS { 741 return Err(ValidationError::InvalidNumPciSegments( 742 self.num_pci_segments, 743 )); 744 } 745 746 if let Some(iommu_segments) = &self.iommu_segments { 747 for segment in iommu_segments { 748 if *segment >= self.num_pci_segments { 749 return Err(ValidationError::InvalidPciSegment(*segment)); 750 } 751 } 752 } 753 754 if self.iommu_address_width_bits > MAX_IOMMU_ADDRESS_WIDTH_BITS { 755 return Err(ValidationError::InvalidIommuAddressWidthBits( 756 self.iommu_address_width_bits, 757 )); 758 } 759 760 Ok(()) 761 } 762 } 763 764 impl MemoryConfig { 765 pub fn parse(memory: &str, memory_zones: Option<Vec<&str>>) -> Result<Self> { 766 let mut parser = OptionParser::new(); 767 parser 768 .add("size") 769 .add("file") 770 .add("mergeable") 771 .add("hotplug_method") 772 .add("hotplug_size") 773 .add("hotplugged_size") 774 .add("shared") 775 .add("hugepages") 776 .add("hugepage_size") 777 .add("prefault") 778 .add("thp"); 779 parser.parse(memory).map_err(Error::ParseMemory)?; 780 781 let size = parser 782 .convert::<ByteSized>("size") 783 .map_err(Error::ParseMemory)? 784 .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20)) 785 .0; 786 let mergeable = parser 787 .convert::<Toggle>("mergeable") 788 .map_err(Error::ParseMemory)? 789 .unwrap_or(Toggle(false)) 790 .0; 791 let hotplug_method = parser 792 .convert("hotplug_method") 793 .map_err(Error::ParseMemory)? 794 .unwrap_or_default(); 795 let hotplug_size = parser 796 .convert::<ByteSized>("hotplug_size") 797 .map_err(Error::ParseMemory)? 798 .map(|v| v.0); 799 let hotplugged_size = parser 800 .convert::<ByteSized>("hotplugged_size") 801 .map_err(Error::ParseMemory)? 802 .map(|v| v.0); 803 let shared = parser 804 .convert::<Toggle>("shared") 805 .map_err(Error::ParseMemory)? 806 .unwrap_or(Toggle(false)) 807 .0; 808 let hugepages = parser 809 .convert::<Toggle>("hugepages") 810 .map_err(Error::ParseMemory)? 811 .unwrap_or(Toggle(false)) 812 .0; 813 let hugepage_size = parser 814 .convert::<ByteSized>("hugepage_size") 815 .map_err(Error::ParseMemory)? 816 .map(|v| v.0); 817 let prefault = parser 818 .convert::<Toggle>("prefault") 819 .map_err(Error::ParseMemory)? 820 .unwrap_or(Toggle(false)) 821 .0; 822 let thp = parser 823 .convert::<Toggle>("thp") 824 .map_err(Error::ParseMemory)? 825 .unwrap_or(Toggle(true)) 826 .0; 827 828 let zones: Option<Vec<MemoryZoneConfig>> = if let Some(memory_zones) = &memory_zones { 829 let mut zones = Vec::new(); 830 for memory_zone in memory_zones.iter() { 831 let mut parser = OptionParser::new(); 832 parser 833 .add("id") 834 .add("size") 835 .add("file") 836 .add("shared") 837 .add("hugepages") 838 .add("hugepage_size") 839 .add("host_numa_node") 840 .add("hotplug_size") 841 .add("hotplugged_size") 842 .add("prefault"); 843 parser.parse(memory_zone).map_err(Error::ParseMemoryZone)?; 844 845 let id = parser.get("id").ok_or(Error::ParseMemoryZoneIdMissing)?; 846 let size = parser 847 .convert::<ByteSized>("size") 848 .map_err(Error::ParseMemoryZone)? 849 .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20)) 850 .0; 851 let file = parser.get("file").map(PathBuf::from); 852 let shared = parser 853 .convert::<Toggle>("shared") 854 .map_err(Error::ParseMemoryZone)? 855 .unwrap_or(Toggle(false)) 856 .0; 857 let hugepages = parser 858 .convert::<Toggle>("hugepages") 859 .map_err(Error::ParseMemoryZone)? 860 .unwrap_or(Toggle(false)) 861 .0; 862 let hugepage_size = parser 863 .convert::<ByteSized>("hugepage_size") 864 .map_err(Error::ParseMemoryZone)? 865 .map(|v| v.0); 866 867 let host_numa_node = parser 868 .convert::<u32>("host_numa_node") 869 .map_err(Error::ParseMemoryZone)?; 870 let hotplug_size = parser 871 .convert::<ByteSized>("hotplug_size") 872 .map_err(Error::ParseMemoryZone)? 873 .map(|v| v.0); 874 let hotplugged_size = parser 875 .convert::<ByteSized>("hotplugged_size") 876 .map_err(Error::ParseMemoryZone)? 877 .map(|v| v.0); 878 let prefault = parser 879 .convert::<Toggle>("prefault") 880 .map_err(Error::ParseMemoryZone)? 881 .unwrap_or(Toggle(false)) 882 .0; 883 884 zones.push(MemoryZoneConfig { 885 id, 886 size, 887 file, 888 shared, 889 hugepages, 890 hugepage_size, 891 host_numa_node, 892 hotplug_size, 893 hotplugged_size, 894 prefault, 895 }); 896 } 897 Some(zones) 898 } else { 899 None 900 }; 901 902 Ok(MemoryConfig { 903 size, 904 mergeable, 905 hotplug_method, 906 hotplug_size, 907 hotplugged_size, 908 shared, 909 hugepages, 910 hugepage_size, 911 prefault, 912 zones, 913 thp, 914 }) 915 } 916 917 pub fn total_size(&self) -> u64 { 918 let mut size = self.size; 919 if let Some(hotplugged_size) = self.hotplugged_size { 920 size += hotplugged_size; 921 } 922 923 if let Some(zones) = &self.zones { 924 for zone in zones.iter() { 925 size += zone.size; 926 if let Some(hotplugged_size) = zone.hotplugged_size { 927 size += hotplugged_size; 928 } 929 } 930 } 931 932 size 933 } 934 } 935 936 impl RateLimiterGroupConfig { 937 pub const SYNTAX: &'static str = "Rate Limit Group parameters \ 938 \"bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\ 939 ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,\ 940 id=<device_id>\""; 941 942 pub fn parse(rate_limit_group: &str) -> Result<Self> { 943 let mut parser = OptionParser::new(); 944 parser 945 .add("bw_size") 946 .add("bw_one_time_burst") 947 .add("bw_refill_time") 948 .add("ops_size") 949 .add("ops_one_time_burst") 950 .add("ops_refill_time") 951 .add("id"); 952 parser 953 .parse(rate_limit_group) 954 .map_err(Error::ParseRateLimiterGroup)?; 955 956 let id = parser.get("id").unwrap_or_default(); 957 let bw_size = parser 958 .convert("bw_size") 959 .map_err(Error::ParseRateLimiterGroup)? 960 .unwrap_or_default(); 961 let bw_one_time_burst = parser 962 .convert("bw_one_time_burst") 963 .map_err(Error::ParseRateLimiterGroup)? 964 .unwrap_or_default(); 965 let bw_refill_time = parser 966 .convert("bw_refill_time") 967 .map_err(Error::ParseRateLimiterGroup)? 968 .unwrap_or_default(); 969 let ops_size = parser 970 .convert("ops_size") 971 .map_err(Error::ParseRateLimiterGroup)? 972 .unwrap_or_default(); 973 let ops_one_time_burst = parser 974 .convert("ops_one_time_burst") 975 .map_err(Error::ParseRateLimiterGroup)? 976 .unwrap_or_default(); 977 let ops_refill_time = parser 978 .convert("ops_refill_time") 979 .map_err(Error::ParseRateLimiterGroup)? 980 .unwrap_or_default(); 981 982 let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { 983 Some(TokenBucketConfig { 984 size: bw_size, 985 one_time_burst: Some(bw_one_time_burst), 986 refill_time: bw_refill_time, 987 }) 988 } else { 989 None 990 }; 991 let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 { 992 Some(TokenBucketConfig { 993 size: ops_size, 994 one_time_burst: Some(ops_one_time_burst), 995 refill_time: ops_refill_time, 996 }) 997 } else { 998 None 999 }; 1000 1001 Ok(RateLimiterGroupConfig { 1002 id, 1003 rate_limiter_config: RateLimiterConfig { 1004 bandwidth: bw_tb_config, 1005 ops: ops_tb_config, 1006 }, 1007 }) 1008 } 1009 1010 pub fn validate(&self, _vm_config: &VmConfig) -> ValidationResult<()> { 1011 if self.rate_limiter_config.bandwidth.is_none() && self.rate_limiter_config.ops.is_none() { 1012 return Err(ValidationError::InvalidRateLimiterGroup); 1013 } 1014 1015 if self.id.is_empty() { 1016 return Err(ValidationError::InvalidRateLimiterGroup); 1017 } 1018 1019 Ok(()) 1020 } 1021 } 1022 1023 impl DiskConfig { 1024 pub const SYNTAX: &'static str = "Disk parameters \ 1025 \"path=<disk_image_path>,readonly=on|off,direct=on|off,iommu=on|off,\ 1026 num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,\ 1027 vhost_user=on|off,socket=<vhost_user_socket_path>,\ 1028 bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\ 1029 ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,\ 1030 id=<device_id>,pci_segment=<segment_id>,rate_limit_group=<group_id>,\ 1031 queue_affinity=<list_of_queue_indices_with_their_associated_cpuset>,\ 1032 serial=<serial_number>"; 1033 1034 pub fn parse(disk: &str) -> Result<Self> { 1035 let mut parser = OptionParser::new(); 1036 parser 1037 .add("path") 1038 .add("readonly") 1039 .add("direct") 1040 .add("iommu") 1041 .add("queue_size") 1042 .add("num_queues") 1043 .add("vhost_user") 1044 .add("socket") 1045 .add("bw_size") 1046 .add("bw_one_time_burst") 1047 .add("bw_refill_time") 1048 .add("ops_size") 1049 .add("ops_one_time_burst") 1050 .add("ops_refill_time") 1051 .add("id") 1052 .add("_disable_io_uring") 1053 .add("_disable_aio") 1054 .add("pci_segment") 1055 .add("serial") 1056 .add("rate_limit_group") 1057 .add("queue_affinity"); 1058 parser.parse(disk).map_err(Error::ParseDisk)?; 1059 1060 let path = parser.get("path").map(PathBuf::from); 1061 let readonly = parser 1062 .convert::<Toggle>("readonly") 1063 .map_err(Error::ParseDisk)? 1064 .unwrap_or(Toggle(false)) 1065 .0; 1066 let direct = parser 1067 .convert::<Toggle>("direct") 1068 .map_err(Error::ParseDisk)? 1069 .unwrap_or(Toggle(false)) 1070 .0; 1071 let iommu = parser 1072 .convert::<Toggle>("iommu") 1073 .map_err(Error::ParseDisk)? 1074 .unwrap_or(Toggle(false)) 1075 .0; 1076 let queue_size = parser 1077 .convert("queue_size") 1078 .map_err(Error::ParseDisk)? 1079 .unwrap_or_else(default_diskconfig_queue_size); 1080 let num_queues = parser 1081 .convert("num_queues") 1082 .map_err(Error::ParseDisk)? 1083 .unwrap_or_else(default_diskconfig_num_queues); 1084 let vhost_user = parser 1085 .convert::<Toggle>("vhost_user") 1086 .map_err(Error::ParseDisk)? 1087 .unwrap_or(Toggle(false)) 1088 .0; 1089 let vhost_socket = parser.get("socket"); 1090 let id = parser.get("id"); 1091 let disable_io_uring = parser 1092 .convert::<Toggle>("_disable_io_uring") 1093 .map_err(Error::ParseDisk)? 1094 .unwrap_or(Toggle(false)) 1095 .0; 1096 let disable_aio = parser 1097 .convert::<Toggle>("_disable_aio") 1098 .map_err(Error::ParseDisk)? 1099 .unwrap_or(Toggle(false)) 1100 .0; 1101 let pci_segment = parser 1102 .convert("pci_segment") 1103 .map_err(Error::ParseDisk)? 1104 .unwrap_or_default(); 1105 let rate_limit_group = parser.get("rate_limit_group"); 1106 let bw_size = parser 1107 .convert("bw_size") 1108 .map_err(Error::ParseDisk)? 1109 .unwrap_or_default(); 1110 let bw_one_time_burst = parser 1111 .convert("bw_one_time_burst") 1112 .map_err(Error::ParseDisk)? 1113 .unwrap_or_default(); 1114 let bw_refill_time = parser 1115 .convert("bw_refill_time") 1116 .map_err(Error::ParseDisk)? 1117 .unwrap_or_default(); 1118 let ops_size = parser 1119 .convert("ops_size") 1120 .map_err(Error::ParseDisk)? 1121 .unwrap_or_default(); 1122 let ops_one_time_burst = parser 1123 .convert("ops_one_time_burst") 1124 .map_err(Error::ParseDisk)? 1125 .unwrap_or_default(); 1126 let ops_refill_time = parser 1127 .convert("ops_refill_time") 1128 .map_err(Error::ParseDisk)? 1129 .unwrap_or_default(); 1130 let serial = parser.get("serial"); 1131 let queue_affinity = parser 1132 .convert::<Tuple<u16, Vec<usize>>>("queue_affinity") 1133 .map_err(Error::ParseDisk)? 1134 .map(|v| { 1135 v.0.iter() 1136 .map(|(e1, e2)| VirtQueueAffinity { 1137 queue_index: *e1, 1138 host_cpus: e2.clone(), 1139 }) 1140 .collect() 1141 }); 1142 let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { 1143 Some(TokenBucketConfig { 1144 size: bw_size, 1145 one_time_burst: Some(bw_one_time_burst), 1146 refill_time: bw_refill_time, 1147 }) 1148 } else { 1149 None 1150 }; 1151 let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 { 1152 Some(TokenBucketConfig { 1153 size: ops_size, 1154 one_time_burst: Some(ops_one_time_burst), 1155 refill_time: ops_refill_time, 1156 }) 1157 } else { 1158 None 1159 }; 1160 let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() { 1161 Some(RateLimiterConfig { 1162 bandwidth: bw_tb_config, 1163 ops: ops_tb_config, 1164 }) 1165 } else { 1166 None 1167 }; 1168 1169 Ok(DiskConfig { 1170 path, 1171 readonly, 1172 direct, 1173 iommu, 1174 num_queues, 1175 queue_size, 1176 vhost_user, 1177 vhost_socket, 1178 rate_limit_group, 1179 rate_limiter_config, 1180 id, 1181 disable_io_uring, 1182 disable_aio, 1183 pci_segment, 1184 serial, 1185 queue_affinity, 1186 }) 1187 } 1188 1189 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1190 if self.num_queues > vm_config.cpus.boot_vcpus as usize { 1191 return Err(ValidationError::TooManyQueues); 1192 } 1193 1194 if self.queue_size <= MINIMUM_BLOCK_QUEUE_SIZE { 1195 return Err(ValidationError::InvalidQueueSize(self.queue_size)); 1196 } 1197 1198 if self.vhost_user && self.iommu { 1199 return Err(ValidationError::IommuNotSupported); 1200 } 1201 1202 if let Some(platform_config) = vm_config.platform.as_ref() { 1203 if self.pci_segment >= platform_config.num_pci_segments { 1204 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1205 } 1206 1207 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1208 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1209 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1210 } 1211 } 1212 } 1213 1214 if self.rate_limiter_config.is_some() && self.rate_limit_group.is_some() { 1215 return Err(ValidationError::InvalidRateLimiterGroup); 1216 } 1217 1218 // Check Block device serial length 1219 if let Some(ref serial) = self.serial { 1220 if serial.len() > VIRTIO_BLK_ID_BYTES as usize { 1221 return Err(ValidationError::InvalidSerialLength( 1222 serial.len(), 1223 VIRTIO_BLK_ID_BYTES as usize, 1224 )); 1225 } 1226 } 1227 1228 Ok(()) 1229 } 1230 } 1231 1232 #[derive(Debug)] 1233 pub enum ParseVhostModeError { 1234 InvalidValue(String), 1235 } 1236 1237 impl FromStr for VhostMode { 1238 type Err = ParseVhostModeError; 1239 1240 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 1241 match s.to_lowercase().as_str() { 1242 "client" => Ok(VhostMode::Client), 1243 "server" => Ok(VhostMode::Server), 1244 _ => Err(ParseVhostModeError::InvalidValue(s.to_owned())), 1245 } 1246 } 1247 } 1248 1249 impl NetConfig { 1250 pub const SYNTAX: &'static str = "Network parameters \ 1251 \"tap=<if_name>,ip=<ip_addr>,mask=<net_mask>,mac=<mac_addr>,fd=<fd1,fd2...>,iommu=on|off,\ 1252 num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,id=<device_id>,\ 1253 vhost_user=<vhost_user_enable>,socket=<vhost_user_socket_path>,vhost_mode=client|server,\ 1254 bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\ 1255 ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,pci_segment=<segment_id>\ 1256 offload_tso=on|off,offload_ufo=on|off,offload_csum=on|off\""; 1257 1258 pub fn parse(net: &str) -> Result<Self> { 1259 let mut parser = OptionParser::new(); 1260 1261 parser 1262 .add("tap") 1263 .add("ip") 1264 .add("mask") 1265 .add("mac") 1266 .add("host_mac") 1267 .add("offload_tso") 1268 .add("offload_ufo") 1269 .add("offload_csum") 1270 .add("mtu") 1271 .add("iommu") 1272 .add("queue_size") 1273 .add("num_queues") 1274 .add("vhost_user") 1275 .add("socket") 1276 .add("vhost_mode") 1277 .add("id") 1278 .add("fd") 1279 .add("bw_size") 1280 .add("bw_one_time_burst") 1281 .add("bw_refill_time") 1282 .add("ops_size") 1283 .add("ops_one_time_burst") 1284 .add("ops_refill_time") 1285 .add("pci_segment"); 1286 parser.parse(net).map_err(Error::ParseNetwork)?; 1287 1288 let tap = parser.get("tap"); 1289 let ip = parser 1290 .convert("ip") 1291 .map_err(Error::ParseNetwork)? 1292 .unwrap_or_else(default_netconfig_ip); 1293 let mask = parser 1294 .convert("mask") 1295 .map_err(Error::ParseNetwork)? 1296 .unwrap_or_else(default_netconfig_mask); 1297 let mac = parser 1298 .convert("mac") 1299 .map_err(Error::ParseNetwork)? 1300 .unwrap_or_else(default_netconfig_mac); 1301 let host_mac = parser.convert("host_mac").map_err(Error::ParseNetwork)?; 1302 let offload_tso = parser 1303 .convert::<Toggle>("offload_tso") 1304 .map_err(Error::ParseNetwork)? 1305 .unwrap_or(Toggle(true)) 1306 .0; 1307 let offload_ufo = parser 1308 .convert::<Toggle>("offload_ufo") 1309 .map_err(Error::ParseNetwork)? 1310 .unwrap_or(Toggle(true)) 1311 .0; 1312 let offload_csum = parser 1313 .convert::<Toggle>("offload_csum") 1314 .map_err(Error::ParseNetwork)? 1315 .unwrap_or(Toggle(true)) 1316 .0; 1317 let mtu = parser.convert("mtu").map_err(Error::ParseNetwork)?; 1318 let iommu = parser 1319 .convert::<Toggle>("iommu") 1320 .map_err(Error::ParseNetwork)? 1321 .unwrap_or(Toggle(false)) 1322 .0; 1323 let queue_size = parser 1324 .convert("queue_size") 1325 .map_err(Error::ParseNetwork)? 1326 .unwrap_or_else(default_netconfig_queue_size); 1327 let num_queues = parser 1328 .convert("num_queues") 1329 .map_err(Error::ParseNetwork)? 1330 .unwrap_or_else(default_netconfig_num_queues); 1331 let vhost_user = parser 1332 .convert::<Toggle>("vhost_user") 1333 .map_err(Error::ParseNetwork)? 1334 .unwrap_or(Toggle(false)) 1335 .0; 1336 let vhost_socket = parser.get("socket"); 1337 let vhost_mode = parser 1338 .convert("vhost_mode") 1339 .map_err(Error::ParseNetwork)? 1340 .unwrap_or_default(); 1341 let id = parser.get("id"); 1342 let fds = parser 1343 .convert::<IntegerList>("fd") 1344 .map_err(Error::ParseNetwork)? 1345 .map(|v| v.0.iter().map(|e| *e as i32).collect()); 1346 let pci_segment = parser 1347 .convert("pci_segment") 1348 .map_err(Error::ParseNetwork)? 1349 .unwrap_or_default(); 1350 let bw_size = parser 1351 .convert("bw_size") 1352 .map_err(Error::ParseNetwork)? 1353 .unwrap_or_default(); 1354 let bw_one_time_burst = parser 1355 .convert("bw_one_time_burst") 1356 .map_err(Error::ParseNetwork)? 1357 .unwrap_or_default(); 1358 let bw_refill_time = parser 1359 .convert("bw_refill_time") 1360 .map_err(Error::ParseNetwork)? 1361 .unwrap_or_default(); 1362 let ops_size = parser 1363 .convert("ops_size") 1364 .map_err(Error::ParseNetwork)? 1365 .unwrap_or_default(); 1366 let ops_one_time_burst = parser 1367 .convert("ops_one_time_burst") 1368 .map_err(Error::ParseNetwork)? 1369 .unwrap_or_default(); 1370 let ops_refill_time = parser 1371 .convert("ops_refill_time") 1372 .map_err(Error::ParseNetwork)? 1373 .unwrap_or_default(); 1374 let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { 1375 Some(TokenBucketConfig { 1376 size: bw_size, 1377 one_time_burst: Some(bw_one_time_burst), 1378 refill_time: bw_refill_time, 1379 }) 1380 } else { 1381 None 1382 }; 1383 let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 { 1384 Some(TokenBucketConfig { 1385 size: ops_size, 1386 one_time_burst: Some(ops_one_time_burst), 1387 refill_time: ops_refill_time, 1388 }) 1389 } else { 1390 None 1391 }; 1392 let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() { 1393 Some(RateLimiterConfig { 1394 bandwidth: bw_tb_config, 1395 ops: ops_tb_config, 1396 }) 1397 } else { 1398 None 1399 }; 1400 1401 let config = NetConfig { 1402 tap, 1403 ip, 1404 mask, 1405 mac, 1406 host_mac, 1407 mtu, 1408 iommu, 1409 num_queues, 1410 queue_size, 1411 vhost_user, 1412 vhost_socket, 1413 vhost_mode, 1414 id, 1415 fds, 1416 rate_limiter_config, 1417 pci_segment, 1418 offload_tso, 1419 offload_ufo, 1420 offload_csum, 1421 }; 1422 Ok(config) 1423 } 1424 1425 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1426 if self.num_queues < 2 { 1427 return Err(ValidationError::VnetQueueLowerThan2); 1428 } 1429 1430 if self.fds.is_some() && self.fds.as_ref().unwrap().len() * 2 != self.num_queues { 1431 return Err(ValidationError::VnetQueueFdMismatch); 1432 } 1433 1434 if let Some(fds) = self.fds.as_ref() { 1435 for fd in fds { 1436 if *fd <= 2 { 1437 return Err(ValidationError::VnetReservedFd); 1438 } 1439 } 1440 } 1441 1442 if (self.num_queues / 2) > vm_config.cpus.boot_vcpus as usize { 1443 return Err(ValidationError::TooManyQueues); 1444 } 1445 1446 if self.vhost_user && self.iommu { 1447 return Err(ValidationError::IommuNotSupported); 1448 } 1449 1450 if let Some(platform_config) = vm_config.platform.as_ref() { 1451 if self.pci_segment >= platform_config.num_pci_segments { 1452 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1453 } 1454 1455 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1456 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1457 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1458 } 1459 } 1460 } 1461 1462 if let Some(mtu) = self.mtu { 1463 if mtu < virtio_devices::net::MIN_MTU { 1464 return Err(ValidationError::InvalidMtu(mtu)); 1465 } 1466 } 1467 1468 if !self.offload_csum && (self.offload_tso || self.offload_ufo) { 1469 return Err(ValidationError::NoHardwareChecksumOffload); 1470 } 1471 1472 Ok(()) 1473 } 1474 } 1475 1476 impl RngConfig { 1477 pub fn parse(rng: &str) -> Result<Self> { 1478 let mut parser = OptionParser::new(); 1479 parser.add("src").add("iommu"); 1480 parser.parse(rng).map_err(Error::ParseRng)?; 1481 1482 let src = PathBuf::from( 1483 parser 1484 .get("src") 1485 .unwrap_or_else(|| DEFAULT_RNG_SOURCE.to_owned()), 1486 ); 1487 let iommu = parser 1488 .convert::<Toggle>("iommu") 1489 .map_err(Error::ParseRng)? 1490 .unwrap_or(Toggle(false)) 1491 .0; 1492 1493 Ok(RngConfig { src, iommu }) 1494 } 1495 } 1496 1497 impl BalloonConfig { 1498 pub const SYNTAX: &'static str = 1499 "Balloon parameters \"size=<balloon_size>,deflate_on_oom=on|off,\ 1500 free_page_reporting=on|off\""; 1501 1502 pub fn parse(balloon: &str) -> Result<Self> { 1503 let mut parser = OptionParser::new(); 1504 parser.add("size"); 1505 parser.add("deflate_on_oom"); 1506 parser.add("free_page_reporting"); 1507 parser.parse(balloon).map_err(Error::ParseBalloon)?; 1508 1509 let size = parser 1510 .convert::<ByteSized>("size") 1511 .map_err(Error::ParseBalloon)? 1512 .map(|v| v.0) 1513 .unwrap_or(0); 1514 1515 let deflate_on_oom = parser 1516 .convert::<Toggle>("deflate_on_oom") 1517 .map_err(Error::ParseBalloon)? 1518 .unwrap_or(Toggle(false)) 1519 .0; 1520 1521 let free_page_reporting = parser 1522 .convert::<Toggle>("free_page_reporting") 1523 .map_err(Error::ParseBalloon)? 1524 .unwrap_or(Toggle(false)) 1525 .0; 1526 1527 Ok(BalloonConfig { 1528 size, 1529 deflate_on_oom, 1530 free_page_reporting, 1531 }) 1532 } 1533 } 1534 1535 impl FsConfig { 1536 pub const SYNTAX: &'static str = "virtio-fs parameters \ 1537 \"tag=<tag_name>,socket=<socket_path>,num_queues=<number_of_queues>,\ 1538 queue_size=<size_of_each_queue>,id=<device_id>,pci_segment=<segment_id>\""; 1539 1540 pub fn parse(fs: &str) -> Result<Self> { 1541 let mut parser = OptionParser::new(); 1542 parser 1543 .add("tag") 1544 .add("queue_size") 1545 .add("num_queues") 1546 .add("socket") 1547 .add("id") 1548 .add("pci_segment"); 1549 parser.parse(fs).map_err(Error::ParseFileSystem)?; 1550 1551 let tag = parser.get("tag").ok_or(Error::ParseFsTagMissing)?; 1552 if tag.len() > virtio_devices::vhost_user::VIRTIO_FS_TAG_LEN { 1553 return Err(Error::ParseFsTagTooLong); 1554 } 1555 let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseFsSockMissing)?); 1556 1557 let queue_size = parser 1558 .convert("queue_size") 1559 .map_err(Error::ParseFileSystem)? 1560 .unwrap_or_else(default_fsconfig_queue_size); 1561 let num_queues = parser 1562 .convert("num_queues") 1563 .map_err(Error::ParseFileSystem)? 1564 .unwrap_or_else(default_fsconfig_num_queues); 1565 1566 let id = parser.get("id"); 1567 1568 let pci_segment = parser 1569 .convert("pci_segment") 1570 .map_err(Error::ParseFileSystem)? 1571 .unwrap_or_default(); 1572 1573 Ok(FsConfig { 1574 tag, 1575 socket, 1576 num_queues, 1577 queue_size, 1578 id, 1579 pci_segment, 1580 }) 1581 } 1582 1583 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1584 if self.num_queues > vm_config.cpus.boot_vcpus as usize { 1585 return Err(ValidationError::TooManyQueues); 1586 } 1587 1588 if let Some(platform_config) = vm_config.platform.as_ref() { 1589 if self.pci_segment >= platform_config.num_pci_segments { 1590 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1591 } 1592 1593 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1594 if iommu_segments.contains(&self.pci_segment) { 1595 return Err(ValidationError::IommuNotSupportedOnSegment( 1596 self.pci_segment, 1597 )); 1598 } 1599 } 1600 } 1601 1602 Ok(()) 1603 } 1604 } 1605 1606 impl PmemConfig { 1607 pub const SYNTAX: &'static str = "Persistent memory parameters \ 1608 \"file=<backing_file_path>,size=<persistent_memory_size>,iommu=on|off,\ 1609 discard_writes=on|off,id=<device_id>,pci_segment=<segment_id>\""; 1610 1611 pub fn parse(pmem: &str) -> Result<Self> { 1612 let mut parser = OptionParser::new(); 1613 parser 1614 .add("size") 1615 .add("file") 1616 .add("iommu") 1617 .add("discard_writes") 1618 .add("id") 1619 .add("pci_segment"); 1620 parser.parse(pmem).map_err(Error::ParsePersistentMemory)?; 1621 1622 let file = PathBuf::from(parser.get("file").ok_or(Error::ParsePmemFileMissing)?); 1623 let size = parser 1624 .convert::<ByteSized>("size") 1625 .map_err(Error::ParsePersistentMemory)? 1626 .map(|v| v.0); 1627 let iommu = parser 1628 .convert::<Toggle>("iommu") 1629 .map_err(Error::ParsePersistentMemory)? 1630 .unwrap_or(Toggle(false)) 1631 .0; 1632 let discard_writes = parser 1633 .convert::<Toggle>("discard_writes") 1634 .map_err(Error::ParsePersistentMemory)? 1635 .unwrap_or(Toggle(false)) 1636 .0; 1637 let id = parser.get("id"); 1638 let pci_segment = parser 1639 .convert("pci_segment") 1640 .map_err(Error::ParsePersistentMemory)? 1641 .unwrap_or_default(); 1642 1643 Ok(PmemConfig { 1644 file, 1645 size, 1646 iommu, 1647 discard_writes, 1648 id, 1649 pci_segment, 1650 }) 1651 } 1652 1653 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1654 if let Some(platform_config) = vm_config.platform.as_ref() { 1655 if self.pci_segment >= platform_config.num_pci_segments { 1656 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1657 } 1658 1659 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1660 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1661 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1662 } 1663 } 1664 } 1665 1666 Ok(()) 1667 } 1668 } 1669 1670 impl ConsoleConfig { 1671 pub fn parse(console: &str) -> Result<Self> { 1672 let mut parser = OptionParser::new(); 1673 parser 1674 .add_valueless("off") 1675 .add_valueless("pty") 1676 .add_valueless("tty") 1677 .add_valueless("null") 1678 .add("file") 1679 .add("iommu") 1680 .add("socket"); 1681 parser.parse(console).map_err(Error::ParseConsole)?; 1682 1683 let mut file: Option<PathBuf> = default_consoleconfig_file(); 1684 let mut socket: Option<PathBuf> = None; 1685 let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off; 1686 1687 if parser.is_set("off") { 1688 } else if parser.is_set("pty") { 1689 mode = ConsoleOutputMode::Pty 1690 } else if parser.is_set("tty") { 1691 mode = ConsoleOutputMode::Tty 1692 } else if parser.is_set("null") { 1693 mode = ConsoleOutputMode::Null 1694 } else if parser.is_set("file") { 1695 mode = ConsoleOutputMode::File; 1696 file = 1697 Some(PathBuf::from(parser.get("file").ok_or( 1698 Error::Validation(ValidationError::ConsoleFileMissing), 1699 )?)); 1700 } else if parser.is_set("socket") { 1701 mode = ConsoleOutputMode::Socket; 1702 socket = Some(PathBuf::from(parser.get("socket").ok_or( 1703 Error::Validation(ValidationError::ConsoleSocketPathMissing), 1704 )?)); 1705 } else { 1706 return Err(Error::ParseConsoleInvalidModeGiven); 1707 } 1708 let iommu = parser 1709 .convert::<Toggle>("iommu") 1710 .map_err(Error::ParseConsole)? 1711 .unwrap_or(Toggle(false)) 1712 .0; 1713 1714 Ok(Self { 1715 file, 1716 mode, 1717 iommu, 1718 socket, 1719 }) 1720 } 1721 } 1722 1723 #[cfg(target_arch = "x86_64")] 1724 impl DebugConsoleConfig { 1725 pub fn parse(debug_console_ops: &str) -> Result<Self> { 1726 let mut parser = OptionParser::new(); 1727 parser 1728 .add_valueless("off") 1729 .add_valueless("pty") 1730 .add_valueless("tty") 1731 .add_valueless("null") 1732 .add("file") 1733 .add("iobase"); 1734 parser 1735 .parse(debug_console_ops) 1736 .map_err(Error::ParseConsole)?; 1737 1738 let mut file: Option<PathBuf> = default_consoleconfig_file(); 1739 let mut iobase: Option<u16> = None; 1740 let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off; 1741 1742 if parser.is_set("off") { 1743 } else if parser.is_set("pty") { 1744 mode = ConsoleOutputMode::Pty 1745 } else if parser.is_set("tty") { 1746 mode = ConsoleOutputMode::Tty 1747 } else if parser.is_set("null") { 1748 mode = ConsoleOutputMode::Null 1749 } else if parser.is_set("file") { 1750 mode = ConsoleOutputMode::File; 1751 file = 1752 Some(PathBuf::from(parser.get("file").ok_or( 1753 Error::Validation(ValidationError::ConsoleFileMissing), 1754 )?)); 1755 } else { 1756 return Err(Error::ParseConsoleInvalidModeGiven); 1757 } 1758 1759 if parser.is_set("iobase") { 1760 if let Some(iobase_opt) = parser.get("iobase") { 1761 if !iobase_opt.starts_with("0x") { 1762 return Err(Error::Validation(ValidationError::InvalidIoPortHex( 1763 iobase_opt, 1764 ))); 1765 } 1766 iobase = Some(u16::from_str_radix(&iobase_opt[2..], 16).map_err(|_| { 1767 Error::Validation(ValidationError::InvalidIoPortHex(iobase_opt)) 1768 })?); 1769 } 1770 } 1771 1772 Ok(Self { file, mode, iobase }) 1773 } 1774 } 1775 1776 impl DeviceConfig { 1777 pub const SYNTAX: &'static str = 1778 "Direct device assignment parameters \"path=<device_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\""; 1779 1780 pub fn parse(device: &str) -> Result<Self> { 1781 let mut parser = OptionParser::new(); 1782 parser 1783 .add("path") 1784 .add("id") 1785 .add("iommu") 1786 .add("pci_segment") 1787 .add("x_nv_gpudirect_clique"); 1788 parser.parse(device).map_err(Error::ParseDevice)?; 1789 1790 let path = parser 1791 .get("path") 1792 .map(PathBuf::from) 1793 .ok_or(Error::ParseDevicePathMissing)?; 1794 let iommu = parser 1795 .convert::<Toggle>("iommu") 1796 .map_err(Error::ParseDevice)? 1797 .unwrap_or(Toggle(false)) 1798 .0; 1799 let id = parser.get("id"); 1800 let pci_segment = parser 1801 .convert::<u16>("pci_segment") 1802 .map_err(Error::ParseDevice)? 1803 .unwrap_or_default(); 1804 let x_nv_gpudirect_clique = parser 1805 .convert::<u8>("x_nv_gpudirect_clique") 1806 .map_err(Error::ParseDevice)?; 1807 Ok(DeviceConfig { 1808 path, 1809 iommu, 1810 id, 1811 pci_segment, 1812 x_nv_gpudirect_clique, 1813 }) 1814 } 1815 1816 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1817 if let Some(platform_config) = vm_config.platform.as_ref() { 1818 if self.pci_segment >= platform_config.num_pci_segments { 1819 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1820 } 1821 1822 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1823 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1824 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1825 } 1826 } 1827 } 1828 1829 Ok(()) 1830 } 1831 } 1832 1833 impl UserDeviceConfig { 1834 pub const SYNTAX: &'static str = 1835 "Userspace device socket=<socket_path>,id=<device_id>,pci_segment=<segment_id>\""; 1836 1837 pub fn parse(user_device: &str) -> Result<Self> { 1838 let mut parser = OptionParser::new(); 1839 parser.add("socket").add("id").add("pci_segment"); 1840 parser.parse(user_device).map_err(Error::ParseUserDevice)?; 1841 1842 let socket = parser 1843 .get("socket") 1844 .map(PathBuf::from) 1845 .ok_or(Error::ParseUserDeviceSocketMissing)?; 1846 let id = parser.get("id"); 1847 let pci_segment = parser 1848 .convert::<u16>("pci_segment") 1849 .map_err(Error::ParseUserDevice)? 1850 .unwrap_or_default(); 1851 1852 Ok(UserDeviceConfig { 1853 socket, 1854 id, 1855 pci_segment, 1856 }) 1857 } 1858 1859 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1860 if let Some(platform_config) = vm_config.platform.as_ref() { 1861 if self.pci_segment >= platform_config.num_pci_segments { 1862 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1863 } 1864 1865 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1866 if iommu_segments.contains(&self.pci_segment) { 1867 return Err(ValidationError::IommuNotSupportedOnSegment( 1868 self.pci_segment, 1869 )); 1870 } 1871 } 1872 } 1873 1874 Ok(()) 1875 } 1876 } 1877 1878 impl VdpaConfig { 1879 pub const SYNTAX: &'static str = "vDPA device \ 1880 \"path=<device_path>,num_queues=<number_of_queues>,iommu=on|off,\ 1881 id=<device_id>,pci_segment=<segment_id>\""; 1882 1883 pub fn parse(vdpa: &str) -> Result<Self> { 1884 let mut parser = OptionParser::new(); 1885 parser 1886 .add("path") 1887 .add("num_queues") 1888 .add("iommu") 1889 .add("id") 1890 .add("pci_segment"); 1891 parser.parse(vdpa).map_err(Error::ParseVdpa)?; 1892 1893 let path = parser 1894 .get("path") 1895 .map(PathBuf::from) 1896 .ok_or(Error::ParseVdpaPathMissing)?; 1897 let num_queues = parser 1898 .convert("num_queues") 1899 .map_err(Error::ParseVdpa)? 1900 .unwrap_or_else(default_vdpaconfig_num_queues); 1901 let iommu = parser 1902 .convert::<Toggle>("iommu") 1903 .map_err(Error::ParseVdpa)? 1904 .unwrap_or(Toggle(false)) 1905 .0; 1906 let id = parser.get("id"); 1907 let pci_segment = parser 1908 .convert("pci_segment") 1909 .map_err(Error::ParseVdpa)? 1910 .unwrap_or_default(); 1911 1912 Ok(VdpaConfig { 1913 path, 1914 num_queues, 1915 iommu, 1916 id, 1917 pci_segment, 1918 }) 1919 } 1920 1921 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1922 if let Some(platform_config) = vm_config.platform.as_ref() { 1923 if self.pci_segment >= platform_config.num_pci_segments { 1924 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1925 } 1926 1927 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1928 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1929 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1930 } 1931 } 1932 } 1933 1934 Ok(()) 1935 } 1936 } 1937 1938 impl VsockConfig { 1939 pub const SYNTAX: &'static str = "Virtio VSOCK parameters \ 1940 \"cid=<context_id>,socket=<socket_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\""; 1941 1942 pub fn parse(vsock: &str) -> Result<Self> { 1943 let mut parser = OptionParser::new(); 1944 parser 1945 .add("socket") 1946 .add("cid") 1947 .add("iommu") 1948 .add("id") 1949 .add("pci_segment"); 1950 parser.parse(vsock).map_err(Error::ParseVsock)?; 1951 1952 let socket = parser 1953 .get("socket") 1954 .map(PathBuf::from) 1955 .ok_or(Error::ParseVsockSockMissing)?; 1956 let iommu = parser 1957 .convert::<Toggle>("iommu") 1958 .map_err(Error::ParseVsock)? 1959 .unwrap_or(Toggle(false)) 1960 .0; 1961 let cid = parser 1962 .convert("cid") 1963 .map_err(Error::ParseVsock)? 1964 .ok_or(Error::ParseVsockCidMissing)?; 1965 let id = parser.get("id"); 1966 let pci_segment = parser 1967 .convert("pci_segment") 1968 .map_err(Error::ParseVsock)? 1969 .unwrap_or_default(); 1970 1971 Ok(VsockConfig { 1972 cid, 1973 socket, 1974 iommu, 1975 id, 1976 pci_segment, 1977 }) 1978 } 1979 1980 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1981 if let Some(platform_config) = vm_config.platform.as_ref() { 1982 if self.pci_segment >= platform_config.num_pci_segments { 1983 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1984 } 1985 1986 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1987 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1988 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1989 } 1990 } 1991 } 1992 1993 Ok(()) 1994 } 1995 } 1996 1997 #[cfg(target_arch = "x86_64")] 1998 impl SgxEpcConfig { 1999 pub const SYNTAX: &'static str = "SGX EPC parameters \ 2000 \"id=<epc_section_identifier>,size=<epc_section_size>,prefault=on|off\""; 2001 2002 pub fn parse(sgx_epc: &str) -> Result<Self> { 2003 let mut parser = OptionParser::new(); 2004 parser.add("id").add("size").add("prefault"); 2005 parser.parse(sgx_epc).map_err(Error::ParseSgxEpc)?; 2006 2007 let id = parser.get("id").ok_or(Error::ParseSgxEpcIdMissing)?; 2008 let size = parser 2009 .convert::<ByteSized>("size") 2010 .map_err(Error::ParseSgxEpc)? 2011 .unwrap_or(ByteSized(0)) 2012 .0; 2013 let prefault = parser 2014 .convert::<Toggle>("prefault") 2015 .map_err(Error::ParseSgxEpc)? 2016 .unwrap_or(Toggle(false)) 2017 .0; 2018 2019 Ok(SgxEpcConfig { id, size, prefault }) 2020 } 2021 } 2022 2023 impl NumaConfig { 2024 pub const SYNTAX: &'static str = "Settings related to a given NUMA node \ 2025 \"guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,\ 2026 memory_zones=<list_of_memory_zones>,sgx_epc_sections=<list_of_sgx_epc_sections>,\ 2027 pci_segments=<list_of_pci_segments>\""; 2028 2029 pub fn parse(numa: &str) -> Result<Self> { 2030 let mut parser = OptionParser::new(); 2031 parser 2032 .add("guest_numa_id") 2033 .add("cpus") 2034 .add("distances") 2035 .add("memory_zones") 2036 .add("sgx_epc_sections") 2037 .add("pci_segments"); 2038 2039 parser.parse(numa).map_err(Error::ParseNuma)?; 2040 2041 let guest_numa_id = parser 2042 .convert::<u32>("guest_numa_id") 2043 .map_err(Error::ParseNuma)? 2044 .unwrap_or(0); 2045 let cpus = parser 2046 .convert::<IntegerList>("cpus") 2047 .map_err(Error::ParseNuma)? 2048 .map(|v| v.0.iter().map(|e| *e as u8).collect()); 2049 let distances = parser 2050 .convert::<Tuple<u64, u64>>("distances") 2051 .map_err(Error::ParseNuma)? 2052 .map(|v| { 2053 v.0.iter() 2054 .map(|(e1, e2)| NumaDistance { 2055 destination: *e1 as u32, 2056 distance: *e2 as u8, 2057 }) 2058 .collect() 2059 }); 2060 let memory_zones = parser 2061 .convert::<StringList>("memory_zones") 2062 .map_err(Error::ParseNuma)? 2063 .map(|v| v.0); 2064 #[cfg(target_arch = "x86_64")] 2065 let sgx_epc_sections = parser 2066 .convert::<StringList>("sgx_epc_sections") 2067 .map_err(Error::ParseNuma)? 2068 .map(|v| v.0); 2069 let pci_segments = parser 2070 .convert::<IntegerList>("pci_segments") 2071 .map_err(Error::ParseNuma)? 2072 .map(|v| v.0.iter().map(|e| *e as u16).collect()); 2073 Ok(NumaConfig { 2074 guest_numa_id, 2075 cpus, 2076 distances, 2077 memory_zones, 2078 #[cfg(target_arch = "x86_64")] 2079 sgx_epc_sections, 2080 pci_segments, 2081 }) 2082 } 2083 } 2084 2085 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] 2086 pub struct RestoredNetConfig { 2087 pub id: String, 2088 #[serde(default)] 2089 pub num_fds: usize, 2090 #[serde( 2091 default, 2092 serialize_with = "serialize_restorednetconfig_fds", 2093 deserialize_with = "deserialize_restorednetconfig_fds" 2094 )] 2095 pub fds: Option<Vec<i32>>, 2096 } 2097 2098 fn serialize_restorednetconfig_fds<S>( 2099 x: &Option<Vec<i32>>, 2100 s: S, 2101 ) -> std::result::Result<S::Ok, S::Error> 2102 where 2103 S: serde::Serializer, 2104 { 2105 if let Some(x) = x { 2106 warn!("'RestoredNetConfig' contains FDs that can't be serialized correctly. Serializing them as invalid FDs."); 2107 let invalid_fds = vec![-1; x.len()]; 2108 s.serialize_some(&invalid_fds) 2109 } else { 2110 s.serialize_none() 2111 } 2112 } 2113 2114 fn deserialize_restorednetconfig_fds<'de, D>( 2115 d: D, 2116 ) -> std::result::Result<Option<Vec<i32>>, D::Error> 2117 where 2118 D: serde::Deserializer<'de>, 2119 { 2120 let invalid_fds: Option<Vec<i32>> = Option::deserialize(d)?; 2121 if let Some(invalid_fds) = invalid_fds { 2122 warn!("'RestoredNetConfig' contains FDs that can't be deserialized correctly. Deserializing them as invalid FDs."); 2123 Ok(Some(vec![-1; invalid_fds.len()])) 2124 } else { 2125 Ok(None) 2126 } 2127 } 2128 2129 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] 2130 pub struct RestoreConfig { 2131 pub source_url: PathBuf, 2132 #[serde(default)] 2133 pub prefault: bool, 2134 #[serde(default)] 2135 pub net_fds: Option<Vec<RestoredNetConfig>>, 2136 } 2137 2138 impl RestoreConfig { 2139 pub const SYNTAX: &'static str = "Restore from a VM snapshot. \ 2140 \nRestore parameters \"source_url=<source_url>,prefault=on|off,\ 2141 net_fds=<list_of_net_ids_with_their_associated_fds>\" \ 2142 \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \ 2143 \n`prefault` brings memory pages in when enabled (disabled by default) \ 2144 \n`net_fds` is a list of net ids with new file descriptors. \ 2145 Only net devices backed by FDs directly are needed as input."; 2146 2147 pub fn parse(restore: &str) -> Result<Self> { 2148 let mut parser = OptionParser::new(); 2149 parser.add("source_url").add("prefault").add("net_fds"); 2150 parser.parse(restore).map_err(Error::ParseRestore)?; 2151 2152 let source_url = parser 2153 .get("source_url") 2154 .map(PathBuf::from) 2155 .ok_or(Error::ParseRestoreSourceUrlMissing)?; 2156 let prefault = parser 2157 .convert::<Toggle>("prefault") 2158 .map_err(Error::ParseRestore)? 2159 .unwrap_or(Toggle(false)) 2160 .0; 2161 let net_fds = parser 2162 .convert::<Tuple<String, Vec<u64>>>("net_fds") 2163 .map_err(Error::ParseRestore)? 2164 .map(|v| { 2165 v.0.iter() 2166 .map(|(id, fds)| RestoredNetConfig { 2167 id: id.clone(), 2168 num_fds: fds.len(), 2169 fds: Some(fds.iter().map(|e| *e as i32).collect()), 2170 }) 2171 .collect() 2172 }); 2173 2174 Ok(RestoreConfig { 2175 source_url, 2176 prefault, 2177 net_fds, 2178 }) 2179 } 2180 2181 // Ensure all net devices from 'VmConfig' backed by FDs have a 2182 // corresponding 'RestoreNetConfig' with a matched 'id' and expected 2183 // number of FDs. 2184 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 2185 let mut restored_net_with_fds = HashMap::new(); 2186 for n in self.net_fds.iter().flatten() { 2187 assert_eq!( 2188 n.num_fds, 2189 n.fds.as_ref().map_or(0, |f| f.len()), 2190 "Invalid 'RestoredNetConfig' with conflicted fields." 2191 ); 2192 if restored_net_with_fds.insert(n.id.clone(), n).is_some() { 2193 return Err(ValidationError::IdentifierNotUnique(n.id.clone())); 2194 } 2195 } 2196 2197 for net_fds in vm_config.net.iter().flatten() { 2198 if let Some(expected_fds) = &net_fds.fds { 2199 let expected_id = net_fds 2200 .id 2201 .as_ref() 2202 .expect("Invalid 'NetConfig' with empty 'id' for VM restore."); 2203 if let Some(r) = restored_net_with_fds.remove(expected_id) { 2204 if r.num_fds != expected_fds.len() { 2205 return Err(ValidationError::RestoreNetFdCountMismatch( 2206 expected_id.clone(), 2207 r.num_fds, 2208 expected_fds.len(), 2209 )); 2210 } 2211 } else { 2212 return Err(ValidationError::RestoreMissingRequiredNetId( 2213 expected_id.clone(), 2214 )); 2215 } 2216 } 2217 } 2218 2219 if !restored_net_with_fds.is_empty() { 2220 warn!("Ignoring unused 'net_fds' for VM restore.") 2221 } 2222 2223 Ok(()) 2224 } 2225 } 2226 2227 impl TpmConfig { 2228 pub const SYNTAX: &'static str = "TPM device \ 2229 \"(UNIX Domain Socket from swtpm) socket=</path/to/a/socket>\""; 2230 2231 pub fn parse(tpm: &str) -> Result<Self> { 2232 let mut parser = OptionParser::new(); 2233 parser.add("socket"); 2234 parser.parse(tpm).map_err(Error::ParseTpm)?; 2235 let socket = parser 2236 .get("socket") 2237 .map(PathBuf::from) 2238 .ok_or(Error::ParseTpmPathMissing)?; 2239 Ok(TpmConfig { socket }) 2240 } 2241 } 2242 2243 impl LandlockConfig { 2244 pub const SYNTAX: &'static str = "Landlock parameters \ 2245 \"path=<path/to/{file/dir}>,access=[rw]\""; 2246 2247 pub fn parse(landlock_rule: &str) -> Result<Self> { 2248 let mut parser = OptionParser::new(); 2249 parser.add("path").add("access"); 2250 parser 2251 .parse(landlock_rule) 2252 .map_err(Error::ParseLandlockRules)?; 2253 2254 let path = parser 2255 .get("path") 2256 .map(PathBuf::from) 2257 .ok_or(Error::ParseLandlockMissingFields)?; 2258 2259 let access = parser 2260 .get("access") 2261 .ok_or(Error::ParseLandlockMissingFields)?; 2262 2263 if access.chars().count() > 2 { 2264 return Err(Error::ParseLandlockRules(OptionParserError::InvalidValue( 2265 access.to_string(), 2266 ))); 2267 } 2268 2269 Ok(LandlockConfig { path, access }) 2270 } 2271 2272 pub fn validate(&self) -> ValidationResult<()> { 2273 if !self.path.exists() { 2274 return Err(ValidationError::LandlockPathDoesNotExist(self.path.clone())); 2275 } 2276 LandlockAccess::try_from(self.access.as_str()) 2277 .map_err(|e| ValidationError::InvalidLandlockAccess(e.to_string()))?; 2278 Ok(()) 2279 } 2280 } 2281 2282 impl VmConfig { 2283 fn validate_identifier( 2284 id_list: &mut BTreeSet<String>, 2285 id: &Option<String>, 2286 ) -> ValidationResult<()> { 2287 if let Some(id) = id.as_ref() { 2288 if id.starts_with("__") { 2289 return Err(ValidationError::InvalidIdentifier(id.clone())); 2290 } 2291 2292 if !id_list.insert(id.clone()) { 2293 return Err(ValidationError::IdentifierNotUnique(id.clone())); 2294 } 2295 } 2296 2297 Ok(()) 2298 } 2299 2300 pub fn backed_by_shared_memory(&self) -> bool { 2301 if self.memory.shared || self.memory.hugepages { 2302 return true; 2303 } 2304 2305 if self.memory.size == 0 { 2306 for zone in self.memory.zones.as_ref().unwrap() { 2307 if !zone.shared && !zone.hugepages { 2308 return false; 2309 } 2310 } 2311 true 2312 } else { 2313 false 2314 } 2315 } 2316 2317 // Also enables virtio-iommu if the config needs it 2318 // Returns the list of unique identifiers provided through the 2319 // configuration. 2320 pub fn validate(&mut self) -> ValidationResult<BTreeSet<String>> { 2321 let mut id_list = BTreeSet::new(); 2322 2323 self.payload 2324 .as_ref() 2325 .ok_or(ValidationError::KernelMissing)?; 2326 2327 #[cfg(feature = "tdx")] 2328 { 2329 let tdx_enabled = self.platform.as_ref().map(|p| p.tdx).unwrap_or(false); 2330 // At this point we know payload isn't None. 2331 if tdx_enabled && self.payload.as_ref().unwrap().firmware.is_none() { 2332 return Err(ValidationError::TdxFirmwareMissing); 2333 } 2334 if tdx_enabled && (self.cpus.max_vcpus != self.cpus.boot_vcpus) { 2335 return Err(ValidationError::TdxNoCpuHotplug); 2336 } 2337 } 2338 2339 #[cfg(feature = "sev_snp")] 2340 { 2341 let host_data_opt = &self.payload.as_ref().unwrap().host_data; 2342 2343 if let Some(host_data) = host_data_opt { 2344 if host_data.len() != 64 { 2345 return Err(ValidationError::InvalidHostData); 2346 } 2347 } 2348 } 2349 // The 'conflict' check is introduced in commit 24438e0390d3 2350 // (vm-virtio: Enable the vmm support for virtio-console). 2351 // 2352 // Allow simultaneously set serial and console as TTY mode, for 2353 // someone want to use virtio console for better performance, and 2354 // want to keep legacy serial to catch boot stage logs for debug. 2355 // Using such double tty mode, you need to configure the kernel 2356 // properly, such as: 2357 // "console=hvc0 earlyprintk=ttyS0" 2358 2359 let mut tty_consoles = Vec::new(); 2360 if self.console.mode == ConsoleOutputMode::Tty { 2361 tty_consoles.push("virtio-console"); 2362 }; 2363 if self.serial.mode == ConsoleOutputMode::Tty { 2364 tty_consoles.push("serial-console"); 2365 }; 2366 #[cfg(target_arch = "x86_64")] 2367 if self.debug_console.mode == ConsoleOutputMode::Tty { 2368 tty_consoles.push("debug-console"); 2369 }; 2370 if tty_consoles.len() > 1 { 2371 warn!("Using TTY output for multiple consoles: {:?}", tty_consoles); 2372 } 2373 2374 if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() { 2375 return Err(ValidationError::ConsoleFileMissing); 2376 } 2377 2378 if self.serial.mode == ConsoleOutputMode::File && self.serial.file.is_none() { 2379 return Err(ValidationError::ConsoleFileMissing); 2380 } 2381 2382 if self.cpus.max_vcpus < self.cpus.boot_vcpus { 2383 return Err(ValidationError::CpusMaxLowerThanBoot); 2384 } 2385 2386 if let Some(rate_limit_groups) = &self.rate_limit_groups { 2387 for rate_limit_group in rate_limit_groups { 2388 rate_limit_group.validate(self)?; 2389 2390 Self::validate_identifier(&mut id_list, &Some(rate_limit_group.id.clone()))?; 2391 } 2392 } 2393 2394 if let Some(disks) = &self.disks { 2395 for disk in disks { 2396 if disk.vhost_socket.as_ref().and(disk.path.as_ref()).is_some() { 2397 return Err(ValidationError::DiskSocketAndPath); 2398 } 2399 if disk.vhost_user && !self.backed_by_shared_memory() { 2400 return Err(ValidationError::VhostUserRequiresSharedMemory); 2401 } 2402 if disk.vhost_user && disk.vhost_socket.is_none() { 2403 return Err(ValidationError::VhostUserMissingSocket); 2404 } 2405 if let Some(rate_limit_group) = &disk.rate_limit_group { 2406 if let Some(rate_limit_groups) = &self.rate_limit_groups { 2407 if !rate_limit_groups 2408 .iter() 2409 .any(|cfg| &cfg.id == rate_limit_group) 2410 { 2411 return Err(ValidationError::InvalidRateLimiterGroup); 2412 } 2413 } else { 2414 return Err(ValidationError::InvalidRateLimiterGroup); 2415 } 2416 } 2417 2418 disk.validate(self)?; 2419 self.iommu |= disk.iommu; 2420 2421 Self::validate_identifier(&mut id_list, &disk.id)?; 2422 } 2423 } 2424 2425 if let Some(nets) = &self.net { 2426 for net in nets { 2427 if net.vhost_user && !self.backed_by_shared_memory() { 2428 return Err(ValidationError::VhostUserRequiresSharedMemory); 2429 } 2430 net.validate(self)?; 2431 self.iommu |= net.iommu; 2432 2433 Self::validate_identifier(&mut id_list, &net.id)?; 2434 } 2435 } 2436 2437 if let Some(fses) = &self.fs { 2438 if !fses.is_empty() && !self.backed_by_shared_memory() { 2439 return Err(ValidationError::VhostUserRequiresSharedMemory); 2440 } 2441 for fs in fses { 2442 fs.validate(self)?; 2443 2444 Self::validate_identifier(&mut id_list, &fs.id)?; 2445 } 2446 } 2447 2448 if let Some(pmems) = &self.pmem { 2449 for pmem in pmems { 2450 pmem.validate(self)?; 2451 self.iommu |= pmem.iommu; 2452 2453 Self::validate_identifier(&mut id_list, &pmem.id)?; 2454 } 2455 } 2456 2457 self.iommu |= self.rng.iommu; 2458 self.iommu |= self.console.iommu; 2459 2460 if let Some(t) = &self.cpus.topology { 2461 if t.threads_per_core == 0 2462 || t.cores_per_die == 0 2463 || t.dies_per_package == 0 2464 || t.packages == 0 2465 { 2466 return Err(ValidationError::CpuTopologyZeroPart); 2467 } 2468 2469 // The setting of dies doesn't apply on AArch64. 2470 // Only '1' value is accepted, so its impact on the vcpu topology 2471 // setting can be ignored. 2472 #[cfg(target_arch = "aarch64")] 2473 if t.dies_per_package != 1 { 2474 return Err(ValidationError::CpuTopologyDiesPerPackage); 2475 } 2476 2477 let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages; 2478 if total != self.cpus.max_vcpus { 2479 return Err(ValidationError::CpuTopologyCount); 2480 } 2481 } 2482 2483 if let Some(hugepage_size) = &self.memory.hugepage_size { 2484 if !self.memory.hugepages { 2485 return Err(ValidationError::HugePageSizeWithoutHugePages); 2486 } 2487 if !hugepage_size.is_power_of_two() { 2488 return Err(ValidationError::InvalidHugePageSize(*hugepage_size)); 2489 } 2490 } 2491 2492 if let Some(user_devices) = &self.user_devices { 2493 if !user_devices.is_empty() && !self.backed_by_shared_memory() { 2494 return Err(ValidationError::UserDevicesRequireSharedMemory); 2495 } 2496 2497 for user_device in user_devices { 2498 user_device.validate(self)?; 2499 2500 Self::validate_identifier(&mut id_list, &user_device.id)?; 2501 } 2502 } 2503 2504 if let Some(vdpa_devices) = &self.vdpa { 2505 for vdpa_device in vdpa_devices { 2506 vdpa_device.validate(self)?; 2507 self.iommu |= vdpa_device.iommu; 2508 2509 Self::validate_identifier(&mut id_list, &vdpa_device.id)?; 2510 } 2511 } 2512 2513 if let Some(vsock) = &self.vsock { 2514 if [!0, 0, 1, 2].contains(&vsock.cid) { 2515 return Err(ValidationError::VsockSpecialCid(vsock.cid)); 2516 } 2517 } 2518 2519 if let Some(balloon) = &self.balloon { 2520 let mut ram_size = self.memory.size; 2521 2522 if let Some(zones) = &self.memory.zones { 2523 for zone in zones { 2524 ram_size += zone.size; 2525 } 2526 } 2527 2528 if balloon.size >= ram_size { 2529 return Err(ValidationError::BalloonLargerThanRam( 2530 balloon.size, 2531 ram_size, 2532 )); 2533 } 2534 } 2535 2536 if let Some(devices) = &self.devices { 2537 let mut device_paths = BTreeSet::new(); 2538 for device in devices { 2539 if !device_paths.insert(device.path.to_string_lossy()) { 2540 return Err(ValidationError::DuplicateDevicePath( 2541 device.path.to_string_lossy().to_string(), 2542 )); 2543 } 2544 2545 device.validate(self)?; 2546 self.iommu |= device.iommu; 2547 2548 Self::validate_identifier(&mut id_list, &device.id)?; 2549 } 2550 } 2551 2552 if let Some(vsock) = &self.vsock { 2553 vsock.validate(self)?; 2554 self.iommu |= vsock.iommu; 2555 2556 Self::validate_identifier(&mut id_list, &vsock.id)?; 2557 } 2558 2559 let num_pci_segments = match &self.platform { 2560 Some(platform_config) => platform_config.num_pci_segments, 2561 None => 1, 2562 }; 2563 if let Some(numa) = &self.numa { 2564 let mut used_numa_node_memory_zones = HashMap::new(); 2565 let mut used_pci_segments = HashMap::new(); 2566 for numa_node in numa.iter() { 2567 if let Some(memory_zones) = numa_node.memory_zones.clone() { 2568 for memory_zone in memory_zones.iter() { 2569 if !used_numa_node_memory_zones.contains_key(memory_zone) { 2570 used_numa_node_memory_zones 2571 .insert(memory_zone.to_string(), numa_node.guest_numa_id); 2572 } else { 2573 return Err(ValidationError::MemoryZoneReused( 2574 memory_zone.to_string(), 2575 *used_numa_node_memory_zones.get(memory_zone).unwrap(), 2576 numa_node.guest_numa_id, 2577 )); 2578 } 2579 } 2580 } 2581 2582 if let Some(pci_segments) = numa_node.pci_segments.clone() { 2583 for pci_segment in pci_segments.iter() { 2584 if *pci_segment >= num_pci_segments { 2585 return Err(ValidationError::InvalidPciSegment(*pci_segment)); 2586 } 2587 if *pci_segment == 0 && numa_node.guest_numa_id != 0 { 2588 return Err(ValidationError::DefaultPciSegmentInvalidNode( 2589 numa_node.guest_numa_id, 2590 )); 2591 } 2592 if !used_pci_segments.contains_key(pci_segment) { 2593 used_pci_segments.insert(*pci_segment, numa_node.guest_numa_id); 2594 } else { 2595 return Err(ValidationError::PciSegmentReused( 2596 *pci_segment, 2597 *used_pci_segments.get(pci_segment).unwrap(), 2598 numa_node.guest_numa_id, 2599 )); 2600 } 2601 } 2602 } 2603 } 2604 } 2605 2606 if let Some(zones) = &self.memory.zones { 2607 for zone in zones.iter() { 2608 let id = zone.id.clone(); 2609 Self::validate_identifier(&mut id_list, &Some(id))?; 2610 } 2611 } 2612 2613 #[cfg(target_arch = "x86_64")] 2614 if let Some(sgx_epcs) = &self.sgx_epc { 2615 for sgx_epc in sgx_epcs.iter() { 2616 let id = sgx_epc.id.clone(); 2617 Self::validate_identifier(&mut id_list, &Some(id))?; 2618 } 2619 } 2620 2621 if let Some(pci_segments) = &self.pci_segments { 2622 for pci_segment in pci_segments { 2623 pci_segment.validate(self)?; 2624 } 2625 } 2626 2627 self.platform.as_ref().map(|p| p.validate()).transpose()?; 2628 self.iommu |= self 2629 .platform 2630 .as_ref() 2631 .map(|p| p.iommu_segments.is_some()) 2632 .unwrap_or_default(); 2633 2634 if let Some(landlock_rules) = &self.landlock_rules { 2635 for landlock_rule in landlock_rules { 2636 landlock_rule.validate()?; 2637 } 2638 } 2639 2640 Ok(id_list) 2641 } 2642 2643 pub fn parse(vm_params: VmParams) -> Result<Self> { 2644 let mut rate_limit_groups: Option<Vec<RateLimiterGroupConfig>> = None; 2645 if let Some(rate_limit_group_list) = &vm_params.rate_limit_groups { 2646 let mut rate_limit_group_config_list = Vec::new(); 2647 for item in rate_limit_group_list.iter() { 2648 let rate_limit_group_config = RateLimiterGroupConfig::parse(item)?; 2649 rate_limit_group_config_list.push(rate_limit_group_config); 2650 } 2651 rate_limit_groups = Some(rate_limit_group_config_list); 2652 } 2653 2654 let mut disks: Option<Vec<DiskConfig>> = None; 2655 if let Some(disk_list) = &vm_params.disks { 2656 let mut disk_config_list = Vec::new(); 2657 for item in disk_list.iter() { 2658 let disk_config = DiskConfig::parse(item)?; 2659 disk_config_list.push(disk_config); 2660 } 2661 disks = Some(disk_config_list); 2662 } 2663 2664 let mut net: Option<Vec<NetConfig>> = None; 2665 if let Some(net_list) = &vm_params.net { 2666 let mut net_config_list = Vec::new(); 2667 for item in net_list.iter() { 2668 let net_config = NetConfig::parse(item)?; 2669 net_config_list.push(net_config); 2670 } 2671 net = Some(net_config_list); 2672 } 2673 2674 let rng = RngConfig::parse(vm_params.rng)?; 2675 2676 let mut balloon: Option<BalloonConfig> = None; 2677 if let Some(balloon_params) = &vm_params.balloon { 2678 balloon = Some(BalloonConfig::parse(balloon_params)?); 2679 } 2680 2681 #[cfg(feature = "pvmemcontrol")] 2682 let pvmemcontrol: Option<PvmemcontrolConfig> = vm_params 2683 .pvmemcontrol 2684 .then_some(PvmemcontrolConfig::default()); 2685 2686 let mut fs: Option<Vec<FsConfig>> = None; 2687 if let Some(fs_list) = &vm_params.fs { 2688 let mut fs_config_list = Vec::new(); 2689 for item in fs_list.iter() { 2690 fs_config_list.push(FsConfig::parse(item)?); 2691 } 2692 fs = Some(fs_config_list); 2693 } 2694 2695 let mut pmem: Option<Vec<PmemConfig>> = None; 2696 if let Some(pmem_list) = &vm_params.pmem { 2697 let mut pmem_config_list = Vec::new(); 2698 for item in pmem_list.iter() { 2699 let pmem_config = PmemConfig::parse(item)?; 2700 pmem_config_list.push(pmem_config); 2701 } 2702 pmem = Some(pmem_config_list); 2703 } 2704 2705 let console = ConsoleConfig::parse(vm_params.console)?; 2706 let serial = ConsoleConfig::parse(vm_params.serial)?; 2707 #[cfg(target_arch = "x86_64")] 2708 let debug_console = DebugConsoleConfig::parse(vm_params.debug_console)?; 2709 2710 let mut devices: Option<Vec<DeviceConfig>> = None; 2711 if let Some(device_list) = &vm_params.devices { 2712 let mut device_config_list = Vec::new(); 2713 for item in device_list.iter() { 2714 let device_config = DeviceConfig::parse(item)?; 2715 device_config_list.push(device_config); 2716 } 2717 devices = Some(device_config_list); 2718 } 2719 2720 let mut user_devices: Option<Vec<UserDeviceConfig>> = None; 2721 if let Some(user_device_list) = &vm_params.user_devices { 2722 let mut user_device_config_list = Vec::new(); 2723 for item in user_device_list.iter() { 2724 let user_device_config = UserDeviceConfig::parse(item)?; 2725 user_device_config_list.push(user_device_config); 2726 } 2727 user_devices = Some(user_device_config_list); 2728 } 2729 2730 let mut vdpa: Option<Vec<VdpaConfig>> = None; 2731 if let Some(vdpa_list) = &vm_params.vdpa { 2732 let mut vdpa_config_list = Vec::new(); 2733 for item in vdpa_list.iter() { 2734 let vdpa_config = VdpaConfig::parse(item)?; 2735 vdpa_config_list.push(vdpa_config); 2736 } 2737 vdpa = Some(vdpa_config_list); 2738 } 2739 2740 let mut vsock: Option<VsockConfig> = None; 2741 if let Some(vs) = &vm_params.vsock { 2742 let vsock_config = VsockConfig::parse(vs)?; 2743 vsock = Some(vsock_config); 2744 } 2745 2746 let mut pci_segments: Option<Vec<PciSegmentConfig>> = None; 2747 if let Some(pci_segment_list) = &vm_params.pci_segments { 2748 let mut pci_segment_config_list = Vec::new(); 2749 for item in pci_segment_list.iter() { 2750 let pci_segment_config = PciSegmentConfig::parse(item)?; 2751 pci_segment_config_list.push(pci_segment_config); 2752 } 2753 pci_segments = Some(pci_segment_config_list); 2754 } 2755 2756 let platform = vm_params.platform.map(PlatformConfig::parse).transpose()?; 2757 2758 #[cfg(target_arch = "x86_64")] 2759 let mut sgx_epc: Option<Vec<SgxEpcConfig>> = None; 2760 #[cfg(target_arch = "x86_64")] 2761 { 2762 if let Some(sgx_epc_list) = &vm_params.sgx_epc { 2763 warn!("SGX support is deprecated and will be removed in a future release."); 2764 let mut sgx_epc_config_list = Vec::new(); 2765 for item in sgx_epc_list.iter() { 2766 let sgx_epc_config = SgxEpcConfig::parse(item)?; 2767 sgx_epc_config_list.push(sgx_epc_config); 2768 } 2769 sgx_epc = Some(sgx_epc_config_list); 2770 } 2771 } 2772 2773 let mut numa: Option<Vec<NumaConfig>> = None; 2774 if let Some(numa_list) = &vm_params.numa { 2775 let mut numa_config_list = Vec::new(); 2776 for item in numa_list.iter() { 2777 let numa_config = NumaConfig::parse(item)?; 2778 numa_config_list.push(numa_config); 2779 } 2780 numa = Some(numa_config_list); 2781 } 2782 2783 #[cfg(not(feature = "igvm"))] 2784 let payload_present = vm_params.kernel.is_some() || vm_params.firmware.is_some(); 2785 2786 #[cfg(feature = "igvm")] 2787 let payload_present = 2788 vm_params.kernel.is_some() || vm_params.firmware.is_some() || vm_params.igvm.is_some(); 2789 2790 let payload = if payload_present { 2791 Some(PayloadConfig { 2792 kernel: vm_params.kernel.map(PathBuf::from), 2793 initramfs: vm_params.initramfs.map(PathBuf::from), 2794 cmdline: vm_params.cmdline.map(|s| s.to_string()), 2795 firmware: vm_params.firmware.map(PathBuf::from), 2796 #[cfg(feature = "igvm")] 2797 igvm: vm_params.igvm.map(PathBuf::from), 2798 #[cfg(feature = "sev_snp")] 2799 host_data: vm_params.host_data.map(|s| s.to_string()), 2800 }) 2801 } else { 2802 None 2803 }; 2804 2805 let mut tpm: Option<TpmConfig> = None; 2806 if let Some(tc) = vm_params.tpm { 2807 let tpm_conf = TpmConfig::parse(tc)?; 2808 tpm = Some(TpmConfig { 2809 socket: tpm_conf.socket, 2810 }); 2811 } 2812 2813 #[cfg(feature = "guest_debug")] 2814 let gdb = vm_params.gdb; 2815 2816 let mut landlock_rules: Option<Vec<LandlockConfig>> = None; 2817 if let Some(ll_rules) = vm_params.landlock_rules { 2818 landlock_rules = Some( 2819 ll_rules 2820 .iter() 2821 .map(|rule| LandlockConfig::parse(rule)) 2822 .collect::<Result<Vec<LandlockConfig>>>()?, 2823 ); 2824 } 2825 2826 let mut config = VmConfig { 2827 cpus: CpusConfig::parse(vm_params.cpus)?, 2828 memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?, 2829 payload, 2830 rate_limit_groups, 2831 disks, 2832 net, 2833 rng, 2834 balloon, 2835 fs, 2836 pmem, 2837 serial, 2838 console, 2839 #[cfg(target_arch = "x86_64")] 2840 debug_console, 2841 devices, 2842 user_devices, 2843 vdpa, 2844 vsock, 2845 #[cfg(feature = "pvmemcontrol")] 2846 pvmemcontrol, 2847 pvpanic: vm_params.pvpanic, 2848 iommu: false, // updated in VmConfig::validate() 2849 #[cfg(target_arch = "x86_64")] 2850 sgx_epc, 2851 numa, 2852 watchdog: vm_params.watchdog, 2853 #[cfg(feature = "guest_debug")] 2854 gdb, 2855 pci_segments, 2856 platform, 2857 tpm, 2858 preserved_fds: None, 2859 landlock_enable: vm_params.landlock_enable, 2860 landlock_rules, 2861 }; 2862 config.validate().map_err(Error::Validation)?; 2863 Ok(config) 2864 } 2865 2866 pub fn remove_device(&mut self, id: &str) -> bool { 2867 let mut removed = false; 2868 2869 // Remove if VFIO device 2870 if let Some(devices) = self.devices.as_mut() { 2871 let len = devices.len(); 2872 devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2873 removed |= devices.len() != len; 2874 } 2875 2876 // Remove if VFIO user device 2877 if let Some(user_devices) = self.user_devices.as_mut() { 2878 let len = user_devices.len(); 2879 user_devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2880 removed |= user_devices.len() != len; 2881 } 2882 2883 // Remove if disk device 2884 if let Some(disks) = self.disks.as_mut() { 2885 let len = disks.len(); 2886 disks.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2887 removed |= disks.len() != len; 2888 } 2889 2890 // Remove if fs device 2891 if let Some(fs) = self.fs.as_mut() { 2892 let len = fs.len(); 2893 fs.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2894 removed |= fs.len() != len; 2895 } 2896 2897 // Remove if net device 2898 if let Some(net) = self.net.as_mut() { 2899 let len = net.len(); 2900 net.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2901 removed |= net.len() != len; 2902 } 2903 2904 // Remove if pmem device 2905 if let Some(pmem) = self.pmem.as_mut() { 2906 let len = pmem.len(); 2907 pmem.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2908 removed |= pmem.len() != len; 2909 } 2910 2911 // Remove if vDPA device 2912 if let Some(vdpa) = self.vdpa.as_mut() { 2913 let len = vdpa.len(); 2914 vdpa.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2915 removed |= vdpa.len() != len; 2916 } 2917 2918 // Remove if vsock device 2919 if let Some(vsock) = self.vsock.as_ref() { 2920 if vsock.id.as_ref().map(|id| id.as_ref()) == Some(id) { 2921 self.vsock = None; 2922 removed = true; 2923 } 2924 } 2925 2926 removed 2927 } 2928 2929 /// # Safety 2930 /// To use this safely, the caller must guarantee that the input 2931 /// fds are all valid. 2932 pub unsafe fn add_preserved_fds(&mut self, mut fds: Vec<i32>) { 2933 if fds.is_empty() { 2934 return; 2935 } 2936 2937 if let Some(preserved_fds) = &self.preserved_fds { 2938 fds.append(&mut preserved_fds.clone()); 2939 } 2940 2941 self.preserved_fds = Some(fds); 2942 } 2943 2944 #[cfg(feature = "tdx")] 2945 pub fn is_tdx_enabled(&self) -> bool { 2946 self.platform.as_ref().map(|p| p.tdx).unwrap_or(false) 2947 } 2948 2949 #[cfg(feature = "sev_snp")] 2950 pub fn is_sev_snp_enabled(&self) -> bool { 2951 self.platform.as_ref().map(|p| p.sev_snp).unwrap_or(false) 2952 } 2953 } 2954 2955 impl Clone for VmConfig { 2956 fn clone(&self) -> Self { 2957 VmConfig { 2958 cpus: self.cpus.clone(), 2959 memory: self.memory.clone(), 2960 payload: self.payload.clone(), 2961 rate_limit_groups: self.rate_limit_groups.clone(), 2962 disks: self.disks.clone(), 2963 net: self.net.clone(), 2964 rng: self.rng.clone(), 2965 balloon: self.balloon.clone(), 2966 #[cfg(feature = "pvmemcontrol")] 2967 pvmemcontrol: self.pvmemcontrol.clone(), 2968 fs: self.fs.clone(), 2969 pmem: self.pmem.clone(), 2970 serial: self.serial.clone(), 2971 console: self.console.clone(), 2972 #[cfg(target_arch = "x86_64")] 2973 debug_console: self.debug_console.clone(), 2974 devices: self.devices.clone(), 2975 user_devices: self.user_devices.clone(), 2976 vdpa: self.vdpa.clone(), 2977 vsock: self.vsock.clone(), 2978 #[cfg(target_arch = "x86_64")] 2979 sgx_epc: self.sgx_epc.clone(), 2980 numa: self.numa.clone(), 2981 pci_segments: self.pci_segments.clone(), 2982 platform: self.platform.clone(), 2983 tpm: self.tpm.clone(), 2984 preserved_fds: self 2985 .preserved_fds 2986 .as_ref() 2987 // SAFETY: FFI call with valid FDs 2988 .map(|fds| fds.iter().map(|fd| unsafe { libc::dup(*fd) }).collect()), 2989 landlock_rules: self.landlock_rules.clone(), 2990 ..*self 2991 } 2992 } 2993 } 2994 2995 impl Drop for VmConfig { 2996 fn drop(&mut self) { 2997 if let Some(mut fds) = self.preserved_fds.take() { 2998 for fd in fds.drain(..) { 2999 // SAFETY: FFI call with valid FDs 3000 unsafe { libc::close(fd) }; 3001 } 3002 } 3003 } 3004 } 3005 3006 #[cfg(test)] 3007 mod tests { 3008 use std::fs::File; 3009 use std::net::{IpAddr, Ipv4Addr}; 3010 use std::os::unix::io::AsRawFd; 3011 3012 use net_util::MacAddr; 3013 3014 use super::*; 3015 3016 #[test] 3017 fn test_cpu_parsing() -> Result<()> { 3018 assert_eq!(CpusConfig::parse("")?, CpusConfig::default()); 3019 3020 assert_eq!( 3021 CpusConfig::parse("boot=1")?, 3022 CpusConfig { 3023 boot_vcpus: 1, 3024 max_vcpus: 1, 3025 ..Default::default() 3026 } 3027 ); 3028 assert_eq!( 3029 CpusConfig::parse("boot=1,max=2")?, 3030 CpusConfig { 3031 boot_vcpus: 1, 3032 max_vcpus: 2, 3033 ..Default::default() 3034 } 3035 ); 3036 assert_eq!( 3037 CpusConfig::parse("boot=8,topology=2:2:1:2")?, 3038 CpusConfig { 3039 boot_vcpus: 8, 3040 max_vcpus: 8, 3041 topology: Some(CpuTopology { 3042 threads_per_core: 2, 3043 cores_per_die: 2, 3044 dies_per_package: 1, 3045 packages: 2 3046 }), 3047 ..Default::default() 3048 } 3049 ); 3050 3051 CpusConfig::parse("boot=8,topology=2:2:1").unwrap_err(); 3052 CpusConfig::parse("boot=8,topology=2:2:1:x").unwrap_err(); 3053 assert_eq!( 3054 CpusConfig::parse("boot=1,kvm_hyperv=on")?, 3055 CpusConfig { 3056 boot_vcpus: 1, 3057 max_vcpus: 1, 3058 kvm_hyperv: true, 3059 ..Default::default() 3060 } 3061 ); 3062 assert_eq!( 3063 CpusConfig::parse("boot=2,affinity=[0@[0,2],1@[1,3]]")?, 3064 CpusConfig { 3065 boot_vcpus: 2, 3066 max_vcpus: 2, 3067 affinity: Some(vec![ 3068 CpuAffinity { 3069 vcpu: 0, 3070 host_cpus: vec![0, 2], 3071 }, 3072 CpuAffinity { 3073 vcpu: 1, 3074 host_cpus: vec![1, 3], 3075 } 3076 ]), 3077 ..Default::default() 3078 }, 3079 ); 3080 3081 Ok(()) 3082 } 3083 3084 #[test] 3085 fn test_mem_parsing() -> Result<()> { 3086 assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default()); 3087 // Default string 3088 assert_eq!( 3089 MemoryConfig::parse("size=512M", None)?, 3090 MemoryConfig::default() 3091 ); 3092 assert_eq!( 3093 MemoryConfig::parse("size=512M,mergeable=on", None)?, 3094 MemoryConfig { 3095 size: 512 << 20, 3096 mergeable: true, 3097 ..Default::default() 3098 } 3099 ); 3100 assert_eq!( 3101 MemoryConfig::parse("mergeable=on", None)?, 3102 MemoryConfig { 3103 mergeable: true, 3104 ..Default::default() 3105 } 3106 ); 3107 assert_eq!( 3108 MemoryConfig::parse("size=1G,mergeable=off", None)?, 3109 MemoryConfig { 3110 size: 1 << 30, 3111 mergeable: false, 3112 ..Default::default() 3113 } 3114 ); 3115 assert_eq!( 3116 MemoryConfig::parse("hotplug_method=acpi", None)?, 3117 MemoryConfig { 3118 ..Default::default() 3119 } 3120 ); 3121 assert_eq!( 3122 MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M", None)?, 3123 MemoryConfig { 3124 hotplug_size: Some(512 << 20), 3125 ..Default::default() 3126 } 3127 ); 3128 assert_eq!( 3129 MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M", None)?, 3130 MemoryConfig { 3131 hotplug_size: Some(512 << 20), 3132 hotplug_method: HotplugMethod::VirtioMem, 3133 ..Default::default() 3134 } 3135 ); 3136 assert_eq!( 3137 MemoryConfig::parse("hugepages=on,size=1G,hugepage_size=2M", None)?, 3138 MemoryConfig { 3139 hugepage_size: Some(2 << 20), 3140 size: 1 << 30, 3141 hugepages: true, 3142 ..Default::default() 3143 } 3144 ); 3145 Ok(()) 3146 } 3147 3148 #[test] 3149 fn test_rate_limit_group_parsing() -> Result<()> { 3150 assert_eq!( 3151 RateLimiterGroupConfig::parse("id=group0,bw_size=1000,bw_refill_time=100")?, 3152 RateLimiterGroupConfig { 3153 id: "group0".to_string(), 3154 rate_limiter_config: RateLimiterConfig { 3155 bandwidth: Some(TokenBucketConfig { 3156 size: 1000, 3157 one_time_burst: Some(0), 3158 refill_time: 100, 3159 }), 3160 ops: None, 3161 } 3162 } 3163 ); 3164 assert_eq!( 3165 RateLimiterGroupConfig::parse("id=group0,ops_size=1000,ops_refill_time=100")?, 3166 RateLimiterGroupConfig { 3167 id: "group0".to_string(), 3168 rate_limiter_config: RateLimiterConfig { 3169 bandwidth: None, 3170 ops: Some(TokenBucketConfig { 3171 size: 1000, 3172 one_time_burst: Some(0), 3173 refill_time: 100, 3174 }), 3175 } 3176 } 3177 ); 3178 Ok(()) 3179 } 3180 3181 #[test] 3182 fn test_pci_segment_parsing() -> Result<()> { 3183 assert_eq!( 3184 PciSegmentConfig::parse("pci_segment=0")?, 3185 PciSegmentConfig { 3186 pci_segment: 0, 3187 mmio32_aperture_weight: 1, 3188 mmio64_aperture_weight: 1, 3189 } 3190 ); 3191 assert_eq!( 3192 PciSegmentConfig::parse( 3193 "pci_segment=0,mmio32_aperture_weight=1,mmio64_aperture_weight=1" 3194 )?, 3195 PciSegmentConfig { 3196 pci_segment: 0, 3197 mmio32_aperture_weight: 1, 3198 mmio64_aperture_weight: 1, 3199 } 3200 ); 3201 assert_eq!( 3202 PciSegmentConfig::parse("pci_segment=0,mmio32_aperture_weight=2")?, 3203 PciSegmentConfig { 3204 pci_segment: 0, 3205 mmio32_aperture_weight: 2, 3206 mmio64_aperture_weight: 1, 3207 } 3208 ); 3209 assert_eq!( 3210 PciSegmentConfig::parse("pci_segment=0,mmio64_aperture_weight=2")?, 3211 PciSegmentConfig { 3212 pci_segment: 0, 3213 mmio32_aperture_weight: 1, 3214 mmio64_aperture_weight: 2, 3215 } 3216 ); 3217 3218 Ok(()) 3219 } 3220 3221 fn disk_fixture() -> DiskConfig { 3222 DiskConfig { 3223 path: Some(PathBuf::from("/path/to_file")), 3224 readonly: false, 3225 direct: false, 3226 iommu: false, 3227 num_queues: 1, 3228 queue_size: 128, 3229 vhost_user: false, 3230 vhost_socket: None, 3231 id: None, 3232 disable_io_uring: false, 3233 disable_aio: false, 3234 rate_limit_group: None, 3235 rate_limiter_config: None, 3236 pci_segment: 0, 3237 serial: None, 3238 queue_affinity: None, 3239 } 3240 } 3241 3242 #[test] 3243 fn test_disk_parsing() -> Result<()> { 3244 assert_eq!( 3245 DiskConfig::parse("path=/path/to_file")?, 3246 DiskConfig { ..disk_fixture() } 3247 ); 3248 assert_eq!( 3249 DiskConfig::parse("path=/path/to_file,id=mydisk0")?, 3250 DiskConfig { 3251 id: Some("mydisk0".to_owned()), 3252 ..disk_fixture() 3253 } 3254 ); 3255 assert_eq!( 3256 DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?, 3257 DiskConfig { 3258 path: None, 3259 vhost_socket: Some(String::from("/tmp/sock")), 3260 vhost_user: true, 3261 ..disk_fixture() 3262 } 3263 ); 3264 assert_eq!( 3265 DiskConfig::parse("path=/path/to_file,iommu=on")?, 3266 DiskConfig { 3267 iommu: true, 3268 ..disk_fixture() 3269 } 3270 ); 3271 assert_eq!( 3272 DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256")?, 3273 DiskConfig { 3274 iommu: true, 3275 queue_size: 256, 3276 ..disk_fixture() 3277 } 3278 ); 3279 assert_eq!( 3280 DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256,num_queues=4")?, 3281 DiskConfig { 3282 iommu: true, 3283 queue_size: 256, 3284 num_queues: 4, 3285 ..disk_fixture() 3286 } 3287 ); 3288 assert_eq!( 3289 DiskConfig::parse("path=/path/to_file,direct=on")?, 3290 DiskConfig { 3291 direct: true, 3292 ..disk_fixture() 3293 } 3294 ); 3295 assert_eq!( 3296 DiskConfig::parse("path=/path/to_file,serial=test")?, 3297 DiskConfig { 3298 serial: Some(String::from("test")), 3299 ..disk_fixture() 3300 } 3301 ); 3302 assert_eq!( 3303 DiskConfig::parse("path=/path/to_file,rate_limit_group=group0")?, 3304 DiskConfig { 3305 rate_limit_group: Some("group0".to_string()), 3306 ..disk_fixture() 3307 } 3308 ); 3309 assert_eq!( 3310 DiskConfig::parse("path=/path/to_file,queue_affinity=[0@[1],1@[2],2@[3,4],3@[5-8]]")?, 3311 DiskConfig { 3312 queue_affinity: Some(vec![ 3313 VirtQueueAffinity { 3314 queue_index: 0, 3315 host_cpus: vec![1], 3316 }, 3317 VirtQueueAffinity { 3318 queue_index: 1, 3319 host_cpus: vec![2], 3320 }, 3321 VirtQueueAffinity { 3322 queue_index: 2, 3323 host_cpus: vec![3, 4], 3324 }, 3325 VirtQueueAffinity { 3326 queue_index: 3, 3327 host_cpus: vec![5, 6, 7, 8], 3328 } 3329 ]), 3330 ..disk_fixture() 3331 } 3332 ); 3333 Ok(()) 3334 } 3335 3336 fn net_fixture() -> NetConfig { 3337 NetConfig { 3338 tap: None, 3339 ip: IpAddr::V4(Ipv4Addr::new(192, 168, 249, 1)), 3340 mask: IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0)), 3341 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), 3342 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), 3343 mtu: None, 3344 iommu: false, 3345 num_queues: 2, 3346 queue_size: 256, 3347 vhost_user: false, 3348 vhost_socket: None, 3349 vhost_mode: VhostMode::Client, 3350 id: None, 3351 fds: None, 3352 rate_limiter_config: None, 3353 pci_segment: 0, 3354 offload_tso: true, 3355 offload_ufo: true, 3356 offload_csum: true, 3357 } 3358 } 3359 3360 #[test] 3361 fn test_net_parsing() -> Result<()> { 3362 // mac address is random 3363 assert_eq!( 3364 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef")?, 3365 net_fixture(), 3366 ); 3367 3368 assert_eq!( 3369 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,id=mynet0")?, 3370 NetConfig { 3371 id: Some("mynet0".to_owned()), 3372 ..net_fixture() 3373 } 3374 ); 3375 3376 assert_eq!( 3377 NetConfig::parse( 3378 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,tap=tap0,ip=192.168.100.1,mask=255.255.255.128" 3379 )?, 3380 NetConfig { 3381 tap: Some("tap0".to_owned()), 3382 ip: "192.168.100.1".parse().unwrap(), 3383 mask: "255.255.255.128".parse().unwrap(), 3384 ..net_fixture() 3385 } 3386 ); 3387 3388 assert_eq!( 3389 NetConfig::parse( 3390 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,vhost_user=true,socket=/tmp/sock" 3391 )?, 3392 NetConfig { 3393 vhost_user: true, 3394 vhost_socket: Some("/tmp/sock".to_owned()), 3395 ..net_fixture() 3396 } 3397 ); 3398 3399 assert_eq!( 3400 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,num_queues=4,queue_size=1024,iommu=on")?, 3401 NetConfig { 3402 num_queues: 4, 3403 queue_size: 1024, 3404 iommu: true, 3405 ..net_fixture() 3406 } 3407 ); 3408 3409 assert_eq!( 3410 NetConfig::parse("mac=de:ad:be:ef:12:34,fd=[3,7],num_queues=4")?, 3411 NetConfig { 3412 host_mac: None, 3413 fds: Some(vec![3, 7]), 3414 num_queues: 4, 3415 ..net_fixture() 3416 } 3417 ); 3418 3419 Ok(()) 3420 } 3421 3422 #[test] 3423 fn test_parse_rng() -> Result<()> { 3424 assert_eq!(RngConfig::parse("")?, RngConfig::default()); 3425 assert_eq!( 3426 RngConfig::parse("src=/dev/random")?, 3427 RngConfig { 3428 src: PathBuf::from("/dev/random"), 3429 ..Default::default() 3430 } 3431 ); 3432 assert_eq!( 3433 RngConfig::parse("src=/dev/random,iommu=on")?, 3434 RngConfig { 3435 src: PathBuf::from("/dev/random"), 3436 iommu: true, 3437 } 3438 ); 3439 assert_eq!( 3440 RngConfig::parse("iommu=on")?, 3441 RngConfig { 3442 iommu: true, 3443 ..Default::default() 3444 } 3445 ); 3446 Ok(()) 3447 } 3448 3449 fn fs_fixture() -> FsConfig { 3450 FsConfig { 3451 socket: PathBuf::from("/tmp/sock"), 3452 tag: "mytag".to_owned(), 3453 num_queues: 1, 3454 queue_size: 1024, 3455 id: None, 3456 pci_segment: 0, 3457 } 3458 } 3459 3460 #[test] 3461 fn test_parse_fs() -> Result<()> { 3462 // "tag" and "socket" must be supplied 3463 FsConfig::parse("").unwrap_err(); 3464 FsConfig::parse("tag=mytag").unwrap_err(); 3465 FsConfig::parse("socket=/tmp/sock").unwrap_err(); 3466 assert_eq!(FsConfig::parse("tag=mytag,socket=/tmp/sock")?, fs_fixture()); 3467 assert_eq!( 3468 FsConfig::parse("tag=mytag,socket=/tmp/sock,num_queues=4,queue_size=1024")?, 3469 FsConfig { 3470 num_queues: 4, 3471 queue_size: 1024, 3472 ..fs_fixture() 3473 } 3474 ); 3475 3476 Ok(()) 3477 } 3478 3479 fn pmem_fixture() -> PmemConfig { 3480 PmemConfig { 3481 file: PathBuf::from("/tmp/pmem"), 3482 size: Some(128 << 20), 3483 iommu: false, 3484 discard_writes: false, 3485 id: None, 3486 pci_segment: 0, 3487 } 3488 } 3489 3490 #[test] 3491 fn test_pmem_parsing() -> Result<()> { 3492 // Must always give a file and size 3493 PmemConfig::parse("").unwrap_err(); 3494 PmemConfig::parse("size=128M").unwrap_err(); 3495 assert_eq!( 3496 PmemConfig::parse("file=/tmp/pmem,size=128M")?, 3497 pmem_fixture() 3498 ); 3499 assert_eq!( 3500 PmemConfig::parse("file=/tmp/pmem,size=128M,id=mypmem0")?, 3501 PmemConfig { 3502 id: Some("mypmem0".to_owned()), 3503 ..pmem_fixture() 3504 } 3505 ); 3506 assert_eq!( 3507 PmemConfig::parse("file=/tmp/pmem,size=128M,iommu=on,discard_writes=on")?, 3508 PmemConfig { 3509 discard_writes: true, 3510 iommu: true, 3511 ..pmem_fixture() 3512 } 3513 ); 3514 3515 Ok(()) 3516 } 3517 3518 #[test] 3519 fn test_console_parsing() -> Result<()> { 3520 ConsoleConfig::parse("").unwrap_err(); 3521 ConsoleConfig::parse("badmode").unwrap_err(); 3522 assert_eq!( 3523 ConsoleConfig::parse("off")?, 3524 ConsoleConfig { 3525 mode: ConsoleOutputMode::Off, 3526 iommu: false, 3527 file: None, 3528 socket: None, 3529 } 3530 ); 3531 assert_eq!( 3532 ConsoleConfig::parse("pty")?, 3533 ConsoleConfig { 3534 mode: ConsoleOutputMode::Pty, 3535 iommu: false, 3536 file: None, 3537 socket: None, 3538 } 3539 ); 3540 assert_eq!( 3541 ConsoleConfig::parse("tty")?, 3542 ConsoleConfig { 3543 mode: ConsoleOutputMode::Tty, 3544 iommu: false, 3545 file: None, 3546 socket: None, 3547 } 3548 ); 3549 assert_eq!( 3550 ConsoleConfig::parse("null")?, 3551 ConsoleConfig { 3552 mode: ConsoleOutputMode::Null, 3553 iommu: false, 3554 file: None, 3555 socket: None, 3556 } 3557 ); 3558 assert_eq!( 3559 ConsoleConfig::parse("file=/tmp/console")?, 3560 ConsoleConfig { 3561 mode: ConsoleOutputMode::File, 3562 iommu: false, 3563 file: Some(PathBuf::from("/tmp/console")), 3564 socket: None, 3565 } 3566 ); 3567 assert_eq!( 3568 ConsoleConfig::parse("null,iommu=on")?, 3569 ConsoleConfig { 3570 mode: ConsoleOutputMode::Null, 3571 iommu: true, 3572 file: None, 3573 socket: None, 3574 } 3575 ); 3576 assert_eq!( 3577 ConsoleConfig::parse("file=/tmp/console,iommu=on")?, 3578 ConsoleConfig { 3579 mode: ConsoleOutputMode::File, 3580 iommu: true, 3581 file: Some(PathBuf::from("/tmp/console")), 3582 socket: None, 3583 } 3584 ); 3585 assert_eq!( 3586 ConsoleConfig::parse("socket=/tmp/serial.sock,iommu=on")?, 3587 ConsoleConfig { 3588 mode: ConsoleOutputMode::Socket, 3589 iommu: true, 3590 file: None, 3591 socket: Some(PathBuf::from("/tmp/serial.sock")), 3592 } 3593 ); 3594 Ok(()) 3595 } 3596 3597 fn device_fixture() -> DeviceConfig { 3598 DeviceConfig { 3599 path: PathBuf::from("/path/to/device"), 3600 id: None, 3601 iommu: false, 3602 pci_segment: 0, 3603 x_nv_gpudirect_clique: None, 3604 } 3605 } 3606 3607 #[test] 3608 fn test_device_parsing() -> Result<()> { 3609 // Device must have a path provided 3610 DeviceConfig::parse("").unwrap_err(); 3611 assert_eq!( 3612 DeviceConfig::parse("path=/path/to/device")?, 3613 device_fixture() 3614 ); 3615 3616 assert_eq!( 3617 DeviceConfig::parse("path=/path/to/device,iommu=on")?, 3618 DeviceConfig { 3619 iommu: true, 3620 ..device_fixture() 3621 } 3622 ); 3623 3624 assert_eq!( 3625 DeviceConfig::parse("path=/path/to/device,iommu=on,id=mydevice0")?, 3626 DeviceConfig { 3627 id: Some("mydevice0".to_owned()), 3628 iommu: true, 3629 ..device_fixture() 3630 } 3631 ); 3632 3633 Ok(()) 3634 } 3635 3636 fn vdpa_fixture() -> VdpaConfig { 3637 VdpaConfig { 3638 path: PathBuf::from("/dev/vhost-vdpa"), 3639 num_queues: 1, 3640 iommu: false, 3641 id: None, 3642 pci_segment: 0, 3643 } 3644 } 3645 3646 #[test] 3647 fn test_vdpa_parsing() -> Result<()> { 3648 // path is required 3649 VdpaConfig::parse("").unwrap_err(); 3650 assert_eq!(VdpaConfig::parse("path=/dev/vhost-vdpa")?, vdpa_fixture()); 3651 assert_eq!( 3652 VdpaConfig::parse("path=/dev/vhost-vdpa,num_queues=2,id=my_vdpa")?, 3653 VdpaConfig { 3654 num_queues: 2, 3655 id: Some("my_vdpa".to_owned()), 3656 ..vdpa_fixture() 3657 } 3658 ); 3659 Ok(()) 3660 } 3661 3662 #[test] 3663 fn test_tpm_parsing() -> Result<()> { 3664 // path is required 3665 TpmConfig::parse("").unwrap_err(); 3666 assert_eq!( 3667 TpmConfig::parse("socket=/var/run/tpm.sock")?, 3668 TpmConfig { 3669 socket: PathBuf::from("/var/run/tpm.sock"), 3670 } 3671 ); 3672 Ok(()) 3673 } 3674 3675 #[test] 3676 fn test_vsock_parsing() -> Result<()> { 3677 // socket and cid is required 3678 VsockConfig::parse("").unwrap_err(); 3679 assert_eq!( 3680 VsockConfig::parse("socket=/tmp/sock,cid=3")?, 3681 VsockConfig { 3682 cid: 3, 3683 socket: PathBuf::from("/tmp/sock"), 3684 iommu: false, 3685 id: None, 3686 pci_segment: 0, 3687 } 3688 ); 3689 assert_eq!( 3690 VsockConfig::parse("socket=/tmp/sock,cid=3,iommu=on")?, 3691 VsockConfig { 3692 cid: 3, 3693 socket: PathBuf::from("/tmp/sock"), 3694 iommu: true, 3695 id: None, 3696 pci_segment: 0, 3697 } 3698 ); 3699 Ok(()) 3700 } 3701 3702 #[test] 3703 fn test_restore_parsing() -> Result<()> { 3704 assert_eq!( 3705 RestoreConfig::parse("source_url=/path/to/snapshot")?, 3706 RestoreConfig { 3707 source_url: PathBuf::from("/path/to/snapshot"), 3708 prefault: false, 3709 net_fds: None, 3710 } 3711 ); 3712 assert_eq!( 3713 RestoreConfig::parse( 3714 "source_url=/path/to/snapshot,prefault=off,net_fds=[net0@[3,4],net1@[5,6,7,8]]" 3715 )?, 3716 RestoreConfig { 3717 source_url: PathBuf::from("/path/to/snapshot"), 3718 prefault: false, 3719 net_fds: Some(vec![ 3720 RestoredNetConfig { 3721 id: "net0".to_string(), 3722 num_fds: 2, 3723 fds: Some(vec![3, 4]), 3724 }, 3725 RestoredNetConfig { 3726 id: "net1".to_string(), 3727 num_fds: 4, 3728 fds: Some(vec![5, 6, 7, 8]), 3729 } 3730 ]), 3731 } 3732 ); 3733 // Parsing should fail as source_url is a required field 3734 RestoreConfig::parse("prefault=off").unwrap_err(); 3735 Ok(()) 3736 } 3737 3738 #[test] 3739 fn test_restore_config_validation() { 3740 // interested in only VmConfig.net, so set rest to default values 3741 let mut snapshot_vm_config = VmConfig { 3742 cpus: CpusConfig::default(), 3743 memory: MemoryConfig::default(), 3744 payload: None, 3745 rate_limit_groups: None, 3746 disks: None, 3747 rng: RngConfig::default(), 3748 balloon: None, 3749 fs: None, 3750 pmem: None, 3751 serial: default_serial(), 3752 console: default_console(), 3753 #[cfg(target_arch = "x86_64")] 3754 debug_console: DebugConsoleConfig::default(), 3755 devices: None, 3756 user_devices: None, 3757 vdpa: None, 3758 vsock: None, 3759 #[cfg(feature = "pvmemcontrol")] 3760 pvmemcontrol: None, 3761 pvpanic: false, 3762 iommu: false, 3763 #[cfg(target_arch = "x86_64")] 3764 sgx_epc: None, 3765 numa: None, 3766 watchdog: false, 3767 #[cfg(feature = "guest_debug")] 3768 gdb: false, 3769 pci_segments: None, 3770 platform: None, 3771 tpm: None, 3772 preserved_fds: None, 3773 net: Some(vec![ 3774 NetConfig { 3775 id: Some("net0".to_owned()), 3776 num_queues: 2, 3777 fds: Some(vec![-1, -1, -1, -1]), 3778 ..net_fixture() 3779 }, 3780 NetConfig { 3781 id: Some("net1".to_owned()), 3782 num_queues: 1, 3783 fds: Some(vec![-1, -1]), 3784 ..net_fixture() 3785 }, 3786 NetConfig { 3787 id: Some("net2".to_owned()), 3788 fds: None, 3789 ..net_fixture() 3790 }, 3791 ]), 3792 landlock_enable: false, 3793 landlock_rules: None, 3794 }; 3795 3796 let valid_config = RestoreConfig { 3797 source_url: PathBuf::from("/path/to/snapshot"), 3798 prefault: false, 3799 net_fds: Some(vec![ 3800 RestoredNetConfig { 3801 id: "net0".to_string(), 3802 num_fds: 4, 3803 fds: Some(vec![3, 4, 5, 6]), 3804 }, 3805 RestoredNetConfig { 3806 id: "net1".to_string(), 3807 num_fds: 2, 3808 fds: Some(vec![7, 8]), 3809 }, 3810 ]), 3811 }; 3812 valid_config.validate(&snapshot_vm_config).unwrap(); 3813 3814 let mut invalid_config = valid_config.clone(); 3815 invalid_config.net_fds = Some(vec![RestoredNetConfig { 3816 id: "netx".to_string(), 3817 num_fds: 4, 3818 fds: Some(vec![3, 4, 5, 6]), 3819 }]); 3820 assert_eq!( 3821 invalid_config.validate(&snapshot_vm_config), 3822 Err(ValidationError::RestoreMissingRequiredNetId( 3823 "net0".to_string() 3824 )) 3825 ); 3826 3827 invalid_config.net_fds = Some(vec![ 3828 RestoredNetConfig { 3829 id: "net0".to_string(), 3830 num_fds: 4, 3831 fds: Some(vec![3, 4, 5, 6]), 3832 }, 3833 RestoredNetConfig { 3834 id: "net0".to_string(), 3835 num_fds: 4, 3836 fds: Some(vec![3, 4, 5, 6]), 3837 }, 3838 ]); 3839 assert_eq!( 3840 invalid_config.validate(&snapshot_vm_config), 3841 Err(ValidationError::IdentifierNotUnique("net0".to_string())) 3842 ); 3843 3844 invalid_config.net_fds = Some(vec![RestoredNetConfig { 3845 id: "net0".to_string(), 3846 num_fds: 4, 3847 fds: Some(vec![3, 4, 5, 6]), 3848 }]); 3849 assert_eq!( 3850 invalid_config.validate(&snapshot_vm_config), 3851 Err(ValidationError::RestoreMissingRequiredNetId( 3852 "net1".to_string() 3853 )) 3854 ); 3855 3856 invalid_config.net_fds = Some(vec![RestoredNetConfig { 3857 id: "net0".to_string(), 3858 num_fds: 2, 3859 fds: Some(vec![3, 4]), 3860 }]); 3861 assert_eq!( 3862 invalid_config.validate(&snapshot_vm_config), 3863 Err(ValidationError::RestoreNetFdCountMismatch( 3864 "net0".to_string(), 3865 2, 3866 4 3867 )) 3868 ); 3869 3870 let another_valid_config = RestoreConfig { 3871 source_url: PathBuf::from("/path/to/snapshot"), 3872 prefault: false, 3873 net_fds: None, 3874 }; 3875 snapshot_vm_config.net = Some(vec![NetConfig { 3876 id: Some("net2".to_owned()), 3877 fds: None, 3878 ..net_fixture() 3879 }]); 3880 another_valid_config.validate(&snapshot_vm_config).unwrap(); 3881 } 3882 3883 fn platform_fixture() -> PlatformConfig { 3884 PlatformConfig { 3885 num_pci_segments: MAX_NUM_PCI_SEGMENTS, 3886 iommu_segments: None, 3887 iommu_address_width_bits: MAX_IOMMU_ADDRESS_WIDTH_BITS, 3888 serial_number: None, 3889 uuid: None, 3890 oem_strings: None, 3891 #[cfg(feature = "tdx")] 3892 tdx: false, 3893 #[cfg(feature = "sev_snp")] 3894 sev_snp: false, 3895 } 3896 } 3897 3898 fn numa_fixture() -> NumaConfig { 3899 NumaConfig { 3900 guest_numa_id: 0, 3901 cpus: None, 3902 distances: None, 3903 memory_zones: None, 3904 #[cfg(target_arch = "x86_64")] 3905 sgx_epc_sections: None, 3906 pci_segments: None, 3907 } 3908 } 3909 3910 #[test] 3911 fn test_config_validation() { 3912 let mut valid_config = VmConfig { 3913 cpus: CpusConfig { 3914 boot_vcpus: 1, 3915 max_vcpus: 1, 3916 ..Default::default() 3917 }, 3918 memory: MemoryConfig { 3919 size: 536_870_912, 3920 mergeable: false, 3921 hotplug_method: HotplugMethod::Acpi, 3922 hotplug_size: None, 3923 hotplugged_size: None, 3924 shared: false, 3925 hugepages: false, 3926 hugepage_size: None, 3927 prefault: false, 3928 zones: None, 3929 thp: true, 3930 }, 3931 payload: Some(PayloadConfig { 3932 kernel: Some(PathBuf::from("/path/to/kernel")), 3933 firmware: None, 3934 cmdline: None, 3935 initramfs: None, 3936 #[cfg(feature = "igvm")] 3937 igvm: None, 3938 #[cfg(feature = "sev_snp")] 3939 host_data: Some( 3940 "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb431188673288c07".to_string(), 3941 ), 3942 }), 3943 rate_limit_groups: None, 3944 disks: None, 3945 net: None, 3946 rng: RngConfig { 3947 src: PathBuf::from("/dev/urandom"), 3948 iommu: false, 3949 }, 3950 balloon: None, 3951 fs: None, 3952 pmem: None, 3953 serial: ConsoleConfig { 3954 file: None, 3955 mode: ConsoleOutputMode::Null, 3956 iommu: false, 3957 socket: None, 3958 }, 3959 console: ConsoleConfig { 3960 file: None, 3961 mode: ConsoleOutputMode::Tty, 3962 iommu: false, 3963 socket: None, 3964 }, 3965 #[cfg(target_arch = "x86_64")] 3966 debug_console: DebugConsoleConfig::default(), 3967 devices: None, 3968 user_devices: None, 3969 vdpa: None, 3970 vsock: None, 3971 #[cfg(feature = "pvmemcontrol")] 3972 pvmemcontrol: None, 3973 pvpanic: false, 3974 iommu: false, 3975 #[cfg(target_arch = "x86_64")] 3976 sgx_epc: None, 3977 numa: None, 3978 watchdog: false, 3979 #[cfg(feature = "guest_debug")] 3980 gdb: false, 3981 pci_segments: None, 3982 platform: None, 3983 tpm: None, 3984 preserved_fds: None, 3985 landlock_enable: false, 3986 landlock_rules: None, 3987 }; 3988 3989 valid_config.validate().unwrap(); 3990 3991 let mut invalid_config = valid_config.clone(); 3992 invalid_config.serial.mode = ConsoleOutputMode::Tty; 3993 invalid_config.console.mode = ConsoleOutputMode::Tty; 3994 valid_config.validate().unwrap(); 3995 3996 let mut invalid_config = valid_config.clone(); 3997 invalid_config.payload = None; 3998 assert_eq!( 3999 invalid_config.validate(), 4000 Err(ValidationError::KernelMissing) 4001 ); 4002 4003 let mut invalid_config = valid_config.clone(); 4004 invalid_config.serial.mode = ConsoleOutputMode::File; 4005 invalid_config.serial.file = None; 4006 assert_eq!( 4007 invalid_config.validate(), 4008 Err(ValidationError::ConsoleFileMissing) 4009 ); 4010 4011 let mut invalid_config = valid_config.clone(); 4012 invalid_config.cpus.max_vcpus = 16; 4013 invalid_config.cpus.boot_vcpus = 32; 4014 assert_eq!( 4015 invalid_config.validate(), 4016 Err(ValidationError::CpusMaxLowerThanBoot) 4017 ); 4018 4019 let mut invalid_config = valid_config.clone(); 4020 invalid_config.cpus.max_vcpus = 16; 4021 invalid_config.cpus.boot_vcpus = 16; 4022 invalid_config.cpus.topology = Some(CpuTopology { 4023 threads_per_core: 2, 4024 cores_per_die: 8, 4025 dies_per_package: 1, 4026 packages: 2, 4027 }); 4028 assert_eq!( 4029 invalid_config.validate(), 4030 Err(ValidationError::CpuTopologyCount) 4031 ); 4032 4033 let mut invalid_config = valid_config.clone(); 4034 invalid_config.disks = Some(vec![DiskConfig { 4035 vhost_socket: Some("/path/to/sock".to_owned()), 4036 path: Some(PathBuf::from("/path/to/image")), 4037 ..disk_fixture() 4038 }]); 4039 assert_eq!( 4040 invalid_config.validate(), 4041 Err(ValidationError::DiskSocketAndPath) 4042 ); 4043 4044 let mut invalid_config = valid_config.clone(); 4045 invalid_config.memory.shared = true; 4046 invalid_config.disks = Some(vec![DiskConfig { 4047 path: None, 4048 vhost_user: true, 4049 ..disk_fixture() 4050 }]); 4051 assert_eq!( 4052 invalid_config.validate(), 4053 Err(ValidationError::VhostUserMissingSocket) 4054 ); 4055 4056 let mut invalid_config = valid_config.clone(); 4057 invalid_config.disks = Some(vec![DiskConfig { 4058 path: None, 4059 vhost_user: true, 4060 vhost_socket: Some("/path/to/sock".to_owned()), 4061 ..disk_fixture() 4062 }]); 4063 assert_eq!( 4064 invalid_config.validate(), 4065 Err(ValidationError::VhostUserRequiresSharedMemory) 4066 ); 4067 4068 let mut still_valid_config = valid_config.clone(); 4069 still_valid_config.disks = Some(vec![DiskConfig { 4070 path: None, 4071 vhost_user: true, 4072 vhost_socket: Some("/path/to/sock".to_owned()), 4073 ..disk_fixture() 4074 }]); 4075 still_valid_config.memory.shared = true; 4076 still_valid_config.validate().unwrap(); 4077 4078 let mut invalid_config = valid_config.clone(); 4079 invalid_config.net = Some(vec![NetConfig { 4080 vhost_user: true, 4081 ..net_fixture() 4082 }]); 4083 assert_eq!( 4084 invalid_config.validate(), 4085 Err(ValidationError::VhostUserRequiresSharedMemory) 4086 ); 4087 4088 let mut still_valid_config = valid_config.clone(); 4089 still_valid_config.net = Some(vec![NetConfig { 4090 vhost_user: true, 4091 vhost_socket: Some("/path/to/sock".to_owned()), 4092 ..net_fixture() 4093 }]); 4094 still_valid_config.memory.shared = true; 4095 still_valid_config.validate().unwrap(); 4096 4097 let mut invalid_config = valid_config.clone(); 4098 invalid_config.net = Some(vec![NetConfig { 4099 fds: Some(vec![0]), 4100 ..net_fixture() 4101 }]); 4102 assert_eq!( 4103 invalid_config.validate(), 4104 Err(ValidationError::VnetReservedFd) 4105 ); 4106 4107 let mut invalid_config = valid_config.clone(); 4108 invalid_config.net = Some(vec![NetConfig { 4109 offload_csum: false, 4110 ..net_fixture() 4111 }]); 4112 assert_eq!( 4113 invalid_config.validate(), 4114 Err(ValidationError::NoHardwareChecksumOffload) 4115 ); 4116 4117 let mut invalid_config = valid_config.clone(); 4118 invalid_config.fs = Some(vec![fs_fixture()]); 4119 assert_eq!( 4120 invalid_config.validate(), 4121 Err(ValidationError::VhostUserRequiresSharedMemory) 4122 ); 4123 4124 let mut still_valid_config = valid_config.clone(); 4125 still_valid_config.memory.shared = true; 4126 still_valid_config.validate().unwrap(); 4127 4128 let mut still_valid_config = valid_config.clone(); 4129 still_valid_config.memory.hugepages = true; 4130 still_valid_config.validate().unwrap(); 4131 4132 let mut still_valid_config = valid_config.clone(); 4133 still_valid_config.memory.hugepages = true; 4134 still_valid_config.memory.hugepage_size = Some(2 << 20); 4135 still_valid_config.validate().unwrap(); 4136 4137 let mut invalid_config = valid_config.clone(); 4138 invalid_config.memory.hugepages = false; 4139 invalid_config.memory.hugepage_size = Some(2 << 20); 4140 assert_eq!( 4141 invalid_config.validate(), 4142 Err(ValidationError::HugePageSizeWithoutHugePages) 4143 ); 4144 4145 let mut invalid_config = valid_config.clone(); 4146 invalid_config.memory.hugepages = true; 4147 invalid_config.memory.hugepage_size = Some(3 << 20); 4148 assert_eq!( 4149 invalid_config.validate(), 4150 Err(ValidationError::InvalidHugePageSize(3 << 20)) 4151 ); 4152 4153 let mut still_valid_config = valid_config.clone(); 4154 still_valid_config.platform = Some(platform_fixture()); 4155 still_valid_config.validate().unwrap(); 4156 4157 let mut invalid_config = valid_config.clone(); 4158 invalid_config.platform = Some(PlatformConfig { 4159 num_pci_segments: MAX_NUM_PCI_SEGMENTS + 1, 4160 ..platform_fixture() 4161 }); 4162 assert_eq!( 4163 invalid_config.validate(), 4164 Err(ValidationError::InvalidNumPciSegments( 4165 MAX_NUM_PCI_SEGMENTS + 1 4166 )) 4167 ); 4168 4169 let mut still_valid_config = valid_config.clone(); 4170 still_valid_config.platform = Some(PlatformConfig { 4171 iommu_segments: Some(vec![1, 2, 3]), 4172 ..platform_fixture() 4173 }); 4174 still_valid_config.validate().unwrap(); 4175 4176 let mut invalid_config = valid_config.clone(); 4177 invalid_config.platform = Some(PlatformConfig { 4178 iommu_segments: Some(vec![MAX_NUM_PCI_SEGMENTS + 1, MAX_NUM_PCI_SEGMENTS + 2]), 4179 ..platform_fixture() 4180 }); 4181 assert_eq!( 4182 invalid_config.validate(), 4183 Err(ValidationError::InvalidPciSegment(MAX_NUM_PCI_SEGMENTS + 1)) 4184 ); 4185 4186 let mut invalid_config = valid_config.clone(); 4187 invalid_config.platform = Some(PlatformConfig { 4188 iommu_address_width_bits: MAX_IOMMU_ADDRESS_WIDTH_BITS + 1, 4189 ..platform_fixture() 4190 }); 4191 assert_eq!( 4192 invalid_config.validate(), 4193 Err(ValidationError::InvalidIommuAddressWidthBits( 4194 MAX_IOMMU_ADDRESS_WIDTH_BITS + 1 4195 )) 4196 ); 4197 4198 let mut still_valid_config = valid_config.clone(); 4199 still_valid_config.platform = Some(PlatformConfig { 4200 iommu_segments: Some(vec![1, 2, 3]), 4201 ..platform_fixture() 4202 }); 4203 still_valid_config.disks = Some(vec![DiskConfig { 4204 iommu: true, 4205 pci_segment: 1, 4206 ..disk_fixture() 4207 }]); 4208 still_valid_config.validate().unwrap(); 4209 4210 let mut still_valid_config = valid_config.clone(); 4211 still_valid_config.platform = Some(PlatformConfig { 4212 iommu_segments: Some(vec![1, 2, 3]), 4213 ..platform_fixture() 4214 }); 4215 still_valid_config.net = Some(vec![NetConfig { 4216 iommu: true, 4217 pci_segment: 1, 4218 ..net_fixture() 4219 }]); 4220 still_valid_config.validate().unwrap(); 4221 4222 let mut still_valid_config = valid_config.clone(); 4223 still_valid_config.platform = Some(PlatformConfig { 4224 iommu_segments: Some(vec![1, 2, 3]), 4225 ..platform_fixture() 4226 }); 4227 still_valid_config.pmem = Some(vec![PmemConfig { 4228 iommu: true, 4229 pci_segment: 1, 4230 ..pmem_fixture() 4231 }]); 4232 still_valid_config.validate().unwrap(); 4233 4234 let mut still_valid_config = valid_config.clone(); 4235 still_valid_config.platform = Some(PlatformConfig { 4236 iommu_segments: Some(vec![1, 2, 3]), 4237 ..platform_fixture() 4238 }); 4239 still_valid_config.devices = Some(vec![DeviceConfig { 4240 iommu: true, 4241 pci_segment: 1, 4242 ..device_fixture() 4243 }]); 4244 still_valid_config.validate().unwrap(); 4245 4246 let mut still_valid_config = valid_config.clone(); 4247 still_valid_config.platform = Some(PlatformConfig { 4248 iommu_segments: Some(vec![1, 2, 3]), 4249 ..platform_fixture() 4250 }); 4251 still_valid_config.vsock = Some(VsockConfig { 4252 cid: 3, 4253 socket: PathBuf::new(), 4254 id: None, 4255 iommu: true, 4256 pci_segment: 1, 4257 }); 4258 still_valid_config.validate().unwrap(); 4259 4260 let mut invalid_config = valid_config.clone(); 4261 invalid_config.platform = Some(PlatformConfig { 4262 iommu_segments: Some(vec![1, 2, 3]), 4263 ..platform_fixture() 4264 }); 4265 invalid_config.disks = Some(vec![DiskConfig { 4266 iommu: false, 4267 pci_segment: 1, 4268 ..disk_fixture() 4269 }]); 4270 assert_eq!( 4271 invalid_config.validate(), 4272 Err(ValidationError::OnIommuSegment(1)) 4273 ); 4274 4275 let mut invalid_config = valid_config.clone(); 4276 invalid_config.platform = Some(PlatformConfig { 4277 iommu_segments: Some(vec![1, 2, 3]), 4278 ..platform_fixture() 4279 }); 4280 invalid_config.net = Some(vec![NetConfig { 4281 iommu: false, 4282 pci_segment: 1, 4283 ..net_fixture() 4284 }]); 4285 assert_eq!( 4286 invalid_config.validate(), 4287 Err(ValidationError::OnIommuSegment(1)) 4288 ); 4289 4290 let mut invalid_config = valid_config.clone(); 4291 invalid_config.platform = Some(PlatformConfig { 4292 num_pci_segments: MAX_NUM_PCI_SEGMENTS, 4293 iommu_segments: Some(vec![1, 2, 3]), 4294 ..platform_fixture() 4295 }); 4296 invalid_config.pmem = Some(vec![PmemConfig { 4297 iommu: false, 4298 pci_segment: 1, 4299 ..pmem_fixture() 4300 }]); 4301 assert_eq!( 4302 invalid_config.validate(), 4303 Err(ValidationError::OnIommuSegment(1)) 4304 ); 4305 4306 let mut invalid_config = valid_config.clone(); 4307 invalid_config.platform = Some(PlatformConfig { 4308 num_pci_segments: MAX_NUM_PCI_SEGMENTS, 4309 iommu_segments: Some(vec![1, 2, 3]), 4310 ..platform_fixture() 4311 }); 4312 invalid_config.devices = Some(vec![DeviceConfig { 4313 iommu: false, 4314 pci_segment: 1, 4315 ..device_fixture() 4316 }]); 4317 assert_eq!( 4318 invalid_config.validate(), 4319 Err(ValidationError::OnIommuSegment(1)) 4320 ); 4321 4322 let mut invalid_config = valid_config.clone(); 4323 invalid_config.platform = Some(PlatformConfig { 4324 iommu_segments: Some(vec![1, 2, 3]), 4325 ..platform_fixture() 4326 }); 4327 invalid_config.vsock = Some(VsockConfig { 4328 cid: 3, 4329 socket: PathBuf::new(), 4330 id: None, 4331 iommu: false, 4332 pci_segment: 1, 4333 }); 4334 assert_eq!( 4335 invalid_config.validate(), 4336 Err(ValidationError::OnIommuSegment(1)) 4337 ); 4338 4339 let mut invalid_config = valid_config.clone(); 4340 invalid_config.memory.shared = true; 4341 invalid_config.platform = Some(PlatformConfig { 4342 iommu_segments: Some(vec![1, 2, 3]), 4343 ..platform_fixture() 4344 }); 4345 invalid_config.user_devices = Some(vec![UserDeviceConfig { 4346 pci_segment: 1, 4347 socket: PathBuf::new(), 4348 id: None, 4349 }]); 4350 assert_eq!( 4351 invalid_config.validate(), 4352 Err(ValidationError::IommuNotSupportedOnSegment(1)) 4353 ); 4354 4355 let mut invalid_config = valid_config.clone(); 4356 invalid_config.platform = Some(PlatformConfig { 4357 iommu_segments: Some(vec![1, 2, 3]), 4358 ..platform_fixture() 4359 }); 4360 invalid_config.vdpa = Some(vec![VdpaConfig { 4361 pci_segment: 1, 4362 ..vdpa_fixture() 4363 }]); 4364 assert_eq!( 4365 invalid_config.validate(), 4366 Err(ValidationError::OnIommuSegment(1)) 4367 ); 4368 4369 let mut invalid_config = valid_config.clone(); 4370 invalid_config.memory.shared = true; 4371 invalid_config.platform = Some(PlatformConfig { 4372 iommu_segments: Some(vec![1, 2, 3]), 4373 ..platform_fixture() 4374 }); 4375 invalid_config.fs = Some(vec![FsConfig { 4376 pci_segment: 1, 4377 ..fs_fixture() 4378 }]); 4379 assert_eq!( 4380 invalid_config.validate(), 4381 Err(ValidationError::IommuNotSupportedOnSegment(1)) 4382 ); 4383 4384 let mut invalid_config = valid_config.clone(); 4385 invalid_config.platform = Some(PlatformConfig { 4386 num_pci_segments: 2, 4387 ..platform_fixture() 4388 }); 4389 invalid_config.numa = Some(vec![ 4390 NumaConfig { 4391 guest_numa_id: 0, 4392 pci_segments: Some(vec![1]), 4393 ..numa_fixture() 4394 }, 4395 NumaConfig { 4396 guest_numa_id: 1, 4397 pci_segments: Some(vec![1]), 4398 ..numa_fixture() 4399 }, 4400 ]); 4401 assert_eq!( 4402 invalid_config.validate(), 4403 Err(ValidationError::PciSegmentReused(1, 0, 1)) 4404 ); 4405 4406 let mut invalid_config = valid_config.clone(); 4407 invalid_config.pci_segments = Some(vec![PciSegmentConfig { 4408 pci_segment: 0, 4409 mmio32_aperture_weight: 1, 4410 mmio64_aperture_weight: 0, 4411 }]); 4412 assert_eq!( 4413 invalid_config.validate(), 4414 Err(ValidationError::InvalidPciSegmentApertureWeight(0)) 4415 ); 4416 4417 let mut invalid_config = valid_config.clone(); 4418 invalid_config.pci_segments = Some(vec![PciSegmentConfig { 4419 pci_segment: 0, 4420 mmio32_aperture_weight: 0, 4421 mmio64_aperture_weight: 1, 4422 }]); 4423 assert_eq!( 4424 invalid_config.validate(), 4425 Err(ValidationError::InvalidPciSegmentApertureWeight(0)) 4426 ); 4427 4428 let mut invalid_config = valid_config.clone(); 4429 invalid_config.numa = Some(vec![ 4430 NumaConfig { 4431 guest_numa_id: 0, 4432 ..numa_fixture() 4433 }, 4434 NumaConfig { 4435 guest_numa_id: 1, 4436 pci_segments: Some(vec![0]), 4437 ..numa_fixture() 4438 }, 4439 ]); 4440 assert_eq!( 4441 invalid_config.validate(), 4442 Err(ValidationError::DefaultPciSegmentInvalidNode(1)) 4443 ); 4444 4445 let mut invalid_config = valid_config.clone(); 4446 invalid_config.numa = Some(vec![ 4447 NumaConfig { 4448 guest_numa_id: 0, 4449 pci_segments: Some(vec![0]), 4450 ..numa_fixture() 4451 }, 4452 NumaConfig { 4453 guest_numa_id: 1, 4454 pci_segments: Some(vec![1]), 4455 ..numa_fixture() 4456 }, 4457 ]); 4458 assert_eq!( 4459 invalid_config.validate(), 4460 Err(ValidationError::InvalidPciSegment(1)) 4461 ); 4462 4463 let mut invalid_config = valid_config.clone(); 4464 invalid_config.disks = Some(vec![DiskConfig { 4465 rate_limit_group: Some("foo".into()), 4466 ..disk_fixture() 4467 }]); 4468 assert_eq!( 4469 invalid_config.validate(), 4470 Err(ValidationError::InvalidRateLimiterGroup) 4471 ); 4472 4473 // Test serial length validation 4474 let mut valid_serial_config = valid_config.clone(); 4475 valid_serial_config.disks = Some(vec![DiskConfig { 4476 serial: Some("valid_serial".to_string()), 4477 ..disk_fixture() 4478 }]); 4479 valid_serial_config.validate().unwrap(); 4480 4481 // Test empty string serial (should be valid) 4482 let mut empty_serial_config = valid_config.clone(); 4483 empty_serial_config.disks = Some(vec![DiskConfig { 4484 serial: Some("".to_string()), 4485 ..disk_fixture() 4486 }]); 4487 empty_serial_config.validate().unwrap(); 4488 4489 // Test None serial (should be valid) 4490 let mut none_serial_config = valid_config.clone(); 4491 none_serial_config.disks = Some(vec![DiskConfig { 4492 serial: None, 4493 ..disk_fixture() 4494 }]); 4495 none_serial_config.validate().unwrap(); 4496 4497 // Test maximum length serial (exactly VIRTIO_BLK_ID_BYTES) 4498 let max_serial = "a".repeat(VIRTIO_BLK_ID_BYTES as usize); 4499 let mut max_serial_config = valid_config.clone(); 4500 max_serial_config.disks = Some(vec![DiskConfig { 4501 serial: Some(max_serial), 4502 ..disk_fixture() 4503 }]); 4504 max_serial_config.validate().unwrap(); 4505 4506 // Test serial length exceeding VIRTIO_BLK_ID_BYTES 4507 let long_serial = "a".repeat(VIRTIO_BLK_ID_BYTES as usize + 1); 4508 let mut invalid_serial_config = valid_config.clone(); 4509 invalid_serial_config.disks = Some(vec![DiskConfig { 4510 serial: Some(long_serial.clone()), 4511 ..disk_fixture() 4512 }]); 4513 assert_eq!( 4514 invalid_serial_config.validate(), 4515 Err(ValidationError::InvalidSerialLength( 4516 long_serial.len(), 4517 VIRTIO_BLK_ID_BYTES as usize 4518 )) 4519 ); 4520 4521 let mut still_valid_config = valid_config.clone(); 4522 still_valid_config.devices = Some(vec![ 4523 DeviceConfig { 4524 path: "/device1".into(), 4525 ..device_fixture() 4526 }, 4527 DeviceConfig { 4528 path: "/device2".into(), 4529 ..device_fixture() 4530 }, 4531 ]); 4532 still_valid_config.validate().unwrap(); 4533 4534 let mut invalid_config = valid_config.clone(); 4535 invalid_config.devices = Some(vec![ 4536 DeviceConfig { 4537 path: "/device1".into(), 4538 ..device_fixture() 4539 }, 4540 DeviceConfig { 4541 path: "/device1".into(), 4542 ..device_fixture() 4543 }, 4544 ]); 4545 invalid_config.validate().unwrap_err(); 4546 #[cfg(feature = "sev_snp")] 4547 { 4548 // Payload with empty host data 4549 let mut config_with_no_host_data = valid_config.clone(); 4550 config_with_no_host_data.payload = Some(PayloadConfig { 4551 kernel: Some(PathBuf::from("/path/to/kernel")), 4552 firmware: None, 4553 cmdline: None, 4554 initramfs: None, 4555 #[cfg(feature = "igvm")] 4556 igvm: None, 4557 #[cfg(feature = "sev_snp")] 4558 host_data: Some("".to_string()), 4559 }); 4560 config_with_no_host_data.validate().unwrap_err(); 4561 4562 // Payload with no host data provided 4563 let mut valid_config_with_no_host_data = valid_config.clone(); 4564 valid_config_with_no_host_data.payload = Some(PayloadConfig { 4565 kernel: Some(PathBuf::from("/path/to/kernel")), 4566 firmware: None, 4567 cmdline: None, 4568 initramfs: None, 4569 #[cfg(feature = "igvm")] 4570 igvm: None, 4571 #[cfg(feature = "sev_snp")] 4572 host_data: None, 4573 }); 4574 valid_config_with_no_host_data.validate().unwrap(); 4575 4576 // Payload with invalid host data length i.e less than 64 4577 let mut config_with_invalid_host_data = valid_config.clone(); 4578 config_with_invalid_host_data.payload = Some(PayloadConfig { 4579 kernel: Some(PathBuf::from("/path/to/kernel")), 4580 firmware: None, 4581 cmdline: None, 4582 initramfs: None, 4583 #[cfg(feature = "igvm")] 4584 igvm: None, 4585 #[cfg(feature = "sev_snp")] 4586 host_data: Some( 4587 "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb43118867328".to_string(), 4588 ), 4589 }); 4590 config_with_invalid_host_data.validate().unwrap_err(); 4591 } 4592 4593 let mut still_valid_config = valid_config; 4594 // SAFETY: Safe as the file was just opened 4595 let fd1 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) }; 4596 // SAFETY: Safe as the file was just opened 4597 let fd2 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) }; 4598 // SAFETY: safe as both FDs are valid 4599 unsafe { 4600 still_valid_config.add_preserved_fds(vec![fd1, fd2]); 4601 } 4602 let _still_valid_config = still_valid_config.clone(); 4603 } 4604 #[test] 4605 fn test_landlock_parsing() -> Result<()> { 4606 // should not be empty 4607 LandlockConfig::parse("").unwrap_err(); 4608 // access should not be empty 4609 LandlockConfig::parse("path=/dir/path1").unwrap_err(); 4610 LandlockConfig::parse("path=/dir/path1,access=rwr").unwrap_err(); 4611 assert_eq!( 4612 LandlockConfig::parse("path=/dir/path1,access=rw")?, 4613 LandlockConfig { 4614 path: PathBuf::from("/dir/path1"), 4615 access: "rw".to_string(), 4616 } 4617 ); 4618 Ok(()) 4619 } 4620 } 4621