Open BrandonLeeDotDev opened 8 months ago
this is ~(the response) in Firefox
I see why the above failed. By calling accept I was attempting to accept the next incoming connection. Which is why I tried the following, non preferred, Fd path...
Even if I create a stream from an OwnedFd I get the following:
�2HTTP/1.1 301 Moved Permanently Location: https://localhost:4010/
What are the leading bytes? How do I prevent them from being sent? How do I access the underlying stream should TLS fail?
pub async fn accept(&self) -> io::Result<(TlsStream<TcpStream>, SocketAddr)> {
let listener = &self.3;
let (mut stream, peer_addr) = listener.accept().await?;
let owned_fd: OwnedFd = stream.as_fd().try_clone_to_owned().unwrap();
let std_stream = unsafe { std::net::TcpStream::from_raw_fd(owned_fd.into_raw_fd()) };
std_stream.set_nonblocking(true)?;
let tokio_stream = TcpStream::from_std(std_stream)?;
match self.2.accept(tokio_stream).await {
Err(_error) => {
let redirect =
b"HTTP/1.1 301 Moved Permanently\r\nLocation: https://localhost:4010/\r\n\r\n";
stream.write(redirect).await?;
stream.flush().await?;
stream.shutdown().await?;
Err(io::Error::new(io::ErrorKind::Other, "Redirected"))
},
Ok(tls_stream) => Ok((tls_stream, peer_addr))
}
}
So you want to achieve that plaintext (HTTP) clients connecting to your TLS server socket get a HTTP redirect? I don't think tokio-rustls will facilitate that use case, because it will ~always send a TLS alert when it fails to parse the client's stream as TLS.
If you drop down directly to the rustls API you can probably make it work, but I'm not sure you can easily wrap a tokio-rustls connection around that after the fact. I suppose we could maybe support this use case in the LazyConfigAcceptor
, optionally postponing the sending of the alert until the caller has okayed it?
Imo, this is just default behavior... that said this is my final solution
pub async fn accept<F, Fut>(&self, fun: F) -> io::Result<()>
where
F: FnOnce(TlsStream<TcpStream>, SocketAddr) -> Fut,
Fut: Future<Output = ()> + Send + 'static,
F: Send + 'static,
{
let acceptor = self.2.clone();
let (mut stream, peer_addr) = self.3.accept().await?;
tokio::spawn(async move {
let mut buf = [0; 1]; // Buffer to read the first byte
match timeout(Duration::from_secs(10), stream.peek(&mut buf)).await {
Ok(Ok(_)) => {}
_ => {
let _ = Self::redirect(&mut stream).await;
}
}
if buf[0] == 0x16 {
// 0x16 is the first byte of a TLS handshake
if let Ok(tls_stream) = acceptor.accept(stream).await {
fun(tls_stream, peer_addr).await;
}
} else {
let _ = Self::redirect(&mut stream).await;
}
});
Ok(())
}
@djc
I suppose we could maybe support this use case in the LazyConfigAcceptor, optionally postponing the sending of the alert until the caller has okayed it? - here
Yeah, should be supported somewhere... as arg or something... something like .force_tls(bool)
Can you elaborate on how the peeking trick should work? For me, in both cases (connecting via HTTP & HTTPS), the timer does not run in a timeout, as the peek was successful.
Using the following:
tokio::spawn(async move {
mut buf = [0u8; 1];
tcp_stream.peek(&mut buf).await.unwrap();
println!("peeked: {:?}", buf);
...
successfully peeks something with HTTP and HTTPS:
peeked: [22] <-- HTTPS
peeked: [71] <-- HTTP
@c92s I ended up doing this. The first byte is the type signature. 22 (peeked: [22] <-- HTTPS) or 0x16 == HTTPS. I also check the client hello byte. If not not both then 301. 'let _ = Self::redirect(&mut stream).await;'
pub async fn accept<F, Fut>(&self, handle_client: F) -> io::Result<()>
where
F: FnOnce(TlsStream<TcpStream>, SocketAddr) -> Fut,
Fut: Future<Output = ()> + Send + 'static,
F: Send + 'static,
{
let (mut stream, peer_addr) = self.1.accept().await?;
let acceptor = self.0.clone();
tokio::spawn(async move {
let mut buf = [0; 6];
match timeout(Duration::from_secs(60), stream.peek(&mut buf)).await {
Ok(Ok(_)) => {}
_ => {
let _ = Self::redirect(&mut stream).await;
}
}
// Check for the handshake message type (0x16) and the ClientHello byte (0x01)
if buf[0] == 0x16 && buf[5] == 0x01 {
crate::debug_with_time!({},"TLS connection from {peer_addr}",);
if let Ok(tls_stream) = acceptor.accept(stream).await {
tokio::spawn(handle_client(tls_stream, peer_addr));
}
} else {
let _ = Self::redirect(&mut stream).await;
}
});
Ok(())
}
@BrandonLeeDotDev thanks for the info!
FYI: we could further refine that, using the available rustls
enums (rustls::ContentType::Handshake
& rustls::HandshakeType::ClientHello
), e.g.:
if buf[0] == rustls::ContentType::Handshake.get_u8() && buf[5] == rustls::HandshakeType::ClientHello.get_u8() {
crate::debug_with_time!({},"TLS connection from {peer_addr}",);
if let Ok(tls_stream) = acceptor.accept(stream).await {
tokio::spawn(handle_client(tls_stream, peer_addr));
}
} else {
let _ = Self::redirect(&mut stream).await;
}
However, IMO this looks very "handcrafted", I am still not sure, if this is the proper way to handle such cases...
This is my current attempt among others. Both print statements print. I have had intermittent success... its just not stable. Whats the correct way to approach this within the lib itself?