tls: fix ho leak on tcp connect return
[vpp.git] / src / vnet / tls / tls.c
index 159ed85..a27d731 100644 (file)
@@ -117,6 +117,9 @@ tls_ctx_half_open_alloc (void)
   tls_main_t *tm = &tls_main;
   tls_ctx_t *ctx;
 
+  if (vec_len (tm->postponed_ho_free))
+    tls_flush_postponed_ho_cleanups ();
+
   pool_get_aligned_safe (tm->half_open_ctx_pool, ctx, CLIB_CACHE_LINE_BYTES);
 
   clib_memset (ctx, 0, sizeof (*ctx));
@@ -139,6 +142,49 @@ tls_ctx_half_open_get (u32 ctx_index)
   return pool_elt_at_index (tm->half_open_ctx_pool, ctx_index);
 }
 
+void
+tls_add_postponed_ho_cleanups (u32 ho_index)
+{
+  tls_main_t *tm = &tls_main;
+  vec_add1 (tm->postponed_ho_free, ho_index);
+}
+
+static void
+tls_ctx_ho_try_free (u32 ho_index)
+{
+  tls_ctx_t *ctx;
+
+  ctx = tls_ctx_half_open_get (ho_index);
+  /* Probably tcp connected just before tcp establish timeout and
+   * worker that owns established session has not yet received
+   * @ref tls_session_connected_cb */
+  if (!(ctx->flags & TLS_CONN_F_HO_DONE))
+    {
+      ctx->tls_session_handle = SESSION_INVALID_HANDLE;
+      tls_add_postponed_ho_cleanups (ho_index);
+      return;
+    }
+  if (!ctx->no_app_session)
+    session_half_open_delete_notify (&ctx->connection);
+  tls_ctx_half_open_free (ho_index);
+}
+
+void
+tls_flush_postponed_ho_cleanups ()
+{
+  tls_main_t *tm = &tls_main;
+  u32 *ho_indexp, *tmp;
+
+  tmp = tm->postponed_ho_free;
+  tm->postponed_ho_free = tm->ho_free_list;
+  tm->ho_free_list = tmp;
+
+  vec_foreach (ho_indexp, tm->ho_free_list)
+    tls_ctx_ho_try_free (*ho_indexp);
+
+  vec_reset_length (tm->ho_free_list);
+}
+
 void
 tls_notify_app_enqueue (tls_ctx_t * ctx, session_t * app_session)
 {
@@ -170,6 +216,7 @@ tls_notify_app_accept (tls_ctx_t * ctx)
     {
       TLS_DBG (1, "failed to allocate fifos");
       session_free (app_session);
+      ctx->no_app_session = 1;
       return rv;
     }
   ctx->app_session_handle = session_handle (app_session);
@@ -227,7 +274,12 @@ tls_notify_app_connected (tls_ctx_t * ctx, session_error_t err)
   app_session->opaque = ctx->parent_app_api_context;
 
   if ((err = app_worker_init_connected (app_wrk, app_session)))
-    goto failed;
+    {
+      app_worker_connect_notify (app_wrk, 0, err, ctx->parent_app_api_context);
+      ctx->no_app_session = 1;
+      session_free (app_session);
+      return -1;
+    }
 
   app_session->session_state = SESSION_STATE_READY;
   parent_app_api_ctx = ctx->parent_app_api_context;
@@ -244,9 +296,6 @@ tls_notify_app_connected (tls_ctx_t * ctx, session_error_t err)
 
   return 0;
 
-failed:
-  ctx->no_app_session = 1;
-  tls_disconnect (ctx->tls_ctx_handle, vlib_get_thread_index ());
 send_reply:
   return app_worker_connect_notify (app_wrk, 0, err,
                                    ctx->parent_app_api_context);
@@ -407,26 +456,23 @@ tls_session_reset_callback (session_t * s)
       tls_disconnect_transport (ctx);
     }
   else
-    if ((app_session =
-        session_get_if_valid (ctx->c_s_index, ctx->c_thread_index)))
     {
-      session_free (app_session);
-      ctx->c_s_index = SESSION_INVALID_INDEX;
-      tls_disconnect_transport (ctx);
+      app_session = session_get_if_valid (ctx->c_s_index, ctx->c_thread_index);
+      if (app_session)
+       {
+         session_free (app_session);
+         ctx->c_s_index = SESSION_INVALID_INDEX;
+         ctx->no_app_session = 1;
+         tls_disconnect_transport (ctx);
+       }
     }
 }
 
 static void
 tls_session_cleanup_ho (session_t *s)
 {
-  tls_ctx_t *ctx;
-  u32 ho_index;
-
   /* session opaque stores the opaque passed on connect */
-  ho_index = s->opaque;
-  ctx = tls_ctx_half_open_get (ho_index);
-  session_half_open_delete_notify (&ctx->connection);
-  tls_ctx_half_open_free (ho_index);
+  tls_ctx_ho_try_free (s->opaque);
 }
 
 int
@@ -464,7 +510,6 @@ tls_session_accept_callback (session_t * tls_session)
   session_t *tls_listener, *app_session;
   tls_ctx_t *lctx, *ctx;
   u32 ctx_handle;
-  int rv;
 
   tls_listener =
     listen_session_get_from_handle (tls_session->listener_handle);
@@ -494,32 +539,37 @@ tls_session_accept_callback (session_t * tls_session)
   TLS_DBG (1, "Accept on listener %u new connection [%u]%x",
           tls_listener->opaque, vlib_get_thread_index (), ctx_handle);
 
