diff --git a/.github/assets/water_rust_lib_draft1.png b/.github/assets/water_rust_lib_draft1.png new file mode 100644 index 0000000..34e5578 Binary files /dev/null and b/.github/assets/water_rust_lib_draft1.png differ diff --git a/Cargo.toml b/Cargo.toml index f3c178f..5aa0234 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ resolver="2" [workspace.package] -authors = ["cerikccc@gmail.com", "gaukas", "jmwample", "ewust"] +authors = ["hi@erikchi.com", "gaukas", "jmwample", "ewust"] description = "Water WebAssembly Transport Executor for rust" edition = "2021" # documentation = "https://example.com/bar" diff --git a/README.md b/README.md index f9f3a31..45355d7 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,11 @@ If you quoted or used our work, please cite our paper [Just add WATER: WebAssemb The repo contains 2 main components: 1. A Rust crate [`water`](https://github.com/erikziyunchi/water-rs/tree/main/crates/water) runtime library used to interact with `.wasm` WebAssembly Transport Modules(WATM). -2. A Rust crate [`water-wasm-crate`](https://github.com/erikziyunchi/water-rs/tree/main/crates/wasm) for WATM-development where developers can easily create their own `.wasm`. +2. A Rust crate [`water_wasm`](https://github.com/erikziyunchi/water-rs/tree/main/crates/wasm) for WATM-development where developers can easily create their own `.wasm`. Also include examples for demonstration of usage: 1. A standalone cli tool which can be used to load a `.wasm` WATM directly and run it. See [water-rs/tree/main/examples/clients/cli](https://github.com/erikziyunchi/water-rs/tree/main/examples/clients/cli). -2. A few examples WATM implementations with `water-wasm-crate`, see [water-rs/tree/main/examples/water_bins](https://github.com/erikziyunchi/water-rs/tree/main/examples/water_bins). +2. A few examples WATM implementations with `water_wasm` crate, see [water-rs/tree/main/examples/water_bins](https://github.com/erikziyunchi/water-rs/tree/main/examples/water_bins). 3. Examples of using the above WATM examples with our `water` library, see [tests](https://github.com/erikziyunchi/water-rs/tree/main/tests/tests) for usage. ## Running tests @@ -61,4 +61,4 @@ cargo test -p --verbose # run a single test (or test matching name prefix) in a single crate cargo test -p --verbose -- -``` \ No newline at end of file +``` diff --git a/crates/wasm/README.md b/crates/wasm/README.md deleted file mode 100644 index 1a06592..0000000 --- a/crates/wasm/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# WATM development Library -- `water-wasm-crate` - -A library for eaiser development of WATM modules, satisfies APIs [here](https://app.gitbook.com/o/KHlQypYtIQKkb8YeZ6Hx/s/lVX5MqollomuX6vW80T6/wasm-and-wasi-apis). \ No newline at end of file diff --git a/crates/wasm/src/config.rs b/crates/wasm/src/config.rs index b16d87a..6393e1e 100644 --- a/crates/wasm/src/config.rs +++ b/crates/wasm/src/config.rs @@ -1,6 +1,11 @@ +//! This module contains the default config struct for the WATM module +//! +//! The config should be read from a .json file by the WATM module and setup the +//! corresponding connection addresses and ports + use super::*; -// A Config currently contains the local + remote ip & port +/// A Config currently contains the local + remote ip & port + bypass flag #[derive(Debug, Deserialize, Clone)] pub struct Config { pub remote_address: String, @@ -16,7 +21,7 @@ impl Default for Config { } } -// implement a constructor for the config +/// implement a constructor for the config impl Config { pub fn new() -> Self { Config { @@ -29,8 +34,9 @@ impl Config { } } -// ============ Some implementation for V1 ============ -// A config struct that shares between your host & wasm to establish a connection +// ============ Below are some implementations for V1 ============ + +/// A config struct that shares between your host & wasm to establish a connection // #[cfg(feature = "v1")] #[derive(Serialize, Deserialize)] pub struct StreamConfigV1 { diff --git a/crates/wasm/src/connections.rs b/crates/wasm/src/connections.rs index b5e45f8..f33fff7 100644 --- a/crates/wasm/src/connections.rs +++ b/crates/wasm/src/connections.rs @@ -1,3 +1,14 @@ +//! This module contains the Connection struct, which is the main struct for a connection +//! that contains the inbound and outbound streams and the config for the connection in the WATM module. +//! +//! The config is a generic type that can be defined / replaced by the WATM module where it decouples +//! the configuration of WATM module from the Host program, where if there is a config diff, +//! we don't need to recompile the Host program to achieve our fast deloyment goal. +//! +//! +//! The module also contains the ConnFile struct, which is the struct for one way of connection -- either for in / outbound +//! + use super::*; // ConnStream can store either a network stream Or a file stream @@ -15,7 +26,7 @@ impl ConnStream { } } -// ConnFile is the struct for a connection -- either for in / outbound +/// ConnFile is the struct for a connection -- either for in / outbound pub struct ConnFile { pub fd: i32, pub file: Option, @@ -28,7 +39,7 @@ impl Default for ConnFile { } impl ConnFile { - // A default constructor for ConnFile + /// A default constructor for ConnFile pub fn new() -> Self { ConnFile { fd: -1, file: None } } @@ -62,7 +73,7 @@ impl ConnFile { } } -// A Connection normally contains both in & outbound streams + a config +/// A Connection normally contains both in & outbound streams + a generic config pub struct Connection { pub inbound_conn: ConnFile, pub outbound_conn: ConnFile, @@ -81,7 +92,7 @@ impl Default for Connection { } impl Connection { - // A default constructor + /// A constructor that takes in the customized config pub fn new(config: T) -> Self { Connection { inbound_conn: ConnFile::new(), @@ -90,6 +101,7 @@ impl Connection { } } + /// Setting the inbound connection for the WATM module, which is talking to the Host pub fn set_inbound(&mut self, fd: i32, stream: ConnStream) { if fd < 0 { eprintln!("[WASM] > ERROR: fd is negative"); @@ -105,6 +117,7 @@ impl Connection { self.inbound_conn.file = Some(stream); } + /// Setting the outbound connection for the WATM module, which is talking to the remote pub fn set_outbound(&mut self, fd: i32, stream: ConnStream) { if fd < 0 { eprintln!("[WASM] > ERROR: fd is negative"); @@ -120,20 +133,6 @@ impl Connection { self.outbound_conn.file = Some(stream); } - // pub fn decoder_read_from_outbound(&mut self, decoder: &mut D, buf: &mut [u8]) -> Result { - // debug!("[WASM] running in decoder_read_from_outbound"); - - // // match self.outbound_conn.file.as_mut().unwrap() { - // // ConnStream::TcpStream(stream) => { - // // decoder.read_decrypted(stream); - // // }, - // // ConnStream::File(stream) => { - // // decoder.read_decrypted(stream); - // // }, - // // } - // Ok(decoder.poll_read_decrypted(self.outbound_conn.file.as_mut().unwrap().as_read(), buf)? as i64) - // } - /// this _read function is triggered by the Host to read from the remote connection pub fn _read_from_outbound( &mut self, @@ -186,6 +185,7 @@ impl Connection { Ok(len_after_decoding as i64) } + /// this _write function is triggered by the Host to write to the remote connection pub fn _write_2_outbound( &mut self, encoder: &mut E, diff --git a/crates/wasm/src/decoder.rs b/crates/wasm/src/decoder.rs index 3c608a6..c51e41c 100644 --- a/crates/wasm/src/decoder.rs +++ b/crates/wasm/src/decoder.rs @@ -1,15 +1,15 @@ +//! Logic for unpackaging trait + use super::*; use tokio::io::AsyncRead; -// Developer Guide: Logic for packaging - -// A trait for a decoder, developers should implement this trait and pass it to _read_from_outbound +/// A trait for a decoder, developers should implement this trait and pass it to _read_from_outbound pub trait Decoder { fn decode(&self, input: &[u8], output: &mut [u8]) -> Result; } -// A default decoder that does just copy + paste +/// A default decoder that does just copy + paste pub struct DefaultDecoder; impl Decoder for DefaultDecoder { diff --git a/crates/wasm/src/dialer.rs b/crates/wasm/src/dialer.rs index 4e60c89..d5030ad 100644 --- a/crates/wasm/src/dialer.rs +++ b/crates/wasm/src/dialer.rs @@ -1,3 +1,11 @@ +//! This module is responsible for v1's dialing to remote server. +//! +//! It will create a TCP connection to the remote server by calling the +//! Host exported helper function `connect_tcp`, and receives a file descriptor +//! for the connection. +//! +//! Developers can use this lib to create a TCP connection to the remote server (ip:port from config). + use super::*; use anyhow::{anyhow, Ok}; diff --git a/crates/wasm/src/encoder.rs b/crates/wasm/src/encoder.rs index 46adf05..c8d39d2 100644 --- a/crates/wasm/src/encoder.rs +++ b/crates/wasm/src/encoder.rs @@ -1,15 +1,15 @@ +//! Logic for packaging trait + use super::*; use tokio::io::AsyncWrite; -// Developer Guide: Logic for packaging - -// A trait for a encoder, developers should implement this trait and pass it to _write_to_outbound +/// A trait for a encoder, developers should implement this trait and pass it to _write_to_outbound pub trait Encoder { fn encode(&self, input: &[u8], output: &mut [u8]) -> Result; } -// A default encoder that does just copy + paste +/// A default encoder that does just copy + paste pub struct DefaultEncoder; impl Encoder for DefaultEncoder { diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs index f16ca21..bc78cc7 100644 --- a/crates/wasm/src/lib.rs +++ b/crates/wasm/src/lib.rs @@ -1,5 +1,5 @@ -// lib.rs -// export all modules +//! This lib is for a demo and ease of developing the WATM module + pub mod config; pub mod connections; pub mod decoder; @@ -34,10 +34,15 @@ use tracing::{debug, info}; use anyhow::Result; use serde::{Deserialize, Serialize}; -// TODO: move these to speicific implementations, shouldn't be in the crate lib // =================== WASM Imports ===================== extern "C" { - // #[link_name = "create-listen"] + /// These functions are exported from the host to the WATM module, + /// which means host must provide these functions to the WATM module. + /// + /// create a listener (specified by returned fd) -- pass ptr + size for the ip:port struct sharing to Host + // #[link_name = "create_listen"] pub fn create_listen(ptr: u32, size: u32) -> i32; + + /// create a TcpStream connection (specified by returned fd) -- pass ptr + size for the ip:port struct sharing to Host pub fn connect_tcp(ptr: u32, size: u32) -> i32; } diff --git a/crates/wasm/src/listener.rs b/crates/wasm/src/listener.rs deleted file mode 100644 index 1225769..0000000 --- a/crates/wasm/src/listener.rs +++ /dev/null @@ -1,5 +0,0 @@ -use super::*; - -// TODO: Implement a accept wrapper for Host -// #[export_name = "_water_accept"] -// fn _water_accept() {} \ No newline at end of file diff --git a/crates/wasm/src/v1/README.md b/crates/wasm/src/v1/README.md deleted file mode 100644 index 9ae957c..0000000 --- a/crates/wasm/src/v1/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Folder for V1 implementations - -Currently this is just a backup file that not yet being compiled to the library, once V0 is stable will evolve to V1 features. \ No newline at end of file diff --git a/crates/wasm/src/v1/async_listener_v1.rs b/crates/wasm/src/v1/async_listener_v1.rs deleted file mode 100644 index 052f58b..0000000 --- a/crates/wasm/src/v1/async_listener_v1.rs +++ /dev/null @@ -1,245 +0,0 @@ -use crate::*; - -use tokio::{ - io::{AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt}, - net::{TcpListener, TcpStream}, - time, - time::timeout, -}; -use tracing_subscriber::fmt::format; - -use std::net::{ToSocketAddrs, TcpStream as StdTcpStream, SocketAddr}; - -// ----------------------- Listener methods ----------------------- -#[export_name = "v1_listen"] -fn listen() { - wrapper().unwrap(); -} - -fn _listener_creation() -> Result { - let global_conn = match CONN.lock() { - Ok(conf) => conf, - Err(e) => { - eprintln!("[WASM] > ERROR: {}", e); - return Err(std::io::Error::new(std::io::ErrorKind::Other, "failed to lock config")); - } - }; - - let stream = StreamConfigV1::init(global_conn.config.local_address.clone(), global_conn.config.local_port, "LISTEN".to_string()); - - let encoded: Vec = bincode::serialize(&stream).expect("Failed to serialize"); - - let address = encoded.as_ptr() as u32; - let size = encoded.len() as u32; - - let mut fd = -1; - unsafe { - fd = create_listen(address, size); - }; - - if fd < 0 { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "failed to create listener")); - } - - info!("[WASM] ready to start listening at {}:{}", global_conn.config.local_address, global_conn.config.local_port); - - Ok(fd) -} - -#[tokio::main(flavor = "current_thread")] -async fn wrapper() -> std::io::Result<()> { - let fd = _listener_creation().unwrap(); - - // Set up pre-established listening socket. - let standard = unsafe { std::net::TcpListener::from_raw_fd(fd) }; - // standard.set_nonblocking(true).unwrap(); - let listener = TcpListener::from_std(standard)?; - - info!("[WASM] Starting to listen..."); - - loop { - // Accept new sockets in a loop. - let socket = match listener.accept().await { - Ok(s) => s.0, - Err(e) => { - eprintln!("[WASM] > ERROR: {}", e); - continue; - } - }; - - // Spawn a background task for each new connection. - tokio::spawn(async move { - eprintln!("[WASM] > CONNECTED"); - match handle_incoming(socket).await { - Ok(()) => eprintln!("[WASM] > DISCONNECTED"), - Err(e) => eprintln!("[WASM] > ERROR: {}", e), - } - }); - } -} - -// SS handle incoming connections -async fn handle_incoming(mut stream: TcpStream) -> std::io::Result<()> { - let mut buffer = [0; 512]; - - // Read the SOCKS5 greeting - let nbytes = stream.read(&mut buffer).await.expect("Failed to read from stream"); - - println!("Received {} bytes: {:?}", nbytes, buffer[..nbytes].to_vec()); - - if nbytes < 2 || buffer[0] != 0x05 { - eprintln!("Not a SOCKS5 request"); - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Not a SOCKS5 request")); - } - - let nmethods = buffer[1] as usize; - if nbytes < 2 + nmethods { - eprintln!("Incomplete SOCKS5 greeting"); - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Incomplete SOCKS5 greeting")); - } - - // For simplicity, always use "NO AUTHENTICATION REQUIRED" - stream.write_all(&[0x05, 0x00]).await.expect("Failed to write to stream"); - - // Read the actual request - let nbytes = stream.read(&mut buffer).await.expect("Failed to read from stream"); - - println!("Received {} bytes: {:?}", nbytes, buffer[..nbytes].to_vec()); - - if nbytes < 7 || buffer[0] != 0x05 || buffer[1] != 0x01 { - println!("Invalid SOCKS5 request"); - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid SOCKS5 request")); - } - - // Extract address and port - let addr: SocketAddr = match buffer[3] { - 0x01 => { // IPv4 - if nbytes < 10 { - eprintln!("Incomplete request for IPv4 address"); - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Incomplete request for IPv4 address")); - } - let addr = std::net::Ipv4Addr::new(buffer[4], buffer[5], buffer[6], buffer[7]); - let port = u16::from_be_bytes([buffer[8], buffer[9]]); - SocketAddr::from((addr, port)) - }, - 0x03 => { // Domain name - let domain_length = buffer[4] as usize; - if nbytes < domain_length + 5 { - eprintln!("Incomplete request for domain name"); - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Incomplete request for domain name")); - } - let domain = std::str::from_utf8(&buffer[5..5+domain_length]).expect("Invalid domain string"); - - println!("Domain: {}", domain); - - let port = u16::from_be_bytes([buffer[5+domain_length], buffer[5+domain_length+1]]); - - println!("Port: {}", port); - - let domain_with_port = format!("{}:443", domain); // Assuming HTTPS - - // domain.to_socket_addrs().unwrap().next().unwrap() - match domain_with_port.to_socket_addrs() { - Ok(mut addrs) => match addrs.next() { - Some(addr) => addr, - None => { - eprintln!("Domain resolved, but no addresses found for {}", domain); - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("Domain resolved, but no addresses found for {}", domain))); - } - }, - Err(e) => { - eprintln!("Failed to resolve domain {}: {}", domain, e); - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("Failed to resolve domain {}: {}", domain, e))); - } - } - }, - _ => { - eprintln!("Address type not supported"); - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Address type not supported")); - } - }; - - // Connect to target address - let target_stream = StdTcpStream::connect(addr).expect("Failed to connect to target"); - target_stream.set_nonblocking(true).expect("Failed to set non-blocking"); - - let target_stream = TcpStream::from_std(target_stream).expect("Failed to convert to tokio stream"); - - // Construct the response based on the target address - let response = match addr { - SocketAddr::V4(a) => { - let mut r = vec![0x05, 0x00, 0x00, 0x01]; - r.extend_from_slice(&a.ip().octets()); - r.extend_from_slice(&a.port().to_be_bytes()); - r - }, - SocketAddr::V6(a) => { - let mut r = vec![0x05, 0x00, 0x00, 0x04]; - r.extend_from_slice(&a.ip().octets()); - r.extend_from_slice(&a.port().to_be_bytes()); - r - }, - }; - - stream.write_all(&response).await.expect("Failed to write to stream"); - - let (mut client_read, mut client_write) = tokio::io::split(stream); - let (mut target_read, mut target_write) = tokio::io::split(target_stream); - - let client_to_target = async move { - let mut buffer = vec![0; 4096]; - loop { - match client_read.read(&mut buffer).await { - Ok(0) => { - break; - } - Ok(n) => { - if let Err(_) = target_write.write_all(&buffer[0..n]).await { - break; - } - } - Err(_) => break, - } - } - }; - - let target_to_client = async move { - let mut buffer = vec![0; 4096]; - loop { - match target_read.read(&mut buffer).await { - Ok(0) => { - break; - } - Ok(n) => { - if let Err(_) = client_write.write_all(&buffer[0..n]).await { - break; - } - } - Err(_) => break, - } - } - }; - - // Run both handlers concurrently - tokio::join!(client_to_target, target_to_client); - - Ok(()) -} - -// async fn handle_incoming(mut socket: TcpStream) -> std::io::Result<()> { -// loop { -// let mut buf = [0u8; 4096]; - -// // Read some bytes from the socket. -// let read = socket.read(&mut buf).await?; - -// // Handle a clean disconnection. -// if read == 0 { -// return Ok(()); -// } - -// // Write bytes both locally and remotely. -// // std::io::stdout().write_all(&buf[..read])?; -// socket.write_all(&buf[..read]).await?; -// } -// } \ No newline at end of file diff --git a/crates/wasm/src/v1/config_v1.rs b/crates/wasm/src/v1/config_v1.rs deleted file mode 100644 index ce4f64a..0000000 --- a/crates/wasm/src/v1/config_v1.rs +++ /dev/null @@ -1,43 +0,0 @@ -use super::*; - -// A Config currently contains the local + remote ip & port -#[derive(Debug, Deserialize, Clone)] -pub struct Config { - pub local_address: String, - pub local_port: u32, - pub remote_address: String, - pub remote_port: u32, -} - -// implement a constructor for the config -impl Config { - pub fn new() -> Self { - Config { - local_address: String::from("127.0.0.1"), - local_port: 8080, - remote_address: String::from("example.com"), - remote_port: 8082, - } - } -} - -// ============ Some implementation for V1 ============ -// A config struct that shares between your host & wasm to establish a connection -#[cfg(feature = "v1")] -#[derive(Serialize, Deserialize)] -pub struct StreamConfigV1 { - pub addr: String, - pub port: u32, - pub name: String, -} - -#[cfg(feature = "v1")] -impl StreamConfigV1 { - pub fn init(addr: String, port: u32, name: String) -> Self { - StreamConfigV1 { - addr: addr, - port: port, - name: name, - } - } -} \ No newline at end of file diff --git a/crates/wasm/src/v1/dial_v1.rs b/crates/wasm/src/v1/dial_v1.rs deleted file mode 100644 index 07b7e32..0000000 --- a/crates/wasm/src/v1/dial_v1.rs +++ /dev/null @@ -1,57 +0,0 @@ -use super::*; - -use anyhow::{Ok, anyhow}; - -pub struct Dialer { - pub file_conn: Connection, - pub config: Config, -} - -impl Dialer { - pub fn new() -> Self { - Dialer { - file_conn: Connection::new(), - config: Config::new(), - } - } - - // v1 dial, where WASM has the ability to specify ip:port - #[cfg(feature = "v1")] - fn dial_v1(&mut self) -> Result<(), anyhow::Error> { - info!("[WASM] running in dial func..."); - - let mut fd: i32 = -1; - - fd = self.tcp_connect()?; - - if fd < 0 { - eprintln!("failed to create connection to remote"); - return Err(anyhow!("failed to create connection to remote")); - } - - self.file_conn.set_outbound(fd, ConnStream::TcpStream(unsafe { std::net::TcpStream::from_raw_fd(fd) })); - - Ok(()) - } - - #[cfg(feature = "v1")] - fn tcp_connect(&self) -> Result { - let stream = StreamConfigV1::init(self.config.remote_address.clone(), self.config.remote_port, "CONNECT_REMOTE".to_string()); - - let encoded: Vec = bincode::serialize(&stream).expect("Failed to serialize"); - - let address = encoded.as_ptr() as u32; - let size = encoded.len() as u32; - - let mut fd = -1; - unsafe { - fd = connect_tcp(address, size); - }; - - if fd < 0 { - return Err(anyhow!("failed to create listener")); - } - - Ok(fd) - } -} \ No newline at end of file diff --git a/crates/wasm/src/v1/mod.rs b/crates/wasm/src/v1/mod.rs deleted file mode 100644 index 5ee7816..0000000 --- a/crates/wasm/src/v1/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod async_listener_v1; \ No newline at end of file diff --git a/crates/wasm/src/version.rs b/crates/wasm/src/version.rs index 39236cd..848dce7 100644 --- a/crates/wasm/src/version.rs +++ b/crates/wasm/src/version.rs @@ -1,3 +1,7 @@ -// must have something like this in your WASM module, the following is just an example -// #[export_name = "_water_v0"] -// pub static V0: i32 = 0; +//! must have something like this in your WASM module, the following is just an example +//! +//! ```ignore +//! // This attribute specifies the export name of the symbol in the WASM module +//! #[export_name = "_water_v0"] +//! pub static V0: i32 = 0; +//! ``` diff --git a/crates/wasm_v0/README.md b/crates/wasm_v0/README.md new file mode 100644 index 0000000..b77c471 --- /dev/null +++ b/crates/wasm_v0/README.md @@ -0,0 +1,128 @@ +# WebAssembly Transport Module (WATM) APIs -- `water_wasm-v0` + +--- +A set of Universal APIs in WASM when integrate with Rust / Go Host +--- + + +## WASM -> Host + +Every valid WASM Transport Module is required to export a set of functions. + +Currently this documentation contains both `generic` (version-independent) specs and `v0` specs. Unless otherwise specified, all APIs are mandatory. + +### Generic: version independent exports + +```rust +/// init is for the WASM module to setup its internal states with or without a +/// configuration specified by the Host. +/// +/// A configurable WATM should call pull_config() and parse +/// the config file. +#[export_name = "_water_init"] +pub fn _init() -> i32 +``` + +### v0: water draft version 0 + +```rust +/// _v0 showing up in the export list tells the runtime it should interpret +/// this WATM in v0 spec and talk to it with v0 APIs only. +/// +/// The literal name/type of this export does not matter. +#[export_name = "_water_v0"] +pub static VERSION: i32 = v0plus::VERSION; + +/// _cancel_with specifies a file descriptor for the cancellation channel. +/// The WATM will select on this channel, and if successfully read any +/// bytes, abort and exit with error "Aborted". +#[export_name = "_water_cancel_with"] +pub fn _cancel_with(fd: i32) -> i32 + +/// _worker is the entry point for the WATM. It is used to spin up +/// a blocking thread that runs indefinitely, in which the WATM +/// do its tasks asynchronously. +#[export_name = "_water_worker"] +pub fn _worker() -> i32 +``` + +#### v0-Dialer: dialer in water draft version 0 + +All dialer-compliant WATM must also implement + +```rust +// in _dial, a dialer WATM opens the file indicated by +// caller_conn_fd as caller_conn, calls back to the host +// via host_dial() and open the file indicated by dst_conn_fd +// (returned by host_dial) as dst_conn. The dialer UPGRADEs +// the caller_conn and sends upgraded payload to dst_conn. +#[export_name = "_water_dial"] +pub fn _dial(caller_conn_fd: i32) -> i32 // caller_conn_fd -> dst_conn_fd +``` + +#### v0-Listener: listener in water draft version 0 + +All listener-compliant WATM must also implement + +```rust +// in _accept, a listener WATM opens the file indicated by +// caller_conn_fd as caller_conn, calls back to the host via +// host_accept() and open the file indicated by src_conn_fd +// (returned by host_accept) as src_conn. The listener +// UPGRADEs the caller_conn and sends upgraded payload to src_conn. +#[export_name = "_water_accept"] +pub fn _accept(caller_conn_fd: i32) -> i32 // caller_conn_fd -> src_conn_fd +``` + +#### v0-Relay: relay in water draft version 0 + +All relay-compliant WATM must also implement + +```rust +// in _associate, a relay calls back to the host +// via host_accept() to accept src_conn, and +// calls back to the host via host_dial() to +// dial dst_conn. Then, relay UPGRADEs the +// src_conn and sends upgraded payload +// to dst_conn. +#[export_name = "_water_associate"] +pub fn _associate() -> i32 +``` + +## Host -> WASM + +Functions that the host MUST import/link to every WASM Transport Module + +```rust +fn host_accept() -> i32 // -> src_conn_fd +fn host_dial() -> i32 // -> dst_conn_fd +fn host_defer() // notify the host the WASM instance is winding down + +// If no config is available, INVALID_FD will be returned. +// A config-optional WATM SHOULD proceed, and a config-mandatory +// WATM MUST fail. +// +// If config file is set but other error (e.g., I/O Error) happened, +// a trap will be triggered. +fn pull_config() -> i32 // fetch config from the host. -> config_fd +``` + +## Host internal + +``` +// Validate WASMBin to ensure that it implements a compatible transport. +// 1. Ensure using the Module that the binary exposes the required functions +// with the correct signatures. +// 2. On launch, call version and ensure that the version is compatible with +// the current Host library version. +// 3. Check for presense and correctness of any version specific function +// signatures. +fn validate() -> Error<()> +func validate() error + +// TODO: research how to cleanly implement this on Go. +// currently it seems we will need to dump exports one by one and cast/test them into +// FuncType, then pre-build the signatures to compare them to. This doesn't look clean +// enough. +``` + diff --git a/crates/wasm_v0/src/common.rs b/crates/wasm_v0/src/common.rs index ae945e7..6885272 100644 --- a/crates/wasm_v0/src/common.rs +++ b/crates/wasm_v0/src/common.rs @@ -1,20 +1,26 @@ +//! The common code for v0 development of WATM module + use std::os::fd::FromRawFd; use tokio::net::TcpStream; // WASI Imports extern "C" { - pub fn host_accept() -> i32; // obtain a connection (specified by returned fd) accepted by the host - pub fn host_dial() -> i32; // obtain a connection (specified by returned fd) dialed by the host - pub fn host_defer(); // call when exiting + /// obtain a connection (specified by returned fd) accepted by the host + pub fn host_accept() -> i32; + /// obtain a connection (specified by returned fd) dialed by the host + pub fn host_dial() -> i32; + /// call when exiting + pub fn host_defer(); + /// obtain a configuration file (specified by returned fd) from the host #[allow(dead_code)] - pub fn pull_config() -> i32; // obtain a configuration file (specified by returned fd) from the host + pub fn pull_config() -> i32; } -// enumerated constants for Role (i32) -// 0: unknown -// 1: dialer -// 2: listener -// 3: relay +/// enumerated constants for Role (i32) +/// 0: unknown +/// 1: dialer +/// 2: listener +/// 3: relay #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Role { Unknown = 0, @@ -25,7 +31,8 @@ pub enum Role { pub struct AsyncFdConn { fd: i32, - temp_stream: Option, // used to hold the std tcp stream, will be upgraded to tokio stream later + /// used to hold the std tcp stream, will be upgraded to tokio stream later + temp_stream: Option, stream: Option, } @@ -56,18 +63,11 @@ impl AsyncFdConn { let stdstream = unsafe { std::net::TcpStream::from_raw_fd(fd) }; self.temp_stream = Some(stdstream); - // println!("wrap: stdstream = {:?}", stdstream); - // stdstream - // .set_nonblocking(true) - // .expect("Failed to set non-blocking"); - // println!("wrap: stream = {:?}", stdstream); - // self.stream = - // Some(TcpStream::from_std(stdstream).expect("Failed to convert to tokio stream")); - // Ok(()) Ok(()) } + /// upgrade the std stream to tokio stream where to explicitly make it non-blocking for async pub fn tokio_upgrade(&mut self) -> Result<(), String> { if self.fd < 0 { return Err("invalid fd".to_string()); diff --git a/crates/wasm_v0/src/error.rs b/crates/wasm_v0/src/error.rs index c816173..641c70f 100644 --- a/crates/wasm_v0/src/error.rs +++ b/crates/wasm_v0/src/error.rs @@ -1,16 +1,26 @@ -// Error is a enum in i32 +//! Error codes for the WATM module + +/// Error is a enum in i32 #[allow(dead_code)] #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Error { None = 0, - Unknown = -1, // general error - InvalidArgument = -2, // invalid argument supplied to func call - InvalidConfig = -3, // config file provided is invalid - InvalidFd = -4, // invalid file descriptor provided - InvalidFunction = -5, // invalid function called - DoubleInit = -6, // initializing twice - FailedIO = -7, // Failing an I/O operation - NotInitialized = -8, // not initialized + /// general error + Unknown = -1, + /// invalid argument supplied to func call + InvalidArgument = -2, + /// config file provided is invalid + InvalidConfig = -3, + /// invalid file descriptor provided + InvalidFd = -4, + /// invalid function called + InvalidFunction = -5, + /// initializing twice + DoubleInit = -6, + /// Failing an I/O operation + FailedIO = -7, + /// not initialized + NotInitialized = -8, } impl Error { diff --git a/crates/wasm_v0/src/v0plus.rs b/crates/wasm_v0/src/v0plus.rs index c93a3a3..d63373a 100644 --- a/crates/wasm_v0/src/v0plus.rs +++ b/crates/wasm_v0/src/v0plus.rs @@ -1,6 +1,9 @@ +//! v0plus APIsm, which supports async connections for WATM module + use crate::{common::*, error}; -pub const VERSION: i32 = 0x00000000; // v0plus share the same version number with v0 +/// v0plus share the same version number with v0 +pub const VERSION: i32 = 0x00000000; pub struct Dialer { caller_conn: AsyncFdConn, @@ -11,6 +14,7 @@ pub struct Listener { caller_conn: AsyncFdConn, source_conn: AsyncFdConn, } + pub struct Relay { source_conn: AsyncFdConn, remote_conn: AsyncFdConn, diff --git a/crates/water/README.md b/crates/water/README.md index 4cca134..4f00bf9 100644 --- a/crates/water/README.md +++ b/crates/water/README.md @@ -1,6 +1,154 @@ -# Host Library -- `water` +# Rust APIs -- `water` -A library for integrating WATER, satisfies APIs [here](https://app.gitbook.com/o/KHlQypYtIQKkb8YeZ6Hx/s/lVX5MqollomuX6vW80T6/rust-apis). +Library docs will be availale soon on `docs.rs`, for now you can run `cargo doc --no-deps --open` to generate the docs locally. + +## External (Caller Facing) + +

