Fix error codes in TCP sendto and recvfrom

This commit is contained in:
Ruihan Li
2024-01-08 23:32:04 +08:00
committed by Tate, Hongliang Tian
parent 51080c1925
commit 00cfdf86c8
3 changed files with 91 additions and 19 deletions

View File

@ -2,6 +2,8 @@
use alloc::sync::Weak; use alloc::sync::Weak;
use smoltcp::socket::tcp::{RecvError, SendError};
use crate::{ use crate::{
events::{IoEvents, Observer}, events::{IoEvents, Observer},
net::{ net::{
@ -34,25 +36,33 @@ impl ConnectedStream {
} }
pub fn try_recvfrom(&self, buf: &mut [u8], flags: SendRecvFlags) -> Result<usize> { pub fn try_recvfrom(&self, buf: &mut [u8], flags: SendRecvFlags) -> Result<usize> {
let recv_bytes = self let result = self
.bound_socket .bound_socket
.raw_with(|socket: &mut RawTcpSocket| socket.recv_slice(buf)) .raw_with(|socket: &mut RawTcpSocket| socket.recv_slice(buf));
.map_err(|_| Error::with_message(Errno::ENOTCONN, "fail to recv packet"))?; match result {
if recv_bytes == 0 { Ok(0) => return_errno_with_message!(Errno::EAGAIN, "the receive buffer is empty"),
return_errno_with_message!(Errno::EAGAIN, "try to recv again"); Ok(recv_bytes) => Ok(recv_bytes),
Err(RecvError::Finished) => Ok(0),
Err(RecvError::InvalidState) => {
return_errno_with_message!(Errno::ECONNRESET, "the connection is reset")
}
} }
Ok(recv_bytes)
} }
pub fn try_sendto(&self, buf: &[u8], flags: SendRecvFlags) -> Result<usize> { pub fn try_sendto(&self, buf: &[u8], flags: SendRecvFlags) -> Result<usize> {
let sent_bytes = self let result = self
.bound_socket .bound_socket
.raw_with(|socket: &mut RawTcpSocket| socket.send_slice(buf)) .raw_with(|socket: &mut RawTcpSocket| socket.send_slice(buf));
.map_err(|_| Error::with_message(Errno::ENOBUFS, "cannot send packet"))?; match result {
if sent_bytes == 0 { Ok(0) => return_errno_with_message!(Errno::EAGAIN, "the send buffer is full"),
return_errno_with_message!(Errno::EAGAIN, "try to send again"); Ok(sent_bytes) => Ok(sent_bytes),
Err(SendError::InvalidState) => {
// FIXME: `EPIPE` is another possibility, which means that the socket is shut down
// for writing. In that case, we should also trigger a `SIGPIPE` if `MSG_NOSIGNAL`
// is not specified.
return_errno_with_message!(Errno::ECONNRESET, "the connection is reset");
}
} }
Ok(sent_bytes)
} }
pub fn local_endpoint(&self) -> IpEndpoint { pub fn local_endpoint(&self) -> IpEndpoint {

View File

@ -184,9 +184,16 @@ impl StreamSocket {
fn try_recvfrom(&self, buf: &mut [u8], flags: SendRecvFlags) -> Result<(usize, SocketAddr)> { fn try_recvfrom(&self, buf: &mut [u8], flags: SendRecvFlags) -> Result<(usize, SocketAddr)> {
let state = self.state.read(); let state = self.state.read();
let State::Connected(connected_stream) = state.as_ref() else { let connected_stream = match state.as_ref() {
return_errno_with_message!(Errno::EINVAL, "the socket is not connected"); State::Connected(connected_stream) => connected_stream,
State::Init(_) | State::Listen(_) => {
return_errno_with_message!(Errno::ENOTCONN, "the socket is not connected")
}
State::Connecting(_) => {
return_errno_with_message!(Errno::EAGAIN, "the socket is connecting")
}
}; };
let recv_bytes = connected_stream.try_recvfrom(buf, flags)?; let recv_bytes = connected_stream.try_recvfrom(buf, flags)?;
connected_stream.update_io_events(&self.pollee); connected_stream.update_io_events(&self.pollee);
Ok((recv_bytes, connected_stream.remote_endpoint().into())) Ok((recv_bytes, connected_stream.remote_endpoint().into()))
@ -195,9 +202,19 @@ impl StreamSocket {
fn try_sendto(&self, buf: &[u8], flags: SendRecvFlags) -> Result<usize> { fn try_sendto(&self, buf: &[u8], flags: SendRecvFlags) -> Result<usize> {
let state = self.state.read(); let state = self.state.read();
let State::Connected(connected_stream) = state.as_ref() else { let connected_stream = match state.as_ref() {
return_errno_with_message!(Errno::EINVAL, "the socket is not connected"); State::Connected(connected_stream) => connected_stream,
State::Init(_) | State::Listen(_) => {
// TODO: Trigger `SIGPIPE` if `MSG_NOSIGNAL` is not specified
return_errno_with_message!(Errno::EPIPE, "the socket is not connected");
}
State::Connecting(_) => {
// FIXME: Linux indeed allows data to be buffered at this point. Can we do
// something similar?
return_errno_with_message!(Errno::EAGAIN, "the socket is connecting")
}
}; };
let sent_bytes = connected_stream.try_sendto(buf, flags)?; let sent_bytes = connected_stream.try_sendto(buf, flags)?;
connected_stream.update_io_events(&self.pollee); connected_stream.update_io_events(&self.pollee);
Ok(sent_bytes) Ok(sent_bytes)
@ -427,9 +444,9 @@ impl Socket for StreamSocket {
) -> Result<usize> { ) -> Result<usize> {
debug_assert!(flags.is_all_supported()); debug_assert!(flags.is_all_supported());
if remote.is_some() { // According to the Linux man pages, `EISCONN` _may_ be returned when the destination
return_errno_with_message!(Errno::EINVAL, "tcp socked should not provide remote addr"); // address is specified for a connection-mode socket. In practice, the destination address
} // is simply ignored. We follow the same behavior as the Linux implementation to ignore it.
let sent_bytes = if self.is_nonblocking() { let sent_bytes = if self.is_nonblocking() {
self.try_sendto(buf, flags)? self.try_sendto(buf, flags)?

View File

@ -136,3 +136,48 @@ FN_TEST(peername_is_peer_sockname)
addrlen == sizeof(saddr) && saddr.sin_port == em_port); addrlen == sizeof(saddr) && saddr.sin_port == em_port);
} }
END_TEST() END_TEST()
FN_TEST(send)
{
char buf[1] = { 'z' };
TEST_ERRNO(send(sk_unbound, buf, 1, 0), EPIPE);
TEST_ERRNO(send(sk_bound, buf, 1, 0), EPIPE);
TEST_ERRNO(send(sk_listen, buf, 1, 0), EPIPE);
}
END_TEST()
FN_TEST(recv)
{
char buf[1] = { 'z' };
TEST_ERRNO(recv(sk_unbound, buf, 1, 0), ENOTCONN);
TEST_ERRNO(recv(sk_bound, buf, 1, 0), ENOTCONN);
TEST_ERRNO(recv(sk_listen, buf, 1, 0), ENOTCONN);
}
END_TEST()
FN_TEST(send_and_recv)
{
char buf[1];
buf[0] = 'a';
TEST_RES(send(sk_connected, buf, 1, 0), _ret == 1);
buf[0] = 'b';
sk_addr.sin_port = 0xbeef;
TEST_RES(sendto(sk_accepted, buf, 1, 0, (struct sockaddr *)&sk_addr,
sizeof(sk_addr)),
_ret == 1);
TEST_RES(recv(sk_accepted, buf, 1, 0), buf[0] == 'a');
TEST_RES(recv(sk_connected, buf, 1, 0), buf[0] == 'b');
TEST_ERRNO(recv(sk_connected, buf, 1, 0), EAGAIN);
}
END_TEST()