Skip to content

Commit

Permalink
🎨 Replace axum-server with hyper for HTTPS service listening
Browse files Browse the repository at this point in the history
  • Loading branch information
mokeyish committed Jan 10, 2025
1 parent 1c3f84e commit 59ab3e6
Show file tree
Hide file tree
Showing 9 changed files with 501 additions and 488 deletions.
714 changes: 310 additions & 404 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,24 @@ resolve-cli = ["dep:console"]
dns-over-tls = []
dns-over-https = ["dns-over-https-rustls"]
dns-over-quic = [
"dns-over-rustls",
"hickory-proto/dns-over-quic",
"hickory-resolver/dns-over-quic",
]
dns-over-h3 = [
"dns-over-rustls",
"hickory-proto/dns-over-h3",
"hickory-resolver/dns-over-h3"
]

dns-over-https-rustls = [
"dns-over-rustls",
"hickory-proto/dns-over-https-rustls",
"hickory-resolver/dns-over-https-rustls",
]

dns-over-rustls = []

mdns = []

service = [
Expand Down Expand Up @@ -104,9 +109,11 @@ bytes = "1.6.0"
either = { version = "1.12.0", optional = true }


# api
# webapi
axum = { version = "0.8.1" }
axum-server = { version = "0.7.1", features = ["tls-rustls-no-provider"] }
hyper = { version = "1.1.0", default-features = false }
hyper-util = { version = "0.1.3", features = ["http2"]}
tower = { version = "0.5.2", default-features = false }

# serde
serde = { version = "1.0", features = ["derive"]}
Expand Down
24 changes: 12 additions & 12 deletions localhost.crt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICFDCCAbqgAwIBAgIQfLSfIzXeJH7uFbCJAix0BzAKBggqhkjOPQQDAjAfMR0w
GwYDVQQDExRZSVNIIEludGVybWVkaWF0ZSBDQTAeFw0yMzEwMjIxMjU2NTRaFw0y
NDEwMjExMjU3NTRaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDBZMBMGByqGSM49AgEG
CCqGSM49AwEHA0IABDbTES0Vlqw7ZQhC6vs/BsOBv0mOdGcuoMYzgeowwcJjWtB/
jA5H5EeOT8TZ9eKnPIMlIqyNdyyf161qNvZqCBSjgeIwgd8wDgYDVR0PAQH/BAQD
AgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUvpjZ
q4jLLqC7xrUDXkdVSrUs618wHwYDVR0jBBgwFoAUzRzI+eUvnS+jvviAwgddIuAY
xV0wFAYDVR0RBA0wC4IJbG9jYWxob3N0MFgGDCsGAQQBgqRkxihAAQRIMEYCAQEE
FG1va2V5aXNoQGhvdG1haWwuY29tBCtJRjZUc3dPcmg0bDlQaVVfSTdDQU52THkx
X0FXdUtwNnR4NjA4REJvSnlNMAoGCCqGSM49BAMCA0gAMEUCICNKhfMUEiXwVQrx
WE2KEEdmUi18VvNa41/59MOMmCHYAiEA7bzVMr9hTGhUXkwiI6kDMNr+v2bxEuM7
3XLSMaYmACk=
MIICGzCCAcGgAwIBAgIRAIlF4oZgMEpjPuo7cdTGezowCgYIKoZIzj0EAwIwHzEd
MBsGA1UEAxMUWUlTSCBJbnRlcm1lZGlhdGUgQ0EwHhcNMjQxMTA4MjIyMzI5WhcN
MjUxMTA4MjIyNDI5WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAATXM+PRPb6/9QhyNFQaACLg4XhlEynJ3LG1rs9tOvAkn9oM
D3J7CXIBo7KGPOdqnrqTla+ykcqdxLE/Ic88CTzgo4HoMIHlMA4GA1UdDwEB/wQE
AwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFEyS
NwRJtBEWPVcxbu28kyONOSvtMB8GA1UdIwQYMBaAFM0cyPnlL50vo774gMIHXSLg
GMVdMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATBYBgwrBgEEAYKkZMYoQAEE
SDBGAgEBBBRtb2tleWlzaEBob3RtYWlsLmNvbQQrSUY2VHN3T3JoNGw5UGlVX0k3
Q0FOdkx5MV9BV3VLcDZ0eDYwOERCb0p5TTAKBggqhkjOPQQDAgNIADBFAiEA66eA
hdwqgOLwMQ25OsJUDsRbVsGZxrq1RyiVCcZoNZECIEWV5/vsUUBVyMC4GbOMkk1D
9pSAf1zPy40ptBeZIsuN
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBmjCCAUGgAwIBAgIRANnVfniG4v7Op0QDTUGMWTowCgYIKoZIzj0EAwIwFzEV
Expand Down
6 changes: 3 additions & 3 deletions localhost.key
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIM95oiaM3BuvVhb2b8+kv1lEjQS4NDDr5XIucF1k07KooAoGCCqGSM49
AwEHoUQDQgAENtMRLRWWrDtlCELq+z8Gw4G/SY50Zy6gxjOB6jDBwmNa0H+MDkfk
R45PxNn14qc8gyUirI13LJ/XrWo29moIFA==
MHcCAQEEILniizPfytTENXlicDVeWfxPKGIyHp+sFlMulXxqhk2IoAoGCCqGSM49
AwEHoUQDQgAE1zPj0T2+v/UIcjRUGgAi4OF4ZRMpydyxta7PbTrwJJ/aDA9yewly
AaOyhjznap66k5WvspHKncSxPyHPPAk84A==
-----END EC PRIVATE KEY-----
61 changes: 5 additions & 56 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
use std::{io, net::SocketAddr, sync::Arc};
use std::sync::Arc;

use axum::{
http::StatusCode,
response::{IntoResponse, Response},
routing::get,
Json, Router,
};
use axum_server::{tls_rustls::RustlsConfig, Handle};
use serde::{Deserialize, Serialize};
use tokio::net::TcpListener;
use tokio_util::sync::CancellationToken;

mod address;
mod audit;
Expand All @@ -21,67 +18,19 @@ mod nameserver;
mod serve_dns;
mod settings;

use crate::rustls::{Certificate, PrivateKey};
use crate::{app::App, server::DnsHandle};

type StatefulRouter = Router<Arc<ServeState>>;

pub struct ServeState {
app: Arc<App>,
dns_handle: DnsHandle,
pub app: Arc<App>,
pub dns_handle: DnsHandle,
}

pub async fn serve(
app: Arc<App>,
dns_handle: DnsHandle,
tcp_listener: TcpListener,
certificate: Vec<Certificate>,
certificate_key: PrivateKey,
) -> io::Result<CancellationToken> {
let token = CancellationToken::new();
let cancellation_token = token.clone();

let state = Arc::new(ServeState { app, dns_handle });

let app = Router::new()
pub fn routes() -> StatefulRouter {
Router::new()
.merge(serve_dns::routes())
.nest("/api", api_routes())
.with_state(state.clone())
.into_make_service_with_connect_info::<SocketAddr>();

let certificate = certificate
.into_iter()
.map(|c| c.as_ref().to_vec())
.collect::<Vec<_>>();
let certificate_key = certificate_key.secret_der().to_vec();

let tcp_listener = tcp_listener.into_std()?;
let rustls_config = RustlsConfig::from_der(certificate, certificate_key).await?;

tokio::spawn(async move {
use crate::log;
let shutdown_handle = Handle::new();

tokio::select! {
result = axum_server::from_tcp_rustls(
tcp_listener,
rustls_config,
)
.handle(shutdown_handle.clone())
.serve(app) => match result {
Ok(()) => (),
Err(e) => {
log::debug!("error receiving quic connection: {e}");
}
},
_ = cancellation_token.cancelled() => {
// A graceful shutdown was initiated. Break out of the loop.
shutdown_handle.graceful_shutdown(Some(std::time::Duration::from_secs(5)))
},
};
});

Ok(token)
}

fn api_routes() -> StatefulRouter {
Expand Down
30 changes: 29 additions & 1 deletion src/rustls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rustls::pki_types::{CertificateDer, PrivateKeyDer};
pub type Certificate = CertificateDer<'static>;
pub type PrivateKey = PrivateKeyDer<'static>;

use rustls::ClientConfig;
use rustls::{ClientConfig, ServerConfig};

use crate::{
config::SslConfig,
Expand Down Expand Up @@ -218,3 +218,31 @@ pub fn load_certificate_and_key(

Ok((certificate, certificate_key))
}

#[cfg(feature = "dns-over-rustls")]
pub fn tls_server_config(
protocol: &[u8],
cert: Vec<CertificateDer<'static>>,
key: PrivateKeyDer<'static>,
) -> Result<ServerConfig, io::Error> {
let mut config =
ServerConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
.with_safe_default_protocol_versions()
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("error creating TLS acceptor: {e}"),
)
})?
.with_no_client_auth()
.with_single_cert(cert, key)
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("error creating TLS acceptor: {e}"),
)
})?;

