Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #643 +/- ##
==========================================
+ Coverage 75.99% 81.38% +5.38%
==========================================
Files 27 31 +4
Lines 4033 4642 +609
==========================================
+ Hits 3065 3778 +713
+ Misses 968 864 -104 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
If I get it right, using |
ff445af to
abe8633
Compare
|
Some bugs were found when it comes to handling BARs: size/mode aren't handled correctly. I'll document progress and fix on BaderSZ#2 |
f4b2793 to
269af79
Compare
130b7fe to
69b95db
Compare
61a154d to
5260dbd
Compare
5260dbd to
512df5e
Compare
|
Blocked by hermit-os/kernel#2326 |
e682d18 to
4ca871c
Compare
8eff073 to
ae9e118
Compare
Co-authored-by: Bader Zaidan <bader.zaidan@rwth-aachen.de>
| let mut uhyve_rw_paths: Vec<PathBuf> = vec![PathBuf::from("/dev/kvm")]; | ||
| let mut uhyve_rw_paths: Vec<PathBuf> = vec![ | ||
| PathBuf::from("/dev/kvm"), | ||
| #[cfg(target_os = "linux")] |
There was a problem hiding this comment.
Unnecessary, Landlock is only available on Linux.
|
|
||
| fn r#continue(&mut self) -> HypervisorResult<VcpuStopReason> { | ||
| loop { | ||
| let virtio_device = || self.peripherals.virtio_device.lock().unwrap(); |
There was a problem hiding this comment.
It definitively looks like a rebasing/merge mistake.
It should probably look like:
let virtio_device = || self.peripherals.virtio_device.map(|vd| vd.lock().unwrap());| } | ||
| match port { | ||
| //TODO: | ||
| // Legacy PCI addressing method |
There was a problem hiding this comment.
Not sure about this: If this is legacy, is this necessary for v2? (I think it's probably independent but could be made not to be that way. It would be best if that were moved into a separate function, similarly to the hypercall handling.)
| } | ||
| VIRTIO_PCI_QUEUE_PFN => { | ||
| virtio_device().write_pfn(&addr, &self.peripherals.mem); | ||
| self.pci_addr = Some(unsafe { *(addr.as_ptr() as *const u32) }); |
There was a problem hiding this comment.
SAFETY comment that explains why this is necessary would be appreciated.
| let err = io::Error::other(format!("{debug:?}")); | ||
| return Err(err.into()); | ||
| } | ||
| VcpuExit::MmioWrite(addr, data) => match addr { |
There was a problem hiding this comment.
Did anything in particular motivate moving this further down?
fogti
left a comment
There was a problem hiding this comment.
It looks good overall, but some decisions like parametrizing VmPeripherals over the entire virtualization backend, or a lack of documentation of large, new APIs feel worthy of improvement.
| config: Some(PathBuf::from("config.txt")), | ||
| #[cfg(feature = "instrument")] | ||
| trace: Some(PathBuf::from(".")), | ||
| net: Some(String::from("tap10")), |
There was a problem hiding this comment.
This format doesn't look like it conforms to the format mentioned in the command line description above. Shouldn't it be "tap:tap10"?
Also, you should probably use Some("tap10".to_string()) instead.
| pub struct KvmVm { | ||
| vm_fd: VmFd, | ||
| peripherals: Arc<VmPeripherals>, | ||
| pub(crate) vm_fd: VmFd, |
There was a problem hiding this comment.
This is a bad idea because this hampers the ability to maintain cross-platform compatibility long-term if platform differences are spread too much across the codebase.
There was a problem hiding this comment.
This could be fixed by "gating" whatever needs to access vm_fd behind a function for VirtualizationBackendInternal, which we intend to do more for gdb cross-platform compatibility. We can stub the function declaration in XhyveCpu with unimplemented/unreachable in the meantime.
|
|
||
| fn r#continue(&mut self) -> HypervisorResult<VcpuStopReason> { | ||
| loop { | ||
| let virtio_device = || self.peripherals.virtio_device.lock().unwrap(); |
There was a problem hiding this comment.
It definitively looks like a rebasing/merge mistake.
It should probably look like:
let virtio_device = || self.peripherals.virtio_device.map(|vd| vd.lock().unwrap());| PciConfigurationAddress(pci_addr & 0x3ff), | ||
| addr, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Why do we not do anything if we can't read? I would at least expect a warn!ing.
| } | ||
| } | ||
|
|
||
| /// Thin Wrapper around `EventFd` to implement `VirtQueueInterrupter` |
There was a problem hiding this comment.
Could you merge the two wrappers into one, that just implements both traits?
| }; | ||
| queue.set_ready(stat); | ||
| // we'll need to set if we're enabling, as queue is_valid will return false | ||
| // the queue is disabled |
| } | ||
|
|
||
| #[cfg(target_os = "linux")] | ||
| pub(crate) fn start_network_threads< |
There was a problem hiding this comment.
Can we ensure this only gets called once the DeviceStatus::DRIVER_OK is set, or what's the intended call order?
I think the intended interaction should be documented.
| } | ||
|
|
||
| #[allow(dead_code)] | ||
| fn reset_interrupt(&mut self) { |
There was a problem hiding this comment.
If this is neither used nor implemented, nor documented, I think this method should just be removed.
There was a problem hiding this comment.
If it's a placeholder for some sort of future work, I'd explain what the intention is/could be in a comment.
| DeviceLow, | ||
| } | ||
|
|
||
| enum ThreadStartMsg { |
There was a problem hiding this comment.
This enum ThreadStartMsg has a weird name. Perhaps use ThreadControlMsg instead?
| pub(crate) struct VmPeripherals<VirtBackend: VirtualizationBackendInternal> { | ||
| pub file_mapping: Mutex<UhyveFileMap>, | ||
| pub mem: MmapMemory, | ||
| pub mem: Arc<MmapMemory>, | ||
| pub(crate) serial: UhyveSerial, | ||
| pub virtio_device: Mutex<VirtioNetPciDevice>, | ||
| pub virtio_device: Option<Mutex<VirtBackend::VirtioNetImpl>>, |
There was a problem hiding this comment.
| pub(crate) struct VmPeripherals<VirtBackend: VirtualizationBackendInternal> { | |
| pub file_mapping: Mutex<UhyveFileMap>, | |
| pub mem: MmapMemory, | |
| pub mem: Arc<MmapMemory>, | |
| pub(crate) serial: UhyveSerial, | |
| pub virtio_device: Mutex<VirtioNetPciDevice>, | |
| pub virtio_device: Option<Mutex<VirtBackend::VirtioNetImpl>>, | |
| pub(crate) struct VmPeripherals<Virtio> { | |
| pub file_mapping: Mutex<UhyveFileMap>, | |
| pub mem: Arc<MmapMemory>, | |
| pub(crate) serial: UhyveSerial, | |
| pub virtio_device: Option<Mutex<Virtio>>, |
This would avoid dragging the entire VirtualizationBackend around everywhere, and make this struct work more similarly to VirtualCPU.
n0toose
left a comment
There was a problem hiding this comment.
I did another pass, took a bit but I hope it's all helpful.
| let fd = OpenOptions::new() | ||
| .read(true) | ||
| .write(true) | ||
| .open("/dev/net/tun")?; |
There was a problem hiding this comment.
I'm not 100% sure about that: Is it possible to turn this into a const? Would be nice for Landlock, as we wouldn't have to split things across the codebase.
| let res = | ||
| unsafe { tun_set_iff(fd.as_raw_fd(), &config_str as *const ifreq as u64).unwrap() }; | ||
|
|
||
| if res == -1 { |
There was a problem hiding this comment.
Are you completely sure that this is correct? If I'm not misunderstanding anything, the function can return more than just -1 (also watch out for the err = register_netdevice(tun->dev)): https://elixir.bootlin.com/linux/v6.19.8/source/drivers/net/tun.c#L2692-L2831
You could check whether the result is negative, I think. Optionally, you could present more detailed errors depending on the error.
Tangentially relevant example:
Lines 240 to 260 in c5d5737
| /// Vendor-specific PCI capability. | ||
| /// Section 4.1.4 virtio v1.2 | ||
| #[derive(IntoBytes, Clone, Copy, Debug, Immutable)] | ||
| #[repr(C)] |
There was a problem hiding this comment.
I'm a bit skeptical about this (together with CfgType's #[repr(u8)]) because of the use of padding and mix of repr's, but I'm not 100% positive on what else to recommend:https://doc.rust-lang.org/nomicon/other-reprs.html
| #[repr(C)] | ||
| pub struct NotifyCap { | ||
| pub cap: PciCap, | ||
| /// Combind with queue_notify_off to derive the Queue Notify address |
| /// All data should be treated as little-endian. | ||
| #[derive(IntoBytes, Clone, Copy, Debug, Immutable)] | ||
| #[repr(C)] | ||
| #[allow(dead_code)] |
|
|
||
| /// Provides the empty but linked datastructures for VirtioPCI. See module level description for the internal memory layout. | ||
| pub fn new() -> Self { | ||
| let mut h: Self = Default::default(); |
There was a problem hiding this comment.
nit: could be initialized as:
Self {
pci_config.hdr.capabilities_ptr = Self::COMMON_CAP_START,
// insert rest here
..Default::default(),
}| warn!("PciConfigurationAddress not at word boundary"); | ||
| } | ||
|
|
||
| if address < IOBASE || address >= IOEND { |
There was a problem hiding this comment.
Given that this function is only is used to display information when panicking (when an MmioRead takes place outside of IOBASE_U64..IOEND_U64), what information will be shown when a panic takes place? I feel like this is method is some sort of a noop, if I'm not missing anything?
There was a problem hiding this comment.
Alternatively: Wouldn't it be best to make such a check part of the impl Add<usize> instead, if we assume that a PciConfigurationAddress should never, under any circumstance, "escape" this range?
It might be a bit redundant, but could prevent future bugs or something like that.
| /// as IO/MMIO writes are otherwise dismissed. | ||
| // pub const IOBASE: u64 = 0xFE000000; | ||
| pub const IOBASE_U64: u64 = 0xFE000000; | ||
| pub const IOBASE: GuestPhysAddr = GuestPhysAddr::new(IOBASE_U64); |
There was a problem hiding this comment.
We have two declarations of pub const IOBASE, one here and one at the end of src/virtio/mod.rs with a different data type (u32) compared to the GuestPhysAddr with an underlying u64.
/// For now, use an address large enough to be outside of kvm_userspace,
/// as IO/MMIO writes are otherwise dismissed.
pub const IOBASE: u32 = 0xFE000000;
const VIRTIO_MSI_NO_VECTOR: u16 = 0xffff;| } | ||
|
|
||
| pub fn guest_address(&self) -> GuestPhysAddr { | ||
| IOBASE + self.0 as u64 |
There was a problem hiding this comment.
This is not really a complaint, but I generally feel a bit uneasy when not using methods for adding addresses, I think we generally do that for a limited amount of cases.
For ASLR, I explicitly used add to signal that implicitly (followed by a checked_sub).
Line 181 in c5d5737
I think that it is worth clarifying that this is only used when initializing new objects, i.e. malicious input from the guest does not come at play.
| if address < IOBASE || address >= IOEND { | ||
| return None; | ||
| } | ||
| Some(Self((address - IOBASE) as u32)) |
There was a problem hiding this comment.
checked_sub? (see review comment above)
| // } | ||
| // pub fn read_lower(&self) -> u32 { | ||
| // self.address as u32 | ||
| // } |
| // The precise timestampe can be important when debugging networking, | ||
| builder.format_timestamp_nanos().try_init().ok(); | ||
|
|
||
| let bin_path = build_hermit_bin("network_test", BuildMode::Debug); |
There was a problem hiding this comment.
Would be nice if this could be changed to invoke run_vm directly (after #1302 is merged), perhaps through another helper. The env_logger_build() in the irrelevant PR might have to be adjusted.
|
Also, the MTU should be read from the interface information, right? |
Yes, it's accessible over the sysfs, but I also think it's completely fine if we get this merged and implement that later. I'd presume that it's "sufficient" enough for now, as long as the value from the Linux default is decoupled (healthy practice or so). |
|
Thank you both for the extensive feedback! 🙂 |
| data.copy_from_slice(&[offs, 0]); | ||
| } | ||
|
|
||
| pub fn write_selected_queue(&mut self, data: &[u8]) { |
There was a problem hiding this comment.
Why is #[inline] used in the functions above but not e.g. here?
This is a rebase and rework of #536 done by @BaderSZ
Original description:
Fixes: #1
Fixes: #1162