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 2018/03/29 22:42:55 UTC

[trafficserver] branch master updated: Added VConn args support Changed TS_VCONN_PRE_ACCEPT to TS_VCONN_START Added TS_VCONN_CLOSE hooks Fixed docs for TS_VCONN_PRE_ACCEPT renaming to TS_VCONN_START and added docs for TSVconnArgs Added an example plugin vconn_args to test

This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 8d01164  Added VConn args support Changed TS_VCONN_PRE_ACCEPT to TS_VCONN_START Added TS_VCONN_CLOSE hooks Fixed docs for TS_VCONN_PRE_ACCEPT renaming to TS_VCONN_START and added docs for TSVconnArgs Added an example plugin vconn_args to test
8d01164 is described below

commit 8d01164257417434b667021365b5f07d0ec596e0
Author: dyrock <ze...@gmail.com>
AuthorDate: Tue Mar 13 16:12:17 2018 -0500

    Added VConn args support
    Changed TS_VCONN_PRE_ACCEPT to TS_VCONN_START
    Added TS_VCONN_CLOSE hooks
    Fixed docs for TS_VCONN_PRE_ACCEPT renaming to TS_VCONN_START and added docs for TSVconnArgs
    Added an example plugin vconn_args to test
    
    Added vconn args man page
    
    Make VCONN_PRE_ACCEPT deprecated but still compatible
---
 .../api/functions/TSVConnArgs.en.rst               |  82 ++++++++++++++++
 .../api/functions/TSVConnTunnel.en.rst             |   2 +-
 doc/developer-guide/api/types/TSEvent.en.rst       |   4 +-
 doc/developer-guide/api/types/TSHttpHookID.en.rst  |   6 +-
 .../hooks-and-transactions/ssl-hooks.en.rst        |  24 +++--
 example/Makefile.am                                |   4 +-
 example/ssl_preaccept/ssl_preaccept.cc             |   6 +-
 example/vconn_args/vconn_args.cc                   | 104 +++++++++++++++++++++
 iocore/eventsystem/I_VConnection.h                 |  39 +++++++-
 iocore/net/I_NetVConnection.h                      |   4 +-
 iocore/net/P_SSLNetVConnection.h                   |   8 +-
 iocore/net/SSLNetVConnection.cc                    |  25 +++--
 lib/ts/apidefs.h.in                                |  16 ++--
 .../ssl_cert_loader/ssl-cert-loader.cc             |   4 +-
 proxy/InkAPI.cc                                    |  49 +++++++++-
 proxy/InkAPIInternal.h                             |   3 +-
 proxy/InkAPITest.cc                                |   5 +-
 proxy/api/ts/ts.h                                  |   5 +
 proxy/http/HttpDebugNames.cc                       |   6 +-
 tests/tools/plugins/ssl_hook_test.cc               |  12 +--
 20 files changed, 352 insertions(+), 56 deletions(-)

diff --git a/doc/developer-guide/api/functions/TSVConnArgs.en.rst b/doc/developer-guide/api/functions/TSVConnArgs.en.rst
new file mode 100644
index 0000000..6f912b3
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSVConnArgs.en.rst
@@ -0,0 +1,82 @@
+.. 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:: ../common.defs
+.. default-domain:: c
+
+.. _TSVConnArgs:
+
+TSVConnArgs
+************
+
+Synopsis
+========
+
+`#include <ts/ts.h>`
+
+.. function:: TSReturnCode TSVConnArgIndexReserve(const char * name, const char * description, int * arg_idx)
+.. function:: TSReturnCode TSVConnArgIndexNameLookup(const char * name, int * arg_idx, const char ** description)
+.. function:: TSReturnCode TSVConnArgIndexLookup(int arg_idx, const char ** name, const char ** description)
+.. function:: void TSVConnArgSet(TSVConn vc, int arg_idx, void * arg)
+.. function:: void * TSVConnArgGet(TSVConn vc, int arg_idx)
+
+Description
+===========
+
+Virtual connection objects (API type :c:type:`TSVConn`) support an array of :code:`void *` values that
+are controlled entirely by plugins. These are not used in any way by the core. This allows plugins
+to store data associated with a specific virtual connection for later retrieval by the same plugin
+in a different hook or by another plugin. Because the core does not interact with these values any
+cleanup is the responsibility of the plugin.
+
+To avoid collisions between plugins a plugin should first *reserve* an index in the array by calling
+:func:`TSVConnArgIndexReserve` passing it an identifying name, a description, and a pointer to an
+integer which will get the reserved index. The function returns :code:`TS_SUCCESS` if an index was
+reserved, :code:`TS_ERROR` if not (most likely because all of the indices have already been
+reserved). Generally this will be a file or library scope global which is set at plugin
+initialization. Note the reservation is by convention - nothing stops a plugin from interacting with
+a :code:`TSVConn` arg it has not reserved.
+
+To look up the owner of a reserved index use :func:`TSVConnArgIndexNameLookup`. If the :arg:`name` is
+found as an owner, the function returns :code:`TS_SUCCESS` and :arg:`arg_index` is updated with the
+index reserved under that name. If :arg:`description` is not :code:`nullptr` then it will be updated
+with the description for that reserved index. This enables communication between plugins where
+plugin "A" reserves an index under a well known name and plugin "B" locates the index by looking it
+up under that name.
+
+The owner of a reserved index can be found with :func:`TSVConnArgIndexLookup`. If :arg:`arg_index` is
+reserved then the function returns :code:`TS_SUCCESS` and :arg:`name` and :arg:`description` are
+updated. :arg:`name` must point at a valid character pointer but :arg:`description` can be
+:code:`nullptr`.
+
+Manipulating the array is simple. :func:`TSVConnArgSet` sets the array slot at :arg:`arg_idx` for
+the :arg:`vc` to the value :arg:`arg`. Note this sets the value only for the specific
+:c:type:`TSVConn`. The values can be retrieved with :func:`TSVConnArgGet` which returns the
+specified value. Values that have not been set are :code:`nullptr`.
+
+Hooks
+=====
+
+Although these can be used from any hook that has access to a :c:type:`TSVConn` it will generally be
+the case that :func:`TSVConnArgSet` will be used in early intervention hooks and
+:func:`TSVConnArgGet` in session / transaction hooks. Cleanup should be done on the
+:code:`TS_VCONN_CLOSE_HOOK`.
+
+.. rubric:: Appendix
+
+.. note::
+   This is originally from `Issue 2388 <https://github.com/apache/trafficserver/issues/2388>`__. It
+   has been extended based on discussions with Kees and Leif Hedstrom at the ATS summit.
diff --git a/doc/developer-guide/api/functions/TSVConnTunnel.en.rst b/doc/developer-guide/api/functions/TSVConnTunnel.en.rst
index 85fa184..e27b811 100644
--- a/doc/developer-guide/api/functions/TSVConnTunnel.en.rst
+++ b/doc/developer-guide/api/functions/TSVConnTunnel.en.rst
@@ -32,5 +32,5 @@ Description
 ===========
 
 Set the SSL connection :arg:`svc` to convert to a blind tunnel. Can be called
