-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide an example on how to use rustls::server::Acceptor
#97
Comments
You will want to start at https://docs.rs/tokio-rustls/latest/tokio_rustls/struct.LazyConfigAcceptor.html rather than |
Thank you very much! I have a lot more clarity now. I guess these basic examples should be included in the examples folder. It can save a lot of time for other developers figuring out these fairly common functionalities. I guess following examples needs to be added. I'll try to create some examples and submit through PR. I'll need some guidance though: Client Side:
Server Side:
|
I'm generally wary of adding a bunch of examples that are all slightly different (but will usually also share a lot of code), but I agree that it makes sense to add an example for the |
I just had one follow up question: How do you define a timeout on client-hello and overall TLS handshake? How to prevent attacks like Slowloris? Where to configure it? I don't see any configuration in server.rs example as well. |
We have #4 for the general |
I've been trying to achieve the same and was quite pleased when I discovered this issue calling out the Note I've generated the certs with mkcert:
use axum::{extract::connect_info::IntoMakeServiceWithConnectInfo, Router};
use hyper_util::{
rt::{TokioExecutor, TokioIo},
server::conn::auto::Builder,
service::TowerToHyperService,
};
use std::{io, net::SocketAddr, time::Duration};
use tokio::{
io::AsyncWriteExt,
net::{TcpListener, TcpStream},
};
use tokio_rustls::{rustls::server::Acceptor, LazyConfigAcceptor};
use tower_service::Service;
use tracing::{error, trace};
pub async fn serve_https(
tcp_listener: TcpListener,
mut make_service: IntoMakeServiceWithConnectInfo<Router, SocketAddr>,
) -> Result<(), anyhow::Error> {
loop {
let (tcp_stream, remote_addr) = match tcp_accept(&tcp_listener).await {
Some(conn) => conn,
None => continue,
};
let tower_service = make_service
.call(remote_addr)
.await
.unwrap_or_else(|err| match err {});
let hyper_service = TowerToHyperService::new(tower_service);
let acceptor = LazyConfigAcceptor::new(Acceptor::default(), tcp_stream);
tokio::spawn(async move {
tokio::pin!(acceptor);
let result: Result<(), anyhow::Error> = async {
let start = acceptor.as_mut().await?;
let client_hello = start.client_hello();
let config = get_server_config(client_hello).await;
let tcp_stream = start.into_stream(config).await?;
trace!("connection {remote_addr} accepted");
let tcp_stream = TokioIo::new(tcp_stream);
Builder::new(TokioExecutor::new())
.serve_connection_with_upgrades(tcp_stream, hyper_service)
.await
.or_else(|_err| Ok(()))
}
.await;
if let Err(err) = result {
error!("unable to process request: {err}");
if let Some(mut stream) = acceptor.take_io() {
stream
.write_all("HTTP/1.1 400 Bad Request\r\n\r\n\r\n".as_bytes())
.await
.unwrap_or_else(|err| {
error!("unable to send error response: {err}");
});
}
}
});
}
}
fn is_connection_error(e: &io::Error) -> bool {
matches!(
e.kind(),
io::ErrorKind::ConnectionRefused
| io::ErrorKind::ConnectionAborted
| io::ErrorKind::ConnectionReset
)
}
async fn tcp_accept(listener: &TcpListener) -> Option<(TcpStream, SocketAddr)> {
match listener.accept().await {
Ok(conn) => Some(conn),
Err(e) => {
if is_connection_error(&e) {
return None;
}
error!("accept error: {e}");
tokio::time::sleep(Duration::from_secs(1)).await;
None
}
}
}
pub async fn get_server_config(client_hello: ClientHello<'_>) -> Arc<ServerConfig> {
let mut config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
CertificateDer::pem_file_iter(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("localhost-cert.pem"),
)
.unwrap()
.map(|cert| cert.unwrap())
.collect(),
PrivateKeyDer::from_pem_file(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("localhost-key.pem"),
)
.unwrap(),
)
.unwrap();
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
Arc::new(config)
} |
You'll want both: the former covers receiving the I started on a fairly unopinionated server implementation that could maybe live in hyper-rustls, I should finish it up some time. |
I think it might go little off topic, but I want to implement this case: In a pub/sub broker it is inefficient to allocate a buffer for each connection as there can be potentially thousands of connections which remain idle most of the time. This basically consumes too much memory. So there can be thousands of clients, but at any instance only hundreds are sending data. I see that underlying tcpstream has readable method which basically wait until socket becomes readable. Once readable, I am thinking to pass on message through a channel to some worker task which will read the socket by fetching stream from hashmap and calling poll_read into a buffer and process the incoming data with it's own pre-allocated buffer. I am trying like this: match acceptor.as_mut().await {
Ok(start) => {
let client_hello = start.client_hello();
// Get the dynamic config here
let stream = start.into_stream(final_config).await.unwrap();
// Store the stream into a HashMap of struct along with other client related info
tokio::spawn(async move {
let client_ctx_data = client_ctx;
let (tcp_stream, conn_data) = stream.into_inner();
tcp_stream.readable().await?;
// Send message over a channel to worker task that this connection is trying to send something
}) My question is: Is this right way to do this? or tokio-rustls has some method to wait until stream is readable without copying anything in a buffer? |
In async Rust, tasks are generally only woken up by the runtime when there is something for them to do, so that doesn't seem like a process you should try to duplicate at your level? |
I agree but here goal is to delay the read buffer allocation until actually needed. Even though execution wise they are same, they wake up when there's data, there's a significant difference between implementing with When using In |
I think you should be able to use something like |
Hello,
I am new to both Rust and async in Rust with tokio so there might be something I might have missed or don't know. Rustls recommends here to use
rustls::server::Acceptor
for retrieving server certificates dynamically based onClientHello
when working in async manner. But I could not find any useful information on how to use that with Tokio-rustls.What I have observed is Acceptor implements
read_tls
method which accepts a parameter which implementsRead
trait. Buttokio::net::TcpStream
implementsAsyncRead
which is not compatible to be passed in Acceptor. Or may be I don't know how to use that.Any guidance or minimal example is appreciated on how to use Acceptor.
If there are any better ways to implement dynamic server certificates fetched from a key store, I would like to know that too.
PS: I need TCP socket stream directly as I am implementing a Pub/Sub Broker both for experimentation and learning Rust for real use case.
The text was updated successfully, but these errors were encountered: