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