Be able to get the SNI from the HTTP filter context
What is the problem your feature solves, or the need it fulfills?
I would like to be able to get the SNI when in the req_filter() callback, so that I can do some business logic on it with the HTTP HOST header.
Describe the solution you'd like
In the self or more likely the session, store the SNI (e.g. in the downstream session) so it can be retrieved. The SSL Digest is available in the req_filter and that would be a suitable place to put it. (session.digest();)
Describe alternatives you've considered
I tried to dive into the stream and get to the get_ssl() function, but was unable to figure out how (or if it is possible) to downcast stream to the SslStream.
if let Some(stream) = session.stream() {
println!("session stream: {:?}", stream);
println!("Actual type of stream: {}", std::any::type_name_of_val(&stream));
// downcast it somehow
// then
if let Some(ssl) = ssl_stream.get_ssl() {
if let Some(sni) = ssl.servername(pingora::tls::ssl::NameType::HOST_NAME) {
but the 5-6 methods I tried for downcasting didn't work. It would also be a lot of insider baseball even if it did work and likely fragile.
Additional context
From looking at the code, the SNI is not extracted anywhere. It is available in the TLS callback via a call to the underlying ssl instance but since there's no HTTP context there's no place to put it.
#[async_trait]
impl pingora::listeners::TlsAccept for DynamicCert {
async fn certificate_callback(&self, ssl: &mut pingora::tls::ssl::SslRef) {
// Inspect the SNI
if let Some(sni) = ssl.servername(pingora::tls::ssl::NameType::HOST_NAME) {
println!("SNI: {}", sni);
}
ext::ssl_use_certificate(ssl, &self.cert).unwrap();
ext::ssl_use_private_key(ssl, &self.key).unwrap();
}
}
It's possible I missed an ext field somewhere where the SNI could be stuffed.
Patch to rc/protocols/tls/boringssl_openssl/stream.rs
+ use crate::protocols::tls::boringssl_openssl::stream::ssl::NameType;
+ let sni = ssl.servername(NameType::HOST_NAME);
+ let sni_string: Option<String> = sni.map(ToOwned::to_owned);
SslDigest {
cipher,
version: ssl.version_str(),
organization: org,
serial_number: sn,
cert_digest,
+ sni: sni_string,
}
and src/protocols/tls/digest.rs
/// The TLS connection information
#[derive(Clone, Debug)]
pub struct SslDigest {
/// The cipher used
pub cipher: &'static str,
/// The TLS version of this connection
pub version: &'static str,
/// The organization of the peer's certificate
pub organization: Option<String>,
/// The serial number of the peer's certificate
pub serial_number: Option<String>,
/// The digest of the peer's certificate
pub cert_digest: Vec<u8>,
+ /// the SNI used in the negotiation
+ pub sni: Option<String>,
}
usage:
async fn request_filter(&self, session: &mut Session, ctx: &mut Self::CTX) -> Result<bool> {
let digest:Option<&Digest> = session.digest();
if let Some(digest) = digest {
println!("session digest: {:?}", digest);
if let Some(ssl_digest) = &digest.ssl_digest {
println!("session digest: {:?}", ssl_digest);
} else {
println!("session ssl digest not found");
}
} else {
println!("session digest not found");
}
We're quite open to this!
If you would like to open a PR, we'd be more than happy to review it!