[glib-networking/wip/pwithnall/dtls: 17/18] gnutls: Implement vectored I/O support for TLS and DTLS



commit d0f3d83f0b09a99f4f910791e905a9a0097d91d1
Author: Philip Withnall <philip withnall collabora co uk>
Date:   Thu Jul 23 17:16:47 2015 +0100

    gnutls: Implement vectored I/O support for TLS and DTLS
    
    This bumps the GnuTLS dependency to 3.3.5 for
    gnutls_record_recv_packet(), which gives us access to the internal
    plaintext GnuTLS buffer, from which we can copy out to multiple
    GInputVectors.
    
    Similarly, add support for vectored sends, using gnutls_record_cork()
    (requires GnuTLS 3.1.9) to queue up multiple vectors from a single
    message before sending the message.
    
    Include some trivial modifications of the unit tests to use multiple
    vectors in a message.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=697908

 configure.ac                       |    2 +-
 tls/gnutls/gtlsconnection-gnutls.c |  203 +++++++++++++++++++++++++++++++-----
 tls/tests/dtls-connection.c        |   15 ++-
 3 files changed, 185 insertions(+), 35 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 0ce8b99..3a68556 100644
--- a/configure.ac
+++ b/configure.ac
@@ -84,7 +84,7 @@ dnl *****************************
 dnl *** Checks for GNUTLS     ***
 dnl *****************************
 
