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