Skip to content

Commit

Permalink
Move bind into SystemTcpSocket
Browse files Browse the repository at this point in the history
  • Loading branch information
badeend committed Jan 11, 2024
1 parent d6087b9 commit 663c62e
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 80 deletions.
38 changes: 0 additions & 38 deletions crates/wasi/src/preview2/host/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,16 +361,6 @@ pub(crate) mod util {
Ok(OwnedFd::from(socket))
}

pub fn tcp_bind<Fd: AsFd>(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<Fd: AsFd>(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
Expand Down Expand Up @@ -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<Fd: AsFd>(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<Fd: AsFd>(sockfd: Fd, value: Duration) -> rustix::io::Result<()> {
if value <= Duration::ZERO {
// WIT: "If the provided value is 0, an `invalid-argument` error is returned."
Expand Down
47 changes: 7 additions & 40 deletions crates/wasi/src/preview2/host/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -107,8 +100,12 @@ impl<T: WasiView> 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 {
Expand All @@ -117,37 +114,7 @@ impl<T: WasiView> 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(())
Expand Down
55 changes: 53 additions & 2 deletions crates/wasi/src/preview2/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ use tokio::io::Interest;

/// A cross-platform WASI-compliant `TcpSocket` implementation.
pub struct SystemTcpSocket {
pub(crate) stream: Arc<tokio::net::TcpStream>,
stream: Arc<tokio::net::TcpStream>,

/// 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
Expand Down Expand Up @@ -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<io::Result<()>> {
fn initiate_connect(me: &SystemTcpSocket, remote_address: &SocketAddr) -> io::Result<()> {
util::validate_unicast(&remote_address)?;
Expand Down

0 comments on commit 663c62e

Please sign in to comment.