Skip to content

Commit

Permalink
Add support for rustls websocket connection as feature `ws-rustls-tok…
Browse files Browse the repository at this point in the history
…io` (#687)

* Add support for rustls websocket connection

* Update README.md

Co-authored-by: Tomasz Drwięga <[email protected]>

* Clarify rustls features

* Update tokio-rustls, webpki-roots

* Fix compile & deprecated usage

Require min rustls 0.21.7

* Update rustls to 0.22

* cargo fmt

* Remove rustls explicit dependency

As we only use tokio-rustls

* Improve test get_available_port()

---------

Co-authored-by: Tomasz Drwięga <[email protected]>
  • Loading branch information
alexheretic and tomusdrw authored Dec 19, 2023
1 parent dd85e08 commit 6fcad83
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 19 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ jobs:
command: check
toolchain: stable
args: --no-default-features --features ws-tls-tokio
- name: Checking ws-rustls-tokio
uses: actions-rs/cargo@master
with:
command: check
toolchain: stable
args: --no-default-features --features ws-rustls-tokio
- name: Checking ws-async-std
uses: actions-rs/cargo@master
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ target
Cargo.lock
*.swp
.idea/
.vscode
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ headers = { version = "0.4", optional = true }
# async-native-tls = { git = "https://github.com/async-email/async-native-tls.git", rev = "b5b5562d6cea77f913d4cbe448058c031833bf17", optional = true, default-features = false }
# Temporarily use forked version released to crates.io
async-native-tls = { package = "web3-async-native-tls", version = "0.4", optional = true, default-features = false }
tokio-rustls = { version = "0.25", optional = true }
rustls-pki-types = { version = "1", optional = true }
webpki-roots = { version = "0.26", optional = true }
async-std = { version = "1.6", optional = true }
tokio = { version = "1.0", optional = true, features = ["full"] }
tokio-stream = { version = "0.1", optional = true }
Expand Down Expand Up @@ -81,6 +84,7 @@ signing = ["secp256k1", "once_cell"]
ws-tokio = ["soketto", "url", "tokio", "tokio-util", "headers"]
ws-async-std = ["soketto", "url", "async-std", "headers"]
ws-tls-tokio = ["async-native-tls", "async-native-tls/runtime-tokio", "ws-tokio"]
ws-rustls-tokio = ["tokio-rustls", "webpki-roots", "rustls-pki-types", "ws-tokio"]
ws-tls-async-std = ["async-native-tls", "async-native-tls/runtime-async-std", "ws-async-std"]
ipc-tokio = ["tokio", "tokio-stream", "tokio-util"]
arbitrary_precision = ["serde_json/arbitrary_precision", "jsonrpc-core/arbitrary_precision"]
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,20 @@ web3.api::<CustomNamespace>().custom_method().wait().unwrap()

Currently, Windows does not support IPC, which is enabled in the library by default.
To compile, you need to disable the IPC feature:
```
web3 = { version = "0.19.0", default-features = false, features = ["http"] }
```toml
web3 = { version = "_", default-features = false, features = ["http"] }
```

# Avoiding OpenSSL dependency

On Linux, `native-tls` is implemented using OpenSSL. To avoid that dependency
for HTTPS use the corresponding feature.
```
web3 = { version = "0.19.0", default-features = false, features = ["http-rustls-tls"] }
for HTTPS or WSS use the corresponding features.
```toml
web3 = { version = "_", default-features = false, features = ["http-rustls-tls", "ws-rustls-tokio"] }
```

_Note: To fully replicate the default features also add `signing` & `ipc-tokio` features_.

# Cargo Features

The library supports following features:
Expand All @@ -129,6 +131,7 @@ The library supports following features:
- `http-rustls-tls` - Enables TLS support via `reqwest/rustls-tls` for HTTP transport (implies `http`).
- `ws-tokio` - Enables WS transport using `tokio` runtime.
- `ws-tls-tokio` - Enables TLS support for WS transport (implies `ws-tokio`; default).
- `ws-rustls-tokio` - Enables rustls TLS support for WS transport (implies `ws-tokio`).
- `ws-async-std` - Enables WS transport using `async-std` runtime.
- `ws-tls-async-std` - Enables TLS support for WS transport (implies `ws-async-std`).
- `ipc-tokio` - Enables IPC transport using `tokio` runtime (default).
Expand Down
9 changes: 1 addition & 8 deletions src/transports/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,8 @@ mod tests {
use jsonrpc_core::ErrorCode;
use std::net::TcpListener;

fn port_is_available(port: u16) -> bool {
match TcpListener::bind(("127.0.0.1", port)) {
Ok(_) => true,
Err(_) => false,
}
}

fn get_available_port() -> Option<u16> {
(3001..65535).find(|port| port_is_available(*port))
Some(TcpListener::bind(("127.0.0.1", 0)).ok()?.local_addr().ok()?.port())
}

async fn server(req: hyper::Request<hyper::Body>) -> hyper::Result<hyper::Response<hyper::Body>> {
Expand Down
4 changes: 2 additions & 2 deletions src/transports/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ mod test {
})
);

tx.write(r#"{"jsonrpc": "2.0", "id": 1, "result": {"test": 1}}"#.as_ref())
tx.write_all(r#"{"jsonrpc": "2.0", "id": 1, "result": {"test": 1}}"#.as_ref())
.await
.unwrap();
tx.flush().await.unwrap();
Expand All @@ -417,7 +417,7 @@ mod test {

let response_bytes = r#"{"jsonrpc": "2.0", "id": 2, "result": {"test": "string1"}}"#;
for chunk in response_bytes.as_bytes().chunks(3) {
tx.write(chunk).await.unwrap();
tx.write_all(chunk).await.unwrap();
tx.flush().await.unwrap();
}
}
Expand Down
46 changes: 42 additions & 4 deletions src/transports/ws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,17 @@ impl WsServerTask {
let stream = async_native_tls::connect(host, stream).await?;
MaybeTlsStream::Tls(compat::compat(stream))
}
#[cfg(not(any(feature = "ws-tls-tokio", feature = "ws-tls-async-std")))]
panic!("The library was compiled without TLS support. Enable ws-tls-tokio or ws-tls-async-std feature.");
#[cfg(all(
feature = "ws-rustls-tokio",
not(feature = "ws-tls-tokio"),
not(feature = "ws-tls-async-std")
))]
{
let stream = tokio_rustls_connect(host, stream).await?;
MaybeTlsStream::Tls(compat::compat(stream))
}
#[cfg(not(any(feature = "ws-tls-tokio", feature = "ws-tls-async-std", feature = "ws-rustls-tokio")))]
panic!("The library was compiled without TLS support. Enable ws-tls-tokio, ws-rustls-tokio or ws-tls-async-std feature.");
} else {
let stream = compat::compat(stream);
MaybeTlsStream::Plain(stream)
Expand Down Expand Up @@ -245,6 +254,32 @@ impl WsServerTask {
}
}

