diff --git a/crates/wasi/src/preview2/host/network.rs b/crates/wasi/src/preview2/host/network.rs index 8a6bb73ec8c4..18f56023fafb 100644 --- a/crates/wasi/src/preview2/host/network.rs +++ b/crates/wasi/src/preview2/host/network.rs @@ -361,16 +361,6 @@ pub(crate) mod util { Ok(OwnedFd::from(socket)) } - pub fn tcp_bind(sockfd: Fd, addr: &SocketAddr) -> rustix::io::Result<()> { - rustix::net::bind(sockfd, addr).map_err(|error| match error { - // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS - // Windows returns WSAENOBUFS when the ephemeral ports have been exhausted. - #[cfg(windows)] - Errno::NOBUFS => Errno::ADDRINUSE, - _ => error, - }) - } - pub fn udp_bind(sockfd: Fd, addr: &SocketAddr) -> rustix::io::Result<()> { rustix::net::bind(sockfd, addr).map_err(|error| match error { // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS @@ -449,34 +439,6 @@ pub(crate) mod util { } } - // Even though SO_REUSEADDR is a SOL_* level option, this function contain a - // compatibility fix specific to TCP. That's why it contains the `_tcp_` infix instead of `_socket_`. - #[allow(unused_variables)] // Parameters are not used on Windows - pub fn set_tcp_reuseaddr(sockfd: Fd, value: bool) -> rustix::io::Result<()> { - // When a TCP socket is closed, the system may - // temporarily reserve that specific address+port pair in a so called - // TIME_WAIT state. During that period, any attempt to rebind to that pair - // will fail. Setting SO_REUSEADDR to true bypasses that behaviour. Unlike - // the name "SO_REUSEADDR" might suggest, it does not allow multiple - // active sockets to share the same local address. - - // On Windows that behavior is the default, so there is no need to manually - // configure such an option. But (!), Windows _does_ have an identically - // named socket option which allows users to "hijack" active sockets. - // This is definitely not what we want to do here. - - // Microsoft's own documentation[1] states that we should set SO_EXCLUSIVEADDRUSE - // instead (to the inverse value), however the github issue below[2] seems - // to indicate that that may no longer be correct. - // [1]: https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse - // [2]: https://github.com/python-trio/trio/issues/928 - - #[cfg(not(windows))] - sockopt::set_socket_reuseaddr(sockfd, value)?; - - Ok(()) - } - pub fn set_tcp_keepidle(sockfd: Fd, value: Duration) -> rustix::io::Result<()> { if value <= Duration::ZERO { // WIT: "If the provided value is 0, an `invalid-argument` error is returned." diff --git a/crates/wasi/src/preview2/host/tcp.rs b/crates/wasi/src/preview2/host/tcp.rs index 34992ed8304d..85eacd180b4f 100644 --- a/crates/wasi/src/preview2/host/tcp.rs +++ b/crates/wasi/src/preview2/host/tcp.rs @@ -2,15 +2,12 @@ use crate::preview2::bindings::sockets::{ network::{ErrorCode, IpAddressFamily, IpSocketAddress, Network}, tcp::ShutdownType, }; -use crate::preview2::host::network::util; -use crate::preview2::network::SocketAddrUse; use crate::preview2::pipe::{AsyncReadStream, AsyncWriteStream}; use crate::preview2::tcp::SystemTcpSocket; use crate::preview2::{ DynFuture, InputStream, OutputStream, Pollable, SocketAddrFamily, SocketResult, Subscribe, WasiView, }; -use rustix::io::Errno; use std::io; use std::net::SocketAddr; use std::task::{Context, Poll}; @@ -71,10 +68,6 @@ impl TcpSocketWrapper { }) } - pub fn tcp_socket(&self) -> &tokio::net::TcpStream { - &self.inner.stream - } - /// Create the input/output stream pair for a tcp socket. pub fn as_split(&self) -> (InputStream, OutputStream) { const SOCKET_READY_SIZE: usize = 1024 * 1024 * 1024; @@ -107,8 +100,12 @@ impl crate::preview2::bindings::sockets::tcp::HostTcpSocket for T { ) -> SocketResult<()> { self.ctx().allowed_network_uses.check_allowed_tcp()?; let table = self.table_mut(); - let socket = table.get(&this)?; - let network = table.get(&network)?; + + // At the moment, there's only one network handle (`instance-network`) + // in existence. All we have to do here is validate that the caller indeed + // has possesion of a valid handle and then we're good to go: + let _network = table.get(&network)?; + let socket = table.get_mut(&this)?; let local_address: SocketAddr = local_address.into(); match socket.tcp_state { @@ -117,37 +114,7 @@ impl crate::preview2::bindings::sockets::tcp::HostTcpSocket for T { _ => return Err(ErrorCode::InvalidState.into()), } - util::validate_unicast(&local_address)?; - util::validate_address_family(&local_address, &socket.inner.family)?; - - { - // Ensure that we're allowed to connect to this address. - network.check_socket_addr(&local_address, SocketAddrUse::TcpBind)?; - - // Automatically bypass the TIME_WAIT state when the user is trying - // to bind to a specific port: - let reuse_addr = local_address.port() > 0; - - // Unconditionally (re)set SO_REUSEADDR, even when the value is false. - // This ensures we're not accidentally affected by any socket option - // state left behind by a previous failed call to this method (start_bind). - util::set_tcp_reuseaddr(socket.tcp_socket(), reuse_addr)?; - - // Perform the OS bind call. - util::tcp_bind(socket.tcp_socket(), &local_address).map_err(|error| match error { - // From https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html: - // > [EAFNOSUPPORT] The specified address is not a valid address for the address family of the specified socket - // - // The most common reasons for this error should have already - // been handled by our own validation slightly higher up in this - // function. This error mapping is here just in case there is - // an edge case we didn't catch. - Errno::AFNOSUPPORT => ErrorCode::InvalidArgument, - _ => ErrorCode::from(error), - })?; - } - - let socket = table.get_mut(&this)?; + socket.inner.bind(&local_address)?; socket.tcp_state = TcpState::BindStarted; Ok(()) diff --git a/crates/wasi/src/preview2/tcp.rs b/crates/wasi/src/preview2/tcp.rs index 26e49118e125..dd01031129c7 100644 --- a/crates/wasi/src/preview2/tcp.rs +++ b/crates/wasi/src/preview2/tcp.rs @@ -15,13 +15,13 @@ use tokio::io::Interest; /// A cross-platform WASI-compliant `TcpSocket` implementation. pub struct SystemTcpSocket { - pub(crate) stream: Arc, + stream: Arc, /// The desired listen queue size. Set to None to use the system's default. listen_backlog_size: i32, is_listening: bool, - pub(crate) family: SocketAddressFamily, + family: SocketAddressFamily, // The socket options below are not automatically inherited from the listener // on all platforms. So we keep track of which options have been explicitly @@ -93,6 +93,57 @@ impl SystemTcpSocket { ) } + #[allow(unused_variables)] // Parameters are not used on Windows + fn set_reuseaddr(&mut self, value: bool) -> rustix::io::Result<()> { + // When a TCP socket is closed, the system may + // temporarily reserve that specific address+port pair in a so called + // TIME_WAIT state. During that period, any attempt to rebind to that pair + // will fail. Setting SO_REUSEADDR to true bypasses that behaviour. Unlike + // the name "SO_REUSEADDR" might suggest, it does not allow multiple + // active sockets to share the same local address. + + // On Windows that behavior is the default, so there is no need to manually + // configure such an option. But (!), Windows _does_ have an identically + // named socket option which allows users to "hijack" active sockets. + // This is definitely not what we want to do here. + + // Microsoft's own documentation[1] states that we should set SO_EXCLUSIVEADDRUSE + // instead (to the inverse value), however the github issue below[2] seems + // to indicate that that may no longer be correct. + // [1]: https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse + // [2]: https://github.com/python-trio/trio/issues/928 + + #[cfg(not(windows))] + sockopt::set_socket_reuseaddr(&self.stream, value)?; + + Ok(()) + } + + pub fn bind(&mut self, local_address: &SocketAddr) -> io::Result<()> { + util::validate_unicast(&local_address)?; + util::validate_address_family(&local_address, &self.family)?; + + // Automatically bypass the TIME_WAIT state when the user is trying + // to bind to a specific port: + let reuse_addr = local_address.port() > 0; + + // Unconditionally (re)set SO_REUSEADDR, even when the value is false. + // This ensures we're not accidentally affected by any socket option + // state left behind by a previous failed call to this method (start_bind). + self.set_reuseaddr(reuse_addr)?; + + // Perform the OS bind call. + rustix::net::bind(&self.stream, &local_address).map_err(|error| match error { + // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS + // Windows returns WSAENOBUFS when the ephemeral ports have been exhausted. + #[cfg(windows)] + Errno::NOBUFS => Errno::ADDRINUSE, + _ => error, + })?; + + Ok(()) + } + pub fn connect(&mut self, remote_address: &SocketAddr) -> DynFuture> { fn initiate_connect(me: &SystemTcpSocket, remote_address: &SocketAddr) -> io::Result<()> { util::validate_unicast(&remote_address)?;