wip: support rustls-0.23 for http1.1 and 1.2

This commit is contained in:
Jun Kurihara 2024-05-28 20:49:11 +09:00
commit 0c6f3edf18
No known key found for this signature in database
GPG key ID: 48ADFD173ED22B03
16 changed files with 80 additions and 393 deletions

View file

@ -1,5 +1,4 @@
use crate::{
crypto::CryptoSource,
error::*,
log::*,
name_exp::{ByteName, ServerName},
@ -13,10 +12,7 @@ use super::upstream::PathManager;
/// Struct serving information to route incoming connections, like server name to be handled and tls certs/keys settings.
#[derive(Builder)]
pub struct BackendApp<T>
where
T: CryptoSource,
{
pub struct BackendApp {
#[builder(setter(into))]
/// backend application name, e.g., app1
pub app_name: String,
@ -28,50 +24,27 @@ where
/// tls settings: https redirection with 30x
#[builder(default)]
pub https_redirection: Option<bool>,
/// TLS settings: source meta for server cert, key, client ca cert
#[builder(default)]
pub crypto_source: Option<T>,
}
impl<'a, T> BackendAppBuilder<T>
where
T: CryptoSource,
{
impl<'a> BackendAppBuilder {
pub fn server_name(&mut self, server_name: impl Into<Cow<'a, str>>) -> &mut Self {
self.server_name = Some(server_name.to_server_name());
self
}
}
#[derive(Default)]
/// HashMap and some meta information for multiple Backend structs.
pub struct BackendAppManager<T>
where
T: CryptoSource,
{
pub struct BackendAppManager {
/// HashMap of Backend structs, key is server name
pub apps: HashMap<ServerName, BackendApp<T>>,
pub apps: HashMap<ServerName, BackendApp>,
/// for plaintext http
pub default_server_name: Option<ServerName>,
}
impl<T> Default for BackendAppManager<T>
where
T: CryptoSource,
{
fn default() -> Self {
Self {
apps: HashMap::<ServerName, BackendApp<T>>::default(),
default_server_name: None,
}
}
}
impl<T> TryFrom<&AppConfig<T>> for BackendApp<T>
where
T: CryptoSource + Clone,
{
impl TryFrom<&AppConfig> for BackendApp {
type Error = RpxyError;
fn try_from(app_config: &AppConfig<T>) -> Result<Self, Self::Error> {
fn try_from(app_config: &AppConfig) -> Result<Self, Self::Error> {
let mut backend_builder = BackendAppBuilder::default();
let path_manager = PathManager::try_from(app_config)?;
backend_builder
@ -83,28 +56,20 @@ where
backend_builder.build()?
} else {
let tls = app_config.tls.as_ref().unwrap();
backend_builder
.https_redirection(Some(tls.https_redirection))
.crypto_source(Some(tls.inner.clone()))
.build()?
backend_builder.https_redirection(Some(tls.https_redirection)).build()?
};
Ok(backend)
}
}
impl<T> TryFrom<&AppConfigList<T>> for BackendAppManager<T>
where
T: CryptoSource + Clone,
{
impl TryFrom<&AppConfigList> for BackendAppManager {
type Error = RpxyError;
fn try_from(config_list: &AppConfigList<T>) -> Result<Self, Self::Error> {
fn try_from(config_list: &AppConfigList) -> Result<Self, Self::Error> {
let mut manager = Self::default();
for app_config in config_list.inner.iter() {
let backend: BackendApp<T> = BackendApp::try_from(app_config)?;
manager
.apps
.insert(app_config.server_name.clone().to_server_name(), backend);
let backend: BackendApp = BackendApp::try_from(app_config)?;
manager.apps.insert(app_config.server_name.clone().to_server_name(), backend);
info!(
"Registering application {} ({})",

View file

@ -6,7 +6,6 @@ use super::load_balance::{
// use super::{BytesName, LbContext, PathNameBytesExp, UpstreamOption};
use super::upstream_opts::UpstreamOption;
use crate::{
crypto::CryptoSource,
error::RpxyError,
globals::{AppConfig, UpstreamUri},
log::*,
@ -28,12 +27,9 @@ pub struct PathManager {
inner: HashMap<PathName, UpstreamCandidates>,
}
impl<T> TryFrom<&AppConfig<T>> for PathManager
where
T: CryptoSource,
{
impl TryFrom<&AppConfig> for PathManager {
type Error = RpxyError;
fn try_from(app_config: &AppConfig<T>) -> Result<Self, Self::Error> {
fn try_from(app_config: &AppConfig) -> Result<Self, Self::Error> {
let mut inner: HashMap<PathName, UpstreamCandidates> = HashMap::default();
app_config.reverse_proxy.iter().for_each(|rpc| {

View file

@ -1,21 +1,10 @@
pub const RESPONSE_HEADER_SERVER: &str = "rpxy";
// pub const LISTEN_ADDRESSES_V4: &[&str] = &["0.0.0.0"];
// pub const LISTEN_ADDRESSES_V6: &[&str] = &["[::]"];
pub const TCP_LISTEN_BACKLOG: u32 = 1024;
// pub const HTTP_LISTEN_PORT: u16 = 8080;
// pub const HTTPS_LISTEN_PORT: u16 = 8443;
pub const PROXY_IDLE_TIMEOUT_SEC: u64 = 20;
pub const UPSTREAM_IDLE_TIMEOUT_SEC: u64 = 20;
pub const TLS_HANDSHAKE_TIMEOUT_SEC: u64 = 15; // default as with firefox browser
pub const MAX_CLIENTS: usize = 512;
pub const MAX_CONCURRENT_STREAMS: u32 = 64;
pub const CERTS_WATCH_DELAY_SECS: u32 = 60;
pub const LOAD_CERTS_ONLY_WHEN_UPDATED: bool = true;
// #[cfg(feature = "http3")]
// pub const H3_RESPONSE_BUF_SIZE: usize = 65_536; // 64KB
// #[cfg(feature = "http3")]
// pub const H3_REQUEST_BUF_SIZE: usize = 65_536; // 64KB // handled by quinn
#[allow(non_snake_case)]
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]

View file

@ -59,8 +59,8 @@ pub enum RpxyError {
// certificate reloader errors
#[error("No certificate reloader when building a proxy for TLS")]
NoCertificateReloader,
#[error("Certificate reload error: {0}")]
CertificateReloadError(#[from] hot_reload::ReloaderError<crate::crypto::ServerCryptoBase>),
// #[error("Certificate reload error: {0}")]
// CertificateReloadError(#[from] hot_reload::ReloaderError<crate::crypto::ServerCryptoBase>),
// backend errors
#[error("Invalid reverse proxy setting")]

View file

@ -1,9 +1,6 @@
use crate::{
constants::*,
count::RequestCount,
crypto::{CryptoSource, ServerCryptoBase},
};
use crate::{constants::*, count::RequestCount};
use hot_reload::ReloaderReceiver;
use rpxy_certs::ServerCryptoBase;
use std::{net::SocketAddr, sync::Arc, time::Duration};
/// Global object containing proxy configurations and shared object like counters.
@ -17,10 +14,8 @@ pub struct Globals {
pub runtime_handle: tokio::runtime::Handle,
/// Shared context - Notify object to stop async tasks
pub term_notify: Option<Arc<tokio::sync::Notify>>,
/// Shared context - Certificate reloader service receiver
pub cert_reloader_rx: Option<ReloaderReceiver<ServerCryptoBase>>,
/// Shared context - Certificate reloader service receiver // TODO: newer one
pub cert_reloader_rx_new: Option<ReloaderReceiver<rpxy_certs::ServerCryptoBase>>,
pub cert_reloader_rx: Option<ReloaderReceiver<ServerCryptoBase>>,
}
/// Configuration parameters for proxy transport and request handlers
@ -129,24 +124,18 @@ impl Default for ProxyConfig {
/// Configuration parameters for backend applications
#[derive(PartialEq, Eq, Clone)]
pub struct AppConfigList<T>
where
T: CryptoSource,
{
pub inner: Vec<AppConfig<T>>,
pub struct AppConfigList {
pub inner: Vec<AppConfig>,
pub default_app: Option<String>,
}
/// Configuration parameters for single backend application
#[derive(PartialEq, Eq, Clone)]
pub struct AppConfig<T>
where
T: CryptoSource,
{
pub struct AppConfig {
pub app_name: String,
pub server_name: String,
pub reverse_proxy: Vec<ReverseProxyConfig>,
pub tls: Option<TlsConfig<T>>,
pub tls: Option<TlsConfig>,
}
/// Configuration parameters for single reverse proxy corresponding to the path
@ -167,10 +156,6 @@ pub struct UpstreamUri {
/// Configuration parameters on TLS for a single backend application
#[derive(PartialEq, Eq, Clone)]
pub struct TlsConfig<T>
where
T: CryptoSource,
{
pub inner: T,
pub struct TlsConfig {
pub https_redirection: bool,
}

View file

@ -1,7 +1,6 @@
mod backend;
mod constants;
mod count;
mod crypto;
mod error;
mod forwarder;
mod globals;
@ -12,8 +11,13 @@ mod name_exp;
mod proxy;
/* ------------------------------------------------ */
use crate::{
crypto::build_cert_reloader, error::*, forwarder::Forwarder, globals::Globals, log::*,
message_handler::HttpMessageHandlerBuilder, proxy::Proxy,
// crypto::build_cert_reloader,
error::*,
forwarder::Forwarder,
globals::Globals,
log::*,
message_handler::HttpMessageHandlerBuilder,
proxy::Proxy,
};
use futures::future::select_all;
use hot_reload::ReloaderReceiver;
@ -21,26 +25,19 @@ use rpxy_certs::ServerCryptoBase;
use std::sync::Arc;
/* ------------------------------------------------ */
pub use crate::{
crypto::{CertsAndKeys, CryptoSource},
globals::{AppConfig, AppConfigList, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri},
};
pub use crate::globals::{AppConfig, AppConfigList, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri};
pub mod reexports {
pub use hyper::Uri;
pub use rustls::{Certificate, PrivateKey};
}
/// Entrypoint that creates and spawns tasks of reverse proxy services
pub async fn entrypoint<T>(
pub async fn entrypoint(
proxy_config: &ProxyConfig,
app_config_list: &AppConfigList<T>,
app_config_list: &AppConfigList,
cert_rx: Option<&ReloaderReceiver<ServerCryptoBase>>, // TODO:
runtime_handle: &tokio::runtime::Handle,
term_notify: Option<Arc<tokio::sync::Notify>>,
) -> RpxyResult<()>
where
T: CryptoSource + Clone + Send + Sync + 'static,
{
) -> RpxyResult<()> {
#[cfg(all(feature = "http3-quinn", feature = "http3-s2n"))]
warn!("Both \"http3-quinn\" and \"http3-s2n\" features are enabled. \"http3-quinn\" will be used");
@ -82,26 +79,16 @@ where
// 1. build backends, and make it contained in Arc
let app_manager = Arc::new(backend::BackendAppManager::try_from(app_config_list)?);
// 2. build crypto reloader service
let (cert_reloader_service, cert_reloader_rx) = match proxy_config.https_port {
Some(_) => {
let (s, r) = build_cert_reloader(&app_manager).await?;
(Some(s), Some(r))
}
None => (None, None),
};
// 3. build global shared context
// 2. build global shared context
let globals = Arc::new(Globals {
proxy_config: proxy_config.clone(),
request_count: Default::default(),
runtime_handle: runtime_handle.clone(),
term_notify: term_notify.clone(),
cert_reloader_rx: cert_reloader_rx.clone(),
cert_reloader_rx_new: cert_rx.cloned(), // TODO: newer one
cert_reloader_rx: cert_rx.cloned(),
});
// 4. build message handler containing Arc-ed http_client and backends, and make it contained in Arc as well
// 3. build message handler containing Arc-ed http_client and backends, and make it contained in Arc as well
let forwarder = Arc::new(Forwarder::try_new(&globals).await?);
let message_handler = Arc::new(
HttpMessageHandlerBuilder::default()
@ -111,7 +98,7 @@ where
.build()?,
);
// 5. spawn each proxy for a given socket with copied Arc-ed message_handler.
// 4. spawn each proxy for a given socket with copied Arc-ed message_handler.
// build hyper connection builder shared with proxy instances
let connection_builder = proxy::connection_builder(&globals);
@ -132,23 +119,9 @@ where
globals.runtime_handle.spawn(async move { proxy.start().await })
});
// wait for all future
match cert_reloader_service {
Some(cert_service) => {
tokio::select! {
_ = cert_service.start() => {
error!("Certificate reloader service got down");
}
_ = select_all(futures_iter) => {
error!("Some proxy services are down");
}
}
}
None => {
if let (Ok(Err(e)), _, _) = select_all(futures_iter).await {
error!("Some proxy services are down: {}", e);
}
}
if let (Ok(Err(e)), _, _) = select_all(futures_iter).await {
error!("Some proxy services are down: {}", e);
return Err(e);
}
Ok(())

View file

@ -7,7 +7,6 @@ use super::{
};
use crate::{
backend::{BackendAppManager, LoadBalanceContext},
crypto::CryptoSource,
error::*,
forwarder::{ForwardRequest, Forwarder},
globals::Globals,
@ -34,20 +33,18 @@ pub(super) struct HandlerContext {
#[derive(Clone, Builder)]
/// HTTP message handler for requests from clients and responses from backend applications,
/// responsible to manipulate and forward messages to upstream backends and downstream clients.
pub struct HttpMessageHandler<U, C>
pub struct HttpMessageHandler<C>
where
C: Send + Sync + Connect + Clone + 'static,
U: CryptoSource + Clone,
{
forwarder: Arc<Forwarder<C>>,
pub(super) globals: Arc<Globals>,
app_manager: Arc<BackendAppManager<U>>,
app_manager: Arc<BackendAppManager>,
}
impl<U, C> HttpMessageHandler<U, C>
impl<C> HttpMessageHandler<C>
where
C: Send + Sync + Connect + Clone + 'static,
U: CryptoSource + Clone,
{
/// Handle incoming request message from a client.
/// Responsible to passthrough responses from backend applications or generate synthetic error responses.
@ -64,14 +61,7 @@ where
log_data.client_addr(&client_addr);
let http_result = self
.handle_request_inner(
&mut log_data,
req,
client_addr,
listen_addr,
tls_enabled,
tls_server_name,
)
.handle_request_inner(&mut log_data, req, client_addr, listen_addr, tls_enabled, tls_server_name)
.await;
// passthrough or synthetic response

View file

@ -3,17 +3,15 @@ use crate::{
backend::{BackendApp, UpstreamCandidates},
constants::RESPONSE_HEADER_SERVER,
log::*,
CryptoSource,
};
use anyhow::{anyhow, ensure, Result};
use http::{header, HeaderValue, Request, Response, Uri};
use hyper_util::client::legacy::connect::Connect;
use std::net::SocketAddr;
impl<U, C> HttpMessageHandler<U, C>
impl<C> HttpMessageHandler<C>
where
C: Send + Sync + Connect + Clone + 'static,
U: CryptoSource + Clone,
{
////////////////////////////////////////////////////
// Functions to generate messages
@ -21,7 +19,7 @@ where
#[allow(unused_variables)]
/// Manipulate a response message sent from a backend application to forward downstream to a client.
pub(super) fn generate_response_forwarded<B>(&self, response: &mut Response<B>, backend_app: &BackendApp<U>) -> Result<()> {
pub(super) fn generate_response_forwarded<B>(&self, response: &mut Response<B>, backend_app: &BackendApp) -> Result<()> {
let headers = response.headers_mut();
remove_connection_header(headers);
remove_hop_header(headers);

View file

@ -11,10 +11,16 @@ mod proxy_quic_s2n;
use crate::{
globals::Globals,
hyper_ext::rt::{LocalExecutor, TokioTimer},
name_exp::ServerName,
};
use hyper_util::server::{self, conn::auto::Builder as ConnectionBuilder};
use rustc_hash::FxHashMap as HashMap;
use rustls::ServerConfig;
use std::sync::Arc;
/// SNI to ServerConfig map type
pub type SniServerCryptoMap = HashMap<ServerName, Arc<ServerConfig>>;
pub(crate) use proxy_main::Proxy;
/// build connection builder shared with proxy instances

View file

@ -1,7 +1,6 @@
use super::socket::bind_tcp_socket;
use crate::{
constants::TLS_HANDSHAKE_TIMEOUT_SEC,
crypto::{CryptoSource, ServerCrypto, SniServerCryptoMap},
error::*,
globals::Globals,
hyper_ext::{
@ -20,14 +19,15 @@ use hyper::{
service::service_fn,
};
use hyper_util::{client::legacy::connect::Connect, rt::TokioIo, server::conn::auto::Builder as ConnectionBuilder};
use rpxy_certs::ServerCrypto;
use std::{net::SocketAddr, sync::Arc, time::Duration};
use tokio::time::timeout;
/// Wrapper function to handle request for HTTP/1.1 and HTTP/2
/// HTTP/3 is handled in proxy_h3.rs which directly calls the message handler
async fn serve_request<U, T>(
async fn serve_request<T>(
req: Request<Incoming>,
handler: Arc<HttpMessageHandler<U, T>>,
handler: Arc<HttpMessageHandler<T>>,
client_addr: SocketAddr,
listen_addr: SocketAddr,
tls_enabled: bool,
@ -35,7 +35,6 @@ async fn serve_request<U, T>(
) -> RpxyResult<Response<ResponseBody>>
where
T: Send + Sync + Connect + Clone,
U: CryptoSource + Clone,
{
handler
.handle_request(
@ -50,10 +49,9 @@ where
#[derive(Clone)]
/// Proxy main object responsible to serve requests received from clients at the given socket address.
pub(crate) struct Proxy<U, T, E = LocalExecutor>
pub(crate) struct Proxy<T, E = LocalExecutor>
where
T: Send + Sync + Connect + Clone + 'static,
U: CryptoSource + Clone + Sync + Send + 'static,
{
/// global context shared among async tasks
pub globals: Arc<Globals>,
@ -64,13 +62,12 @@ where
/// hyper connection builder serving http request
pub connection_builder: Arc<ConnectionBuilder<E>>,
/// message handler serving incoming http request
pub message_handler: Arc<HttpMessageHandler<U, T>>,
pub message_handler: Arc<HttpMessageHandler<T>>,
}
impl<U, T> Proxy<U, T>
impl<T> Proxy<T>
where
T: Send + Sync + Connect + Clone + 'static,
U: CryptoSource + Clone + Sync + Send + 'static,
{
/// Serves requests from clients
fn serve_connection<I>(&self, stream: I, peer_addr: SocketAddr, tls_server_name: Option<ServerName>)
@ -164,15 +161,11 @@ where
let Some(mut server_crypto_rx) = self.globals.cert_reloader_rx.clone() else {
return Err(RpxyError::NoCertificateReloader);
};
// TODO: newer one
let Some(mut server_crypto_rx_new) = self.globals.cert_reloader_rx_new.clone() else {
return Err(RpxyError::NoCertificateReloader);
};
let tcp_socket = bind_tcp_socket(&self.listening_on)?;
let tcp_listener = tcp_socket.listen(self.globals.proxy_config.tcp_listen_backlog)?;
info!("Start TCP proxy serving with HTTPS request for configured host names");
let mut server_crypto_map: Option<Arc<SniServerCryptoMap>> = None;
let mut server_crypto_map: Option<Arc<super::SniServerCryptoMap>> = None;
loop {
select! {
tcp_cnx = tcp_listener.accept().fuse() => {
@ -234,28 +227,16 @@ where
error!("Reloader is broken");
break;
}
let cert_keys_map = server_crypto_rx.borrow().clone().unwrap();
let Some(server_crypto): Option<Arc<ServerCrypto>> = (&cert_keys_map).try_into().ok() else {
let server_crypto_base = server_crypto_rx.borrow().clone().unwrap();
let Some(server_config): Option<Arc<ServerCrypto>> = (&server_crypto_base).try_into().ok() else {
error!("Failed to update server crypto");
break;
};
server_crypto_map = Some(server_crypto.inner_local_map.clone());
}
// TODO: newer one
_ = server_crypto_rx_new.changed().fuse() => {
if server_crypto_rx_new.borrow().is_none() {
error!("Reloader is broken");
break;
}
let cert_keys_map = server_crypto_rx_new.borrow().clone().unwrap();
// let Some(server_crypto) = cert_keys_map.try_into().ok() else {
// break;
// };
// let Some(server_crypto): Option<Arc<ServerCrypto>> = (&cert_keys_map).try_into().ok() else {
// error!("Failed to update server crypto");
// break;
// };
// server_crypto_map = Some(server_crypto.inner_local_map.clone());
let map = server_config.individual_config_map.clone().iter().map(|(k,v)| {
let server_name = ServerName::from(k.as_slice());
(server_name, v.clone())
}).collect::<rustc_hash::FxHashMap<_,_>>();
server_crypto_map = Some(Arc::new(map));
}
}
}