-from :member:`TS_VCONN_PRE_ACCEPT_HOOK`, :member:`TS_SSL_SERVERNAME_HOOK`, or :member:`TS_SSL_SNI_HOOK` / :member:`TS_SSL_CERT_HOOK`.
+from :member:`TS_VCONN_START_HOOK`, :member:`TS_SSL_SERVERNAME_HOOK`, or :member:`TS_SSL_SNI_HOOK` / :member:`TS_SSL_CERT_HOOK`.
 
diff --git a/doc/developer-guide/api/types/TSEvent.en.rst b/doc/developer-guide/api/types/TSEvent.en.rst
index e4abf84..96e8b73 100644
--- a/doc/developer-guide/api/types/TSEvent.en.rst
+++ b/doc/developer-guide/api/types/TSEvent.en.rst
@@ -177,7 +177,9 @@ Enumeration Members
 
 .. c:macro:: TS_EVENT_LIFECYCLE_CLIENT_SSL_CTX_INITIALIZED
 
-.. c:macro:: TS_EVENT_VCONN_PRE_ACCEPT
+.. c:macro:: TS_EVENT_VCONN_START
+
+.. c:macro:: TS_EVENT_VCONN_CLOSE
 
 .. c:macro:: TS_EVENT_MGMT_UPDATE
 
diff --git a/doc/developer-guide/api/types/TSHttpHookID.en.rst b/doc/developer-guide/api/types/TSHttpHookID.en.rst
index 85c6f02..f03444a 100644
--- a/doc/developer-guide/api/types/TSHttpHookID.en.rst
+++ b/doc/developer-guide/api/types/TSHttpHookID.en.rst
@@ -70,7 +70,9 @@ Enumeration Members
 
 .. c:macro:: TSHttpHookID TS_SSL_FIRST_HOOK
 
-.. c:macro:: TSHttpHookID TS_VCONN_PRE_ACCEPT_HOOK
+.. c:macro:: TSHttpHookID TS_VCONN_START_HOOK
+
+.. c:macro:: TSHttpHookID TS_VCONN_CLOSE_HOOK
 
 .. c:macro:: TSHttpHookID TS_SSL_SNI_HOOK
 
