You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2021/02/03 23:45:59 UTC

[trafficserver] 04/20: Generalize SNI support (#6870)

This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch 9.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 3ba9fbdb97db59b9266048743c8673baeaf2a718
Author: Masakazu Kitajo <ma...@apache.org>
AuthorDate: Fri Jan 29 09:08:19 2021 +0900

    Generalize SNI support (#6870)
    
    * Introduce SNISupport to generalize callback function
    
    This makes callback functions for SNI usable on QUIC connections.
    
    * Move code for SNI actions from the callback functions to SNIActionPerformers
---
 iocore/net/Makefile.am            |   1 +
 iocore/net/P_SNIActionPerformer.h |  31 ++++----
 iocore/net/P_SSLNetVConnection.h  |  17 +++--
 iocore/net/SSLNetVConnection.cc   |  13 ++--
 iocore/net/SSLUtils.cc            | 121 +++++++++----------------------
 iocore/net/TLSSNISupport.cc       | 145 ++++++++++++++++++++++++++++++++++++++
 iocore/net/TLSSNISupport.h        |  61 ++++++++++++++++
 7 files changed, 273 insertions(+), 116 deletions(-)

diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am
index 0209c3a..34d9fff 100644
--- a/iocore/net/Makefile.am
+++ b/iocore/net/Makefile.am
@@ -185,6 +185,7 @@ libinknet_a_SOURCES = \
 	SSLUtils.cc \
 	OCSPStapling.cc \
 	TLSSessionResumptionSupport.cc \
+	TLSSNISupport.cc \
 	UDPIOEvent.cc \
 	UnixConnection.cc \
 	UnixNet.cc \
diff --git a/iocore/net/P_SNIActionPerformer.h b/iocore/net/P_SNIActionPerformer.h
index cd0cd6e..b255969 100644
--- a/iocore/net/P_SNIActionPerformer.h
+++ b/iocore/net/P_SNIActionPerformer.h
@@ -49,7 +49,7 @@ public:
     std::optional<std::vector<std::string>> _fqdn_wildcard_captured_groups;
   };
 
-  virtual int SNIAction(Continuation *cont, const Context &ctx) const = 0;
+  virtual int SNIAction(TLSSNISupport *snis, const Context &ctx) const = 0;
 
   /**
     This method tests whether this action would have been triggered by a
@@ -72,9 +72,9 @@ public:
   ~ControlH2() override {}
 
   int
-  SNIAction(Continuation *cont, const Context &ctx) const override
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
   {
-    auto ssl_vc = dynamic_cast<SSLNetVConnection *>(cont);
+    auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
     if (ssl_vc) {
       if (!enable_h2) {
         ssl_vc->disableProtocol(TS_ALPN_PROTOCOL_INDEX_HTTP_2_0);
@@ -100,10 +100,10 @@ public:
   ~TunnelDestination() override {}
 
   int
-  SNIAction(Continuation *cont, const Context &ctx) const override
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
   {
     // Set the netvc option?
-    SSLNetVConnection *ssl_netvc = dynamic_cast<SSLNetVConnection *>(cont);
+    SSLNetVConnection *ssl_netvc = dynamic_cast<SSLNetVConnection *>(snis);
     if (ssl_netvc) {
       // If needed, we will try to amend the tunnel destination.
       if (ctx._fqdn_wildcard_captured_groups && need_fix) {
@@ -113,6 +113,9 @@ public:
       } else {
         ssl_netvc->set_tunnel_destination(destination, tunnel_decrypt, tls_upstream);
       }
+      if (ssl_netvc->has_tunnel_destination() && !ssl_netvc->decrypt_tunnel()) {
+        ssl_netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+      }
     }
     return SSL_TLSEXT_ERR_OK;
   }
@@ -201,9 +204,9 @@ public:
   VerifyClient(const char *param, std::string_view file, std::string_view dir) : VerifyClient(atoi(param), file, dir) {}
   ~VerifyClient() override;
   int
-  SNIAction(Continuation *cont, const Context &ctx) const override
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
   {
-    auto ssl_vc = dynamic_cast<SSLNetVConnection *>(cont);
+    auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
     Debug("ssl_sni", "action verify param %d", this->mode);
     setClientCertLevel(ssl_vc->ssl, this->mode);
     ssl_vc->set_ca_cert_file(ca_file, ca_dir);
@@ -232,7 +235,7 @@ public:
   HostSniPolicy(uint8_t param) : policy(param) {}
   ~HostSniPolicy() override {}
   int
-  SNIAction(Continuation *cont, const Context &ctx) const override
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
   {
     // On action this doesn't do anything
     return SSL_TLSEXT_ERR_OK;
@@ -261,14 +264,14 @@ public:
   TLSValidProtocols() : protocol_mask(max_mask) {}
   TLSValidProtocols(unsigned long protocols) : unset(false), protocol_mask(protocols) {}
   int
-  SNIAction(Continuation *cont, const Context & /* ctx */) const override
+  SNIAction(TLSSNISupport *snis, const Context & /* ctx */) const override
   {
     if (!unset) {
-      auto ssl_vc = dynamic_cast<SSLNetVConnection *>(cont);
+      auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
       Debug("ssl_sni", "TLSValidProtocol param 0%x", static_cast<unsigned int>(this->protocol_mask));
-      ssl_vc->protocol_mask_set = true;
-      ssl_vc->protocol_mask     = protocol_mask;
+      ssl_vc->set_valid_tls_protocols(protocol_mask, TLSValidProtocols::max_mask);
     }
+
     return SSL_TLSEXT_ERR_OK;
   }
 };
