You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jp...@apache.org on 2012/02/19 17:47:50 UTC

[1/5] git commit: TS-841: support TLS NextProtocol negotiation

Updated Branches:
  refs/heads/master 19f367381 -> c85a874f4


TS-841: support TLS NextProtocol negotiation

  - Add a Next Protocol accept handler to maintain the supported
    protocols. Route the OpenSSL NPN callbacks up through the
    SSLNetVConnection so we can advertise the correct protocol set.
    Add a helper class to perform the OpenSSL protocol list serialization.

  - Keep a back pointer from the SSL connection to the SSLNetVConnection
    so that we can route the SSL accept through the NextProtocol acceptor.

  - Cache the selected endpoint on the SSLVConnection so we can
    tell which endpoint to route the accept to. Add a default endpoint
    for backwards compatibility. Create a SSLNextProtocolSet API
    to keep a proper endpoint mapping.

  - If we are building the plugin API, track the SSL protocol
    acceptors so that we can register endpoints on them later using
    the TSAPI.

  - Add TSNetAcceptNamedProtocol plugin API and make NPN protocol
    names API constants.


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/c85a874f
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/c85a874f
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/c85a874f

Branch: refs/heads/master
Commit: c85a874f4d92278e5bc183b587dde8a5d4491d9e
Parents: b282375
Author: James Peach <jp...@apache.org>
Authored: Sat Feb 11 20:30:11 2012 -0800
Committer: James Peach <jp...@apache.org>
Committed: Sun Feb 19 08:46:10 2012 -0800

----------------------------------------------------------------------
 iocore/net/Makefile.am               |    2 +
 iocore/net/P_SSLNetVConnection.h     |   25 +++-
 iocore/net/P_SSLNextProtocolAccept.h |   61 ++++++++++
 iocore/net/P_SSLNextProtocolSet.h    |   68 +++++++++++
 iocore/net/SSLNetProcessor.cc        |   32 +-----
 iocore/net/SSLNetVConnection.cc      |   70 +++++++++--
 iocore/net/SSLNextProtocolAccept.cc  |  118 +++++++++++++++++++
 iocore/net/SSLNextProtocolSet.cc     |  182 +++++++++++++++++++++++++++++
 lib/ts/List.h                        |   10 ++-
 lib/ts/ink_mutex.h                   |   14 +++
 proxy/InkAPI.cc                      |   27 +++++
 proxy/api/ts/ts.h.in                 |   31 +++++
 proxy/http/HttpAccept.h              |    2 +-
 proxy/http/HttpProxyServerMain.cc    |   50 ++++++++-
 14 files changed, 641 insertions(+), 51 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/iocore/net/Makefile.am
----------------------------------------------------------------------
diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am
index e78aba2..fed40de 100644
--- a/iocore/net/Makefile.am
+++ b/iocore/net/Makefile.am
@@ -79,6 +79,8 @@ libinknet_a_SOURCES = \
   SSLNetProcessor.cc \
   SSLNetVConnection.cc \
   SSLNetAccept.cc \
+  SSLNextProtocolAccept.cc \
+  SSLNextProtocolSet.cc \
   UDPIOEvent.cc \
   UnixConnection.cc \
   UnixNetAccept.cc \

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/iocore/net/P_SSLNetVConnection.h
----------------------------------------------------------------------
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index dbf9372..90c0fb6 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -40,6 +40,7 @@
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 
+class SSLNextProtocolSet;
 
 //////////////////////////////////////////////////////////////////
 //
@@ -107,22 +108,36 @@ public:
   int sslClientHandShakeEvent(int &err);
   virtual void net_read_io(NetHandler * nh, EThread * lthread);
   virtual int64_t load_buffer_and_write(int64_t towrite, int64_t &wattempted, int64_t &total_wrote, MIOBufferAccessor & buf);
-  virtual ~ SSLNetVConnection() { }
+
+  void registerNextProtocolSet(const SSLNextProtocolSet *);
+
   ////////////////////////////////////////////////////////////
-  // instances of NetVConnection should be allocated        //
+  // Instances of NetVConnection should be allocated        //
   // only from the free list using NetVConnection::alloc(). //
-  // The constructo is public just to avoid compile errors. //
+  // The constructor is public just to avoid compile errors.//
   ////////////////////////////////////////////////////////////
   SSLNetVConnection();
+  virtual ~SSLNetVConnection() { }
+
   SSL *ssl;
   X509 *client_cert;
   X509 *server_cert;
 
+  static int advertise_next_protocol(SSL *ssl,
+    const unsigned char **out, unsigned int *outlen, void *arg);
+
+  Continuation * endpoint() const {
+    return npnEndpoint;
+  }
+
 private:
-  bool sslHandShakeComplete;
-  bool sslClientConnection;
   SSLNetVConnection(const SSLNetVConnection &);
   SSLNetVConnection & operator =(const SSLNetVConnection &);