@@ -97,7 +99,7 @@ to be deprecated and removed, plugins using this should change to :macro:`TS_SSL
    :macro:`TS_SSL_SERVERNAME_HOOK` is invoked for the openssl servername callback.
    :macro:`TS_SSL_SNI_HOOK` and :macro:`TS_SSL_CERT_HOOK` are invoked for the openssl certificate
    callback which is not guaranteed to be invoked for a TLS transaction.
-   
+
    This is a behavior change dependent on the version of openssl. To avoid problems use
    :macro:`TS_SSL_SERVERNAME_HOOK` to get called back for all TLS transaction and
    :macro:`TS_SSL_CERT_HOOK` to get called back only to select a certificate.
diff --git a/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst b/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst
index cb026c8..518d0e8 100644
--- a/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst
+++ b/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst
@@ -22,10 +22,10 @@
 TLS User Agent Hooks
 ********************
 
-In addition to the HTTP oriented hooks, a plugin can add hooks to trigger code 
+In addition to the HTTP oriented hooks, a plugin can add hooks to trigger code
 during the TLS handshake with the user agent.  This TLS handshake occurs well before
 the HTTP transaction is available, so a separate state machine is required to track the
-TLS hooks.  
+TLS hooks.
 
 TLS Hooks
 ---------
@@ -45,7 +45,7 @@ The following actions are valid from these callbacks.
   * Find SSL context by address - :c:func:`TSSslContextFindByAddr`
   * Determine whether the TSVConn is really representing a SSL connection - :c:func:`TSVConnIsSsl`
 
-TS_VCONN_PRE_ACCEPT_HOOK
+TS_VCONN_START_HOOK
 ------------------------
 
 This hook is invoked after the client has connected to ATS and before the SSL handshake is started, i.e., before any bytes have been read from the client. The data for the callback is a TSVConn instance which represents the client connection. There is no HTTP transaction as no headers have been read.
@@ -55,10 +55,15 @@ In theory this hook could apply and be useful for non-SSL connections as well, b
 The TLS handshake processing will not proceed until :c:func:`TSSslVConnReenable()` is called either from within the hook
 callback or from another piece of code.
 
+TS_VCONN_CLOSE_HOOK
+------------------------
+
+This hook is invoked after the SSL handshake is done and when the IO is closing. The TSVConnArgs should be cleaned up here.
+
 TS_SSL_SERVERNAME_HOOK
 ----------------------
 
-This hook is called if the client provides SNI information in the SSL handshake. If called it will always be called after TS_VCONN_PRE_ACCEPT_HOOK.
+This hook is called if the client provides SNI information in the SSL handshake. If called it will always be called after TS_VCONN_START_HOOK.
 
 The Traffic Server core first evaluates the settings in the ssl_multicert.config file based on the server name. Then the core SNI callback executes the plugin registered SNI callback code. The plugin callback can access the servername by calling the openssl function SSL_get_servername().
 
@@ -72,12 +77,12 @@ This hook is called as the server certificate is selected for the TLS handshake.
 code to create or select the certificate that should be used for the TLS handshake.  This will override the default
 Traffic Server certificate selection.
 
-If you are running with openssl 1.0.2 or later, you can control whether the TLS handshake processing will 
-continue after the certificate hook callback execute by calling :c:func:`TSSslVConnReenable()` or not.  The TLS 
+If you are running with openssl 1.0.2 or later, you can control whether the TLS handshake processing will
+continue after the certificate hook callback execute by calling :c:func:`TSSslVConnReenable()` or not.  The TLS
 handshake processing will not proceed until :c:func:`TSSslVConnReenable()` is called.
 
 It may be useful to delay the TLS handshake processing if other resources must be consulted to select or create
-a certificate. 
+a certificate.
 
 TLS Hook State Diagram
 ----------------------
@@ -86,11 +91,11 @@ TLS Hook State Diagram
    :alt: TLS Hook State Diagram
 
    digraph tls_hook_state_diagram{
-     HANDSHAKE_HOOKS_PRE -> TS_VCONN_PRE_ACCEPT_HOOK;
+     HANDSHAKE_HOOKS_PRE -> TS_VCONN_START_HOOK;
      HANDSHAKE_HOOKS_PRE -> TS_SSL_CERT_HOOK;
      HANDSHAKE_HOOKS_PRE -> TS_SSL_SERVERNAME_HOOK;
      HANDSHAKE_HOOKS_PRE -> HANDSHAKE_HOOKS_DONE;
-     TS_VCONN_PRE_ACCEPT_HOOK -> HANDSHAKE_HOOKS_PRE_INVOKE;
+     TS_VCONN_START_HOOK -> HANDSHAKE_HOOKS_PRE_INVOKE;
      HANDSHAKE_HOOKS_PRE_INVOKE -> TSSslVConnReenable;
      TSSslVConnReenable -> HANDSHAKE_HOOKS_PRE;
      TS_SSL_SERVERNAME_HOOK -> HANDSHAKE_HOOKS_SNI;
@@ -102,6 +107,7 @@ TLS Hook State Diagram
      HANDSHAKE_HOOKS_CERT_INVOKE -> TSSslVConnReenable2;
      TSSslVConnReenable2 -> HANDSHAKE_HOOKS_CERT;
      HANDSHAKE_HOOKS_CERT -> HANDSHAKE_HOOKS_DONE;
+     HANDSHAKE_HOOKS_DONE -> TS_VCONN_CLOSE_HOOK;
 
      HANDSHAKE_HOOKS_PRE [shape=box];
      HANDSHAKE_HOOKS_PRE_INVOKE [shape=box];
diff --git a/example/Makefile.am b/example/Makefile.am
index 6c816ee..5134873 100644
--- a/example/Makefile.am
+++ b/example/Makefile.am
@@ -62,7 +62,8 @@ example_Plugins = \
 	txn_data_sink.la \
 	version.la \
 	disable_http2.la \
-	verify_cert.la
+	verify_cert.la \
+	vconn_args.la
 
 example_Plugins += \
 	cppapi/AsyncHttpFetch.la \
@@ -133,6 +134,7 @@ txn_data_sink_la_SOURCES = txn_data_sink/txn_data_sink.c
 version_la_SOURCES = version/version.c
 redirect_1_la_SOURCES = redirect_1/redirect_1.c
 session_hooks_la_SOURCES = session_hooks/session_hooks.c
+vconn_args_la_SOURCES = vconn_args/vconn_args.cc
 
 cppapi_AsyncHttpFetchStreaming_la_SOURCES = cppapi/async_http_fetch_streaming/AsyncHttpFetchStreaming.cc
 cppapi_AsyncHttpFetch_la_SOURCES = cppapi/async_http_fetch/AsyncHttpFetch.cc
diff --git a/example/ssl_preaccept/ssl_preaccept.cc b/example/ssl_preaccept/ssl_preaccept.cc
index 4bc822c..55c9595 100644
--- a/example/ssl_preaccept/ssl_preaccept.cc
+++ b/example/ssl_preaccept/ssl_preaccept.cc
@@ -143,8 +143,8 @@ CB_Pre_Accept(TSCont, TSEvent event, void *edata)
   }
 
   TSDebug(PLUGIN_NAME, "Pre accept callback %p - event is %s, target address %s, client address %s%s", ssl_vc,
-          event == TS_EVENT_VCONN_PRE_ACCEPT ? "good" : "bad", ip.toString(buff, sizeof(buff)),
-          ip_client.toString(buff2, sizeof(buff2)), proxy_tunnel ? "" : " blind tunneled");
+          event == TS_EVENT_VCONN_START ? "good" : "bad", ip.toString(buff, sizeof(buff)), ip_client.toString(buff2, sizeof(buff2)),
+          proxy_tunnel ? "" : " blind tunneled");
 
   // All done, reactivate things
   TSVConnReenable(ssl_vc);
@@ -193,7 +193,7 @@ TSPluginInit(int argc, const char *argv[])
   } else if (nullptr == (cb_pa = TSContCreate(&CB_Pre_Accept, TSMutexCreate()))) {
     TSError(PCP "Failed to pre-accept callback");
   } else {
-    TSHttpHookAdd(TS_VCONN_PRE_ACCEPT_HOOK, cb_pa);
+    TSHttpHookAdd(TS_VCONN_START_HOOK, cb_pa);
     success = true;
   }
 
diff --git a/example/vconn_args/vconn_args.cc b/example/vconn_args/vconn_args.cc
new file mode 100644
index 0000000..a997a98
--- /dev/null
+++ b/example/vconn_args/vconn_args.cc
@@ -0,0 +1,104 @@
+/** @file
+
+  VConn Args test plugin.
+
+  Tests VConn arg reserve/lookup/set/get.
+
+  @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 <cstdio>
+
+#include "ts/ts.h"
+
+const char *PLUGIN_NAME = "vconn_arg_test";
+static int last_arg     = 0;
+
+static int
+vconn_arg_handler(TSCont contp, TSEvent event, void *edata)
+{
+  TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
+  switch (event) {
+  case TS_EVENT_VCONN_START: {
+    // Testing set argument
+    int idx = 0;
+    while (TSVConnArgIndexReserve(PLUGIN_NAME, "test", &idx) == TS_SUCCESS) {
+      char *buf = (char *)TSmalloc(64);
+      snprintf(buf, 64, "Test Arg Idx %d", idx);
+      TSVConnArgSet(ssl_vc, idx, (void *)buf);
+      TSDebug(PLUGIN_NAME, "Successfully reserve and set arg #%d", idx);
+    }
+    last_arg = idx;
+    break;
+  }
+  case TS_EVENT_SSL_SERVERNAME: {
+    // Testing lookup argument
+    int idx = 0;
+    while (idx <= last_arg) {
+      const char *name = nullptr;
+      const char *desc = nullptr;
+      if (TSVConnArgIndexLookup(idx, &name, &desc) == TS_SUCCESS) {
+        TSDebug(PLUGIN_NAME, "Successful lookup for arg #%d: [%s] [%s]", idx, name, desc);
+      } else {
+        TSDebug(PLUGIN_NAME, "Failed lookup for arg #%d", idx);
+      }
+      idx++;
+    }
+    break;
+  }
+  case TS_EVENT_VCONN_CLOSE: {
+    // Testing argget and delete
+    int idx = 0;
+    while (idx <= last_arg) {
+      char *buf = (char *)TSVConnArgGet(ssl_vc, idx);
+      if (buf) {
+        TSDebug(PLUGIN_NAME, "Successfully retrieve vconn arg #%d: %s", idx, buf);
+        TSfree(buf);
+      } else {
+        TSDebug(PLUGIN_NAME, "Failed to retrieve vconn arg #%d", idx);
+      }
+      idx++;
+    }
+  } break;
+  default: {
+    TSDebug(PLUGIN_NAME, "Unexpected event %d", event);
+    break;
+  }
+  }
+  TSVConnReenable(ssl_vc);
+  return 0;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSDebug(PLUGIN_NAME, "Initializing plugin.");
+  TSPluginRegistrationInfo info;
+  info.plugin_name   = PLUGIN_NAME;
+  info.vendor_name   = "Oath";
+  info.support_email = "zeyuany@oath.com";
+
+  if (TSPluginRegister(&info) != TS_SUCCESS) {
+    TSError("[%s] Unable to initialize plugin. Failed to register.", PLUGIN_NAME);
+  } else {
+    TSCont cb = TSContCreate(vconn_arg_handler, nullptr);
+    TSHttpHookAdd(TS_VCONN_START_HOOK, cb);
+    TSHttpHookAdd(TS_SSL_SERVERNAME_HOOK, cb);
+    TSHttpHookAdd(TS_VCONN_CLOSE_HOOK, cb);
+  }
+}
diff --git a/iocore/eventsystem/I_VConnection.h b/iocore/eventsystem/I_VConnection.h
index 374302e..006867f 100644
--- a/iocore/eventsystem/I_VConnection.h
+++ b/iocore/eventsystem/I_VConnection.h
@@ -32,6 +32,10 @@
 #error "include I_VIO.h"
 #endif
 
+#include <array>
+
+static constexpr int TS_VCONN_MAX_USER_ARG = 4;
+
 //
 // Data Types
 //
@@ -374,7 +378,38 @@ public:
   int lerrno;
 };
 
-struct DummyVConnection : public VConnection {
+/**
+  Subclass of VConnection to provide support for user arguments
+
+  Inherited by DummyVConnection (down to INKContInternal) and NetVConnection
+*/
+class AnnotatedVConnection : public VConnection
+{
+  using self_type  = AnnotatedVConnection;
+  using super_type = VConnection;
+
+public:
+  AnnotatedVConnection(ProxyMutex *aMutex) : super_type(aMutex){};
+  AnnotatedVConnection(Ptr<ProxyMutex> &aMutex) : super_type(aMutex){};
+
+  void *
+  get_user_arg(unsigned ix) const
+  {
+    ink_assert(ix < user_args.size());
+    return this->user_args[ix];
+  };
+  void
+  set_user_arg(unsigned ix, void *arg)
+  {
+    ink_assert(ix < user_args.size());
+    user_args[ix] = arg;
+  };
+
+protected:
+  std::array<void *, TS_VCONN_MAX_USER_ARG> user_args;
+};
+
+struct DummyVConnection : public AnnotatedVConnection {
   virtual VIO *
   do_io_write(Continuation * /* c ATS_UNUSED */, int64_t /* nbytes ATS_UNUSED */, IOBufferReader * /* buf ATS_UNUSED */,
               bool /* owner ATS_UNUSED */)
@@ -405,7 +440,7 @@ struct DummyVConnection : public VConnection {
                 "cannot use default implementation");
   }
 
-  DummyVConnection(ProxyMutex *m) : VConnection(m) {}
+  DummyVConnection(ProxyMutex *m) : AnnotatedVConnection(m) {}
 };
 
 #endif /*_I_VConnection_h_*/
diff --git a/iocore/net/I_NetVConnection.h b/iocore/net/I_NetVConnection.h
index 12b1448..62e6754 100644
--- a/iocore/net/I_NetVConnection.h
+++ b/iocore/net/I_NetVConnection.h
@@ -258,7 +258,7 @@ struct NetVCOptions {
   stream IO to be done based on a single read or write call.
 
 */
-class NetVConnection : public VConnection
+class NetVConnection : public AnnotatedVConnection
 {
 public:
   // How many bytes have been queued to the OS for sending by haven't been sent yet
@@ -661,7 +661,7 @@ protected:
 };
 
 inline NetVConnection::NetVConnection()
-  : VConnection(nullptr),
+  : AnnotatedVConnection(nullptr),
     attributes(0),
     thread(nullptr),
     got_local_addr(0),
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index ac1cd51..797a689 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -230,14 +230,14 @@ public:
     switch (this->sslHandshakeHookState) {
     case HANDSHAKE_HOOKS_PRE:
     case HANDSHAKE_HOOKS_PRE_INVOKE:
-      if (eventId == TS_EVENT_VCONN_PRE_ACCEPT) {
+      if (eventId == TS_EVENT_VCONN_START) {
         if (curHook) {
           retval = true;
         }
       }
       break;
     case HANDSHAKE_HOOKS_SNI:
-      if (eventId == TS_EVENT_VCONN_PRE_ACCEPT) {
+      if (eventId == TS_EVENT_VCONN_START) {
         retval = true;
       } else if (eventId == TS_EVENT_SSL_SERVERNAME) {
         if (curHook) {
@@ -247,7 +247,7 @@ public:
       break;
     case HANDSHAKE_HOOKS_CERT:
     case HANDSHAKE_HOOKS_CERT_INVOKE:
-      if (eventId == TS_EVENT_VCONN_PRE_ACCEPT || eventId == TS_EVENT_SSL_SERVERNAME) {
+      if (eventId == TS_EVENT_VCONN_START || eventId == TS_EVENT_SSL_SERVERNAME) {
         retval = true;
       } else if (eventId == TS_EVENT_SSL_CERT) {
         if (curHook) {
@@ -257,7 +257,7 @@ public:
       break;
     case HANDSHAKE_HOOKS_CLIENT_CERT:
     case HANDSHAKE_HOOKS_CLIENT_CERT_INVOKE:
-      if (eventId == TS_EVENT_SSL_VERIFY_CLIENT || eventId == TS_EVENT_VCONN_PRE_ACCEPT) {
+      if (eventId == TS_EVENT_SSL_VERIFY_CLIENT || eventId == TS_EVENT_VCONN_START) {
         retval = true;
       }
       break;
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 0bd0bc3..7e1b169 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -122,11 +122,16 @@ public:
        )
   {
     EThread *eth = this_ethread();
-    MUTEX_TRY_LOCK(lock, target->mutex, eth);
-    if (lock.is_locked()) {
+    if (!target->mutex) {
+      // If there's no mutex, plugin doesn't care about locking so why should we?
       target->handleEvent(eventId, edata);
     } else {
-      eventProcessor.schedule_imm(new ContWrapper(mutex, target, eventId, edata), ET_NET);
+      MUTEX_TRY_LOCK(lock, target->mutex, eth);
+      if (lock.is_locked()) {
+        target->handleEvent(eventId, edata);
+      } else {
+        eventProcessor.schedule_imm(new ContWrapper(mutex, target, eventId, edata), ET_NET);
+      }
     }
   }
 
@@ -820,6 +825,7 @@ void
 SSLNetVConnection::do_io_close(int lerrno)
 {
   if (this->ssl != nullptr && sslHandShakeComplete) {
+    callHooks(TS_EVENT_VCONN_CLOSE);
     int shutdown_mode = SSL_get_shutdown(ssl);
     Debug("ssl-shutdown", "previous shutdown state 0x%x", shutdown_mode);
     int new_shutdown_mode = shutdown_mode | SSL_RECEIVED_SHUTDOWN;
@@ -1042,7 +1048,7 @@ SSLNetVConnection::sslServerHandShakeEvent(int &err)
   if (sslHandshakeHookState == HANDSHAKE_HOOKS_PRE) {
     if (!curHook) {
       Debug("ssl", "Initialize preaccept curHook from NULL");
-      curHook = ssl_hooks->get(TS_VCONN_PRE_ACCEPT_INTERNAL_HOOK);
+      curHook = ssl_hooks->get(TS_VCONN_START_INTERNAL_HOOK);
     } else {
       curHook = curHook->next();
     }
@@ -1051,7 +1057,7 @@ SSLNetVConnection::sslServerHandShakeEvent(int &err)
       sslHandshakeHookState = HANDSHAKE_HOOKS_SNI;
     } else {
       sslHandshakeHookState = HANDSHAKE_HOOKS_PRE_INVOKE;
-      ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_PRE_ACCEPT, this);
+      ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_START, this);
       return SSL_WAIT_FOR_HOOK;
     }
   }
@@ -1470,7 +1476,7 @@ SSLNetVConnection::reenable(NetHandler *nh)
     } else if (sslHandshakeHookState == HANDSHAKE_HOOKS_PRE) {
       Debug("ssl", "Reenable preaccept");
       sslHandshakeHookState = HANDSHAKE_HOOKS_PRE_INVOKE;
-      ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_PRE_ACCEPT, this);
+      ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_START, this);
     }
     return;
   } else {
@@ -1518,7 +1524,7 @@ SSLNetVConnection::callHooks(TSEvent eventId)
 {
   // Only dealing with the SNI/CERT hook so far.
   ink_assert(eventId == TS_EVENT_SSL_CERT || eventId == TS_EVENT_SSL_SERVERNAME || eventId == TS_EVENT_SSL_SERVER_VERIFY_HOOK ||
-             eventId == TS_EVENT_SSL_VERIFY_CLIENT);
+             eventId == TS_EVENT_SSL_VERIFY_CLIENT || eventId == TS_EVENT_VCONN_CLOSE);
   Debug("ssl", "callHooks sslHandshakeHookState=%d", this->sslHandshakeHookState);
 
   // Move state if it is appropriate
@@ -1579,6 +1585,11 @@ SSLNetVConnection::callHooks(TSEvent eventId)
     } else {
       curHook = curHook->next();
     }
+  // fallthrough
+  case HANDSHAKE_HOOKS_DONE:
+    if (eventId == TS_EVENT_VCONN_CLOSE) {
+      curHook = ssl_hooks->get(TS_VCONN_CLOSE_INTERNAL_HOOK);
+    }
     break;
   default:
     curHook                     = nullptr;
diff --git a/lib/ts/apidefs.h.in b/lib/ts/apidefs.h.in
index a035812..164dc95 100644
--- a/lib/ts/apidefs.h.in
+++ b/lib/ts/apidefs.h.in
@@ -257,9 +257,9 @@ typedef enum {
 
     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_VCONN_START_HOOK - called just after the connection is created,
+    before other activity such as I/O or TLS handshakes  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.
@@ -286,7 +286,9 @@ typedef enum {
   // 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_VCONN_START_HOOK = TS_SSL_FIRST_HOOK,
+  TS_VCONN_PRE_ACCEPT_HOOK = TS_VCONN_START_HOOK, // Deprecated but compatible for now.
+  TS_VCONN_CLOSE_HOOK,
   TS_SSL_SNI_HOOK,
   TS_SSL_CERT_HOOK = TS_SSL_SNI_HOOK,
   TS_SSL_SERVERNAME_HOOK,
@@ -294,7 +296,7 @@ typedef enum {
   TS_SSL_VERIFY_CLIENT_HOOK,
   TS_SSL_SESSION_HOOK,
   TS_SSL_LAST_HOOK = TS_SSL_SESSION_HOOK,
-  TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK = 23,
+  TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK = 24,
   TS_HTTP_LAST_HOOK
 } TSHttpHookID;
 
@@ -451,7 +453,9 @@ typedef enum {
   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_VCONN_START                          = 60023,
+  TS_EVENT_VCONN_PRE_ACCEPT                     = TS_EVENT_VCONN_START, // Deprecated but still compatible
+  TS_EVENT_VCONN_CLOSE                          = 60026,
   TS_EVENT_LIFECYCLE_MSG                        = 60024,
   TS_EVENT_HTTP_REQUEST_BUFFER_COMPLETE         = 60025,
   TS_EVENT_MGMT_UPDATE                          = 60100,
diff --git a/plugins/experimental/ssl_cert_loader/ssl-cert-loader.cc b/plugins/experimental/ssl_cert_loader/ssl-cert-loader.cc
index c3aacff..9108485 100644
--- a/plugins/experimental/ssl_cert_loader/ssl-cert-loader.cc
+++ b/plugins/experimental/ssl_cert_loader/ssl-cert-loader.cc
@@ -391,7 +391,7 @@ CB_Pre_Accept(TSCont /*contp*/, TSEvent event, void *edata)
   char buff2[INET6_ADDRSTRLEN];
 
   TSDebug(PN, "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)),
+          event == TS_EVENT_VCONN_START ? "good" : "bad", ip.toString(buff, sizeof(buff)),
           ip_client.toString(buff2, sizeof(buff2)));
 
   // Is there a cert already defined for this IP?
@@ -534,7 +534,7 @@ TSPluginInit(int argc, const char *argv[])
     TSError(PCP "Failed to create SNI callback");
   } else {
     TSLifecycleHookAdd(TS_LIFECYCLE_PORTS_INITIALIZED_HOOK, cb_lc);
-    TSHttpHookAdd(TS_VCONN_PRE_ACCEPT_HOOK, cb_pa);
+    TSHttpHookAdd(TS_VCONN_START_HOOK, cb_pa);
     TSHttpHookAdd(TS_SSL_SNI_HOOK, cb_sni);
     success = true;
   }
diff --git a/proxy/InkAPI.cc b/proxy/InkAPI.cc
index 77c7b03..7b79473 100644
--- a/proxy/InkAPI.cc
+++ b/proxy/InkAPI.cc
@@ -97,9 +97,10 @@ static std::type_info const &TYPE_INFO_MGMT_FLOAT = typeid(MgmtFloat);
 struct UserArg {
   /// Types of user args.
   enum Type {
-    TXN,  ///< Transaction based.
-    SSN,  ///< Session based
-    COUNT ///< Fake enum, # of valid entries.
+    TXN,   ///< Transaction based.
+    SSN,   ///< Session based
+    VCONN, ///< VConnection based
+    COUNT  ///< Fake enum, # of valid entries.
   };
 
   std::string name;        ///< Name of reserving plugin.
@@ -5929,9 +5930,10 @@ TSHttpArgIndexReserve(UserArg::Type type, const char *name, const char *descript
   sdk_assert(sdk_sanity_check_null_ptr(name) == TS_SUCCESS);
   sdk_assert(0 <= type && type < UserArg::Type::COUNT);
 
-  int idx = UserArgIdx[type]++;
+  int idx   = UserArgIdx[type]++;
+  int limit = (type == UserArg::Type::VCONN) ? TS_VCONN_MAX_USER_ARG : TS_HTTP_MAX_USER_ARG;
 
-  if (idx < TS_HTTP_MAX_USER_ARG) {
+  if (idx < limit) {
     UserArg &arg(UserArgTable[type][idx]);
     arg.name = name;
     if (description)
@@ -6018,6 +6020,24 @@ TSHttpSsnArgIndexNameLookup(const char *name, int *arg_idx, const char **descrip
   return TSHttpArgIndexNameLookup(UserArg::SSN, name, arg_idx, description);
 }
 
+TSReturnCode
+TSVConnArgIndexReserve(const char *name, const char *description, int *arg_idx)
+{
+  return TSHttpArgIndexReserve(UserArg::VCONN, name, description, arg_idx);
+}
+
+TSReturnCode
+TSVConnArgIndexLookup(int arg_idx, const char **name, const char **description)
+{
+  return TSHttpArgIndexLookup(UserArg::VCONN, arg_idx, name, description);
+}
+
+TSReturnCode
+TSVConnArgIndexNameLookup(const char *name, int *arg_idx, const char **description)
+{
+  return TSHttpArgIndexNameLookup(UserArg::VCONN, name, arg_idx, description);
+}
+
 void
 TSHttpTxnArgSet(TSHttpTxn txnp, int arg_idx, void *arg)
 {
@@ -6060,6 +6080,25 @@ TSHttpSsnArgGet(TSHttpSsn ssnp, int arg_idx)
 }
 
 void
+TSVConnArgSet(TSVConn connp, int arg_idx, void *arg)
+{
+  sdk_assert(sdk_sanity_check_iocore_structure(connp) == TS_SUCCESS);
+  sdk_assert(arg_idx >= 0 && arg_idx < TS_VCONN_MAX_USER_ARG);
+  AnnotatedVConnection *avc = reinterpret_cast<AnnotatedVConnection *>(connp);
+  avc->set_user_arg(arg_idx, arg);
+}
+
+void *
+TSVConnArgGet(TSVConn connp, int arg_idx)
+{
+  sdk_assert(sdk_sanity_check_iocore_structure(connp) == TS_SUCCESS);
+  sdk_assert(arg_idx >= 0 && arg_idx < TS_VCONN_MAX_USER_ARG);
+
+  AnnotatedVConnection *avc = reinterpret_cast<AnnotatedVConnection *>(connp);
+  return avc->get_user_arg(arg_idx);
+}
+
+void
 TSHttpTxnSetHttpRetStatus(TSHttpTxn txnp, TSHttpStatus http_retstatus)
 {
   sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
diff --git a/proxy/InkAPIInternal.h b/proxy/InkAPIInternal.h
index 34c23be..4b2e58d 100644
--- a/proxy/InkAPIInternal.h
+++ b/proxy/InkAPIInternal.h
@@ -278,7 +278,8 @@ class HttpAPIHooks : public FeatureAPIHooks<TSHttpHookID, TS_HTTP_LAST_HOOK>
 
 typedef enum {
   TS_SSL_INTERNAL_FIRST_HOOK,
-  TS_VCONN_PRE_ACCEPT_INTERNAL_HOOK = TS_SSL_INTERNAL_FIRST_HOOK,
+  TS_VCONN_START_INTERNAL_HOOK = TS_SSL_INTERNAL_FIRST_HOOK,
+  TS_VCONN_CLOSE_INTERNAL_HOOK,
   TS_SSL_CERT_INTERNAL_HOOK,
   TS_SSL_SERVERNAME_INTERNAL_HOOK,
   TS_SSL_SERVER_VERIFY_INTERNAL_HOOK,
diff --git a/proxy/InkAPITest.cc b/proxy/InkAPITest.cc
index 8bcdae8..00b79b9 100644
--- a/proxy/InkAPITest.cc
+++ b/proxy/InkAPITest.cc
@@ -5540,14 +5540,15 @@ typedef enum {
   ORIG_TS_HTTP_POST_REMAP_HOOK,
   ORIG_TS_HTTP_RESPONSE_CLIENT_HOOK,
   ORIG_TS_SSL_FIRST_HOOK,
-  ORIG_TS_VCONN_PRE_ACCEPT_HOOK = ORIG_TS_SSL_FIRST_HOOK,
+  ORIG_TS_VCONN_START_HOOK = ORIG_TS_SSL_FIRST_HOOK,
+  ORIG_TS_VCONN_CLOSE_HOOK,
   ORIG_TS_SSL_SNI_HOOK,
   ORIG_TS_SSL_SERVERNAME_HOOK,
   ORIG_TS_SSL_SERVER_VERIFY_HOOK,
   ORIG_TS_SSL_VERIFY_CLIENT_HOOK,
   ORIG_TS_SSL_SESSION_HOOK,
   ORIG_TS_SSL_LAST_HOOK                          = ORIG_TS_SSL_SESSION_HOOK,
-  ORIG_TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK = 23,
+  ORIG_TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK = 24,
   ORIG_TS_HTTP_LAST_HOOK
 } ORIG_TSHttpHookID;
 
diff --git a/proxy/api/ts/ts.h b/proxy/api/ts/ts.h
index 2f07c64..1d6314f 100644
--- a/proxy/api/ts/ts.h
+++ b/proxy/api/ts/ts.h
@@ -1529,6 +1529,8 @@ tsapi void TSHttpTxnArgSet(TSHttpTxn txnp, int arg_idx, void *arg);
 tsapi void *TSHttpTxnArgGet(TSHttpTxn txnp, int arg_idx);
 tsapi void TSHttpSsnArgSet(TSHttpSsn ssnp, int arg_idx, void *arg);
 tsapi void *TSHttpSsnArgGet(TSHttpSsn ssnp, int arg_idx);
+tsapi void TSVConnArgSet(TSVConn connp, int arg_idx, void *arg);
+tsapi void *TSVConnArgGet(TSVConn connp, int arg_idx);
 
 /* The reserve API should only be use in TSAPI plugins, during plugin initialization! */
 /* The lookup methods can be used anytime, but are best used during initialization as well,
@@ -1539,6 +1541,9 @@ tsapi TSReturnCode TSHttpTxnArgIndexLookup(int arg_idx, const char **name, const
 tsapi TSReturnCode TSHttpSsnArgIndexReserve(const char *name, const char *description, int *arg_idx);
 tsapi TSReturnCode TSHttpSsnArgIndexNameLookup(const char *name, int *arg_idx, const char **description);
 tsapi TSReturnCode TSHttpSsnArgIndexLookup(int arg_idx, const char **name, const char **description);
+tsapi TSReturnCode TSVConnArgIndexReserve(const char *name, const char *description, int *arg_idx);
+tsapi TSReturnCode TSVConnArgIndexNameLookup(const char *name, int *arg_idx, const char **description);
+tsapi TSReturnCode TSVConnArgIndexLookup(int arg_idx, const char **name, const char **description);
 
 /* ToDo: This is a leftover from olden days, can we eliminate? */
 tsapi void TSHttpTxnSetHttpRetStatus(TSHttpTxn txnp, TSHttpStatus http_retstatus);
diff --git a/proxy/http/HttpDebugNames.cc b/proxy/http/HttpDebugNames.cc
index cfb5012..8937eb6 100644
--- a/proxy/http/HttpDebugNames.cc
+++ b/proxy/http/HttpDebugNames.cc
@@ -460,8 +460,10 @@ HttpDebugNames::get_api_hook_name(TSHttpHookID t)
     return "TS_HTTP_RESPONSE_CLIENT_HOOK";
   case TS_HTTP_LAST_HOOK:
     return "TS_HTTP_LAST_HOOK";
-  case TS_VCONN_PRE_ACCEPT_HOOK:
-    return "TS_VCONN_PRE_ACCEPT_HOOK";
+  case TS_VCONN_START_HOOK:
+    return "TS_VCONN_START_HOOK";
+  case TS_VCONN_CLOSE_HOOK:
+    return "TS_VCONN_CLOSE_HOOK";
   case TS_SSL_CERT_HOOK:
     return "TS_SSL_CERT_HOOK";
   case TS_SSL_SERVERNAME_HOOK:
diff --git a/tests/tools/plugins/ssl_hook_test.cc b/tests/tools/plugins/ssl_hook_test.cc
index e8ae055..7321b9c 100644
--- a/tests/tools/plugins/ssl_hook_test.cc
+++ b/tests/tools/plugins/ssl_hook_test.cc
@@ -51,7 +51,7 @@ CB_Pre_Accept(TSCont cont, TSEvent event, void *edata)
 
   int count = reinterpret_cast<intptr_t>(TSContDataGet(cont));
 
-  TSDebug(PN, "Pre accept callback %d %p - event is %s", count, ssl_vc, event == TS_EVENT_VCONN_PRE_ACCEPT ? "good" : "bad");
+  TSDebug(PN, "Pre accept callback %d %p - event is %s", count, ssl_vc, event == TS_EVENT_VCONN_START ? "good" : "bad");
 
   // All done, reactivate things
   TSVConnReenable(ssl_vc);
@@ -65,7 +65,7 @@ CB_Pre_Accept_Delay(TSCont cont, TSEvent event, void *edata)
 
   int count = reinterpret_cast<intptr_t>(TSContDataGet(cont));
 
-  TSDebug(PN, "Pre accept delay callback %d %p - event is %s", count, ssl_vc, event == TS_EVENT_VCONN_PRE_ACCEPT ? "good" : "bad");
+  TSDebug(PN, "Pre accept delay callback %d %p - event is %s", count, ssl_vc, event == TS_EVENT_VCONN_START ? "good" : "bad");
 
   TSCont cb = TSContCreate(&ReenableSSL, TSMutexCreate());
 
@@ -180,18 +180,18 @@ setup_callbacks(TSHttpTxn txn, int preaccept_count, int sni_count, int cert_coun
     cb = TSContCreate(&CB_Pre_Accept, TSMutexCreate());
     TSContDataSet(cb, (void *)(intptr_t)i);
     if (txn) {
-      TSHttpTxnHookAdd(txn, TS_VCONN_PRE_ACCEPT_HOOK, cb);
+      TSHttpTxnHookAdd(txn, TS_VCONN_START_HOOK, cb);
     } else {
-      TSHttpHookAdd(TS_VCONN_PRE_ACCEPT_HOOK, cb);
+      TSHttpHookAdd(TS_VCONN_START_HOOK, cb);
     }
   }
   for (i = 0; i < preaccept_count_delay; i++) {
     cb = TSContCreate(&CB_Pre_Accept_Delay, TSMutexCreate());
     TSContDataSet(cb, (void *)(intptr_t)i);
     if (txn) {
-      TSHttpTxnHookAdd(txn, TS_VCONN_PRE_ACCEPT_HOOK, cb);
+      TSHttpTxnHookAdd(txn, TS_VCONN_START_HOOK, cb);
     } else {
-      TSHttpHookAdd(TS_VCONN_PRE_ACCEPT_HOOK, cb);
+      TSHttpHookAdd(TS_VCONN_START_HOOK, cb);
     }
   }
   for (i = 0; i < sni_count; i++) {

-- 
To stop receiving notification emails like this one, please contact
amc@apache.org.