design diagram draft1

+ +## Examples + +
+ +Example 1: v0 simple tcp connection sending plain text + +```rust +// Some config.json file with the following configs +// { +// "remote_address": "127.0.0.1", +// "remote_port": 8080, +// "local_address": "127.0.0.1", +// "local_port": 8088, +// "bypass": false +// } + +// construct the config +let conf = config::WATERConfig::init( + String::from("./test_wasm/plain.wasm"), + String::from("_water_worker"), + String::from("above_config.json"), + config::WaterBinType::Dial, + true, +) +.unwrap(); + +// creating the WATER client +let mut water_client = runtime::client::WATERClient::new(conf).unwrap(); + +// connecting to the remote_address:remote_port +water_client.connect().unwrap(); + +// setup the cancel pipe for exiting +water_client.cancel_with().unwrap(); + +// run the worker in WATM which is running in a separate thread +let handle_water = water_client.run_worker().unwrap(); + +let test_message = b"hello"; + +// sending to the remote end with the packaging logic (here is plain, depends on the WATM) +water_client.write(test_message).unwrap(); + +// reading from the remote end with the unpackaging logic (here is plain, depends on the WATM) +let mut buf = vec![0; 32]; +let res = water_client.read(&mut buf); + +// close the connection +water_client.cancel().unwrap(); + +// clean things up and wait for the thread to join +drop(file); +dir.close()?; +handle.join().unwrap(); +match handle_water.join().unwrap() { + Ok(_) => {} + Err(e) => { + eprintln!("Running _water_worker ERROR: {}", e); + return Err(Box::new(Error::new( + ErrorKind::Other, + "Failed to join _water_worker thread", + ))); + } +}; +``` +
+ +
+ +Example 2: v1 shadowsocks + +```rust +// first setup the official shadowsocks_server end on port 8088 +const SERVER_ADDR: &str = "127.0.0.1:8088"; +const LOCAL_ADDR: &str = "127.0.0.1:8081"; + +// have the shared password +const PASSWORD: &str = "WATERisAwesome!23"; + +// using CHACHA20 as the cipher method +const METHOD: CipherKind = CipherKind::CHACHA20_POLY1305; + +let svr = Socks5TestServer::new(SERVER_ADDR, LOCAL_ADDR, PASSWORD, METHOD, false); +svr.run().await; + +// Some config.json file with the following configs +// { +// "remote_address": "127.0.0.1", +// "remote_port": 8088, +// "local_address": "127.0.0.1", +// "local_port": 8080, +// "password": "WATERisAwesome!23", +// "bypass": false +// } + +// construct the config +let conf = config::WATERConfig::init( + String::from("./test_wasm/ss_client_wasm.wasm"), + String::from("v1_listen"), + String::from("above_config.json"), + // Runner type is currently for the relay implementation for v1 + config::WaterBinType::Runner, + true, +) +.unwrap(); + +// creating the WATER client +let mut water_client = runtime::client::WATERClient::new(conf).unwrap(); + +// spawn a thread to run the Shadowsocks client WATM +thread::spawn(move || { + water_client.execute().unwrap(); +}); + +// creating the SocketAddr for ss_client +let wasm_ss_client_addr = SocketAddr::new("127.0.0.1".parse().unwrap(), 8080); + +// Give some time for the WASM client to start +thread::sleep(Duration::from_millis(100)); + +// test the Shadowsocks client WATM +let mut c = Socks5TcpClient::connect( + Address::DomainNameAddress("detectportal.firefox.com".to_owned(), 80), + wasm_ss_client_addr, +) +.await +.unwrap(); + +let req = b"GET /success.txt HTTP/1.0\r\nHost: detectportal.firefox.com\r\nAccept: */*\r\n\r\n"; +c.write_all(req).await.unwrap(); +c.flush().await.unwrap(); + +let mut r = BufReader::new(c); + +let mut buf = Vec::new(); +r.read_until(b'\n', &mut buf).await.unwrap(); + +let http_status = b"HTTP/1.0 200 OK\r\n"; +assert!(buf.starts_with(http_status)); +``` +
+ +More example usages can be found in `./tests/tests/`. ## Designs **execute**: @@ -9,5 +157,4 @@ A library for integrating WATER, satisfies APIs [here](https://app.gitbook.com/o 1. memory initialiation & limitation 2. (`v1_preview` feature) wasm_config sharing to WASM 3. export helper functions (e.g. creation of TCP, TLS, crypto, etc) -3. (`v1` feature) setup multi-threading -4. Run the `entry_fn` or execute as the Role (`Dial`, `Listen`, `Relay`) \ No newline at end of file +3. Run the `entry_fn` or execute as the Role (`Dial`, `Listen`, `Relay`) diff --git a/crates/water/src/config/mod.rs b/crates/water/src/config/mod.rs index ad17958..37116db 100644 --- a/crates/water/src/config/mod.rs +++ b/crates/water/src/config/mod.rs @@ -1,10 +1,24 @@ +//! Configuration info for the loading .wasm binary +//! +//! Passed as command line arguments when used with cli tool +//! +//! Will have the similar feat as required in [issue#19](https://github.com/gaukas/water/issues/19) on the go-side. + pub mod wasm_shared_config; +/// WATER configuration #[derive(Clone)] pub struct WATERConfig { + /// Path to the .wasm binary pub filepath: String, + + /// Entry function name pub entry_fn: String, + + /// Path to the configuration file for the WATM binary pub config_wasm: String, + + /// Type of the client -- currently support Dial, Listen, Relay, Runner pub client_type: WaterBinType, pub debug: bool, @@ -28,14 +42,15 @@ impl WATERConfig { } } +/// WATER client type: A enum of types of the client #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum WaterBinType { - Unknown, - Wrap, Dial, Listen, Relay, Runner, + Wrap, + Unknown, } impl From for WaterBinType { @@ -43,9 +58,9 @@ impl From for WaterBinType { match num { 0 => WaterBinType::Dial, 1 => WaterBinType::Listen, - 2 => WaterBinType::Runner, - 3 => WaterBinType::Wrap, - 4 => WaterBinType::Relay, + 2 => WaterBinType::Relay, + 3 => WaterBinType::Runner, + 4 => WaterBinType::Wrap, _ => WaterBinType::Unknown, } } diff --git a/crates/water/src/config/parser.rs b/crates/water/src/config/parser.rs deleted file mode 100644 index e69de29..0000000 diff --git a/crates/water/src/config/wasm_shared_config.rs b/crates/water/src/config/wasm_shared_config.rs index 0070f42..76b1540 100644 --- a/crates/water/src/config/wasm_shared_config.rs +++ b/crates/water/src/config/wasm_shared_config.rs @@ -1,42 +1,34 @@ +//! This module defines the `WASMSharedConfig` struct, which is used to pass infos between the host and the WATM module. +//! +//! This is a temporary workaround for the constraint that the WATM module can only take in parameters with primitive types. +//! +//! We are designing a better way to do this, which can be uniform across all Host languages (Go). +//! +//! Currently only used for v1 implementations, where v1 grant the ability for the WATM module to dial and listen on specific ip + ports. + use serde::{Deserialize, Serialize}; use std::mem; -#[repr(C)] -pub struct WASMSharedConfig { - // pub key: u64, // a pointer to a key string's byte-view - // pub size: u64, // size for the key -} - -impl WASMSharedConfig { - pub fn to_bytes(&self) -> Vec { - let size = mem::size_of::(); - let ptr = self as *const Self; - - let bytes_slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, size) }; - bytes_slice.to_vec() - } - - // pub fn from_bytes(bytes: Vec) -> Option { - // let size = mem::size_of::(); - // if bytes.len() != size { - // return None; - // } - // let mut struct_bytes = bytes; - // let ptr = struct_bytes.as_mut_ptr() as *mut Self; - // let struct_ref = unsafe { Box::from_raw(ptr) }; - // Some(*struct_ref) - // } -} - +/// This struct is used to pass infos between the host and the WATM module. Only addr, port, and name are used for creating the connection for now. #[derive(Serialize, Deserialize)] #[repr(C)] pub struct StreamConfig { + /// ip address pub addr: String, + + /// port pub port: u32, + + /// a name for the stream pub name: String, } impl StreamConfig { + /// Convert the struct to a byte array -- the way of sharing data between the host and the WATM module + /// is using memory sharing where the WATM module will pass in a pointer to the memory location of the byte array to the Host helper function. + /// + /// Then the Host helper function will offset the pointer into WASM's memory addresses to retreive the byte array + /// and convert it back to the struct. pub fn to_bytes(&self) -> Vec { let size = mem::size_of::(); let ptr = self as *const Self; diff --git a/crates/water/src/errors/mod.rs b/crates/water/src/errors/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/crates/water/src/errors/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/water/src/globals.rs b/crates/water/src/globals.rs index 3c9220f..212e10e 100644 --- a/crates/water/src/globals.rs +++ b/crates/water/src/globals.rs @@ -1,7 +1,13 @@ +//! Global constants used by the WATER runtime. +//! +//! Most of these are function names exported by the WATM module, +//! +//! and some default path for the WATM module and the config file. + #![allow(dead_code)] pub const WASM_PATH: &str = "./proxy.wasm"; -pub const CONFIG_WASM_PATH: &str = "./conf.json"; +pub const CONFIG_WASM_PATH: &str = "./config.json"; pub const MAIN: &str = "main"; pub const VERSION_FN: &str = "_water_version"; diff --git a/crates/water/src/lib.rs b/crates/water/src/lib.rs index 58b06d1..37d5818 100644 --- a/crates/water/src/lib.rs +++ b/crates/water/src/lib.rs @@ -8,10 +8,8 @@ extern crate wasmtime_wasi; extern crate wasmtime_wasi_threads; pub mod config; -pub mod errors; pub mod globals; pub mod runtime; -pub mod utils; #[cfg(test)] mod tests { diff --git a/crates/water/src/runtime/client.rs b/crates/water/src/runtime/client.rs index c2041c7..2ab8951 100644 --- a/crates/water/src/runtime/client.rs +++ b/crates/water/src/runtime/client.rs @@ -1,16 +1,30 @@ +//! This module is to define the `WATERClient` struct and its methods +//! +//! `WATERClient` is the main struct that holds the WATERClientType and WATERConfig; used as the entry point of using the WATER runtime +//! +//! `WATERClientType` is an enum type that holds different types of clients + use crate::runtime::*; use listener::WATERListenerTrait; use relay::WATERRelayTrait; use stream::WATERStreamTrait; -// =================== WATERClient Definition =================== +/// `WATERClientType` Definition: A enum type to hold different types of clients pub enum WATERClientType { + /// `Dialer`: create 1 WATM instance with the given `.wasm` binary to connect to a remote address Dialer(Box), + + /// `Listener`: create 1 WATM instance with the given `.wasm` binary to listen on a local address, and accept 1 connection (v0) or multiple connections asynchronizely (v1) Listener(Box), + + /// `Relay`: create 1 WATM instance with the given `.wasm` binary to listen on a local address, and connect to a remote address Relay(Box), - Runner(WATERRunner), // This is a customized runner -- not like any stream + + /// `Runner`: create 1 WATM instance with the given `.wasm` binary to run the `entry_fn` + Runner(WATERRunner), // This is a customized runner -- not like any stream; currently can run v1 relay (shadowsocks client) } +/// `WATERClient` is used as the object for entering and managing the WASM runtime pub struct WATERClient { debug: bool, @@ -19,13 +33,14 @@ pub struct WATERClient { } impl WATERClient { + /// `new` is the constructor of `WATERClient` + /// it checks the client type and the version to create the corresponding `WATERClientType` pub fn new(conf: WATERConfig) -> Result { info!("[HOST] WATERClient initializing ..."); - let mut core = H2O::init(&conf)?; + let mut core = H2O::init_core(&conf)?; core._prepare(&conf)?; - // client_type: 0 -> Dialer, 1 -> Listener, 2 -> Runner let water = match conf.client_type { WaterBinType::Dial => { let stream = match core.version { @@ -81,6 +96,8 @@ impl WATERClient { }) } + /// keep_listen is the function that is called when user wants to accept a newly income connection, + /// it creates a new WASM instance and migrate the previous listener to it. Used by v0 listener and relay for now. pub fn keep_listen(&mut self) -> Result { info!("[HOST] WATERClient keep listening...",); @@ -111,6 +128,7 @@ impl WATERClient { self.debug = debug; } + /// `connect` is the entry point for `Dialer` to connect to a remote address pub fn connect(&mut self) -> Result<(), anyhow::Error> { info!("[HOST] WATERClient connecting ..."); @@ -125,34 +143,25 @@ impl WATERClient { Ok(()) } + /// `listen` is the function for `Listener` and `Relay` to create the Listener and listen on a local addr pub fn listen(&mut self) -> Result<(), anyhow::Error> { - info!("[HOST] WATERClient listening ..."); + info!("[HOST] WATERClient creating listener ..."); match &mut self.stream { WATERClientType::Listener(listener) => { listener.listen(&self.config)?; } - _ => { - return Err(anyhow::anyhow!("[HOST] This client is not a Listener")); - } - } - Ok(()) - } - - pub fn relay(&mut self) -> Result<(), anyhow::Error> { - info!("[HOST] WATERClient relaying ..."); - - match &mut self.stream { WATERClientType::Relay(relay) => { - relay.relay(&self.config)?; + relay.listen(&self.config)?; } _ => { - return Err(anyhow::anyhow!("[HOST] This client is not a Relay")); + return Err(anyhow::anyhow!("[HOST] This client is not a Listener")); } } Ok(()) } + /// `associate` is the entry point for `Relay` to associate with a remote addr pub fn associate(&mut self) -> Result<(), anyhow::Error> { info!("[HOST] WATERClient relaying ..."); @@ -167,6 +176,8 @@ impl WATERClient { Ok(()) } + /// `accept` is the entry point for `Listener` to accept a connection + /// called after `listen` pub fn accept(&mut self) -> Result<(), anyhow::Error> { info!("[HOST] WATERClient accepting ..."); @@ -181,7 +192,8 @@ impl WATERClient { Ok(()) } - // this will start a worker(WATM) in a separate thread -- returns a JoinHandle + /// `run_worker` is the entry point for `Runner` to run the entry_fn(a worker in WATM) in a separate thread + /// it will return a `JoinHandle` for the caller to manage the thread -- used by v0 currently pub fn run_worker( &mut self, ) -> Result>, anyhow::Error> { @@ -195,7 +207,8 @@ impl WATERClient { } } - // this will run the entry_fn(WATM) in the current thread -- replace Host when running + /// `execute` is the entry point for `Runner` to run the entry_fn(a worker in WATM) in the current thread + /// -- replace the thread running Host when running it <- used by v1 currently pub fn execute(&mut self) -> Result<(), anyhow::Error> { info!("[HOST] WATERClient Executing ..."); @@ -216,7 +229,7 @@ impl WATERClient { Ok(()) } - // v0 func for Host to set pipe for canceling later + /// `cancel_with` is the function to set the pipe for canceling later -- v0 pub fn cancel_with(&mut self) -> Result<(), anyhow::Error> { info!("[HOST] WATERClient cancel_with ..."); @@ -238,7 +251,7 @@ impl WATERClient { Ok(()) } - // v0 func for Host to terminate the separate thread running worker(WATM) + /// `cancel` is the function to cancel the thread running the entry_fn -- v0 pub fn cancel(&mut self) -> Result<(), anyhow::Error> { info!("[HOST] WATERClient canceling ..."); @@ -260,6 +273,7 @@ impl WATERClient { Ok(()) } + /// `read` is the function to read from the stream pub fn read(&mut self, buf: &mut Vec) -> Result { info!("[HOST] WATERClient reading ..."); @@ -274,6 +288,7 @@ impl WATERClient { Ok(read_bytes) } + /// `write` is the function to write to the stream pub fn write(&mut self, buf: &[u8]) -> Result<(), anyhow::Error> { info!("[HOST] WATERClient writing ..."); diff --git a/crates/water/src/runtime/core.rs b/crates/water/src/runtime/core.rs index 41bb68f..8ed51a6 100644 --- a/crates/water/src/runtime/core.rs +++ b/crates/water/src/runtime/core.rs @@ -1,13 +1,18 @@ +//! This is the core of the runtime, which is responsible for loading the WASM module and +//! initializing the runtime. It also provides the interface for the host to interact with the runtime. + use std::sync::Mutex; use crate::runtime::*; +/// Host is storing the WasiCtx that we are using, and for the later features will also support the WasiThreadsCtx #[derive(Default, Clone)] pub struct Host { pub preview1_ctx: Option, pub wasi_threads: Option>>, } +/// This is the core of the runtime, which stores the necessary components for a WASM runtime and the version of the WATM module. #[derive(Clone)] pub struct H2O { pub version: Version, @@ -20,7 +25,8 @@ pub struct H2O { } impl H2O { - pub fn init(conf: &WATERConfig) -> Result { + /// generate a new H2O core instance + pub fn init_core(conf: &WATERConfig) -> Result { info!("[HOST] WATERCore H2O initing..."); let wasm_config = wasmtime::Config::new(); @@ -61,6 +67,7 @@ impl H2O { } }); + // MUST have a version -- otherwise return error if version.is_none() { if let Some(e) = error_occured { return Err(e); @@ -68,10 +75,62 @@ impl H2O { return Err(anyhow::Error::msg("WATM module version not found")); } - Self::create_core(conf, linker, store, module, engine, version) + Self::setup_core(conf, linker, store, module, engine, version) + } + + /// This function is for migrating the v0 core for listener and relay + /// to handle every new connection will create a new separate core (as v0 spec) + pub fn v0_migrate_core(conf: &WATERConfig, core: &H2O) -> Result { + info!("[HOST] WATERCore H2O v0_migrating..."); + + // reseting the listener accepted_fd or the relay's accepted_fd & dial_fd + // when migrating from existed listener / relay + let version = match &core.version { + Version::V0(v0conf) => { + match v0conf { + Some(og_v0_conf) => match og_v0_conf.lock() { + Ok(og_v0_conf) => { + let mut new_v0_conf_inner = og_v0_conf.clone(); + // reset the new cloned v0conf + new_v0_conf_inner.reset_listener_or_relay(); + + Version::V0(Some(Arc::new(Mutex::new(new_v0_conf_inner)))) + } + Err(e) => { + return Err(anyhow::anyhow!("Failed to lock v0_conf: {}", e))?; + } + }, + None => { + return Err(anyhow::anyhow!("v0_conf is None"))?; + } + } + } + _ => { + return Err(anyhow::anyhow!("This is not a V0 core"))?; + } + }; + + // NOTE: Some of the followings can reuse the existing core, leave to later explore + let wasm_config = wasmtime::Config::new(); + + #[cfg(feature = "multithread")] + { + wasm_config.wasm_threads(true); + } + + let engine = Engine::new(&wasm_config)?; + let linker: Linker = Linker::new(&engine); + + let module = Module::from_file(&engine, &conf.filepath)?; + + let host = Host::default(); + let store = Store::new(&engine, host); + + Self::setup_core(conf, linker, store, module, engine, Some(version)) } - pub fn create_core( + /// called by init_core() or v0_migrate_core() to setup the core (reduce code duplication) + pub fn setup_core( conf: &WATERConfig, mut linker: Linker, mut store: Store, @@ -89,7 +148,7 @@ impl H2O { wasmtime_wasi::add_to_linker(&mut linker, |h: &mut Host| h.preview1_ctx.as_mut().unwrap())?; - // initializing stuff for multithreading -- currently not used yet (v1+ feature) + /// initialization for WASI-multithread -- currently not completed / used (v1+ feature) #[cfg(feature = "multithread")] { store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new( @@ -106,6 +165,7 @@ impl H2O { // export functions -- version dependent -- has to be done before instantiate match &version { + // V0 export functions Some(Version::V0(ref config)) => match config { Some(v0_conf) => { v0::funcs::export_tcp_connect(&mut linker, Arc::clone(v0_conf))?; @@ -118,13 +178,16 @@ impl H2O { ))?; } }, + + // V1 export functions Some(Version::V1) => { v1::funcs::export_tcp_connect(&mut linker)?; v1::funcs::export_tcplistener_create(&mut linker)?; } + // add export funcs for other versions here _ => { unimplemented!("This version is not supported yet") - } // add export funcs for other versions here + } } // export functions -- version independent @@ -150,63 +213,13 @@ impl H2O { }) } - // This function is for migrating the v0 core for listener and relay - // to handle every new connection is creating a new separate core (as v0 spec) - pub fn v0_migrate_core(conf: &WATERConfig, core: &H2O) -> Result { - info!("[HOST] WATERCore H2O v0_migrating..."); - - // reseting the listener accepted_fd or the relay's accepted_fd & dial_fd - // when migrating from existed listener / relay - let version = match &core.version { - Version::V0(v0conf) => { - match v0conf { - Some(og_v0_conf) => match og_v0_conf.lock() { - Ok(og_v0_conf) => { - let mut new_v0_conf_inner = og_v0_conf.clone(); - // reset the new cloned v0conf - new_v0_conf_inner.reset_listener_or_relay(); - - Version::V0(Some(Arc::new(Mutex::new(new_v0_conf_inner)))) - } - Err(e) => { - return Err(anyhow::anyhow!("Failed to lock v0_conf: {}", e))?; - } - }, - None => { - return Err(anyhow::anyhow!("v0_conf is None"))?; - } - } - } - _ => { - return Err(anyhow::anyhow!("This is not a V0 core"))?; - } - }; - - // NOTE: Some of the followings can reuse the existing core, leave to later explore - let wasm_config = wasmtime::Config::new(); - - #[cfg(feature = "multithread")] - { - wasm_config.wasm_threads(true); - } - - let engine = Engine::new(&wasm_config)?; - let linker: Linker = Linker::new(&engine); - - let module = Module::from_file(&engine, &conf.filepath)?; - - let host = Host::default(); - let store = Store::new(&engine, host); - - Self::create_core(conf, linker, store, module, engine, Some(version)) - } - pub fn _prepare(&mut self, conf: &WATERConfig) -> Result<(), anyhow::Error> { self._init(conf.debug)?; self._process_config(conf)?; // This is for now needed only by v1_preview Ok(()) } + /// This function is called when the host wants to call _init() in WASM pub fn _init(&mut self, debug: bool) -> Result<(), anyhow::Error> { info!("[HOST] WATERCore calling _init from WASM..."); @@ -233,6 +246,9 @@ impl H2O { Ok(()) } + /// This function is called when the host the WATM module to process the configurations, + /// currently used by v1_preview, will change the behavior later to be + /// a exported function from Host to WASM to let the WASM module to pull the config. pub fn _process_config(&mut self, config: &WATERConfig) -> Result<(), anyhow::Error> { info!("[HOST] WATERCore calling _process_config from WASM..."); diff --git a/crates/water/src/runtime/listener.rs b/crates/water/src/runtime/listener.rs index b0ebd3a..8d5a8b9 100644 --- a/crates/water/src/runtime/listener.rs +++ b/crates/water/src/runtime/listener.rs @@ -1,3 +1,5 @@ +//! Listener trait for WATER runtime. + use crate::runtime::{transport::WATERTransportTrait, *}; pub trait WATERListenerTrait: WATERTransportTrait { diff --git a/crates/water/src/runtime/mod.rs b/crates/water/src/runtime/mod.rs index cd7f347..a0ef346 100644 --- a/crates/water/src/runtime/mod.rs +++ b/crates/water/src/runtime/mod.rs @@ -1,3 +1,5 @@ +//! This module contains the runtime implementation of using WASM, including the host, core, and related interaction operations. + // =================== MODULES =================== pub mod client; pub mod core; diff --git a/crates/water/src/runtime/net/tcp.rs b/crates/water/src/runtime/net/tcp.rs deleted file mode 100644 index e69de29..0000000 diff --git a/crates/water/src/runtime/net/tls.rs b/crates/water/src/runtime/net/tls.rs deleted file mode 100644 index e69de29..0000000 diff --git a/crates/water/src/runtime/relay.rs b/crates/water/src/runtime/relay.rs index 6ba90dc..439e267 100644 --- a/crates/water/src/runtime/relay.rs +++ b/crates/water/src/runtime/relay.rs @@ -1,7 +1,9 @@ +//! Relay trait for WATER runtime (currently used for v0 only, v1 is using Runner for relay(ShadowSocks)). + use crate::runtime::{transport::WATERTransportTrait, *}; pub trait WATERRelayTrait: WATERTransportTrait { fn associate(&mut self, conf: &WATERConfig) -> Result<(), anyhow::Error>; - fn relay(&mut self, conf: &WATERConfig) -> Result<(), anyhow::Error>; + fn listen(&mut self, conf: &WATERConfig) -> Result<(), anyhow::Error>; } diff --git a/crates/water/src/runtime/runner.rs b/crates/water/src/runtime/runner.rs index 1e658a8..8d3901a 100644 --- a/crates/water/src/runtime/runner.rs +++ b/crates/water/src/runtime/runner.rs @@ -1,3 +1,6 @@ +//! A general runner that don't have any pre-defined roles where it all depends on the configuration passed in. +//! Currently used for v1_preview's relay mode, running shadowsocks.wasm. + use crate::runtime::*; pub struct WATERRunner { diff --git a/crates/water/src/runtime/stream.rs b/crates/water/src/runtime/stream.rs index b348c82..1ebf6b5 100644 --- a/crates/water/src/runtime/stream.rs +++ b/crates/water/src/runtime/stream.rs @@ -1,3 +1,5 @@ +//! Stream trait for WATER runtime. + use crate::runtime::{transport::WATERTransportTrait, *}; pub trait WATERStreamTrait: WATERTransportTrait { diff --git a/crates/water/src/runtime/transport.rs b/crates/water/src/runtime/transport.rs index e0a6a0b..f05d6bf 100644 --- a/crates/water/src/runtime/transport.rs +++ b/crates/water/src/runtime/transport.rs @@ -1,3 +1,6 @@ +//! Transport trait for WATER runtime that will be implemented by each version and all roles of WATM, +//! It is for the WATM module to communicate with the Host via UnixStream. + use std::thread::JoinHandle; use crate::runtime::*; @@ -46,25 +49,36 @@ pub trait WATERTransportTrait: Send { } } - // ============================ v0 only ============================ + // ======================== v0 only below for now ======================== // Methods to provide access to the shared state, not implemented by default + + /// v0 only, Get the caller_io (UnixStream) from the WATM runtime object fn get_caller_io(&mut self) -> &mut Option { unimplemented!("get_caller_io not implemented") } + + /// v0 only, Get the cancel_io (UnixStream) from the WATM runtime object fn get_cancel_io(&mut self) -> &mut Option { unimplemented!("get_cancel_io not implemented") } + + /// v0 only, Get the core (H2O) from the WATM runtime object fn get_core(&mut self) -> &mut H2O { unimplemented!("get_core not implemented") } + /// v0 only, Set the caller_io (UnixStream) in the WATM runtime object fn set_caller_io(&mut self, _caller_io: Option) { unimplemented!("set_caller_io not implemented") } + + /// v0 only, Set the cancel_io (UnixStream) in the WATM runtime object fn set_cancel_io(&mut self, _cancel_io: Option) { unimplemented!("set_cancel_io not implemented") } + /// v0 only, Set the cancel_io (UnixStream) in the WATM runtime object and + /// call the corresponding setup function in WATM fn cancel_with(&mut self, _conf: &WATERConfig) -> Result<(), anyhow::Error> { info!("[HOST] WATERTransport v0 cancel_with..."); @@ -126,6 +140,7 @@ pub trait WATERTransportTrait: Send { Ok(()) } + /// v0 only, Cancel the connection by writing to the prev set cancel_io (UnixStream) fn cancel(&mut self, _conf: &WATERConfig) -> Result<(), anyhow::Error> { info!("[HOST] WATERTransport v0 cancel..."); @@ -149,6 +164,7 @@ pub trait WATERTransportTrait: Send { } } + /// v0 only, Run the entry_fn in a separate thread fn run_entry_fn( &mut self, conf: &WATERConfig, diff --git a/crates/water/src/runtime/v0/config.rs b/crates/water/src/runtime/v0/config.rs index 22058cb..c8adb20 100644 --- a/crates/water/src/runtime/v0/config.rs +++ b/crates/water/src/runtime/v0/config.rs @@ -1,3 +1,5 @@ +//! Configurations for the v0 runtime + use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd}; use anyhow::Context; @@ -19,7 +21,7 @@ impl Default for Config { } } -// implement a constructor for the config +/// Constructor for the config impl Config { pub fn new() -> Self { Config { @@ -33,7 +35,6 @@ impl Config { pub fn from(config_file: &str) -> Result { let config_file = std::fs::read_to_string(config_file).context("failed to read config file")?; - // let config: Config = json::from_str(&config_file).context("failed to parse config file")?; let config: Config = match serde_json::from_str(&config_file) { Ok(config) => config, @@ -47,16 +48,21 @@ impl Config { } } +/// A enum to store the role of the connection for v0 as well as the fd for the connection +/// Listener and Relay will have multiple fds for bi-directional connections. #[derive(Debug, Clone)] pub enum V0CRole { Unknown, Dialer(i32), - Listener(i32, i32), // listener_fd, accepted_fd - Relay(i32, i32, i32), // listener_fd, accepted_fd, dialer_fd + + /// listener_fd, accepted_fd + Listener(i32, i32), + + /// listener_fd, accepted_fd, dialer_fd + Relay(i32, i32, i32), } -// V0 specific configurations -// The addr:port pair will either be local / remote depend on the client_type +/// V0 specific configurations with the V0Role stored #[derive(Debug, Clone)] pub struct V0Config { pub name: String, diff --git a/crates/water/src/runtime/v0/funcs.rs b/crates/water/src/runtime/v0/funcs.rs index c25af7b..d0b82b8 100644 --- a/crates/water/src/runtime/v0/funcs.rs +++ b/crates/water/src/runtime/v0/funcs.rs @@ -1,7 +1,11 @@ +//! Exported functions implementation for v0 WATM module from the Host + use crate::runtime::v0::config::V0Config; use crate::runtime::*; use std::sync::{Arc, Mutex}; +/// This function is exporting the `host_dial() -> i32` +/// to the WATM where it is used to create a tcp connection and returns the fd of the connection used by Dialer & Relay. pub fn export_tcp_connect( linker: &mut Linker, config: Arc>, @@ -40,6 +44,8 @@ pub fn export_tcp_connect( Ok(()) } +/// This function is exporting the `host_accept() -> i32` +/// to the WATM where it is used to accept a incoming connection from the listener and returns the fd of the connection used by Listener & Relay. pub fn export_accept( linker: &mut Linker, config: Arc>, @@ -78,7 +84,7 @@ pub fn export_accept( Ok(()) } -// TODO: implement this +/// This function is exporting the `host_defer()` to the WATM where it is used to close the connection. pub fn export_defer( linker: &mut Linker, config: Arc>, diff --git a/crates/water/src/runtime/v0/listener.rs b/crates/water/src/runtime/v0/listener.rs index c87da97..f24ddf3 100644 --- a/crates/water/src/runtime/v0/listener.rs +++ b/crates/water/src/runtime/v0/listener.rs @@ -1,14 +1,18 @@ +//! This file contains the v0_plus WATERListener implementation, +//! it implements the WATERListenerTrait and WATERTransportTrait. + use crate::runtime::{listener::WATERListenerTrait, transport::WATERTransportTrait, *}; pub struct WATERListener { - pub caller_io: Option, // the pipe for communcating between Host and WASM - pub cancel_io: Option, // the UnixStream side for communcating between Host and WASM + /// the pipe for communcating between Host and WASM + pub caller_io: Option, + /// the UnixStream side for communcating between Host and WASM + pub cancel_io: Option, - pub core: H2O, // core WASM runtime (engine, linker, instance, store, module) + /// core WASM runtime (engine, linker, instance, store, module) + pub core: H2O, } -// impl WATERTransportTrait for WATERListener {} - impl WATERTransportTrait for WATERListener { fn get_caller_io(&mut self) -> &mut Option { &mut self.caller_io @@ -32,7 +36,7 @@ impl WATERTransportTrait for WATERListener { } impl WATERListenerTrait for WATERListener { - /// Connect to the target address with running the WASM connect function + /// Creates a listener for the WATM module, and stores the fds in the core's version info fn listen(&mut self, _conf: &WATERConfig) -> Result<(), anyhow::Error> { info!("[HOST] WATERListener v0 create listener..."); @@ -55,6 +59,7 @@ impl WATERListenerTrait for WATERListener { Ok(()) } + /// Accept a connection from the listener with running the WATM's accept function and binding the caller_io with Host. fn accept(&mut self, _conf: &WATERConfig) -> Result<(), anyhow::Error> { info!("[HOST] WATERListener v0 accepting..."); @@ -130,6 +135,7 @@ impl WATERListener { Ok(runtime) } + /// Migrates the listener from one WATM instance to another, where every newly accept()'ed connection will be handled by a separate WATM instance. pub fn migrate_listener(_conf: &WATERConfig, core: &H2O) -> Result { info!("[HOST] WATERListener v0 migrating listener..."); diff --git a/crates/water/src/runtime/v0/mod.rs b/crates/water/src/runtime/v0/mod.rs index 85e0d9e..1dce48f 100644 --- a/crates/water/src/runtime/v0/mod.rs +++ b/crates/water/src/runtime/v0/mod.rs @@ -1,3 +1,5 @@ +//! V0 specific implementation, including config, export functions, stream, listener, and relay. + pub mod config; pub mod funcs; pub mod listener; diff --git a/crates/water/src/runtime/v0/relay.rs b/crates/water/src/runtime/v0/relay.rs index cb437a6..520e81d 100644 --- a/crates/water/src/runtime/v0/relay.rs +++ b/crates/water/src/runtime/v0/relay.rs @@ -1,10 +1,16 @@ +//! This file contains the v0_plus WATERRelay implementation, +//! it implements the WATERRelayTrait and WATERTransportTrait. + use crate::runtime::{relay::WATERRelayTrait, transport::WATERTransportTrait, *}; pub struct WATERRelay { - pub caller_io: Option, // the pipe for communcating between Host and WASM - pub cancel_io: Option, // the UnixStream side for communcating between Host and WASM + /// the pipe for communcating between Host and WASM + pub caller_io: Option, + /// the UnixStream side for communcating between Host and WASM + pub cancel_io: Option, - pub core: H2O, // core WASM runtime (engine, linker, instance, store, module) + /// core WASM runtime (engine, linker, instance, store, module) + pub core: H2O, } impl WATERTransportTrait for WATERRelay { @@ -30,7 +36,7 @@ impl WATERTransportTrait for WATERRelay { } impl WATERRelayTrait for WATERRelay { - /// Connect to the target address with running the WASM connect function + /// Associate to the target address with running the WASM associate function fn associate(&mut self, _conf: &WATERConfig) -> Result<(), anyhow::Error> { info!("[HOST] WATERRelay v0 associating..."); @@ -72,8 +78,9 @@ impl WATERRelayTrait for WATERRelay { Ok(()) } - fn relay(&mut self, _conf: &WATERConfig) -> Result<(), anyhow::Error> { - info!("[HOST] WATERRelay v0 relaying..."); + /// Creates a listener for the WATM module, and stores the fds in the core's version info + fn listen(&mut self, _conf: &WATERConfig) -> Result<(), anyhow::Error> { + info!("[HOST] WATERRelay v0 create listener..."); // create listener if let Version::V0(v0_conf) = &mut self.core.version { @@ -109,6 +116,7 @@ impl WATERRelay { Ok(runtime) } + /// Migrates the listener in Relay from one WATM instance to another, where every newly accept()'ed connection will be handled by a separate WATM instance. pub fn migrate_listener(_conf: &WATERConfig, core: &H2O) -> Result { info!("[HOST] WATERelay v0 migrating listener..."); diff --git a/crates/water/src/runtime/v0/stream.rs b/crates/water/src/runtime/v0/stream.rs index 6cde53b..66d675e 100644 --- a/crates/water/src/runtime/v0/stream.rs +++ b/crates/water/src/runtime/v0/stream.rs @@ -1,26 +1,30 @@ +//! This file contains the v0_plus WATERStream implementation, +//! it implements the WATERStreamTrait and WATERTransportTrait. + use crate::runtime::{stream::WATERStreamTrait, transport::WATERTransportTrait, *}; -// use crate::runtime::{stream::WATERStreamTrait, *, v0::transport::WATERTransportTraitV0, transport::WATERTransportTrait}; /// This file contains the WATERStream implementation /// which is a TcpStream liked definition with utilizing WASM - -// UnixSocket Connection created with Host -// Write => u2w +----------------+ w2n -// ----->| WATERStream |------> -// Caller | WASM Runtime | n2w Destination -// <-----| Decode/Encode |<------ -// Read => w2u +----------------+ -// WATERStream +/// ```ignore +/// UnixSocket Connection created with Host +/// Write => u2w +----------------+ w2n +/// -----> | WATERStream | ------> +/// Caller | WASM Runtime | n2w Destination +/// <----- | Decode/Encode | <------ +/// Read => w2u +----------------+ +/// WATERStream +/// ``` pub struct WATERStream { - pub caller_io: Option, // the pipe for communcating between Host and WASM - pub cancel_io: Option, // the UnixStream side for communcating between Host and WASM + /// the pipe for communcating between Host and WASM + pub caller_io: Option, + /// the UnixStream side for communcating between Host and WASM + pub cancel_io: Option, - pub core: H2O, // core WASM runtime (engine, linker, instance, store, module) + /// core WASM runtime (engine, linker, instance, store, module) + pub core: H2O, } -// impl WATERTransportTrait for WATERStream {} - impl WATERTransportTrait for WATERStream { fn get_caller_io(&mut self) -> &mut Option { &mut self.caller_io diff --git a/crates/water/src/runtime/v1/funcs.rs b/crates/water/src/runtime/v1/funcs.rs index be4a7e9..cc2ccaa 100644 --- a/crates/water/src/runtime/v1/funcs.rs +++ b/crates/water/src/runtime/v1/funcs.rs @@ -1,3 +1,5 @@ +//! Exported functions implementation for v1_preview WATM module from the Host + use anyhow::Ok; use crate::config::wasm_shared_config::StreamConfig; @@ -5,6 +7,8 @@ use crate::runtime::*; use std::convert::TryInto; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +/// This function is exporting the `connect_tcp(ptr: u32, size:u32) -> i32` +/// to the WATM where it is used to create a tcp connection and returns the fd of the connection used by Dialer & Relay. pub fn export_tcp_connect(linker: &mut Linker) -> Result<(), anyhow::Error> { linker .func_wrap( @@ -74,6 +78,8 @@ pub fn export_tcp_connect(linker: &mut Linker) -> Result<(), anyhow::Error Ok(()) } +/// This function is exporting the `create_listen(ptr: u32, size: u32) -> i32` +/// to the WATM where it is used to create a tcp listener and returns the fd of the listener used by Listener & Relay. pub fn export_tcplistener_create(linker: &mut Linker) -> Result<(), anyhow::Error> { linker .func_wrap( diff --git a/crates/water/src/runtime/v1/listener.rs b/crates/water/src/runtime/v1/listener.rs index 8992782..08a2066 100644 --- a/crates/water/src/runtime/v1/listener.rs +++ b/crates/water/src/runtime/v1/listener.rs @@ -1,20 +1,24 @@ +//! This file contains the v1_preview WATERListener implementation, +//! it implements the WATERListenerTrait and WATERTransportTrait. + use crate::runtime::{listener::WATERListenerTrait, transport::WATERTransportTrait, *}; pub struct WATERListener { - // WASM functions for reading & writing - - // the reader in WASM (read from net -- n2w) - // returns the number of bytes read + /// the reader in WASM (read from net -- n2w) + /// returns the number of bytes read pub reader: Func, - // the writer in WASM (write to net -- w2n) - // returns the number of bytes written + /// the writer in WASM (write to net -- w2n) + /// returns the number of bytes written pub writer: Func, - pub caller_reader: UnixStream, // the reader in Caller (read from WASM -- w2u) - pub caller_writer: UnixStream, // the writer in Caller (write to WASM -- u2w) + /// the reader in Caller (read from WASM -- w2u) + pub caller_reader: UnixStream, + /// the writer in Caller (write to WASM -- u2w) + pub caller_writer: UnixStream, - pub core: H2O, // core WASM runtime (engine, linker, instance, store, module) + /// core WASM runtime (engine, linker, instance, store, module) + pub core: H2O, } impl WATERTransportTrait for WATERListener { @@ -119,8 +123,6 @@ impl WATERTransportTrait for WATERListener { } } -// impl WATERTransportTraitV1 for WATERListener {} - impl WATERListenerTrait for WATERListener { /// Listening at the addr:port with running the WASM listen function fn accept(&mut self, conf: &WATERConfig) -> Result<(), anyhow::Error> { @@ -155,6 +157,7 @@ impl WATERListenerTrait for WATERListener { } impl WATERListener { + /// The constructor of WATERListener will create 2 pairs of UnixStream for communicating between WATM and Host pub fn init(_conf: &WATERConfig, core: H2O) -> Result { info!("[HOST] WATERListener v1_preview init..."); diff --git a/crates/water/src/runtime/v1/mod.rs b/crates/water/src/runtime/v1/mod.rs index 4f82022..39eec2c 100644 --- a/crates/water/src/runtime/v1/mod.rs +++ b/crates/water/src/runtime/v1/mod.rs @@ -1,3 +1,5 @@ +//! v1_preview specific implementation, including export functions, stream, listener. + pub mod funcs; pub mod listener; pub mod stream; diff --git a/crates/water/src/runtime/v1/stream.rs b/crates/water/src/runtime/v1/stream.rs index 251eaf5..7f49a27 100644 --- a/crates/water/src/runtime/v1/stream.rs +++ b/crates/water/src/runtime/v1/stream.rs @@ -1,34 +1,36 @@ +//! This file contains the v1_preview WATERStream implementation, +//! it implements the WATERStreamTrait and WATERTransportTrait. + use crate::runtime::{stream::WATERStreamTrait, transport::WATERTransportTrait, *}; /// This file contains the WATERStream implementation /// which is a TcpStream liked definition with utilizing WASM - -// UnixSocket Connection created with Host -// Write => u2w +----------------+ w2n -// ----->| WATERStream |------> -// Caller | WASM Runtime | n2w Destination -// <-----| Decode/Encode |<------ -// Read => w2u +----------------+ -// WATERStream +/// ```ignore +/// UnixSocket Connection created with Host +/// Write => u2w +----------------+ w2n +/// -----> | WATERStream | ------> +/// Caller | WASM Runtime | n2w Destination +/// <----- | Decode/Encode | <------ +/// Read => w2u +----------------+ +/// WATERStream +/// ``` pub struct WATERStream { - // WASM functions for reading & writing - - // the reader in WASM (read from net -- n2w) - // returns the number of bytes read + /// the reader in WASM (read from net -- n2w), returns the number of bytes read pub reader: Func, - // the writer in WASM (write to net -- w2n) - // returns the number of bytes written + /// the writer in WASM (write to net -- w2n), returns the number of bytes written pub writer: Func, - pub caller_io: UnixStream, // the pipe for communcating between Host and WASM + /// the pipe for communcating between Host and WASM + pub caller_io: UnixStream, - pub core: H2O, // core WASM runtime (engine, linker, instance, store, module) + /// core WASM runtime (engine, linker, instance, store, module) + pub core: H2O, } impl WATERTransportTrait for WATERStream { - /// Read from the target address + /// Read from the target address thru the WATM module fn read(&mut self, buf: &mut Vec) -> Result { debug!("[HOST] WATERStream v1_preview reading..."); @@ -75,7 +77,7 @@ impl WATERTransportTrait for WATERStream { Ok(nums) } - /// Write to the target address + /// Write to the target address thru the WATM module fn write(&mut self, buf: &[u8]) -> Result<(), anyhow::Error> { debug!("[HOST] WATERStream v1_preview writing..."); @@ -130,7 +132,7 @@ impl WATERTransportTrait for WATERStream { } impl WATERStreamTrait for WATERStream { - /// Connect to the target address with running the WASM connect function + /// Connect to the target address with running the WATM connect function fn connect(&mut self, _conf: &WATERConfig) -> Result<(), anyhow::Error> { info!("[HOST] WATERStream v1_preview connecting..."); diff --git a/crates/water/src/runtime/version.rs b/crates/water/src/runtime/version.rs index 20d0f71..05eecb9 100644 --- a/crates/water/src/runtime/version.rs +++ b/crates/water/src/runtime/version.rs @@ -1,3 +1,5 @@ +//! Version specific logic implementation for WATER, mainly for v0 configuration for now. + use std::fmt; use std::str::FromStr; use std::sync::Mutex; @@ -21,7 +23,7 @@ impl Version { } } - // Current API v0 needs some configurations at the beginning + /// Current API v0 needs some configurations at the beginning pub fn config_v0(&mut self, conf: &WATERConfig) -> Result { info!("[HOST] WATERCore configuring for V0"); @@ -95,6 +97,7 @@ impl FromStr for Version { impl From<&Version> for &'static str { fn from(v: &Version) -> &'static str { match v { + // currently we assign the version as Unknown(dummy var) when WATER is setting up Version::Unknown => "_water_setting_up", Version::V0(_v0_conf) => "_water_v0", Version::V1 => "_water_v1", diff --git a/crates/water/src/runtime/version_common/funcs.rs b/crates/water/src/runtime/version_common/funcs.rs index bf17bc3..d670edd 100644 --- a/crates/water/src/runtime/version_common/funcs.rs +++ b/crates/water/src/runtime/version_common/funcs.rs @@ -1,6 +1,9 @@ +//! This file contains the config related function that will be the same across all versions of WATM + use crate::runtime::*; -// exportint a function for WASM to get CONFIG file +/// exportint a function `pull_config() -> i32` that will be used +/// for WATM to get the config file from the host pub fn export_config(linker: &mut Linker, config_file: String) -> Result<(), anyhow::Error> { linker .func_wrap( diff --git a/crates/water/src/runtime/version_common/mod.rs b/crates/water/src/runtime/version_common/mod.rs index 5ef91ae..8da24f5 100644 --- a/crates/water/src/runtime/version_common/mod.rs +++ b/crates/water/src/runtime/version_common/mod.rs @@ -1 +1,3 @@ +//! The exported function from Host that is suitable for all versions of WATM + pub mod funcs; diff --git a/crates/water/src/utils/mod.rs b/crates/water/src/utils/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/crates/water/src/utils/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/clients/cli/README.md b/examples/clients/cli/README.md index e023753..8c6d89c 100644 --- a/examples/clients/cli/README.md +++ b/examples/clients/cli/README.md @@ -5,11 +5,38 @@ ## How to run? To run the Host program + WASM: ```shell -cargo run --bin wasmable_transport -- --wasm-path <./proxy.wasm> --entry-fn
--config-wasm +cargo run --bin water_cli -- --wasm-path <./proxy.wasm> --entry-fn
--config-wasm --type-client <3> ``` Then you can netcat into the connection, for now, I included a `proxy.wasm` as a multiple conneciton echo server, test with several terminals: ```shell nc 127.0.0.1 9005 ``` -you should see `> CONNECTED` in the terminal of running WASM, then you can connect a bunch like this and input anything to see how it echos. \ No newline at end of file +you should see `> CONNECTED` in the terminal of running WASM, then you can connect a bunch like this and input anything to see how it echos. + +## Examples +To run the shadowsocks wasm: + +1. run the server side from the [official implementation](https://github.com/shadowsocks/shadowsocks-rust) with the following config: + ```json + { + "server": "127.0.0.1", + "server_port": 8388, + "password": "Test!23", + "method": "chacha20-ietf-poly1305" + } + ``` + and run the server side with: + ```shell + cargo run --bin ssserver -- -c .json + ``` + +2. then run the cli tool with the `ss_client_wasm` + ```shell + cargo run --bin water_cli -- --wasm-path demo_wasm/ss_client_wasm.wasm --entry-fn v1_listen --config-wasm demo_configs/ss_config.json --type-client 3 + ``` + +3. to test the traffic is going through + ```shell + curl -4 -v --socks5 localhost:8080 https://erikchi.com + ``` \ No newline at end of file diff --git a/examples/clients/cli/demo_configs/ss_config.json b/examples/clients/cli/demo_configs/ss_config.json new file mode 100644 index 0000000..f9b7db7 --- /dev/null +++ b/examples/clients/cli/demo_configs/ss_config.json @@ -0,0 +1,7 @@ +{ + "remote_address": "127.0.0.1", + "remote_port": 8388, + "local_address": "127.0.0.1", + "local_port": 8080, + "bypass": false +} \ No newline at end of file diff --git a/examples/clients/cli/demo_wasm/echo_client.wasm b/examples/clients/cli/demo_wasm/echo_client.wasm new file mode 100644 index 0000000..ca67e57 Binary files /dev/null and b/examples/clients/cli/demo_wasm/echo_client.wasm differ diff --git a/examples/clients/cli/demo_wasm/plain.wasm b/examples/clients/cli/demo_wasm/plain.wasm new file mode 100644 index 0000000..77e1350 Binary files /dev/null and b/examples/clients/cli/demo_wasm/plain.wasm differ diff --git a/examples/clients/cli/demo_wasm/proxy.wasm b/examples/clients/cli/demo_wasm/proxy.wasm new file mode 100644 index 0000000..c3c13d4 Binary files /dev/null and b/examples/clients/cli/demo_wasm/proxy.wasm differ diff --git a/examples/clients/cli/demo_wasm/ss_client_wasm.wasm b/examples/clients/cli/demo_wasm/ss_client_wasm.wasm new file mode 100644 index 0000000..11e5042 Binary files /dev/null and b/examples/clients/cli/demo_wasm/ss_client_wasm.wasm differ diff --git a/examples/clients/cli/src/cli.rs b/examples/clients/cli/src/cli.rs index 6e3c6a2..4f49088 100644 --- a/examples/clients/cli/src/cli.rs +++ b/examples/clients/cli/src/cli.rs @@ -1,15 +1,12 @@ -use water::config::WATERConfig; +use water::config::{WATERConfig, WaterBinType}; use water::globals::{CONFIG_WASM_PATH, MAIN, WASM_PATH}; +use water::runtime; use clap::Parser; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { - /// optional address on which to listen - #[arg(short, long, default_value_t = String::from("127.0.0.1:9001"))] - listen: String, - /// Optional argument specifying the .wasm file to load #[arg(short, long, default_value_t = String::from(WASM_PATH))] wasm_path: String, @@ -23,11 +20,11 @@ struct Args { config_wasm: String, /// Optional argument specifying the client_type, default to be Runner - #[arg(short, long, default_value_t = 2)] + #[arg(short, long, default_value_t = 3)] type_client: u32, /// Optional argument enabling debug logging - #[arg(short, long, default_value_t = false)] + #[arg(short, long, default_value_t = true)] debug: bool, } @@ -37,7 +34,7 @@ impl From for WATERConfig { filepath: args.wasm_path, entry_fn: args.entry_fn, config_wasm: args.config_wasm, - client_type: args.type_client.into(), + client_type: WaterBinType::from(args.type_client), debug: args.debug, } } @@ -55,22 +52,20 @@ pub fn parse_and_execute() -> Result<(), anyhow::Error> { } pub fn execute(_conf: WATERConfig) -> Result<(), anyhow::Error> { - // let mut water_client = runtime::WATERClient::new(conf)?; - - // water_client.connect("", 0)?; - - // loop { - // // keep reading from stdin and call read and write function from water_client.stream - // let mut buf = String::new(); - // std::io::stdin().read_line(&mut buf)?; - - // water_client.write(buf.as_bytes())?; + let mut water_client = runtime::client::WATERClient::new(_conf).unwrap(); - // let mut buf = vec![0; 1024]; - // water_client.read(&mut buf)?; - - // println!("read: {:?}", String::from_utf8_lossy(&buf)); - // } + match water_client.config.client_type { + WaterBinType::Dial => { + water_client.connect().unwrap(); + } + WaterBinType::Runner => { + water_client.execute().unwrap(); + } + WaterBinType::Listen => {} + WaterBinType::Relay => {} + WaterBinType::Wrap => {} + WaterBinType::Unknown => {} + } Ok(()) } diff --git a/examples/water_bins/README.md b/examples/water_bins/README.md index f3c124d..28c26ac 100644 --- a/examples/water_bins/README.md +++ b/examples/water_bins/README.md @@ -2,4 +2,17 @@ This folder is for all the WATM examples that developed using the [WATM library crate](https://github.com/erikziyunchi/water-rs/tree/main/crates/wasm), and runnable with the [WATER library engine](https://github.com/erikziyunchi/water-rs/tree/main/crates/water). -One can find details of these in each examples' README. \ No newline at end of file +One can find details of these in each examples' README. + +--- + +These WATM examples can be compiled to WASM and optimized with the script I've provided in `./scripts/make_and_opt_wasm.sh` as following: + +For example, if you want to make the `ss_client_wasm`, you can run this command in the root directory of this repo: +```shell +sh ./script/make_and_opt_wasm.sh ss_client_wasm_v1 ss_client_wasm +``` +which is: +```shell +sh ./script/make_and_opt_wasm.sh ./examples/water_bins/ +``` \ No newline at end of file diff --git a/examples/water_bins/ss_client_wasm_v1/ss_client_wasm.wasm b/examples/water_bins/ss_client_wasm_v1/ss_client_wasm.wasm index 17b0e47..54adf30 100644 Binary files a/examples/water_bins/ss_client_wasm_v1/ss_client_wasm.wasm and b/examples/water_bins/ss_client_wasm_v1/ss_client_wasm.wasm differ diff --git a/scripts/make_and_opt_wasm.sh b/scripts/make_and_opt_wasm.sh index cd3d8d2..5580ed3 100644 --- a/scripts/make_and_opt_wasm.sh +++ b/scripts/make_and_opt_wasm.sh @@ -7,7 +7,7 @@ if [ $# -ne 2 ]; then fi # Change directory to the source directory -cd "examples/water_bins/$1" || exit 1 +cd "./examples/water_bins/$1" || exit 1 # Build the project using cargo cargo build --target wasm32-wasi || exit 1 diff --git a/tests/tests/cross_lang_tests.rs b/tests/tests/cross_lang_tests.rs index ac06172..7c32eb4 100644 --- a/tests/tests/cross_lang_tests.rs +++ b/tests/tests/cross_lang_tests.rs @@ -1,3 +1,7 @@ +//! This is the test file for testing the plain.wasm which is a v0_plus WATM module that has been tested with the Go engine. +//! +//! Tests here are showing that the same WATM module can be used interchangeably in both the Rust and Go engine. + #![allow(dead_code)] use water::*; @@ -13,6 +17,7 @@ use std::{ use tempfile::tempdir; +/// Testing the Dialer mode #[test] fn test_cross_lang_wasm_dialer() -> Result<(), Box> { tracing_subscriber::fmt().with_max_level(Level::INFO).init(); @@ -96,6 +101,7 @@ fn test_cross_lang_wasm_dialer() -> Result<(), Box> { Ok(()) } +/// Testing the Listener mode #[test] fn test_cross_lang_wasm_listener() -> Result<(), Box> { let cfg_str = r#" diff --git a/tests/tests/echo_tests.rs b/tests/tests/echo_tests.rs index f3abf77..141d857 100644 --- a/tests/tests/echo_tests.rs +++ b/tests/tests/echo_tests.rs @@ -1,3 +1,6 @@ +//! This is the test file for testing the echo_client.wasm which is a plain v1_preview WATM module, +//! program procedures can also be treat as examples of using the WATER client. + use water::*; use std::{ diff --git a/tests/tests/spinning_relay.rs b/tests/tests/spinning_relay.rs index 366218c..f8084f6 100644 --- a/tests/tests/spinning_relay.rs +++ b/tests/tests/spinning_relay.rs @@ -1,3 +1,6 @@ +//! This is the test file for testing the plain.wasm which is a v0_plus WATM module that has been tested with the Go engine. +//! But with the Relay mode + #![allow(dead_code)] use water::*; @@ -12,6 +15,7 @@ use std::{ use tempfile::tempdir; +/// Testing the Relay mode #[test] fn test_cross_lang_wasm_relay() -> Result<(), Box> { tracing_subscriber::fmt().with_max_level(Level::INFO).init(); @@ -67,7 +71,7 @@ fn test_cross_lang_wasm_relay() -> Result<(), Box> { let mut water_client = runtime::client::WATERClient::new(conf).unwrap(); - water_client.relay().unwrap(); + water_client.listen().unwrap(); // connects to the relay, and the relay will connect to the listener let handle_local = std::thread::spawn(|| { @@ -153,7 +157,7 @@ fn spin_cross_lang_wasm_relay() -> Result<(), Box> { let mut water_client = runtime::client::WATERClient::new(conf).unwrap(); - water_client.relay().unwrap(); + water_client.listen().unwrap(); water_client.associate().unwrap(); water_client.cancel_with().unwrap(); diff --git a/tests/tests/ss_testing.rs b/tests/tests/ss_testing.rs index d70993c..a8b14a5 100644 --- a/tests/tests/ss_testing.rs +++ b/tests/tests/ss_testing.rs @@ -1,14 +1,11 @@ -#![allow(dead_code)] - -use water::*; - -// use rand; -// use pprof::protos::Message; -// use tracing::info; +//! This is the test file for testing the ss_client_wasm.wasm which is a v1_preview ShadowSocks WATM module, +//! program procedures here can also be treat as examples of using the WATER client, with the ShadowSocks protocol WATM. -use tracing::Level; +#![allow(dead_code)] use tempfile::tempdir; +use tracing::Level; +use water::*; use std::thread; use std::{ @@ -116,6 +113,7 @@ impl Socks5TestServer { // } // "#; +/// A test for a normal Shadowsocks client #[tokio::test] async fn wasm_managed_shadowsocks_async() -> Result<(), Box> { tracing_subscriber::fmt().with_max_level(Level::INFO).init(); @@ -194,6 +192,7 @@ async fn wasm_managed_shadowsocks_async() -> Result<(), Box Result<(), Box> { let cfg_str = r#"