#[cfg(feature = "ws-rustls-tokio")]
async fn tokio_rustls_connect(
host: &str,
stream: tokio::net::TcpStream,
) -> error::Result<tokio_rustls::client::TlsStream<tokio::net::TcpStream>> {
use rustls_pki_types::ServerName;
use std::convert::TryFrom;
use tokio_rustls::rustls::{ClientConfig, RootCertStore};

let client_conf = ClientConfig::builder()
.with_root_certificates({
let mut root_cert_store = RootCertStore::empty();
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
root_cert_store
})
.with_no_client_auth();

let dnsname = ServerName::try_from(host)
.map_err(|err| error::Error::Transport(TransportError::Message(format!("Invalid host: {err}"))))?
.to_owned();

Ok(tokio_rustls::TlsConnector::from(Arc::new(client_conf))
.connect(dnsname, stream)
.await?)
}

fn as_data_stream<T: Unpin + futures::AsyncRead + futures::AsyncWrite>(
receiver: soketto::connection::Receiver<T>,
) -> impl Stream<Item = Result<Vec<u8>, soketto::connection::Error>> {
Expand Down Expand Up @@ -512,13 +547,16 @@ pub mod compat {
/// TLS stream type for tokio runtime.
#[cfg(feature = "ws-tls-tokio")]
pub type TlsStream = Compat<async_native_tls::TlsStream<tokio::net::TcpStream>>;
/// Rustls TLS stream type for tokio runtime.
#[cfg(all(feature = "ws-rustls-tokio", not(feature = "ws-tls-tokio")))]
pub type TlsStream = Compat<tokio_rustls::client::TlsStream<tokio::net::TcpStream>>;
/// Dummy TLS stream type.
#[cfg(not(feature = "ws-tls-tokio"))]
#[cfg(all(not(feature = "ws-tls-tokio"), not(feature = "ws-rustls-tokio")))]
pub type TlsStream = TcpStream;

/// Create new TcpStream object.
pub async fn raw_tcp_stream(addrs: String) -> io::Result<tokio::net::TcpStream> {
Ok(tokio::net::TcpStream::connect(addrs).await?)
tokio::net::TcpStream::connect(addrs).await
}

/// Wrap given argument into compatibility layer.
Expand Down

0 comments on commit 6fcad83

Please sign in to comment.