-GNUTLS_MIN_REQUIRED=3.0
+GNUTLS_MIN_REQUIRED=3.3.5
 
 AC_ARG_WITH(gnutls,
     [AC_HELP_STRING([--with-gnutls],
diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c
index 07379b0..4e1baf2 100644
--- a/tls/gnutls/gtlsconnection-gnutls.c
+++ b/tls/gnutls/gtlsconnection-gnutls.c
@@ -1998,6 +1998,105 @@ g_tls_connection_gnutls_read (GTlsConnectionGnutls  *gnutls,
     return -1;
 }
 
+static gsize
+input_vectors_from_gnutls_datum_t (GInputVector          *vectors,
+                                   guint                  num_vectors,
+                                   const gnutls_datum_t  *datum)
+{
+  guint i;
+  gsize total = 0;
+
+  /* Copy into the receive vectors. */
+  for (i = 0; i < num_vectors && total < datum->size; i++)
+    {
+      gsize count;
+      GInputVector *vec = &vectors[i];
+
+      count = MIN (vec->size, datum->size - total);
+
+      memcpy (vec->buffer, datum->data + total, count);
+      total += count;
+    }
+
+  g_assert (total <= datum->size);
+
+  return total;
+}
+
+static gssize
+g_tls_connection_gnutls_read_message (GTlsConnectionGnutls  *gnutls,
+                                      GInputVector          *vectors,
+                                      guint                  num_vectors,
+                                      gint                  *flags,
+                                      gint64                 timeout,
+                                      GCancellable          *cancellable,
+                                      GError               **error)
+{
+  guint i;
+  gssize ret;
+  gnutls_packet_t packet = { 0, };
+
+  if (flags != NULL && *flags != G_SOCKET_MSG_NONE)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Receive flags are not supported"));
+      return -1;
+    }
+
+  /* Copy data out of the app data buffer first. */
+  if (gnutls->priv->app_data_buf && !gnutls->priv->handshaking)
+    {
+      ret = 0;
+
+      for (i = 0; i < num_vectors; i++)
+        {
+          gsize count;
+          GInputVector *vec = &vectors[i];
+
+          count = MIN (vec->size, gnutls->priv->app_data_buf->len);
+          ret += count;
+
+          memcpy (vec->buffer, gnutls->priv->app_data_buf->data, count);
+          if (count == gnutls->priv->app_data_buf->len)
+            g_clear_pointer (&gnutls->priv->app_data_buf, g_byte_array_unref);
+          else
+            g_byte_array_remove_range (gnutls->priv->app_data_buf, 0, count);
+        }
+
+      return ret;
+    }
+
+ again:
+  if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ,
+                timeout != 0, cancellable, error))
+    return -1;
+
+  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout != 0, cancellable);
+
+  /* Receive the entire datagram (zero-copy). */
+  ret = gnutls_record_recv_packet (gnutls->priv->session, &packet);
+
+  if (ret > 0)
+    {
+      gnutls_datum_t data = { 0, };
+
+      gnutls_packet_get (packet, &data, NULL);
+      ret = input_vectors_from_gnutls_datum_t (vectors, num_vectors, &data);
+      gnutls_packet_deinit (packet);
+    }
+
+  END_GNUTLS_IO (gnutls, G_IO_IN, ret, _("Error reading data from TLS socket: %s"), error);
+
+  yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ);
+
+  if (ret >= 0)
+    return ret;
+  else if (ret == GNUTLS_E_REHANDSHAKE)
+    goto again;
+  else
+    return -1;
+}
+
 static gint
 g_tls_connection_gnutls_receive_messages (GDatagramBased  *datagram_based,
                                           GInputMessage   *messages,
@@ -2025,23 +2124,13 @@ g_tls_connection_gnutls_receive_messages (GDatagramBased  *datagram_based,
       GInputMessage *message = &messages[i];
       gssize n_bytes_read;
 
-      /* FIXME: Unfortunately GnuTLS doesn’t have a vectored read function.
-       * See: https://gitlab.com/gnutls/gnutls/issues/16 */
-      g_assert (message->num_vectors == 1);
-
-      if (message->flags != G_SOCKET_MSG_NONE)
-        {
-          g_set_error (&child_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
-                       _("Receive flags are not supported"));
-          break;
-        }
-
-      n_bytes_read = g_tls_connection_gnutls_read (gnutls,
-                                                   message->vectors[0].buffer,
-                                                   message->vectors[0].size,
-                                                   timeout != 0,
-                                                   cancellable,
-                                                   &child_error);
+      n_bytes_read = g_tls_connection_gnutls_read_message (gnutls,
+                                                           message->vectors,
+                                                           message->num_vectors,
+                                                           &message->flags,
+                                                           timeout,
+                                                           cancellable,
+                                                           &child_error);
 
       if (n_bytes_read > 0)
         {
@@ -2128,6 +2217,69 @@ g_tls_connection_gnutls_write (GTlsConnectionGnutls  *gnutls,
     return -1;
 }
 
+static gssize
+g_tls_connection_gnutls_write_message (GTlsConnectionGnutls  *gnutls,
+                                       GOutputVector         *vectors,
+                                       guint                  num_vectors,
+                                       gint64                 timeout,
+                                       GCancellable          *cancellable,
+                                       GError               **error)
+{
+  gssize ret;
+  guint i;
+  gsize total_message_size;
+
+ again:
+  if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE,
+                 timeout != 0, cancellable, error))
+    return -1;
+
+  /* Calculate the total message size and check it’s not too big. */
+  for (i = 0, total_message_size = 0; i < num_vectors; i++)
+    total_message_size += vectors[i].size;
+
+  if (gnutls_dtls_get_data_mtu (gnutls->priv->session) < total_message_size)
+    {
+      ret = GNUTLS_E_LARGE_PACKET;
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
+                   _("Message of size %lu bytes is too large for "
+                     "DTLS connection, maximum is %u bytes"),
+                   total_message_size,
+                   (guint) gnutls_dtls_get_data_mtu (gnutls->priv->session));
+      goto done;
+    }
+
+  /* Queue up the data from all the vectors. */
+  gnutls_record_cork (gnutls->priv->session);
+
+  for (i = 0; i < num_vectors; i++)
+    {
+      ret = gnutls_record_send (gnutls->priv->session,
+                                vectors[i].buffer, vectors[i].size);
+
+      if (ret < 0 || ret < vectors[i].size)
+        {
+          /* Uncork to restore state, then bail. The peer will receive a
+           * truncated datagram. */
+          break;
+        }
+    }
+
+  BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout != 0, cancellable);
+  ret = gnutls_record_uncork (gnutls->priv->session, 0  /* flags */);
+  END_GNUTLS_IO (gnutls, G_IO_OUT, ret, _("Error writing data to TLS socket: %s"), error);
+
+ done:
+  yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE);
+
+  if (ret >= 0)
+    return ret;
+  else if (ret == GNUTLS_E_REHANDSHAKE)
+    goto again;
+  else
+    return -1;
+}
+
 static gint
 g_tls_connection_gnutls_send_messages (GDatagramBased  *datagram_based,
                                        GOutputMessage  *messages,
@@ -2155,17 +2307,12 @@ g_tls_connection_gnutls_send_messages (GDatagramBased  *datagram_based,
       GOutputMessage *message = &messages[i];
       gssize n_bytes_sent;
 
-      /* FIXME: Unfortunately GnuTLS doesn’t have a vectored write function.
-       * See: https://gitlab.com/gnutls/gnutls/issues/16 */
-      /* TODO: gnutls_record_cork(), gnutls_record_uncork(), 3.3.0 */
-      g_assert (message->num_vectors == 1);
-
-      n_bytes_sent = g_tls_connection_gnutls_write (gnutls,
-                                                    message->vectors[0].buffer,
-                                                    message->vectors[0].size,
-                                                    timeout != 0,
-                                                    cancellable,
-                                                    &child_error);
+      n_bytes_sent = g_tls_connection_gnutls_write_message (gnutls,
+                                                            message->vectors,
+                                                            message->num_vectors,
+                                                            timeout,
+                                                            cancellable,
+                                                            &child_error);
 
       if (n_bytes_sent >= 0)
         {
diff --git a/tls/tests/dtls-connection.c b/tls/tests/dtls-connection.c
index ac4fbab..87393e2 100644
--- a/tls/tests/dtls-connection.c
+++ b/tls/tests/dtls-connection.c
@@ -211,11 +211,11 @@ on_rehandshake_finish (GObject        *object,
 {
   TestConnection *test = user_data;
   GError *error = NULL;
-  GOutputVector vector = {
-    TEST_DATA + TEST_DATA_LENGTH / 2,
-    TEST_DATA_LENGTH / 2
+  GOutputVector vectors[2] = {
+    { TEST_DATA + TEST_DATA_LENGTH / 2, TEST_DATA_LENGTH / 4 },
+    { TEST_DATA + 3 * TEST_DATA_LENGTH / 4, TEST_DATA_LENGTH / 4},
   };
-  GOutputMessage message = { NULL, &vector, 1, 0, NULL, 0 };
+  GOutputMessage message = { NULL, vectors, G_N_ELEMENTS (vectors), 0, NULL, 0 };
   gint n_sent;
 
   g_dtls_connection_handshake_finish (G_DTLS_CONNECTION (object), res, &error);
@@ -400,8 +400,11 @@ read_test_data_async (TestConnection *test)
   gchar *check;
   GError *error = NULL;
   guint8 buf[TEST_DATA_LENGTH * 2];
-  GInputVector vector = { &buf, sizeof (buf) };
-  GInputMessage message = { NULL, &vector, 1, 0, 0, NULL, NULL };
+  GInputVector vectors[2] = {
+    { &buf, sizeof (buf) / 2 },
+    { &buf + sizeof (buf) / 2, sizeof (buf) / 2 },
+  };
+  GInputMessage message = { NULL, vectors, G_N_ELEMENTS (vectors), 0, 0, NULL, NULL };
   gint n_read;
 
   do


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]