+
+  bool sslHandShakeComplete;
+  bool sslClientConnection;
+  const SSLNextProtocolSet * npnSet;
+  Continuation * npnEndpoint;
 };
 
 typedef int (SSLNetVConnection::*SSLNetVConnHandler) (int, void *);

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/iocore/net/P_SSLNextProtocolAccept.h
----------------------------------------------------------------------
diff --git a/iocore/net/P_SSLNextProtocolAccept.h b/iocore/net/P_SSLNextProtocolAccept.h
new file mode 100644
index 0000000..7c44bbc
--- /dev/null
+++ b/iocore/net/P_SSLNextProtocolAccept.h
@@ -0,0 +1,61 @@
+/** @file
+
+  SSLNextProtocolAccept
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#ifndef P_SSLNextProtocolAccept_H_
+#define P_SSLNextProtocolAccept_H_
+
+#include "P_Net.h"
+#include "P_EventSystem.h"
+#include "P_UnixNet.h"
+#include "P_SSLNetVConnection.h"
+#include "P_SSLNextProtocolSet.h"
+#include "I_IOBuffer.h"
+
+class SSLNextProtocolAccept: public Continuation
+{
+public:
+  SSLNextProtocolAccept(Continuation *);
+  ~SSLNextProtocolAccept();
+
+  // Register handler as an endpoint for the specified protocol. Neither
+  // handler nor protocol are copied, so the caller must guarantee their
+  // lifetime is at least as long as that of the acceptor.
+  bool registerEndpoint(const char * protocol, Continuation * handler);
+
+  // Unregister the handler. Returns false if this protocol is not registered
+  // or if it is not registered for the specified handler.
+  bool unregisterEndpoint(const char * protocol, Continuation * handler);
+
+  SLINK(SSLNextProtocolAccept, link);
+
+private:
+  int mainEvent(int event, void * netvc);
+  SSLNextProtocolAccept(const SSLNextProtocolAccept &); // disabled
+  SSLNextProtocolAccept& operator =(const SSLNextProtocolAccept&); // disabled
+
+  MIOBuffer * buffer; // XXX do we really need this?
+  Continuation * endpoint;
+  SSLNextProtocolSet protoset;
+};
+
+#endif /* P_SSLNextProtocolAccept_H_ */

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/iocore/net/P_SSLNextProtocolSet.h
----------------------------------------------------------------------
diff --git a/iocore/net/P_SSLNextProtocolSet.h b/iocore/net/P_SSLNextProtocolSet.h
new file mode 100644
index 0000000..98ba11b
--- /dev/null
+++ b/iocore/net/P_SSLNextProtocolSet.h
@@ -0,0 +1,68 @@
+/** @file
+
+  SSLNextProtocolSet
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#ifndef P_SSLNextProtocolSet_H_
+#define P_SSLNextProtocolSet_H_
+
+#include "List.h"
+
+class Continuation;
+
+class SSLNextProtocolSet
+{
+public:
+  SSLNextProtocolSet();
+  ~SSLNextProtocolSet();
+
+  bool registerEndpoint(const char *, Continuation *);
+  bool unregisterEndpoint(const char *, Continuation *);
+  bool advertiseProtocols(const unsigned char ** out, unsigned * len) const;
+
+  Continuation * findEndpoint(const char *) const;
+  Continuation * findEndpoint(const unsigned char *, unsigned) const;
+
+  struct NextProtocolEndpoint
+  {
+    // NOTE: the protocol and endpoint are NOT copied. The caller is
+    // responsible for ensuring their lifetime.
+    NextProtocolEndpoint(const char * protocol, Continuation * endpoint);
+    ~NextProtocolEndpoint();
+
+    const char * protocol;
+    Continuation * endpoint;
+    LINK(NextProtocolEndpoint, link);
+
+    typedef DLL<NextProtocolEndpoint> list_type;
+  };
+
+private:
+  SSLNextProtocolSet(const SSLNextProtocolSet&); // disabled
+  SSLNextProtocolSet& operator=(const SSLNextProtocolSet&); // disabled
+
+  mutable unsigned char * npn;
+  mutable size_t npnsz;
+
+  NextProtocolEndpoint::list_type endpoints;
+};
+
+#endif /* P_SSLNextProtocolSet_H_ */

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/iocore/net/SSLNetProcessor.cc
----------------------------------------------------------------------
diff --git a/iocore/net/SSLNetProcessor.cc b/iocore/net/SSLNetProcessor.cc
index 826cf25..c9f848c 100644
--- a/iocore/net/SSLNetProcessor.cc
+++ b/iocore/net/SSLNetProcessor.cc
@@ -39,35 +39,6 @@ unsigned long SSL_pthreads_thread_id();
 
 bool SSLNetProcessor::open_ssl_initialized = false;
 
