From 96f241a40b266b2f271c5b0b7701eb14e66bf548 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Thu, 9 Jul 2026 12:26:03 +0300 Subject: [PATCH] udp: sendmsg/sendto support for unconnected UDP sockets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SchemeSocket trait gains handle_sendmsg(file, payload, flags) with default EOPNOTSUPP. SocketScheme::call_inner now dispatches SocketCall::SendMsg (previously commented out). UDP handle_sendmsg parses the sendmsg buffer format: [name_len: usize][payload_len: usize][msg_controllen: usize] [address: bytes][payload: bytes] Extracts the destination IP:port from the address, sends via smoltcp's send_slice. Enables sendto() on unconnected UDP sockets. Unconnected UDP write_buf now returns EDESTADDRREQ instead of EADDRNOTAVAIL — clearer error for unconnected writes without sendto. TCP and ICMP use the default EOPNOTSUPP implementation (they are connection-oriented or use write_buf). All 31 tests pass. --- netstack/src/scheme/socket.rs | 25 +++++++++++++- netstack/src/scheme/udp.rs | 61 ++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/netstack/src/scheme/socket.rs b/netstack/src/scheme/socket.rs index dfdd3372b8..336f3d32b1 100644 --- a/netstack/src/scheme/socket.rs +++ b/netstack/src/scheme/socket.rs @@ -251,6 +251,15 @@ where flags: usize, ) -> SyscallResult; + fn handle_sendmsg( + &mut self, + _file: &mut SchemeFile, + _payload: &[u8], + _flags: usize, + ) -> SyscallResult { + Err(SyscallError::new(syscall::EOPNOTSUPP)) + } + fn get_sock_opt( &self, _file: &SchemeFile, @@ -506,7 +515,21 @@ where Handle::SchemeRoot => Err(SyscallError::new(syscall::EBADF)), } } - // SocketCall::SendMsg => self.handle_sendmsg(id, payload, ctx), + SocketCall::SendMsg => { + let flags = metadata[1] as usize; + let handle = self.handles.get_mut(fd)?; + + match *handle { + Handle::File(ref mut file) => { + let mut socket_set = self.socket_set.borrow_mut(); + let socket = socket_set.get_mut::(file.socket_handle()); + + SocketT::handle_sendmsg(socket, file, payload, flags) + } + Handle::Null(_) => Err(SyscallError::new(syscall::EINVAL)), + Handle::SchemeRoot => Err(SyscallError::new(syscall::EBADF)), + } + } // SocketCall::Unbind => self.handle_unbind(id), // SocketCall::GetToken => self.handle_get_token(id, payload), SocketCall::GetPeerName => { diff --git a/netstack/src/scheme/udp.rs b/netstack/src/scheme/udp.rs index 36e7b32074..085cf66cb4 100644 --- a/netstack/src/scheme/udp.rs +++ b/netstack/src/scheme/udp.rs @@ -3,7 +3,7 @@ use smoltcp::iface::SocketHandle; use smoltcp::socket::udp::{ PacketBuffer as UdpSocketBuffer, PacketMetadata as UdpPacketMetadata, Socket as UdpSocket, }; -use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; +use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint}; use std::str; use syscall; use syscall::{Error as SyscallError, Result as SyscallResult}; @@ -199,12 +199,13 @@ impl<'a> SchemeSocket for UdpSocket<'a> { file: &mut SocketFile, buf: &[u8], ) -> SyscallResult { - if !file.data.is_specified() { - return Err(SyscallError::new(syscall::EADDRNOTAVAIL)); - } if !file.write_enabled { return Err(SyscallError::new(syscall::EPIPE)); } + // Unconnected sockets can only send via sendmsg/sendto. + if !file.data.is_specified() { + return Err(SyscallError::new(syscall::EDESTADDRREQ)); + } if self.can_send() { let endpoint = file.data; let endpoint = IpEndpoint::new( @@ -218,7 +219,57 @@ impl<'a> SchemeSocket for UdpSocket<'a> { } else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { Err(SyscallError::new(syscall::EAGAIN)) } else { - Err(SyscallError::new(syscall::EWOULDBLOCK)) // internally scheduled to re-read + Err(SyscallError::new(syscall::EWOULDBLOCK)) + } + } + + fn handle_sendmsg( + &mut self, + file: &mut SchemeFile, + how: &[u8], + _flags: usize, + ) -> SyscallResult { + let socket_file = match file { + SchemeFile::Socket(ref sock_f) => sock_f, + _ => return Err(SyscallError::new(syscall::EBADF)), + }; + if !socket_file.write_enabled { + return Err(SyscallError::new(syscall::EPIPE)); + } + if self.can_send() { + let usize_length = core::mem::size_of::(); + if how.len() < 3 * usize_length { + return Err(SyscallError::new(syscall::EINVAL)); + } + let name_len = usize::from_le_bytes( + how[0..usize_length].try_into().map_err(|_| SyscallError::new(syscall::EINVAL))?, + ); + let payload_len = usize::from_le_bytes( + how[usize_length..2 * usize_length] + .try_into() + .map_err(|_| SyscallError::new(syscall::EINVAL))?, + ); + // msg_controllen at [2*usize_length..3*usize_length], not used. + let addr_start = 3 * usize_length; + let payload_start = addr_start + name_len; + if payload_start + payload_len > how.len() { + return Err(SyscallError::new(syscall::EINVAL)); + } + // Parse destination from the sendmsg address. + let addr_str = core::str::from_utf8(&how[addr_start..addr_start + name_len]) + .map_err(|_| SyscallError::new(syscall::EINVAL))?; + let dest = parse_endpoint(addr_str); + let endpoint = IpEndpoint::new( + dest.addr.unwrap_or(IpAddress::v4(0, 0, 0, 0)), + dest.port, + ); + self.send_slice(&how[payload_start..payload_start + payload_len], endpoint) + .map_err(|_| SyscallError::new(syscall::EIO))?; + Ok(payload_len) + } else if socket_file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + Err(SyscallError::new(syscall::EAGAIN)) + } else { + Err(SyscallError::new(syscall::EWOULDBLOCK)) } }