diff --git a/lib/virtual-net/src/lib.rs b/lib/virtual-net/src/lib.rs index 410a4c36020d..769e6e82ffa6 100644 --- a/lib/virtual-net/src/lib.rs +++ b/lib/virtual-net/src/lib.rs @@ -813,6 +813,7 @@ pub struct UnsupportedVirtualNetworking {} #[async_trait::async_trait] impl VirtualNetworking for UnsupportedVirtualNetworking {} +#[non_exhaustive] #[derive(Error, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum NetworkError { /// The handle given was not usable @@ -888,6 +889,12 @@ pub enum NetworkError { /// The operation is not supported. #[error("unsupported")] Unsupported, + /// The network containing the remote host is not reachable. + #[error("network unreachable")] + NetworkUnreachable, + /// The remote host is not reachable. + #[error("host unreachable")] + HostUnreachable, /// Some other unhandled error. If you see this, it's probably a bug. #[error("unknown error found")] UnknownError, @@ -917,6 +924,8 @@ pub fn io_err_into_net_error(net_error: std::io::Error) -> NetworkError { ErrorKind::TimedOut => NetworkError::TimedOut, ErrorKind::UnexpectedEof => NetworkError::UnexpectedEof, ErrorKind::WouldBlock => NetworkError::WouldBlock, + ErrorKind::NetworkUnreachable => NetworkError::NetworkUnreachable, + ErrorKind::HostUnreachable => NetworkError::HostUnreachable, ErrorKind::WriteZero => NetworkError::WriteZero, ErrorKind::Unsupported => NetworkError::Unsupported, @@ -938,6 +947,8 @@ pub fn io_err_into_net_error(net_error: std::io::Error) -> NetworkError { libc::EINVAL => NetworkError::InvalidInput, libc::EMSGSIZE => NetworkError::MessageSize, libc::EPIPE => NetworkError::BrokenPipe, + libc::ENETUNREACH => NetworkError::NetworkUnreachable, + libc::EHOSTUNREACH => NetworkError::HostUnreachable, err => { tracing::trace!("unknown os error {}", err); NetworkError::UnknownError @@ -974,6 +985,8 @@ pub fn net_error_into_io_err(net_error: NetworkError) -> std::io::Error { NetworkError::TimedOut => ErrorKind::TimedOut.into(), NetworkError::UnexpectedEof => ErrorKind::UnexpectedEof.into(), NetworkError::WouldBlock => ErrorKind::WouldBlock.into(), + NetworkError::NetworkUnreachable => ErrorKind::NetworkUnreachable.into(), + NetworkError::HostUnreachable => ErrorKind::HostUnreachable.into(), NetworkError::WriteZero => ErrorKind::WriteZero.into(), NetworkError::Unsupported => ErrorKind::Unsupported.into(), NetworkError::UnknownError => ErrorKind::BrokenPipe.into(), diff --git a/lib/wasix/src/net/mod.rs b/lib/wasix/src/net/mod.rs index 6f8c79c2e8f7..f65029d5bbe1 100644 --- a/lib/wasix/src/net/mod.rs +++ b/lib/wasix/src/net/mod.rs @@ -396,10 +396,13 @@ pub fn net_error_into_wasi_err(net_error: NetworkError) -> Errno { NetworkError::TimedOut => Errno::Timedout, NetworkError::UnexpectedEof => Errno::Proto, NetworkError::WouldBlock => Errno::Again, + NetworkError::NetworkUnreachable => Errno::Netunreach, + NetworkError::HostUnreachable => Errno::Hostunreach, NetworkError::WriteZero => Errno::Nospc, NetworkError::TooManyOpenFiles => Errno::Mfile, NetworkError::InsufficientMemory => Errno::Nomem, NetworkError::Unsupported => Errno::Notsup, NetworkError::UnknownError => Errno::Io, + _ => Errno::Io, } } diff --git a/lib/wasix/src/syscalls/wasix/sock_connect.rs b/lib/wasix/src/syscalls/wasix/sock_connect.rs index 4e879b3728f4..cd4837f3b671 100644 --- a/lib/wasix/src/syscalls/wasix/sock_connect.rs +++ b/lib/wasix/src/syscalls/wasix/sock_connect.rs @@ -50,10 +50,13 @@ pub fn sock_connect( fn nonblocking_connect_result(status: crate::net::socket::WasiSocketStatus) -> Result<(), Errno> { match status { - crate::net::socket::WasiSocketStatus::Opening => Err(Errno::Inprogress), + // This is called immediately after initiating a nonblocking connect. + // A failure observed here is asynchronous and must remain available + // via SO_ERROR, so report EINPROGRESS rather than consume it. + crate::net::socket::WasiSocketStatus::Opening + | crate::net::socket::WasiSocketStatus::Closed + | crate::net::socket::WasiSocketStatus::Failed => Err(Errno::Inprogress), crate::net::socket::WasiSocketStatus::Opened => Ok(()), - crate::net::socket::WasiSocketStatus::Closed - | crate::net::socket::WasiSocketStatus::Failed => Err(Errno::Notconn), } } @@ -116,11 +119,11 @@ mod tests { assert_eq!(nonblocking_connect_result(WasiSocketStatus::Opened), Ok(())); assert_eq!( nonblocking_connect_result(WasiSocketStatus::Failed), - Err(Errno::Notconn) + Err(Errno::Inprogress) ); assert_eq!( nonblocking_connect_result(WasiSocketStatus::Closed), - Err(Errno::Notconn) + Err(Errno::Inprogress) ); } } diff --git a/lib/wasix/tests/wasm_tests/socket/get-last-socket-error/main.c b/lib/wasix/tests/wasm_tests/socket/get-last-socket-error/main.c index f9d05851873f..33e9a738649f 100644 --- a/lib/wasix/tests/wasm_tests/socket/get-last-socket-error/main.c +++ b/lib/wasix/tests/wasm_tests/socket/get-last-socket-error/main.c @@ -1,5 +1,4 @@ //#MinimalLibc: v2026-06-18.1 -//#ExpectedStdout: SO_ERROR: Connection refused, Success #include #include @@ -11,6 +10,10 @@ int main(void) { int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (fd < 0) { + perror("socket"); + return 1; + } struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; @@ -19,23 +22,42 @@ int main(void) { errno = 0; int connect_res = connect(fd, (struct sockaddr*)&addr, sizeof(addr)); - if (connect_res == 0 || - (errno != EINPROGRESS && errno != EWOULDBLOCK && errno != ENOTCONN)) { - fprintf(stderr, "connect did not enter async failure path: %s\n", - strerror(errno)); + /* Even if the host rejects the connection immediately, a nonblocking + connect reports EINPROGRESS and leaves the failure for SO_ERROR. */ + if (connect_res != -1 || errno != EINPROGRESS) { + fprintf(stderr, + "connect returned %d with errno %d (%s), expected -1 with " + "EINPROGRESS\n", + connect_res, errno, strerror(errno)); + close(fd); return 1; } struct pollfd pfd = {.fd = fd, .events = POLLOUT}; - poll(&pfd, 1, 1000); + int poll_res = poll(&pfd, 1, 1000); + if (poll_res != 1) { + if (poll_res < 0) { + perror("poll"); + } else { + fprintf(stderr, "poll timed out waiting for the connect result\n"); + } + close(fd); + return 1; + } int err = 0; socklen_t errlen = sizeof(err); int res = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen); - if (res) { + if (res < 0) { + perror("getsockopt(SO_ERROR)"); + close(fd); + return 1; + } + if (err != ECONNREFUSED) { + fprintf(stderr, "SO_ERROR returned %d (%s), expected ECONNREFUSED\n", err, + strerror(err)); close(fd); - fprintf(stderr, "Cannot get socket error\n"); return 1; } @@ -43,14 +65,18 @@ int main(void) { socklen_t errlen2 = sizeof(err); res = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err2, &errlen2); - if (res) { + if (res < 0) { + perror("getsockopt(SO_ERROR) after clearing"); + close(fd); + return 1; + } + if (err2 != 0) { + fprintf(stderr, "second SO_ERROR returned %d (%s), expected success\n", + err2, strerror(err2)); close(fd); - fprintf(stderr, "Cannot get socket error (2)\n"); return 1; } close(fd); - printf("SO_ERROR: %s, %s\n", strerror(err), strerror(err2)); - return 0; } diff --git a/lib/wasix/tests/wasm_tests/socket/nonblocking-connect-network-unreachable/main.c b/lib/wasix/tests/wasm_tests/socket/nonblocking-connect-network-unreachable/main.c new file mode 100644 index 000000000000..664ffbc94144 --- /dev/null +++ b/lib/wasix/tests/wasm_tests/socket/nonblocking-connect-network-unreachable/main.c @@ -0,0 +1,35 @@ +//#MinimalLibc: v2026-06-18.1 + +#include +#include +#include +#include +#include +#include + +int main(void) { + int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (fd < 0) { + perror("socket"); + return 1; + } + + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_port = htons(9); + addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); + + errno = 0; + int connect_res = connect(fd, (struct sockaddr*)&addr, sizeof(addr)); + if (connect_res != -1 || errno != ENETUNREACH) { + fprintf(stderr, + "connect returned %d with errno %d (%s), expected -1 with " + "ENETUNREACH\n", + connect_res, errno, strerror(errno)); + close(fd); + return 1; + } + + close(fd); + return 0; +}