> On Nov 17, 2014, at 1:12 PM, sudhe...@apache.org wrote: > > 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 <sudhe...@yahoo-inc.com> > Authored: Mon Nov 17 21:10:59 2014 +0000 > Committer: Sudheer Vinukonda <sudhe...@yahoo-inc.com> > 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);
This is non-static by accident. Please make a static helper API in SSLNextProtocolSet to construct the advertisement. Maybe something like this: size_t SSLNextProtocolSet::create_advertisement(const char **, unsigned, unsigned char * buf, size_t bufsz) > + > // 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) You don't need to store npnAdvertisedBufIndex, since the size tells you which iobuf allocator to use. > { > } > > @@ -815,6 +820,9 @@ SSLNetVConnection::free(EThread * t) { > hookOpRequested = TS_SSL_HOOK_OP_DEFAULT; > npnSet = NULL; > npnEndpoint= NULL; > + npnAdvertised = NULL; > + npnszAdvertised = 0; > + npnAdvertisedBufIndex = -1; I think you need to release npnAdvertised here. > > 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; > + } I believe that Alan's suggestion was to construct a new SSLNextProtocolSet and attach it to SSLNetVConnection::npnSet. You could use the lower pointer bits to know whether you have a shared or unique set. You also need to make some changes to SSLNetVConnection::select_next_protocol(). We don't distinguish between NPN and ALPN. > + // 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) I don't really see why you need this. Just do it inline in the caller. > +{ > + 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) This should be spelled setAdvertisedProtocols > +{ > + size_t total_len = 0; > + > + if (cnt == 0) { list == NULL or cnt == 0 should just fail. The npn set already is the default. I can see that this would let you return to the default after you have set the advertisement once, but I don't think that would ever get used. > + // 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++) { Style -- there should be single space around binary operators. i = 0; i < cnt > + 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; > + } There's no way for this to happen since you require the protocols to already be in the set. > + > + 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); Should this be iobuffer_size_to_index()? > + 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 */ What is this change for? > + > // 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); Config files are always relative to TSConfigDirGet() > + 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"; Is this some Yahoo thing? > + > + 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) list should be "const char **", there's no unsigned there. > +{ > + 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); Don't crash if it's not a SSL VC, just fail. > + ssl_vc->setAdvertiseProtocols(list, count); Propagate the return code. > + 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 */ >