-  rv = tls_ctx_init_server (ctx);
-  if (rv)
+  if (tls_ctx_init_server (ctx))
     {
+      /* Do not free ctx yet, in case we have pending rx events */
       session_free (app_session);
-      tls_ctx_free (ctx);
+      ctx->no_app_session = 1;
+      tls_disconnect_transport (ctx);
     }
 
-  return rv;
+  return 0;
 }
 
 int
-tls_app_rx_callback (session_t * tls_session)
+tls_app_rx_callback (session_t *ts)
 {
   tls_ctx_t *ctx;
 
   /* DTLS session migrating, wait for next notification */
-  if (PREDICT_FALSE (tls_session->flags & SESSION_F_IS_MIGRATING))
+  if (PREDICT_FALSE (ts->flags & SESSION_F_IS_MIGRATING))
     return 0;
 
-  ctx = tls_ctx_get (tls_session->opaque);
-  if (PREDICT_FALSE (ctx->no_app_session))
+  /* Read rescheduled but underlying transport deleted now */
+  if (PREDICT_FALSE ((ts->session_state == SESSION_STATE_TRANSPORT_DELETED)))
+    return 0;
+
+  ctx = tls_ctx_get (ts->opaque);
+  if (PREDICT_FALSE (ctx->no_app_session || ctx->app_closed))
     {
       TLS_DBG (1, "Local App closed");
       return 0;
     }
-  tls_ctx_read (ctx, tls_session);
+  tls_ctx_read (ctx, ts);
   return 0;
 }
 
@@ -542,9 +592,9 @@ tls_session_connected_cb (u32 tls_app_index, u32 ho_ctx_index,
   tls_ctx_t *ho_ctx, *ctx;
   session_type_t st;
   u32 ctx_handle;
-  int rv;
 
   ho_ctx = tls_ctx_half_open_get (ho_ctx_index);
+  ho_ctx->flags |= TLS_CONN_F_HO_DONE;
 
   ctx_handle = tls_ctx_alloc (ho_ctx->tls_ctx_engine);
   ctx = tls_ctx_get (ctx_handle);
@@ -572,14 +622,13 @@ tls_session_connected_cb (u32 tls_app_index, u32 ho_ctx_index,
   app_session->session_type = st;
   app_session->connection_index = ctx->tls_ctx_handle;
 
-  rv = tls_ctx_init_client (ctx);
-  if (rv)
+  if (tls_ctx_init_client (ctx))
     {
-      session_free (app_session);
-      tls_ctx_free (ctx);
+      tls_notify_app_connected (ctx, SESSION_E_TLS_HANDSHAKE);
+      tls_disconnect_transport (ctx);
     }
 
-  return rv;
+  return 0;
 }
 
 int
@@ -611,6 +660,7 @@ tls_session_connected_callback (u32 tls_app_index, u32 ho_ctx_index,
       u32 api_context;
 
       ho_ctx = tls_ctx_half_open_get (ho_ctx_index);
+      ho_ctx->flags |= TLS_CONN_F_HO_DONE;
       app_wrk = app_worker_get_if_valid (ho_ctx->parent_app_wrk_index);
       if (app_wrk)
        {
@@ -697,11 +747,22 @@ dtls_session_migrate_callback (session_t *us, session_handle_t new_sh)
   tls_ctx_free (ctx);
 }
 
+static void
+tls_session_transport_closed_callback (session_t *ts)
+{
+  tls_ctx_t *ctx;
+
+  ctx = tls_ctx_get_w_thread (ts->opaque, ts->thread_index);
+  if (!ctx->no_app_session)
+    session_transport_closed_notify (&ctx->connection);
+}
+
 static session_cb_vft_t tls_app_cb_vft = {
   .session_accept_callback = tls_session_accept_callback,
   .session_disconnect_callback = tls_session_disconnect_callback,
   .session_connected_callback = tls_session_connected_callback,
   .session_reset_callback = tls_session_reset_callback,
+  .session_transport_closed_callback = tls_session_transport_closed_callback,
   .half_open_cleanup_callback = tls_session_cleanup_ho,
   .add_segment_callback = tls_add_segment_callback,
   .del_segment_callback = tls_del_segment_callback,
@@ -763,7 +824,10 @@ tls_connect (transport_endpoint_cfg_t * tep)
   cargs->api_context = ctx_index;
   cargs->sep_ext.ns_index = app->ns_index;
   if ((rv = vnet_connect (cargs)))
-    return rv;
+    {
+      tls_ctx_half_open_free (ctx_index);
+      return rv;
+    }
 
   /* Track half-open tcp session in case we need to clean it up */
   ctx->tls_session_handle = cargs->sh;
@@ -932,10 +996,27 @@ static void
 tls_cleanup_ho (u32 ho_index)
 {
   tls_ctx_t *ctx;
+  session_t *s;
 
   ctx = tls_ctx_half_open_get (ho_index);
-  session_cleanup_half_open (ctx->tls_session_handle);
-  tls_ctx_half_open_free (ho_index);
+  /* Already pending cleanup */
+  if (ctx->tls_session_handle == SESSION_INVALID_HANDLE)
+    {
+      ASSERT (ctx->flags & TLS_CONN_F_HO_DONE);
+      ctx->no_app_session = 1;
+      return;
+    }
+
+  s = session_get_from_handle (ctx->tls_session_handle);
+  /* If no pending cleanup notification, force cleanup now. Otherwise,
+   * wait for cleanup notification and set no app session on ctx */
+  if (s->session_state != SESSION_STATE_TRANSPORT_DELETED)
+    {
+      session_cleanup_half_open (ctx->tls_session_handle);
+      tls_ctx_half_open_free (ho_index);
+    }
+  else
+    ctx->no_app_session = 1;
 }
 
 int