udp: sendmsg/sendto support for unconnected UDP sockets

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.
This commit is contained in:
Red Bear OS
2026-07-09 12:26:03 +03:00
parent d818ce4957
commit 96f241a40b
2 changed files with 80 additions and 6 deletions
+24 -1
View File
@@ -251,6 +251,15 @@ where
flags: usize,
) -> SyscallResult<usize>;
fn handle_sendmsg(
&mut self,
_file: &mut SchemeFile<Self>,
_payload: &[u8],
_flags: usize,
) -> SyscallResult<usize> {
Err(SyscallError::new(syscall::EOPNOTSUPP))
}
fn get_sock_opt(
&self,
_file: &SchemeFile<Self>,
@@ -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::<SocketT>(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 => {
+56 -5
View File
@@ -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<Self::DataT>,
buf: &[u8],
) -> SyscallResult<usize> {
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<Self>,
how: &[u8],
_flags: usize,
) -> SyscallResult<usize> {
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::<usize>();
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))
}
}