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