-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
I've been adding WASIp2 support to mio and triaging failures in its test suite. This test is failing, which I tracked down to wasmtime-wasi spuriously changing the local address of a UDP socket when wasi:sockets/udp#udp-socket.stream is called a second time on the same socket (equivalent to calling connect(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, with socket1's local address remaining unchanged before and after the UdpSocket::connect calls.
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 to socket1's local address changing due to the second UdpSocket::connect call.
While digging into this, I found that wasmtime-wasi proactively disconnects the socket by calling rustix::net::connect_unspec prior 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 #7411, so perhaps you have thoughts?