From a13f368b898fd92a9c60b1fb6b25d7c09ee23077 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 21 Dec 2022 13:56:18 -0800 Subject: [PATCH] Add basic TCP socket APIs. This is based on @npmccullum's [wasi-snapshot-preview2 draft] which is in turn based on the [wasi-sockets proposal], though for simplify for now it omits UDP sockets and some other features. It's also based on the [pseudo-streams PR]; so that should proceed first before this. [draft wasi-snapshot-preview2]: https://github.com/npmccallum/wasi-snapshot-preview2 [wasi-sockets proposal]: https://github.com/WebAssembly/wasi-sockets [pseudo-streams PR]: https://github.com/bytecodealliance/preview2-prototyping/pull/29 --- Cargo.lock | 10 +- host/src/filesystem.rs | 41 +--- host/src/tcp.rs | 260 +++++++++++++++++++++++++- src/lib.rs | 119 +++++------- wasi-common/cap-std-sync/src/net.rs | 74 ++++++-- wasi-common/src/connection.rs | 10 +- wasi-common/src/error.rs | 3 - wasi-common/src/lib.rs | 2 + wasi-common/src/listener.rs | 11 +- wasi-common/src/tcp_listener.rs | 36 ++++ wasi-common/tokio/src/file.rs | 4 +- wit/wasi-command.wit | 2 + wit/wasi-dns.wit | 83 +++++++++ wit/wasi-filesystem.wit | 62 ------- wit/wasi-ip.wit | 35 ++++ wit/wasi-net.wit | 5 + wit/wasi-tcp.wit | 277 ++++++++++++++++++++++++++-- wit/wasi.wit | 2 + 18 files changed, 811 insertions(+), 225 deletions(-) create mode 100644 wasi-common/src/tcp_listener.rs create mode 100644 wit/wasi-dns.wit create mode 100644 wit/wasi-ip.wit create mode 100644 wit/wasi-net.wit diff --git a/Cargo.lock b/Cargo.lock index 4a36f97a..ddf6a5cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1879,7 +1879,7 @@ dependencies = [ [[package]] name = "wit-bindgen-core" version = "0.3.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen#98c2b1e4cb10d3757114b7524d112511a4c7459e" +source = "git+https://github.com/bytecodealliance/wit-bindgen#c482614fdedeaff78d8e95daa01bc6ab11e32073" dependencies = [ "anyhow", "wit-component", @@ -1889,7 +1889,7 @@ dependencies = [ [[package]] name = "wit-bindgen-gen-guest-rust" version = "0.3.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen#98c2b1e4cb10d3757114b7524d112511a4c7459e" +source = "git+https://github.com/bytecodealliance/wit-bindgen#c482614fdedeaff78d8e95daa01bc6ab11e32073" dependencies = [ "heck", "wit-bindgen-core", @@ -1900,7 +1900,7 @@ dependencies = [ [[package]] name = "wit-bindgen-gen-rust-lib" version = "0.3.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen#98c2b1e4cb10d3757114b7524d112511a4c7459e" +source = "git+https://github.com/bytecodealliance/wit-bindgen#c482614fdedeaff78d8e95daa01bc6ab11e32073" dependencies = [ "heck", "wit-bindgen-core", @@ -1909,7 +1909,7 @@ dependencies = [ [[package]] name = "wit-bindgen-guest-rust" version = "0.3.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen#98c2b1e4cb10d3757114b7524d112511a4c7459e" +source = "git+https://github.com/bytecodealliance/wit-bindgen#c482614fdedeaff78d8e95daa01bc6ab11e32073" dependencies = [ "bitflags", "wit-bindgen-guest-rust-macro", @@ -1918,7 +1918,7 @@ dependencies = [ [[package]] name = "wit-bindgen-guest-rust-macro" version = "0.3.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen#98c2b1e4cb10d3757114b7524d112511a4c7459e" +source = "git+https://github.com/bytecodealliance/wit-bindgen#c482614fdedeaff78d8e95daa01bc6ab11e32073" dependencies = [ "anyhow", "proc-macro2", diff --git a/host/src/filesystem.rs b/host/src/filesystem.rs index b5fc9e50..d115adc2 100644 --- a/host/src/filesystem.rs +++ b/host/src/filesystem.rs @@ -14,7 +14,7 @@ use wasi_common::{ WasiDir, WasiFile, }; -// FIXME: guest rust bindgen should add this method to all flags. +/// TODO: Remove once wasmtime #5589 lands. fn contains + Eq + Copy>(flags: T, flag: T) -> bool { (flags & flag) == flag } @@ -26,53 +26,28 @@ fn convert(error: wasi_common::Error) -> anyhow::Error { match errno { Acces => Errno::Access, - Addrinuse => Errno::Addrinuse, - Addrnotavail => Errno::Addrnotavail, - Afnosupport => Errno::Afnosupport, Again => Errno::Again, Already => Errno::Already, Badf => Errno::Badf, - Badmsg => Errno::Badmsg, Busy => Errno::Busy, - Canceled => Errno::Canceled, - Child => Errno::Child, - Connaborted => Errno::Connaborted, - Connrefused => Errno::Connrefused, - Connreset => Errno::Connreset, Deadlk => Errno::Deadlk, - Destaddrreq => Errno::Destaddrreq, Dquot => Errno::Dquot, Exist => Errno::Exist, - Fault => Errno::Fault, Fbig => Errno::Fbig, - Hostunreach => Errno::Hostunreach, - Idrm => Errno::Idrm, Ilseq => Errno::Ilseq, Inprogress => Errno::Inprogress, Intr => Errno::Intr, Inval => Errno::Inval, Io => Errno::Io, - Isconn => Errno::Isconn, Isdir => Errno::Isdir, Loop => Errno::Loop, - Mfile => Errno::Mfile, Mlink => Errno::Mlink, Msgsize => Errno::Msgsize, - Multihop => Errno::Multihop, Nametoolong => Errno::Nametoolong, - Netdown => Errno::Netdown, - Netreset => Errno::Netreset, - Netunreach => Errno::Netunreach, - Nfile => Errno::Nfile, - Nobufs => Errno::Nobufs, Nodev => Errno::Nodev, Noent => Errno::Noent, - Noexec => Errno::Noexec, Nolck => Errno::Nolck, - Nolink => Errno::Nolink, Nomem => Errno::Nomem, - Nomsg => Errno::Nomsg, - Noprotoopt => Errno::Noprotoopt, Nospc => Errno::Nospc, Nosys => Errno::Nosys, Notdir => Errno::Notdir, @@ -82,21 +57,21 @@ fn convert(error: wasi_common::Error) -> anyhow::Error { Notty => Errno::Notty, Nxio => Errno::Nxio, Overflow => Errno::Overflow, - Ownerdead => Errno::Ownerdead, Perm => Errno::Perm, Pipe => Errno::Pipe, - Range => Errno::Range, Rofs => Errno::Rofs, Spipe => Errno::Spipe, - Srch => Errno::Srch, - Stale => Errno::Stale, - Timedout => Errno::Timedout, Txtbsy => Errno::Txtbsy, Xdev => Errno::Xdev, - Success | Dom | Notcapable | Notsock | Proto | Protonosupport | Prototype | TooBig - | Notconn => { + Success | Notsock | Proto | Protonosupport | Prototype | TooBig | Notconn => { return error.into(); } + Addrinuse | Addrnotavail | Afnosupport | Badmsg | Canceled | Connaborted + | Connrefused | Connreset | Destaddrreq | Fault | Hostunreach | Idrm | Isconn + | Mfile | Multihop | Netdown | Netreset | Netunreach | Nfile | Nobufs | Noexec + | Nolink | Nomsg | Noprotoopt | Ownerdead | Range | Srch | Stale | Timedout => { + panic!("Unexpected errno: {:?}", errno); + } } .into() } else { diff --git a/host/src/tcp.rs b/host/src/tcp.rs index a009e077..eca61b29 100644 --- a/host/src/tcp.rs +++ b/host/src/tcp.rs @@ -1,17 +1,271 @@ +#![allow(unused_variables)] + use crate::{ - wasi_tcp::{self, BytesResult, Socket, WasiTcp}, + wasi_poll::WasiStream, + wasi_tcp::{ + Connection, ConnectionFlags, Errno, IoSize, IpSocketAddress, Ipv4SocketAddress, + Ipv6SocketAddress, Listener, ListenerFlags, Network, TcpListener, 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() + } +} #[async_trait::async_trait] impl WasiTcp for WasiCtx { - async fn bytes_readable(&mut self, socket: Socket) -> HostResult { + async fn listen( + &mut self, + network: Network, + address: IpSocketAddress, + backlog: Option, + flags: ListenerFlags, + ) -> HostResult { + todo!() + } + + async fn accept( + &mut self, + listener: Listener, + flags: ConnectionFlags, + ) -> HostResult<(Connection, WasiStream), Errno> { + let table = self.table_mut(); + let l = table.get_listener_mut(listener)?; + + let nonblocking = contains(flags, ConnectionFlags::NONBLOCK); + + if contains(flags, ConnectionFlags::KEEPALIVE) || contains(flags, ConnectionFlags::NODELAY) + { + todo!() + } + + let (connection, stream) = l.accept(nonblocking).await?; + + let connection = table.push(Box::new(connection)).map_err(convert)?; + let stream = table.push(Box::new(stream)).map_err(convert)?; + + Ok(Ok((connection, stream))) + } + + async fn accept_tcp( + &mut self, + listener: TcpListener, + flags: ConnectionFlags, + ) -> HostResult<(Connection, WasiStream, IpSocketAddress), Errno> { + let table = self.table_mut(); + let l = table.get_tcp_listener_mut(listener)?; + + let nonblocking = contains(flags, ConnectionFlags::NONBLOCK); + + if contains(flags, ConnectionFlags::KEEPALIVE) || contains(flags, ConnectionFlags::NODELAY) + { + todo!() + } + + let (connection, stream, addr) = l.accept(nonblocking).await?; + + let connection = table.push(Box::new(connection)).map_err(convert)?; + let stream = table.push(Box::new(stream)).map_err(convert)?; + + Ok(Ok((connection, stream, addr.into()))) + } + + async fn connect( + &mut self, + network: Network, + local_address: IpSocketAddress, + remote_address: IpSocketAddress, + flags: ConnectionFlags, + ) -> HostResult<(Connection, WasiStream), Errno> { + todo!() + } + + async fn send(&mut self, connection: Connection, bytes: Vec) -> HostResult { + todo!() + } + + async fn receive( + &mut self, + connection: Connection, + length: IoSize, + ) -> HostResult<(Vec, bool), Errno> { + 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. + todo!() + } + + async fn get_flags(&mut self, connection: Connection) -> HostResult { + todo!() + } + + async fn set_flags( + &mut self, + connection: Connection, + flags: ConnectionFlags, + ) -> HostResult<(), Errno> { + todo!() + } + + async fn get_receive_buffer_size( + &mut self, + connection: Connection, + ) -> HostResult { + todo!() + } + + async fn set_receive_buffer_size( + &mut self, + connection: Connection, + value: IoSize, + ) -> HostResult<(), Errno> { + todo!() + } + + async fn get_send_buffer_size(&mut self, connection: Connection) -> HostResult { + todo!() + } + + async fn set_send_buffer_size( + &mut self, + connection: Connection, + value: IoSize, + ) -> HostResult<(), Errno> { + todo!() + } + + async fn bytes_readable(&mut self, socket: Connection) -> HostResult<(IoSize, bool), Errno> { drop(socket); todo!() } - async fn bytes_writable(&mut self, socket: Socket) -> HostResult { + async fn bytes_writable(&mut self, socket: Connection) -> HostResult<(IoSize, bool), Errno> { drop(socket); todo!() } + + async fn close_tcp_listener(&mut self, listener: TcpListener) -> anyhow::Result<()> { + drop(listener); + todo!() + } + + async fn close_connection(&mut self, connection: Connection) -> anyhow::Result<()> { + drop(connection); + 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()), + } + } +} + +impl From for Ipv4SocketAddress { + fn from(addr: SocketAddrV4) -> Self { + Self { + address: MyIpv4Addr::from(addr.ip()).0, + port: addr.port(), + } + } +} + +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(), + } + } +} + +// 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/src/lib.rs b/src/lib.rs index 973ff6f7..5dcb2161 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_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_logging, wasi_poll, - wasi_random, wasi_stderr, wasi_tcp, + wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_poll, wasi_random, + wasi_stderr, wasi_tcp, }; use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; @@ -1430,9 +1430,9 @@ impl Drop for Pollables { } } -impl From for Errno { - fn from(error: wasi_tcp::Error) -> Errno { - use wasi_tcp::Error::*; +impl From for Errno { + fn from(error: wasi_tcp::Errno) -> Errno { + use wasi_tcp::Errno::*; match error { // Use a black box to prevent the optimizer from generating a @@ -1638,25 +1638,24 @@ pub unsafe extern "C" fn poll_oneoff( flags = 0; } }, - StreamType::Socket(_) => unreachable!(), // TODO - /* - StreamType::Socket(_) => match wasi_tcp::bytes_readable(todo!()) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.nbytes; - flags = if result.is_closed { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; + StreamType::Socket(connection) => { + match wasi_tcp::bytes_readable(*connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } - }, - */ + } StreamType::EmptyStdin => { error = ERRNO_SUCCESS; nbytes = 0; @@ -1681,25 +1680,24 @@ pub unsafe extern "C" fn poll_oneoff( nbytes = 1; flags = 0; } - StreamType::Socket(_) => unreachable!(), // TODO - /* - StreamType::Socket(_) => match wasi_tcp::bytes_writable(todo!()) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.nbytes; - flags = if result.is_closed { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; + StreamType::Socket(connection) => { + match wasi_tcp::bytes_writable(connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } - }, - */ + } StreamType::EmptyStdin => { error = ERRNO_BADF; nbytes = 0; @@ -1875,55 +1873,29 @@ impl From for Errno { match err { // Use a black box to prevent the optimizer from generating a // lookup table, which would require a static initializer. - wasi_filesystem::Errno::Toobig => black_box(ERRNO_2BIG), - wasi_filesystem::Errno::Access => ERRNO_ACCES, - wasi_filesystem::Errno::Addrinuse => ERRNO_ADDRINUSE, - wasi_filesystem::Errno::Addrnotavail => ERRNO_ADDRNOTAVAIL, - wasi_filesystem::Errno::Afnosupport => ERRNO_AFNOSUPPORT, + wasi_filesystem::Errno::Access => black_box(ERRNO_ACCES), wasi_filesystem::Errno::Again => ERRNO_AGAIN, wasi_filesystem::Errno::Already => ERRNO_ALREADY, - wasi_filesystem::Errno::Badmsg => ERRNO_BADMSG, wasi_filesystem::Errno::Badf => ERRNO_BADF, wasi_filesystem::Errno::Busy => ERRNO_BUSY, - wasi_filesystem::Errno::Canceled => ERRNO_CANCELED, - wasi_filesystem::Errno::Child => ERRNO_CHILD, - wasi_filesystem::Errno::Connaborted => ERRNO_CONNABORTED, - wasi_filesystem::Errno::Connrefused => ERRNO_CONNREFUSED, - wasi_filesystem::Errno::Connreset => ERRNO_CONNRESET, wasi_filesystem::Errno::Deadlk => ERRNO_DEADLK, - wasi_filesystem::Errno::Destaddrreq => ERRNO_DESTADDRREQ, wasi_filesystem::Errno::Dquot => ERRNO_DQUOT, wasi_filesystem::Errno::Exist => ERRNO_EXIST, - wasi_filesystem::Errno::Fault => ERRNO_FAULT, wasi_filesystem::Errno::Fbig => ERRNO_FBIG, - wasi_filesystem::Errno::Hostunreach => ERRNO_HOSTUNREACH, - wasi_filesystem::Errno::Idrm => ERRNO_IDRM, wasi_filesystem::Errno::Ilseq => ERRNO_ILSEQ, wasi_filesystem::Errno::Inprogress => ERRNO_INPROGRESS, wasi_filesystem::Errno::Intr => ERRNO_INTR, wasi_filesystem::Errno::Inval => ERRNO_INVAL, wasi_filesystem::Errno::Io => ERRNO_IO, - wasi_filesystem::Errno::Isconn => ERRNO_ISCONN, wasi_filesystem::Errno::Isdir => ERRNO_ISDIR, wasi_filesystem::Errno::Loop => ERRNO_LOOP, - wasi_filesystem::Errno::Mfile => ERRNO_MFILE, wasi_filesystem::Errno::Mlink => ERRNO_MLINK, wasi_filesystem::Errno::Msgsize => ERRNO_MSGSIZE, - wasi_filesystem::Errno::Multihop => ERRNO_MULTIHOP, wasi_filesystem::Errno::Nametoolong => ERRNO_NAMETOOLONG, - wasi_filesystem::Errno::Netdown => ERRNO_NETDOWN, - wasi_filesystem::Errno::Netreset => ERRNO_NETRESET, - wasi_filesystem::Errno::Netunreach => ERRNO_NETUNREACH, - wasi_filesystem::Errno::Nfile => ERRNO_NFILE, - wasi_filesystem::Errno::Nobufs => ERRNO_NOBUFS, wasi_filesystem::Errno::Nodev => ERRNO_NODEV, wasi_filesystem::Errno::Noent => ERRNO_NOENT, - wasi_filesystem::Errno::Noexec => ERRNO_NOEXEC, wasi_filesystem::Errno::Nolck => ERRNO_NOLCK, - wasi_filesystem::Errno::Nolink => ERRNO_NOLINK, wasi_filesystem::Errno::Nomem => ERRNO_NOMEM, - wasi_filesystem::Errno::Nomsg => ERRNO_NOMSG, - wasi_filesystem::Errno::Noprotoopt => ERRNO_NOPROTOOPT, wasi_filesystem::Errno::Nospc => ERRNO_NOSPC, wasi_filesystem::Errno::Nosys => ERRNO_NOSYS, wasi_filesystem::Errno::Notdir => ERRNO_NOTDIR, @@ -1933,15 +1905,10 @@ impl From for Errno { wasi_filesystem::Errno::Notty => ERRNO_NOTTY, wasi_filesystem::Errno::Nxio => ERRNO_NXIO, wasi_filesystem::Errno::Overflow => ERRNO_OVERFLOW, - wasi_filesystem::Errno::Ownerdead => ERRNO_OWNERDEAD, wasi_filesystem::Errno::Perm => ERRNO_PERM, wasi_filesystem::Errno::Pipe => ERRNO_PIPE, - wasi_filesystem::Errno::Range => ERRNO_RANGE, wasi_filesystem::Errno::Rofs => ERRNO_ROFS, wasi_filesystem::Errno::Spipe => ERRNO_SPIPE, - wasi_filesystem::Errno::Srch => ERRNO_SRCH, - wasi_filesystem::Errno::Stale => ERRNO_STALE, - wasi_filesystem::Errno::Timedout => ERRNO_TIMEDOUT, wasi_filesystem::Errno::Txtbsy => ERRNO_TXTBSY, wasi_filesystem::Errno::Xdev => ERRNO_XDEV, } @@ -2044,8 +2011,8 @@ enum StreamType { /// Streaming data with a file. File(File), - /// Streaming data with a socket. - Socket(wasi_tcp::Socket), + /// Streaming data with a socket connection. + Socket(wasi_tcp::Connection), } impl Drop for Descriptor { @@ -2386,7 +2353,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/net.rs b/wasi-common/cap-std-sync/src/net.rs index d98601e5..4c31cd74 100644 --- a/wasi-common/cap-std-sync/src/net.rs +++ b/wasi-common/cap-std-sync/src/net.rs @@ -9,6 +9,8 @@ use io_lifetimes::{AsSocket, BorrowedSocket}; use std::any::Any; use std::convert::TryInto; use std::io::{self, Read, Write}; +use std::net::SocketAddr; +use std::sync::Arc; use system_interface::io::IoExt; use system_interface::io::IsReadWrite; use system_interface::io::ReadReady; @@ -16,6 +18,7 @@ use wasi_common::{ connection::{RiFlags, RoFlags, SdFlags, SiFlags, WasiConnection}, listener::WasiListener, stream::WasiStream, + tcp_listener::WasiTcpListener, Error, ErrorExt, }; @@ -95,7 +98,7 @@ impl From for Box { } } -macro_rules! wasi_listen_write_impl { +macro_rules! wasi_listener_impl { ($ty:ty, $stream:ty) => { #[async_trait::async_trait] impl WasiListener for $ty { @@ -103,14 +106,15 @@ macro_rules! wasi_listen_write_impl { self } - async fn sock_accept( + async fn accept( &mut self, nonblocking: bool, - ) -> Result, Error> { + ) -> Result<(Box, Box), Error> { let (stream, _) = self.0.accept()?; stream.set_nonblocking(nonblocking)?; - let stream = <$stream>::from_cap_std(stream); - Ok(Box::new(stream)) + let connection = <$stream>::from_cap_std(stream); + let stream = connection.clone(); + Ok((Box::new(connection), Box::new(stream))) } fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error> { @@ -144,6 +148,37 @@ macro_rules! wasi_listen_write_impl { }; } +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, SocketAddr), Error> { + let (stream, addr) = self.0.accept()?; + stream.set_nonblocking(nonblocking)?; + let connection = <$stream>::from_cap_std(stream); + let stream = connection.clone(); + Ok((Box::new(connection), Box::new(stream), addr)) + } + + fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error> { + self.0.set_nonblocking(flag)?; + Ok(()) + } + + fn into_listener(self) -> Box { + Box::new(self) + } + } + }; +} + pub struct TcpListener(cap_std::net::TcpListener); impl TcpListener { @@ -151,7 +186,8 @@ impl TcpListener { TcpListener(cap_std) } } -wasi_listen_write_impl!(TcpListener, TcpStream); +wasi_listener_impl!(TcpListener, TcpStream); +wasi_tcp_listener_impl!(TcpListener, TcpStream); #[cfg(unix)] pub struct UnixListener(cap_std::os::unix::net::UnixListener); @@ -164,7 +200,7 @@ impl UnixListener { } #[cfg(unix)] -wasi_listen_write_impl!(UnixListener, UnixStream); +wasi_listener_impl!(UnixListener, UnixStream); macro_rules! wasi_stream_write_impl { ($ty:ty, $std_ty:ty) => { @@ -316,7 +352,7 @@ macro_rules! wasi_stream_write_impl { ) -> Result<(u64, bool), Error> { if let Some(writeable) = dst.pollable_write() { let num = io::copy( - &mut io::Read::take(&self.0, nelem), + &mut io::Read::take(&*self.0, nelem), &mut BorrowedWriteable::borrow(writeable), )?; Ok((num, num < nelem)) @@ -325,11 +361,11 @@ macro_rules! wasi_stream_write_impl { } } 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())?; + let num = io::copy(&mut io::Read::take(&*self.0, nelem), &mut io::sink())?; Ok((num, num < nelem)) } async fn write_repeated(&mut self, byte: u8, nelem: u64) -> Result { - let num = io::copy(&mut io::Read::take(io::repeat(byte), nelem), &mut self.0)?; + let num = io::copy(&mut io::Read::take(io::repeat(byte), nelem), &mut &*self.0)?; Ok(num) } async fn num_ready_bytes(&self) -> Result { @@ -337,7 +373,7 @@ macro_rules! wasi_stream_write_impl { Ok(val) } async fn readable(&self) -> Result<(), Error> { - if is_read_write(&self.0)?.0 { + if is_read_write(&*self.0)?.0 { Ok(()) } else { Err(Error::badf()) @@ -376,23 +412,31 @@ macro_rules! wasi_stream_write_impl { }; } -pub struct TcpStream(cap_std::net::TcpStream); +pub struct TcpStream(Arc); impl TcpStream { pub fn from_cap_std(socket: cap_std::net::TcpStream) -> Self { - TcpStream(socket) + Self(Arc::new(socket)) + } + + pub fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) } } wasi_stream_write_impl!(TcpStream, std::net::TcpStream); #[cfg(unix)] -pub struct UnixStream(cap_std::os::unix::net::UnixStream); +pub struct UnixStream(Arc); #[cfg(unix)] impl UnixStream { pub fn from_cap_std(socket: cap_std::os::unix::net::UnixStream) -> Self { - UnixStream(socket) + Self(Arc::new(socket)) + } + + pub fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) } } diff --git a/wasi-common/src/connection.rs b/wasi-common/src/connection.rs index 553a2990..1c41f704 100644 --- a/wasi-common/src/connection.rs +++ b/wasi-common/src/connection.rs @@ -11,19 +11,19 @@ pub trait WasiConnection: Send + Sync { async fn sock_recv<'a>( &mut self, - _ri_data: &mut [std::io::IoSliceMut<'a>], - _ri_flags: RiFlags, + 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, + 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>; + fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error>; async fn readable(&self) -> Result<(), Error>; diff --git a/wasi-common/src/error.rs b/wasi-common/src/error.rs index 80f8b6ae..637cc4e2 100644 --- a/wasi-common/src/error.rs +++ b/wasi-common/src/error.rs @@ -38,13 +38,11 @@ pub enum Errno { Badmsg, Busy, Canceled, - Child, Connaborted, Connrefused, Connreset, Deadlk, Destaddrreq, - Dom, Dquot, Exist, Fault, @@ -102,7 +100,6 @@ pub enum Errno { Timedout, Txtbsy, Xdev, - Notcapable, } impl std::fmt::Display for Errno { diff --git a/wasi-common/src/lib.rs b/wasi-common/src/lib.rs index a9caa447..187a61d5 100644 --- a/wasi-common/src/lib.rs +++ b/wasi-common/src/lib.rs @@ -63,6 +63,7 @@ pub mod random; pub mod sched; pub mod stream; pub mod table; +pub mod tcp_listener; pub use cap_fs_ext::SystemTimeSpec; pub use cap_rand::RngCore; @@ -76,3 +77,4 @@ pub use listener::WasiListener; pub use sched::{Poll, WasiSched}; pub use stream::WasiStream; pub use table::Table; +pub use tcp_listener::WasiTcpListener; diff --git a/wasi-common/src/listener.rs b/wasi-common/src/listener.rs index 0a993a2a..f83d13f6 100644 --- a/wasi-common/src/listener.rs +++ b/wasi-common/src/listener.rs @@ -1,6 +1,8 @@ +//! Socket listeners. + use crate::connection::WasiConnection; -/// Socket listeners. use crate::Error; +use crate::WasiStream; use std::any::Any; /// A socket listener. @@ -8,9 +10,12 @@ use std::any::Any; pub trait WasiListener: Send + Sync { fn as_any(&self) -> &dyn Any; - async fn sock_accept(&mut self, nonblocking: bool) -> Result, Error>; + async fn accept( + &mut self, + nonblocking: bool, + ) -> Result<(Box, Box), Error>; - fn set_nonblocking(&mut self, _flag: bool) -> Result<(), Error>; + fn set_nonblocking(&mut self, flag: bool) -> Result<(), Error>; } pub trait TableListenerExt { diff --git a/wasi-common/src/tcp_listener.rs b/wasi-common/src/tcp_listener.rs new file mode 100644 index 00000000..b182453b --- /dev/null +++ b/wasi-common/src/tcp_listener.rs @@ -0,0 +1,36 @@ +//! TCP socket listeners. + +use crate::connection::WasiConnection; +use crate::Error; +use crate::WasiListener; +use crate::WasiStream; +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, 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/tokio/src/file.rs b/wasi-common/tokio/src/file.rs index b7cc8b2d..255b19cf 100644 --- a/wasi-common/tokio/src/file.rs +++ b/wasi-common/tokio/src/file.rs @@ -219,8 +219,8 @@ macro_rules! wasi_file_impl { } } - async fn sock_accept(&mut self, fdflags: FdFlags) -> Result, Error> { - block_on_dummy_executor(|| self.0.sock_accept(fdflags)) + async fn accept(&mut self, fdflags: FdFlags) -> Result, Error> { + block_on_dummy_executor(|| self.0.accept(fdflags)) } } #[cfg(windows)] diff --git a/wit/wasi-command.wit b/wit/wasi-command.wit index 5296c243..b18063f2 100644 --- a/wit/wasi-command.wit +++ b/wit/wasi-command.wit @@ -7,6 +7,8 @@ default world wasi-command { import wasi-random: pkg.wasi-random import wasi-poll: pkg.wasi-poll import wasi-tcp: pkg.wasi-tcp + import wasi-ip: pkg.wasi-ip + import wasi-dns: pkg.wasi-dns import wasi-exit: pkg.wasi-exit export command: func( diff --git a/wit/wasi-dns.wit b/wit/wasi-dns.wit new file mode 100644 index 00000000..d7b1e947 --- /dev/null +++ b/wit/wasi-dns.wit @@ -0,0 +1,83 @@ +/// 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-filesystem.wit b/wit/wasi-filesystem.wit index f8bd18ce..b18ba24b 100644 --- a/wit/wasi-filesystem.wit +++ b/wit/wasi-filesystem.wit @@ -173,52 +173,24 @@ default interface wasi-filesystem { /// API; some are used in higher-level library layers, and others are provided /// merely for alignment with POSIX. enum errno { - /// Argument list too long. This is similar to `E2BIG` in POSIX. - toobig, /// 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 message. - badmsg, /// Bad descriptor. badf, /// Device or resource busy. busy, - /// Operation canceled. - canceled, - /// No child processes. - child, - /// Connection aborted. - connaborted, - /// Connection refused. - connrefused, - /// Connection reset. - connreset, /// Resource deadlock would occur. deadlk, - /// Destination address required. - destaddrreq, /// Storage quota exceeded. dquot, /// File exists. exist, - /// Bad address. - fault, /// File too large. fbig, - /// Host is unreachable. - hostunreach, - /// Identifier removed. - idrm, /// Illegal byte sequence. ilseq, /// Operation in progress. @@ -229,48 +201,24 @@ default interface wasi-filesystem { inval, /// I/O error. io, - /// Socket is connected. - isconn, /// Is a directory. isdir, /// Too many levels of symbolic links. loop, - /// File descriptor value too large. - mfile, /// Too many links. mlink, /// Message too large. msgsize, - /// Multihop attempted. - multihop, /// Filename too long. nametoolong, - /// Network is down. - netdown, - /// Connection aborted by network. - netreset, - /// Network unreachable. - netunreach, - /// Too many files open in system. - nfile, - /// No buffer space available. - nobufs, /// No such device. nodev, /// No such file or directory. noent, - /// Executable file format error. - noexec, /// No locks available. nolck, - /// Link has been severed. - nolink, /// Not enough space. nomem, - /// No message of the desired type. - nomsg, - /// Protocol not available. - noprotoopt, /// No space left on device. nospc, /// Function not supported. @@ -289,24 +237,14 @@ default interface wasi-filesystem { nxio, /// Value too large to be stored in data type. overflow, - /// Previous owner died. - ownerdead, /// Operation not permitted. perm, /// Broken pipe. pipe, - /// Result too large. - range, /// Read-only file system. rofs, /// Invalid seek. spipe, - /// No such process. - srch, - /// Stale file handle. - stale, - /// Connection timed out. - timedout, /// Text file busy. txtbsy, /// Cross-device link. diff --git a/wit/wasi-ip.wit b/wit/wasi-ip.wit new file mode 100644 index 00000000..1bef3504 --- /dev/null +++ b/wit/wasi-ip.wit @@ -0,0 +1,35 @@ +/// 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 new file mode 100644 index 00000000..12d7440e --- /dev/null +++ b/wit/wasi-net.wit @@ -0,0 +1,5 @@ +/// 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-tcp.wit b/wit/wasi-tcp.wit index 05b45b18..44705e9c 100644 --- a/wit/wasi-tcp.wit +++ b/wit/wasi-tcp.wit @@ -1,34 +1,275 @@ +/// 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-poll.{pollable, wasi-stream} + use pkg.wasi-net.{network} - /// A socket pseudo-handle. In the future, this will be replaced by a handle type. - type socket = u32 + // 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 - /// Errors which may be encountered when performing socket-related operations - // TODO: expand this list - enum error { + /// 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, - timeout - } + /// 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, - /// Result of querying bytes readable or writable for a `socket` - record bytes-result { - /// Indicates the number of bytes readable or writable for a still-open socket - nbytes: u64, - /// Indicates whether the other end of the stream has disconnected, in which case - /// no further data will be received (when reading) or accepted (when writing) on - /// this stream. - is-closed: bool + /// 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. + /// + /// 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: socket) -> result + bytes-readable: func(s: connection) -> result, errno> /// Query the specified `socket` for the number of bytes ready to be accepted. - bytes-writable: func(s: socket) -> result + 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) } diff --git a/wit/wasi.wit b/wit/wasi.wit index d0bbe16a..3e873fda 100644 --- a/wit/wasi.wit +++ b/wit/wasi.wit @@ -7,5 +7,7 @@ default world wasi { import wasi-random: pkg.wasi-random import wasi-poll: pkg.wasi-poll import wasi-tcp: pkg.wasi-tcp + import wasi-ip: pkg.wasi-ip + import wasi-dns: pkg.wasi-dns import wasi-exit: pkg.wasi-exit }