config.alpn_protocols = vec![protocol.to_vec()];
Ok(config)
}
127 changes: 126 additions & 1 deletion src/server/https.rs
Original file line number Diff line number Diff line change
@@ -1 +1,126 @@
pub use crate::api::serve;
use axum::extract::Request;
use hyper::body::Incoming;
use hyper_util::{
rt::{TokioExecutor, TokioIo},
server,
};
use std::{convert::Infallible, io, net::SocketAddr, sync::Arc};
use tokio::{net, task::JoinSet};
use tokio_util::sync::CancellationToken;
use tower::{Service as _, ServiceExt};

use tokio_rustls::TlsAcceptor;

use super::{reap_tasks, sanitize_src_address, DnsHandle};

use crate::{
api::ServeState,
app::App,
log,
rustls::{tls_server_config, Certificate, PrivateKey},
};

pub fn serve(
app: Arc<App>,
listener: net::TcpListener,
dns_handle: DnsHandle,
certificate_and_key: (Vec<Certificate>, PrivateKey),
) -> io::Result<CancellationToken> {
let token = CancellationToken::new();
let cancellation_token = token.clone();

let tls_config = tls_server_config(b"h2", certificate_and_key.0, certificate_and_key.1)
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("error creating TLS acceptor: {e}"),
)
})?;

