This is an automated email from the ASF dual-hosted git repository.
maskit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 5bb84630f Add TLSCertSwitchSupport (#9322)
5bb84630f is described below
commit 5bb84630f103c0f75c3ff936db9ed5e798856633
Author: Masakazu Kitajo <[email protected]>
AuthorDate: Tue Mar 14 11:11:01 2023 -0600
Add TLSCertSwitchSupport (#9322)
* Add TLSCertSwitchSupport
This is a preparation for enabling cert switching on QUIC connections.
* Fix an invalid cast
---
iocore/net/Makefile.am | 1 +
iocore/net/P_SSLNetVConnection.h | 6 +
iocore/net/SSLNetVConnection.cc | 74 +++++++++++++
iocore/net/SSLUtils.cc | 220 ++++++++++++-------------------------
iocore/net/TLSCertSwitchSupport.cc | 103 +++++++++++++++++
iocore/net/TLSCertSwitchSupport.h | 51 +++++++++
6 files changed, 307 insertions(+), 148 deletions(-)
diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am
index fac842765..fd5026e87 100644
--- a/iocore/net/Makefile.am
+++ b/iocore/net/Makefile.am
@@ -200,6 +200,7 @@ libinknet_a_SOURCES = \
TLSSessionResumptionSupport.cc \
TLSSNISupport.cc \
TLSTunnelSupport.cc \
+ TLSCertSwitchSupport.cc \
UDPIOEvent.cc \
UnixConnection.cc \
UnixNet.cc \
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index ca6f8b17a..6fcacdf72 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -52,6 +52,7 @@
#include "TLSEarlyDataSupport.h"
#include "TLSTunnelSupport.h"
#include "TLSBasicSupport.h"
+#include "TLSCertSwitchSupport.h"
#include "P_SSLUtils.h"
#include "P_SSLConfig.h"
@@ -103,6 +104,7 @@ class SSLNetVConnection : public UnixNetVConnection,
public TLSSNISupport,
public TLSEarlyDataSupport,
public TLSTunnelSupport,
+ public TLSCertSwitchSupport,
public TLSBasicSupport
{
typedef UnixNetVConnection super; ///< Parent type.
@@ -410,6 +412,10 @@ protected:
void _fire_ssl_servername_event() override;
+ bool _isTryingRenegotiation() const override;
+ shared_SSL_CTX _lookupContextByName(const std::string &servername,
SSLCertContextType ctxType) override;
+ shared_SSL_CTX _lookupContextByIP() override;
+
private:
std::string_view map_tls_protocol_to_tag(const char *proto_string) const;
bool update_rbio(bool move_to_socket);
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index f7fb137aa..48546217c 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -225,6 +225,7 @@ SSLNetVConnection::_bindSSLObject()
TLSSNISupport::bind(this->ssl, this);
TLSEarlyDataSupport::bind(this->ssl, this);
TLSTunnelSupport::bind(this->ssl, this);
+ TLSCertSwitchSupport::bind(this->ssl, this);
}
void
@@ -237,6 +238,7 @@ SSLNetVConnection::_unbindSSLObject()
TLSSNISupport::unbind(this->ssl);
TLSEarlyDataSupport::unbind(this->ssl);
TLSTunnelSupport::unbind(this->ssl);
+ TLSCertSwitchSupport::unbind(this->ssl);
}
static void
@@ -971,6 +973,7 @@ SSLNetVConnection::clear()
TLSSessionResumptionSupport::clear();
TLSSNISupport::_clear();
TLSTunnelSupport::_clear();
+ TLSCertSwitchSupport::_clear();
sslHandshakeStatus = SSL_HANDSHAKE_ONGOING;
sslLastWriteTime = 0;
@@ -1999,6 +2002,77 @@ SSLNetVConnection::_fire_ssl_servername_event()
this->callHooks(TS_EVENT_SSL_SERVERNAME);
}
+bool
+SSLNetVConnection::_isTryingRenegotiation() const
+{
+ if (SSLConfigParams::ssl_allow_client_renegotiation == false &&
this->getSSLHandShakeComplete()) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+shared_SSL_CTX
+SSLNetVConnection::_lookupContextByName(const std::string &servername,
SSLCertContextType ctxType)
+{
+ shared_SSL_CTX ctx = nullptr;
+ SSLCertificateConfig::scoped_config lookup;
+ SSLCertContext *cc = lookup->find(servername, ctxType);
+
+ if (cc) {
+ ctx = cc->getCtx();
+ }
+
+ if (cc && ctx && SSLCertContextOption::OPT_TUNNEL == cc->opt &&
this->get_is_transparent()) {
+ this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+ this->setSSLHandShakeComplete(SSL_HANDSHAKE_DONE);
+ return nullptr;
+ } else {
+ return ctx;
+ }
+}
+
+shared_SSL_CTX
+SSLNetVConnection::_lookupContextByIP()
+{
+ shared_SSL_CTX ctx = nullptr;
+ SSLCertificateConfig::scoped_config lookup;
+ IpEndpoint ip;
+ int namelen = sizeof(ip);
+
+ // Return null if this vc is already configured as a tunnel
+ if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL) {
+ return nullptr;
+ }
+
+ SSLCertContext *cc = nullptr;
+ if (this->get_is_proxy_protocol() && this->get_proxy_protocol_version() !=
ProxyProtocolVersion::UNDEFINED) {
+ ip.sa = *(this->get_proxy_protocol_dst_addr());
+ ip_port_text_buffer ipb1;
+ ats_ip_nptop(&ip, ipb1, sizeof(ipb1));
+ cc = lookup->find(ip);
+ if (is_debug_tag_set("proxyprotocol")) {
+ IpEndpoint src;
+ ip_port_text_buffer ipb2;
+ int ip_len = sizeof(src);
+
+ if (0 != safe_getpeername(this->get_socket(), &src.sa, &ip_len)) {
+ Debug("proxyprotocol", "Failed to get src ip, errno = [%d]", errno);
+ return nullptr;
+ }
+ ats_ip_nptop(&src, ipb2, sizeof(ipb2));
+ Debug("proxyprotocol", "IP context is %p for [%s] -> [%s], default
context %p", cc, ipb2, ipb1, lookup->defaultContext());
+ }
+ } else if (0 == safe_getsockname(this->get_socket(), &ip.sa, &namelen)) {
+ cc = lookup->find(ip);
+ }
+ if (cc) {
+ ctx = cc->getCtx();
+ }
+
+ return ctx;
+}
+
void
SSLNetVConnection::set_ca_cert_file(std::string_view file, std::string_view
dir)
{
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index 11a2d2d0a..8920e17ca 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -261,118 +261,6 @@ ssl_rm_cached_session(SSL_CTX *ctx, SSL_SESSION *sess)
session_cache->removeSession(sid);
}
-static int
-set_context_cert(SSL *ssl, void *arg)
-{
- shared_SSL_CTX ctx = nullptr;
- SSL_CTX *verify_ctx = nullptr;
- SSLCertContext *cc = nullptr;
- SSLCertificateConfig::scoped_config lookup;
-
- const char *servername = SSL_get_servername(ssl,
TLSEXT_NAMETYPE_host_name);
- SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
- bool found = true;
- int retval = 1;
- SSLCertContextType ctxType = SSLCertContextType::GENERIC;
-
- if (!netvc || netvc->ssl != ssl) {
- Debug("ssl.error", "set_context_cert call back on stale netvc");
- retval = 0; // Error
- goto done;
- }
-
- Debug("ssl_load", "set_context_cert ssl=%p server=%s handshake_complete=%d",
ssl, servername, netvc->getSSLHandShakeComplete());
-
- // catch the client renegotiation early on
- if (SSLConfigParams::ssl_allow_client_renegotiation == false &&
netvc->getSSLHandShakeComplete()) {
- Debug("ssl_load", "set_context_cert trying to renegotiate from the
client");
- retval = 0; // Error
- goto done;
- }
-
-#ifdef OPENSSL_IS_BORINGSSL
- if (arg != nullptr) {
- const SSL_CLIENT_HELLO *client_hello = (const SSL_CLIENT_HELLO *)arg;
- const bool client_ecdsa_capable =
BoringSSLUtils::isClientEcdsaCapable(client_hello);
- ctxType = client_ecdsa_capable ?
SSLCertContextType::EC : SSLCertContextType::RSA;
- }
-#endif
-
- // The incoming SSL_CTX is either the one mapped from the inbound IP address
or the default one. If we
- // don't find a name-based match at this point, we *do not* want to mess
with the context because we've
- // already made a best effort to find the best match.
- if (likely(servername)) {
- cc = lookup->find(servername, ctxType);
- if (cc) {
- ctx = cc->getCtx();
- }
- if (cc && ctx && SSLCertContextOption::OPT_TUNNEL == cc->opt &&
netvc->get_is_transparent()) {
- netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
- netvc->setSSLHandShakeComplete(SSL_HANDSHAKE_DONE);
- retval = -1;
- goto done;
- }
- }
-
- // If there's no match on the server name, try to match on the peer address.
- if (ctx == nullptr) {
- IpEndpoint ip;
- int namelen = sizeof(ip);
-
- if (netvc->get_is_proxy_protocol() && netvc->get_proxy_protocol_version()
!= ProxyProtocolVersion::UNDEFINED) {
- ip.sa = *(netvc->get_proxy_protocol_dst_addr());
- ip_port_text_buffer ipb1;
- ats_ip_nptop(&ip, ipb1, sizeof(ipb1));
- cc = lookup->find(ip);
- if (is_debug_tag_set("proxyprotocol")) {
- IpEndpoint src;
- ip_port_text_buffer ipb2;
- int ip_len = sizeof(src);
-
- if (0 != safe_getpeername(netvc->get_socket(), &src.sa, &ip_len)) {
- Debug("proxyprotocol", "Failed to get src ip, errno = [%d]", errno);
- return EVENT_ERROR;
- }
- ats_ip_nptop(&src, ipb2, sizeof(ipb2));
- Debug("proxyprotocol", "IP context is %p for [%s] -> [%s], default
context %p", cc, ipb2, ipb1, lookup->defaultContext());
- }
- } else if (0 == safe_getsockname(netvc->get_socket(), &ip.sa, &namelen)) {
- cc = lookup->find(ip);
- }
- if (cc) {
- ctx = cc->getCtx();
- }
- }
-
- if (ctx != nullptr) {
- SSL_set_SSL_CTX(ssl, ctx.get());
-#if TS_HAS_TLS_SESSION_TICKET
- // Reset the ticket callback if needed
-#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
- SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx.get(),
ssl_callback_session_ticket);
-#else
- SSL_CTX_set_tlsext_ticket_key_cb(ctx.get(), ssl_callback_session_ticket);
-#endif
-#endif
- // After replacing the SSL_CTX, make sure the overridden ca_cert_file is
still set
- setClientCertCACerts(ssl, netvc->get_ca_cert_file(),
netvc->get_ca_cert_dir());
- } else {
- found = false;
- }
-
- verify_ctx = SSL_get_SSL_CTX(ssl);
- // set_context_cert found SSL context for ...
- Debug("ssl_load", "ssl_cert_callback %s SSL context %p for requested name
'%s'", found ? "found" : "using", verify_ctx,
- servername);
-
- if (verify_ctx == nullptr) {
- retval = 0;
- goto done;
- }
-done:
- return retval;
-}
-
// Callback function for verifying client certificate
static int
ssl_verify_client_callback(int preverify_ok, X509_STORE_CTX *ctx)
@@ -418,17 +306,19 @@ ssl_client_hello_callback(SSL *s, int *al, void *arg)
return SSL_CLIENT_HELLO_ERROR;
}
- 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;
- }
-
- bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO);
+ SSLNetVConnection *netvc = dynamic_cast<SSLNetVConnection *>(snis);
+ if (netvc) {
+ if (netvc->ssl != s) {
+ Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc");
+ return SSL_CLIENT_HELLO_ERROR;
+ }
- if (!reenabled) {
- return SSL_CLIENT_HELLO_RETRY;
+ bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO);
+ if (!reenabled) {
+ return SSL_CLIENT_HELLO_RETRY;
+ }
}
+
return SSL_CLIENT_HELLO_SUCCESS;
}
#elif defined(OPENSSL_IS_BORINGSSL)
@@ -452,17 +342,19 @@ ssl_client_hello_callback(const SSL_CLIENT_HELLO
*client_hello)
return ssl_select_cert_error;
}
- SSLNetVConnection *netvc = SSLNetVCAccess(s);
- if (!netvc || netvc->ssl != s) {
- Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc");
- return ssl_select_cert_error;
- }
-
- bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO);
+ SSLNetVConnection *netvc = dynamic_cast<SSLNetVConnection *>(snis);
+ if (netvc) {
+ if (netvc->ssl != s) {
+ Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc");
+ return ssl_select_cert_error;
+ }
- if (!reenabled) {
- return ssl_select_cert_retry;
+ bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO);
+ if (!reenabled) {
+ return ssl_select_cert_retry;
+ }
}
+
return ssl_select_cert_success;
}
#endif
@@ -474,16 +366,13 @@ ssl_client_hello_callback(const SSL_CLIENT_HELLO
*client_hello)
static int
ssl_cert_callback(SSL *ssl, void *arg)
{
- SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
+ TLSCertSwitchSupport *tcss = TLSCertSwitchSupport::getInstance(ssl);
+ SSLNetVConnection *sslnetvc = dynamic_cast<SSLNetVConnection *>(tcss);
bool reenabled;
int retval = 1;
- if (!netvc || netvc->ssl != ssl) {
- Debug("ssl.error", "ssl_cert_callback call back on stale netvc");
- return 0;
- }
-
// If we are in tunnel mode, don't select a cert. Pause!
+ NetVConnection *netvc = reinterpret_cast<NetVConnection *>(sslnetvc);
if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == netvc->attributes) {
#ifdef OPENSSL_IS_BORINGSSL
return -2; // Retry
@@ -492,22 +381,56 @@ ssl_cert_callback(SSL *ssl, void *arg)
#endif
}
- // Do the common certificate lookup only once. If we pause
- // and restart processing, do not execute the common logic again
- if (!netvc->calledHooks(TS_EVENT_SSL_CERT)) {
- retval = set_context_cert(ssl, arg);
- if (retval != 1) {
- return retval;
+ SSLCertContextType ctxType = SSLCertContextType::GENERIC;
+#ifdef OPENSSL_IS_BORINGSSL
+ if (arg != nullptr) {
+ const SSL_CLIENT_HELLO *client_hello = (const SSL_CLIENT_HELLO *)arg;
+ const bool client_ecdsa_capable =
BoringSSLUtils::isClientEcdsaCapable(client_hello);
+ ctxType = client_ecdsa_capable ?
SSLCertContextType::EC : SSLCertContextType::RSA;
+ }
+#endif
+
+ if (sslnetvc) {
+ // Do the common certificate lookup only once. If we pause
+ // and restart processing, do not execute the common logic again
+ if (!sslnetvc->calledHooks(TS_EVENT_SSL_CERT)) {
+ retval = sslnetvc->selectCertificate(ssl, ctxType);
+ if (retval != 1) {
+ return retval;
+ }
+ }
+
+ // Call the plugin cert code
+ reenabled = sslnetvc->callHooks(TS_EVENT_SSL_CERT);
+ // If it did not re-enable, return the code to
+ // stop the accept processing
+ if (!reenabled) {
+ retval = -1; // Pause
+ }
+ } else {
+ if (tcss->selectCertificate(ssl, ctxType) == 1) {
+ retval = 1;
+ } else {
+ retval = 0;
}
}
- // Call the plugin cert code
- reenabled = netvc->callHooks(TS_EVENT_SSL_CERT);
- // If it did not re-enable, return the code to
- // stop the accept processing
- if (!reenabled) {
- retval = -1; // Pause
+#if TS_HAS_TLS_SESSION_TICKET
+ if (retval == 1) {
+ // After replacing the SSL_CTX, make sure the overridden ca_cert_file is
still set
+ if (sslnetvc) {
+ setClientCertCACerts(ssl, sslnetvc->get_ca_cert_file(),
sslnetvc->get_ca_cert_dir());
+ }
+
+ // Reset the ticket callback if needed
+ SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
+#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
+ SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket);
+#else
+ SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket);
+#endif
}
+#endif
// Return 1 for success, 0 for error, or -1 to pause
return retval;
@@ -1020,6 +943,7 @@ SSLInitializeLibrary()
TLSSNISupport::initialize();
TLSEarlyDataSupport::initialize();
TLSTunnelSupport::initialize();
+ TLSCertSwitchSupport::initialize();
open_ssl_initialized = true;
}
diff --git a/iocore/net/TLSCertSwitchSupport.cc
b/iocore/net/TLSCertSwitchSupport.cc
new file mode 100644
index 000000000..4ee68605d
--- /dev/null
+++ b/iocore/net/TLSCertSwitchSupport.cc
@@ -0,0 +1,103 @@
+/** @file
+
+ @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 "TLSCertSwitchSupport.h"
+#include "P_SSLCertLookup.h"
+
+int TLSCertSwitchSupport::_ex_data_index = -1;
+
+void
+TLSCertSwitchSupport::initialize()
+{
+ ink_assert(_ex_data_index == -1);
+ if (_ex_data_index == -1) {
+ _ex_data_index = SSL_get_ex_new_index(0, (void *)"TLSEarlyDataSupport
index", nullptr, nullptr, nullptr);
+ }
+}
+
+TLSCertSwitchSupport *
+TLSCertSwitchSupport::getInstance(SSL *ssl)
+{
+ return static_cast<TLSCertSwitchSupport *>(SSL_get_ex_data(ssl,
_ex_data_index));
+}
+
+void
+TLSCertSwitchSupport::bind(SSL *ssl, TLSCertSwitchSupport *tcss)
+{
+ SSL_set_ex_data(ssl, _ex_data_index, tcss);
+}
+
+void
+TLSCertSwitchSupport::unbind(SSL *ssl)
+{
+ SSL_set_ex_data(ssl, _ex_data_index, nullptr);
+}
+
+void
+TLSCertSwitchSupport::_clear()
+{
+}
+
+int
+TLSCertSwitchSupport::selectCertificate(SSL *ssl, SSLCertContextType ctxType)
+{
+ shared_SSL_CTX ctx = nullptr;
+
+ const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ bool found = true;
+
+ Debug("ssl", "set_context_cert ssl=%p server=%s", ssl, servername);
+
+ // catch the client renegotiation early on
+ if (this->_isTryingRenegotiation()) {
+ Debug("ssl_load", "set_context_cert trying to renegotiate from the
client");
+ return 0;
+ }
+
+ // The incoming SSL_CTX is either the one mapped from the inbound IP address
or the default one. If we
+ // don't find a name-based match at this point, we *do not* want to mess
with the context because we've
+ // already made a best effort to find the best match.
+ if (likely(servername)) {
+ ctx = this->_lookupContextByName(servername, ctxType);
+ }
+
+ // If there's no match on the server name, try to match on the peer address.
+ if (ctx == nullptr) {
+ ctx = this->_lookupContextByIP();
+ }
+
+ if (ctx != nullptr) {
+ SSL_set_SSL_CTX(ssl, ctx.get());
+ } else {
+ found = false;
+ }
+
+ SSL_CTX *verify_ctx = SSL_get_SSL_CTX(ssl);
+ // set_context_cert found SSL context for ...
+ Debug("ssl_load", "ssl_cert_callback %s SSL context %p for requested name
'%s'", found ? "found" : "using", verify_ctx,
+ servername);
+
+ if (verify_ctx == nullptr) {
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/iocore/net/TLSCertSwitchSupport.h
b/iocore/net/TLSCertSwitchSupport.h
new file mode 100644
index 000000000..451933f5f
--- /dev/null
+++ b/iocore/net/TLSCertSwitchSupport.h
@@ -0,0 +1,51 @@
+/** @file
+
+ TLSCertSwitchSupport implements common methods and members to
+ support switching certificate
+
+ @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 <openssl/ssl.h>
+#include "P_SSLCertLookup.h"
+
+class TLSCertSwitchSupport
+{
+public:
+ virtual ~TLSCertSwitchSupport() = default;
+
+ static void initialize();
+ static TLSCertSwitchSupport *getInstance(SSL *ssl);
+ static void bind(SSL *ssl, TLSCertSwitchSupport *tcss);
+ static void unbind(SSL *ssl);
+
+ int selectCertificate(SSL *ssl, SSLCertContextType ctxType);
+
+protected:
+ void _clear();
+
+ virtual bool _isTryingRenegotiation() const
= 0;
+ virtual shared_SSL_CTX _lookupContextByName(const std::string &servername,
SSLCertContextType ctxType) = 0;
+ virtual shared_SSL_CTX _lookupContextByIP()
= 0;
+
+private:
+ static int _ex_data_index;
+};