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