log::debug!("registered HTTPS: {:?}", listener);

let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config));

let state = Arc::new(ServeState { app, dns_handle });

let make_service = crate::api::routes()
.with_state(state.clone())
.into_make_service_with_connect_info::<SocketAddr>();

tokio::spawn(async move {
let mut inner_join_set = JoinSet::new();
loop {
let (tcp_stream, src_addr) = tokio::select! {
tcp_stream = listener.accept() => match tcp_stream {
Ok((t, s)) => (t, s),
Err(e) => {
log::debug!("error receiving TLS tcp_stream error: {}", e);
continue;
},
},
_ = cancellation_token.cancelled() => {
// A graceful shutdown was initiated. Break out of the loop.
break;
},
};

// verify that the src address is safe for responses
if let Err(e) = sanitize_src_address(src_addr) {
log::warn!(
"address can not be responded to {src_addr}: {e}",
src_addr = src_addr,
e = e
);
continue;
}

let tls_acceptor = tls_acceptor.clone();

// kick out to a different task immediately, let them do the TLS handshake
let mut make_service = make_service.clone();
inner_join_set.spawn(async move {
log::debug!("starting HTTPS request from: {}", src_addr);

// perform the TLS
let tls_stream = tls_acceptor.accept(tcp_stream).await;

let socket = match tls_stream {
Ok(tls_stream) => TokioIo::new(tls_stream),
Err(e) => {
log::debug!("https handshake src: {} error: {}", src_addr, e);
return;
}
};

log::debug!("accepted HTTPS request from: {}", src_addr);

let tower_service = unwrap_infallible(make_service.call(src_addr).await);

let hyper_service =
hyper::service::service_fn(move |request: Request<Incoming>| {
tower_service.clone().oneshot(request)
});

if let Err(err) = server::conn::auto::Builder::new(TokioExecutor::new())
.http2()
.enable_connect_protocol()
.serve_connection_with_upgrades(socket, hyper_service)
.await
{
eprintln!("failed to serve connection: {err:#}");
}
});

reap_tasks(&mut inner_join_set);
}
});

Ok(token)
}

fn unwrap_infallible<T>(result: Result<T, Infallible>) -> T {
match result {
Ok(value) => value,
Err(err) => match err {},
}
}
8 changes: 3 additions & 5 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,10 @@ pub async fn serve(
let app = app.clone();
https::serve(
app,
dns_handle,
https_listener,
certificate,
certificate_key,
)
.await?
dns_handle,
(certificate.clone(), certificate_key.clone_key()),
)?
}
#[cfg(feature = "dns-over-quic")]
ListenerConfig::Quic(listener) => {
Expand Down
8 changes: 4 additions & 4 deletions src/server/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,24 @@ pub fn serve(
timeout: Duration,
certificate_and_key: (Vec<Certificate>, PrivateKey),
) -> io::Result<CancellationToken> {
use crate::libdns::proto::rustls::{tls_from_stream, tls_server};
use crate::libdns::proto::rustls::tls_from_stream;
use crate::rustls::tls_server_config;
use tokio_rustls::TlsAcceptor;

let token = CancellationToken::new();
let cancellation_token = token.clone();

let mut tls_config = tls_server::new_acceptor(certificate_and_key.0, certificate_and_key.1)
let tls_config = tls_server_config(b"dot", certificate_and_key.0, certificate_and_key.1)
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("error creating TLS acceptor: {e}"),
)
})?;
tls_config.alpn_protocols = vec![b"dot".to_vec()];

let handler = handler.clone();

log::debug!("registered tcp: {:?}", listener);
log::debug!("registered TLS: {:?}", listener);

let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config));

Expand Down

0 comments on commit 59ab3e6

Please sign in to comment.