-#if TS_USE_TLS_NPN
-static int
-npn_advertise_protocols(SSL *ssl,
-    const unsigned char **out, unsigned int *outlen, void *arg)
-{
-  static const unsigned char protocols[] =
-    "\x08http/1.0"
-    "\x08http/1.1";
-
-  SSLNetProcessor * sslNetProc = (SSLNetProcessor *)arg;
-
-  // XXX: At some point we need to figure out how to know which protocols to
-  // advertise.
-  (void)sslNetProc;
-
-  // For currently defined protocol strings,
-  // see http://technotes.googlecode.com/git/nextprotoneg.html. The OpenSSL
-  // documentation tells us to return a string in "wire format". The draft NPN
-  // RFC helpfuly refuses to document the wire format. The above link says we
-  // need to send length-prefixed strings, but does not say how many bytes the
-  // length is. Nice.
-  *out = protocols;
-  *outlen = sizeof(protocols) - 1;
-
-  // Successful return tells OpenSSL to advertise.
-  return SSL_TLSEXT_ERR_OK;
-}
-#endif /* TS_USE_TLS_NPN */
-
 static int
 SSL_CTX_add_extra_chain_cert_file(SSL_CTX * ctx, const char *file)
 {
@@ -397,7 +368,8 @@ SSLNetProcessor::initSSLServerCTX(SSL_CTX * lCtx, const SslConfigParams * param,
   }
 
 #if TS_USE_TLS_NPN
-  SSL_CTX_set_next_protos_advertised_cb(lCtx, npn_advertise_protocols, this);
+  SSL_CTX_set_next_protos_advertised_cb(lCtx,
+      SSLNetVConnection::advertise_next_protocol, this);
 #endif /* TS_USE_TLS_NPN */
 
   return 0;

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/iocore/net/SSLNetVConnection.cc
----------------------------------------------------------------------
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index ff7df1d..7d7597a 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -22,6 +22,7 @@
  */
 #include "ink_config.h"
 #include "P_Net.h"
+#include "P_SSLNextProtocolSet.h"
 
 #define SSL_READ_ERROR_NONE	  0
 #define SSL_READ_ERROR		  1
@@ -34,13 +35,26 @@
 #define SSL_HANDSHAKE_WANT_ACCEPT 8
 #define SSL_HANDSHAKE_WANT_CONNECT 9
 #define SSL_WRITE_WOULD_BLOCK     10
-ClassAllocator<SSLNetVConnection> sslNetVCAllocator("sslNetVCAllocator");
 
+ClassAllocator<SSLNetVConnection> sslNetVCAllocator("sslNetVCAllocator");
 
 //
 // Private
 //
 
+static SSL *
+make_ssl_connection(SSL_CTX * ctx, SSLNetVConnection * netvc)
+{
+  SSL * ssl;
+
+  if (likely(ssl = SSL_new(ctx))) {
+    SSL_set_fd(ssl, netvc->get_socket());
+    SSL_set_app_data(ssl, netvc);
+  }
+
+  return ssl;
+}
+
 static inline int
 do_SSL_write(SSL * ssl, void *buf, int size)
 {
@@ -408,7 +422,13 @@ SSLNetVConnection::SSLNetVConnection():
   write_want_write(0),
   write_want_read(0),
   write_want_ssl(0),
-  write_want_syscal(0), write_want_x509(0), write_error_zero(0), sslHandShakeComplete(false), sslClientConnection(false)
+  write_want_syscal(0),
+  write_want_x509(0),
+  write_error_zero(0),
+  sslHandShakeComplete(false),
+  sslClientConnection(false),
+  npnSet(0),
+  npnEndpoint(0)
 {
   ssl = NULL;
 }
@@ -472,26 +492,25 @@ SSLNetVConnection::free(EThread * t) {
 int
 SSLNetVConnection::sslStartHandShake(int event, int &err)
 {
-  SSL_CTX *ctx = NULL;
   ts_ip_endpoint ip;
   int namelen = sizeof(ip);
 
   if (event == SSL_EVENT_SERVER) {
+    SSL_CTX *ctx = ssl_NetProcessor.ctx;
+
     if (ssl == NULL) {
       if (sslCertLookup.hasMultipleCerts()) {
         char buff[INET6_ADDRSTRLEN];
         safe_getsockname(get_socket(), &ip.sa, &namelen);
         ink_inet_ntop(&ip.sa, buff, sizeof(buff));
         ctx = sslCertLookup.findInfoInHash(buff);
-        if (ctx == NULL)
+        if (ctx == NULL) {
           ctx = ssl_NetProcessor.ctx;
-      } else {
-        ctx = ssl_NetProcessor.ctx;
+        }
       }
-      ssl = SSL_new(ctx);
-      if (ssl != NULL) {
-        SSL_set_fd(ssl, get_socket());
-      } else {
+
+      ssl = make_ssl_connection(ctx, this);
+      if (ssl == NULL) {
         Debug("ssl", "SSLNetVConnection::sslServerHandShakeEvent, ssl create failed");
         SSLNetProcessor::logSSLError("SSL_StartHandShake");
         return EVENT_ERROR;
@@ -501,8 +520,7 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
     return (sslServerHandShakeEvent(err));
   } else {
     if (ssl == NULL) {
-      ssl = SSL_new(ssl_NetProcessor.client_ctx);
-      SSL_set_fd(ssl, get_socket());
+      ssl = make_ssl_connection(ssl_NetProcessor.client_ctx, this);
     }
     ink_assert(event == SSL_EVENT_CLIENT);
     return (sslClientHandShakeEvent(err));
@@ -549,6 +567,11 @@ SSLNetVConnection::sslServerHandShakeEvent(int &err)
     } else {
       Debug("ssl", "client did not select a next protocol");
     }
+
+    if (len && this->npnSet) {
+      this->npnEndpoint = this->npnSet->findEndpoint(proto, len);
+      this->npnSet = NULL;
+    }
   }
 #endif /* TS_USE_TLS_NPN */
 
@@ -667,3 +690,26 @@ SSLNetVConnection::sslClientHandShakeEvent(int &err)
   return EVENT_CONT;
 
 }
+
+void
+SSLNetVConnection::registerNextProtocolSet(const SSLNextProtocolSet * s)
+{
+  ink_release_assert(this->npnSet == NULL);
+  this->npnSet = s;
+}
+
+int
+SSLNetVConnection::advertise_next_protocol(
+    SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg)
+{
+  SSLNetVConnection * netvc = (SSLNetVConnection *)SSL_get_app_data(ssl);
+
+  ink_release_assert(netvc != NULL);
+
+  if (netvc->npnSet && netvc->npnSet->advertiseProtocols(out, outlen)) {
+    // Successful return tells OpenSSL to advertise.
+    return SSL_TLSEXT_ERR_OK;
+  }
+
+  return SSL_TLSEXT_ERR_NOACK;
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/iocore/net/SSLNextProtocolAccept.cc
----------------------------------------------------------------------
diff --git a/iocore/net/SSLNextProtocolAccept.cc b/iocore/net/SSLNextProtocolAccept.cc
new file mode 100644
index 0000000..963fd7e
--- /dev/null
+++ b/iocore/net/SSLNextProtocolAccept.cc
@@ -0,0 +1,118 @@
+/** @file
+
+  SSLNextProtocolAccept
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "P_SSLNextProtocolAccept.h"
+
+static void
+send_plugin_event(Continuation * plugin, int event, void * edata)
+{
+  if (likely(plugin)) {
+    EThread * thread(this_ethread());
+    MUTEX_TAKE_LOCK(plugin->mutex, thread);
+    plugin->handleEvent(event, edata);
+    MUTEX_UNTAKE_LOCK(plugin->mutex, thread);
+  }
+}
+
+static SSLNetVConnection *
+ssl_netvc_cast(int event, void * edata)
+{
+  union {
+    VIO * vio;
+    NetVConnection * vc;
+  } ptr;
+
+  switch (event) {
+  case NET_EVENT_ACCEPT:
+    ptr.vc = static_cast<NetVConnection *>(edata);
+    return dynamic_cast<SSLNetVConnection *>(ptr.vc);
+  case VC_EVENT_READ_COMPLETE:
+    ptr.vio = static_cast<VIO *>(edata);
+    return dynamic_cast<SSLNetVConnection *>(ptr.vio->vc_server);
+  default:
+    return NULL;
+  }
+}
+
+int
+SSLNextProtocolAccept::mainEvent(int event, void * edata)
+{
+  SSLNetVConnection * netvc = ssl_netvc_cast(event, edata);
+  Continuation * plugin;
+
+  Debug("ssl",
+      "[SSLNextProtocolAccept:mainEvent] event %d netvc %p", event, netvc);
+
+
+  switch (event) {
+  case NET_EVENT_ACCEPT:
+    ink_release_assert(netvc != NULL);
+    // Register our protocol set with the VC and kick off a zero-length read to
+    // force the SSLNetVConnection to complete the SSL handshake. Don't tell
+    // the endpoint that there is an accept to handle until the read completes
+    // and we know which protocol was negotiated.
+    netvc->registerNextProtocolSet(&this->protoset);
+    netvc->do_io(VIO::READ, this, 0, this->buffer, 0);
+    return EVENT_CONT;
+  case VC_EVENT_READ_COMPLETE:
+    ink_release_assert(netvc != NULL);
+    plugin = netvc->endpoint();
+    if (plugin) {
+      send_plugin_event(plugin, NET_EVENT_ACCEPT, netvc);
+    } else if (this->endpoint) {
+      // Route to the default endpoint
+      send_plugin_event(this->endpoint, NET_EVENT_ACCEPT, netvc);
+    } else {
+      // No handler, what should we do? Best to just kill the VC while we can.
+      netvc->do_io(VIO::CLOSE);
+    }
+    return EVENT_CONT;
+  default:
+    return EVENT_DONE;
+  }
+}
+
+bool
+SSLNextProtocolAccept::registerEndpoint(
+    const char * protocol, Continuation * handler)
+{
+  return this->protoset.registerEndpoint(protocol, handler);
+}
+
+bool
+SSLNextProtocolAccept::unregisterEndpoint(
+    const char * protocol, Continuation * handler)
+{
+  return this->protoset.unregisterEndpoint(protocol, handler);
+}
+
+SSLNextProtocolAccept::SSLNextProtocolAccept(Continuation * ep)
+    : Continuation(new_ProxyMutex()), buffer(new_empty_MIOBuffer()), endpoint(ep)
+{
+  SET_HANDLER(&SSLNextProtocolAccept::mainEvent);
+}
+
+SSLNextProtocolAccept::~SSLNextProtocolAccept()
+{
+  free_MIOBuffer(this->buffer);
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/iocore/net/SSLNextProtocolSet.cc
----------------------------------------------------------------------
diff --git a/iocore/net/SSLNextProtocolSet.cc b/iocore/net/SSLNextProtocolSet.cc
new file mode 100644
index 0000000..15aaf8d
--- /dev/null
+++ b/iocore/net/SSLNextProtocolSet.cc
@@ -0,0 +1,182 @@
+/** @file
+
+  SSLNextProtocolSet
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "ink_config.h"
+#include "libts.h"
+#include "P_SSLNextProtocolSet.h"
+
+// For currently defined protocol strings, see
+// http://technotes.googlecode.com/git/nextprotoneg.html. The OpenSSL
+// documentation tells us to return a string in "wire format". The
+// draft NPN RFC helpfuly refuses to document the wire format. The
+// above link says we need to send length-prefixed strings, but does
+// not say how many bytes the length is. For the record, it's 1.
+
+unsigned char *
+append_protocol(const char * proto, unsigned char * buf)
+{
+  size_t sz = strlen(proto);
+  *buf++ = (unsigned char)sz;
+  memcpy(buf, proto, sz);
+  return buf + sz;
+}
+
+static bool
+create_npn_advertisement(
+  const SSLNextProtocolSet::NextProtocolEndpoint::list_type& endpoints,
+  unsigned char ** npn, size_t * len)
+{
+  const SSLNextProtocolSet::NextProtocolEndpoint * ep;
+  unsigned char * advertised;
+
+  *npn = NULL;
+  *len = 0;
+
+  for (ep = endpoints.head; ep != NULL; ep = endpoints.next(ep)) {
+    *len += (strlen(ep->protocol) + 1);
+  }
+
+  *npn = advertised = (unsigned char *)ats_malloc(*len);
+  if (!(*npn)) {
+    goto fail;
+  }
+
+  for (ep = endpoints.head; ep != NULL; ep = endpoints.next(ep)) {
+    advertised = append_protocol(ep->protocol, advertised);
+  }
+
+  return true;
+
+fail:
+  ats_free(*npn);
+  *npn = NULL;
+  *len = 0;
+  return false;
+}
+
+bool
+SSLNextProtocolSet::advertiseProtocols(const unsigned char ** out, unsigned * len) const
+{
+  if (!npn && !this->endpoints.empty()) {
+    create_npn_advertisement(this->endpoints, &npn, &npnsz);
+  }
+
+  if (npn && npnsz) {
+    *out = npn;
+    *len = npnsz;
+    Debug("ssl", "advertised NPN set %.*s", (int)npnsz, npn);
+    return true;
+  }
+
+  return false;
+}
+
+bool
+SSLNextProtocolSet::registerEndpoint(const char * proto, Continuation * ep)
+{
+  // Once we start advertising, the set is closed. We need to hand an immutable
+  // string down into OpenSSL, and there is no mechanism to tell us when it's
+  // done with it so we have to keep it forever.
+  if (this->npn) {
+    return false;
+  }
+
+  if (strlen(proto) > 255) {
+    return false;
+  }
+
+  if (findEndpoint(proto) == NULL) {
+    this->endpoints.push(NEW(new NextProtocolEndpoint(proto, ep)));
+    return true;
+  }
+
+  return false;
+}
+
+bool
+SSLNextProtocolSet::unregisterEndpoint(const char * proto, Continuation * ep)
+{
+
+  for (NextProtocolEndpoint * e = this->endpoints.head;
+        e; e = this->endpoints.next(e)) {
+    if (strcmp(proto, e->protocol) == 0 && e->endpoint == ep) {
+      // Protocol must be registered only once; no need to remove
+      // any more entries.
+      this->endpoints.remove(e);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+Continuation *
+SSLNextProtocolSet::findEndpoint(const unsigned char * proto, unsigned len) const
+{
+  for (const NextProtocolEndpoint * ep = this->endpoints.head;
+        ep != NULL; ep = this->endpoints.next(ep)) {
+    size_t sz = strlen(ep->protocol);
+    if (sz == len && memcmp(ep->protocol, proto, len) == 0) {
+      return ep->endpoint;
+    }
+  }
+
+  return NULL;
+}
+
+Continuation *
+SSLNextProtocolSet::findEndpoint(const char * proto) const
+{
+  for (const NextProtocolEndpoint * ep = this->endpoints.head;
+        ep != NULL; ep = this->endpoints.next(ep)) {
+    if (strcmp(proto, ep->protocol) == 0) {
+      return ep->endpoint;
+    }
+  }
+
+  return NULL;
+}
+
+SSLNextProtocolSet::SSLNextProtocolSet()
+  : npn(0), npnsz(0)
+{
+}
+
+SSLNextProtocolSet::~SSLNextProtocolSet()
+{
+  ats_free(this->npn);
+
+  for (NextProtocolEndpoint * ep; (ep = this->endpoints.pop());) {
+    delete ep;
+  }
+}
+
+SSLNextProtocolSet::NextProtocolEndpoint::NextProtocolEndpoint(
+        const char * proto, Continuation * ep)
+  : protocol(proto), endpoint(ep)
+{
+}
+
+SSLNextProtocolSet::NextProtocolEndpoint::~NextProtocolEndpoint()
+{
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/lib/ts/List.h
----------------------------------------------------------------------
diff --git a/lib/ts/List.h b/lib/ts/List.h
index 5ece3e0..e72358c 100644
--- a/lib/ts/List.h
+++ b/lib/ts/List.h
@@ -73,6 +73,7 @@ template <class C> class SLink {
 };
 #define SLINK(_c,_f) class Link##_##_f : public SLink<_c> { public:    \
     static _c *& next_link(_c *c) { return c->_f.next; }                \
+    static const _c * next_link(const _c *c) { return c->_f.next; } \
   }; SLink<_c> _f
 #define SLINKM(_c,_m,_f) class Link##_##_m##_##_f : public SLink<_c> { public: \
     static _c *& next_link(_c *c) { return c->_m._f.next; }             \
@@ -88,6 +89,8 @@ template <class C> struct Link : public SLink<C> {
 #define LINK(_c,_f) class Link##_##_f : public Link<_c> { public:       \
     static _c *& next_link(_c *c) { return c->_f.next; }                \
     static _c *& prev_link(_c *c) { return c->_f.prev; }                \
+    static const _c * next_link(const _c *c) { return c->_f.next; }     \
+    static const _c * prev_link(const _c *c) { return c->_f.prev; }     \
   }; Link<_c> _f
 #define LINKM(_c,_m,_f) class Link##_##_m##_##_f : public Link<_c> { public:  \
     static _c *& next_link(_c *c) { return c->_m._f.next; }             \
@@ -107,11 +110,12 @@ template <class C> struct Link : public SLink<C> {
 template <class C, class L = typename C::Link_link> class SLL {
  public:
   C *head;
-  bool empty() { return head == NULL; }
+  bool empty() const { return head == NULL; }
   void push(C *e);
   C *pop();
   void clear() { head = NULL; }
   C *& next(C *e) { return L::next_link(e); }
+  const C * next(const C *e) const { return L::next_link(e); }
 
   SLL() : head(NULL) {}
   SLL(C *c) : head(c) {}
@@ -141,7 +145,7 @@ SLL<C,L>::pop() {
 //
 template <class C, class L = typename C::Link_link> struct DLL {
   C *head;
-  bool empty() { return head == NULL; }
+  bool empty() const { return head == NULL; }
   void push(C *e);
   C *pop();
   void remove(C *e);
@@ -150,6 +154,8 @@ template <class C, class L = typename C::Link_link> struct DLL {
   void clear() { head = NULL; }
   C *&next(C *e) { return *(C**)&L::next_link(e); }
   C *&prev(C *e) { return *(C**)&L::prev_link(e); }
+  const C *next(const C *e) const { return L::next_link(e); }
+  const C *prev(const C *e) const { return L::prev_link(e); }
 
   DLL() : head(NULL) {}
 };

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/lib/ts/ink_mutex.h
----------------------------------------------------------------------
diff --git a/lib/ts/ink_mutex.h b/lib/ts/ink_mutex.h
index 5919704..f54b91a 100644
--- a/lib/ts/ink_mutex.h
+++ b/lib/ts/ink_mutex.h
@@ -126,4 +126,18 @@ int ink_ProcessMutex_try_acquire(ProcessMutex *);
 void ink_ProcessMutex_print(FILE * out, ProcessMutex *);
 
 
+struct ink_scoped_mutex
+{
+  explicit ink_scoped_mutex(ink_mutex& m) : mtx(m) {
+    ink_mutex_acquire(&mtx);
+  }
+
+  ~ink_scoped_mutex() {
+    ink_mutex_release(&mtx);
+  }
+
+private:
+  ink_mutex& mtx;
+};
+
 #endif /* _ink_mutex_h_ */

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/proxy/InkAPI.cc
----------------------------------------------------------------------
diff --git a/proxy/InkAPI.cc b/proxy/InkAPI.cc
index 89ce991..679fbe1 100644
--- a/proxy/InkAPI.cc
+++ b/proxy/InkAPI.cc
@@ -357,6 +357,14 @@ tsapi int TS_HTTP_LEN_PURGE;
 tsapi int TS_HTTP_LEN_PUT;
 tsapi int TS_HTTP_LEN_TRACE;
 
+/* TLS Next Protocol well-known protocol names. */
+
+tsapi const char * TS_NPN_PROTOCOL_HTTP_1_0 = "http/1.0";
+tsapi const char * TS_NPN_PROTOCOL_HTTP_1_1 = "http/1.1";
+tsapi const char * TS_NPN_PROTOCOL_SPDY_1   = "spdy/1";   // obsolete
+tsapi const char * TS_NPN_PROTOCOL_SPDY_2   = "spdy/2";   // shipping
+tsapi const char * TS_NPN_PROTOCOL_SPDY_3   = "spdy/3";   // upcoming
+
 /* MLoc Constants */
 tsapi const TSMLoc TS_NULL_MLOC = (TSMLoc)NULL;
 
@@ -6409,6 +6417,25 @@ TSNetAccept(TSCont contp, int port, int domain, int accept_threads)
   return (TSAction)netProcessor.accept(i, opt);
 }
 
+/* From proxy/http/HttpProxyServerMain.c: */
+extern bool ssl_register_protocol(const char *, Continuation *);
+extern bool ssl_unregister_protocol(const char *, Continuation *);
+
+TSReturnCode
+TSNetAcceptNamedProtocol(TSCont contp, const char * protocol)
+{
+  sdk_assert(protocol != NULL);
+  sdk_assert(contp != NULL);
+  sdk_assert(sdk_sanity_check_continuation(contp) == TS_SUCCESS);
+
+  if (!ssl_register_protocol(protocol, (INKContInternal *)contp)) {
+    ssl_unregister_protocol(protocol, (INKContInternal *)contp);
+    return TS_ERROR;
+  }
+
+  return TS_SUCCESS;
+}
+
 /* DNS Lookups */
 TSAction
 TSHostLookup(TSCont contp, const char *hostname, size_t namelen)

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/proxy/api/ts/ts.h.in
----------------------------------------------------------------------
diff --git a/proxy/api/ts/ts.h.in b/proxy/api/ts/ts.h.in
index 341290d..6c676ed 100644
--- a/proxy/api/ts/ts.h.in
+++ b/proxy/api/ts/ts.h.in
@@ -970,6 +970,14 @@ extern "C"
   extern tsapi int TS_HTTP_LEN_TRACE;
 
   /* --------------------------------------------------------------------------
+     TLS Next Protocol well-known protocol names. */
+  extern tsapi const char * TS_NPN_PROTOCOL_HTTP_1_0;
+  extern tsapi const char * TS_NPN_PROTOCOL_HTTP_1_1;
+  extern tsapi const char * TS_NPN_PROTOCOL_SPDY_1;
+  extern tsapi const char * TS_NPN_PROTOCOL_SPDY_2;
+  extern tsapi const char * TS_NPN_PROTOCOL_SPDY_3;
+
+  /* --------------------------------------------------------------------------
      MLoc Constants */
   /**
       Use TS_NULL_MLOC as the parent in calls that require a parent
@@ -2533,6 +2541,29 @@ extern "C"
 
   tsapi TSAction TSNetAccept(TSCont contp, int port, int domain, int accept_threads);
 
+  /**
+    Listen on all SSL ports for connections for the specified protocol name.
+
+    TSNetAcceptNamedProtocol registers the specified protocol for all
+    statically configured TLS ports. When a client using the TLS Next Protocol
+    Negotiation extension negotiates the requested protocol, TrafficServer will
+    route the request to the given handler. Note that the protocol is not
+    registered on ports opened by other plugins.
+
+    The event and data provided to the handler are the same as for
+    TSNetAccept(). If a connection is successfully accepted, the event code
+    will be TS_EVENT_NET_ACCEPT and the event data will be a valid TSVConn
+    bound to the accepted connection.
+
+    Neither contp nor protocol are copied. They must remain valid for the
+    lifetime of the plugin.
+
+    TSNetAcceptNamedProtocol fails if the requested protocol cannot be
+    registered on all of the configured TLS ports. If it fails, the protocol
+    will not be registered on any ports (ie.. no partial failure).
+  */
+  tsapi TSReturnCode TSNetAcceptNamedProtocol(TSCont contp, const char * protocol);
+
   /* --------------------------------------------------------------------------
      DNS Lookups */
   tsapi TSAction TSHostLookup(TSCont contp, const char* hostname, size_t namelen);

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/proxy/http/HttpAccept.h
----------------------------------------------------------------------
diff --git a/proxy/http/HttpAccept.h b/proxy/http/HttpAccept.h
index af3584b..d5f659a 100644
--- a/proxy/http/HttpAccept.h
+++ b/proxy/http/HttpAccept.h
@@ -147,7 +147,7 @@ public:
   typedef detail::HttpAcceptOptions Options;
 
   HttpAccept(Options const& opt = DEFAULT_OPTIONS)
-    : Continuation(0)
+    : Continuation(new_ProxyMutex())
     , detail::HttpAcceptOptions(opt) // copy these.
   {
     SET_HANDLER(&HttpAccept::mainEvent);

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c85a874f/proxy/http/HttpProxyServerMain.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc
index 407918b..5ebbb22 100644
--- a/proxy/http/HttpProxyServerMain.cc
+++ b/proxy/http/HttpProxyServerMain.cc
@@ -34,6 +34,7 @@
 #include "HttpPages.h"
 #include "HttpTunnel.h"
 #include "Tokenizer.h"
+#include "P_SSLNextProtocolAccept.h"
 
 #ifdef DEBUG
 extern "C"
@@ -76,6 +77,42 @@ struct DumpStats: public Continuation
 HttpAccept *plugin_http_accept = NULL;
 HttpAccept *plugin_http_transparent_accept = 0;
 
+#if !defined(TS_NO_API)
+static SLL<SSLNextProtocolAccept> ssl_plugin_acceptors;
+static ProcessMutex ssl_plugin_mutex;
+
+bool
+ssl_register_protocol(const char * protocol, Continuation * contp)
+{
+  ink_scoped_mutex lock(ssl_plugin_mutex);
+
+  for (SSLNextProtocolAccept * ssl = ssl_plugin_acceptors.head;
+        ssl; ssl = ssl_plugin_acceptors.next(ssl)) {
+    if (!ssl->registerEndpoint(protocol, contp)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool
+ssl_unregister_protocol(const char * protocol, Continuation * contp)
+{
+  ink_scoped_mutex lock(ssl_plugin_mutex);
+
+  for (SSLNextProtocolAccept * ssl = ssl_plugin_acceptors.head;
+        ssl; ssl = ssl_plugin_acceptors.next(ssl)) {
+    // Ignore possible failure because we want to try to unregister
+    // from all SSL ports.
+    ssl->unregisterEndpoint(protocol, contp);
+  }
+
+  return true;
+}
+
+#endif /* !defined(TS_NO_API) */
+
 /////////////////////////////////////////////////////////////////
 //
 //  main()
@@ -113,6 +150,7 @@ init_HttpProxyServer(void)
     plugin_http_transparent_accept = NEW(new HttpAccept(ha_opt));
     plugin_http_transparent_accept->mutex = new_ProxyMutex();
   }
+  ink_mutex_init(&ssl_plugin_mutex, "SSL Acceptor List");
 #endif
 }
 
@@ -170,7 +208,17 @@ start_HttpProxyServer(int accept_threads)
 
     if (HttpProxyPort::TRANSPORT_SSL == p.m_type) {
       if (sslParam->getTerminationMode() & sslParam->SSL_TERM_MODE_CLIENT) {
-        sslNetProcessor.main_accept(NEW(new HttpAccept(ha_opt)), p.m_fd, opt);
+        HttpAccept * http = NEW(new HttpAccept(ha_opt));
+        SSLNextProtocolAccept * ssl = NEW(new SSLNextProtocolAccept(http));
+        ssl->registerEndpoint(TS_NPN_PROTOCOL_HTTP_1_0, http);
+        ssl->registerEndpoint(TS_NPN_PROTOCOL_HTTP_1_1, http);
+
+#ifndef TS_NO_API
+        ink_scoped_mutex lock(ssl_plugin_mutex);
+        ssl_plugin_acceptors.push(ssl);
+#endif
+
+        sslNetProcessor.main_accept(ssl, p.m_fd, opt);
       }
     } else {
       netProcessor.main_accept(NEW(new HttpAccept(ha_opt)), p.m_fd, opt);