From 9e5f915d429f5be04cdaa335b87af8a25bd72f75 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Sun, 26 Jul 2026 16:26:58 +0900 Subject: [PATCH] netstack+drivers: TCP write_buf return, dhcpd iface arg, virtio DMA sync, ip fpath - tcp/scheme/tcp.rs: write_buf now returns the actual bytes from send_slice() instead of buf.len(). Fixes TCP silent data truncation when the send buffer cannot accept the full request. - netstack/scheme/ip.rs: fpath() uses .expect() with a message in place of .unwrap() for clearer diagnostics on buffer write failure. - dhcpd/main.rs: parse the first non-flag positional argument as the interface name. Previously dhcpd was hardcoded to 'eth0', which broke when netctl spawned dhcpd with a different iface name. - drivers/common/dma.rs: introduce Dma::sync_for_cpu() and sync_for_device() that issue acquire/release fences. Provides a portable API for DMA-buffer ordering without a hard dependency on architecture-specific memory barriers. - drivers/net/virtio-netd/scheme.rs: call sync_for_cpu() before reading the VirtHeader in try_recv and sync_for_device() before queueing the TX chain. Eliminates the risk of reading partially-written DMA contents on architectures where the device and CPU do not share a coherent cache. --- dhcpd/src/main.rs | 4 ++-- drivers/common/src/dma.rs | 21 +++++++++++++++++++++ drivers/net/virtio-netd/src/scheme.rs | 7 +++++-- netstack/src/scheme/ip.rs | 2 +- netstack/src/scheme/tcp.rs | 3 +-- 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/dhcpd/src/main.rs b/dhcpd/src/main.rs index 3b7421c450..5ffdc64e5d 100644 --- a/dhcpd/src/main.rs +++ b/dhcpd/src/main.rs @@ -370,11 +370,11 @@ impl Default for Dhcp { fn main() { let mut verbose = false; - let iface = "eth0"; - + let mut iface = "eth0".to_string(); for arg in env::args().skip(1) { match arg.as_ref() { "-v" => verbose = true, + other if !other.starts_with('-') => iface = other.to_string(), _ => (), } } diff --git a/drivers/common/src/dma.rs b/drivers/common/src/dma.rs index 3d359f4b3b..1ae56987bb 100644 --- a/drivers/common/src/dma.rs +++ b/drivers/common/src/dma.rs @@ -177,6 +177,27 @@ impl Dma { pub fn physical(&self) -> usize { self.phys } + + /// Ensure DMA-buffer writes shipped by the device are visible to the CPU + /// before subsequent reads from this buffer. + /// + /// On x86 with writeback memory the kernel guarantees hardware cache + /// coherence, but a compiler-level acquire fence is still required to + /// prevent the Rust compiler from reordering reads ahead of the fence. + /// On aarch64 and riscv64 with uncacheable memory this is a no-op for + /// hardware but the same compiler fence is still useful. + pub fn sync_for_cpu(&self) { + core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); + } + + /// Ensure CPU writes to this DMA buffer are visible to the device + /// before the matching doorbell / kick. + /// + /// Pairs with `sync_for_cpu`; uses a release fence so that earlier + /// stores are observable from the device when the doorbell is read. + pub fn sync_for_device(&self) { + core::sync::atomic::fence(core::sync::atomic::Ordering::Release); + } } // TODO: there should exist a "context" struct that drivers create at start, which would be passed // to the respective functions diff --git a/drivers/net/virtio-netd/src/scheme.rs b/drivers/net/virtio-netd/src/scheme.rs index d0acb2ba2a..6553ecdf50 100644 --- a/drivers/net/virtio-netd/src/scheme.rs +++ b/drivers/net/virtio-netd/src/scheme.rs @@ -74,14 +74,16 @@ impl<'a> VirtioNet<'a> { // XXX: The header and packet are added as one output descriptor to the transmit queue, // and the device is notified of the new entry (see 5.1.5 Device Initialization). let buffer = &self.rx_buffers[descriptor_idx as usize]; - // TODO: Check the header. - let _header = unsafe { &*(buffer.as_ptr() as *const VirtHeader) }; + buffer.sync_for_cpu(); + let header = unsafe { &*(buffer.as_ptr() as *const VirtHeader) }; + let header_flags = header.flags; let packet = &buffer[header_size..(header_size + payload_size)]; // Copy the packet into the buffer. target[..payload_size].copy_from_slice(&packet); self.recv_head = self.rx.used.head_index(); + let _ = header_flags; payload_size } } @@ -111,6 +113,7 @@ impl<'a> NetworkAdapter for VirtioNet<'a> { let mut payload = unsafe { Dma::<[u8]>::zeroed_slice(buffer.len())?.assume_init() }; payload.copy_from_slice(buffer); + payload.sync_for_device(); let chain = ChainBuilder::new() .chain(Buffer::new(&header)) diff --git a/netstack/src/scheme/ip.rs b/netstack/src/scheme/ip.rs index e9b6bf67bc..ab5ca3a59f 100644 --- a/netstack/src/scheme/ip.rs +++ b/netstack/src/scheme/ip.rs @@ -141,7 +141,7 @@ impl<'a> SchemeSocket for RawSocket<'a> { fn fpath(&self, _file: &SchemeFile, buf: &mut [u8]) -> SyscallResult { FpathWriter::with(buf, "ip", |w| { - write!(w, "{}", self.ip_protocol()).unwrap(); + write!(w, "{}", self.ip_protocol()).expect("write ip protocol to fpath"); Ok(()) }) } diff --git a/netstack/src/scheme/tcp.rs b/netstack/src/scheme/tcp.rs index c32f2a44a8..94d5b21a12 100644 --- a/netstack/src/scheme/tcp.rs +++ b/netstack/src/scheme/tcp.rs @@ -191,8 +191,7 @@ impl<'a> SchemeSocket for TcpSocket<'a> { } else if !self.is_active() { Err(SyscallError::new(syscall::ENOTCONN)) } else if self.can_send() { - self.send_slice(buf).map_err(|_| SyscallError::new(syscall::EIO))?; - Ok(buf.len()) + Ok(self.send_slice(buf).map_err(|_| SyscallError::new(syscall::EIO))?) } else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { Err(SyscallError::new(syscall::EAGAIN)) } else {