From 909c04a851c3c3275b824d04738fa401d59904a4 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 17 Feb 2023 08:51:19 -0800 Subject: [PATCH] Rebase on the new wasi-sockets. This switches to using the wasi-sockets wit files from WebAssembly/wasi-sockets#16. Many things are still stubbed out with `todo!()` for now. --- host/src/ip_name_lookup.rs | 51 +++ host/src/lib.rs | 7 + host/src/network.rs | 114 +++++ host/src/tcp.rs | 262 ++++------- host/src/udp.rs | 127 ++++++ src/lib.rs | 24 +- wasi-common/cap-std-sync/src/lib.rs | 10 +- wasi-common/cap-std-sync/src/net.rs | 654 +++++++++++----------------- wasi-common/src/connection.rs | 69 --- wasi-common/src/ctx.rs | 4 +- wasi-common/src/lib.rs | 10 +- wasi-common/src/listener.rs | 39 -- wasi-common/src/tcp_listener.rs | 44 -- wasi-common/src/tcp_socket.rs | 54 +++ wasi-common/src/udp_socket.rs | 51 +++ wit/wasi-command.wit | 6 +- wit/wasi-default-network.wit | 9 + wit/wasi-dns.wit | 83 ---- wit/wasi-ip-name-lookup.wit | 69 +++ wit/wasi-ip.wit | 35 -- wit/wasi-net.wit | 5 - wit/wasi-network.wit | 54 +++ wit/wasi-tcp.wit | 481 +++++++++----------- wit/wasi-udp.wit | 193 ++++++++ wit/wasi.wit | 6 +- 25 files changed, 1302 insertions(+), 1159 deletions(-) create mode 100644 host/src/ip_name_lookup.rs create mode 100644 host/src/network.rs create mode 100644 host/src/udp.rs delete mode 100644 wasi-common/src/connection.rs delete mode 100644 wasi-common/src/listener.rs delete mode 100644 wasi-common/src/tcp_listener.rs create mode 100644 wasi-common/src/tcp_socket.rs create mode 100644 wasi-common/src/udp_socket.rs create mode 100644 wit/wasi-default-network.wit delete mode 100644 wit/wasi-dns.wit create mode 100644 wit/wasi-ip-name-lookup.wit delete mode 100644 wit/wasi-ip.wit delete mode 100644 wit/wasi-net.wit create mode 100644 wit/wasi-network.wit create mode 100644 wit/wasi-udp.wit diff --git a/host/src/ip_name_lookup.rs b/host/src/ip_name_lookup.rs new file mode 100644 index 00000000..a1cc6c15 --- /dev/null +++ b/host/src/ip_name_lookup.rs @@ -0,0 +1,51 @@ +#![allow(unused_variables)] + +use crate::{ + wasi_ip_name_lookup::{ResolveAddressStream, WasiIpNameLookup}, + wasi_network::{Error, IpAddress, IpAddressFamily, Network}, + wasi_poll::Pollable, + HostResult, WasiCtx, +}; + +#[async_trait::async_trait] +impl WasiIpNameLookup for WasiCtx { + async fn resolve_addresses( + &mut self, + network: Network, + name: String, + address_family: Option, + include_unavailable: bool, + ) -> HostResult { + todo!() + } + + async fn resolve_next_address( + &mut self, + stream: ResolveAddressStream, + ) -> HostResult, Error> { + todo!() + } + + async fn drop_resolve_address_stream( + &mut self, + stream: ResolveAddressStream, + ) -> anyhow::Result<()> { + todo!() + } + + async fn non_blocking(&mut self, stream: ResolveAddressStream) -> HostResult { + todo!() + } + + async fn set_non_blocking( + &mut self, + stream: ResolveAddressStream, + value: bool, + ) -> HostResult<(), Error> { + todo!() + } + + async fn subscribe(&mut self, stream: ResolveAddressStream) -> anyhow::Result { + todo!() + } +} diff --git a/host/src/lib.rs b/host/src/lib.rs index 0f886cad..f2f4356a 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -2,11 +2,14 @@ mod clocks; mod exit; mod filesystem; mod io; +mod ip_name_lookup; mod logging; +mod network; mod poll; mod random; mod stderr; mod tcp; +mod udp; pub use wasi_common::{table::Table, WasiCtx}; type HostResult = anyhow::Result>; @@ -32,6 +35,10 @@ pub fn add_to_linker( wasi_io::add_to_linker(l, f)?; wasi_random::add_to_linker(l, f)?; wasi_tcp::add_to_linker(l, f)?; + wasi_udp::add_to_linker(l, f)?; + wasi_ip_name_lookup::add_to_linker(l, f)?; + wasi_default_network::add_to_linker(l, f)?; + wasi_network::add_to_linker(l, f)?; wasi_exit::add_to_linker(l, f)?; Ok(()) } diff --git a/host/src/network.rs b/host/src/network.rs new file mode 100644 index 00000000..59a21145 --- /dev/null +++ b/host/src/network.rs @@ -0,0 +1,114 @@ +use crate::{ + wasi_default_network::WasiDefaultNetwork, + wasi_network::{Network, WasiNetwork}, + WasiCtx, +}; +use crate::{ + //wasi_network::{IpSocketAddress, Ipv4SocketAddress, Ipv6SocketAddress}, + wasi_tcp, + wasi_udp, +}; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; + +pub(crate) fn convert(_error: wasi_common::Error) -> anyhow::Error { + todo!("convert wasi-common Error to wasi_network::Error") +} + +#[async_trait::async_trait] +impl WasiNetwork for WasiCtx { + async fn drop_network(&mut self, _network: Network) -> anyhow::Result<()> { + todo!() + } +} + +#[async_trait::async_trait] +impl WasiDefaultNetwork for WasiCtx { + async fn default_network(&mut self) -> anyhow::Result { + todo!() + } +} + +impl From for wasi_tcp::IpSocketAddress { + fn from(addr: SocketAddr) -> Self { + match addr { + SocketAddr::V4(v4) => Self::Ipv4(v4.into()), + SocketAddr::V6(v6) => Self::Ipv6(v6.into()), + } + } +} + +impl From for wasi_udp::IpSocketAddress { + fn from(addr: SocketAddr) -> Self { + match addr { + SocketAddr::V4(v4) => Self::Ipv4(v4.into()), + SocketAddr::V6(v6) => Self::Ipv6(v6.into()), + } + } +} + +impl From for wasi_tcp::Ipv4SocketAddress { + fn from(addr: SocketAddrV4) -> Self { + Self { + address: MyIpv4Addr::from(addr.ip()).0, + port: addr.port(), + } + } +} + +impl From for wasi_udp::Ipv4SocketAddress { + fn from(addr: SocketAddrV4) -> Self { + Self { + address: MyIpv4Addr::from(addr.ip()).0, + port: addr.port(), + } + } +} + +impl From for wasi_tcp::Ipv6SocketAddress { + fn from(addr: SocketAddrV6) -> Self { + Self { + address: MyIpv6Addr::from(addr.ip()).0, + port: addr.port(), + flow_info: addr.flowinfo(), + scope_id: addr.scope_id(), + } + } +} + +impl From for wasi_udp::Ipv6SocketAddress { + fn from(addr: SocketAddrV6) -> Self { + Self { + address: MyIpv6Addr::from(addr.ip()).0, + port: addr.port(), + flow_info: addr.flowinfo(), + scope_id: addr.scope_id(), + } + } +} + +// Newtypes to guide conversions. +struct MyIpv4Addr((u8, u8, u8, u8)); +struct MyIpv6Addr((u16, u16, u16, u16, u16, u16, u16, u16)); + +impl From<&Ipv4Addr> for MyIpv4Addr { + fn from(addr: &Ipv4Addr) -> Self { + let octets = addr.octets(); + Self((octets[0], octets[1], octets[2], octets[3])) + } +} + +impl From<&Ipv6Addr> for MyIpv6Addr { + fn from(addr: &Ipv6Addr) -> Self { + let segments = addr.segments(); + Self(( + segments[0], + segments[1], + segments[2], + segments[3], + segments[4], + segments[5], + segments[6], + segments[7], + )) + } +} diff --git a/host/src/tcp.rs b/host/src/tcp.rs index a2e84d67..90b25807 100644 --- a/host/src/tcp.rs +++ b/host/src/tcp.rs @@ -1,101 +1,29 @@ #![allow(unused_variables)] use crate::{ + network::convert, wasi_io::{InputStream, OutputStream}, - wasi_tcp::{ - Connection, ConnectionFlags, Errno, IoSize, IpSocketAddress, Ipv4SocketAddress, - Ipv6SocketAddress, Listener, ListenerFlags, Network, TcpListener, WasiTcp, - }, + wasi_network::{Error, IpAddressFamily, Network}, + wasi_poll::Pollable, + wasi_tcp::{IpSocketAddress, ShutdownType, TcpSocket, WasiTcp}, HostResult, WasiCtx, }; -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; -use std::ops::BitAnd; -use wasi_common::listener::TableListenerExt; -use wasi_common::tcp_listener::TableTcpListenerExt; - -/// TODO: Remove once wasmtime #5589 lands. -fn contains + Eq + Copy>(flags: T, flag: T) -> bool { - (flags & flag) == flag -} - -fn convert(error: wasi_common::Error) -> anyhow::Error { - if let Some(errno) = error.downcast_ref() { - use wasi_common::Errno::*; - - match errno { - Acces => Errno::Access, - Again => Errno::Again, - Already => Errno::Already, - Badf => Errno::Badf, - Busy => Errno::Busy, - Ilseq => Errno::Ilseq, - Inprogress => Errno::Inprogress, - Intr => Errno::Intr, - Inval => Errno::Inval, - Io => Errno::Io, - Msgsize => Errno::Msgsize, - Nametoolong => Errno::Nametoolong, - Noent => Errno::Noent, - Nomem => Errno::Nomem, - Nosys => Errno::Nosys, - Notrecoverable => Errno::Notrecoverable, - Notsup => Errno::Notsup, - Overflow => Errno::Overflow, - Perm => Errno::Perm, - Addrinuse => Errno::Addrinuse, - Addrnotavail => Errno::Addrnotavail, - Afnosupport => Errno::Afnosupport, - Connaborted => Errno::ConnectionAborted, - Connrefused => Errno::ConnectionRefused, - Connreset => Errno::ConnectionReset, - Destaddrreq => Errno::Destaddrreq, - Hostunreach => Errno::HostUnreachable, - Isconn => Errno::Isconn, - Multihop => Errno::Multihop, - Netreset => Errno::NetworkReset, - Netdown => Errno::NetworkDown, - Netunreach => Errno::NetworkUnreachable, - Nobufs => Errno::Nobufs, - Noprotoopt => Errno::Noprotoopt, - Timedout => Errno::Timedout, - _ => { - panic!("Unexpected errno: {:?}", errno); - } - } - .into() - } else { - error.into() - } -} +use wasi_common::tcp_socket::TableTcpSocketExt; #[async_trait::async_trait] impl WasiTcp for WasiCtx { - async fn listen( - &mut self, - network: Network, - address: IpSocketAddress, - backlog: Option, - flags: ListenerFlags, - ) -> HostResult { + async fn listen(&mut self, socket: TcpSocket, backlog: Option) -> HostResult<(), Error> { todo!() } async fn accept( &mut self, - listener: Listener, - flags: ConnectionFlags, - ) -> HostResult<(Connection, InputStream, OutputStream), Errno> { + socket: TcpSocket, + ) -> HostResult<(TcpSocket, InputStream, OutputStream), Error> { let table = self.table_mut(); - let l = table.get_listener_mut(listener)?; + let l = table.get_tcp_socket_mut(socket)?; - let nonblocking = contains(flags, ConnectionFlags::NONBLOCK); - - if contains(flags, ConnectionFlags::KEEPALIVE) || contains(flags, ConnectionFlags::NODELAY) - { - todo!() - } - - let (connection, input_stream, output_stream) = l.accept(nonblocking).await?; + let (connection, input_stream, output_stream, _addr) = l.accept(false).await?; let connection = table.push(Box::new(connection)).map_err(convert)?; let input_stream = table.push(Box::new(input_stream)).map_err(convert)?; @@ -104,170 +32,132 @@ impl WasiTcp for WasiCtx { Ok(Ok((connection, input_stream, output_stream))) } - async fn accept_tcp( + async fn connect( &mut self, - listener: TcpListener, - flags: ConnectionFlags, - ) -> HostResult<(Connection, InputStream, OutputStream, IpSocketAddress), Errno> { - let table = self.table_mut(); - let l = table.get_tcp_listener_mut(listener)?; - - let nonblocking = contains(flags, ConnectionFlags::NONBLOCK); + socket: TcpSocket, + remote_address: IpSocketAddress, + ) -> HostResult<(InputStream, OutputStream), Error> { + todo!() + } - if contains(flags, ConnectionFlags::KEEPALIVE) || contains(flags, ConnectionFlags::NODELAY) - { - todo!() - } + async fn receive_buffer_size(&mut self, socket: TcpSocket) -> HostResult { + todo!() + } - let (connection, input_stream, output_stream, addr) = l.accept(nonblocking).await?; + async fn set_receive_buffer_size( + &mut self, + socket: TcpSocket, + value: u64, + ) -> HostResult<(), Error> { + todo!() + } - let connection = table.push(Box::new(connection)).map_err(convert)?; - let input_stream = table.push(Box::new(input_stream)).map_err(convert)?; - let output_stream = table.push(Box::new(output_stream)).map_err(convert)?; + async fn send_buffer_size(&mut self, socket: TcpSocket) -> HostResult { + todo!() + } - Ok(Ok((connection, input_stream, output_stream, addr.into()))) + async fn set_send_buffer_size( + &mut self, + socket: TcpSocket, + value: u64, + ) -> HostResult<(), Error> { + todo!() } - async fn connect( + async fn create_tcp_socket( &mut self, network: Network, + address_family: IpAddressFamily, + ) -> HostResult { + todo!() + } + + async fn bind( + &mut self, + this: TcpSocket, local_address: IpSocketAddress, - remote_address: IpSocketAddress, - flags: ConnectionFlags, - ) -> HostResult<(Connection, InputStream, OutputStream), Errno> { + ) -> HostResult<(), Error> { todo!() } - async fn send(&mut self, connection: Connection, bytes: Vec) -> HostResult { + async fn local_address(&mut self, this: TcpSocket) -> HostResult { todo!() } - async fn receive( + async fn shutdown( &mut self, - connection: Connection, - length: IoSize, - ) -> HostResult<(Vec, bool), Errno> { + this: TcpSocket, + shutdown_type: ShutdownType, + ) -> HostResult<(), Error> { todo!() } - async fn is_connected(&mut self, connection: Connection) -> anyhow::Result { - // This should ultimately call `getpeername` and test whether it - // gets a `ENOTCONN` error indicating not-connected. + async fn remote_address(&mut self, this: TcpSocket) -> HostResult { todo!() } - async fn get_flags(&mut self, connection: Connection) -> HostResult { + async fn keep_alive(&mut self, this: TcpSocket) -> HostResult { todo!() } - async fn set_flags( - &mut self, - connection: Connection, - flags: ConnectionFlags, - ) -> HostResult<(), Errno> { + async fn set_keep_alive(&mut self, this: TcpSocket, value: bool) -> HostResult<(), Error> { todo!() } - async fn get_receive_buffer_size( - &mut self, - connection: Connection, - ) -> HostResult { + async fn no_delay(&mut self, this: TcpSocket) -> HostResult { todo!() } - async fn set_receive_buffer_size( - &mut self, - connection: Connection, - value: IoSize, - ) -> HostResult<(), Errno> { + async fn set_no_delay(&mut self, this: TcpSocket, value: bool) -> HostResult<(), Error> { todo!() } - async fn get_send_buffer_size(&mut self, connection: Connection) -> HostResult { + async fn address_family(&mut self, this: TcpSocket) -> anyhow::Result { todo!() } - async fn set_send_buffer_size( - &mut self, - connection: Connection, - value: IoSize, - ) -> HostResult<(), Errno> { + async fn unicast_hop_limit(&mut self, this: TcpSocket) -> HostResult { todo!() } - async fn bytes_readable(&mut self, socket: Connection) -> HostResult<(IoSize, bool), Errno> { - drop(socket); + async fn set_unicast_hop_limit(&mut self, this: TcpSocket, value: u8) -> HostResult<(), Error> { todo!() } - async fn bytes_writable(&mut self, socket: Connection) -> HostResult<(IoSize, bool), Errno> { - drop(socket); + async fn ipv6_only(&mut self, this: TcpSocket) -> HostResult { todo!() } - async fn close_tcp_listener(&mut self, listener: TcpListener) -> anyhow::Result<()> { - drop(listener); + async fn set_ipv6_only(&mut self, this: TcpSocket, value: bool) -> HostResult<(), Error> { todo!() } - async fn close_connection(&mut self, connection: Connection) -> anyhow::Result<()> { - drop(connection); + async fn non_blocking(&mut self, this: TcpSocket) -> HostResult { todo!() } -} -impl From for IpSocketAddress { - fn from(addr: SocketAddr) -> Self { - match addr { - SocketAddr::V4(v4) => Self::Ipv4(v4.into()), - SocketAddr::V6(v6) => Self::Ipv6(v6.into()), - } + async fn set_non_blocking(&mut self, this: TcpSocket, value: bool) -> HostResult<(), Error> { + todo!() } -} -impl From for Ipv4SocketAddress { - fn from(addr: SocketAddrV4) -> Self { - Self { - address: MyIpv4Addr::from(addr.ip()).0, - port: addr.port(), - } + async fn subscribe(&mut self, this: TcpSocket) -> anyhow::Result { + todo!() } -} -impl From for Ipv6SocketAddress { - fn from(addr: SocketAddrV6) -> Self { - Self { - address: MyIpv6Addr::from(addr.ip()).0, - port: addr.port(), - flow_info: addr.flowinfo(), - scope_id: addr.scope_id(), - } + /* TODO: Revisit after https://github.com/WebAssembly/wasi-sockets/issues/17 + async fn bytes_readable(&mut self, socket: Connection) -> HostResult<(u64, bool), Error> { + drop(socket); + todo!() } -} - -// Newtypes to guide conversions. -struct MyIpv4Addr((u8, u8, u8, u8)); -struct MyIpv6Addr((u16, u16, u16, u16, u16, u16, u16, u16)); -impl From<&Ipv4Addr> for MyIpv4Addr { - fn from(addr: &Ipv4Addr) -> Self { - let octets = addr.octets(); - Self((octets[0], octets[1], octets[2], octets[3])) + async fn bytes_writable(&mut self, socket: Connection) -> HostResult<(u64, bool), Error> { + drop(socket); + todo!() } -} + */ -impl From<&Ipv6Addr> for MyIpv6Addr { - fn from(addr: &Ipv6Addr) -> Self { - let segments = addr.segments(); - Self(( - segments[0], - segments[1], - segments[2], - segments[3], - segments[4], - segments[5], - segments[6], - segments[7], - )) + async fn drop_tcp_socket(&mut self, socket: TcpSocket) -> anyhow::Result<()> { + drop(socket); + todo!() } } diff --git a/host/src/udp.rs b/host/src/udp.rs new file mode 100644 index 00000000..c36d76bc --- /dev/null +++ b/host/src/udp.rs @@ -0,0 +1,127 @@ +#![allow(unused_variables)] + +use crate::{ + wasi_network::{Error, IpAddressFamily, Network}, + wasi_poll::Pollable, + wasi_udp::{Datagram, IpSocketAddress, UdpSocket, WasiUdp}, + HostResult, WasiCtx, +}; +use wasi_common::udp_socket::TableUdpSocketExt; + +#[async_trait::async_trait] +impl WasiUdp for WasiCtx { + async fn connect( + &mut self, + network: Network, + remote_address: IpSocketAddress, + ) -> HostResult<(), Error> { + todo!() + } + + async fn send(&mut self, socket: UdpSocket, datagram: Datagram) -> HostResult<(), Error> { + todo!() + } + + async fn receive(&mut self, socket: UdpSocket) -> HostResult { + todo!() + } + + async fn receive_buffer_size(&mut self, socket: UdpSocket) -> HostResult { + todo!() + } + + async fn set_receive_buffer_size( + &mut self, + socket: UdpSocket, + value: u64, + ) -> HostResult<(), Error> { + todo!() + } + + async fn send_buffer_size(&mut self, socket: UdpSocket) -> HostResult { + todo!() + } + + async fn set_send_buffer_size( + &mut self, + socket: UdpSocket, + value: u64, + ) -> HostResult<(), Error> { + todo!() + } + + async fn create_udp_socket( + &mut self, + network: Network, + address_family: IpAddressFamily, + ) -> HostResult { + todo!() + } + + async fn bind( + &mut self, + this: UdpSocket, + local_address: IpSocketAddress, + ) -> HostResult<(), Error> { + todo!() + } + + async fn local_address(&mut self, this: UdpSocket) -> HostResult { + todo!() + } + + async fn remote_address(&mut self, this: UdpSocket) -> HostResult { + todo!() + } + + async fn address_family(&mut self, this: UdpSocket) -> anyhow::Result { + todo!() + } + + async fn unicast_hop_limit(&mut self, this: UdpSocket) -> HostResult { + todo!() + } + + async fn set_unicast_hop_limit(&mut self, this: UdpSocket, value: u8) -> HostResult<(), Error> { + todo!() + } + + async fn ipv6_only(&mut self, this: UdpSocket) -> HostResult { + todo!() + } + + async fn set_ipv6_only(&mut self, this: UdpSocket, value: bool) -> HostResult<(), Error> { + todo!() + } + + async fn non_blocking(&mut self, this: UdpSocket) -> HostResult { + todo!() + } + + async fn set_non_blocking(&mut self, this: UdpSocket, value: bool) -> HostResult<(), Error> { + let this = self.table.get_udp_socket_mut(this)?; + this.set_nonblocking(value)?; + Ok(Ok(())) + } + + async fn subscribe(&mut self, this: UdpSocket) -> anyhow::Result { + todo!() + } + + /* TODO: Revisit after https://github.com/WebAssembly/wasi-sockets/issues/17 + async fn bytes_readable(&mut self, socket: UdpSocket) -> HostResult<(u64, bool), Error> { + drop(socket); + todo!() + } + + async fn bytes_writable(&mut self, socket: UdpSocket) -> HostResult<(u64, bool), Error> { + drop(socket); + todo!() + } + */ + + async fn drop_udp_socket(&mut self, socket: UdpSocket) -> anyhow::Result<()> { + drop(socket); + todo!() + } +} diff --git a/src/lib.rs b/src/lib.rs index 4b05a109..b2f2ef56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented use crate::bindings::{ - wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_io, wasi_monotonic_clock, wasi_poll, - wasi_random, wasi_stderr, wasi_tcp, wasi_wall_clock, + wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_io, wasi_monotonic_clock, wasi_network, + wasi_poll, wasi_random, wasi_stderr, wasi_tcp, wasi_wall_clock, }; use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; @@ -1467,11 +1467,12 @@ impl Drop for Pollables { } } -impl From for Errno { - fn from(error: wasi_tcp::Errno) -> Errno { - use wasi_tcp::Errno::*; - +impl From for Errno { + fn from(error: wasi_network::Error) -> Errno { match error { + wasi_network::Error::Unknown => unreachable!(), // TODO + wasi_network::Error::Again => ERRNO_AGAIN, + /* TODO // Use a black box to prevent the optimizer from generating a // lookup table, which would require a static initializer. ConnectionAborted => black_box(ERRNO_CONNABORTED), @@ -1482,6 +1483,7 @@ impl From for Errno { NetworkUnreachable => ERRNO_NETUNREACH, Timedout => ERRNO_TIMEDOUT, _ => unreachable!(), + */ } } } @@ -1688,6 +1690,7 @@ pub unsafe extern "C" fn poll_oneoff( } }, StreamType::Socket(connection) => { + /* TODO: Revisit after https://github.com/WebAssembly/wasi-sockets/issues/17 match wasi_tcp::bytes_readable(*connection) { Ok(result) => { error = ERRNO_SUCCESS; @@ -1704,6 +1707,8 @@ pub unsafe extern "C" fn poll_oneoff( flags = 0; } } + */ + unreachable!() // TODO } StreamType::EmptyStdin => { error = ERRNO_SUCCESS; @@ -1732,6 +1737,7 @@ pub unsafe extern "C" fn poll_oneoff( flags = 0; } StreamType::Socket(connection) => { + /* TODO: Revisit after https://github.com/WebAssembly/wasi-sockets/issues/17 match wasi_tcp::bytes_writable(connection) { Ok(result) => { error = ERRNO_SUCCESS; @@ -1748,6 +1754,8 @@ pub unsafe extern "C" fn poll_oneoff( flags = 0; } } + */ + unreachable!() // TODO } StreamType::EmptyStdin => { error = ERRNO_BADF; @@ -2077,7 +2085,7 @@ enum StreamType { File(File), /// Streaming data with a socket connection. - Socket(wasi_tcp::Connection), + Socket(wasi_tcp::TcpSocket), } impl Drop for Descriptor { @@ -2454,7 +2462,7 @@ impl State { } #[allow(dead_code)] // until Socket is implemented - fn get_socket(&self, fd: Fd) -> Result { + fn get_socket(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(Streams { type_: StreamType::Socket(socket), diff --git a/wasi-common/cap-std-sync/src/lib.rs b/wasi-common/cap-std-sync/src/lib.rs index af35c1b3..0bd84132 100644 --- a/wasi-common/cap-std-sync/src/lib.rs +++ b/wasi-common/cap-std-sync/src/lib.rs @@ -45,12 +45,12 @@ pub use cap_std::net::TcpListener; pub use clocks::clocks_ctx; pub use sched::sched_ctx; -use crate::net::Listener; +use crate::net::TcpSocket; use cap_rand::{Rng, RngCore, SeedableRng}; use wasi_common::{ - listener::WasiListener, stream::{InputStream, OutputStream}, table::Table, + tcp_socket::WasiTcpSocket, WasiCtx, }; @@ -94,9 +94,9 @@ impl WasiCtxBuilder { self.0.insert_dir(fd, dir); self } - pub fn preopened_listener(mut self, fd: u32, listener: impl Into) -> Self { - let listener: Listener = listener.into(); - let listener: Box = listener.into(); + pub fn preopened_listener(mut self, fd: u32, listener: impl Into) -> Self { + let listener: TcpSocket = listener.into(); + let listener: Box = Box::new(TcpSocket::from(listener)); self.0.insert_listener(fd, listener); self diff --git a/wasi-common/cap-std-sync/src/net.rs b/wasi-common/cap-std-sync/src/net.rs index c36ff5e7..ed0f77a8 100644 --- a/wasi-common/cap-std-sync/src/net.rs +++ b/wasi-common/cap-std-sync/src/net.rs @@ -1,3 +1,4 @@ +use cap_std::net::{TcpListener, TcpStream}; use io_extras::borrowed::BorrowedReadable; #[cfg(windows)] use io_extras::os::windows::{AsHandleOrSocket, BorrowedHandleOrSocket}; @@ -6,6 +7,7 @@ use io_lifetimes::AsSocketlike; use io_lifetimes::{AsFd, BorrowedFd}; #[cfg(windows)] use io_lifetimes::{AsSocket, BorrowedSocket}; +use rustix::fd::OwnedFd; use std::any::Any; use std::convert::TryInto; use std::io::{self, Read, Write}; @@ -15,471 +17,331 @@ use system_interface::io::IoExt; use system_interface::io::IsReadWrite; use system_interface::io::ReadReady; use wasi_common::{ - connection::{RiFlags, RoFlags, SdFlags, SiFlags, WasiConnection}, - listener::WasiListener, stream::{InputStream, OutputStream}, - tcp_listener::WasiTcpListener, + tcp_socket::{SdFlags, WasiTcpSocket}, + udp_socket::{RiFlags, RoFlags, WasiUdpSocket}, Error, ErrorExt, }; -pub enum Listener { - TcpListener(cap_std::net::TcpListener), - #[cfg(unix)] - UnixListener(cap_std::os::unix::net::UnixListener), -} +pub struct TcpSocket(Arc); +pub struct UdpSocket(Arc); -pub enum Connection { - TcpStream(cap_std::net::TcpStream), - #[cfg(unix)] - UnixStream(cap_std::os::unix::net::UnixStream), -} +impl TcpSocket { + pub fn new(owned: OwnedFd) -> Self { + Self(Arc::new(owned)) + } -impl From for Listener { - fn from(listener: cap_std::net::TcpListener) -> Self { - Self::TcpListener(listener) + pub fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) } } -impl From for Connection { - fn from(stream: cap_std::net::TcpStream) -> Self { - Self::TcpStream(stream) +impl UdpSocket { + pub fn new(owned: OwnedFd) -> Self { + Self(Arc::new(owned)) } -} -#[cfg(unix)] -impl From for Listener { - fn from(listener: cap_std::os::unix::net::UnixListener) -> Self { - Self::UnixListener(listener) + pub fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) } } -#[cfg(unix)] -impl From for Connection { - fn from(stream: cap_std::os::unix::net::UnixStream) -> Self { - Self::UnixStream(stream) +#[async_trait::async_trait] +impl WasiTcpSocket for TcpSocket { + fn as_any(&self) -> &dyn Any { + self } -} -#[cfg(unix)] -impl From for Box { - fn from(listener: Listener) -> Self { - match listener { - Listener::TcpListener(l) => Box::new(crate::net::TcpListener::from_cap_std(l)), - Listener::UnixListener(l) => Box::new(crate::net::UnixListener::from_cap_std(l)), - } + async fn accept( + &mut self, + nonblocking: bool, + ) -> Result< + ( + Box, + Box, + Box, + SocketAddr, + ), + Error, + > { + let (connection, addr) = self.0.as_socketlike_view::().accept()?; + connection.set_nonblocking(nonblocking)?; + let connection = TcpSocket::new(connection.into()); + let input_stream = connection.clone(); + let output_stream = connection.clone(); + Ok(( + Box::new(connection), + Box::new(input_stream), + Box::new(output_stream), + addr, + )) } -} -#[cfg(windows)] -impl From for Box { - fn from(listener: Listener) -> Self { - match listener { - Listener::TcpListener(l) => Box::new(crate::net::TcpListener::from_cap_std(l)), - } + fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error> { + self.0 + .as_socketlike_view::() + .set_nonblocking(flag)?; + Ok(()) } -} -#[cfg(unix)] -impl From for Box { - fn from(listener: Connection) -> Self { - match listener { - Connection::TcpStream(l) => Box::new(crate::net::TcpStream::from_cap_std(l)), - Connection::UnixStream(l) => Box::new(crate::net::UnixStream::from_cap_std(l)), + async fn sock_shutdown(&mut self, how: SdFlags) -> Result<(), Error> { + let how = if how == SdFlags::READ | SdFlags::WRITE { + cap_std::net::Shutdown::Both + } else if how == SdFlags::READ { + cap_std::net::Shutdown::Read + } else if how == SdFlags::WRITE { + cap_std::net::Shutdown::Write + } else { + return Err(Error::invalid_argument()); + }; + self.0.as_socketlike_view::().shutdown(how)?; + Ok(()) + } + + async fn readable(&self) -> Result<(), Error> { + if is_read_write(&self.0)?.0 { + Ok(()) + } else { + Err(Error::badf()) } } -} -#[cfg(windows)] -impl From for Box { - fn from(listener: Connection) -> Self { - match listener { - Connection::TcpStream(l) => Box::new(crate::net::TcpStream::from_cap_std(l)), + async fn writable(&self) -> Result<(), Error> { + if is_read_write(&self.0)?.1 { + Ok(()) + } else { + Err(Error::badf()) } } } -macro_rules! wasi_listener_impl { - ($ty:ty, $stream:ty) => { - #[async_trait::async_trait] - impl WasiListener for $ty { - fn as_any(&self) -> &dyn Any { - self - } - - async fn accept( - &mut self, - nonblocking: bool, - ) -> Result< - ( - Box, - Box, - Box, - ), - Error, - > { - let (stream, _) = self.0.accept()?; - stream.set_nonblocking(nonblocking)?; - let connection = <$stream>::from_cap_std(stream); - let input_stream = connection.clone(); - let output_stream = connection.clone(); - Ok(( - Box::new(connection), - Box::new(input_stream), - Box::new(output_stream), - )) - } +#[async_trait::async_trait] +impl WasiUdpSocket for UdpSocket { + fn as_any(&self) -> &dyn Any { + self + } - fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error> { - self.0.set_nonblocking(flag)?; - Ok(()) - } - } + fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error> { + self.0 + .as_socketlike_view::() + .set_nonblocking(flag)?; + Ok(()) + } - #[cfg(windows)] - impl AsSocket for $ty { - #[inline] - fn as_socket(&self) -> BorrowedSocket<'_> { - self.0.as_socket() - } + async fn sock_recv<'a>( + &mut self, + ri_data: &mut [io::IoSliceMut<'a>], + ri_flags: RiFlags, + ) -> Result<(u64, RoFlags), Error> { + if (ri_flags & !(RiFlags::RECV_PEEK | RiFlags::RECV_WAITALL)) != RiFlags::empty() { + return Err(Error::not_supported()); } - #[cfg(windows)] - impl AsHandleOrSocket for $ty { - #[inline] - fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket { - self.0.as_handle_or_socket() + if ri_flags.contains(RiFlags::RECV_PEEK) { + if let Some(first) = ri_data.iter_mut().next() { + let n = self.0.peek(first)?; + return Ok((n as u64, RoFlags::empty())); + } else { + return Ok((0, RoFlags::empty())); } } - #[cfg(unix)] - impl AsFd for $ty { - fn as_fd(&self) -> BorrowedFd<'_> { - self.0.as_fd() - } + if ri_flags.contains(RiFlags::RECV_WAITALL) { + let n: usize = ri_data.iter().map(|buf| buf.len()).sum(); + self.0.read_exact_vectored(ri_data)?; + return Ok((n as u64, RoFlags::empty())); } - }; -} -macro_rules! wasi_tcp_listener_impl { - ($ty:ty, $stream:ty) => { - #[async_trait::async_trait] - impl WasiTcpListener for $ty { - fn as_any(&self) -> &dyn Any { - self - } - - async fn accept( - &mut self, - nonblocking: bool, - ) -> Result< - ( - Box, - Box, - Box, - SocketAddr, - ), - Error, - > { - let (stream, addr) = self.0.accept()?; - stream.set_nonblocking(nonblocking)?; - let connection = <$stream>::from_cap_std(stream); - let input_stream = connection.clone(); - let output_stream = connection.clone(); - Ok(( - Box::new(connection), - Box::new(input_stream), - Box::new(output_stream), - addr, - )) - } + let n = self.0.read_vectored(ri_data)?; + Ok((n as u64, RoFlags::empty())) + } - fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error> { - self.0.set_nonblocking(flag)?; - Ok(()) - } + async fn sock_send<'a>(&mut self, si_data: &[io::IoSlice<'a>]) -> Result { + let n = self.0.write_vectored(si_data)?; + Ok(n as u64) + } - fn into_listener(self) -> Box { - Box::new(self) - } + async fn readable(&self) -> Result<(), Error> { + if is_read_write(&self.0)?.0 { + Ok(()) + } else { + Err(Error::badf()) } - }; -} - -pub struct TcpListener(cap_std::net::TcpListener); - -impl TcpListener { - pub fn from_cap_std(cap_std: cap_std::net::TcpListener) -> Self { - TcpListener(cap_std) } -} -wasi_listener_impl!(TcpListener, TcpStream); -wasi_tcp_listener_impl!(TcpListener, TcpStream); - -#[cfg(unix)] -pub struct UnixListener(cap_std::os::unix::net::UnixListener); -#[cfg(unix)] -impl UnixListener { - pub fn from_cap_std(cap_std: cap_std::os::unix::net::UnixListener) -> Self { - UnixListener(cap_std) + async fn writable(&self) -> Result<(), Error> { + if is_read_write(&self.0)?.1 { + Ok(()) + } else { + Err(Error::badf()) + } } } -#[cfg(unix)] -wasi_listener_impl!(UnixListener, UnixStream); - -macro_rules! wasi_stream_write_impl { - ($ty:ty, $std_ty:ty) => { - #[async_trait::async_trait] - impl WasiConnection for $ty { - fn as_any(&self) -> &dyn Any { - self - } - - async fn sock_recv<'a>( - &mut self, - ri_data: &mut [io::IoSliceMut<'a>], - ri_flags: RiFlags, - ) -> Result<(u64, RoFlags), Error> { - if (ri_flags & !(RiFlags::RECV_PEEK | RiFlags::RECV_WAITALL)) != RiFlags::empty() { - return Err(Error::not_supported()); - } - - if ri_flags.contains(RiFlags::RECV_PEEK) { - if let Some(first) = ri_data.iter_mut().next() { - let n = self.0.peek(first)?; - return Ok((n as u64, RoFlags::empty())); - } else { - return Ok((0, RoFlags::empty())); - } - } - - if ri_flags.contains(RiFlags::RECV_WAITALL) { - let n: usize = ri_data.iter().map(|buf| buf.len()).sum(); - self.0.read_exact_vectored(ri_data)?; - return Ok((n as u64, RoFlags::empty())); - } - - let n = self.0.read_vectored(ri_data)?; - Ok((n as u64, RoFlags::empty())) - } - - async fn sock_send<'a>( - &mut self, - si_data: &[io::IoSlice<'a>], - si_flags: SiFlags, - ) -> Result { - if si_flags != SiFlags::empty() { - return Err(Error::not_supported()); - } - - let n = self.0.write_vectored(si_data)?; - Ok(n as u64) - } - - async fn sock_shutdown(&mut self, how: SdFlags) -> Result<(), Error> { - let how = if how == SdFlags::RD | SdFlags::WR { - cap_std::net::Shutdown::Both - } else if how == SdFlags::RD { - cap_std::net::Shutdown::Read - } else if how == SdFlags::WR { - cap_std::net::Shutdown::Write - } else { - return Err(Error::invalid_argument()); - }; - self.0.shutdown(how)?; - Ok(()) - } - - fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error> { - self.0.set_nonblocking(flag)?; - Ok(()) - } +#[async_trait::async_trait] +impl InputStream for TcpSocket { + fn as_any(&self) -> &dyn Any { + self + } + #[cfg(unix)] + fn pollable_read(&self) -> Option { + Some(self.0.as_fd()) + } - async fn readable(&self) -> Result<(), Error> { - if is_read_write(&*self.0)?.0 { - Ok(()) - } else { - Err(Error::badf()) - } - } + #[cfg(windows)] + fn pollable_read(&self) -> Option { + Some(self.0.as_handle_or_socket()) + } - async fn writable(&self) -> Result<(), Error> { - if is_read_write(&*self.0)?.1 { - Ok(()) - } else { - Err(Error::badf()) - } - } + async fn read(&mut self, buf: &mut [u8]) -> Result<(u64, bool), Error> { + match Read::read(&mut &*self.as_socketlike_view::(), buf) { + Ok(0) => Ok((0, true)), + Ok(n) => Ok((n as u64, false)), + Err(err) if err.kind() == io::ErrorKind::Interrupted => Ok((0, false)), + Err(err) => Err(err.into()), } + } + async fn read_vectored<'a>( + &mut self, + bufs: &mut [io::IoSliceMut<'a>], + ) -> Result<(u64, bool), Error> { + match Read::read_vectored(&mut &*self.as_socketlike_view::(), bufs) { + Ok(0) => Ok((0, true)), + Ok(n) => Ok((n as u64, false)), + Err(err) if err.kind() == io::ErrorKind::Interrupted => Ok((0, false)), + Err(err) => Err(err.into()), + } + } + #[cfg(can_vector)] + fn is_read_vectored(&self) -> bool { + Read::is_read_vectored(&mut &*self.as_socketlike_view::()) + } - #[async_trait::async_trait] - impl InputStream for $ty { - fn as_any(&self) -> &dyn Any { - self - } - #[cfg(unix)] - fn pollable_read(&self) -> Option { - Some(self.0.as_fd()) - } - - #[cfg(windows)] - fn pollable_read(&self) -> Option { - Some(self.0.as_handle_or_socket()) - } - - async fn read(&mut self, buf: &mut [u8]) -> Result<(u64, bool), Error> { - match Read::read(&mut &*self.as_socketlike_view::<$std_ty>(), buf) { - Ok(0) => Ok((0, true)), - Ok(n) => Ok((n as u64, false)), - Err(err) if err.kind() == io::ErrorKind::Interrupted => Ok((0, false)), - Err(err) => Err(err.into()), - } - } - async fn read_vectored<'a>( - &mut self, - bufs: &mut [io::IoSliceMut<'a>], - ) -> Result<(u64, bool), Error> { - match Read::read_vectored(&mut &*self.as_socketlike_view::<$std_ty>(), bufs) { - Ok(0) => Ok((0, true)), - Ok(n) => Ok((n as u64, false)), - Err(err) if err.kind() == io::ErrorKind::Interrupted => Ok((0, false)), - Err(err) => Err(err.into()), - } - } - #[cfg(can_vector)] - fn is_read_vectored(&self) -> bool { - Read::is_read_vectored(&mut &*self.as_socketlike_view::<$std_ty>()) - } - - async fn skip(&mut self, nelem: u64) -> Result<(u64, bool), Error> { - let num = io::copy(&mut io::Read::take(&*self.0, nelem), &mut io::sink())?; - Ok((num, num < nelem)) - } + async fn skip(&mut self, nelem: u64) -> Result<(u64, bool), Error> { + let num = io::copy( + &mut io::Read::take(&*self.0.as_socketlike_view::(), nelem), + &mut io::sink(), + )?; + Ok((num, num < nelem)) + } - async fn num_ready_bytes(&self) -> Result { - let val = self.as_socketlike_view::<$std_ty>().num_ready_bytes()?; - Ok(val) - } + async fn num_ready_bytes(&self) -> Result { + let val = self.as_socketlike_view::().num_ready_bytes()?; + Ok(val) + } - async fn readable(&self) -> Result<(), Error> { - if is_read_write(&*self.0)?.0 { - Ok(()) - } else { - Err(Error::badf()) - } - } + async fn readable(&self) -> Result<(), Error> { + if is_read_write(&self.0)?.0 { + Ok(()) + } else { + Err(Error::badf()) } - #[async_trait::async_trait] - impl OutputStream for $ty { - fn as_any(&self) -> &dyn Any { - self - } + } +} - #[cfg(unix)] - fn pollable_write(&self) -> Option { - Some(self.0.as_fd()) - } +#[async_trait::async_trait] +impl OutputStream for TcpSocket { + fn as_any(&self) -> &dyn Any { + self + } - #[cfg(windows)] - fn pollable_write(&self) -> Option { - Some(self.0.as_handle_or_socket()) - } + #[cfg(unix)] + fn pollable_write(&self) -> Option { + Some(self.0.as_fd()) + } - async fn write(&mut self, buf: &[u8]) -> Result { - let n = Write::write(&mut &*self.as_socketlike_view::<$std_ty>(), buf)?; - Ok(n.try_into()?) - } - async fn write_vectored<'a>(&mut self, bufs: &[io::IoSlice<'a>]) -> Result { - let n = Write::write_vectored(&mut &*self.as_socketlike_view::<$std_ty>(), bufs)?; - Ok(n.try_into()?) - } - #[cfg(can_vector)] - fn is_write_vectored(&self) -> bool { - Write::is_write_vectored(&mut &*self.as_socketlike_view::<$std_ty>()) - } - async fn splice( - &mut self, - src: &mut dyn InputStream, - nelem: u64, - ) -> Result<(u64, bool), Error> { - if let Some(readable) = src.pollable_read() { - let num = io::copy( - &mut io::Read::take(BorrowedReadable::borrow(readable), nelem), - &mut &*self.0, - )?; - Ok((num, num < nelem)) - } else { - OutputStream::splice(self, src, nelem).await - } - } - async fn write_zeroes(&mut self, nelem: u64) -> Result { - let num = io::copy(&mut io::Read::take(io::repeat(0), nelem), &mut &*self.0)?; - Ok(num) - } - async fn writable(&self) -> Result<(), Error> { - if is_read_write(&*self.0)?.1 { - Ok(()) - } else { - Err(Error::badf()) - } - } - } - #[cfg(unix)] - impl AsFd for $ty { - fn as_fd(&self) -> BorrowedFd<'_> { - self.0.as_fd() - } - } + #[cfg(windows)] + fn pollable_write(&self) -> Option { + Some(self.0.as_handle_or_socket()) + } - #[cfg(windows)] - impl AsSocket for $ty { - /// Borrows the socket. - fn as_socket(&self) -> BorrowedSocket<'_> { - self.0.as_socket() - } + async fn write(&mut self, buf: &[u8]) -> Result { + let n = Write::write(&mut &*self.as_socketlike_view::(), buf)?; + Ok(n.try_into()?) + } + async fn write_vectored<'a>(&mut self, bufs: &[io::IoSlice<'a>]) -> Result { + let n = Write::write_vectored(&mut &*self.as_socketlike_view::(), bufs)?; + Ok(n.try_into()?) + } + #[cfg(can_vector)] + fn is_write_vectored(&self) -> bool { + Write::is_write_vectored(&mut &*self.as_socketlike_view::()) + } + async fn splice( + &mut self, + src: &mut dyn InputStream, + nelem: u64, + ) -> Result<(u64, bool), Error> { + if let Some(readable) = src.pollable_read() { + let num = io::copy( + &mut io::Read::take(BorrowedReadable::borrow(readable), nelem), + &mut &*self.0.as_socketlike_view::(), + )?; + Ok((num, num < nelem)) + } else { + OutputStream::splice(self, src, nelem).await } - - #[cfg(windows)] - impl AsHandleOrSocket for TcpStream { - #[inline] - fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket { - self.0.as_handle_or_socket() - } + } + async fn write_zeroes(&mut self, nelem: u64) -> Result { + let num = io::copy( + &mut io::Read::take(io::repeat(0), nelem), + &mut &*self.0.as_socketlike_view::(), + )?; + Ok(num) + } + async fn writable(&self) -> Result<(), Error> { + if is_read_write(&self.0)?.1 { + Ok(()) + } else { + Err(Error::badf()) } - }; -} - -pub struct TcpStream(Arc); - -impl TcpStream { - pub fn from_cap_std(socket: cap_std::net::TcpStream) -> Self { - Self(Arc::new(socket)) } +} - pub fn clone(&self) -> Self { - Self(Arc::clone(&self.0)) +#[cfg(unix)] +impl AsFd for TcpSocket { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() } } -wasi_stream_write_impl!(TcpStream, std::net::TcpStream); - #[cfg(unix)] -pub struct UnixStream(Arc); +impl AsFd for UdpSocket { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} -#[cfg(unix)] -impl UnixStream { - pub fn from_cap_std(socket: cap_std::os::unix::net::UnixStream) -> Self { - Self(Arc::new(socket)) +#[cfg(windows)] +impl AsSocket for TcpSocket { + /// Borrows the socket. + fn as_socket(&self) -> BorrowedSocket<'_> { + self.0.as_socket() } +} - pub fn clone(&self) -> Self { - Self(Arc::clone(&self.0)) +#[cfg(windows)] +impl AsHandleOrSocket for TcpSocket { + #[inline] + fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket { + self.0.as_handle_or_socket() + } +} +#[cfg(windows)] +impl AsSocket for UdpSocket { + /// Borrows the socket. + fn as_socket(&self) -> BorrowedSocket<'_> { + self.0.as_socket() } } -#[cfg(unix)] -wasi_stream_write_impl!(UnixStream, std::os::unix::net::UnixStream); +#[cfg(windows)] +impl AsHandleOrSocket for UdpSocket { + #[inline] + fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket { + self.0.as_handle_or_socket() + } +} /// Return the file-descriptor flags for a given file-like object. /// diff --git a/wasi-common/src/connection.rs b/wasi-common/src/connection.rs deleted file mode 100644 index 1c41f704..00000000 --- a/wasi-common/src/connection.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Socket connections. - -use crate::Error; -use bitflags::bitflags; -use std::any::Any; - -/// A socket connection. -#[async_trait::async_trait] -pub trait WasiConnection: Send + Sync { - fn as_any(&self) -> &dyn Any; - - async fn sock_recv<'a>( - &mut self, - ri_data: &mut [std::io::IoSliceMut<'a>], - ri_flags: RiFlags, - ) -> Result<(u64, RoFlags), Error>; - - async fn sock_send<'a>( - &mut self, - si_data: &[std::io::IoSlice<'a>], - si_flags: SiFlags, - ) -> Result; - - async fn sock_shutdown(&mut self, _how: SdFlags) -> Result<(), Error>; - - fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error>; - - async fn readable(&self) -> Result<(), Error>; - - async fn writable(&self) -> Result<(), Error>; -} - -bitflags! { - pub struct SdFlags: u32 { - const RD = 0b1; - const WR = 0b10; - } -} - -bitflags! { - pub struct SiFlags: u32 { - } -} - -bitflags! { - pub struct RiFlags: u32 { - const RECV_PEEK = 0b1; - const RECV_WAITALL = 0b10; - } -} - -bitflags! { - pub struct RoFlags: u32 { - const RECV_DATA_TRUNCATED = 0b1; - } -} - -pub trait TableConnectionExt { - fn get_connection(&self, fd: u32) -> Result<&dyn WasiConnection, Error>; - fn get_connection_mut(&mut self, fd: u32) -> Result<&mut Box, Error>; -} -impl TableConnectionExt for crate::table::Table { - fn get_connection(&self, fd: u32) -> Result<&dyn WasiConnection, Error> { - self.get::>(fd).map(|f| f.as_ref()) - } - fn get_connection_mut(&mut self, fd: u32) -> Result<&mut Box, Error> { - self.get_mut::>(fd) - } -} diff --git a/wasi-common/src/ctx.rs b/wasi-common/src/ctx.rs index 1b4a4968..36757be9 100644 --- a/wasi-common/src/ctx.rs +++ b/wasi-common/src/ctx.rs @@ -1,10 +1,10 @@ use crate::clocks::WasiClocks; use crate::dir::WasiDir; use crate::file::WasiFile; -use crate::listener::WasiListener; use crate::sched::WasiSched; use crate::stream::{InputStream, OutputStream}; use crate::table::Table; +use crate::tcp_socket::WasiTcpSocket; use crate::Error; use cap_rand::RngCore; @@ -46,7 +46,7 @@ impl WasiCtx { self.table_mut().insert_at(fd, Box::new(stream)); } - pub fn insert_listener(&mut self, fd: u32, listener: Box) { + pub fn insert_listener(&mut self, fd: u32, listener: Box) { self.table_mut().insert_at(fd, Box::new(listener)); } diff --git a/wasi-common/src/lib.rs b/wasi-common/src/lib.rs index c3d70f7d..9f7763ca 100644 --- a/wasi-common/src/lib.rs +++ b/wasi-common/src/lib.rs @@ -52,29 +52,27 @@ //! `wasi_cap_std_sync::WasiCtxBuilder::new()` function uses this public //! interface to plug in its own implementations of each of these resources. pub mod clocks; -pub mod connection; mod ctx; pub mod dir; mod error; pub mod file; -pub mod listener; pub mod pipe; pub mod random; pub mod sched; pub mod stream; pub mod table; -pub mod tcp_listener; +pub mod tcp_socket; +pub mod udp_socket; pub use cap_fs_ext::SystemTimeSpec; pub use cap_rand::RngCore; pub use clocks::{WasiClocks, WasiMonotonicClock, WasiWallClock}; -pub use connection::WasiConnection; pub use ctx::WasiCtx; pub use dir::WasiDir; pub use error::{Errno, Error, ErrorExt, I32Exit}; pub use file::WasiFile; -pub use listener::WasiListener; pub use sched::{Poll, WasiSched}; pub use stream::{InputStream, OutputStream}; pub use table::Table; -pub use tcp_listener::WasiTcpListener; +pub use tcp_socket::WasiTcpSocket; +pub use udp_socket::WasiUdpSocket; diff --git a/wasi-common/src/listener.rs b/wasi-common/src/listener.rs deleted file mode 100644 index b8eb34a7..00000000 --- a/wasi-common/src/listener.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Socket listeners. - -use crate::connection::WasiConnection; -use crate::Error; -use crate::{InputStream, OutputStream}; -use std::any::Any; - -/// A socket listener. -#[async_trait::async_trait] -pub trait WasiListener: Send + Sync { - fn as_any(&self) -> &dyn Any; - - async fn accept( - &mut self, - nonblocking: bool, - ) -> Result< - ( - Box, - Box, - Box, - ), - Error, - >; - - fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error>; -} - -pub trait TableListenerExt { - fn get_listener(&self, fd: u32) -> Result<&dyn WasiListener, Error>; - fn get_listener_mut(&mut self, fd: u32) -> Result<&mut Box, Error>; -} -impl TableListenerExt for crate::table::Table { - fn get_listener(&self, fd: u32) -> Result<&dyn WasiListener, Error> { - self.get::>(fd).map(|f| f.as_ref()) - } - fn get_listener_mut(&mut self, fd: u32) -> Result<&mut Box, Error> { - self.get_mut::>(fd) - } -} diff --git a/wasi-common/src/tcp_listener.rs b/wasi-common/src/tcp_listener.rs deleted file mode 100644 index 337567a8..00000000 --- a/wasi-common/src/tcp_listener.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! TCP socket listeners. - -use crate::connection::WasiConnection; -use crate::Error; -use crate::WasiListener; -use crate::{InputStream, OutputStream}; -use std::any::Any; -use std::net::SocketAddr; - -/// A TCP socket listener. -#[async_trait::async_trait] -pub trait WasiTcpListener: Send + Sync { - fn as_any(&self) -> &dyn Any; - - async fn accept( - &mut self, - nonblocking: bool, - ) -> Result< - ( - Box, - Box, - Box, - SocketAddr, - ), - Error, - >; - - fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error>; - - fn into_listener(self) -> Box; -} - -pub trait TableTcpListenerExt { - fn get_tcp_listener(&self, fd: u32) -> Result<&dyn WasiTcpListener, Error>; - fn get_tcp_listener_mut(&mut self, fd: u32) -> Result<&mut Box, Error>; -} -impl TableTcpListenerExt for crate::table::Table { - fn get_tcp_listener(&self, fd: u32) -> Result<&dyn WasiTcpListener, Error> { - self.get::>(fd).map(|f| f.as_ref()) - } - fn get_tcp_listener_mut(&mut self, fd: u32) -> Result<&mut Box, Error> { - self.get_mut::>(fd) - } -} diff --git a/wasi-common/src/tcp_socket.rs b/wasi-common/src/tcp_socket.rs new file mode 100644 index 00000000..4d6b00a5 --- /dev/null +++ b/wasi-common/src/tcp_socket.rs @@ -0,0 +1,54 @@ +//! TCP sockets. + +use crate::Error; +use crate::{InputStream, OutputStream}; +use bitflags::bitflags; +use std::any::Any; +use std::net::SocketAddr; + +/// A TCP socket. +#[async_trait::async_trait] +pub trait WasiTcpSocket: Send + Sync { + fn as_any(&self) -> &dyn Any; + + async fn accept( + &mut self, + nonblocking: bool, + ) -> Result< + ( + Box, + Box, + Box, + SocketAddr, + ), + Error, + >; + + async fn sock_shutdown(&mut self, how: SdFlags) -> Result<(), Error>; + + fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error>; + + async fn readable(&self) -> Result<(), Error>; + + async fn writable(&self) -> Result<(), Error>; +} + +bitflags! { + pub struct SdFlags: u32 { + const READ = 0b1; + const WRITE = 0b10; + } +} + +pub trait TableTcpSocketExt { + fn get_tcp_socket(&self, fd: u32) -> Result<&dyn WasiTcpSocket, Error>; + fn get_tcp_socket_mut(&mut self, fd: u32) -> Result<&mut Box, Error>; +} +impl TableTcpSocketExt for crate::table::Table { + fn get_tcp_socket(&self, fd: u32) -> Result<&dyn WasiTcpSocket, Error> { + self.get::>(fd).map(|f| f.as_ref()) + } + fn get_tcp_socket_mut(&mut self, fd: u32) -> Result<&mut Box, Error> { + self.get_mut::>(fd) + } +} diff --git a/wasi-common/src/udp_socket.rs b/wasi-common/src/udp_socket.rs new file mode 100644 index 00000000..f849c656 --- /dev/null +++ b/wasi-common/src/udp_socket.rs @@ -0,0 +1,51 @@ +//! UDP sockets. + +use crate::Error; +use bitflags::bitflags; +use std::any::Any; + +/// A UDP socket. +#[async_trait::async_trait] +pub trait WasiUdpSocket: Send + Sync { + fn as_any(&self) -> &dyn Any; + + async fn sock_recv<'a>( + &mut self, + ri_data: &mut [std::io::IoSliceMut<'a>], + ri_flags: RiFlags, + ) -> Result<(u64, RoFlags), Error>; + + async fn sock_send<'a>(&mut self, si_data: &[std::io::IoSlice<'a>]) -> Result; + + fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error>; + + async fn readable(&self) -> Result<(), Error>; + + async fn writable(&self) -> Result<(), Error>; +} + +bitflags! { + pub struct RoFlags: u32 { + const RECV_DATA_TRUNCATED = 0b1; + } +} + +bitflags! { + pub struct RiFlags: u32 { + const RECV_PEEK = 0b1; + const RECV_WAITALL = 0b10; + } +} + +pub trait TableUdpSocketExt { + fn get_udp_socket(&self, fd: u32) -> Result<&dyn WasiUdpSocket, Error>; + fn get_udp_socket_mut(&mut self, fd: u32) -> Result<&mut Box, Error>; +} +impl TableUdpSocketExt for crate::table::Table { + fn get_udp_socket(&self, fd: u32) -> Result<&dyn WasiUdpSocket, Error> { + self.get::>(fd).map(|f| f.as_ref()) + } + fn get_udp_socket_mut(&mut self, fd: u32) -> Result<&mut Box, Error> { + self.get_mut::>(fd) + } +} diff --git a/wit/wasi-command.wit b/wit/wasi-command.wit index 6c245a8f..33797d1a 100644 --- a/wit/wasi-command.wit +++ b/wit/wasi-command.wit @@ -9,9 +9,11 @@ default world wasi-command { import wasi-random: pkg.wasi-random import wasi-poll: pkg.wasi-poll import wasi-io: pkg.wasi-io + import wasi-default-network: pkg.wasi-default-network + import wasi-ip-name-lookup: pkg.wasi-ip-name-lookup import wasi-tcp: pkg.wasi-tcp - import wasi-ip: pkg.wasi-ip - import wasi-dns: pkg.wasi-dns + import wasi-udp: pkg.wasi-udp + import wasi-network: pkg.wasi-network import wasi-exit: pkg.wasi-exit export command: func( diff --git a/wit/wasi-default-network.wit b/wit/wasi-default-network.wit new file mode 100644 index 00000000..c9cd3b0e --- /dev/null +++ b/wit/wasi-default-network.wit @@ -0,0 +1,9 @@ + +/// This interface provides a value-export of the default network handle.. +default interface wasi-default-network { + use pkg.wasi-network.{network} + + /// Get a handle to the default network. + default-network: func() -> network + +} \ No newline at end of file diff --git a/wit/wasi-dns.wit b/wit/wasi-dns.wit deleted file mode 100644 index d7b1e947..00000000 --- a/wit/wasi-dns.wit +++ /dev/null @@ -1,83 +0,0 @@ -/// This interface allows callers to perform DNS resolution. Since the existing definition in wasi-sockets uses currently unsupported wit features, I have rewritten this interface to be usable on existing features. -/// -/// First, you call resolve-name() to create a resolver descriptor. Then, you call resolve-next(resolver) in a loop to fetch the IP addresses. Finally, you destroy the resolver with close() (like for all descriptors). This structure allows for non-blocking operation in conjunction with poll-oneoff(). -default interface wasi-dns { - // TODO: use { ip-address-family, ip-address } from wasi-ip - enum ip-address-family { - /// Similar to `AF_INET` in POSIX. - ipv4, - - /// Similar to `AF_INET6` in POSIX. - ipv6, - } - type ipv4-address = tuple - type ipv6-address = tuple - variant ip-address { - ipv4(ipv4-address), - ipv6(ipv6-address), - } - - /// Resolution flags. - flags resolver-flags { - /// Equivalent to `O_NONBLOCK`. - nonblock, - } - - /// An iterator over resolution results. - /// - /// In the future, this will be replaced by handle types. - type resolver = u32 - - // TODO: use { network } from wasi-net - type network = u32 - - enum resolve-name-error { - invalid-name, - } - - enum resolve-error { - /// The resolve is in non-blocking mode and the request would block. - would-block, - /// The DNS resolver is unable to provide results. - dns-unavailable, - } - - /// Starts resolving an internet host name to a list of IP addresses. - /// - /// This function returns a new resolver on success or an error if - /// immediately available. For example, this function fails with - /// `invalid-name` when `name` is: - /// - empty - /// - an IP address - /// - a syntactically invalid domain name in another way - resolve-name: func( - network: network, - - /// The name to look up. - /// - /// IP addresses are not allowed and will fail with `invalid-name`. - /// - /// Unicode domain names are automatically converted to ASCII using - /// IDNA encoding. - name: string, - - /// If provided, limit the results the specified address family. - address-family: option, - - /// Flags controlling the behavior of the name resolution. - %flags: resolver-flags - ) -> result - - /// Get the next address from the resolver. - /// - /// This function should be called multiple times. On each call, it will - /// return the next address in connection order preference. If all - /// addresses have been exhausted, this function returns `none`. If - /// non-blocking mode is used, this function may return `errno::again` - /// indicating that the caller should poll for incoming data. - /// This function never returns IPv4-mapped IPv6 addresses. - resolve-next: func(resolver: resolver) -> result, resolve-error> - - /// Closes a handle returned by `resolve-name`. - close-resolver: func(resolver: resolver) -} diff --git a/wit/wasi-ip-name-lookup.wit b/wit/wasi-ip-name-lookup.wit new file mode 100644 index 00000000..67fa0a3d --- /dev/null +++ b/wit/wasi-ip-name-lookup.wit @@ -0,0 +1,69 @@ + +default interface wasi-ip-name-lookup { + use pkg.wasi-poll.{pollable} + use pkg.wasi-network.{network, error, ip-address, ip-address-family} + + + /// Resolve an internet host name to a list of IP addresses. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// Parameters: + /// - `name`: The name to look up. IP addresses are not allowed. Unicode domain names are automatically converted + /// to ASCII using IDNA encoding. + /// - `address-family`: If provided, limit the results to addresses of this specific address family. + /// - `include-unavailable`: When set to true, this function will also return addresses of which the runtime + /// thinks (or knows) can't be connected to at the moment. For example, this will return IPv6 addresses on + /// systems without an active IPv6 interface. Notes: + /// - Even when no public IPv6 interfaces are present or active, names like "localhost" can still resolve to an IPv6 address. + /// - Whatever is "available" or "unavailable" is volatile and can change everytime a network cable is unplugged. + /// + /// This function never blocks. It either immediately returns successfully with a `resolve-address-stream` + /// that can be used to (asynchronously) fetch the results. + /// Or it immediately fails whenever `name` is: + /// - empty + /// - an IP address + /// - a syntactically invalid domain name in another way + /// + /// References: + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html + /// - https://man7.org/linux/man-pages/man3/getaddrinfo.3.html + /// + resolve-addresses: func(network: network, name: string, address-family: option, include-unavailable: bool) -> result + + + + type resolve-address-stream = u32 + + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// After which, you should release the stream with `drop-resolve-address-stream`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + resolve-next-address: func(this: resolve-address-stream) -> result, error> + + + + /// Dispose of the specified `resolve-address-stream`, after which it may no longer be used. + drop-resolve-address-stream: func(this: resolve-address-stream) + + /// Get/set the blocking mode of the stream. + /// + /// By default a stream is in "blocking" mode, meaning that any function blocks and waits for its completion. + /// When switched to "non-blocking" mode, operations that would block return an `again` error. After which + /// the API consumer is expected to call `subscribe` and wait for completion using the wasi-poll module. + /// + /// Note: these functions are here for WASI Preview2 only. + /// They're planned to be removed when `async` is natively supported in Preview3. + non-blocking: func(this: resolve-address-stream) -> result + set-non-blocking: func(this: resolve-address-stream, value: bool) -> result<_, error> + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `async` is natively supported in Preview3. + subscribe: func(this: resolve-address-stream) -> pollable +} diff --git a/wit/wasi-ip.wit b/wit/wasi-ip.wit deleted file mode 100644 index 1bef3504..00000000 --- a/wit/wasi-ip.wit +++ /dev/null @@ -1,35 +0,0 @@ -/// This interface defines IP-address-related data structures. Note that I chose to represent addresses as fixed tuples rather than other representations. This avoids allocations and makes it trivial to convert the addresses to strings. -default interface wasi-ip { - type ipv4-address = tuple - type ipv6-address = tuple - - enum ip-address-family { - /// Similar to `AF_INET` in POSIX. - ipv4, - - /// Similar to `AF_INET6` in POSIX. - ipv6, - } - - variant ip-address { - ipv4(ipv4-address), - ipv6(ipv6-address), - } - - record ipv4-socket-address { - address: ipv4-address, - port: u16, - } - - record ipv6-socket-address { - address: ipv6-address, - port: u16, - flow-info: u32, - scope-id: u32, - } - - variant ip-socket-address { - ipv4(ipv4-socket-address), - ipv6(ipv6-socket-address), - } -} diff --git a/wit/wasi-net.wit b/wit/wasi-net.wit deleted file mode 100644 index 12d7440e..00000000 --- a/wit/wasi-net.wit +++ /dev/null @@ -1,5 +0,0 @@ -/// This interface defines an opaque descriptor type that represents a particular network. This enables context-based security for networking. -default interface wasi-net { - /// A network, possibly virtual. - type network = u32 -} diff --git a/wit/wasi-network.wit b/wit/wasi-network.wit new file mode 100644 index 00000000..80fc56c2 --- /dev/null +++ b/wit/wasi-network.wit @@ -0,0 +1,54 @@ + +default interface wasi-network { + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + /// + /// FYI, In the future this will be replaced by handle types. + type network = u32 + + /// Dispose of the specified `network`, after which it may no longer be used. + drop-network: func(this: network) + + + + enum error { + unknown, + again, + // TODO ... + } + + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + type ipv4-address = tuple + type ipv6-address = tuple + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + record ipv4-socket-address { + port: u16, // sin_port + address: ipv4-address, // sin_addr + } + + record ipv6-socket-address { + port: u16, // sin6_port + flow-info: u32, // sin6_flowinfo + address: ipv6-address, // sin6_addr + scope-id: u32, // sin6_scope_id + } + + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + +} \ No newline at end of file diff --git a/wit/wasi-tcp.wit b/wit/wasi-tcp.wit index b5681c20..4b53359f 100644 --- a/wit/wasi-tcp.wit +++ b/wit/wasi-tcp.wit @@ -1,279 +1,206 @@ -/// This interface represents the core of TCP networking. There are two types of TCP descriptors: -/// -/// - a listener, which produces new incoming connections; and ... -/// - a connection, which actually transfers data to remote parties. -/// -/// Note that this interface does not implement bind(). Binding inputs happen as part of the listen() or connect() calls. -/// -/// Non-blocking connections should be supported by calling connect() with the nonblock flag. Then you poll on the resulting connection for output. Once poll returns, you can call `is-connected` to determine the connection status and receive(connection, 1) to collect the error status. This follows the existing usage under POSIX. -default interface wasi-tcp { - use pkg.wasi-poll.{pollable} - use pkg.wasi-io.{input-stream, output-stream} - use pkg.wasi-net.{network} - - // TODO: Use ip-socket-address from wasi-ip. - //use pkg.wasi-ip.{ip-socket-address} - type ipv4-address = tuple - type ipv6-address = tuple - record ipv4-socket-address { - address: ipv4-address, - port: u16, - } - record ipv6-socket-address { - address: ipv6-address, - port: u16, - flow-info: u32, - scope-id: u32, - } - variant ip-socket-address { - ipv4(ipv4-socket-address), - ipv6(ipv6-socket-address), - } - - /// Size of a range of bytes that may be ready to be read. - type io-size = u64 - - /// Error codes returned by functions. - /// Not all of these error codes are returned by the functions provided by this - /// API; some are used in higher-level library layers, and others are provided - /// merely for alignment with POSIX. - enum errno { - /// Permission denied. - access, - /// Address in use. - addrinuse, - /// Address not available. - addrnotavail, - /// Address family not supported. - afnosupport, - /// Resource unavailable, or operation would block. - again, - /// Connection already in progress. - already, - /// Bad descriptor. - badf, - /// Device or resource busy. - busy, - /// Connection aborted. - connection-aborted, - /// Connection refused. - connection-refused, - /// Connection reset. - connection-reset, - /// Resource deadlock would occur. - deadlock, - /// Destination address required. - destaddrreq, - /// Host is unreachable. - host-unreachable, - /// Illegal byte sequence. - ilseq, - /// Operation in progress. - inprogress, - /// Interrupted function. - intr, - /// Invalid argument. - inval, - /// I/O error. - io, - /// Socket is connected. - isconn, - /// Message too large. - msgsize, - /// Multihop attempted. - multihop, - /// Filename too long. - nametoolong, - /// Network is down. - network-down, - /// Connection aborted by network. - network-reset, - /// Network unreachable. - network-unreachable, - /// No buffer space available. - nobufs, - /// No such file or directory. - noent, - /// Not enough space. - nomem, - /// Protocol not available. - noprotoopt, - /// Function not supported. - nosys, - /// State not recoverable. - notrecoverable, - /// Not supported, or operation not supported on socket. - notsup, - /// Value too large to be stored in data type. - overflow, - /// Operation not permitted. - perm, - /// Connection timed out. - timedout, - } - - /// Listener flags. - flags listener-flags { - /// Equivalent to `O_NONBLOCK`. - nonblock, - } - - /// Connection flags. - flags connection-flags { - /// Equivalent to `SO_KEEPALIVE`. - keepalive, - - /// Equivalent to `O_NONBLOCK`. - nonblock, - - /// Equivalent to `TCP_NODELAY`. - nodelay, - } - - /// A "socket" descriptor for a listener. In the future, this will be - /// replaced by handle types. - /// fixme: move this and related stuff into a separate wasi-socket? - type listener = u32 - - /// A "socket" descriptor for a TCP listener. In the future, this will be - /// replaced by handle types. - type tcp-listener = u32 - - /// A "socket" descriptor for a TCP connection. In the future, this will be - /// replaced by handle types. - type connection = u32 - - /// Creates a new listener. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), the - /// implementation will decide which network address to bind to. - /// - /// If the TCP/UDP port is zero, the socket will be bound to an - /// unspecified free port. - /// - /// The listener should be destroyed with `close-tcp-listener` when no longer in use. - listen: func( - network: network, - address: ip-socket-address, - backlog: option, - %flags: listener-flags - ) -> result - - /// Accepts a new incoming connection. - /// - /// When in non-blocking mode, this function will return `errno::again` - /// when no new incoming connection is immediately available. This is an - /// indication to poll for incoming data on the listener. Otherwise, this - /// function will block until an incoming connection is available. - /// - /// Returns a tuple of a connection handle, and input stream, and an output - /// stream for the socket. - /// - /// The connection should be destroyed with `close-connection` when no longer in use. - accept: func( - listener: listener, - %flags: connection-flags - ) -> result, errno> - - /// Accepts a new incoming connection on a TCP socket. - /// - /// This is the same as `accept`, but takes a `tcp-listener` - /// and additionally returns an `ip-socket-address`. - accept-tcp: func( - listener: tcp-listener, - %flags: connection-flags - ) -> result, errno> - /// Connect to a remote endpoint. - /// - /// If the local IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), the - /// implementation will decide which network address to bind to. - /// - /// If the local TCP/UDP port is zero, the socket will be bound to an - /// unspecified free port. - /// - /// The connection should be destroyed with `close-connection` when no longer in use. - /// - /// References - /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html - /// - https://man7.org/linux/man-pages/man2/bind.2.html - /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html - /// - https://man7.org/linux/man-pages/man2/connect.2.html - connect: func( - network: network, - local-address: ip-socket-address, - remote-address: ip-socket-address, - %flags: connection-flags, - ) -> result, errno> - - /// Send bytes to the remote connection. - /// - /// This function may not successfully send all bytes. Check the number of - /// bytes returned. - /// - /// Note: This is similar to `pwrite` in POSIX. - send: func(connection: connection, bytes: list) -> result - - /// Receive bytes from the remote connection. - /// - /// This function receives **at most** `length` bytes from the remote - /// connection. - /// - /// Note: This is similar to `recv` in POSIX. - receive: func(connection: connection, length: io-size) -> result, bool>, errno> - - /// Get the flags set for the connection. - get-flags: func(connection: connection) -> result - - /// Sets the flags for the connection. - set-flags: func(connection: connection, %flags: connection-flags) -> result<_, errno> - - /// Gets the receive-buffer size. - /// - /// Note: this is only a hint. Implementations may internally handle this - /// in any way, including ignoring it. - /// - /// Equivalent to `SO_RCVBUF`. - get-receive-buffer-size: func(connection: connection) -> result - - /// Gets the receive-buffer size. - /// - /// Note: this is only a hint. Implementations may internally handle this - /// in any way, including ignoring it. - /// - /// Equivalent to `SO_RCVBUF`. - set-receive-buffer-size: func(connection: connection, value: io-size) -> result<_, errno> - - /// Gets the send-buffer size. - /// - /// Note: this is only a hint. Implementations may internally handle this - /// in any way, including ignoring it. - /// - /// Equivalent to `SO_SNDBUF`. - get-send-buffer-size: func(connection: connection) -> result - - /// Sets the send-buffer size. - /// - /// Note: this is only a hint. Implementations may internally handle this - /// in any way, including ignoring it. - /// - /// Equivalent to `SO_SNDBUF`. - set-send-buffer-size: func(connection: connection, value: io-size) -> result<_, errno> - - /// Query the specified `socket` for how many bytes are available to read. - bytes-readable: func(s: connection) -> result, errno> - - /// Query the specified `socket` for the number of bytes ready to be accepted. - bytes-writable: func(s: connection) -> result, errno> - - /// Test whether a connection is connected. - /// - /// In POSIX, this is typically done using `getpeername`. - is-connected: func(connection: connection) -> bool - - /// Closes a handle returned by `listen`. - close-tcp-listener: func(listener: tcp-listener) - - /// Closes a handle returned by `connect`, `accept`, or `accept-tcp`. - close-connection: func(listener: tcp-listener) +default interface wasi-tcp { + use pkg.wasi-io.{input-stream, output-stream} + use pkg.wasi-poll.{pollable} + use pkg.wasi-network.{network, error, ip-address-family} + + // TODO: For now, duplicate the types of these to work around a bug + // in wit-bindgen-0.3.0 which causes the bindings to reference + // `Ipv4SocketAddress` and `Ipv6SocketAddress without them being in + // scope. This bug is fixed in newer versions. + type ipv4-address = tuple + type ipv6-address = tuple + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + record ipv4-socket-address { + port: u16, // sin_port + address: ipv4-address, // sin_addr + } + record ipv6-socket-address { + port: u16, // sin6_port + flow-info: u32, // sin6_flowinfo + address: ipv6-address, // sin6_addr + scope-id: u32, // sin6_scope_id + } + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + + /// A TCP socket handle. + type tcp-socket = u32 + + + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + + /// Similar to `SHUT_WR` in POSIX. + send, + + /// Similar to `SHUT_RDWR` in POSIX. + both, + } + + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// + /// References: + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html + /// - https://man7.org/linux/man-pages/man2/socket.2.html + /// + create-tcp-socket: func(network: network, address-family: ip-address-family) -> result + + /// Dispose of the specified `tcp-socket`, after which it may no longer be used. + drop-tcp-socket: func(this: tcp-socket) + + /// Bind the socket to a specific IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will + /// implicitly bind the socket. + /// + /// Returns an error if the socket is already bound. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html + /// - https://man7.org/linux/man-pages/man2/bind.2.html + bind: func(this: tcp-socket, local-address: ip-socket-address) -> result<_, error> + + /// Get the current bound address. + /// + /// Returns an error if the socket is not bound. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html + /// - https://man7.org/linux/man-pages/man2/getsockname.2.html + local-address: func(this: tcp-socket) -> result + + /// Connect to a remote endpoint. + /// + /// Transitions the socket into the Connection state. + /// Fails when the socket is already in the Connection or Listener state. + /// + /// On success, this function returns a pair of streams that can be used to read & write to the connection. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html + /// - https://man7.org/linux/man-pages/man2/connect.2.html + connect: func(this: tcp-socket, remote-address: ip-socket-address) -> result, error> + + /// Gracefully shut down the connection. + /// + /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read + /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. + /// Any data still in the receive queue at time of calling `shutdown` will be discarded. + /// - send: the socket is not expecting to send any more data to the peer. All subsequent write + /// operations on the `output-stream` associated with this socket will return an error. + /// - both: same effect as receive & send combined. + /// + /// The shutdown function does not close the socket. + /// + /// Fails when the socket is not in the Connection state. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html + /// - https://man7.org/linux/man-pages/man2/shutdown.2.html + shutdown: func(this: tcp-socket, shutdown-type: shutdown-type) -> result<_, error> + + /// Start listening for new connections. + /// + /// Transitions the socket into the Listener state. + /// Fails when the socket is already in the Connection or Listener state. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html + /// - https://man7.org/linux/man-pages/man2/listen.2.html + listen: func(this: tcp-socket, backlog-size-hint: option) -> result<_, error> + + /// Fails when the socket is not in the Connection state. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html + /// - https://man7.org/linux/man-pages/man2/getpeername.2.html + remote-address: func(this: tcp-socket) -> result + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the Connection state. + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// Fails when this socket is not in the Listening state. + /// + /// References: + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html + /// - https://man7.org/linux/man-pages/man2/accept.2.html + accept: func(this: tcp-socket) -> result, error> + + /// Equivalent to the SO_KEEPALIVE socket option. + keep-alive: func(this: tcp-socket) -> result + set-keep-alive: func(this: tcp-socket, value: bool) -> result<_, error> + + /// Equivalent to the TCP_NODELAY socket option. + no-delay: func(this: tcp-socket) -> result + set-no-delay: func(this: tcp-socket, value: bool) -> result<_, error> + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Fails when this socket is in the Listening state. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + receive-buffer-size: func(this: tcp-socket) -> result + set-receive-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error> + send-buffer-size: func(this: tcp-socket) -> result + set-send-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error> + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func(this: tcp-socket) -> ip-address-family + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + unicast-hop-limit: func(this: tcp-socket) -> result + set-unicast-hop-limit: func(this: tcp-socket, value: u8) -> result<_, error> + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// Implementations are not required to support dual-stack mode, so calling `set-ipv6-only(false)` might fail. + /// + /// Fails when called on an IPv4 socket. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + ipv6-only: func(this: tcp-socket) -> result + set-ipv6-only: func(this: tcp-socket, value: bool) -> result<_, error> + + + /// Get/set the blocking mode of the socket. + /// + /// By default a socket is in "blocking" mode, meaning that any function blocks and waits for its completion. + /// When switched to "non-blocking" mode, operations that would block return an `again` error. After which + /// the API consumer is expected to call `subscribe` and wait for completion using the wasi-poll module. + /// + /// Note: these functions are here for WASI Preview2 only. + /// They're planned to be removed when `async` is natively supported in Preview3. + non-blocking: func(this: tcp-socket) -> result + set-non-blocking: func(this: tcp-socket, value: bool) -> result<_, error> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `async` is natively supported in Preview3. + subscribe: func(this: tcp-socket) -> pollable } diff --git a/wit/wasi-udp.wit b/wit/wasi-udp.wit new file mode 100644 index 00000000..83b0dac9 --- /dev/null +++ b/wit/wasi-udp.wit @@ -0,0 +1,193 @@ + +default interface wasi-udp { + use pkg.wasi-poll.{pollable} + use pkg.wasi-network.{network, error, ip-address-family} + + // TODO: For now, duplicate the types of these to work around a bug + // in wit-bindgen-0.3.0 which causes the bindings to reference + // `Ipv4SocketAddress` and `Ipv6SocketAddress without them being in + // scope. This bug is fixed in newer versions. + type ipv4-address = tuple + type ipv6-address = tuple + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + record ipv4-socket-address { + port: u16, // sin_port + address: ipv4-address, // sin_addr + } + record ipv6-socket-address { + port: u16, // sin6_port + flow-info: u32, // sin6_flowinfo + address: ipv6-address, // sin6_addr + scope-id: u32, // sin6_scope_id + } + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + + record datagram { + data: list, // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + remote-address: ip-socket-address, + + /// Possible future additions: + /// local-address: ip-socket-address, // IP_PKTINFO / IP_RECVDSTADDR / IPV6_PKTINFO + /// local-interface: u32, // IP_PKTINFO / IP_RECVIF + /// ttl: u8, // IP_RECVTTL + /// dscp: u6, // IP_RECVTOS + /// ecn: u2, // IP_RECVTOS + } + + /// A UDP socket handle. + type udp-socket = u32 + + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// + /// References: + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html + /// - https://man7.org/linux/man-pages/man2/socket.2.html + /// + create-udp-socket: func(network: network, address-family: ip-address-family) -> result + + /// Dispose of the specified `udp-socket`, after which it may no longer be used. + drop-udp-socket: func(this: udp-socket) + + /// Bind the socket to a specific IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to a send or receive operation will + /// implicitly bind the socket. + /// + /// Returns an error if the socket is already bound. + /// + /// TODO: disallow wildcard binds as long as there isn't a way to pass the local address to send & receive? + /// - https://blog.cloudflare.com/everything-you-ever-wanted-to-know-about-udp-sockets-but-were-afraid-to-ask-part-1/#sourcing-packets-from-a-wildcard-socket + /// - https://blog.powerdns.com/2012/10/08/on-binding-datagram-udp-sockets-to-the-any-addresses/ + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html + /// - https://man7.org/linux/man-pages/man2/bind.2.html + bind: func(this: udp-socket, local-address: ip-socket-address) -> result<_, error> + + /// Get the current bound address. + /// + /// Returns an error if the socket is not bound. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html + /// - https://man7.org/linux/man-pages/man2/getsockname.2.html + local-address: func(this: udp-socket) -> result + + /// receive a message. + /// + /// Returns: + /// - The sender address of the datagram + /// - The number of bytes read. + /// - When the received datagram is larger than the provided buffers, + /// the excess data is lost and the `truncated` flag will be set. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html + /// - https://man7.org/linux/man-pages/man2/recv.2.html + receive: func(this: udp-socket) -> result + + /// send a message to a specific destination address. + /// + /// The remote address option is required. To send a message to the "connected" peer, + /// call `remote-address` to get their address. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html + /// - https://man7.org/linux/man-pages/man2/send.2.html + send: func(this: udp-socket, datagram: datagram) -> result<_, error> + + /// Set the destination address. + /// + /// When a destination address is set: + /// - all receive operations will only return datagrams sent from the provided `remote-address`. + /// - the `send` function can still be used to send to any other destination, however you can't receive their response. + /// + /// Similar to `connect(sock, ...)` in POSIX. + /// + /// Note that this function does not generate any network traffic and the peer is not aware of this "connection". + /// + /// TODO: "connect" is a rather odd name for this function because it doesn't reflect what's actually happening. + /// Feels like it was chosen just to shoehorn UDP into the existing Socket interface. + /// Do we have to keep this name? + /// + /// TODO: add unconnect ability. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html + /// - https://man7.org/linux/man-pages/man2/connect.2.html + connect: func(this: udp-socket, remote-address: ip-socket-address) -> result<_, error> + + /// Get the address set with `connect`. + /// + /// References + /// - https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html + /// - https://man7.org/linux/man-pages/man2/getpeername.2.html + remote-address: func(this: udp-socket) -> result + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Fails when this socket is in the Listening state. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + receive-buffer-size: func(this: udp-socket) -> result + set-receive-buffer-size: func(this: udp-socket, value: u64) -> result<_, error> + send-buffer-size: func(this: udp-socket) -> result + set-send-buffer-size: func(this: udp-socket, value: u64) -> result<_, error> + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func(this: udp-socket) -> ip-address-family + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + unicast-hop-limit: func(this: udp-socket) -> result + set-unicast-hop-limit: func(this: udp-socket, value: u8) -> result<_, error> + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// Implementations are not required to support dual-stack mode, so calling `set-ipv6-only(false)` might fail. + /// + /// Fails when called on an IPv4 socket. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + ipv6-only: func(this: udp-socket) -> result + set-ipv6-only: func(this: udp-socket, value: bool) -> result<_, error> + + + /// Get/set the blocking mode of the socket. + /// + /// By default a socket is in "blocking" mode, meaning that any function blocks and waits for its completion. + /// When switched to "non-blocking" mode, operations that would block return an `again` error. After which + /// the API consumer is expected to call `subscribe` and wait for completion using the wasi-poll module. + /// + /// Note: these functions are here for WASI Preview2 only. + /// They're planned to be removed when `async` is natively supported in Preview3. + non-blocking: func(this: udp-socket) -> result + set-non-blocking: func(this: udp-socket, value: bool) -> result<_, error> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `async` is natively supported in Preview3. + subscribe: func(this: udp-socket) -> pollable +} diff --git a/wit/wasi.wit b/wit/wasi.wit index 76b07d4c..fd9d04f6 100644 --- a/wit/wasi.wit +++ b/wit/wasi.wit @@ -9,8 +9,10 @@ default world wasi { import wasi-random: pkg.wasi-random import wasi-poll: pkg.wasi-poll import wasi-io: pkg.wasi-io + import wasi-default-network: pkg.wasi-default-network + import wasi-ip-name-lookup: pkg.wasi-ip-name-lookup import wasi-tcp: pkg.wasi-tcp - import wasi-ip: pkg.wasi-ip - import wasi-dns: pkg.wasi-dns + import wasi-udp: pkg.wasi-udp + import wasi-network: pkg.wasi-network import wasi-exit: pkg.wasi-exit }