use std::{collections::BTreeSet, net::SocketAddr, sync::Arc};
use async_trait::async_trait;
use once_cell::sync::OnceCell;
use pingora::{
apps::ServerApp,
tls::ssl::{AlpnError, NameType, SslAcceptor, SslAcceptorBuilder, SslMethod},
listeners::tls::TlsSettings,
protocols::Stream,
proxy::{http_proxy, HttpProxy},
server::{configuration::ServerConf, ShutdownWatch},
services::listening::Service,
};
use crate::config::server_tls::ServerTls;
use crate::tcp::{
alpn::{select_protocols, AlpnProtocols},
on_address,
proxy::TcpProxy,
TcpTarget,
};
use super::{
cert_store::CertStore, connections::Counted, https_proxy::Router, listener::Listener,
timeouts::WithTimeouts,
};
pub type ProxyService = Service<Counted<WithTimeouts<HttpProxy<Router>>>>;
pub struct HttpsApp {
http: Arc<WithTimeouts<HttpProxy<Router>>>,
tcp: Option<TcpProxy>,
}
#[async_trait]
impl ServerApp for HttpsApp {
async fn process_new(
self: &Arc<Self>,
stream: Stream,
shutdown: &ShutdownWatch,
) -> Option<Stream> {
if let Some(tcp) = &self.tcp {
if let Some(target) = tcp.target(&stream) {
tcp.forward(stream, target, None).await;
return None;
}
}
self.http.process_new(stream, shutdown).await
}
async fn cleanup(&self) {
self.http.cleanup().await;
}
}
pub fn http_service(
configuration: &Arc<ServerConf>,
addresses: &BTreeSet<String>,
default_address: SocketAddr,
worker_threads: Option<usize>,
) -> ProxyService {
let router = Router {
listener: Listener::Http,
default_address,
};
let mut service = Service::new(
"Pingora HTTP Proxy Service".to_string(),
Counted::new(WithTimeouts::new(http_proxy(configuration, router))),
);
service.threads = worker_threads;
for address in addresses {
service.add_tcp(address);
}
service
}
pub fn https_service(
configuration: &Arc<ServerConf>,
addresses: &BTreeSet<String>,
default_address: SocketAddr,
tcp_targets: &Arc<Vec<TcpTarget>>,
worker_threads: Option<usize>,
) -> Result<Service<Counted<HttpsApp>>, anyhow::Error> {
let app = https_app(configuration, addresses, default_address, tcp_targets);
let mut service = Service::new("Pingora HTTP Proxy Service".to_string(), Counted::new(app));
service.threads = worker_threads;
for address in addresses {
let mut settings = tls_settings()?;
let protocols = AlpnProtocols::new(on_address(tcp_targets, address));
select_protocols(&mut settings, protocols, true);
service.add_tls_with_settings(address, None, settings);
}
Ok(service)
}
pub fn https_app(
configuration: &Arc<ServerConf>,
addresses: &BTreeSet<String>,
default_address: SocketAddr,
tcp_targets: &Arc<Vec<TcpTarget>>,
) -> HttpsApp {
let tcp = addresses
.iter()
.any(|address| on_address(tcp_targets, address).next().is_some())
.then(|| TcpProxy::new(tcp_targets.clone()));
let router = Router {
listener: Listener::Https,
default_address,
};
HttpsApp {
http: Arc::new(WithTimeouts::new(http_proxy(configuration, router))),
tcp,
}
}
fn tls_settings() -> Result<TlsSettings, anyhow::Error> {
let mut tls_settings = TlsSettings::with_callbacks(Box::new(CertStore::new()))?;
tls_settings.enable_h2();
configure_tls(&mut tls_settings)?;
Ok(tls_settings)
}
pub fn tls_acceptor(protocols: AlpnProtocols) -> Result<SslAcceptor, anyhow::Error> {
let mut builder = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls())?;
configure_tls(&mut builder)?;
builder.set_alpn_select_callback(move |ssl, offered| {
protocols
.select(ssl.servername(NameType::HOST_NAME), offered, true)
.ok_or(AlpnError::NOACK)
});
Ok(builder.build())
}
static SERVER_TLS: OnceCell<ServerTls> = OnceCell::new();
pub fn set_server_tls(tls: ServerTls) {
let _ = SERVER_TLS.set(tls);
}
fn configure_tls(builder: &mut SslAcceptorBuilder) -> Result<(), anyhow::Error> {
builder.set_servername_callback(move |ssl_ref, _| CertStore::sni_callback(ssl_ref));
builder.set_client_hello_callback(|ssl, _| {
super::handshakes::started(ssl);
Ok(pingora::tls::ssl::ClientHelloResponse::SUCCESS)
});
let tls = SERVER_TLS.get().cloned().unwrap_or_default();
builder.set_min_proto_version(Some(tls.min_version.ssl_version()))?;
builder.set_max_proto_version(Some(pingora::tls::ssl::SslVersion::TLS1_3))?;
builder.set_cipher_list(&tls.cipher_list())?;
Ok(())
}