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



commit 8e84ca7e1122aa288345bf0839e0f625155a9ac5
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

 meson.build                        |    2 +-
 tls/gnutls/gtlsconnection-gnutls.c |  187 ++++++++++++++++++++++++++++++++----
 tls/tests/dtls-connection.c        |   15 ++-
 3 files changed, 176 insertions(+), 28 deletions(-)
---
diff --git a/meson.build b/meson.build
index e193b5f..4254037 100644
--- a/meson.build
+++ b/meson.build
@@ -65,7 +65,7 @@ enable_tls_support = get_option('tls_support')
 enable_pkcs11_support = false
 
 if enable_tls_support
-  gnutls_dep = dependency('gnutls', version: '>= 3.0', required: true)
+  gnutls_dep = dependency('gnutls', version: '>= 3.3.5', required: true)
 
   msg = 'location of system Certificate Authority list: '
   res = run_command(join_paths(meson.source_root(), 'find-ca-certificates'), 
get_option('ca_certificates_path'))
diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c
index 5f4899e..c56b33b 100644
--- a/tls/gnutls/gtlsconnection-gnutls.c
+++ b/tls/gnutls/gtlsconnection-gnutls.c
@@ -2031,6 +2031,97 @@ 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,
+                                      gint64                 timeout,
+                                      GCancellable          *cancellable,
+                                      GError               **error)
+{
+  guint i;
+  gssize ret;
+  gnutls_packet_t packet = { 0, };
+
+  /* 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,
@@ -2058,16 +2149,12 @@ 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);
-
-      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,
+                                                           timeout,
+                                                           cancellable,
+                                                           &child_error);
 
       if (message->address != NULL)
         *message->address = NULL;
@@ -2141,6 +2228,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,
@@ -2168,17 +2318,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 842ba06..5c4e1b9 100644
--- a/tls/tests/dtls-connection.c
+++ b/tls/tests/dtls-connection.c
@@ -214,11 +214,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);
@@ -404,8 +404,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]