Fix WASIX socket connect to detect connection failures#6059
Fix WASIX socket connect to detect connection failures#6059
Conversation
Handle POLLERR in poll_write_ready to detect failed TCP connections. When POLLERR is detected, use getsockopt with SO_ERROR to retrieve the actual socket error and propagate it properly. This fixes the issue where socket connections always appeared successful even when they failed (e.g., connection refused). Co-authored-by: zebreus <1557253+zebreus@users.noreply.github.com>
- Use explicit cast to libc::c_void for better clarity - Properly handle getsockopt failure case with last_os_error - Handle edge case where POLLERR is set but SO_ERROR is 0 - Improve error handling robustness with better fallback logic Co-authored-by: zebreus <1557253+zebreus@users.noreply.github.com>
|
@copilot check again |
|
Closing as the PR is not ready. |
|
@theduke wanted to take a look at this PR |
|
@copilot fix CI failures (lints) |
There was a problem hiding this comment.
Pull request overview
Fixes WASIX TCP connect reporting success even when the underlying connection fails (e.g., ECONNREFUSED) by improving error detection in write-readiness polling.
Changes:
- Extend
LocalTcpStream::poll_write_readypolling mask to includePOLLERR. - When
POLLERRis observed, querySO_ERRORviagetsockoptand propagate the resulting error asNetworkError. - Add handling for
getsockoptfailure and the edge case wherePOLLERRis set butSO_ERROR == 0.
Comments suppressed due to low confidence (1)
lib/virtual-net/src/host.rs:756
poll_write_readyonly callsgetsockopt(SO_ERROR)whenPOLLERRis set. For nonblockingconnect(2),poll/selectcommonly reports the socket as writable (POLLOUT) on both success and failure, and the recommended way to distinguish the two is always checkingSO_ERRORonce the fd becomes writable. As-is, a failed connect could still be treated as ready if the OS setsPOLLOUTwithoutPOLLERR. Consider checkingSO_ERRORwheneverreventsincludesPOLLOUT(and/orPOLLERR) before returningOk(10240).
match libc_poll(
stream.as_raw_fd(),
libc::POLLOUT | libc::POLLHUP | libc::POLLERR,
) {
Some(val) if (val & libc::POLLERR) != 0 => {
// Get the actual socket error using SO_ERROR
let mut error: libc::c_int = 0;
let mut len = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
let result = unsafe {
libc::getsockopt(
stream.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_ERROR,
&mut error as *mut libc::c_int as *mut libc::c_void,
&mut len,
)
};
if result != 0 {
// getsockopt itself failed
let io_error = std::io::Error::last_os_error();
return Poll::Ready(Err(io_err_into_net_error(io_error)));
}
if error != 0 {
// Socket has a pending error
let io_error = std::io::Error::from_raw_os_error(error);
return Poll::Ready(Err(io_err_into_net_error(io_error)));
}
// POLLERR was set but SO_ERROR is 0 - this shouldn't normally happen,
// but we'll treat it as a generic IO error
return Poll::Ready(Err(NetworkError::IOError));
}
Some(val) if (val & libc::POLLHUP) != 0 => {
return Poll::Ready(Ok(0));
}
Some(val) if (val & libc::POLLOUT) != 0 => return Poll::Ready(Ok(10240)),
_ => {}
| match libc_poll( | ||
| stream.as_raw_fd(), | ||
| libc::POLLOUT | libc::POLLHUP | libc::POLLERR, | ||
| ) { | ||
| Some(val) if (val & libc::POLLERR) != 0 => { |
There was a problem hiding this comment.
This change fixes a subtle readiness/connection-error behavior; please add a regression test to cover it. A stable approach is to create a nonblocking TCP socket to a known-closed localhost port (e.g., bind a TcpListener to 127.0.0.1:0, capture the port, drop the listener, then attempt connect), wrap it as LocalTcpStream, and assert poll_write_ready resolves to Err(NetworkError::ConnectionRefused) (or appropriate mapped error) instead of reporting writable.
theduke
left a comment
There was a problem hiding this comment.
NOTE: this is an incomplete/incorrect fix and requires more work.
Description
Socket connect operations in WASIX always returned success, even when connections failed (e.g., ECONNREFUSED). This broke applications that probe ports for availability.
Root Cause
poll_write_readyinLocalTcpStreamonly checkedPOLLOUTandPOLLHUPflags. When TCP connections fail, the OS setsPOLLERR, which was being ignored.Changes
POLLERRto poll flags inpoll_write_ready(lib/virtual-net/src/host.rs)POLLERRis detected, retrieve actual error viagetsockopt(SO_ERROR)and propagate asNetworkErrorgetsockoptfailure,POLLERRwithSO_ERROR=0Before:
After:
This follows standard POSIX socket error handling and enables port probing logic.
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
registry.wasmer.io/home/REDACTED/work/wasmer/wasmer/target/debug/deps/wasmer_wasix-539a270b3a08905e /home/REDACTED/work/wasmer/wasmer/target/debug/deps/wasmer_wasix-539a270b3a08905e 5-cgu.0.rcgu.o stup/toolchains//home/REDACTED/work/wasmer/wasmer/target/debug/build/blake3-de6d20debuginfo=2 n/rustc -f39�� o ld/syn-f84641bab/home/REDACTED/work/wasmer/wasmer/target/debug/deps/wasmer_derive-feature="std" f/num_enum-0.7.5/src/lib.rs c6597.tracing_test_macro.db42951b32e2e2f8-cgu.0.rcgu.o c6597.tracing_test_macro.db42951b32e2e2f8-cgu.1.rcgu.o own-linux-gnu/li--64 c6597.28uagvhqakf0ge67mb1wav86y.rcgu.o own-�� a8ae0c185c/out/1/tmp/ccDYIUaT.s 6b65.rlib(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.