Repository: trafficserver Updated Branches: refs/heads/master 08f19da9b -> 34ca6f2dc
[TS-3153]: Ability to disable or modify npn advertisement based on SNI Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/24262d8f Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/24262d8f Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/24262d8f Branch: refs/heads/master Commit: 24262d8f6a14b6bb7bf7288f6309a68f6dc8589b Parents: 08f19da Author: Sudheer Vinukonda <[email protected]> Authored: Mon Nov 17 21:10:59 2014 +0000 Committer: Sudheer Vinukonda <[email protected]> Committed: Mon Nov 17 21:10:59 2014 +0000 ---------------------------------------------------------------------- configure.ac | 1 + iocore/net/P_SSLNetVConnection.h | 6 + iocore/net/SSLNetVConnection.cc | 82 +++++++- iocore/net/SSLUtils.cc | 5 + plugins/experimental/Makefile.am | 1 + plugins/experimental/sni_proto_nego/Makefile.am | 21 ++ .../sni_proto_nego/sni_proto_nego.cc | 194 +++++++++++++++++++ proxy/InkAPI.cc | 10 + proxy/api/ts/ts.h | 1 + 9 files changed, 320 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/24262d8f/configure.ac ---------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index 3e4465b..91e9874 100644 --- a/configure.ac +++ b/configure.ac @@ -1945,6 +1945,7 @@ AS_IF([test "x$enable_experimental_plugins" = xyes], [ plugins/experimental/regex_revalidate/Makefile plugins/experimental/remap_stats/Makefile plugins/experimental/s3_auth/Makefile + plugins/experimental/sni_proto_nego/Makefile plugins/experimental/sslheaders/Makefile plugins/experimental/ssl_cert_loader/Makefile plugins/experimental/stale_while_revalidate/Makefile http://git-wip-us.apache.org/repos/asf/trafficserver/blob/24262d8f/iocore/net/P_SSLNetVConnection.h ---------------------------------------------------------------------- diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h index c481c8b..1dc7071 100644 --- a/iocore/net/P_SSLNetVConnection.h +++ b/iocore/net/P_SSLNetVConnection.h @@ -122,6 +122,9 @@ public: static int advertise_next_protocol(SSL * ssl, const unsigned char ** out, unsigned * outlen, void *); static int select_next_protocol(SSL * ssl, const unsigned char ** out, unsigned char * outlen, const unsigned char * in, unsigned inlen, void *); + bool modify_npn_advertisement(const unsigned char ** list, unsigned cnt); + bool setAdvertiseProtocols(const unsigned char ** list, unsigned cnt); + Continuation * endpoint() const { return npnEndpoint; } @@ -198,6 +201,9 @@ private: const SSLNextProtocolSet * npnSet; Continuation * npnEndpoint; + unsigned char * npnAdvertised; + size_t npnszAdvertised; + int npnAdvertisedBufIndex; }; typedef int (SSLNetVConnection::*SSLNetVConnHandler) (int, void *); http://git-wip-us.apache.org/repos/asf/trafficserver/blob/24262d8f/iocore/net/SSLNetVConnection.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc index 4a9ec29..60fcbf9 100644 --- a/iocore/net/SSLNetVConnection.cc +++ b/iocore/net/SSLNetVConnection.cc @@ -27,6 +27,8 @@ #include "P_SSLUtils.h" #include "InkAPIInternal.h" // Added to include the ssl_hook definitions +extern unsigned char * append_protocol(const char * proto, unsigned char * buf); + // Defined in SSLInternal.c, should probably make a separate include // file for this at some point void SSL_set_rbio(SSLNetVConnection *sslvc, BIO *rbio); @@ -776,7 +778,10 @@ SSLNetVConnection::SSLNetVConnection(): sslPreAcceptHookState(SSL_HOOKS_INIT), sslSNIHookState(SNI_HOOKS_INIT), npnSet(NULL), - npnEndpoint(NULL) + npnEndpoint(NULL), + npnAdvertised(NULL), + npnszAdvertised(0), + npnAdvertisedBufIndex(-1) { } @@ -815,6 +820,9 @@ SSLNetVConnection::free(EThread * t) { hookOpRequested = TS_SSL_HOOK_OP_DEFAULT; npnSet = NULL; npnEndpoint= NULL; + npnAdvertised = NULL; + npnszAdvertised = 0; + npnAdvertisedBufIndex = -1; if (from_accept_thread) { sslNetVCAllocator.free(this); @@ -1160,6 +1168,14 @@ SSLNetVConnection::advertise_next_protocol(SSL *ssl, const unsigned char **out, ink_release_assert(netvc != NULL); + // check if there's a SNI based customized advertisement + if (netvc->npnAdvertised && netvc->npnszAdvertised) { + *out = netvc->npnAdvertised; + *outlen = netvc->npnszAdvertised; + return SSL_TLSEXT_ERR_OK; + } + + // use default endPoint advertisement if (netvc->npnSet && netvc->npnSet->advertiseProtocols(out, outlen)) { // Successful return tells OpenSSL to advertise. return SSL_TLSEXT_ERR_OK; @@ -1168,6 +1184,70 @@ SSLNetVConnection::advertise_next_protocol(SSL *ssl, const unsigned char **out, return SSL_TLSEXT_ERR_NOACK; } +bool +SSLNetVConnection::modify_npn_advertisement(const unsigned char ** list, unsigned cnt) +{ + unsigned char* advertised = npnAdvertised; + + for (unsigned int i=0; i<cnt; i++) { + const char* proto = (const char*) list[i]; + Debug("ssl", "advertising protocol %s", proto); + advertised = append_protocol(proto, advertised); + } + + return true; +} + +bool +SSLNetVConnection::setAdvertiseProtocols(const unsigned char ** list, unsigned cnt) +{ + size_t total_len = 0; + + if (cnt == 0) { + // set default list based on server_ports config + if (npnAdvertised) { + ink_assert (npnAdvertisedBufIndex >= 0); + ioBufAllocator[npnAdvertisedBufIndex].free_void(npnAdvertised); + npnAdvertised = NULL; + npnszAdvertised = 0; + npnAdvertisedBufIndex = -1; + } + return true; + } + + // validate the modified npn list + for (unsigned int i=0; i<cnt; i++) { + const char* proto = (const char*) list[i]; + size_t len = strlen(proto); + + // Both ALPN and NPN only allow 255 bytes of protocol name. + if (len > 255) { + return false; + } + + if (!npnSet->findEndpoint((const unsigned char *)proto, len)) { + return false; + } + total_len += (len + 1); + } + + if (npnAdvertised) { + ink_assert (npnAdvertisedBufIndex >= 0); + ioBufAllocator[npnAdvertisedBufIndex].free_void(npnAdvertised); + } + + npnszAdvertised = total_len; + npnAdvertisedBufIndex = buffer_size_to_index(npnszAdvertised); + npnAdvertised = (unsigned char *)ioBufAllocator[npnAdvertisedBufIndex].alloc_void(); + if (npnAdvertised == NULL) { + npnszAdvertised = 0; + npnAdvertisedBufIndex = -1; + return false; + } + + return modify_npn_advertisement(list, cnt); +} + // ALPN TLS extension callback. Given the client's set of offered // protocols, we have to select a protocol to use for this session. int http://git-wip-us.apache.org/repos/asf/trafficserver/blob/24262d8f/iocore/net/SSLUtils.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc index 537cc9d..a6ef4b7 100644 --- a/iocore/net/SSLUtils.cc +++ b/iocore/net/SSLUtils.cc @@ -305,6 +305,11 @@ ssl_servername_callback(SSL * ssl, int * ad, void * /*arg*/) goto done; } + // set the default +#if TS_USE_TLS_NPN + SSL_CTX_set_next_protos_advertised_cb(ctx, SSLNetVConnection::advertise_next_protocol, NULL); +#endif /* TS_USE_TLS_NPN */ + // Call the plugin SNI code reenabled = netvc->callHooks(TS_SSL_SNI_HOOK); // If it did not re-enable, return the code to http://git-wip-us.apache.org/repos/asf/trafficserver/blob/24262d8f/plugins/experimental/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am index 51b06f0..091557d 100644 --- a/plugins/experimental/Makefile.am +++ b/plugins/experimental/Makefile.am @@ -33,6 +33,7 @@ SUBDIRS = \ regex_revalidate \ remap_stats \ s3_auth \ + sni_proto_nego \ ssl_cert_loader \ sslheaders \ stale_while_revalidate \ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/24262d8f/plugins/experimental/sni_proto_nego/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/sni_proto_nego/Makefile.am b/plugins/experimental/sni_proto_nego/Makefile.am new file mode 100644 index 0000000..958634c --- /dev/null +++ b/plugins/experimental/sni_proto_nego/Makefile.am @@ -0,0 +1,21 @@ +# 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 + +pkglib_LTLIBRARIES = sni_proto_nego.la +sni_proto_nego_la_SOURCES = sni_proto_nego.cc +sni_proto_nego_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) http://git-wip-us.apache.org/repos/asf/trafficserver/blob/24262d8f/plugins/experimental/sni_proto_nego/sni_proto_nego.cc ---------------------------------------------------------------------- diff --git a/plugins/experimental/sni_proto_nego/sni_proto_nego.cc b/plugins/experimental/sni_proto_nego/sni_proto_nego.cc new file mode 100644 index 0000000..cd1f4db --- /dev/null +++ b/plugins/experimental/sni_proto_nego/sni_proto_nego.cc @@ -0,0 +1,194 @@ +#include <stdio.h> +#include <ts/ts.h> +#include <ts/apidefs.h> +#include <openssl/ssl.h> +#include <string> +#include <map> +#include <string.h> + +using namespace std; + +const char* PLUGIN_NAME = "sni_proto_nego"; +const int MAX_BUFFER_SIZE = 1024; +const int MAX_FILE_PATH_SIZE = 1024; +const unsigned int MAX_PROTO_LIST_LEN = 100; +const unsigned int MAX_PROTO_NAME_LEN = 255; + +typedef struct { + bool enableNpn; + unsigned int npn_proto_list_count; + unsigned char npn_proto_list [MAX_PROTO_LIST_LEN] [MAX_PROTO_NAME_LEN]; +} SNIProtoConfig; + +typedef map<string, SNIProtoConfig> stringMap; +static stringMap _sniProtoMap; + +static +bool read_config(char* config_file) { + char file_path[MAX_FILE_PATH_SIZE]; + TSFile file; + if (config_file == NULL) { + TSError("invalid config file"); + return false; + } + TSDebug(PLUGIN_NAME, "trying to open config file in this path: %s", file_path); + file = TSfopen(config_file, "r"); + if (file == NULL) { + snprintf(file_path, sizeof(file_path), "%s/%s", TSInstallDirGet(), config_file); + file = TSfopen(file_path, "r"); + if (file == NULL) { + TSError("Failed to open config file %s", config_file); + return false; + } + } + char buffer[MAX_BUFFER_SIZE]; + memset(buffer, 0, sizeof(buffer)); + while (TSfgets(file, buffer, sizeof(buffer) - 1) != NULL) { + char *eol = 0; + // make sure line was not bigger than buffer + if ((eol = strchr(buffer, '\n')) == NULL && (eol = strstr(buffer, "\r\n")) == NULL) { + TSError("sni_proto_nego line too long, did not get a good line in cfg, skipping, line: %s", buffer); + memset(buffer, 0, sizeof(buffer)); + continue; + } + // make sure line has something useful on it + if (eol - buffer < 2 || buffer[0] == '#') { + memset(buffer, 0, sizeof(buffer)); + continue; + } + char* cfg = strtok(buffer, "\n\r\n"); + + if (cfg != NULL) { + TSDebug(PLUGIN_NAME, "setting SniProto based on string: %s", cfg); + + char* domain = strtok(buffer, " "); + SNIProtoConfig sniProtoConfig = {1, 1}; + + if (domain) { + if ((*domain == '*') && (domain+1) && (*(domain+1)=='.')) { + domain += 2; + if (domain == NULL) { + continue; + } + } + char* sni_proto_config = strtok (NULL, " "); + if (sni_proto_config) { + sniProtoConfig.enableNpn = atoi(sni_proto_config); + TSDebug(PLUGIN_NAME, "npn_proto_config %d", sniProtoConfig.enableNpn); + sni_proto_config = strtok (NULL, " "); + // now get the npn proto advertisment list + sni_proto_config = strtok (NULL, " "); + sniProtoConfig.npn_proto_list_count = 0; + while (sni_proto_config != NULL) { + char* proto = strtok(NULL, "|"); + if ((proto == NULL) || + (sniProtoConfig.npn_proto_list_count >= MAX_PROTO_LIST_LEN) || + (strlen(proto) >= MAX_PROTO_NAME_LEN)) { + break; + } + _TSstrlcpy((char*)sniProtoConfig.npn_proto_list[sniProtoConfig.npn_proto_list_count++], proto, (strlen(proto) + 1)); + } + } + _sniProtoMap.insert(make_pair(domain, sniProtoConfig)); + } + + memset(buffer, 0, sizeof(buffer)); + } + } + + TSfclose(file); + + TSDebug(PLUGIN_NAME, "Done parsing config"); + + return true; +} + + +static void +init_sni_callback(void *sslNetVC) +{ + TSVConn ssl_vc = reinterpret_cast<TSVConn>(sslNetVC); + TSSslConnection sslobj = TSVConnSSLConnectionGet(ssl_vc); + SSL *ssl = reinterpret_cast<SSL *>(sslobj); + const char *serverName = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + SSL_CTX * ctx = SSL_get_SSL_CTX(ssl); + + if (serverName == NULL) { + TSDebug(PLUGIN_NAME, "invalid ssl netVC %p, servername %s for ssl obj %p", sslNetVC, serverName, ssl); + return; + } + + TSDebug(PLUGIN_NAME, "ssl netVC %p, servername %s for ssl obj %p", sslNetVC, serverName, ssl); + + stringMap::iterator it; + it=_sniProtoMap.find(serverName); + + // check for wild-card domains + if(it==_sniProtoMap.end()) { + char* domain = strstr((char*)serverName, "."); + if (domain && (domain+1)) { + it=_sniProtoMap.find(domain+1); + } + } + + if (it!=_sniProtoMap.end()) { + SNIProtoConfig sniProtoConfig = it->second; + if (!sniProtoConfig.enableNpn) { + TSDebug(PLUGIN_NAME, "disabling NPN for serverName %s", serverName); + SSL_CTX_set_next_protos_advertised_cb(ctx, NULL, NULL); + } else { + TSDebug(PLUGIN_NAME, "setting NPN advertised list for %s", serverName); + TSSslAdvertiseProtocolSet(ssl_vc, (const unsigned char **)sniProtoConfig.npn_proto_list, sniProtoConfig.npn_proto_list_count); + } + } else { + TSDebug(PLUGIN_NAME, "setting NPN advertised list for %s", serverName); + TSSslAdvertiseProtocolSet(ssl_vc, NULL, 0); + } +} + +int +SSLSniInitCallbackHandler(TSCont cont, TSEvent id, void* sslNetVC) { + (void) cont; + TSDebug(PLUGIN_NAME, "SSLSniInitCallbackHandler with id %d", id); + switch (id) { + case TS_SSL_SNI_HOOK: + { + init_sni_callback(sslNetVC); + } + break; + + default: + TSDebug(PLUGIN_NAME, "Unexpected event %d", id); + break; + } + + return TS_EVENT_NONE; +} + +void +TSPluginInit(int argc, const char *argv[]) +{ + (void) argc; + TSPluginRegistrationInfo info; + + info.plugin_name = (char *)("sni_proto_nego"); + info.vendor_name = (char *)("ats"); + + if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) { + TSError("Plugin registration failed."); + } + + char* config_file = (char*)"conf/sni_proto_nego/sni_proto_nego.config"; + + if (argc >= 2) { + config_file = (char*)argv[1]; + } + + if (!read_config(config_file)) { + TSDebug(PLUGIN_NAME, "nothing to do.."); + return; + } + + TSCont cont = TSContCreate(SSLSniInitCallbackHandler, NULL); + TSHttpHookAdd(TS_SSL_SNI_HOOK, cont); +} http://git-wip-us.apache.org/repos/asf/trafficserver/blob/24262d8f/proxy/InkAPI.cc ---------------------------------------------------------------------- diff --git a/proxy/InkAPI.cc b/proxy/InkAPI.cc index 62f0870..d61e997 100644 --- a/proxy/InkAPI.cc +++ b/proxy/InkAPI.cc @@ -8757,6 +8757,16 @@ tsapi int TSVConnIsSsl(TSVConn sslp) return ssl_vc != NULL; } +tsapi TSReturnCode +TSSslAdvertiseProtocolSet(TSVConn sslp, const unsigned char ** list, unsigned int count) +{ + NetVConnection *vc = reinterpret_cast<NetVConnection*>(sslp); + SSLNetVConnection *ssl_vc = dynamic_cast<SSLNetVConnection*>(vc); + sdk_assert(sdk_sanity_check_null_ptr((void*)ssl_vc) == TS_SUCCESS); + ssl_vc->setAdvertiseProtocols(list, count); + return TS_SUCCESS; +} + void TSVConnReenable(TSVConn vconn) { http://git-wip-us.apache.org/repos/asf/trafficserver/blob/24262d8f/proxy/api/ts/ts.h ---------------------------------------------------------------------- diff --git a/proxy/api/ts/ts.h b/proxy/api/ts/ts.h index b5b0abe..8950b5c 100644 --- a/proxy/api/ts/ts.h +++ b/proxy/api/ts/ts.h @@ -1238,6 +1238,7 @@ extern "C" tsapi TSSslContext TSSslContextFindByAddr(struct sockaddr const*); // Returns 1 if the sslp argument refers to a SSL connection tsapi int TSVConnIsSsl(TSVConn sslp); + tsapi TSReturnCode TSSslAdvertiseProtocolSet(TSVConn sslp, const unsigned char ** list, unsigned int count); /* -------------------------------------------------------------------------- HTTP transactions */
