You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2014/09/22 21:13:12 UTC
[2/2] git commit: TS-3006: Add SSL extensions and examples.
TS-3006: Add SSL extensions and examples.
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/044da699
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/044da699
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/044da699
Branch: refs/heads/master
Commit: 044da6999442449434b282d8b537d8858505bbfc
Parents: 2f6d6e0
Author: Susan Hinrichs <sh...@network-geographics.com>
Authored: Mon Sep 22 14:12:15 2014 -0500
Committer: Alan M. Carroll <am...@apache.org>
Committed: Mon Sep 22 14:12:15 2014 -0500
----------------------------------------------------------------------
CHANGES | 2 +
build/plugins.mk | 3 +-
configure.ac | 1 +
example/Makefile.am | 6 +
example/ssl-preaccept/ats-util.h | 40 ++
example/ssl-preaccept/ssl-preaccept.cc | 197 +++++++
example/ssl-preaccept/ssl_preaccept.config | 7 +
example/ssl-sni-whitelist/ssl-sni-whitelist.cc | 141 +++++
.../ssl-sni-whitelist/ssl_sni_whitelist.config | 3 +
example/ssl-sni/ssl-sni.cc | 162 ++++++
example/ssl-sni/ssl_sni.config | 7 +
iocore/net/Makefile.am | 4 +-
iocore/net/OCSPStapling.cc | 31 +-
iocore/net/P_SSLCertLookup.h | 53 +-
iocore/net/P_SSLNetVConnection.h | 56 ++
iocore/net/SSLCertLookup.cc | 171 +++---
iocore/net/SSLNetVConnection.cc | 508 +++++++++++++++--
iocore/net/SSLUtils.cc | 110 +++-
iocore/net/UnixNetVConnection.cc | 6 +-
lib/records/I_RecHttp.h | 9 +-
lib/ts/Vec.h | 7 +
lib/ts/apidefs.h.in | 20 +
lib/ts/ink_sock.cc | 10 +
lib/ts/ink_sock.h | 1 +
plugins/experimental/Makefile.am | 1 +
.../experimental/ssl_cert_loader/Makefile.am | 26 +
plugins/experimental/ssl_cert_loader/ats-util.h | 40 ++
.../experimental/ssl_cert_loader/domain-tree.cc | 168 ++++++
.../experimental/ssl_cert_loader/domain-tree.h | 66 +++
.../ssl_cert_loader/ssl-cert-loader.cc | 541 +++++++++++++++++++
.../ssl_cert_loader/ssl_cert_loader.cfg | 135 +++++
.../experimental/ssl_cert_loader/ssl_start.cfg | 55 ++
proxy/InkAPI.cc | 129 ++++-
proxy/InkAPIInternal.h | 12 +
proxy/api/ts/ts.h | 15 +
proxy/config/ssl_multicert.config.default | 14 +-
proxy/http/HttpDebugNames.cc | 4 +
proxy/http/HttpSM.cc | 2 +-
proxy/http/HttpSessionAccept.cc | 6 +-
proxy/http/HttpSessionAccept.h | 1 +
proxy/http/HttpTransact.cc | 8 +-
proxy/http/HttpTunnel.cc | 3 +-
42 files changed, 2620 insertions(+), 161 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index d79dadb..446f140 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,7 @@
-*- coding: utf-8 -*-
Changes with Apache Traffic Server 5.2.0
+ *) [TS-3006] Add SSL extensions and examples.
+ Author: Susan Hinrichs <sh...@network-geographics.com>
*) [TS-3054] Forward partial chunked data to client to be more transparent.
Author: Susan Hinrichs <sh...@network-geographics.com>
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/build/plugins.mk
----------------------------------------------------------------------
diff --git a/build/plugins.mk b/build/plugins.mk
index 560d8b9..b92eb1f 100644
--- a/build/plugins.mk
+++ b/build/plugins.mk
@@ -26,7 +26,8 @@ TS_PLUGIN_CPPFLAGS = \
-I$(top_builddir)/proxy/api \
-I$(top_srcdir)/proxy/api \
-I$(top_builddir)/lib/ts \
- -I$(top_srcdir)/lib/ts
+ -I$(top_srcdir)/lib/ts \
+ -I$(top_srcdir)/lib
# Provide a default AM_CPPFLAGS. Automake handles this correctly, but libtool
# throws an error if we try to do the same with AM_LDFLAGS. Hence, we provide
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 0785790..c3c7e0e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1942,6 +1942,7 @@ AS_IF([test "x$enable_experimental_plugins" = xyes], [
plugins/experimental/remap_stats/Makefile
plugins/experimental/s3_auth/Makefile
plugins/experimental/sslheaders/Makefile
+ plugins/experimental/ssl_cert_loader/Makefile
plugins/experimental/stale_while_revalidate/Makefile
plugins/experimental/ts_lua/Makefile
plugins/experimental/url_sig/Makefile
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/Makefile.am
----------------------------------------------------------------------
diff --git a/example/Makefile.am b/example/Makefile.am
index c57fbea..47c1d90 100644
--- a/example/Makefile.am
+++ b/example/Makefile.am
@@ -40,6 +40,9 @@ plugins = \
response-header-1.la \
secure-link.la \
server-transform.la \
+ ssl-preaccept.la \
+ ssl-sni.la \
+ ssl-sni-whitelist.la \
thread-1.la \
version.la
@@ -72,6 +75,9 @@ thread_1_la_SOURCES = thread-1/thread-1.c
psi_la_SOURCES = thread-pool/psi.c thread-pool/thread.c
version_la_SOURCES = version/version.c
secure_link_la_SOURCES = secure-link/secure-link.c
+ssl_preaccept_la_SOURCES = ssl-preaccept/ssl-preaccept.cc
+ssl_sni_la_SOURCES = ssl-sni/ssl-sni.cc
+ssl_sni_whitelist_la_SOURCES = ssl-sni-whitelist/ssl-sni-whitelist.cc
# The following examples do not build:
#
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-preaccept/ats-util.h
----------------------------------------------------------------------
diff --git a/example/ssl-preaccept/ats-util.h b/example/ssl-preaccept/ats-util.h
new file mode 100644
index 0000000..1164a20
--- /dev/null
+++ b/example/ssl-preaccept/ats-util.h
@@ -0,0 +1,40 @@
+# if !defined(_ats_util_h)
+# define _ats_util_h
+
+# if defined(__cplusplus)
+/** Set data to zero.
+
+ Calls @c memset on @a t with a value of zero and a length of @c
+ sizeof(t). This can be used on ordinary and array variables. While
+ this can be used on variables of intrinsic type it's inefficient.
+
+ @note Because this uses templates it cannot be used on unnamed or
+ locally scoped structures / classes. This is an inherent
+ limitation of templates.
+
+ Examples:
+ @code
+ foo bar; // value.
+ ink_zero(bar); // zero bar.
+
+ foo *bar; // pointer.
+ ink_zero(bar); // WRONG - makes the pointer @a bar zero.
+ ink_zero(*bar); // zero what bar points at.
+
+ foo bar[ZOMG]; // Array of structs.
+ ink_zero(bar); // Zero all structs in array.
+
+ foo *bar[ZOMG]; // array of pointers.
+ ink_zero(bar); // zero all pointers in the array.
+ @endcode
+
+ */
+template < typename T > inline void
+ink_zero(
+ T& t ///< Object to zero.
+ ) {
+ memset(&t, 0, sizeof(t));
+}
+# endif /* __cplusplus */
+
+# endif // ats-util.h
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-preaccept/ssl-preaccept.cc
----------------------------------------------------------------------
diff --git a/example/ssl-preaccept/ssl-preaccept.cc b/example/ssl-preaccept/ssl-preaccept.cc
new file mode 100644
index 0000000..9a9a7be
--- /dev/null
+++ b/example/ssl-preaccept/ssl-preaccept.cc
@@ -0,0 +1,197 @@
+/** @file
+ SSL Preaccept test plugin
+ Implements blind tunneling based on the client IP address
+ The client ip addresses are specified in the plugin's
+ config file as an array of IP addresses or IP address ranges under the
+ key "client-blind-tunnel"
+*/
+
+# include <stdio.h>
+# include <memory.h>
+# include <inttypes.h>
+# include <ts/ts.h>
+# include <tsconfig/TsValue.h>
+# include <alloca.h>
+# include <ts/ink_inet.h>
+
+using ts::config::Configuration;
+using ts::config::Value;
+
+# define PN "ssl-preaccept"
+# define PCP "[" PN " Plugin] "
+
+namespace {
+
+std::string ConfigPath;
+typedef std::pair<IpAddr, IpAddr> IpRange;
+typedef std::deque<IpRange> IpRangeQueue;
+IpRangeQueue ClientBlindTunnelIp;
+
+Configuration Config; // global configuration
+
+void
+Parse_Addr_String(ts::ConstBuffer const &text, IpRange &range) {
+ IpAddr newAddr;
+ std::string textstr(text._ptr, text._size);
+ // Is there a hyphen?
+ size_t hyphen_pos = textstr.find("-");
+ if (hyphen_pos != std::string::npos) {
+ std::string addr1 = textstr.substr(0, hyphen_pos);
+ std::string addr2 = textstr.substr(hyphen_pos+1);
+ range.first.load(ts::ConstBuffer(addr1.c_str(), addr1.length()));
+ range.second.load(ts::ConstBuffer(addr2.c_str(), addr2.length()));
+ }
+ else { // Assume it is a single address
+ newAddr.load(text);
+ range.first = newAddr;
+ range.second = newAddr;
+ }
+}
+
+/// Get a string value from a config node.
+void Load_Config_Value(Value const& parent, char const* name, IpRangeQueue &addrs) {
+ Value v = parent[name];
+ std::string zret;
+ IpRange ipRange;
+ if (v.isLiteral()) {
+ Parse_Addr_String(v.getText(), ipRange);
+ addrs.push_back(ipRange);
+ } else if (v.isContainer()) {
+ size_t i;
+ for (i = 0; i < v.childCount(); i++) {
+ std::string val_str(v[i].getText()._ptr, v[i].getText()._size);
+ Parse_Addr_String(v[i].getText(), ipRange);
+ addrs.push_back(ipRange);
+ }
+ }
+}
+
+
+int
+Load_Config_File() {
+ ts::Rv<Configuration> cv = Configuration::loadFromPath(ConfigPath.c_str());
+ if (!cv.isOK()) {
+ TSError(PCP "Failed to parse %s as TSConfig format", ConfigPath.c_str());
+ return -1;
+ }
+ Config = cv;
+ return 1;
+}
+
+int
+Load_Configuration(int argc, const char *argv[]) {
+ts::ConstBuffer text;
+ std::string s; // temp holder.
+ TSMgmtString config_path = NULL;
+
+ // get the path to the config file if one was specified
+ static char const * const CONFIG_ARG = "--config=";
+ int arg_idx;
+ for (arg_idx = 0; arg_idx < argc; arg_idx++) {
+ if (0 == memcmp(argv[arg_idx], CONFIG_ARG, strlen(CONFIG_ARG))) {
+ config_path = TSstrdup(argv[arg_idx] + strlen(CONFIG_ARG));
+ TSDebug(PN, "Found config path %s", config_path);
+ }
+ }
+ if (NULL == config_path) {
+ static char const * const DEFAULT_CONFIG_PATH = "ssl_preaccept.config";
+ config_path = TSstrdup(DEFAULT_CONFIG_PATH);
+ TSDebug(PN, "No config path set in arguments, using default: %s", DEFAULT_CONFIG_PATH);
+ }
+
+ // translate relative paths to absolute
+ if (config_path[0] != '/') {
+ ConfigPath = std::string(TSConfigDirGet()) + '/' + std::string(config_path);
+ } else {
+ ConfigPath = config_path;
+ }
+
+ // free up the path
+ TSfree(config_path);
+
+ int ret = Load_Config_File();
+ if (ret != 0) {
+ TSError(PCP "Failed to load the config file, check debug output for errata");
+ }
+
+ // Still need to use the file
+ Value root = Config.getRoot();
+ Load_Config_Value(root, "client-blind-tunnel", ClientBlindTunnelIp);
+
+ return 0;
+}
+
+int
+CB_Pre_Accept(TSCont, TSEvent event, void *edata) {
+ TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
+ IpAddr ip(TSNetVConnLocalAddrGet(ssl_vc));
+ char buff[INET6_ADDRSTRLEN];
+ IpAddr ip_client(TSNetVConnRemoteAddrGet(ssl_vc));
+ char buff2[INET6_ADDRSTRLEN];
+
+ TSDebug("skh", "Pre accept callback %p - event is %s, target address %s, client address %s"
+ , ssl_vc
+ , event == TS_EVENT_VCONN_PRE_ACCEPT ? "good" : "bad"
+ , ip.toString(buff, sizeof(buff))
+ , ip_client.toString(buff2, sizeof(buff2))
+ );
+
+ // Not the worlds most efficient address comparison. For short lists
+ // shouldn't be too bad. If the client IP is in any of the ranges,
+ // flip the tunnel to be blind tunneled instead of decrypted and proxied
+ bool proxy_tunnel = true;
+ IpRangeQueue::iterator iter;
+ for (iter = ClientBlindTunnelIp.begin(); iter != ClientBlindTunnelIp.end() && proxy_tunnel; iter++) {
+ if (ip_client >= iter->first && ip_client <= iter->second) {
+ proxy_tunnel = false;
+ }
+ }
+ if (!proxy_tunnel) {
+ TSDebug("skh", "Blind tunnel");
+ // Push everything to blind tunnel
+ TSVConnTunnel(ssl_vc);
+ }
+ else {
+ TSDebug("skh", "Proxy tunnel");
+ }
+
+ // All done, reactivate things
+ TSVConnReenable(ssl_vc);
+ return TS_SUCCESS;
+}
+
+} // Anon namespace
+
+// Called by ATS as our initialization point
+void
+TSPluginInit(int argc, const char *argv[]) {
+ bool success = false;
+ TSPluginRegistrationInfo info;
+ TSCont cb_pa = 0; // pre-accept callback continuation
+
+ info.plugin_name = const_cast<char*>("SSL Preaccept test");
+ info.vendor_name = const_cast<char*>("Network Geographics");
+ info.support_email = const_cast<char*>("shinrich@network-geographics.com");
+
+ if (TS_SUCCESS != TSPluginRegister(TS_SDK_VERSION_2_0, &info)) {
+ TSError(PCP "registration failed.");
+ } else if (TSTrafficServerVersionGetMajor() < 2) {
+ TSError(PCP "requires Traffic Server 2.0 or later.");
+ } else if (0 > Load_Configuration(argc, argv)) {
+ TSError(PCP "Failed to load config file.");
+ } else if (0 == (cb_pa = TSContCreate(&CB_Pre_Accept, TSMutexCreate()))) {
+ TSError(PCP "Failed to pre-accept callback.");
+ } else {
+ TSHttpHookAdd(TS_VCONN_PRE_ACCEPT_HOOK, cb_pa);
+ success = true;
+ }
+
+ if (!success) {
+ if (cb_pa) TSContDestroy(cb_pa);
+ TSError(PCP "not initialized");
+ }
+ TSDebug(PN, "Plugin %s", success ? "online" : "offline");
+
+ return;
+}
+
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-preaccept/ssl_preaccept.config
----------------------------------------------------------------------
diff --git a/example/ssl-preaccept/ssl_preaccept.config b/example/ssl-preaccept/ssl_preaccept.config
new file mode 100644
index 0000000..2ec52ec
--- /dev/null
+++ b/example/ssl-preaccept/ssl_preaccept.config
@@ -0,0 +1,7 @@
+
+// SSL traffic initiating from these addresses should
+// be placed into a blind tunnel. ATS should not inspect
+// the tunnel
+client-blind-tunnel = "192.168.56.145"
+
+//client-blind-tunnel = "192.168.56.0/24"
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-sni-whitelist/ssl-sni-whitelist.cc
----------------------------------------------------------------------
diff --git a/example/ssl-sni-whitelist/ssl-sni-whitelist.cc b/example/ssl-sni-whitelist/ssl-sni-whitelist.cc
new file mode 100644
index 0000000..4cd1aaa
--- /dev/null
+++ b/example/ssl-sni-whitelist/ssl-sni-whitelist.cc
@@ -0,0 +1,141 @@
+/** @file
+ SSL SNI white list plugin
+ If the server name and IP address are not in the ssl_multicert.config
+ go head and blind tunnel it.
+*/
+
+# include <stdio.h>
+# include <memory.h>
+# include <inttypes.h>
+# include <ts/ts.h>
+# include <tsconfig/TsValue.h>
+# include <alloca.h>
+# include <openssl/ssl.h>
+
+using ts::config::Configuration;
+using ts::config::Value;
+
+# define PN "ssl-sni-whitelist"
+# define PCP "[" PN " Plugin] "
+
+namespace {
+
+std::string ConfigPath;
+
+Configuration Config; // global configuration
+
+int
+Load_Config_File() {
+ ts::Rv<Configuration> cv = Configuration::loadFromPath(ConfigPath.c_str());
+ if (!cv.isOK()) {
+ TSError(PCP "Failed to parse %s as TSConfig format", ConfigPath.c_str());
+ return -1;
+ }
+ Config = cv;
+ return 1;
+}
+
+int
+Load_Configuration(int argc, const char *argv[]) {
+ts::ConstBuffer text;
+ std::string s; // temp holder.
+ TSMgmtString config_path = NULL;
+
+ // get the path to the config file if one was specified
+ static char const * const CONFIG_ARG = "--config=";
+ int arg_idx;
+ for (arg_idx = 0; arg_idx < argc; arg_idx++) {
+ if (0 == memcmp(argv[arg_idx], CONFIG_ARG, strlen(CONFIG_ARG))) {
+ config_path = TSstrdup(argv[arg_idx] + strlen(CONFIG_ARG));
+ TSDebug(PN, "Found config path %s", config_path);
+ }
+ }
+ if (NULL == config_path) {
+ static char const * const DEFAULT_CONFIG_PATH = "ssl_sni_whitelist.config";
+ config_path = TSstrdup(DEFAULT_CONFIG_PATH);
+ TSDebug(PN, "No config path set in arguments, using default: %s", DEFAULT_CONFIG_PATH);
+ }
+
+ // translate relative paths to absolute
+ if (config_path[0] != '/') {
+ ConfigPath = std::string(TSConfigDirGet()) + '/' + std::string(config_path);
+ } else {
+ ConfigPath = config_path;
+ }
+
+ // free up the path
+ TSfree(config_path);
+
+ int ret = Load_Config_File();
+ if (ret != 0) {
+ TSError(PCP "Failed to load the config file, check debug output for errata");
+ }
+
+ return 0;
+}
+
+int
+CB_servername_whitelist(TSCont contp, TSEvent event, void *edata) {
+ TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
+ TSSslConnection sslobj = TSVConnSSLConnectionGet(ssl_vc);
+ SSL *ssl = reinterpret_cast<SSL *>(sslobj);
+ const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+
+ bool do_blind_tunnel = true;
+ if (servername != NULL) {
+ TSSslContext ctxobj = TSSslContextFindByName(servername);
+ if (ctxobj != NULL) {
+ do_blind_tunnel = false;
+ }
+ else {
+ // Look up by destination address
+ ctxobj = TSSslContextFindByAddr(TSNetVConnRemoteAddrGet(ssl_vc));
+ if (ctxobj != NULL) {
+ do_blind_tunnel = false;
+ }
+ }
+ }
+ if (do_blind_tunnel) {
+ TSDebug("skh", "SNI callback: do blind tunnel for %s", servername);
+ TSVConnTunnel(ssl_vc);
+ return TS_SUCCESS; // Don't re-enable so we interrupt processing
+ }
+ TSVConnReenable(ssl_vc);
+ return TS_SUCCESS;
+}
+
+} // Anon namespace
+
+// Called by ATS as our initialization point
+void
+TSPluginInit(int argc, const char *argv[]) {
+ bool success = false;
+ TSPluginRegistrationInfo info;
+ TSCont cb_sni = 0; // sni callback continuation
+
+ info.plugin_name = const_cast<char*>("SSL SNI whitelist");
+ info.vendor_name = const_cast<char*>("Network Geographics");
+ info.support_email = const_cast<char*>("shinrich@network-geographics.com");
+
+ if (TS_SUCCESS != TSPluginRegister(TS_SDK_VERSION_2_0, &info)) {
+ TSError(PCP "registration failed.");
+ } else if (TSTrafficServerVersionGetMajor() < 2) {
+ TSError(PCP "requires Traffic Server 2.0 or later.");
+ } else if (0 > Load_Configuration(argc, argv)) {
+ TSError(PCP "Failed to load config file.");
+ } else if (0 == (cb_sni = TSContCreate(&CB_servername_whitelist, TSMutexCreate()))) {
+ TSError(PCP "Failed to create SNI callback.");
+ } else {
+ TSHttpHookAdd(TS_SSL_SNI_HOOK, cb_sni);
+ success = true;
+ }
+
+ if (!success) {
+ if (cb_sni) TSContDestroy(cb_sni);
+ TSError(PCP "not initialized");
+ }
+ TSDebug(PN, "Plugin %s", success ? "online" : "offline");
+
+ return;
+}
+
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-sni-whitelist/ssl_sni_whitelist.config
----------------------------------------------------------------------
diff --git a/example/ssl-sni-whitelist/ssl_sni_whitelist.config b/example/ssl-sni-whitelist/ssl_sni_whitelist.config
new file mode 100644
index 0000000..706e6a8
--- /dev/null
+++ b/example/ssl-sni-whitelist/ssl_sni_whitelist.config
@@ -0,0 +1,3 @@
+//
+// Place holder
+
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-sni/ssl-sni.cc
----------------------------------------------------------------------
diff --git a/example/ssl-sni/ssl-sni.cc b/example/ssl-sni/ssl-sni.cc
new file mode 100644
index 0000000..d1aa856
--- /dev/null
+++ b/example/ssl-sni/ssl-sni.cc
@@ -0,0 +1,162 @@
+/** @file
+ SSL Preaccept test plugin
+ Implements blind tunneling based on the client IP address
+ The client ip addresses are specified in the plugin's
+ config file as an array of IP addresses or IP address ranges under the
+ key "client-blind-tunnel"
+*/
+
+# include <stdio.h>
+# include <memory.h>
+# include <inttypes.h>
+# include <ts/ts.h>
+# include <tsconfig/TsValue.h>
+# include <alloca.h>
+# include <openssl/ssl.h>
+
+using ts::config::Configuration;
+using ts::config::Value;
+
+# define PN "ssl-sni-test"
+# define PCP "[" PN " Plugin] "
+
+namespace {
+
+std::string ConfigPath;
+
+Configuration Config; // global configuration
+
+int
+Load_Config_File() {
+ ts::Rv<Configuration> cv = Configuration::loadFromPath(ConfigPath.c_str());
+ if (!cv.isOK()) {
+ TSError(PCP "Failed to parse %s as TSConfig format", ConfigPath.c_str());
+ return -1;
+ }
+ Config = cv;
+ return 1;
+}
+
+int
+Load_Configuration(int argc, const char *argv[]) {
+ts::ConstBuffer text;
+ std::string s; // temp holder.
+ TSMgmtString config_path = NULL;
+
+ // get the path to the config file if one was specified
+ static char const * const CONFIG_ARG = "--config=";
+ int arg_idx;
+ for (arg_idx = 0; arg_idx < argc; arg_idx++) {
+ if (0 == memcmp(argv[arg_idx], CONFIG_ARG, strlen(CONFIG_ARG))) {
+ config_path = TSstrdup(argv[arg_idx] + strlen(CONFIG_ARG));
+ TSDebug(PN, "Found config path %s", config_path);
+ }
+ }
+ if (NULL == config_path) {
+ static char const * const DEFAULT_CONFIG_PATH = "ssl_sni.config";
+ config_path = TSstrdup(DEFAULT_CONFIG_PATH);
+ TSDebug(PN, "No config path set in arguments, using default: %s", DEFAULT_CONFIG_PATH);
+ }
+
+ // translate relative paths to absolute
+ if (config_path[0] != '/') {
+ ConfigPath = std::string(TSConfigDirGet()) + '/' + std::string(config_path);
+ } else {
+ ConfigPath = config_path;
+ }
+
+ // free up the path
+ TSfree(config_path);
+
+ int ret = Load_Config_File();
+ if (ret != 0) {
+ TSError(PCP "Failed to load the config file, check debug output for errata");
+ }
+
+ return 0;
+}
+
+/**
+ Somewhat nonscensically exercise some scenarios of proxying
+ and blind tunneling from the SNI callback plugin
+
+ Case 1: If the servername ends in facebook.com, blind tunnel
+ Case 2: If the servername is www.yahoo.com and there is a context
+ entry for "safelyfiled.com", use the "safelyfiled.com" context for
+ this connection.
+ */
+int
+CB_servername(TSCont /* contp */, TSEvent /* event */, void *edata) {
+ TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
+ TSSslConnection sslobj = TSVConnSSLConnectionGet(ssl_vc);
+ SSL *ssl = reinterpret_cast<SSL *>(sslobj);
+ const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ if (servername != NULL) {
+ int servername_len = strlen(servername);
+ int facebook_name_len = strlen("facebook.com");
+ if (servername_len >= facebook_name_len) {
+ const char *server_ptr = servername + (servername_len - facebook_name_len);
+ if (strcmp(server_ptr, "facebook.com") == 0) {
+ TSDebug("skh", "Blind tunnel from SNI callback");
+ TSVConnTunnel(ssl_vc);
+ // Don't reenable to ensure that we break out of the
+ // SSL handshake processing
+ return TS_SUCCESS; // Don't re-enable so we interrupt processing
+ }
+ }
+ // If the name is yahoo, look for a context for safelyfiled and use that here
+ if (strcmp("www.yahoo.com", servername) == 0) {
+ TSDebug("skh", "SNI name is yahoo ssl obj is %p", sslobj);
+ if (sslobj) {
+ TSSslContext ctxobj = TSSslContextFindByName("safelyfiled.com");
+ if (ctxobj != NULL) {
+ TSDebug("skh", "Found cert for safelyfiled");
+ SSL_CTX *ctx = reinterpret_cast<SSL_CTX *>(ctxobj);
+ SSL_set_SSL_CTX(ssl, ctx);
+ TSDebug("skh", "SNI plugin cb: replace SSL CTX");
+ }
+ }
+ }
+ }
+
+
+ // All done, reactivate things
+ TSVConnReenable(ssl_vc);
+ return TS_SUCCESS;
+}
+
+} // Anon namespace
+
+// Called by ATS as our initialization point
+void
+TSPluginInit(int argc, const char *argv[]) {
+ bool success = false;
+ TSPluginRegistrationInfo info;
+ TSCont cb_sni = 0; // sni callback continuation
+
+ info.plugin_name = const_cast<char*>("SSL SNI callback test");
+ info.vendor_name = const_cast<char*>("Network Geographics");
+ info.support_email = const_cast<char*>("shinrich@network-geographics.com");
+
+ if (TS_SUCCESS != TSPluginRegister(TS_SDK_VERSION_2_0, &info)) {
+ TSError(PCP "registration failed.");
+ } else if (TSTrafficServerVersionGetMajor() < 2) {
+ TSError(PCP "requires Traffic Server 2.0 or later.");
+ } else if (0 > Load_Configuration(argc, argv)) {
+ TSError(PCP "Failed to load config file.");
+ } else if (0 == (cb_sni = TSContCreate(&CB_servername, TSMutexCreate()))) {
+ TSError(PCP "Failed to create SNI callback.");
+ } else {
+ TSHttpHookAdd(TS_SSL_SNI_HOOK, cb_sni);
+ success = true;
+ }
+
+ if (!success) {
+ if (cb_sni) TSContDestroy(cb_sni);
+ TSError(PCP "not initialized");
+ }
+ TSDebug(PN, "Plugin %s", success ? "online" : "offline");
+
+ return;
+}
+
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-sni/ssl_sni.config
----------------------------------------------------------------------
diff --git a/example/ssl-sni/ssl_sni.config b/example/ssl-sni/ssl_sni.config
new file mode 100644
index 0000000..2ec52ec
--- /dev/null
+++ b/example/ssl-sni/ssl_sni.config
@@ -0,0 +1,7 @@
+
+// SSL traffic initiating from these addresses should
+// be placed into a blind tunnel. ATS should not inspect
+// the tunnel
+client-blind-tunnel = "192.168.56.145"
+
+//client-blind-tunnel = "192.168.56.0/24"
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/Makefile.am
----------------------------------------------------------------------
diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am
index 907be2e..0120528 100644
--- a/iocore/net/Makefile.am
+++ b/iocore/net/Makefile.am
@@ -26,7 +26,9 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/proxy/hdrs \
-I$(top_srcdir)/proxy/shared \
-I$(top_srcdir)/mgmt \
- -I$(top_srcdir)/mgmt/utils
+ -I$(top_srcdir)/mgmt/utils \
+ -I$(top_srcdir)/proxy/api/ts \
+ -I$(top_srcdir)/proxy/http
TESTS = $(check_PROGRAMS)
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/OCSPStapling.cc
----------------------------------------------------------------------
diff --git a/iocore/net/OCSPStapling.cc b/iocore/net/OCSPStapling.cc
index 136c623..51212a7 100644
--- a/iocore/net/OCSPStapling.cc
+++ b/iocore/net/OCSPStapling.cc
@@ -363,20 +363,23 @@ ocsp_update()
const unsigned ctxCount = certLookup->count();
for (unsigned i = 0; i < ctxCount; i++) {
- ctx = certLookup->get(i);
- cinf = stapling_get_cert_info(ctx);
- if (cinf) {
- ink_mutex_acquire(&cinf->stapling_mutex);
- current_time = time(NULL);
- if (cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time) {
- ink_mutex_release(&cinf->stapling_mutex);
- if (stapling_refresh_response(cinf, &resp)) {
- Note("Success to refresh OCSP response for 1 certificate.");
- } else {
- Note("Fail to refresh OCSP response for 1 certificate.");
- }
- } else {
- ink_mutex_release(&cinf->stapling_mutex);
+ SSLCertContext *cc = certLookup->get(i);
+ if (cc && cc->ctx) {
+ ctx = cc->ctx;
+ cinf = stapling_get_cert_info(ctx);
+ if (cinf) {
+ ink_mutex_acquire(&cinf->stapling_mutex);
+ current_time = time(NULL);
+ if (cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time) {
+ ink_mutex_release(&cinf->stapling_mutex);
+ if (stapling_refresh_response(cinf, &resp)) {
+ Note("Success to refresh OCSP response for 1 certificate.");
+ } else {
+ Note("Fail to refresh OCSP response for 1 certificate.");
+ }
+ } else {
+ ink_mutex_release(&cinf->stapling_mutex);
+ }
}
}
}
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/P_SSLCertLookup.h
----------------------------------------------------------------------
diff --git a/iocore/net/P_SSLCertLookup.h b/iocore/net/P_SSLCertLookup.h
index f2328ef..05d908c 100644
--- a/iocore/net/P_SSLCertLookup.h
+++ b/iocore/net/P_SSLCertLookup.h
@@ -30,21 +30,64 @@
struct SSLConfigParams;
struct SSLContextStorage;
+/** A certificate context.
+
+ This holds data about a certificate and how it is used by the SSL logic. Current this is mainly
+ the openSSL certificate and an optional action, which in turn is limited to just tunneling.
+
+ Instances are passed around and returned when matching connections to certificates.
+
+ Instances of this class are stored on a list and then referenced via index in that list so that
+ there is exactly one place we can find all the @c SSL_CTX instances exactly once.
+
+*/
+struct SSLCertContext
+{
+ /** Special things to do instead of use a context.
+ In general an option will be associated with a @c NULL context because
+ the context is not used.
+ */
+ enum Option {
+ OPT_NONE, ///< Nothing special. Implies valid context.
+ OPT_TUNNEL ///< Just tunnel, don't terminate.
+ };
+
+ SSLCertContext() : ctx(0), opt(OPT_NONE) {}
+ explicit SSLCertContext(SSL_CTX* c) : ctx(c), opt(OPT_NONE) {}
+ SSLCertContext(SSL_CTX* c, Option o) : ctx(c), opt(o) {}
+
+ SSL_CTX* ctx; ///< openSSL context.
+ Option opt; ///< Special handling option.
+};
+
struct SSLCertLookup : public ConfigInfo
{
SSLContextStorage * ssl_storage;
SSL_CTX * ssl_default;
- bool insert(SSL_CTX * ctx, const char * name);
- bool insert(SSL_CTX * ctx, const IpEndpoint& address);
- SSL_CTX * findInfoInHash(const char * address) const;
- SSL_CTX * findInfoInHash(const IpEndpoint& address) const;
+ int insert(const char *name, SSLCertContext const &cc);
+ int insert(const IpEndpoint& address, SSLCertContext const &cc);
+
+ /** Find certificate context by IP address.
+ The IP addresses are taken from the socket @a s.
+ Exact matches have priority, then wildcards. The destination address is preferred to the source address.
+ @return @c A pointer to the matched context, @c NULL if no match is found.
+ */
+ SSLCertContext* find(const IpEndpoint& address) const;
+
+ /** Find certificate context by name (FQDN).
+ Exact matches have priority, then wildcards. Only destination based matches are checked.
+ @return @c A pointer to the matched context, @c NULL if no match is found.
+ */
+ SSLCertContext* find(char const* name) const;
+
+
// Return the last-resort default TLS context if there is no name or address match.
SSL_CTX * defaultContext() const { return ssl_default; }
unsigned count() const;
- SSL_CTX * get(unsigned i) const;
+ SSLCertContext * get(unsigned i) const;
SSLCertLookup();
virtual ~SSLCertLookup();
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/P_SSLNetVConnection.h
----------------------------------------------------------------------
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index c464e60..863b5da 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -36,6 +36,7 @@
#include "P_EventSystem.h"
#include "P_UnixNetVConnection.h"
#include "P_UnixNet.h"
+#include "apidefs.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
@@ -52,6 +53,7 @@
#endif
class SSLNextProtocolSet;
+struct SSLCertLookup;
//////////////////////////////////////////////////////////////////
//
@@ -62,6 +64,7 @@ class SSLNextProtocolSet;
//////////////////////////////////////////////////////////////////
class SSLNetVConnection:public UnixNetVConnection
{
+ typedef UnixNetVConnection super; ///< Parent type.
public:
virtual int sslStartHandShake(int event, int &err);
virtual void free(EThread * t);
@@ -120,6 +123,36 @@ public:
sslClientRenegotiationAbort = state;
};
+ /// Reenable the VC after a pre-accept or SNI hook is called.
+ virtual void reenable(NetHandler* nh);
+ /// Set the SSL context.
+ /// @note This must be called after the SSL endpoint has been created.
+ virtual bool sslContextSet(void* ctx);
+
+ /// Set by asynchronous hooks to request a specific operation.
+ TSSslVConnOp hookOpRequested;
+
+ // Store the servername returned by SNI
+ char sniServername[TS_MAX_HOST_NAME_LEN];
+
+ int64_t read_raw_data();
+ void initialize_handshake_buffers() {
+ this->handShakeBuffer = new_MIOBuffer();
+ this->handShakeReader = this->handShakeBuffer->alloc_reader();
+ this->handShakeHolder = this->handShakeReader->clone();
+ }
+ void free_handshake_buffers() {
+
+ this->handShakeReader->dealloc();
+ this->handShakeHolder->dealloc();
+ free_MIOBuffer(this->handShakeBuffer);
+ this->handShakeReader = NULL;
+ this->handShakeHolder = NULL;
+ this->handShakeBuffer = NULL;
+ }
+ // Returns true if all the hooks reenabled
+ bool callHooks(TSHttpHookID eventId);
+
private:
SSLNetVConnection(const SSLNetVConnection &);
SSLNetVConnection & operator =(const SSLNetVConnection &);
@@ -127,6 +160,29 @@ private:
bool sslHandShakeComplete;
bool sslClientConnection;
bool sslClientRenegotiationAbort;
+ MIOBuffer *handShakeBuffer;
+ IOBufferReader *handShakeHolder;
+ IOBufferReader *handShakeReader;
+
+ /// The current hook.
+ /// @note For @C SSL_HOOKS_INVOKE, this is the hook to invoke.
+ class APIHook* curHook;
+
+ enum {
+ SSL_HOOKS_INIT, ///< Initial state, no hooks called yet.
+ SSL_HOOKS_INVOKE, ///< Waiting to invoke hook.
+ SSL_HOOKS_ACTIVE, ///< Hook invoked, waiting for it to complete.
+ SSL_HOOKS_CONTINUE, ///< All hooks have been called and completed
+ SSL_HOOKS_DONE ///< All hooks have been called and completed
+ } sslPreAcceptHookState;
+
+ enum {
+ SNI_HOOKS_INIT,
+ SNI_HOOKS_ACTIVE,
+ SNI_HOOKS_DONE,
+ SNI_HOOKS_CONTINUE
+ } sslSNIHookState;
+
const SSLNextProtocolSet * npnSet;
Continuation * npnEndpoint;
};
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/SSLCertLookup.cc
----------------------------------------------------------------------
diff --git a/iocore/net/SSLCertLookup.cc b/iocore/net/SSLCertLookup.cc
index 253f9a2..19c22ca 100644
--- a/iocore/net/SSLCertLookup.cc
+++ b/iocore/net/SSLCertLookup.cc
@@ -70,28 +70,51 @@ private:
struct SSLContextStorage
{
+public:
SSLContextStorage();
~SSLContextStorage();
- bool insert(SSL_CTX * ctx, const char * name);
- SSL_CTX * lookup(const char * name) const;
- unsigned count() const { return this->references.count(); }
- SSL_CTX * get(unsigned i) const { return this->references[i]; }
+ /// Add a cert context to storage
+ /// @return The @a host_store index or -1 on error.
+ int insert(const char * name, SSLCertContext const& cc);
+ /// Add a cert context to storage.
+ /// @a idx must be a value returned by a previous call to insert.
+ /// This creates an alias, a different @a name referring to the same
+ /// cert context.
+ /// @return @a idx
+ int insert(const char * name, int idx);
+ SSLCertContext* lookup(const char * name) const;
+ unsigned count() const { return this->ctx_store.length(); }
+ SSLCertContext* get(unsigned i) const { return &this->ctx_store[i]; }
private:
- struct SSLEntry
+ /** A struct that can stored a @c Trie.
+ It contains the index of the real certificate and the
+ linkage required by @c Trie.
+ */
+ struct ContextRef
{
- explicit SSLEntry(SSL_CTX * c) : ctx(c) {}
-
- void Print() const { Debug("ssl", "SSLEntry=%p SSL_CTX=%p", this, ctx); }
-
- SSL_CTX * ctx;
- LINK(SSLEntry, link);
+ ContextRef(): idx(-1) {}
+ explicit ContextRef(int n) : idx(n) {}
+ void Print() const { Debug("ssl", "Item=%p SSL_CTX=#%d", this, idx); }
+ int idx; ///< Index in the context store.
+ LINK(ContextRef, link); ///< Require by @c Trie
};
- Trie<SSLEntry> wildcards;
+ /// Items tored by wildcard name
+ Trie<ContextRef> wildcards;
+ /// Contexts store by IP address or FQDN
InkHashTable * hostnames;
- Vec<SSL_CTX *> references;
+ /// List for cleanup.
+ /// Exactly one pointer to each SSL context is stored here.
+ Vec<SSLCertContext> ctx_store;
+
+ /// Add a context to the clean up list.
+ /// @return The index of the added context.
+ int store(SSLCertContext const& cc);
+ /// Remove last added context
+ void unstore();
+
};
SSLCertLookup::SSLCertLookup()
@@ -104,21 +127,21 @@ SSLCertLookup::~SSLCertLookup()
delete this->ssl_storage;
}
-SSL_CTX *
-SSLCertLookup::findInfoInHash(const char * address) const
+SSLCertContext *
+SSLCertLookup::find(const char * address) const
{
return this->ssl_storage->lookup(address);
}
-SSL_CTX *
-SSLCertLookup::findInfoInHash(const IpEndpoint& address) const
+SSLCertContext *
+SSLCertLookup::find(const IpEndpoint& address) const
{
- SSL_CTX * ctx;
+ SSLCertContext * cc;
SSLAddressLookupKey key(address);
// First try the full address.
- if ((ctx = this->ssl_storage->lookup(key.get()))) {
- return ctx;
+ if ((cc = this->ssl_storage->lookup(key.get()))) {
+ return cc;
}
// If that failed, try the address without the port.
@@ -130,17 +153,17 @@ SSLCertLookup::findInfoInHash(const IpEndpoint& address) const
return NULL;
}
-bool
-SSLCertLookup::insert(SSL_CTX * ctx, const char * name)
+int
+SSLCertLookup::insert(const char *name, SSLCertContext const &cc)
{
- return this->ssl_storage->insert(ctx, name);
+ return this->ssl_storage->insert(name, cc);
}
-bool
-SSLCertLookup::insert(SSL_CTX * ctx, const IpEndpoint& address)
+int
+SSLCertLookup::insert(const IpEndpoint& address, SSLCertContext const &cc)
{
SSLAddressLookupKey key(address);
- return this->ssl_storage->insert(ctx, key.get());
+ return this->ssl_storage->insert(key.get(), cc);
}
unsigned
@@ -149,7 +172,7 @@ SSLCertLookup::count() const
return ssl_storage->count();
}
-SSL_CTX *
+SSLCertContext *
SSLCertLookup::get(unsigned i) const
{
return ssl_storage->get(i);
@@ -212,15 +235,38 @@ SSLContextStorage::SSLContextStorage()
SSLContextStorage::~SSLContextStorage()
{
- for (unsigned i = 0; i < this->references.count(); ++i) {
- SSLReleaseContext(this->references[i]);
+ for (unsigned i = 0; i < this->ctx_store.length(); ++i) {
+ SSLReleaseContext(this->ctx_store[i].ctx);
}
ink_hash_table_destroy(this->hostnames);
}
-bool
-SSLContextStorage::insert(SSL_CTX * ctx, const char * name)
+int
+SSLContextStorage::store(SSLCertContext const& cc)
+{
+ int idx = this->ctx_store.length();
+ this->ctx_store.add(cc);
+ return idx;
+}
+
+void
+SSLContextStorage::unstore()
+{
+ this->ctx_store.drop();
+}
+
+int
+SSLContextStorage::insert(const char* name, SSLCertContext const& cc)
+{
+ int idx = this->store(cc);
+ idx = this->insert(name, idx);
+ if (idx < 0) this->unstore();
+ return idx;
+}
+
+int
+SSLContextStorage::insert(const char* name, int idx)
{
ats_wildcard_matcher wildcard;
bool inserted = false;
@@ -230,70 +276,59 @@ SSLContextStorage::insert(SSL_CTX * ctx, const char * name)
// so that we can do a longest match lookup.
char namebuf[TS_MAX_HOST_NAME_LEN + 1];
char * reversed;
- ats_scoped_obj<SSLEntry> entry;
+ ats_scoped_obj<ContextRef> ref;
reversed = reverse_dns_name(name + 1, namebuf);
if (!reversed) {
Error("wildcard name '%s' is too long", name);
- return false;
+ return -1;
}
- entry = new SSLEntry(ctx);
- inserted = this->wildcards.Insert(reversed, entry, 0 /* rank */, -1 /* keylen */);
+ ref = new ContextRef(idx);
+ inserted = this->wildcards.Insert(reversed, ref, 0 /* rank */, -1 /* keylen */);
if (!inserted) {
- SSLEntry * found;
+ ContextRef * found;
// We fail to insert, so the longest wildcard match search should return the full match value.
found = this->wildcards.Search(reversed);
- if (found != NULL && found->ctx != ctx) {
- Warning("previously indexed wildcard certificate for '%s' as '%s', cannot index it with SSL_CTX %p now",
- name, reversed, ctx);
+ if (found != NULL && found->idx != idx) {
+ Warning("previously indexed wildcard certificate for '%s' as '%s', cannot index it with SSL_CTX #%d now",
+ name, reversed, idx);
}
-
- goto done;
+ idx = -1;
}
- Debug("ssl", "indexed wildcard certificate for '%s' as '%s' with SSL_CTX %p", name, reversed, ctx);
- entry.release();
+ Debug("ssl", "%s wildcard certificate for '%s' as '%s' with SSL_CTX %p [%d]",
+ idx >= 0 ? "index" : "failed to index", name, reversed, this->ctx_store[(*ref).idx].ctx, (*ref).idx);
+ ref.release();
} else {
InkHashTableValue value;
- if (ink_hash_table_lookup(this->hostnames, name, &value) && (void *)ctx != value) {
- Warning("previously indexed '%s' with SSL_CTX %p, cannot index it with SSL_CTX %p now", name, value, ctx);
- goto done;
+ if (ink_hash_table_lookup(this->hostnames, name, &value) && (void *)idx != value) {
+ Warning("previously indexed '%s' with SSL_CTX %p, cannot index it with SSL_CTX #%d now", name, value, idx);
+ } else {
+ inserted = true;
+ ink_hash_table_insert(this->hostnames, name, reinterpret_cast<void*>(static_cast<intptr_t>(idx)));
+ Debug("ssl", "indexed '%s' with SSL_CTX %p [%d]",
+ name, this->ctx_store[idx].ctx, idx);
}
-
- inserted = true;
- ink_hash_table_insert(this->hostnames, name, (void *)ctx);
- Debug("ssl", "indexed '%s' with SSL_CTX %p", name, ctx);
}
-
-done:
- // Keep a unique reference to the SSL_CTX, so that we can free it later. Since we index by name, multiple
- // certificates can be indexed for the same name. If this happens, we will overwrite the previous pointer
- // and leak a context. So if we insert a certificate, keep an ownership reference to it.
- if (inserted) {
- if (this->references.in(ctx) == NULL) {
- this->references.push_back(ctx);
- }
- }
-
- return inserted;
+ return idx;
}
-SSL_CTX *
+SSLCertContext *
SSLContextStorage::lookup(const char * name) const
{
InkHashTableValue value;
if (ink_hash_table_lookup(const_cast<InkHashTable *>(this->hostnames), name, &value)) {
- return (SSL_CTX *)value;
+ return &(this->ctx_store[reinterpret_cast<intptr_t>(value)]);
}
if (!this->wildcards.Empty()) {
char namebuf[TS_MAX_HOST_NAME_LEN + 1];
char * reversed;
- SSLEntry * entry;
+ ContextRef * ref;
reversed = reverse_dns_name(name, namebuf);
if (!reversed) {
@@ -302,9 +337,9 @@ SSLContextStorage::lookup(const char * name) const
}
Debug("ssl", "attempting wildcard match for %s", reversed);
- entry = this->wildcards.Search(reversed);
- if (entry) {
- return entry->ctx;
+ ref = this->wildcards.Search(reversed);
+ if (ref) {
+ return &(this->ctx_store[ref->idx]);
}
}
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/SSLNetVConnection.cc
----------------------------------------------------------------------
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 7978013..b4269e7 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -21,9 +21,11 @@
limitations under the License.
*/
#include "ink_config.h"
+#include "records/I_RecHttp.h"
#include "P_Net.h"
#include "P_SSLNextProtocolSet.h"
#include "P_SSLUtils.h"
+#include "InkAPIInternal.h" // Added to include the ssl_hook definitions
#define SSL_READ_ERROR_NONE 0
#define SSL_READ_ERROR 1
@@ -36,9 +38,87 @@
#define SSL_HANDSHAKE_WANT_ACCEPT 8
#define SSL_HANDSHAKE_WANT_CONNECT 9
#define SSL_WRITE_WOULD_BLOCK 10
+#define SSL_WAIT_FOR_HOOK 11
+
+#ifndef UIO_MAXIOV
+#define NET_MAX_IOV 16 // UIO_MAXIOV shall be at least 16 1003.1g (5.4.1.1)
+#else
+#define NET_MAX_IOV UIO_MAXIOV
+#endif
ClassAllocator<SSLNetVConnection> sslNetVCAllocator("sslNetVCAllocator");
+namespace {
+ /// Callback to get two locks.
+ /// The lock for this continuation, and for the target continuation.
+ class ContWrapper : public Continuation
+ {
+ public:
+ /** Constructor.
+ This takes the secondary @a mutex and the @a target continuation
+ to invoke, along with the arguments for that invocation.
+ */
+ ContWrapper(
+ ProxyMutex* mutex ///< Mutex for this continuation (primary lock).
+ , Continuation* target ///< "Real" continuation we want to call.
+ , int eventId = EVENT_IMMEDIATE ///< Event ID for invocation of @a target.
+ , void* edata = 0 ///< Data for invocation of @a target.
+ )
+ : Continuation(mutex)
+ , _target(target)
+ , _eventId(eventId)
+ , _edata(edata)
+ {
+ SET_HANDLER(&ContWrapper::event_handler);
+ }
+
+ /// Required event handler method.
+ int event_handler(int, void*)
+ {
+ EThread* eth = this_ethread();
+
+ MUTEX_TRY_LOCK(lock, _target->mutex, eth);
+ if (lock) { // got the target lock, we can proceed.
+ _target->handleEvent(_eventId, _edata);
+ delete this;
+ } else { // can't get both locks, try again.
+ eventProcessor.schedule_imm(this, ET_NET);
+ }
+ return 0;
+ }
+
+ /** Convenience static method.
+
+ This lets a client make one call and not have to (accurately)
+ copy the invocation logic embedded here. We duplicate it near
+ by textually so it is easier to keep in sync.
+
+ This takes the same arguments as the constructor but, if the
+ lock can be obtained immediately, does not construct an
+ instance but simply calls the @a target.
+ */
+ static void wrap(
+ ProxyMutex* mutex ///< Mutex for this continuation (primary lock).
+ , Continuation* target ///< "Real" continuation we want to call.
+ , int eventId = EVENT_IMMEDIATE ///< Event ID for invocation of @a target.
+ , void* edata = 0 ///< Data for invocation of @a target.
+ ) {
+ EThread* eth = this_ethread();
+ MUTEX_TRY_LOCK(lock, target->mutex, eth);
+ if (lock) {
+ target->handleEvent(eventId, edata);
+ } else {
+ eventProcessor.schedule_imm(new ContWrapper(mutex, target, eventId, edata), ET_NET);
+ }
+ }
+
+ private:
+ Continuation* _target; ///< Continuation to invoke.
+ int _eventId; ///< with this event
+ void* _edata; ///< and this data
+ };
+}
+
//
// Private
//
@@ -49,7 +129,19 @@ make_ssl_connection(SSL_CTX * ctx, SSLNetVConnection * netvc)
SSL * ssl;
if (likely(ssl = SSL_new(ctx))) {
- SSL_set_fd(ssl, netvc->get_socket());
+ netvc->ssl = ssl;
+
+ // Only set up the bio stuff for the server side
+ if (netvc->getSSLClientConnection()) {
+ SSL_set_fd(ssl, netvc->get_socket());
+ } else {
+ netvc->initialize_handshake_buffers();
+ BIO *rbio = BIO_new(BIO_s_mem());
+ BIO *wbio = BIO_new_fd(netvc->get_socket(), BIO_NOCLOSE);
+ BIO_set_mem_eof_return(wbio, -1);
+ SSL_set_bio(ssl, rbio, wbio);
+ }
+
SSL_set_app_data(ssl, netvc);
}
@@ -208,6 +300,99 @@ ssl_read_from_net(SSLNetVConnection * sslvc, EThread * lthread, int64_t &ret)
}
+/**
+ * Read from socket directly for handshake data. Store the data in
+ * a MIOBuffer. Place the data in the read BIO so the openssl library
+ * has access to it.
+ * If for some ready we much abort out of the handshake, we can replay
+ * the stored data (e.g. back out to blind tunneling)
+ */
+int64_t
+SSLNetVConnection::read_raw_data()
+{
+ int64_t r = 0;
+ int64_t toread = INT_MAX;
+
+ // read data
+ int64_t rattempted = 0, total_read = 0;
+ int niov = 0;
+ IOVec tiovec[NET_MAX_IOV];
+ if (toread) {
+ IOBufferBlock *b = this->handShakeBuffer->first_write_block();
+ do {
+ niov = 0;
+ rattempted = 0;
+ while (b && niov < NET_MAX_IOV) {
+ int64_t a = b->write_avail();
+ if (a > 0) {
+ tiovec[niov].iov_base = b->_end;
+ int64_t togo = toread - total_read - rattempted;
+ if (a > togo)
+ a = togo;
+ tiovec[niov].iov_len = a;
+ rattempted += a;
+ niov++;
+ if (a >= togo)
+ break;
+ }
+ b = b->next;
+ }
+
+ if (niov == 1) {
+ r = socketManager.read(this->con.fd, tiovec[0].iov_base, tiovec[0].iov_len);
+ } else {
+ r = socketManager.readv(this->con.fd, &tiovec[0], niov);
+ }
+ NET_DEBUG_COUNT_DYN_STAT(net_calls_to_read_stat, 1);
+ total_read += rattempted;
+ } while (rattempted && r == rattempted && total_read < toread);
+
+ // if we have already moved some bytes successfully, summarize in r
+ if (total_read != rattempted) {
+ if (r <= 0)
+ r = total_read - rattempted;
+ else
+ r = total_read - rattempted + r;
+ }
+ // check for errors
+ if (r <= 0) {
+
+ if (r == -EAGAIN || r == -ENOTCONN) {
+ NET_DEBUG_COUNT_DYN_STAT(net_calls_to_read_nodata_stat, 1);
+ return r;
+ }
+
+ if (!r || r == -ECONNRESET) {
+ return r;
+ }
+ return r;
+ }
+ NET_SUM_DYN_STAT(net_read_bytes_stat, r);
+
+ this->handShakeBuffer->fill(r);
+
+ } else
+ r = 0;
+
+ char *start = this->handShakeReader->start();
+ char *end = this->handShakeReader->end();
+
+ // Sets up the buffer as a read only bio target
+ // Must be reset on each read
+ BIO *rbio = BIO_new_mem_buf(start, end - start);
+ BIO_set_mem_eof_return(rbio, -1);
+ // Assigning directly into the SSL structure
+ // is dirty, but there is no openssl function that only
+ // assigns the read bio. Originally I was getting and
+ // resetting the same write bio, but that caused the
+ // inserted buffer bios to be freed and then reinserted.
+ //BIO *wbio = SSL_get_wbio(this->ssl);
+ //SSL_set_bio(this->ssl, rbio, wbio);
+ this->ssl->rbio = rbio;
+
+ return r;
+}
+
//changed by YTS Team, yamsat
void
@@ -220,6 +405,11 @@ SSLNetVConnection::net_read_io(NetHandler *nh, EThread *lthread)
MIOBufferAccessor &buf = s->vio.buffer;
int64_t ntodo = s->vio.ntodo();
+ if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == this->attributes) {
+ this->super::net_read_io(nh, lthread);
+ return;
+ }
+
if (sslClientRenegotiationAbort == true) {
this->read.triggered = 0;
readSignalError(nh, (int)r);
@@ -246,37 +436,82 @@ SSLNetVConnection::net_read_io(NetHandler *nh, EThread *lthread)
// vc is an SSLNetVConnection.
if (!getSSLHandShakeComplete()) {
int err;
+ int data_to_read = 0;
+ char *data_ptr = NULL;
- if (getSSLClientConnection()) {
- ret = sslStartHandShake(SSL_EVENT_CLIENT, err);
- } else {
- ret = sslStartHandShake(SSL_EVENT_SERVER, err);
- }
+ // Not done handshaking, go into the SSL handshake logic again
+ if (!getSSLHandShakeComplete()) {
- if (ret == EVENT_ERROR) {
- this->read.triggered = 0;
- readSignalError(nh, err);
- } else if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT) {
- read.triggered = 0;
- nh->read_ready_list.remove(this);
- readReschedule(nh);
- } else if (ret == SSL_HANDSHAKE_WANT_CONNECT || ret == SSL_HANDSHAKE_WANT_WRITE) {
- write.triggered = 0;
- nh->write_ready_list.remove(this);
- writeReschedule(nh);
- } else if (ret == EVENT_DONE) {
- // If this was driven by a zero length read, signal complete when
- // the handshake is complete. Otherwise set up for continuing read
- // operations.
- if (ntodo <= 0) {
- readSignalDone(VC_EVENT_READ_COMPLETE, nh);
+ if (getSSLClientConnection()) {
+ ret = sslStartHandShake(SSL_EVENT_CLIENT, err);
} else {
- read.triggered = 1;
- if (read.enabled)
- nh->read_ready_list.in_or_enqueue(this);
+ ret = sslStartHandShake(SSL_EVENT_SERVER, err);
}
- } else
- readReschedule(nh);
+ // If we have flipped to blind tunnel, don't read ahead
+ if (this->handShakeReader) {
+ if (this->attributes != HttpProxyPort::TRANSPORT_BLIND_TUNNEL) {
+ // Check and consume data that has been read
+ int data_still_to_read = BIO_get_mem_data(SSL_get_rbio(this->ssl), &data_ptr);
+ data_to_read = this->handShakeReader->read_avail();
+ this->handShakeReader->consume(data_to_read - data_still_to_read);
+ }
+ else { // Now in blind tunnel. Set things up to read what is in the buffer
+ this->readSignalDone(VC_EVENT_READ_COMPLETE, nh);
+
+ // If the handshake isn't set yet, this means the tunnel
+ // decision was make in the SNI callback. We must move
+ // the client hello message back into the standard read.vio
+ // so it will get forwarded onto the origin server
+ if (!this->sslHandShakeComplete) {
+ // Kick things to get the http forwarding buffers set up
+ this->sslHandShakeComplete = 1;
+ // Copy over all data already read in during the SSL_accept
+ // (the client hello message)
+ NetState *s = &this->read;
+ MIOBufferAccessor &buf = s->vio.buffer;
+ int64_t r = buf.writer()->write(this->handShakeHolder);
+ s->vio.nbytes += r;
+ s->vio.ndone += r;
+
+ // Clean up the handshake buffers
+ this->free_handshake_buffers();
+
+ // Kick things again, so the data that was copied into the
+ // vio.read buffer gets processed
+ this->readSignalDone(VC_EVENT_READ_COMPLETE, nh);
+ }
+ return;
+ }
+ }
+
+ if (ret == EVENT_ERROR) {
+ this->read.triggered = 0;
+ readSignalError(nh, err);
+ } else if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT) {
+ read.triggered = 0;
+ nh->read_ready_list.remove(this);
+ readReschedule(nh);
+ } else if (ret == SSL_HANDSHAKE_WANT_CONNECT || ret == SSL_HANDSHAKE_WANT_WRITE) {
+ write.triggered = 0;
+ nh->write_ready_list.remove(this);
+ writeReschedule(nh);
+ } else if (ret == EVENT_DONE) {
+ // If this was driven by a zero length read, signal complete when
+ // the handshake is complete. Otherwise set up for continuing read
+ // operations.
+ if (ntodo <= 0) {
+ readSignalDone(VC_EVENT_READ_COMPLETE, nh);
+ } else {
+ read.triggered = 1;
+ if (read.enabled)
+ nh->read_ready_list.in_or_enqueue(this);
+ }
+ } else if (ret == SSL_WAIT_FOR_HOOK) {
+ // avoid readReschedule - done when the plugin calls us back to reenable
+ } else {
+ readReschedule(nh);
+ }
+ }
return;
}
@@ -286,7 +521,43 @@ SSLNetVConnection::net_read_io(NetHandler *nh, EThread *lthread)
return;
}
- // not sure if this do-while loop is really needed here, please replace this comment if you know
+ // At this point we are at the post-handshake SSL processing
+ // If the read BIO is not already a socket, consider changing it
+ if (this->handShakeReader) {
+ if (this->handShakeReader->read_avail() <= 0) {
+ // Switch the read bio over to a socket bio
+ SSL_set_rfd(this->ssl, this->get_socket());
+ this->free_handshake_buffers();
+ }
+ else { // There is still data in the buffer to drain
+ char *data_ptr = NULL;
+ int data_still_to_read = BIO_get_mem_data(SSL_get_rbio(this->ssl), &data_ptr);
+ if (data_still_to_read > 0) {
+ // Still data remaining in the current BIO block
+ }
+ else {
+ // reset the block
+ char *start = this->handShakeReader->start();
+ char *end = this->handShakeReader->end();
+ // Sets up the buffer as a read only bio target
+ // Must be reset on each read
+ BIO *rbio = BIO_new_mem_buf(start, end - start);
+ BIO_set_mem_eof_return(rbio, -1);
+ // So assigning directly into the SSL structure
+ // is dirty, but there is no openssl function that only
+ // assigns the read bio. Originally I was getting and
+ // resetting the same write bio, but that caused the
+ // inserted buffer bios to be freed and then reinserted.
+ this->ssl->rbio = rbio;
+ //BIO *wbio = SSL_get_wbio(this->ssl);
+ //SSL_set_bio(this->ssl, rbio, wbio);
+ }
+ }
+ }
+ // Otherwise, we already replaced the buffer bio with a socket bio
+
+ // not sure if this do-while loop is really needed here, please replace
+ // this comment if you know
do {
ret = ssl_read_from_net(this, lthread, r);
if (ret == SSL_READ_READY || ret == SSL_READ_ERROR_NONE) {
@@ -370,6 +641,10 @@ SSLNetVConnection::load_buffer_and_write(int64_t towrite, int64_t &wattempted, i
int64_t offset = buf.reader()->start_offset;
IOBufferBlock *b = buf.reader()->block;
+ if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == this->attributes) {
+ return this->super::load_buffer_and_write(towrite, wattempted, total_wrote, buf, needs);
+ }
+
do {
// check if we have done this block
l = b->read_avail();
@@ -476,14 +751,21 @@ SSLNetVConnection::load_buffer_and_write(int64_t towrite, int64_t &wattempted, i
}
SSLNetVConnection::SSLNetVConnection():
+ ssl(NULL),
+ sslHandshakeBeginTime(0),
+ hookOpRequested(TS_SSL_HOOK_OP_DEFAULT),
sslHandShakeComplete(false),
sslClientConnection(false),
sslClientRenegotiationAbort(false),
+ handShakeBuffer(NULL),
+ handShakeHolder(NULL),
+ handShakeReader(NULL),
+ sslPreAcceptHookState(SSL_HOOKS_INIT),
+ sslSNIHookState(SNI_HOOKS_INIT),
npnSet(NULL),
npnEndpoint(NULL)
{
- ssl = NULL;
- sslHandshakeBeginTime = 0;
+ sniServername[0] = '\0';
}
void
@@ -511,6 +793,12 @@ SSLNetVConnection::free(EThread * t) {
sslHandShakeComplete = false;
sslClientConnection = false;
sslClientRenegotiationAbort = false;
+ if (SSL_HOOKS_ACTIVE == sslPreAcceptHookState) {
+ Error("SSLNetVconnection freed with outstanding hook");
+ }
+ sslPreAcceptHookState = SSL_HOOKS_INIT;
+ curHook = 0;
+ hookOpRequested = TS_SSL_HOOK_OP_DEFAULT;
npnSet = NULL;
npnEndpoint= NULL;
@@ -529,6 +817,33 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
case SSL_EVENT_SERVER:
if (this->ssl == NULL) {
SSLCertificateConfig::scoped_config lookup;
+ IpEndpoint ip;
+ int namelen = sizeof(ip);
+ safe_getsockname(this->get_socket(), &ip.sa, &namelen);
+ SSLCertContext *cc = lookup->find(ip);
+ if (is_debug_tag_set("ssl")) {
+ IpEndpoint src, dst;
+ ip_port_text_buffer ipb1, ipb2;
+ int ip_len;
+
+ safe_getsockname(this->get_socket(), &dst.sa, &(ip_len = sizeof ip));
+ safe_getpeername(this->get_socket(), &src.sa, &(ip_len = sizeof ip));
+ ats_ip_nptop(&dst, ipb1, sizeof(ipb1));
+ ats_ip_nptop(&src, ipb2, sizeof(ipb2));
+ Debug("ssl", "IP context is %p for [%s] -> [%s], default context %p", cc, ipb2, ipb1, lookup->defaultContext());
+ }
+
+ // Escape if this is marked to be a tunnel.
+ // No data has been read at this point, so we can go
+ // directly into blind tunnel mode
+ if (cc && SSLCertContext::OPT_TUNNEL == cc->opt && this->is_transparent) {
+ this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+ sslHandShakeComplete = 1;
+ SSL_free(this->ssl);
+ this->ssl = NULL;
+ return EVENT_DONE;
+ }
+
// Attach the default SSL_CTX to this SSL session. The default context is never going to be able
// to negotiate a SSL session, but it's enough to trampoline us into the SNI callback where we
@@ -565,7 +880,66 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
int
SSLNetVConnection::sslServerHandShakeEvent(int &err)
{
- int ret = SSL_accept(ssl);
+ int ret;
+
+ if (SSL_HOOKS_DONE != sslPreAcceptHookState) {
+ // Get the first hook if we haven't started invoking yet.
+ if (SSL_HOOKS_INIT == sslPreAcceptHookState) {
+ curHook = ssl_hooks->get(TS_VCONN_PRE_ACCEPT_INTERNAL_HOOK);
+ sslPreAcceptHookState = SSL_HOOKS_INVOKE;
+ } else if (SSL_HOOKS_INVOKE == sslPreAcceptHookState) {
+ // if the state is anything else, we haven't finished
+ // the previous hook yet.
+ curHook = curHook->next();
+ }
+ if (SSL_HOOKS_INVOKE == sslPreAcceptHookState) {
+ if (0 == curHook) { // no hooks left, we're done
+ sslPreAcceptHookState = SSL_HOOKS_DONE;
+ } else {
+ sslPreAcceptHookState = SSL_HOOKS_ACTIVE;
+ ContWrapper::wrap(mutex, curHook->m_cont, TS_EVENT_VCONN_PRE_ACCEPT, this);
+ return SSL_WAIT_FOR_HOOK;
+ }
+ } else { // waiting for hook to complete
+ /* A note on waiting for the hook. I believe that because this logic
+ cannot proceed as long as a hook is outstanding, the underlying VC
+ can't go stale. If that can happen for some reason, we'll need to be
+ more clever and provide some sort of cancel mechanism. I have a trap
+ in SSLNetVConnection::free to check for this.
+ */
+ return SSL_WAIT_FOR_HOOK;
+ }
+ }
+
+ // If a blind tunnel was requested in the pre-accept calls, convert.
+ // Again no data has been exchanged, so we can go directly
+ // without data replay.
+ // Note we can't arrive here if a hook is active.
+ if (TS_SSL_HOOK_OP_TUNNEL == hookOpRequested) {
+ this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+ SSL_free(this->ssl);
+ this->ssl = NULL;
+ sslHandShakeComplete = 1;
+ return EVENT_DONE;
+ } else if (TS_SSL_HOOK_OP_TERMINATE == hookOpRequested) {
+ sslHandShakeComplete = 1;
+ return EVENT_DONE;
+ }
+
+ // All the pre-accept hooks have completed, proceed with the actual accept.
+
+ char *data_ptr = NULL;
+ int data_to_read = BIO_get_mem_data(SSL_get_rbio(this->ssl), &data_ptr);
+ if (data_to_read <= 0) { // If there is not already data in the buffer
+ // Read from socket to fill in the BIO buffer with the
+ // raw handshake data before calling the ssl accept calls.
+ int64_t data_read;
+ if ((data_read = this->read_raw_data()) > 0) {
+ data_to_read = BIO_get_mem_data(SSL_get_rbio(this->ssl), &data_ptr);
+ }
+ }
+
+ ret = SSL_accept(ssl);
int ssl_error = SSL_get_error(ssl, ret);
if (ssl_error != SSL_ERROR_NONE) {
@@ -644,6 +1018,23 @@ SSLNetVConnection::sslServerHandShakeEvent(int &err)
case SSL_ERROR_WANT_READ:
return SSL_HANDSHAKE_WANT_READ;
+// This value is only defined in openssl has been patched to
+// enable the sni callback to break out of the SSL_accept processing
+#ifdef SSL_ERROR_WANT_SNI_RESOLVE
+ case SSL_ERROR_WANT_SNI_RESOLVE:
+ if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL ||
+ TS_SSL_HOOK_OP_TUNNEL == hookOpRequested) {
+ this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+ sslHandShakeComplete = 0;
+ return EVENT_CONT;
+ }
+ else {
+ // Stopping for some other reason, perhaps loading certificate
+ //;return EVENT_ERROR;
+ return EVENT_CONT;
+ }
+#endif
+
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_X509_LOOKUP:
return EVENT_CONT;
@@ -673,7 +1064,7 @@ SSLNetVConnection::sslClientHandShakeEvent(int &err)
}
}
#endif
-
+
ret = SSL_connect(ssl);
switch (SSL_get_error(ssl, ret)) {
case SSL_ERROR_NONE:
@@ -794,3 +1185,54 @@ SSLNetVConnection::select_next_protocol(SSL * ssl, const unsigned char ** out, u
*outlen = 0;
return SSL_TLSEXT_ERR_NOACK;
}
+
+void
+SSLNetVConnection::reenable(NetHandler* nh) {
+ if (this->sslPreAcceptHookState != SSL_HOOKS_DONE) {
+ this->sslPreAcceptHookState = SSL_HOOKS_INVOKE;
+ this->readReschedule(nh);
+ } else {
+ // Reenabling from the SNI callback
+ this->sslSNIHookState = SNI_HOOKS_CONTINUE;
+ }
+}
+
+
+bool
+SSLNetVConnection::sslContextSet(void* ctx) {
+ bool zret = true;
+ if (ssl)
+ SSL_set_SSL_CTX(ssl, static_cast<SSL_CTX*>(ctx));
+ else
+ zret = false;
+ return zret;
+}
+
+bool
+SSLNetVConnection::callHooks(TSHttpHookID eventId)
+{
+ // Only dealing with the SNI hook so far
+ ink_assert(eventId == TS_SSL_SNI_HOOK);
+
+ APIHook *hook = ssl_hooks->get(TS_SSL_SNI_INTERNAL_HOOK);
+ bool reenabled = true;
+ while (hook && reenabled) {
+ // Must reset to a completed state for each invocation
+ this->sslSNIHookState = SNI_HOOKS_DONE;
+
+ // Invoke the hook
+ hook->invoke(TS_SSL_SNI_HOOK, this);
+
+ // If it did not re-enable, return the code to
+ // stop the accept processing
+ if (this->sslSNIHookState == SNI_HOOKS_DONE) {
+ reenabled = false;
+ }
+
+ // Otherwise, look for the next plugin code
+ hook = hook->next();
+ }
+ return reenabled;
+}
+
+
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/SSLUtils.cc
----------------------------------------------------------------------
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index 9ebdf2e..8a144cd 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -20,6 +20,7 @@
*/
#include "ink_config.h"
+#include "records/I_RecHttp.h"
#include "libts.h"
#include "I_Layout.h"
#include "P_Net.h"
@@ -57,6 +58,8 @@
#define SSL_CERT_TAG "ssl_cert_name"
#define SSL_PRIVATE_KEY_TAG "ssl_key_name"
#define SSL_CA_TAG "ssl_ca_name"
+#define SSL_ACTION_TAG "action"
+#define SSL_ACTION_TUNNEL_TAG "tunnel"
#define SSL_SESSION_TICKET_ENABLED "ssl_ticket_enabled"
#define SSL_SESSION_TICKET_KEY_FILE_TAG "ticket_key_name"
#define SSL_KEY_DIALOG "ssl_key_dialog"
@@ -84,7 +87,7 @@ typedef SSL_METHOD * ink_ssl_method_t;
// gather user provided settings from ssl_multicert.config in to a single struct
struct ssl_user_config
{
- ssl_user_config () : session_ticket_enabled(1) {
+ ssl_user_config () : session_ticket_enabled(1), opt(SSLCertContext::OPT_NONE) {
}
int session_ticket_enabled; // ssl_ticket_enabled - session ticket enabled
@@ -95,6 +98,7 @@ struct ssl_user_config
ats_scoped_str key; // ssl_key_name - Private key
ats_scoped_str ticket_key_filename; // ticket_key_name - session key file. [key_name (16Byte) + HMAC_secret (16Byte) + AES_key (16Byte)]
ats_scoped_str dialog; // ssl_key_dialog - Private key dialog
+ SSLCertContext::Option opt;
};
// Check if the ticket_key callback #define is available, and if so, enable session tickets.
@@ -173,27 +177,51 @@ SSL_CTX_add_extra_chain_cert_file(SSL_CTX * ctx, const char * chainfile)
#if TS_USE_TLS_SNI
static int
-ssl_servername_callback(SSL * ssl, int * ad, void * arg)
+ssl_servername_callback(SSL * ssl, int * ad, void * /*arg*/)
{
SSL_CTX * ctx = NULL;
- SSLCertLookup * lookup = (SSLCertLookup *) arg;
+ SSLCertContext * cc = NULL;
+ // Fetching the lookup via the callback arg allows for race
+ // condition with reconfigure
+ //SSLCertLookup * lookup = (SSLCertLookup *) arg;
+ SSLCertLookup *lookup = SSLCertificateConfig::acquire();
const char * servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
SSLNetVConnection * netvc = (SSLNetVConnection *)SSL_get_app_data(ssl);
+ bool found = true;
+ bool reenabled;
+ int retval = SSL_TLSEXT_ERR_OK;
- Debug("ssl", "ssl_servername_callback ssl=%p ad=%d lookup=%p server=%s handshake_complete=%d", ssl, *ad, lookup, servername,
+ Debug("ssl", "ssl_servername_callback ssl=%p ad=%d server=%s handshake_complete=%d", ssl, *ad, servername,
netvc->getSSLHandShakeComplete());
+
+ if (servername != NULL) {
+ strncpy(netvc->sniServername, servername, TS_MAX_HOST_NAME_LEN);
+ }
// catch the client renegotiation early on
if (SSLConfigParams::ssl_allow_client_renegotiation == false && netvc->getSSLHandShakeComplete()) {
Debug("ssl", "ssl_servername_callback trying to renegotiate from the client");
- return SSL_TLSEXT_ERR_ALERT_FATAL;
+ retval = SSL_TLSEXT_ERR_ALERT_FATAL;
+ goto done;
}
// 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 = lookup->findInfoInHash((char *)servername);
+ cc = lookup->find((char *)servername);
+ if (cc && cc->ctx) ctx = cc->ctx;
+ if (cc && SSLCertContext::OPT_TUNNEL == cc->opt && netvc->get_is_transparent()) {
+#ifdef SSL_TLSEXT_ERR_READ_AGAIN
+ netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+ netvc->setSSLHandShakeComplete(true);
+ retval = SSL_TLSEXT_ERR_READ_AGAIN;
+#else
+ Error("Must have openssl patch to support OPT_TUNNEL from SNI callback");
+ retval = SSL_TLSEXT_ERR_ALERT_FATAL;
+#endif
+ goto done;
+ }
}
// If there's no match on the server name, try to match on the peer address.
@@ -202,36 +230,60 @@ ssl_servername_callback(SSL * ssl, int * ad, void * arg)
int namelen = sizeof(ip);
safe_getsockname(netvc->get_socket(), &ip.sa, &namelen);
- ctx = lookup->findInfoInHash(ip);
+ cc = lookup->find(ip);
+ if (cc && cc->ctx) ctx = cc->ctx;
}
if (ctx != NULL) {
SSL_set_SSL_CTX(ssl, ctx);
}
+ else {
+ found = false;
+ }
ctx = SSL_get_SSL_CTX(ssl);
- Debug("ssl", "ssl_servername_callback found SSL context %p for requested name '%s'", ctx, servername);
+ Debug("ssl", "ssl_servername_callback %s SSL context %p for requested name '%s'", found ? "found" : "using", ctx, servername);
if (ctx == NULL) {
- return SSL_TLSEXT_ERR_NOACK;
+ retval = SSL_TLSEXT_ERR_NOACK;
+ goto done;
+ }
+
+ // Call the plugin SNI code
+ reenabled = netvc->callHooks(TS_SSL_SNI_HOOK);
+ // If it did not re-enable, return the code to
+ // stop the accept processing
+ if (!reenabled){
+#ifdef SSL_TLSEXT_ERR_READ_AGAIN
+ retval = SSL_TLSEXT_ERR_READ_AGAIN;
+#else
+ Error("Must have openssl patch to support OPT_TUNNEL from SNI callback");
+ retval = SSL_TLSEXT_ERR_ALERT_FATAL;
+#endif
+ goto done;
}
+done:
+ SSLCertificateConfig::release(lookup);
// We need to return one of the SSL_TLSEXT_ERR constants. If we return an
// error, we can fill in *ad with an alert code to propgate to the
// client, see SSL_AD_*.
- return SSL_TLSEXT_ERR_OK;
+ return retval;
}
#endif /* TS_USE_TLS_SNI */
static SSL_CTX *
-ssl_context_enable_sni(SSL_CTX * ctx, SSLCertLookup * lookup)
+ssl_context_enable_sni(SSL_CTX * ctx, SSLCertLookup * /*lookup*/)
{
#if TS_USE_TLS_SNI
if (ctx) {
Debug("ssl", "setting SNI callbacks with for ctx %p", ctx);
SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_callback);
- SSL_CTX_set_tlsext_servername_arg(ctx, lookup);
+ // Possible race conditions against reconfigure here
+ // Better to use the SSLCertificate.acquire function to access the
+ // lookup data structure safely
+ //SSL_CTX_set_tlsext_servername_arg(ctx, lookup);
}
#else
(void)lookup;
@@ -487,12 +539,13 @@ SSLRecRawStatSyncCount(const char *name, RecDataT data_type, RecData *data, RecR
if (certLookup) {
const unsigned ctxCount = certLookup->count();
for (size_t i = 0; i < ctxCount; i++) {
- SSL_CTX * ctx = certLookup->get(i);
-
- sessions += SSL_CTX_sess_accept_good(ctx);
- hits += SSL_CTX_sess_hits(ctx);
- misses += SSL_CTX_sess_misses(ctx);
- timeouts += SSL_CTX_sess_timeouts(ctx);
+ SSLCertContext *cc = certLookup->get(i);
+ if (cc && cc->ctx) {
+ sessions += SSL_CTX_sess_accept_good(cc->ctx);
+ hits += SSL_CTX_sess_hits(cc->ctx);
+ misses += SSL_CTX_sess_misses(cc->ctx);
+ timeouts += SSL_CTX_sess_timeouts(cc->ctx);
+ }
}
}
@@ -1224,7 +1277,7 @@ asn1_strdup(ASN1_STRING * s)
// table aliases for subject CN and subjectAltNames DNS without wildcard,
// insert trie aliases for those with wildcard.
static void
-ssl_index_certificate(SSLCertLookup * lookup, SSL_CTX * ctx, const char * certfile)
+ssl_index_certificate(SSLCertLookup * lookup, SSLCertContext const& cc, const char * certfile)
{
X509_NAME * subject = NULL;
X509 * cert;
@@ -1250,7 +1303,7 @@ ssl_index_certificate(SSLCertLookup * lookup, SSL_CTX * ctx, const char * certfi
ats_scoped_str name(asn1_strdup(cn));
Debug("ssl", "mapping '%s' to certificate %s", (const char *) name, certfile);
- lookup->insert(ctx, name);
+ lookup->insert(name, cc);
}
}
@@ -1266,7 +1319,7 @@ ssl_index_certificate(SSLCertLookup * lookup, SSL_CTX * ctx, const char * certfi
if (name->type == GEN_DNS) {
ats_scoped_str dns(asn1_strdup(name->d.dNSName));
Debug("ssl", "mapping '%s' to certificate %s", (const char *) dns, certfile);
- lookup->insert(ctx, dns);
+ lookup->insert(dns, cc);
}
}
@@ -1339,13 +1392,13 @@ ssl_store_ssl_context(
if (sslMultCertSettings.addr) {
if (strcmp(sslMultCertSettings.addr, "*") == 0) {
lookup->ssl_default = ctx;
- lookup->insert(ctx, sslMultCertSettings.addr);
+ lookup->insert(sslMultCertSettings.addr, SSLCertContext(ctx, sslMultCertSettings.opt));
} else {
IpEndpoint ep;
if (ats_ip_pton(sslMultCertSettings.addr, &ep) == 0) {
Debug("ssl", "mapping '%s' to certificate %s", (const char *)sslMultCertSettings.addr, (const char *)certpath);
- lookup->insert(ctx, ep);
+ lookup->insert(ep, SSLCertContext(ctx, sslMultCertSettings.opt));
} else {
Error("'%s' is not a valid IPv4 or IPv6 address", (const char *)sslMultCertSettings.addr);
}
@@ -1386,7 +1439,7 @@ ssl_store_ssl_context(
// this code is updated to reconfigure the SSL certificates, it will need some sort of
// refcounting or alternate way of avoiding double frees.
Debug("ssl", "importing SNI names from %s", (const char *)certpath);
- ssl_index_certificate(lookup, ctx, certpath);
+ ssl_index_certificate(lookup, SSLCertContext(ctx, sslMultCertSettings.opt), certpath);
if (SSLConfigParams::init_ssl_ctx_cb) {
SSLConfigParams::init_ssl_ctx_cb(ctx, true);
@@ -1439,6 +1492,15 @@ ssl_extract_certificate(
if (strcasecmp(label, SSL_KEY_DIALOG) == 0) {
sslMultCertSettings.dialog = ats_strdup(value);
}
+ if (strcasecmp(label, SSL_ACTION_TAG) == 0) {
+ if (strcasecmp(SSL_ACTION_TUNNEL_TAG, value) == 0) {
+ sslMultCertSettings.opt = SSLCertContext::OPT_TUNNEL;
+ }
+ else {
+ Error("Unrecognized action for " SSL_ACTION_TAG);
+ return false;
+ }
+ }
}
if (!sslMultCertSettings.cert) {
Error("missing %s tag", SSL_CERT_TAG);
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/UnixNetVConnection.cc
----------------------------------------------------------------------
diff --git a/iocore/net/UnixNetVConnection.cc b/iocore/net/UnixNetVConnection.cc
index ec8605c..35db956 100644
--- a/iocore/net/UnixNetVConnection.cc
+++ b/iocore/net/UnixNetVConnection.cc
@@ -384,7 +384,9 @@ write_to_net_io(NetHandler *nh, UnixNetVConnection *vc, EThread *thread)
nh->read_ready_list.remove(vc);
vc->write.triggered = 0;
nh->write_ready_list.remove(vc);
- if (!(ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT))
+ if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT)
+ read_reschedule(nh, vc);
+ else
write_reschedule(nh, vc);
} else if (ret == EVENT_DONE) {
vc->write.triggered = 1;
@@ -497,7 +499,6 @@ write_to_net_io(NetHandler *nh, UnixNetVConnection *vc, EThread *thread)
} else if (signalled && (wbe_event != vc->write_buffer_empty_event)) {
// @a signalled means we won't send an event, and the event values differing means we
// had a write buffer trap and cleared it, so we need to send it now.
- Debug("amc", "empty write buffer trap [%d]", wbe_event);
if (write_signal_and_update(wbe_event, vc) != EVENT_CONT)
return;
} else if (!signalled) {
@@ -1187,6 +1188,7 @@ UnixNetVConnection::free(EThread *t)
action_.mutex.clear();
got_remote_addr = 0;
got_local_addr = 0;
+ attributes = 0;
read.vio.mutex.clear();
write.vio.mutex.clear();
flags = 0;
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/records/I_RecHttp.h
----------------------------------------------------------------------
diff --git a/lib/records/I_RecHttp.h b/lib/records/I_RecHttp.h
index c121dfe..32a9f2d 100644
--- a/lib/records/I_RecHttp.h
+++ b/lib/records/I_RecHttp.h
@@ -181,11 +181,12 @@ public:
/// Type of transport on the connection.
enum TransportType {
- TRANSPORT_DEFAULT = 0, ///< Default (normal HTTP).
- TRANSPORT_COMPRESSED, ///< Compressed HTTP.
+ TRANSPORT_NONE = 0, ///< Unspecified / uninitialized
+ TRANSPORT_DEFAULT, ///< Default (normal HTTP).
+ TRANSPORT_COMPRESSED, ///< Compressed HTTP.
TRANSPORT_BLIND_TUNNEL, ///< Blind tunnel (no processing).
- TRANSPORT_SSL, ///< SSL connection.
- TRANSPORT_PLUGIN /// < Protocol plugin connection
+ TRANSPORT_SSL, ///< SSL connection.
+ TRANSPORT_PLUGIN /// < Protocol plugin connection
};
int m_fd; ///< Pre-opened file descriptor if present.
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/ts/Vec.h
----------------------------------------------------------------------
diff --git a/lib/ts/Vec.h b/lib/ts/Vec.h
index 589da0e..9dcf933 100644
--- a/lib/ts/Vec.h
+++ b/lib/ts/Vec.h
@@ -61,6 +61,7 @@ class Vec {
void push_back(C a) { add(a); } // std::vector name
bool add_exclusive(C a);
C& add();
+ void drop();
C pop();
void reset();
void clear();
@@ -211,6 +212,12 @@ Vec<C,A,S>::add() {
return *ret;
}
+template <class C, class A, int S> inline void
+Vec<C,A,S>::drop() {
+ if (n && 0 == --n)
+ clear();
+}
+
template <class C, class A, int S> inline C
Vec<C,A,S>::pop() {
if (!n)
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/ts/apidefs.h.in
----------------------------------------------------------------------
diff --git a/lib/ts/apidefs.h.in b/lib/ts/apidefs.h.in
index 96896f6..2b62427 100644
--- a/lib/ts/apidefs.h.in
+++ b/lib/ts/apidefs.h.in
@@ -255,6 +255,10 @@ extern "C"
The two transform hooks can ONLY be added as transaction hooks.
+ TS_VCONN_PRE_ACCEPT_HOOK - Called before the SSL hand
+ shake starts. No handshake data has been read or sent (from the
+ proxy) at this point
+
TS_HTTP_LAST_HOOK _must_ be the last element. Only right place
to insert a new element is just before TS_HTTP_LAST_HOOK.
@@ -278,6 +282,12 @@ extern "C"
TS_HTTP_PRE_REMAP_HOOK,
TS_HTTP_POST_REMAP_HOOK,
TS_HTTP_RESPONSE_CLIENT_HOOK,
+ // Putting the SSL hooks in the same enum space
+ // So both sets of hooks can be set by the same Hook function
+ TS_SSL_FIRST_HOOK,
+ TS_VCONN_PRE_ACCEPT_HOOK = TS_SSL_FIRST_HOOK,
+ TS_SSL_SNI_HOOK,
+ TS_SSL_LAST_HOOK = TS_SSL_SNI_HOOK,
TS_HTTP_LAST_HOOK
} TSHttpHookID;
#define TS_HTTP_READ_REQUEST_PRE_REMAP_HOOK TS_HTTP_PRE_REMAP_HOOK /* backwards compat */
@@ -437,6 +447,7 @@ extern "C"
TS_EVENT_LIFECYCLE_CACHE_READY = 60020,
TS_EVENT_LIFECYCLE_SERVER_SSL_CTX_INITIALIZED = 60021,
TS_EVENT_LIFECYCLE_CLIENT_SSL_CTX_INITIALIZED = 60022,
+ TS_EVENT_VCONN_PRE_ACCEPT = 60023,
TS_EVENT_MGMT_UPDATE = 60100,
/* EVENTS 60200 - 60202 for internal use */
@@ -781,6 +792,13 @@ extern "C"
TS_MILESTONE_LAST_ENTRY
} TSMilestonesType;
+ /// Operation requested by asynchronous hooks during SSL processing
+ typedef enum {
+ TS_SSL_HOOK_OP_DEFAULT, ///< Null / initialization value. Do normal processing.
+ TS_SSL_HOOK_OP_TUNNEL, ///< Switch to blind tunnel
+ TS_SSL_HOOK_OP_TERMINATE, ///< Termination connection / transaction.
+ TS_SSL_HOOK_OP_LAST = TS_SSL_HOOK_OP_TERMINATE ///< End marker value.
+ } TSSslVConnOp;
/* These typedefs are used with the corresponding TSMgmt*Get functions
for storing the values retrieved by those functions. For example,
@@ -796,6 +814,7 @@ extern "C"
typedef struct tsapi_mbuffer* TSMBuffer;
typedef struct tsapi_httpssn* TSHttpSsn;
typedef struct tsapi_httptxn* TSHttpTxn;
+ typedef struct tsapi_ssl_obj* TSSslConnection;
typedef struct tsapi_httpaltinfo* TSHttpAltInfo;
typedef struct tsapi_mimeparser* TSMimeParser;
typedef struct tsapi_httpparser* TSHttpParser;
@@ -810,6 +829,7 @@ extern "C"
typedef struct tsapi_config* TSConfig;
typedef struct tsapi_cont* TSCont;
typedef struct tsapi_cont* TSVConn; /* a VConn is really a specialized TSCont */
+ typedef struct tsapi_ssl_context* TSSslContext;
typedef struct tsapi_action* TSAction;
typedef struct tsapi_iobuffer* TSIOBuffer;
typedef struct tsapi_iobufferdata* TSIOBufferData;
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/ts/ink_sock.cc
----------------------------------------------------------------------
diff --git a/lib/ts/ink_sock.cc b/lib/ts/ink_sock.cc
index 7429e53..055cd38 100644
--- a/lib/ts/ink_sock.cc
+++ b/lib/ts/ink_sock.cc
@@ -192,6 +192,16 @@ safe_getsockname(int s, struct sockaddr *name, int *namelen)
return r;
}
+int
+safe_getpeername(int s, struct sockaddr *name, int *namelen)
+{
+ int r;
+ do {
+ r = getpeername(s, name, (socklen_t*)namelen);
+ } while (r < 0 && (errno == EAGAIN || errno == EINTR));
+ return r;
+}
+
char
fd_read_char(int fd)
{
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/ts/ink_sock.h
----------------------------------------------------------------------
diff --git a/lib/ts/ink_sock.h b/lib/ts/ink_sock.h
index 39488d9..0f6660d 100644
--- a/lib/ts/ink_sock.h
+++ b/lib/ts/ink_sock.h
@@ -40,6 +40,7 @@ int safe_getsockopt(int s, int level, int optname, char *optval, int *optlevel);
int safe_bind(int s, struct sockaddr const* name, int namelen);
int safe_listen(int s, int backlog);
int safe_getsockname(int s, struct sockaddr *name, int *namelen);
+int safe_getpeername(int s, struct sockaddr *name, int *namelen);
int safe_fcntl(int fd, int cmd, int arg);
int safe_ioctl(int fd, int request, char *arg);
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/plugins/experimental/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am
index 7e5ab51..7bb1452 100644
--- a/plugins/experimental/Makefile.am
+++ b/plugins/experimental/Makefile.am
@@ -32,6 +32,7 @@ SUBDIRS = \
regex_revalidate \
remap_stats \
s3_auth \
+ ssl_cert_loader \
sslheaders \
stale_while_revalidate \
url_sig \
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/plugins/experimental/ssl_cert_loader/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/ssl_cert_loader/Makefile.am b/plugins/experimental/ssl_cert_loader/Makefile.am
new file mode 100644
index 0000000..def7a7f
--- /dev/null
+++ b/plugins/experimental/ssl_cert_loader/Makefile.am
@@ -0,0 +1,26 @@
+# 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 $(top_srcdir)/build/plugins.mk
+
+AM_CPPFLAGS += -I$(top_srcdir)/lib -I$(top_srcdir)/lib/ts
+
+pkglib_LTLIBRARIES = ssl_cert_loader.la
+
+ssl_cert_loader_la_SOURCES = ssl-cert-loader.cc ip.cc IpMap.cc domain-tree.cc
+ssl_cert_loader_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) -L$(top_srcdir)/lib/tsconfig/.libs -ltsconfig
+
+
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/plugins/experimental/ssl_cert_loader/ats-util.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/ssl_cert_loader/ats-util.h b/plugins/experimental/ssl_cert_loader/ats-util.h
new file mode 100644
index 0000000..1164a20
--- /dev/null
+++ b/plugins/experimental/ssl_cert_loader/ats-util.h
@@ -0,0 +1,40 @@
+# if !defined(_ats_util_h)
+# define _ats_util_h
+
+# if defined(__cplusplus)
+/** Set data to zero.
+
+ Calls @c memset on @a t with a value of zero and a length of @c
+ sizeof(t). This can be used on ordinary and array variables. While
+ this can be used on variables of intrinsic type it's inefficient.
+
+ @note Because this uses templates it cannot be used on unnamed or
+ locally scoped structures / classes. This is an inherent
+ limitation of templates.
+
+ Examples:
+ @code
+ foo bar; // value.
+ ink_zero(bar); // zero bar.
+
+ foo *bar; // pointer.
+ ink_zero(bar); // WRONG - makes the pointer @a bar zero.
+ ink_zero(*bar); // zero what bar points at.
+
+ foo bar[ZOMG]; // Array of structs.
+ ink_zero(bar); // Zero all structs in array.
+
+ foo *bar[ZOMG]; // array of pointers.
+ ink_zero(bar); // zero all pointers in the array.
+ @endcode
+
+ */
+template < typename T > inline void
+ink_zero(
+ T& t ///< Object to zero.
+ ) {
+ memset(&t, 0, sizeof(t));
+}
+# endif /* __cplusplus */
+
+# endif // ats-util.h