dicej opened issue #12589:
I've been adding WASIp2 support to
mioand triaging failures in its test suite. This test is failing, which I tracked down towasmtime-wasispuriously changing the local address of a UDP socket whenwasi:sockets/udp#udp-socket.streamis called a second time on the same socket (equivalent to callingconnect(2)a second time in POSIX). The test fails because it uses the old local address, not realizing the socket suddenly has a new local address.Here's a minimal test case:
// udp.rs use std::{error::Error, net::UdpSocket}; fn main() -> Result<(), Box<dyn Error>> { let socket1 = UdpSocket::bind("127.0.0.1:0")?; let socket2 = UdpSocket::bind("127.0.0.1:0")?; let address1 = socket1.local_addr()?; let address2 = socket2.local_addr()?; println!( "before:\n \ socket1 local address: {address1:?}\n \ socket2 local address: {address2:?}" ); socket1.connect(address1)?; socket1.connect(address2)?; socket2.connect(address1)?; println!( "before:\n \ socket1 local address: {address1:?} remote address: {remote1:?}\n \ socket2 local address: {address2:?} remote address: {remote2:?}", address1 = socket1.local_addr()?, address2 = socket2.local_addr()?, remote1 = socket1.peer_addr()?, remote2 = socket2.peer_addr()? ); let data = b"foobar"; socket1.send(data)?; let mut buffer = [0u8; 20]; let count = socket2.recv(&mut buffer)?; assert_eq!(data, &buffer[..count]); println!("success!"); Ok(()) }If you build and run that natively on e.g. Linux using
rustc udp.rs && ./udp, you'll see it succeed, withsocket1's local address remaining unchanged before and after theUdpSocket::connectcalls.If you build and run for WASIp2 using
rustc --target=wasm32-wasip2 udp.rs && wasmtime run -Sudp,inherit-network udp.wasm, you'll see it hang indefinitely due tosocket1's local address changing due to the secondUdpSocket::connectcall.While digging into this, I found that
wasmtime-wasiproactively disconnects the socket by callingrustix::net::connect_unspecprior to reconnecting. When I comment out that code, the issue is fixed:diff --git a/crates/wasi/src/p2/host/udp.rs b/crates/wasi/src/p2/host/udp.rs index 1ef8350399..5dd426e736 100644 --- a/crates/wasi/src/p2/host/udp.rs +++ b/crates/wasi/src/p2/host/udp.rs @@ -71,9 +71,9 @@ impl udp::HostUdpSocket for WasiSocketsCtxView<'_> { // if there isn't a disconnect in between. // Step #1: Disconnect - if socket.is_connected() { - socket.disconnect()?; - } + // if socket.is_connected() { + // socket.disconnect()?; + // } // Step #2: (Re)connect if let Some(connect_addr) = remote_address { diff --git a/crates/wasi/src/sockets/udp.rs b/crates/wasi/src/sockets/udp.rs index 482c6aa090..ba1e93f18d 100644 --- a/crates/wasi/src/sockets/udp.rs +++ b/crates/wasi/src/sockets/udp.rs @@ -162,10 +162,10 @@ impl UdpSocket { // if there isn't a disconnect in between. // Step #1: Disconnect - if let UdpState::Connected(..) = self.udp_state { - udp_disconnect(&self.socket)?; - self.udp_state = UdpState::Bound; - } + // if let UdpState::Connected(..) = self.udp_state { + // udp_disconnect(&self.socket)?; + // self.udp_state = UdpState::Bound; + // } // Step #2: (Re)connect connect(&self.socket, &addr).map_err(|error| match error { Errno::AFNOSUPPORT => ErrorCode::InvalidArgument, // See `udp_bind` implementation.However, per the surrounding comments, that code is there for a reason, so I assume that removing it isn't the right solution. @badeend this seems to be related to https://github.com/bytecodealliance/wasmtime/pull/7411, so perhaps you have thoughts?
dicej edited issue #12589:
I've been adding WASIp2 support to
mioand triaging failures in its test suite. This test is failing, which I tracked down towasmtime-wasispuriously changing the local address of a UDP socket whenwasi:sockets/udp#udp-socket.streamis called a second time on the same socket (equivalent to callingconnect(2)a second time in POSIX). The test fails because it uses the old local address, not realizing the socket suddenly has a new local address.Here's a minimal test case:
// udp.rs use std::{error::Error, net::UdpSocket}; fn main() -> Result<(), Box<dyn Error>> { let socket1 = UdpSocket::bind("127.0.0.1:0")?; let socket2 = UdpSocket::bind("127.0.0.1:0")?; let address1 = socket1.local_addr()?; let address2 = socket2.local_addr()?; println!( "before:\n \ socket1 local address: {address1:?}\n \ socket2 local address: {address2:?}" ); socket1.connect(address1)?; socket1.connect(address2)?; socket2.connect(address1)?; println!( "after:\n \ socket1 local address: {address1:?} remote address: {remote1:?}\n \ socket2 local address: {address2:?} remote address: {remote2:?}", address1 = socket1.local_addr()?, address2 = socket2.local_addr()?, remote1 = socket1.peer_addr()?, remote2 = socket2.peer_addr()? ); let data = b"foobar"; socket1.send(data)?; let mut buffer = [0u8; 20]; let count = socket2.recv(&mut buffer)?; assert_eq!(data, &buffer[..count]); println!("success!"); Ok(()) }If you build and run that natively on e.g. Linux using
rustc udp.rs && ./udp, you'll see it succeed, withsocket1's local address remaining unchanged before and after theUdpSocket::connectcalls.If you build and run for WASIp2 using
rustc --target=wasm32-wasip2 udp.rs && wasmtime run -Sudp,inherit-network udp.wasm, you'll see it hang indefinitely due tosocket1's local address changing due to the secondUdpSocket::connectcall.While digging into this, I found that
wasmtime-wasiproactively disconnects the socket by callingrustix::net::connect_unspecprior to reconnecting. When I comment out that code, the issue is fixed:diff --git a/crates/wasi/src/p2/host/udp.rs b/crates/wasi/src/p2/host/udp.rs index 1ef8350399..5dd426e736 100644 --- a/crates/wasi/src/p2/host/udp.rs +++ b/crates/wasi/src/p2/host/udp.rs @@ -71,9 +71,9 @@ impl udp::HostUdpSocket for WasiSocketsCtxView<'_> { // if there isn't a disconnect in between. // Step #1: Disconnect - if socket.is_connected() { - socket.disconnect()?; - } + // if socket.is_connected() { + // socket.disconnect()?; + // } // Step #2: (Re)connect if let Some(connect_addr) = remote_address { diff --git a/crates/wasi/src/sockets/udp.rs b/crates/wasi/src/sockets/udp.rs index 482c6aa090..ba1e93f18d 100644 --- a/crates/wasi/src/sockets/udp.rs +++ b/crates/wasi/src/sockets/udp.rs @@ -162,10 +162,10 @@ impl UdpSocket { // if there isn't a disconnect in between. // Step #1: Disconnect - if let UdpState::Connected(..) = self.udp_state { - udp_disconnect(&self.socket)?; - self.udp_state = UdpState::Bound; - } + // if let UdpState::Connected(..) = self.udp_state { + // udp_disconnect(&self.socket)?; + // self.udp_state = UdpState::Bound; + // } // Step #2: (Re)connect connect(&self.socket, &addr).map_err(|error| match error { Errno::AFNOSUPPORT => ErrorCode::InvalidArgument, // See `udp_bind` implementation.However, per the surrounding comments, that code is there for a reason, so I assume that removing it isn't the right solution. @badeend this seems to be related to https://github.com/bytecodealliance/wasmtime/pull/7411, so perhaps you have thoughts?
dicej added the wasi label to Issue #12589.
Last updated: Feb 24 2026 at 04:36 UTC