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 */

Reply via email to