Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ferron-modules-builtin = { path = "./ferron-modules-builtin", default-features =
ferron-observability-builtin = { path = "./ferron-observability-builtin", default-features = false }

[patch.crates-io]
rustls = { git = "https://github.com/selfhoster1312/rustls", branch = "more-public-types" }
monoio = { git = "https://github.com/DorianNiemiecSVRJS/monoio.git" } # This is needed for the web server to be able to accept more than one connection on Windows, and to be able to be compiled correctly on FreeBSD
cache_control = { git = "https://github.com/DorianNiemiecSVRJS/rust-cache-control.git" } # This is a fork of "cache-control" crate, with a bugfix related to "s-maxage" directive
desec_api = { git = "https://github.com/DorianNiemiecSVRJS/desec_api.git" } # This is a fork of "desec_api" crate, with updated dependencies and crypto provider requirement removed
Expand Down
85 changes: 85 additions & 0 deletions ferron/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use monoio_compat::hyper::{MonoioExecutor, MonoioIo, MonoioTimer};
use rustls::server::Acceptor;
use rustls::ServerConfig;
#[cfg(feature = "runtime-tokio")]
use tokio::io::AsyncWriteExt;
#[cfg(feature = "runtime-tokio")]
use tokio::net::TcpStream;
use tokio_rustls::server::TlsStream;
use tokio_rustls::LazyConfigAcceptor;
Expand Down Expand Up @@ -66,6 +68,7 @@ pub fn create_http_handler(
acme_tls_alpn_01_configs: HashMap<u16, Arc<ServerConfig>>,
acme_http_01_resolvers: Arc<tokio::sync::RwLock<Vec<crate::acme::Http01DataLock>>>,
enable_proxy_protocol: bool,
proxied_tls_domains: HashMap<String, String>,
) -> Result<CancellationToken, Box<dyn Error + Send + Sync>> {
let shutdown_tx = CancellationToken::new();
let shutdown_rx = shutdown_tx.clone();
Expand All @@ -85,6 +88,7 @@ pub fn create_http_handler(
acme_tls_alpn_01_configs,
acme_http_01_resolvers,
enable_proxy_protocol,
proxied_tls_domains,
)
.await
.err()
Expand Down Expand Up @@ -116,6 +120,7 @@ async fn http_handler_fn(
acme_tls_alpn_01_configs: HashMap<u16, Arc<ServerConfig>>,
acme_http_01_resolvers: Arc<tokio::sync::RwLock<Vec<crate::acme::Http01DataLock>>>,
enable_proxy_protocol: bool,
proxied_tls_domains: HashMap<String, String>,
) -> Result<(), Box<dyn Error + Send + Sync>> {
handler_init_tx.send(None).await.unwrap_or_default();

Expand Down Expand Up @@ -154,6 +159,7 @@ async fn http_handler_fn(
let acme_http_01_resolvers = acme_http_01_resolvers.clone();
let connections_references_cloned = connections_references.clone();
let shutdown_rx_clone = shutdown_rx.clone();
let proxied_tls_domains_clone = proxied_tls_domains.clone();
crate::runtime::spawn(async move {
match conn_data.connection {
crate::listener_handler_communication::Connection::Tcp(tcp_stream) => {
Expand Down Expand Up @@ -187,6 +193,7 @@ async fn http_handler_fn(
acme_http_01_resolvers,
enable_proxy_protocol,
shutdown_rx_clone,
proxied_tls_domains_clone,
)
.await;
}
Expand Down Expand Up @@ -247,6 +254,7 @@ async fn http_tcp_handler_fn(
acme_http_01_resolvers: Arc<tokio::sync::RwLock<Vec<crate::acme::Http01DataLock>>>,
enable_proxy_protocol: bool,
shutdown_rx: CancellationToken,
tls_proxied_domains: HashMap<String, String>,
) {
let _connection_reference = Arc::downgrade(&connection_reference);
#[cfg(feature = "runtime-monoio")]
Expand Down Expand Up @@ -293,6 +301,83 @@ async fn http_tcp_handler_fn(
(tcp_stream, None, None)
};

let (mut client, server) = tokio::io::duplex(4096);
let mut buf = [0; 4096];
match tokio::time::timeout(Duration::from_secs(60), tcp_stream.peek(&mut buf)).await {
Ok(Ok(len)) => client.write_all(&buf[0..len]).await.unwrap(),
_ => panic!(),
}

let sni: Option<String> = match LazyConfigAcceptor::new(Acceptor::default(), server).await {
Ok(start_handshake) => start_handshake.client_hello().server_name().map(|s| s.to_string()),
Err(_e) => None,
};

if let Some(tls_proxy_domain) = sni.and_then(|sni| tls_proxied_domains.get(&sni)) {
println!("Found matching TLS proxy: {}", tls_proxy_domain);
use std::str::FromStr;
let dest = SocketAddr::from_str(&tls_proxy_domain).unwrap();
let dest_stream = TcpStream::connect(dest).await.unwrap();

// First read all client data
loop {
println!("READ CLIENT");
// let mut buf = Vec::with_capacity(4096);
let mut buf = [0; 4096];
tcp_stream.readable().await.unwrap();
match tcp_stream.try_read(&mut buf) {
Ok(0) => {
println!("END READ CLIENT");
break;
}
Ok(n) => loop {
println!("WRITE BACKEND");
dest_stream.writable().await.unwrap();
match dest_stream.try_write(&mut buf[0..n]) {
Ok(_n) => break,
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
Err(_e) => panic!("This is a real writing error to the backend!"),
}
},
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
// Is there a way to know the client is done with the request?
// Or do we just end up here?
break;
}
Err(_e) => {
panic!("This is a real error reading from the client!");
}
}
}

// Now read the response
loop {
println!("READ BACKEND");
let mut buf = [0; 4096];
dest_stream.readable().await.unwrap();
match dest_stream.try_read(&mut buf) {
Ok(0) => break,
Ok(n) => loop {
println!("WRITE CLIENT");
tcp_stream.writable().await.unwrap();
match tcp_stream.try_write(&mut buf[0..n]) {
Ok(_n) => break,
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
Err(_e) => panic!("This is a real writing error to the client!"),
}
},
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
continue;
}
Err(_e) => {
panic!("This is a real error reading from the backend!");
}
}
}

return;
}

let maybe_tls_stream = if let Some(tls_config) = tls_config {
let start_handshake = match LazyConfigAcceptor::new(Acceptor::default(), tcp_stream).await {
Ok(start_handshake) => start_handshake,
Expand Down
13 changes: 13 additions & 0 deletions ferron/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ fn before_starting_server(
}
});

let mut proxied_tls_domains: HashMap<String, String> = HashMap::new();
let mut tls_ports: HashMap<u16, CustomSniResolver> = HashMap::new();
#[allow(clippy::type_complexity)]
let mut tls_port_locks: HashMap<u16, Arc<tokio::sync::RwLock<Vec<(String, Arc<dyn ResolvesServerCert>)>>>> =
Expand Down Expand Up @@ -454,6 +455,17 @@ fn before_starting_server(

let https_port = server_configuration.filters.port.or(default_https_port);

// Check for a `proxy_tls` directive in the server configuration block
if let Some(proxied_tls_domain) = get_value!("proxy_tls", server_configuration) {
// Woops apparently this automatically adds 443 to the * block listened ports which is definitely not what we want
// but it works with globals so hey this is just a PoC
proxied_tls_domains.insert(
server_configuration.filters.hostname.clone().unwrap(),
proxied_tls_domain.as_str().unwrap().to_string(),
);
tls_ports.insert(https_port.unwrap_or(443), CustomSniResolver::new());
}

let sni_hostname = server_configuration.filters.hostname.clone().or_else(|| {
// !!! UNTESTED, many clients don't send SNI hostname when accessing via IP address anyway
match server_configuration.filters.ip {
Expand Down Expand Up @@ -1444,6 +1456,7 @@ fn before_starting_server(
acme_tls_alpn_01_configs.clone(),
acme_http_01_resolvers.clone(),
enable_proxy_protocol,
proxied_tls_domains.clone(),
)?);
}

Expand Down