tcp: override SchemeSocket::call to implement SocketCall::SendMsg + MSG_NOSIGNAL
Adds a call() override for TcpSocket that handles SocketCall::SendMsg. The default trait impl returned EOPNOTSUPP, which meant TCP sendmsg() with MSG_NOSIGNAL would fail even for perfectly valid traffic. TCP send() returns EPIPE on a peer-closed connection, which triggers SIGPIPE. MSG_NOSIGNAL emulation: 1. Block SIGPIPE for the duration of send_slice() 2. If send returned < 0 (likely EPIPE), dequeue any newly-pending SIGPIPE via sigtimedwait(0) before restoring the old mask 3. Restore the caller's signal mask 4. If pthread_sigmask failed, fall through to plain send
This commit is contained in:
@@ -44,6 +44,63 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
|
||||
self.can_send()
|
||||
}
|
||||
|
||||
fn call(
|
||||
&mut self,
|
||||
_file: &mut SchemeFile<Self>,
|
||||
payload: &mut [u8],
|
||||
metadata: &[u64],
|
||||
_ctx: &CallerCtx,
|
||||
) -> SyscallResult<usize> {
|
||||
match SocketCall::from(metadata[0]) {
|
||||
SocketCall::SendMsg => {
|
||||
let flags = metadata[1] as u16;
|
||||
// smoltcp's TCP socket has no MSG_NOSIGNAL awareness: a
|
||||
// peer-closed connection returns EPIPE on the send path
|
||||
// and the kernel delivers SIGPIPE to the calling thread.
|
||||
// We emulate POSIX MSG_NOSIGNAL by suppressing SIGPIPE
|
||||
// for the duration of this send only. The block is
|
||||
// restored before returning, so any newly-pending SIGPIPE
|
||||
// (delivered between the signal mask save and restore) is
|
||||
// dequeued via sigtimedwait(..., NULL) before we resume
|
||||
// the caller's signal mask.
|
||||
if flags & libc::MSG_NOSIGNAL as u16 != 0 {
|
||||
let mut old_mask: libc::sigset_t = unsafe { std::mem::zeroed() };
|
||||
let mut block_mask: libc::sigset_t = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::sigemptyset(&mut block_mask) };
|
||||
unsafe { libc::sigaddset(&mut block_mask, libc::SIGPIPE) };
|
||||
if unsafe { libc::pthread_sigmask(libc::SIG_BLOCK, &block_mask, &mut old_mask) } == 0 {
|
||||
let written = self.send_slice(payload);
|
||||
if written < 0 {
|
||||
self.close();
|
||||
let mut ts: libc::timespec =
|
||||
unsafe { std::mem::zeroed() };
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 0;
|
||||
unsafe {
|
||||
let _ = libc::sigtimedwait(
|
||||
&block_mask,
|
||||
std::ptr::null_mut(),
|
||||
&ts,
|
||||
);
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
libc::pthread_sigmask(libc::SIG_SETMASK, &old_mask, std::ptr::null_mut());
|
||||
}
|
||||
Ok(written.max(0) as usize)
|
||||
} else {
|
||||
// pthread_sigmask failed; fall through to plain
|
||||
// send without suppression.
|
||||
Ok(self.send_slice(payload).max(0) as usize)
|
||||
}
|
||||
} else {
|
||||
Ok(self.send_slice(payload).max(0) as usize)
|
||||
}
|
||||
}
|
||||
_ => Err(SyscallError::new(syscall::EOPNOTSUPP)),
|
||||
}
|
||||
}
|
||||
|
||||
fn can_recv(&mut self, _data: &Self::DataT) -> bool {
|
||||
smoltcp::socket::tcp::Socket::can_recv(self)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user