@@ -301,14 +304,14 @@ public:
   } // end function SNI_IpAllow
 
   int
-  SNIAction(Continuation *cont, const Context &ctx) const override
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
   {
     // i.e, ip filtering is not required
     if (ip_map.count() == 0) {
       return SSL_TLSEXT_ERR_OK;
     }
 
-    auto ssl_vc = dynamic_cast<SSLNetVConnection *>(cont);
+    auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
     auto ip     = ssl_vc->get_remote_endpoint();
 
     // check the allowed ips
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index da47678..3a85970 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -48,6 +48,7 @@
 #include "P_UnixNet.h"
 #include "P_ALPNSupport.h"
 #include "TLSSessionResumptionSupport.h"
+#include "TLSSNISupport.h"
 #include "P_SSLUtils.h"
 #include "P_SSLConfig.h"
 
@@ -93,7 +94,7 @@ enum SSLHandshakeStatus { SSL_HANDSHAKE_ONGOING, SSL_HANDSHAKE_DONE, SSL_HANDSHA
 //  A VConnection for a network socket.
 //
 //////////////////////////////////////////////////////////////////
-class SSLNetVConnection : public UnixNetVConnection, public ALPNSupport, public TLSSessionResumptionSupport
+class SSLNetVConnection : public UnixNetVConnection, public ALPNSupport, public TLSSessionResumptionSupport, public TLSSNISupport
 {
   typedef UnixNetVConnection super; ///< Parent type.
 
@@ -384,11 +385,9 @@ public:
   const char *
   get_server_name() const override
   {
-    return _serverName.get() ? _serverName.get() : "";
+    return _get_sni_server_name() ? _get_sni_server_name() : "";
   }
 
-  void set_server_name(std::string_view name);
-
   bool
   support_sni() const override
   {
@@ -473,6 +472,13 @@ public:
     return _ca_cert_dir.get();
   }
 
+  void
+  set_valid_tls_protocols(unsigned long proto_mask, unsigned long max_mask)
+  {
+    SSL_set_options(this->ssl, proto_mask);
+    SSL_clear_options(this->ssl, max_mask & ~proto_mask);
+  }
+
 protected:
   const IpEndpoint &
   _getLocalEndpoint() override
@@ -480,6 +486,8 @@ protected:
     return local_addr;
   }
 
+  void _fire_ssl_servername_event() override;
+
 private:
   std::string_view map_tls_protocol_to_tag(const char *proto_string) const;
   bool update_rbio(bool move_to_socket);
@@ -526,7 +534,6 @@ private:
   X509_STORE_CTX *verify_cert = nullptr;
 
   // Null-terminated string, or nullptr if there is no SNI server name.
-  std::unique_ptr<char[]> _serverName;
   std::unique_ptr<char[]> _ca_cert_file;
   std::unique_ptr<char[]> _ca_cert_dir;
 
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 1f525df..f4eaaa8 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -215,6 +215,7 @@ SSLNetVConnection::_bindSSLObject()
 {
   SSLNetVCAttach(this->ssl, this);
   TLSSessionResumptionSupport::bind(this->ssl, this);
+  TLSSNISupport::bind(this->ssl, this);
 }
 
 void
@@ -222,6 +223,7 @@ SSLNetVConnection::_unbindSSLObject()
 {
   SSLNetVCDetach(this->ssl);
   TLSSessionResumptionSupport::unbind(this->ssl);
+  TLSSNISupport::unbind(this->ssl);
 }
 
 static void
@@ -928,7 +930,6 @@ SSLNetVConnection::do_io_close(int lerrno)
 void
 SSLNetVConnection::clear()
 {
-  _serverName.reset();
   _ca_cert_file.reset();
   _ca_cert_dir.reset();
 
@@ -938,6 +939,7 @@ SSLNetVConnection::clear()
   }
   ALPNSupport::clear();
   TLSSessionResumptionSupport::clear();
+  TLSSNISupport::_clear();
 
   sslHandshakeStatus          = SSL_HANDSHAKE_ONGOING;
   sslHandshakeBeginTime       = 0;
@@ -1918,14 +1920,9 @@ SSLNetVConnection::protocol_contains(std::string_view prefix) const
 }
 
 void
-SSLNetVConnection::set_server_name(std::string_view name)
+SSLNetVConnection::_fire_ssl_servername_event()
 {
-  if (name.size()) {
-    char *n = new char[name.size() + 1];
-    std::memcpy(n, name.data(), name.size());
-    n[name.size()] = '\0';
-    _serverName.reset(n);
-  }
+  this->callHooks(TS_EVENT_SSL_SERVERNAME);
 }
 
 void
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index 54a9d35..d34d6b8 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -347,76 +347,31 @@ ssl_verify_client_callback(int preverify_ok, X509_STORE_CTX *ctx)
   return preverify_ok;
 }
 
