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