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 smoltcp::socket::tcp::{RecvError, SendError};
use crate::{
events::{IoEvents, Observer},
net::{
@ -34,25 +36,33 @@ impl ConnectedStream {
}
pub fn try_recvfrom(&self, buf: &mut [u8], flags: SendRecvFlags) -> Result<usize> {
let recv_bytes = self
let result = self
.bound_socket
.raw_with(|socket: &mut RawTcpSocket| socket.recv_slice(buf))
.map_err(|_| Error::with_message(Errno::ENOTCONN, "fail to recv packet"))?;
if recv_bytes == 0 {
return_errno_with_message!(Errno::EAGAIN, "try to recv again");
.raw_with(|socket: &mut RawTcpSocket| socket.recv_slice(buf));
match result {
Ok(0) => return_errno_with_message!(Errno::EAGAIN, "the receive buffer is empty"),
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> {
let sent_bytes = self
let result = self
.bound_socket
.raw_with(|socket: &mut RawTcpSocket| socket.send_slice(buf))
.map_err(|_| Error::with_message(Errno::ENOBUFS, "cannot send packet"))?;
if sent_bytes == 0 {
return_errno_with_message!(Errno::EAGAIN, "try to send again");
.raw_with(|socket: &mut RawTcpSocket| socket.send_slice(buf));
match result {
Ok(0) => return_errno_with_message!(Errno::EAGAIN, "the send buffer is full"),
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 {

View File

@ -184,9 +184,16 @@ impl StreamSocket {
fn try_recvfrom(&self, buf: &mut [u8], flags: SendRecvFlags) -> Result<(usize, SocketAddr)> {
let state = self.state.read();
let State::Connected(connected_stream) = state.as_ref() else {
return_errno_with_message!(Errno::EINVAL, "the socket is not connected");
let connected_stream = match state.as_ref() {
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)?;
connected_stream.update_io_events(&self.pollee);
Ok((recv_bytes, connected_stream.remote_endpoint().into()))
@ -195,9 +202,19 @@ impl StreamSocket {
fn try_sendto(&self, buf: &[u8], flags: SendRecvFlags) -> Result<usize> {
let state = self.state.read();
let State::Connected(connected_stream) = state.as_ref() else {
return_errno_with_message!(Errno::EINVAL, "the socket is not connected");
let connected_stream = match state.as_ref() {
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)?;
connected_stream.update_io_events(&self.pollee);
Ok(sent_bytes)
@ -427,9 +444,9 @@ impl Socket for StreamSocket {
) -> Result<usize> {
debug_assert!(flags.is_all_supported());
if remote.is_some() {
return_errno_with_message!(Errno::EINVAL, "tcp socked should not provide remote addr");
}
// According to the Linux man pages, `EISCONN` _may_ be returned when the destination
// 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() {
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);
}
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()