-static int
-PerformAction(Continuation *cont, const char *servername)
-{
-  SNIConfig::scoped_config params;
-  if (const auto &actions = params->get(servername); !actions.first) {
-    Debug("ssl_sni", "%s not available in the map", servername);
-  } else {
-    for (auto &&item : *actions.first) {
-      auto ret = item->SNIAction(cont, actions.second);
-      if (ret != SSL_TLSEXT_ERR_OK) {
-        return ret;
-      }
-    }
-  }
-  return SSL_TLSEXT_ERR_OK;
-}
-
 #if TS_USE_HELLO_CB
 // Pausable callback
 static int
 ssl_client_hello_callback(SSL *s, int *al, void *arg)
 {
-  SSLNetVConnection *netvc = SSLNetVCAccess(s);
-  const char *servername   = nullptr;
-  const unsigned char *p;
-  size_t remaining, len;
-
-  if (!netvc || netvc->ssl != s) {
-    Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc");
+  TLSSNISupport *snis = TLSSNISupport::getInstance(s);
+  if (snis) {
+    snis->on_client_hello(s, al, arg);
+    int ret = snis->perform_sni_action();
+    if (ret != SSL_TLSEXT_ERR_OK) {
+      return SSL_CLIENT_HELLO_ERROR;
+    }
+  } else {
+    // This error suggests either of these:
+    // 1) Call back on unsupported netvc -- Don't register callback unnecessarily
+    // 2) Call back on stale netvc
+    Debug("ssl.error", "ssl_client_hello_callback was called unexpectedly");
     return SSL_CLIENT_HELLO_ERROR;
   }
 
-  // Parse the server name if the get extension call succeeds and there are more than 2 bytes to parse
-  if (SSL_client_hello_get0_ext(s, TLSEXT_TYPE_server_name, &p, &remaining) && remaining > 2) {
-    // Parse to get to the name, originally from test/handshake_helper.c in openssl tree
-    /* Extract the length of the supplied list of names. */
-    len = *(p++) << 8;
-    len += *(p++);
-    if (len + 2 == remaining) {
-      remaining = len;
-      /*
-       * The list in practice only has a single element, so we only consider
-       * the first one.
-       */
-      if (*p++ == TLSEXT_NAMETYPE_host_name) {
-        remaining--;
-        /* Now we can finally pull out the byte array with the actual hostname. */
-        if (remaining > 2) {
-          len = *(p++) << 8;
-          len += *(p++);
-          if (len + 2 <= remaining) {
-            servername = reinterpret_cast<const char *>(p);
-          }
-        }
-      }
-    }
-  }
-  if (servername) {
-    netvc->set_server_name(std::string_view(servername, len));
-  }
-  int ret = PerformAction(netvc, netvc->get_server_name());
-  if (ret != SSL_TLSEXT_ERR_OK) {
+  SSLNetVConnection *netvc = SSLNetVCAccess(s);
+  if (!netvc || netvc->ssl != s) {
+    Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc");
     return SSL_CLIENT_HELLO_ERROR;
   }
-  if (netvc->has_tunnel_destination() && !netvc->decrypt_tunnel()) {
-    netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
-  }
-  if (netvc->protocol_mask_set) {
-    setTLSValidProtocols(s, netvc->protocol_mask, TLSValidProtocols::max_mask);
-  }
 
   bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO);
 
@@ -473,32 +428,26 @@ ssl_cert_callback(SSL *ssl, void * /*arg*/)
  * Cannot stop this callback. Always reeneabled
  */
 static int
-ssl_servername_callback(SSL *ssl, int * /* ad */, void * /*arg*/)
+ssl_servername_callback(SSL *ssl, int *al, void *arg)
 {
-  SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
-
-  if (!netvc || netvc->ssl != ssl) {
-    Debug("ssl.error", "ssl_servername_callback call back on stale netvc");
-    return SSL_TLSEXT_ERR_ALERT_FATAL;
-  }
-
-  netvc->callHooks(TS_EVENT_SSL_SERVERNAME);
-
-  const char *name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
-  if (name) {
-    netvc->set_server_name(name);
-  }
-
+  TLSSNISupport *snis = TLSSNISupport::getInstance(ssl);
+  if (snis) {
+    snis->on_servername(ssl, al, arg);
 #if !TS_USE_HELLO_CB
-  // Only call the SNI actions here if not already performed in the HELLO_CB
-  int ret = PerformAction(netvc, netvc->get_server_name());
-  if (ret != SSL_TLSEXT_ERR_OK) {
-    return SSL_TLSEXT_ERR_ALERT_FATAL;
-  }
+    // Only call the SNI actions here if not already performed in the HELLO_CB
+    int ret = snis->perform_sni_action();
+    if (ret != SSL_TLSEXT_ERR_OK) {
+      return SSL_TLSEXT_ERR_ALERT_FATAL;
+    }
 #endif
-  if (netvc->has_tunnel_destination() && !netvc->decrypt_tunnel()) {
-    netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+  } else {
+    // This error suggests either of these:
+    // 1) Call back on unsupported netvc -- Don't register callback unnecessarily
+    // 2) Call back on stale netvc
+    Debug("ssl.error", "ssl_servername_callback was called unexpectedly");
+    return SSL_TLSEXT_ERR_ALERT_FATAL;
   }
+
   return SSL_TLSEXT_ERR_OK;
 }
 
@@ -912,6 +861,7 @@ SSLInitializeLibrary()
   ssl_vc_index = SSL_get_ex_new_index(0, (void *)"NetVC index", nullptr, nullptr, nullptr);
 
   TLSSessionResumptionSupport::initialize();
+  TLSSNISupport::initialize();
 
   open_ssl_initialized = true;
 }
@@ -1125,13 +1075,6 @@ SSLMultiCertConfigLoader::_set_handshake_callbacks(SSL_CTX *ctx)
 }
 
 void
-setTLSValidProtocols(SSL *ssl, unsigned long proto_mask, unsigned long max_mask)
-{
-  SSL_set_options(ssl, proto_mask);
-  SSL_clear_options(ssl, max_mask & ~proto_mask);
-}
-
-void
 setClientCertLevel(SSL *ssl, uint8_t certLevel)
 {
   SSLConfig::scoped_config params;
diff --git a/iocore/net/TLSSNISupport.cc b/iocore/net/TLSSNISupport.cc
new file mode 100644
index 0000000..a7b883f
--- /dev/null
+++ b/iocore/net/TLSSNISupport.cc
@@ -0,0 +1,145 @@
+/** @file
+
+  SNISupport.cc provides implmentations for SNISupport methods
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+#include "TLSSNISupport.h"
+#include "tscore/ink_assert.h"
+#include "tscore/Diags.h"
+#include "P_SSLSNI.h"
+
+int TLSSNISupport::_ex_data_index = -1;
+
+void
+TLSSNISupport::initialize()
+{
+  ink_assert(_ex_data_index == -1);
+  if (_ex_data_index == -1) {
+    _ex_data_index = SSL_get_ex_new_index(0, (void *)"TLSSNISupport index", nullptr, nullptr, nullptr);
+  }
+}
+
+TLSSNISupport *
+TLSSNISupport::getInstance(SSL *ssl)
+{
+  return static_cast<TLSSNISupport *>(SSL_get_ex_data(ssl, _ex_data_index));
+}
+
+void
+TLSSNISupport::bind(SSL *ssl, TLSSNISupport *snis)
+{
+  SSL_set_ex_data(ssl, _ex_data_index, snis);
+}
+
+void
+TLSSNISupport::unbind(SSL *ssl)
+{
+  SSL_set_ex_data(ssl, _ex_data_index, nullptr);
+}
+
+int
+TLSSNISupport::perform_sni_action()
+{
+  const char *servername = this->_get_sni_server_name();
+  SNIConfig::scoped_config params;
+  if (const auto &actions = params->get(servername); !actions.first) {
+    Debug("ssl_sni", "%s not available in the map", servername);
+  } else {
+    for (auto &&item : *actions.first) {
+      auto ret = item->SNIAction(this, actions.second);
+      if (ret != SSL_TLSEXT_ERR_OK) {
+        return ret;
+      }
+    }
+  }
+  return SSL_TLSEXT_ERR_OK;
+}
+
+#if TS_USE_HELLO_CB
+void
+TLSSNISupport::on_client_hello(SSL *ssl, int *al, void *arg)
+{
+  const char *servername = nullptr;
+  const unsigned char *p;
+  size_t remaining, len;
+  // Parse the server name if the get extension call succeeds and there are more than 2 bytes to parse
+  if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &p, &remaining) && remaining > 2) {
+    // Parse to get to the name, originally from test/handshake_helper.c in openssl tree
+    /* Extract the length of the supplied list of names. */
+    len = *(p++) << 8;
+    len += *(p++);
+    if (len + 2 == remaining) {
+      remaining = len;
+      /*
+       * The list in practice only has a single element, so we only consider
+       * the first one.
+       */
+      if (*p++ == TLSEXT_NAMETYPE_host_name) {
+        remaining--;
+        /* Now we can finally pull out the byte array with the actual hostname. */
+        if (remaining > 2) {
+          len = *(p++) << 8;
+          len += *(p++);
+          if (len + 2 <= remaining) {
+            servername = reinterpret_cast<const char *>(p);
+          }
+        }
+      }
+    }
+  }
+  if (servername) {
+    this->_set_sni_server_name(std::string_view(servername, len));
+  }
+}
+#endif
+
+void
+TLSSNISupport::on_servername(SSL *ssl, int *al, void *arg)
+{
+  this->_fire_ssl_servername_event();
+
+  const char *name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+  if (name) {
+    this->_set_sni_server_name(name);
+  }
+}
+
+void
+TLSSNISupport::_clear()
+{
+  _sni_server_name.reset();
+}
+
+const char *
+TLSSNISupport::_get_sni_server_name() const
+{
+  return _sni_server_name.get() ? _sni_server_name.get() : "";
+}
+
+void
+TLSSNISupport::_set_sni_server_name(std::string_view name)
+{
+  if (name.size()) {
+    char *n = new char[name.size() + 1];
+    std::memcpy(n, name.data(), name.size());
+    n[name.size()] = '\0';
+    _sni_server_name.reset(n);
+  }
+}
diff --git a/iocore/net/TLSSNISupport.h b/iocore/net/TLSSNISupport.h
new file mode 100644
index 0000000..8257202
--- /dev/null
+++ b/iocore/net/TLSSNISupport.h
@@ -0,0 +1,61 @@
+/** @file
+
+  TLSSNISupport implements common methods and members to
+  support protocols for Server Name Indication
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+#pragma once
+
+#include <string_view>
+#include <memory>
+#include <openssl/ssl.h>
+#include "tscore/ink_config.h"
+
+class TLSSNISupport
+{
+public:
+  virtual ~TLSSNISupport() = default;
+
+  static void initialize();
+  static TLSSNISupport *getInstance(SSL *ssl);
+  static void bind(SSL *ssl, TLSSNISupport *snis);
+  static void unbind(SSL *ssl);
+
+  int perform_sni_action();
+  // Callback functions for OpenSSL libraries
+#if TS_USE_HELLO_CB
+  void on_client_hello(SSL *ssl, int *al, void *arg);
+#endif
+  void on_servername(SSL *ssl, int *al, void *arg);
+
+protected:
+  virtual void _fire_ssl_servername_event() = 0;
+
+  void _clear();
+  const char *_get_sni_server_name() const;
+
+private:
+  static int _ex_data_index;
+
+  // Null-terminated string, or nullptr if there is no SNI server name.
+  std::unique_ptr<char[]> _sni_server_name;
+
+  void _set_sni_server_name(std::string_view name);
+};