Add tizen dlog for debugging
[platform/upstream/glib-networking.git] / tls / gnutls / gtlsconnection-gnutls.c
old mode 100644 (file)
new mode 100755 (executable)
index ef53bb2..1163fb5
@@ -1,11 +1,14 @@
-/* GIO - GLib Input, Output and Streaming Library
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
  *
  * Copyright 2009 Red Hat, Inc
+ * Copyright 2015, 2016 Collabora, Ltd.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
+ * version 2.1 of the License, or (at your option) any later version.
  *
  * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * You should have received a copy of the GNU Lesser General
  * Public License along with this library; if not, see
  * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
  */
 
 #include "config.h"
 #include "glib.h"
 
 #include <errno.h>
+#include <stdarg.h>
+#include <gnutls/dtls.h>
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
 
 #include "gtlsconnection-gnutls.h"
 #include "gtlsbackend-gnutls.h"
 #include "gtlscertificate-gnutls.h"
+#include "gtlsclientconnection-gnutls.h"
 #include "gtlsinputstream-gnutls.h"
 #include "gtlsoutputstream-gnutls.h"
 #include "gtlsserverconnection-gnutls.h"
 
-#ifdef HAVE_PKCS11
-#include <p11-kit/pin.h>
-#include "pkcs11/gpkcs11pin.h"
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <winerror.h>
+
+/* It isn’t clear whether MinGW always defines EMSGSIZE. */
+#ifndef EMSGSIZE
+#define EMSGSIZE WSAEMSGSIZE
+#endif
 #endif
 
 #include <glib/gi18n-lib.h>
+#include <glib/gprintf.h>
+
+/*
+ * GTlsConnectionGnutls is the base abstract implementation of TLS and DTLS
+ * support, for both the client and server side of a connection. The choice
+ * between TLS and DTLS is made by setting the base-io-stream or
+ * base-socket properties — exactly one of them must be set at
+ * construction time.
+ *
+ * Client and server specific code is in the GTlsClientConnectionGnutls and
+ * GTlsServerConnectionGnutls concrete subclasses, although the line about where
+ * code is put is a little blurry, and there are various places in
+ * GTlsConnectionGnutls which check G_IS_TLS_CLIENT_CONNECTION(self) to switch
+ * to a client-only code path.
+ *
+ * This abstract class implements a lot of interfaces:
+ *  • Derived from GTlsConnection (itself from GIOStream), for TLS and streaming
+ *    communications.
+ *  • Implements GDtlsConnection and GDatagramBased, for DTLS and datagram
+ *    communications.
+ *  • Implements GInitable for failable GnuTLS initialisation.
+ *
+ * The GTlsClientConnectionGnutls and GTlsServerConnectionGnutls subclasses are
+ * both derived from GTlsConnectionGnutls (and hence GIOStream), and both
+ * implement the relevant TLS and DTLS interfaces:
+ *  • GTlsClientConnection
+ *  • GDtlsClientConnection
+ *  • GTlsServerConnection
+ *  • GDtlsServerConnection
+ */
+
+#include "TIZEN.h"
+
+#if ENABLE(TIZEN_PERFORMANCE_TEST_LOG)
+#include <sys/prctl.h>
+#ifndef PR_TASK_PERF_USER_TRACE
+#define PR_TASK_PERF_USER_TRACE 666
+#endif
+#define HWCLOCK_LOG(s) {const char *str=s; prctl(PR_TASK_PERF_USER_TRACE, str, strlen(str));}
+#endif
 
 static ssize_t g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t  transport_data,
-                                                 const void             *buf,
-                                                 size_t                  buflen);
+                                                  const void             *buf,
+                                                  size_t                  buflen);
+static ssize_t g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t  transport_data,
+                                                      const giovec_t         *iov,
+                                                      int                     iovcnt);
 static ssize_t g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t  transport_data,
-                                                 void                   *buf,
-                                                 size_t                  buflen);
+                                                  void                   *buf,
+                                                  size_t                  buflen);
+
+static int     g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
+                                                          unsigned int           ms);
+
 
 static void     g_tls_connection_gnutls_initable_iface_init (GInitableIface  *iface);
 static gboolean g_tls_connection_gnutls_initable_init       (GInitable       *initable,
-                                                            GCancellable    *cancellable,
-                                                            GError         **error);
-
-#ifdef HAVE_PKCS11
-static P11KitPin*    on_pin_prompt_callback  (const char     *pinfile,
-                                              P11KitUri      *pin_uri,
-                                              const char     *pin_description,
-                                              P11KitPinFlags  pin_flags,
-                                              void           *callback_data);
-#endif
+                                                             GCancellable    *cancellable,
+                                                             GError         **error);
+static void     g_tls_connection_gnutls_dtls_connection_iface_init (GDtlsConnectionInterface *iface);
+static void     g_tls_connection_gnutls_datagram_based_iface_init  (GDatagramBasedInterface  *iface);
 
 static void g_tls_connection_gnutls_init_priorities (void);
 
+static int verify_certificate_cb (gnutls_session_t session);
+
 static gboolean do_implicit_handshake (GTlsConnectionGnutls  *gnutls,
-                                      gboolean               blocking,
-                                      GCancellable          *cancellable,
-                                      GError               **error);
+                                       gint64                 timeout,
+                                       GCancellable          *cancellable,
+                                       GError               **error);
 static gboolean finish_handshake (GTlsConnectionGnutls  *gnutls,
-                                 GTask                 *thread_task,
-                                 GError               **error);
-
-G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionGnutls, g_tls_connection_gnutls, G_TYPE_TLS_CONNECTION,
-                                 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
-                                                        g_tls_connection_gnutls_initable_iface_init);
-                                 g_tls_connection_gnutls_init_priorities ();
-                                 );
-
+                                  GTask                 *task,
+                                  GError               **error);
 
 enum
 {
   PROP_0,
+  /* For this class: */
   PROP_BASE_IO_STREAM,
+  PROP_BASE_SOCKET,
+  /* For GTlsConnection and GDtlsConnection: */
   PROP_REQUIRE_CLOSE_NOTIFY,
   PROP_REHANDSHAKE_MODE,
   PROP_USE_SYSTEM_CERTDB,
@@ -86,20 +139,53 @@ enum
   PROP_CERTIFICATE,
   PROP_INTERACTION,
   PROP_PEER_CERTIFICATE,
-  PROP_PEER_CERTIFICATE_ERRORS
+  PROP_PEER_CERTIFICATE_ERRORS,
+#if GLIB_CHECK_VERSION(2, 60, 0)
+  PROP_ADVERTISED_PROTOCOLS,
+  PROP_NEGOTIATED_PROTOCOL,
+#endif
 };
 
-struct _GTlsConnectionGnutlsPrivate
+typedef struct
 {
+  /* When operating in stream mode, as a GTlsConnection. These are
+   * mutually-exclusive with base_socket. There are two different
+   * GIOStreams here: (a) base_io_stream and (b) the GTlsConnectionGnutls
+   * itself. base_io_stream is the GIOStream used to create the GTlsConnection,
+   * and corresponds to the GTlsConnection::base-io-stream property.
+   * base_istream and base_ostream are the GInputStream and GOutputStream,
+   * respectively, of base_io_stream. These are for the underlying sockets that
+   * don't know about TLS.
+   *
+   * Then the GTlsConnectionGnutls also has tls_istream and tls_ostream which
+   * wrap the aforementioned base streams with a TLS session.
+   *
+   * When operating in datagram mode, none of these are used.
+   */
   GIOStream *base_io_stream;
   GPollableInputStream *base_istream;
   GPollableOutputStream *base_ostream;
+  GInputStream *tls_istream;
+  GOutputStream *tls_ostream;
 
-  gnutls_certificate_credentials creds;
-  gnutls_session session;
+  /* When operating in datagram mode, as a GDtlsConnection, the
+   * GTlsConnectionGnutls is itself the DTLS GDatagramBased. It uses base_socket
+   * for the underlying I/O. It is mutually-exclusive with base_io_stream and
+   * the other streams.
+   */
+  GDatagramBased *base_socket;
+
+  gnutls_certificate_credentials_t creds;
+  gnutls_session_t session;
 
   GTlsCertificate *certificate, *peer_certificate;
   GTlsCertificateFlags peer_certificate_errors;
+
+  GMutex verify_certificate_mutex;
+  GCond verify_certificate_condition;
+  gboolean peer_certificate_accepted;
+  gboolean peer_certificate_examined;
+
   gboolean require_close_notify;
   GTlsRehandshakeMode rehandshake_mode;
   gboolean is_system_certdb;
@@ -107,19 +193,19 @@ struct _GTlsConnectionGnutlsPrivate
   gboolean database_is_unset;
 
   /* need_handshake means the next claim_op() will get diverted into
-   * an implicit handshake (unless it's an OP_HANDSHAKE itself).
-   * need_finish_handshake means the next claim_op() will get
-   * diverted into finish_handshake().
+   * an implicit handshake (unless it's an OP_HANDSHAKE or OP_CLOSE*).
+   * need_finish_handshake means the next claim_op() will get diverted
+   * into finish_handshake() (unless it's an OP_CLOSE*).
    *
-   * handshaking is TRUE as soon as a handshake thread is queued.
-   * Normally it becomes FALSE after finish_handshake() completes. For
-   * an implicit handshake, but in the case of an async implicit
-   * handshake, it becomes FALSE at the end of handshake_thread(),
-   * (and then the next read/write op will call finish_handshake()).
-   * This is because we don't want to call finish_handshake() (and
-   * possibly emit signals) if the caller is not actually in a TLS op
-   * at the time. (Eg, if they're waiting to try a nonblocking call
-   * again, we don't want to emit the signal until they do.)
+   * handshaking is TRUE as soon as a handshake thread is queued. For
+   * a sync handshake it becomes FALSE after finish_handshake()
+   * completes in the calling thread, but for an async implicit
+   * handshake, it becomes FALSE (and need_finish_handshake becomes
+   * TRUE) at the end of the handshaking thread (and then the next
+   * non-close op will call finish_handshake()). We can't just wait
+   * for handshake_thread_completed() to run, because it's possible
+   * that its main loop is being blocked by a synchronous op which is
+   * waiting for handshaking to become FALSE...
    *
    * started_handshake indicates that the current handshake attempt
    * got at least as far as calling gnutls_handshake() (and so any
@@ -127,148 +213,239 @@ struct _GTlsConnectionGnutlsPrivate
    * future operations). ever_handshaked indicates that TLS has
    * been successfully negotiated at some point.
    */
-  gboolean need_handshake, need_finish_handshake;
+  gboolean need_handshake, need_finish_handshake, sync_handshake_completed;
   gboolean started_handshake, handshaking, ever_handshaked;
+  GMainContext *handshake_context;
   GTask *implicit_handshake;
   GError *handshake_error;
+  GByteArray *app_data_buf;
 
-  gboolean closing, closed;
-
-  GInputStream *tls_istream;
-  GOutputStream *tls_ostream;
+  /* read_closed means the read direction has closed; write_closed similarly.
+   * If (and only if) both are set, the entire GTlsConnection is closed. */
+  gboolean read_closing, read_closed;
+  gboolean write_closing, write_closed;
 
   GTlsInteraction *interaction;
   gchar *interaction_id;
 
+#if GLIB_CHECK_VERSION(2, 60, 0)
+  gchar **advertised_protocols;
+  gchar *negotiated_protocol;
+#endif
+
   GMutex        op_mutex;
   GCancellable *waiting_for_op;
 
   gboolean      reading;
-  gboolean      read_blocking;
+  gint64        read_timeout;
   GError       *read_error;
   GCancellable *read_cancellable;
 
   gboolean      writing;
-  gboolean      write_blocking;
+  gint64        write_timeout;
   GError       *write_error;
   GCancellable *write_cancellable;
+} GTlsConnectionGnutlsPrivate;
 
-#ifndef GNUTLS_E_PREMATURE_TERMINATION
-  gboolean eof;
-#endif
-};
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionGnutls, g_tls_connection_gnutls, G_TYPE_TLS_CONNECTION,
+                                  G_ADD_PRIVATE (GTlsConnectionGnutls);
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                         g_tls_connection_gnutls_initable_iface_init);
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_DATAGRAM_BASED,
+                                                         g_tls_connection_gnutls_datagram_based_iface_init);
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_DTLS_CONNECTION,
+                                                         g_tls_connection_gnutls_dtls_connection_iface_init);
+                                  g_tls_connection_gnutls_init_priorities ();
+                                  );
 
 static gint unique_interaction_id = 0;
 
 static void
 g_tls_connection_gnutls_init (GTlsConnectionGnutls *gnutls)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   gint unique_id;
 
-  gnutls->priv = G_TYPE_INSTANCE_GET_PRIVATE (gnutls, G_TYPE_TLS_CONNECTION_GNUTLS, GTlsConnectionGnutlsPrivate);
+  gnutls_certificate_allocate_credentials (&priv->creds);
 
-  gnutls_certificate_allocate_credentials (&gnutls->priv->creds);
-  gnutls_certificate_set_verify_flags (gnutls->priv->creds,
-                                      GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
+  g_mutex_init (&priv->verify_certificate_mutex);
+  g_cond_init (&priv->verify_certificate_condition);
 
-  gnutls->priv->need_handshake = TRUE;
+  priv->need_handshake = TRUE;
 
-  gnutls->priv->database_is_unset = TRUE;
-  gnutls->priv->is_system_certdb = TRUE;
+  priv->database_is_unset = TRUE;
+  priv->is_system_certdb = TRUE;
 
   unique_id = g_atomic_int_add (&unique_interaction_id, 1);
-  gnutls->priv->interaction_id = g_strdup_printf ("gtls:%d", unique_id);
+  priv->interaction_id = g_strdup_printf ("gtls:%d", unique_id);
 
-#ifdef HAVE_PKCS11
-  p11_kit_pin_register_callback (gnutls->priv->interaction_id,
-                                 on_pin_prompt_callback, gnutls, NULL);
-#endif
-
-  gnutls->priv->waiting_for_op = g_cancellable_new ();
-  g_cancellable_cancel (gnutls->priv->waiting_for_op);
+  priv->waiting_for_op = g_cancellable_new ();
+  g_cancellable_cancel (priv->waiting_for_op);
+  g_mutex_init (&priv->op_mutex);
 }
 
-/* First field is "ssl3 only", second is "allow unsafe rehandshaking" */
+/* First field is "fallback", second is "allow unsafe rehandshaking" */
 static gnutls_priority_t priorities[2][2];
 
+#if ENABLE(TIZEN_TV_UPDATE_DEFAULT_PRIORITY)
+#define DEFAULT_BASE_PRIORITY "NORMAL:%COMPAT:!VERS-SSL3.0:%LATEST_RECORD_VERSION"
+#else
+/* TODO: Get rid of this in favor of gnutls_set_default_priority_append()
+ * when upgrading to GnuTLS 3.6.3.
+ */
+#define DEFAULT_BASE_PRIORITY "NORMAL:%COMPAT"
+#endif
+
 static void
 g_tls_connection_gnutls_init_priorities (void)
 {
   const gchar *base_priority;
-  gchar *ssl3_priority, *unsafe_rehandshake_priority, *ssl3_unsafe_rehandshake_priority;
+  gchar *fallback_priority, *unsafe_rehandshake_priority, *fallback_unsafe_rehandshake_priority;
+  const guint *protos;
+  int ret, i, nprotos, fallback_proto;
 
   base_priority = g_getenv ("G_TLS_GNUTLS_PRIORITY");
   if (!base_priority)
-    base_priority = "NORMAL:%COMPAT";
+    base_priority = DEFAULT_BASE_PRIORITY;
+  ret = gnutls_priority_init (&priorities[FALSE][FALSE], base_priority, NULL);
+  if (ret == GNUTLS_E_INVALID_REQUEST)
+    {
+      g_warning ("G_TLS_GNUTLS_PRIORITY is invalid; ignoring!");
+      base_priority = DEFAULT_BASE_PRIORITY;
+      ret = gnutls_priority_init (&priorities[FALSE][FALSE], base_priority, NULL);
+      g_warn_if_fail (ret == 0);
+    }
 
-  ssl3_priority = g_strdup_printf ("%s:!VERS-TLS1.2:!VERS-TLS1.1:!VERS-TLS1.0", base_priority);
   unsafe_rehandshake_priority = g_strdup_printf ("%s:%%UNSAFE_RENEGOTIATION", base_priority);
-  ssl3_unsafe_rehandshake_priority = g_strdup_printf ("%s:!VERS-TLS1.2:!VERS-TLS1.1:!VERS-TLS1.0:%%UNSAFE_RENEGOTIATION", base_priority);
-
-  gnutls_priority_init (&priorities[FALSE][FALSE], base_priority, NULL);
-  gnutls_priority_init (&priorities[TRUE][FALSE], ssl3_priority, NULL);
-  gnutls_priority_init (&priorities[FALSE][TRUE], unsafe_rehandshake_priority, NULL);
-  gnutls_priority_init (&priorities[TRUE][TRUE], ssl3_unsafe_rehandshake_priority, NULL);
-
-  g_free (ssl3_priority);
+  ret = gnutls_priority_init (&priorities[FALSE][TRUE], unsafe_rehandshake_priority, NULL);
+  g_warn_if_fail (ret == 0);
   g_free (unsafe_rehandshake_priority);
-  g_free (ssl3_unsafe_rehandshake_priority);
+
+  /* Figure out the lowest SSl/TLS version supported by base_priority */
+  nprotos = gnutls_priority_protocol_list (priorities[FALSE][FALSE], &protos);
+  fallback_proto = G_MAXUINT;
+  for (i = 0; i < nprotos; i++)
+    {
+      if (protos[i] < fallback_proto)
+        fallback_proto = protos[i];
+    }
+  if (fallback_proto == G_MAXUINT)
+    {
+      g_warning ("All GNUTLS protocol versions disabled?");
+      fallback_priority = g_strdup (base_priority);
+    }
+  else
+    {
+      /* %COMPAT is intentionally duplicated here, to ensure it gets added for
+       * the fallback even if the default priority has been changed. */
+      fallback_priority = g_strdup_printf ("%s:%%COMPAT:!VERS-TLS-ALL:+VERS-%s:%%FALLBACK_SCSV",
+                                           DEFAULT_BASE_PRIORITY,
+                                           gnutls_protocol_get_name (fallback_proto));
+    }
+  fallback_unsafe_rehandshake_priority = g_strdup_printf ("%s:%%UNSAFE_RENEGOTIATION",
+                                                          fallback_priority);
+
+  ret = gnutls_priority_init (&priorities[TRUE][FALSE], fallback_priority, NULL);
+  g_warn_if_fail (ret == 0);
+  ret = gnutls_priority_init (&priorities[TRUE][TRUE], fallback_unsafe_rehandshake_priority, NULL);
+  g_warn_if_fail (ret == 0);
+  g_free (fallback_priority);
+  g_free (fallback_unsafe_rehandshake_priority);
 }
 
 static void
 g_tls_connection_gnutls_set_handshake_priority (GTlsConnectionGnutls *gnutls)
 {
-  gboolean use_ssl3, unsafe_rehandshake;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  gboolean fallback, unsafe_rehandshake;
 
   if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
-    use_ssl3 = g_tls_client_connection_get_use_ssl3 (G_TLS_CLIENT_CONNECTION (gnutls));
+    {
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+      fallback = g_tls_client_connection_get_use_ssl3 (G_TLS_CLIENT_CONNECTION (gnutls));
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+    }
   else
-    use_ssl3 = FALSE;
-  unsafe_rehandshake = (gnutls->priv->rehandshake_mode == G_TLS_REHANDSHAKE_UNSAFELY);
-  gnutls_priority_set (gnutls->priv->session,
-                      priorities[use_ssl3][unsafe_rehandshake]);
+    fallback = FALSE;
+  unsafe_rehandshake = (priv->rehandshake_mode == G_TLS_REHANDSHAKE_UNSAFELY);
+  gnutls_priority_set (priv->session,
+                       priorities[fallback][unsafe_rehandshake]);
+}
+
+static gboolean
+g_tls_connection_gnutls_is_dtls (GTlsConnectionGnutls *gnutls)
+{
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  return (priv->base_socket != NULL);
 }
 
 static gboolean
 g_tls_connection_gnutls_initable_init (GInitable     *initable,
-                                      GCancellable  *cancellable,
-                                      GError       **error)
+                                       GCancellable  *cancellable,
+                                       GError       **error)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (initable);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  gboolean client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
+  guint flags = client ? GNUTLS_CLIENT : GNUTLS_SERVER;
   int status;
 
-  g_return_val_if_fail (gnutls->priv->base_istream != NULL &&
-                       gnutls->priv->base_ostream != NULL, FALSE);
+  g_return_val_if_fail ((priv->base_istream == NULL) ==
+                        (priv->base_ostream == NULL), FALSE);
+  g_return_val_if_fail ((priv->base_socket == NULL) !=
+                        (priv->base_istream == NULL), FALSE);
 
-  /* Make sure gnutls->priv->session has been initialized (it may have
-   * already been initialized by a construct-time property setter).
-   */
-  g_tls_connection_gnutls_get_session (gnutls);
+  /* Check whether to use DTLS or TLS. */
+  if (g_tls_connection_gnutls_is_dtls (gnutls))
+    flags |= GNUTLS_DATAGRAM;
 
-  status = gnutls_credentials_set (gnutls->priv->session,
-                                  GNUTLS_CRD_CERTIFICATE,
-                                  gnutls->priv->creds);
+  gnutls_init (&priv->session, flags);
+
+  gnutls_session_set_ptr (priv->session, gnutls);
+  gnutls_session_set_verify_function (priv->session, verify_certificate_cb);
+
+  status = gnutls_credentials_set (priv->session,
+                                   GNUTLS_CRD_CERTIFICATE,
+                                   priv->creds);
   if (status != 0)
     {
       g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
-                  _("Could not create TLS connection: %s"),
-                  gnutls_strerror (status));
+                   _("Could not create TLS connection: %s"),
+                   gnutls_strerror (status));
       return FALSE;
     }
 
-  /* Some servers (especially on embedded devices) use tiny keys that
-   * gnutls will reject by default. We want it to accept them.
-   */
-  gnutls_dh_set_prime_bits (gnutls->priv->session, 256);
+  gnutls_transport_set_push_function (priv->session,
+                                      g_tls_connection_gnutls_push_func);
+  gnutls_transport_set_pull_function (priv->session,
+                                      g_tls_connection_gnutls_pull_func);
+  gnutls_transport_set_pull_timeout_function (priv->session,
+                                              g_tls_connection_gnutls_pull_timeout_func);
+  gnutls_transport_set_ptr (priv->session, gnutls);
+
+  /* GDatagramBased supports vectored I/O; GPollableOutputStream does not. */
+  if (priv->base_socket != NULL)
+    {
+      gnutls_transport_set_vec_push_function (priv->session,
+                                              g_tls_connection_gnutls_vec_push_func);
+    }
 
-  gnutls_transport_set_push_function (gnutls->priv->session,
-                                     g_tls_connection_gnutls_push_func);
-  gnutls_transport_set_pull_function (gnutls->priv->session,
-                                     g_tls_connection_gnutls_pull_func);
-  gnutls_transport_set_ptr (gnutls->priv->session, gnutls);
+  /* Set reasonable MTU */
+  if (flags & GNUTLS_DATAGRAM)
+    gnutls_dtls_set_mtu (priv->session, 1400);
 
-  gnutls->priv->tls_istream = g_tls_input_stream_gnutls_new (gnutls);
-  gnutls->priv->tls_ostream = g_tls_output_stream_gnutls_new (gnutls);
+  /* Create output streams if operating in streaming mode. */
+  if (!(flags & GNUTLS_DATAGRAM))
+    {
+      priv->tls_istream = g_tls_input_stream_gnutls_new (gnutls);
+      priv->tls_ostream = g_tls_output_stream_gnutls_new (gnutls);
+    }
 
   return TRUE;
 }
@@ -277,89 +454,124 @@ static void
 g_tls_connection_gnutls_finalize (GObject *object)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
 
-  g_clear_object (&gnutls->priv->base_io_stream);
+  g_clear_object (&priv->base_io_stream);
+  g_clear_object (&priv->base_socket);
 
-  g_clear_object (&gnutls->priv->tls_istream);
-  g_clear_object (&gnutls->priv->tls_ostream);
+  g_clear_object (&priv->tls_istream);
+  g_clear_object (&priv->tls_ostream);
 
-  if (gnutls->priv->session)
-    gnutls_deinit (gnutls->priv->session);
-  if (gnutls->priv->creds)
-    gnutls_certificate_free_credentials (gnutls->priv->creds);
+  if (priv->session)
+    gnutls_deinit (priv->session);
+  if (priv->creds)
+    gnutls_certificate_free_credentials (priv->creds);
 
-  g_clear_object (&gnutls->priv->database);
-  g_clear_object (&gnutls->priv->certificate);
-  g_clear_object (&gnutls->priv->peer_certificate);
+  g_clear_object (&priv->database);
+  g_clear_object (&priv->certificate);
+  g_clear_object (&priv->peer_certificate);
 
-#ifdef HAVE_PKCS11
-  p11_kit_pin_unregister_callback (gnutls->priv->interaction_id,
-                                   on_pin_prompt_callback, gnutls);
+  g_mutex_clear (&priv->verify_certificate_mutex);
+  g_cond_clear (&priv->verify_certificate_condition);
+
+
+  g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
+
+  g_free (priv->interaction_id);
+  g_clear_object (&priv->interaction);
+
+#if GLIB_CHECK_VERSION(2, 60, 0)
+  g_clear_pointer (&priv->advertised_protocols, g_strfreev);
+  g_clear_pointer (&priv->negotiated_protocol, g_free);
 #endif
-  g_free (gnutls->priv->interaction_id);
-  g_clear_object (&gnutls->priv->interaction);
 
-  g_clear_error (&gnutls->priv->handshake_error);
-  g_clear_error (&gnutls->priv->read_error);
-  g_clear_error (&gnutls->priv->write_error);
+  g_clear_error (&priv->handshake_error);
+  g_clear_error (&priv->read_error);
+  g_clear_error (&priv->write_error);
 
-  g_clear_object (&gnutls->priv->waiting_for_op);
+  g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+
+  /* This must always be NULL here, as it holds a reference to @gnutls as
+   * its source object. However, we clear it anyway just in case this changes
+   * in future. */
+  g_clear_object (&priv->implicit_handshake);
+
+  g_clear_object (&priv->read_cancellable);
+  g_clear_object (&priv->write_cancellable);
+
+  g_clear_object (&priv->waiting_for_op);
+  g_mutex_clear (&priv->op_mutex);
 
   G_OBJECT_CLASS (g_tls_connection_gnutls_parent_class)->finalize (object);
 }
 
 static void
 g_tls_connection_gnutls_get_property (GObject    *object,
-                                     guint       prop_id,
-                                     GValue     *value,
-                                     GParamSpec *pspec)
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   GTlsBackend *backend;
 
   switch (prop_id)
     {
     case PROP_BASE_IO_STREAM:
-      g_value_set_object (value, gnutls->priv->base_io_stream);
+      g_value_set_object (value, priv->base_io_stream);
+      break;
+
+    case PROP_BASE_SOCKET:
+      g_value_set_object (value, priv->base_socket);
       break;
 
     case PROP_REQUIRE_CLOSE_NOTIFY:
-      g_value_set_boolean (value, gnutls->priv->require_close_notify);
+      g_value_set_boolean (value, priv->require_close_notify);
       break;
 
     case PROP_REHANDSHAKE_MODE:
-      g_value_set_enum (value, gnutls->priv->rehandshake_mode);
+      g_value_set_enum (value, priv->rehandshake_mode);
       break;
 
     case PROP_USE_SYSTEM_CERTDB:
-      g_value_set_boolean (value, gnutls->priv->is_system_certdb);
+      g_value_set_boolean (value, priv->is_system_certdb);
       break;
 
     case PROP_DATABASE:
-      if (gnutls->priv->database_is_unset)
+      if (priv->database_is_unset)
         {
           backend = g_tls_backend_get_default ();
-          gnutls->priv->database =  g_tls_backend_get_default_database (backend);
-          gnutls->priv->database_is_unset = FALSE;
+          priv->database =  g_tls_backend_get_default_database (backend);
+          priv->database_is_unset = FALSE;
         }
-      g_value_set_object (value, gnutls->priv->database);
+      g_value_set_object (value, priv->database);
       break;
 
     case PROP_CERTIFICATE:
-      g_value_set_object (value, gnutls->priv->certificate);
+      g_value_set_object (value, priv->certificate);
       break;
 
     case PROP_INTERACTION:
-      g_value_set_object (value, gnutls->priv->interaction);
+      g_value_set_object (value, priv->interaction);
       break;
 
     case PROP_PEER_CERTIFICATE:
-      g_value_set_object (value, gnutls->priv->peer_certificate);
+      g_value_set_object (value, priv->peer_certificate);
       break;
 
     case PROP_PEER_CERTIFICATE_ERRORS:
-      g_value_set_flags (value, gnutls->priv->peer_certificate_errors);
+      g_value_set_flags (value, priv->peer_certificate_errors);
+      break;
+
+#if GLIB_CHECK_VERSION(2, 60, 0)
+    case PROP_ADVERTISED_PROTOCOLS:
+      g_value_set_boxed (value, priv->advertised_protocols);
+      break;
+
+    case PROP_NEGOTIATED_PROTOCOL:
+      g_value_set_string (value, priv->negotiated_protocol);
       break;
+#endif
 
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -368,11 +580,12 @@ g_tls_connection_gnutls_get_property (GObject    *object,
 
 static void
 g_tls_connection_gnutls_set_property (GObject      *object,
-                                     guint         prop_id,
-                                     const GValue *value,
-                                     GParamSpec   *pspec)
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   GInputStream *istream;
   GOutputStream *ostream;
   gboolean system_certdb;
@@ -381,378 +594,488 @@ g_tls_connection_gnutls_set_property (GObject      *object,
   switch (prop_id)
     {
     case PROP_BASE_IO_STREAM:
-      if (gnutls->priv->base_io_stream)
-       {
-         g_object_unref (gnutls->priv->base_io_stream);
-         gnutls->priv->base_istream = NULL;
-         gnutls->priv->base_ostream = NULL;
-       }
-      gnutls->priv->base_io_stream = g_value_dup_object (value);
-      if (!gnutls->priv->base_io_stream)
-       return;
-
-      istream = g_io_stream_get_input_stream (gnutls->priv->base_io_stream);
-      ostream = g_io_stream_get_output_stream (gnutls->priv->base_io_stream);
+      g_assert (g_value_get_object (value) == NULL ||
+                priv->base_socket == NULL);
+
+      if (priv->base_io_stream)
+        {
+          g_object_unref (priv->base_io_stream);
+          priv->base_istream = NULL;
+          priv->base_ostream = NULL;
+        }
+      priv->base_io_stream = g_value_dup_object (value);
+      if (!priv->base_io_stream)
+        return;
+
+      istream = g_io_stream_get_input_stream (priv->base_io_stream);
+      ostream = g_io_stream_get_output_stream (priv->base_io_stream);
 
       if (G_IS_POLLABLE_INPUT_STREAM (istream) &&
-         g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (istream)))
-       gnutls->priv->base_istream = G_POLLABLE_INPUT_STREAM (istream);
+          g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (istream)))
+        priv->base_istream = G_POLLABLE_INPUT_STREAM (istream);
       if (G_IS_POLLABLE_OUTPUT_STREAM (ostream) &&
-         g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (ostream)))
-       gnutls->priv->base_ostream = G_POLLABLE_OUTPUT_STREAM (ostream);
+          g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (ostream)))
+        priv->base_ostream = G_POLLABLE_OUTPUT_STREAM (ostream);
+      break;
+
+    case PROP_BASE_SOCKET:
+      g_assert (g_value_get_object (value) == NULL ||
+                priv->base_io_stream == NULL);
+
+      g_clear_object (&priv->base_socket);
+      priv->base_socket = g_value_dup_object (value);
       break;
 
     case PROP_REQUIRE_CLOSE_NOTIFY:
-      gnutls->priv->require_close_notify = g_value_get_boolean (value);
+      priv->require_close_notify = g_value_get_boolean (value);
       break;
 
     case PROP_REHANDSHAKE_MODE:
-      gnutls->priv->rehandshake_mode = g_value_get_enum (value);
+      priv->rehandshake_mode = g_value_get_enum (value);
       break;
 
     case PROP_USE_SYSTEM_CERTDB:
       system_certdb = g_value_get_boolean (value);
-      if (system_certdb != gnutls->priv->is_system_certdb)
+      if (system_certdb != priv->is_system_certdb)
         {
-          g_clear_object (&gnutls->priv->database);
+          g_clear_object (&priv->database);
           if (system_certdb)
             {
               backend = g_tls_backend_get_default ();
-              gnutls->priv->database = g_tls_backend_get_default_database (backend);
+              priv->database = g_tls_backend_get_default_database (backend);
             }
-          gnutls->priv->is_system_certdb = system_certdb;
-          gnutls->priv->database_is_unset = FALSE;
+          priv->is_system_certdb = system_certdb;
+          priv->database_is_unset = FALSE;
         }
       break;
 
     case PROP_DATABASE:
-      g_clear_object (&gnutls->priv->database);
-      gnutls->priv->database = g_value_dup_object (value);
-      gnutls->priv->is_system_certdb = FALSE;
-      gnutls->priv->database_is_unset = FALSE;
+      g_clear_object (&priv->database);
+      priv->database = g_value_dup_object (value);
+      priv->is_system_certdb = FALSE;
+      priv->database_is_unset = FALSE;
       break;
 
     case PROP_CERTIFICATE:
-      if (gnutls->priv->certificate)
-       g_object_unref (gnutls->priv->certificate);
-      gnutls->priv->certificate = g_value_dup_object (value);
+      if (priv->certificate)
+        g_object_unref (priv->certificate);
+      priv->certificate = g_value_dup_object (value);
       break;
 
     case PROP_INTERACTION:
-      g_clear_object (&gnutls->priv->interaction);
-      gnutls->priv->interaction = g_value_dup_object (value);
+      g_clear_object (&priv->interaction);
+      priv->interaction = g_value_dup_object (value);
+      break;
+
+#if GLIB_CHECK_VERSION(2, 60, 0)
+    case PROP_ADVERTISED_PROTOCOLS:
+      g_clear_pointer (&priv->advertised_protocols, g_strfreev);
+      priv->advertised_protocols = g_value_dup_boxed (value);
       break;
+#endif
 
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
 }
 
-gnutls_certificate_credentials
+gnutls_certificate_credentials_t
 g_tls_connection_gnutls_get_credentials (GTlsConnectionGnutls *gnutls)
 {
-  return gnutls->priv->creds;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  return priv->creds;
 }
 
-gnutls_session
+gnutls_session_t
 g_tls_connection_gnutls_get_session (GTlsConnectionGnutls *gnutls)
 {
-  /* Ideally we would initialize gnutls->priv->session from
-   * g_tls_connection_gnutls_init(), but we can't tell if it's a
-   * client or server connection at that point... And
-   * g_tls_connection_gnutls_initiable_init() is too late, because
-   * construct-time property setters may need to modify it.
-   */
-  if (!gnutls->priv->session)
-    {
-      gboolean client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
-      gnutls_init (&gnutls->priv->session, client ? GNUTLS_CLIENT : GNUTLS_SERVER);
-    }
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
 
-  return gnutls->priv->session;
+  return priv->session;
 }
 
 void
-g_tls_connection_gnutls_get_certificate (GTlsConnectionGnutls *gnutls,
-                                         gnutls_retr2_st      *st)
+g_tls_connection_gnutls_get_certificate (GTlsConnectionGnutls  *gnutls,
+                                         gnutls_pcert_st      **pcert,
+                                         unsigned int          *pcert_length,
+                                         gnutls_privkey_t      *pkey)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   GTlsCertificate *cert;
 
   cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (gnutls));
 
-  st->cert_type = GNUTLS_CRT_X509;
-  st->ncerts = 0;
-
   if (cert)
+    {
       g_tls_certificate_gnutls_copy (G_TLS_CERTIFICATE_GNUTLS (cert),
-                                     gnutls->priv->interaction_id, st);
+                                     priv->interaction_id,
+                                     pcert, pcert_length, pkey);
+    }
+  else
+    {
+      *pcert = NULL;
+      *pcert_length = 0;
+      *pkey = NULL;
+    }
 }
 
 typedef enum {
   G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE,
   G_TLS_CONNECTION_GNUTLS_OP_READ,
   G_TLS_CONNECTION_GNUTLS_OP_WRITE,
-  G_TLS_CONNECTION_GNUTLS_OP_CLOSE,
+  G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ,
+  G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE,
+  G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH,
 } GTlsConnectionGnutlsOp;
 
 static gboolean
 claim_op (GTlsConnectionGnutls    *gnutls,
-         GTlsConnectionGnutlsOp   op,
-         gboolean                 blocking,
-         GCancellable            *cancellable,
-         GError                 **error)
+          GTlsConnectionGnutlsOp   op,
+          gint64                   timeout,
+          GCancellable            *cancellable,
+          GError                 **error)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
  try_again:
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return FALSE;
 
-  g_mutex_lock (&gnutls->priv->op_mutex);
+  g_mutex_lock (&priv->op_mutex);
 
-  if (gnutls->priv->closing || gnutls->priv->closed)
+  if (((op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE ||
+        op == G_TLS_CONNECTION_GNUTLS_OP_READ) &&
+       (priv->read_closing || priv->read_closed)) ||
+      ((op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE ||
+        op == G_TLS_CONNECTION_GNUTLS_OP_WRITE) &&
+       (priv->write_closing || priv->write_closed)))
     {
       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
-                          _("Connection is closed"));
-      g_mutex_unlock (&gnutls->priv->op_mutex);
+                           _("Connection is closed"));
+      g_mutex_unlock (&priv->op_mutex);
       return FALSE;
     }
 
-  if (gnutls->priv->handshake_error && op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE)
+  if (priv->handshake_error &&
+      op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH &&
+      op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ &&
+      op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE)
     {
       if (error)
-       *error = g_error_copy (gnutls->priv->handshake_error);
-      g_mutex_unlock (&gnutls->priv->op_mutex);
+        *error = g_error_copy (priv->handshake_error);
+      g_mutex_unlock (&priv->op_mutex);
       return FALSE;
     }
 
-  if (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE &&
-      op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE)
+  if (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE)
     {
-      if (gnutls->priv->need_handshake)
-       {
-         gnutls->priv->need_handshake = FALSE;
-         gnutls->priv->handshaking = TRUE;
-         if (!do_implicit_handshake (gnutls, blocking, cancellable, error))
-           {
-             g_mutex_unlock (&gnutls->priv->op_mutex);
-             return FALSE;
-           }
-       }
+      if (op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH &&
+          op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ &&
+          op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE &&
+          priv->need_handshake)
+        {
+          priv->need_handshake = FALSE;
+          priv->handshaking = TRUE;
+          if (!do_implicit_handshake (gnutls, timeout, cancellable, error))
+            {
+              g_mutex_unlock (&priv->op_mutex);
+              return FALSE;
+            }
+        }
+
+      if (priv->need_finish_handshake &&
+          priv->implicit_handshake)
+        {
+          GError *my_error = NULL;
+          gboolean success;
+
+          priv->need_finish_handshake = FALSE;
 
-      if (gnutls->priv->need_finish_handshake)
-       {
-         GError *my_error = NULL;
-         gboolean success;
+          g_mutex_unlock (&priv->op_mutex);
+          success = finish_handshake (gnutls, priv->implicit_handshake, &my_error);
+          g_clear_object (&priv->implicit_handshake);
+          g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+          g_mutex_lock (&priv->op_mutex);
 
-         gnutls->priv->need_finish_handshake = FALSE;
+          if (op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH &&
+              op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ &&
+              op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE &&
+              (!success || g_cancellable_set_error_if_cancelled (cancellable, &my_error)))
+            {
+              g_propagate_error (error, my_error);
+              g_mutex_unlock (&priv->op_mutex);
+              return FALSE;
+            }
 
-         g_mutex_unlock (&gnutls->priv->op_mutex);
-         success = finish_handshake (gnutls, gnutls->priv->implicit_handshake, &my_error);
-         g_clear_object (&gnutls->priv->implicit_handshake);
-         g_mutex_lock (&gnutls->priv->op_mutex);
+          g_clear_error (&my_error);
+        }
+    }
 
-         gnutls->priv->handshaking = FALSE;
-         if (!success || g_cancellable_set_error_if_cancelled (cancellable, &my_error))
-           {
-             g_propagate_error (error, my_error);
-             g_mutex_unlock (&gnutls->priv->op_mutex);
-             return FALSE;
-           }
-       }
+  if (priv->handshaking &&
+      timeout != 0 &&
+      g_main_context_is_owner (priv->handshake_context))
+    {
+      /* Cannot perform a blocking operation during a handshake on the
+       * same thread that triggered the handshake. The only way this can
+       * occur is if the application is doing something weird in its
+       * accept-certificate callback. Allowing a blocking op would stall
+       * the handshake (forever, if there's no timeout). Even a close
+       * op would deadlock here.
+       */
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot perform blocking operation during TLS handshake"));
+      g_mutex_unlock (&priv->op_mutex);
+      return FALSE;
     }
 
-  if ((op != G_TLS_CONNECTION_GNUTLS_OP_WRITE && gnutls->priv->reading) ||
-      (op != G_TLS_CONNECTION_GNUTLS_OP_READ && gnutls->priv->writing) ||
-      (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE && gnutls->priv->handshaking))
+  if ((op != G_TLS_CONNECTION_GNUTLS_OP_WRITE && priv->reading) ||
+      (op != G_TLS_CONNECTION_GNUTLS_OP_READ && priv->writing) ||
+      (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE && priv->handshaking))
     {
       GPollFD fds[2];
       int nfds;
+      gint64 start_time;
+      gint result = 1;  /* if the loop is never entered, it’s as if we cancelled early */
 
-      g_cancellable_reset (gnutls->priv->waiting_for_op);
+      g_cancellable_reset (priv->waiting_for_op);
 
-      g_mutex_unlock (&gnutls->priv->op_mutex);
+      g_mutex_unlock (&priv->op_mutex);
 
-      if (!blocking)
-       {
-         g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
-                              _("Operation would block"));
-         return FALSE;
-       }
+      if (timeout == 0)
+        {
+          /* Intentionally not translated because this is not a fatal error to be
+           * presented to the user, and to avoid this showing up in profiling. */
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
+          return FALSE;
+        }
 
-      g_cancellable_make_pollfd (gnutls->priv->waiting_for_op, &fds[0]);
-      if (g_cancellable_make_pollfd (cancellable, &fds[0]))
-       nfds = 2;
+      g_cancellable_make_pollfd (priv->waiting_for_op, &fds[0]);
+      if (g_cancellable_make_pollfd (cancellable, &fds[1]))
+        nfds = 2;
       else
-       nfds = 1;
-      g_poll (fds, nfds, -1);
-      g_cancellable_release_fd (cancellable);
+        nfds = 1;
+
+      /* Convert from microseconds to milliseconds. */
+      if (timeout != -1)
+        timeout = timeout / 1000;
+
+      /* Poll until cancellation or the timeout is reached. */
+      start_time = g_get_monotonic_time ();
+
+      while (!g_cancellable_is_cancelled (priv->waiting_for_op) &&
+             !g_cancellable_is_cancelled (cancellable))
+        {
+          result = g_poll (fds, nfds, timeout);
+
+          if (result == 0)
+            break;
+          if (result != -1 || errno != EINTR)
+            continue;
+
+          if (timeout != -1)
+            {
+              timeout -= (g_get_monotonic_time () - start_time) / 1000;
+              if (timeout < 0)
+                timeout = 0;
+            }
+        }
+
+      if (nfds > 1)
+        g_cancellable_release_fd (cancellable);
+
+      if (result == 0)
+        {
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                               _("Socket I/O timed out"));
+          return FALSE;
+        }
 
       goto try_again;
     }
 
   if (op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE)
     {
-      gnutls->priv->handshaking = TRUE;
-      gnutls->priv->need_handshake = FALSE;
+      priv->handshaking = TRUE;
+      priv->need_handshake = FALSE;
     }
-  if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE)
-    gnutls->priv->closing = TRUE;
+  if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH ||
+      op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ)
+    priv->read_closing = TRUE;
+  if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH ||
+      op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE)
+    priv->write_closing = TRUE;
 
   if (op != G_TLS_CONNECTION_GNUTLS_OP_WRITE)
-    gnutls->priv->reading = TRUE;
+    priv->reading = TRUE;
   if (op != G_TLS_CONNECTION_GNUTLS_OP_READ)
-    gnutls->priv->writing = TRUE;
+    priv->writing = TRUE;
 
-  g_mutex_unlock (&gnutls->priv->op_mutex);
+  g_mutex_unlock (&priv->op_mutex);
   return TRUE;
 }
 
 static void
 yield_op (GTlsConnectionGnutls   *gnutls,
-         GTlsConnectionGnutlsOp  op)
+          GTlsConnectionGnutlsOp  op)
 {
-  g_mutex_lock (&gnutls->priv->op_mutex);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  g_mutex_lock (&priv->op_mutex);
 
   if (op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE)
-    gnutls->priv->handshaking = FALSE;
-  if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE)
-    gnutls->priv->closing = FALSE;
+    priv->handshaking = FALSE;
+  if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH ||
+      op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ)
+    priv->read_closing = FALSE;
+  if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH ||
+      op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE)
+    priv->write_closing = FALSE;
 
   if (op != G_TLS_CONNECTION_GNUTLS_OP_WRITE)
-    gnutls->priv->reading = FALSE;
+    priv->reading = FALSE;
   if (op != G_TLS_CONNECTION_GNUTLS_OP_READ)
-    gnutls->priv->writing = FALSE;
+    priv->writing = FALSE;
 
-  g_cancellable_cancel (gnutls->priv->waiting_for_op);
-  g_mutex_unlock (&gnutls->priv->op_mutex);
+  g_cancellable_cancel (priv->waiting_for_op);
+  g_mutex_unlock (&priv->op_mutex);
 }
 
 static void
 begin_gnutls_io (GTlsConnectionGnutls  *gnutls,
-                GIOCondition           direction,
-                gboolean               blocking,
-                GCancellable          *cancellable)
+                 GIOCondition           direction,
+                 gint64                 timeout,
+                 GCancellable          *cancellable)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
   g_assert (direction & (G_IO_IN | G_IO_OUT));
 
   if (direction & G_IO_IN)
     {
-      gnutls->priv->read_blocking = blocking;
-      gnutls->priv->read_cancellable = cancellable;
-      g_clear_error (&gnutls->priv->read_error);
+      priv->read_timeout = timeout;
+      priv->read_cancellable = cancellable;
+      g_clear_error (&priv->read_error);
     }
 
   if (direction & G_IO_OUT)
     {
-      gnutls->priv->write_blocking = blocking;
-      gnutls->priv->write_cancellable = cancellable;
-      g_clear_error (&gnutls->priv->write_error);
+      priv->write_timeout = timeout;
+      priv->write_cancellable = cancellable;
+      g_clear_error (&priv->write_error);
     }
 }
 
 static int
 end_gnutls_io (GTlsConnectionGnutls  *gnutls,
-              GIOCondition           direction,
-              int                    status,
-              const char            *errmsg,
-              GError               **error)
+               GIOCondition           direction,
+               int                    status,
+               GError               **error,
+               const char            *err_prefix);
+
+static int
+end_gnutls_io (GTlsConnectionGnutls  *gnutls,
+               GIOCondition           direction,
+               int                    status,
+               GError               **error,
+               const char            *err_prefix)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   GError *my_error = NULL;
 
   g_assert (direction & (G_IO_IN | G_IO_OUT));
   g_assert (!error || !*error);
 
+  /* We intentionally do not check for GNUTLS_E_INTERRUPTED here
+   * Instead, the caller may poll for the source to become ready again.
+   * (Note that GTlsOutputStreamGnutls and GTlsInputStreamGnutls inherit
+   * from GPollableOutputStream and GPollableInputStream, respectively.)
+   * See also the comment in set_gnutls_error().
+   */
   if (status == GNUTLS_E_AGAIN ||
       status == GNUTLS_E_WARNING_ALERT_RECEIVED)
     return GNUTLS_E_AGAIN;
 
   if (direction & G_IO_IN)
     {
-      gnutls->priv->read_cancellable = NULL;
+      priv->read_cancellable = NULL;
       if (status < 0)
-       {
-         my_error = gnutls->priv->read_error;
-         gnutls->priv->read_error = NULL;
-       }
+        {
+          my_error = priv->read_error;
+          priv->read_error = NULL;
+        }
       else
-       g_clear_error (&gnutls->priv->read_error);
+        g_clear_error (&priv->read_error);
     }
   if (direction & G_IO_OUT)
     {
-      gnutls->priv->write_cancellable = NULL;
+      priv->write_cancellable = NULL;
       if (status < 0 && !my_error)
-       {
-         my_error = gnutls->priv->write_error;
-         gnutls->priv->write_error = NULL;
-       }
+        {
+          my_error = priv->write_error;
+          priv->write_error = NULL;
+        }
       else
-       g_clear_error (&gnutls->priv->write_error);
+        g_clear_error (&priv->write_error);
     }
 
   if (status >= 0)
     return status;
 
-  if (gnutls->priv->handshaking && !gnutls->priv->ever_handshaked)
+  if (priv->handshaking && !priv->ever_handshaked)
     {
       if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_FAILED) ||
-#if GLIB_CHECK_VERSION (2, 35, 3)
-         g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE) ||
-#endif
-         status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
-         status == GNUTLS_E_FATAL_ALERT_RECEIVED ||
-         status == GNUTLS_E_DECRYPTION_FAILED ||
-         status == GNUTLS_E_UNSUPPORTED_VERSION_PACKET)
-       {
-         g_clear_error (&my_error);
-         g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
-                              _("Peer failed to perform TLS handshake"));
-         return GNUTLS_E_PULL_ERROR;
-       }
+          g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE) ||
+          status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
+          status == GNUTLS_E_DECRYPTION_FAILED ||
+          status == GNUTLS_E_UNSUPPORTED_VERSION_PACKET)
+        {
+          g_clear_error (&my_error);
+          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+                               _("Peer failed to perform TLS handshake"));
+          return GNUTLS_E_PULL_ERROR;
+        }
     }
 
   if (my_error)
     {
-      if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
-       G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls);
+      if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) &&
+          !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+        G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls);
       g_propagate_error (error, my_error);
       return status;
     }
   else if (status == GNUTLS_E_REHANDSHAKE)
     {
-      if (gnutls->priv->rehandshake_mode == G_TLS_REHANDSHAKE_NEVER)
-       {
-         g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
-                              _("Peer requested illegal TLS rehandshake"));
-         return GNUTLS_E_PULL_ERROR;
-       }
-
-      g_mutex_lock (&gnutls->priv->op_mutex);
-      if (!gnutls->priv->handshaking)
-       gnutls->priv->need_handshake = TRUE;
-      g_mutex_unlock (&gnutls->priv->op_mutex);
+      if (priv->rehandshake_mode == G_TLS_REHANDSHAKE_NEVER)
+        {
+          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+                               _("Peer requested illegal TLS rehandshake"));
+          return GNUTLS_E_PULL_ERROR;
+        }
+
+      g_mutex_lock (&priv->op_mutex);
+      if (!priv->handshaking)
+        priv->need_handshake = TRUE;
+      g_mutex_unlock (&priv->op_mutex);
       return status;
     }
-  else if (status == GNUTLS_E_GOT_APPLICATION_DATA)
+  else if (status == GNUTLS_E_PREMATURE_TERMINATION)
     {
-      if (gnutls->priv->handshaking && G_IS_TLS_SERVER_CONNECTION (gnutls))
-       return GNUTLS_E_AGAIN;
-    }
-  else if (
-#ifdef GNUTLS_E_PREMATURE_TERMINATION
-          status == GNUTLS_E_PREMATURE_TERMINATION
-#else
-          status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH && gnutls->priv->eof
-#endif
-          )
-    {
-      if (gnutls->priv->require_close_notify)
-       {
-         g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF,
-                              _("TLS connection closed unexpectedly"));
-         G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls);
-         return status;
-       }
+      if (priv->handshaking && !priv->ever_handshaked)
+        {
+          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+                               _("Peer failed to perform TLS handshake"));
+          return GNUTLS_E_PULL_ERROR;
+        }
+      else if (priv->require_close_notify)
+        {
+          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF,
+                               _("TLS connection closed unexpectedly"));
+          G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls);
+          return status;
+        }
       else
-       return 0;
+        return 0;
     }
   else if (status == GNUTLS_E_NO_CERTIFICATE_FOUND)
     {
@@ -760,47 +1083,113 @@ end_gnutls_io (GTlsConnectionGnutls  *gnutls,
                            _("TLS connection peer did not send a certificate"));
       return status;
     }
+  else if (status == GNUTLS_E_CERTIFICATE_ERROR)
+    {
+      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                   _("Unacceptable TLS certificate"));
+      return status;
+    }
+  else if (status == GNUTLS_E_FATAL_ALERT_RECEIVED)
+    {
+      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+                   _("Peer sent fatal TLS alert: %s"),
+                   gnutls_alert_get_name (gnutls_alert_get (priv->session)));
+      return status;
+    }
+  else if (status == GNUTLS_E_INAPPROPRIATE_FALLBACK)
+    {
+      g_set_error_literal (error, G_TLS_ERROR,
+#if GLIB_CHECK_VERSION(2, 60, 0)
+                           G_TLS_ERROR_INAPPROPRIATE_FALLBACK,
+#else
+                           G_TLS_ERROR_MISC,
+#endif
+                           _("Protocol version downgrade attack detected"));
+      return status;
+    }
+  else if (status == GNUTLS_E_LARGE_PACKET)
+    {
+      guint mtu = gnutls_dtls_get_data_mtu (priv->session);
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
+                   ngettext ("Message is too large for DTLS connection; maximum is %u byte",
+                             "Message is too large for DTLS connection; maximum is %u bytes", mtu), mtu);
+      return status;
+    }
+  else if (status == GNUTLS_E_TIMEDOUT)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                           _("The operation timed out"));
+      return status;
+    }
 
   if (error)
     {
-      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
-                   errmsg, gnutls_strerror (status));
+      *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s",
+          err_prefix, gnutls_strerror (status));
     }
   return status;
 }
 
-#define BEGIN_GNUTLS_IO(gnutls, direction, blocking, cancellable)      \
-  begin_gnutls_io (gnutls, direction, blocking, cancellable);          \
+#define BEGIN_GNUTLS_IO(gnutls, direction, timeout, cancellable)        \
+  begin_gnutls_io (gnutls, direction, timeout, cancellable);            \
   do {
 
-#define END_GNUTLS_IO(gnutls, direction, ret, errmsg, err)             \
-  } while ((ret = end_gnutls_io (gnutls, direction, ret, errmsg, err)) == GNUTLS_E_AGAIN);
+#define END_GNUTLS_IO(gnutls, direction, ret, errmsg, err)              \
+  } while ((ret = end_gnutls_io (gnutls, direction, ret, err, errmsg)) == GNUTLS_E_AGAIN);
+
+/* Checks whether the underlying base stream or GDatagramBased meets
+ * @condition. */
+static gboolean
+g_tls_connection_gnutls_base_check (GTlsConnectionGnutls  *gnutls,
+                                    GIOCondition           condition)
+{
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  if (g_tls_connection_gnutls_is_dtls (gnutls))
+    return g_datagram_based_condition_check (priv->base_socket,
+                                             condition);
+  else if (condition & G_IO_IN)
+    return g_pollable_input_stream_is_readable (priv->base_istream);
+  else if (condition & G_IO_OUT)
+    return g_pollable_output_stream_is_writable (priv->base_ostream);
+  else
+    g_assert_not_reached ();
+}
 
+/* Checks whether the (D)TLS stream meets @condition; not the underlying base
+ * stream or GDatagramBased. */
 gboolean
 g_tls_connection_gnutls_check (GTlsConnectionGnutls  *gnutls,
-                              GIOCondition           condition)
+                               GIOCondition           condition)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
   /* Racy, but worst case is that we just get WOULD_BLOCK back */
-  if (gnutls->priv->need_finish_handshake)
+  if (priv->need_finish_handshake)
     return TRUE;
 
   /* If a handshake or close is in progress, then tls_istream and
    * tls_ostream are blocked, regardless of the base stream status.
    */
-  if (gnutls->priv->handshaking || gnutls->priv->closing)
+  if (priv->handshaking)
     return FALSE;
 
-  if (condition & G_IO_IN)
-    return g_pollable_input_stream_is_readable (gnutls->priv->base_istream);
-  else
-    return g_pollable_output_stream_is_writable (gnutls->priv->base_ostream);
+  if (((condition & G_IO_IN) && priv->read_closing) ||
+      ((condition & G_IO_OUT) && priv->write_closing))
+    return FALSE;
+
+  /* Defer to the base stream or GDatagramBased. */
+  return g_tls_connection_gnutls_base_check (gnutls, condition);
 }
 
 typedef struct {
   GSource               source;
 
   GTlsConnectionGnutls *gnutls;
-  GObject              *stream;
+  /* Either a GDatagramBased (datagram mode), or a GPollableInputStream or
+   * GPollableOutputStream (streaming mode):
+   */
+  GObject              *base;
 
   GSource              *child_source;
   GIOCondition          condition;
@@ -811,7 +1200,7 @@ typedef struct {
 
 static gboolean
 gnutls_source_prepare (GSource *source,
-                      gint    *timeout)
+                       gint    *timeout)
 {
   *timeout = -1;
   return FALSE;
@@ -823,26 +1212,40 @@ gnutls_source_check (GSource *source)
   return FALSE;
 }
 
+/* Use a custom dummy callback instead of g_source_set_dummy_callback(), as that
+ * uses a GClosure and is slow. (The GClosure is necessary to deal with any
+ * function prototype.) */
+static gboolean
+dummy_callback (gpointer data)
+{
+  return G_SOURCE_CONTINUE;
+}
+
 static void
 gnutls_source_sync (GTlsConnectionGnutlsSource *gnutls_source)
 {
   GTlsConnectionGnutls *gnutls = gnutls_source->gnutls;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   gboolean io_waiting, op_waiting;
 
-  g_mutex_lock (&gnutls->priv->op_mutex);
-  if (((gnutls_source->condition & G_IO_IN) && gnutls->priv->reading) ||
-      ((gnutls_source->condition & G_IO_OUT) && gnutls->priv->writing) ||
-      (gnutls->priv->handshaking && !gnutls->priv->need_finish_handshake))
+  /* Was the source destroyed earlier in this main context iteration? */
+  if (g_source_is_destroyed ((GSource *)gnutls_source))
+    return;
+
+  g_mutex_lock (&priv->op_mutex);
+  if (((gnutls_source->condition & G_IO_IN) && priv->reading) ||
+      ((gnutls_source->condition & G_IO_OUT) && priv->writing) ||
+      (priv->handshaking && !priv->need_finish_handshake))
     op_waiting = TRUE;
   else
     op_waiting = FALSE;
 
-  if (!op_waiting && !gnutls->priv->need_handshake &&
-      !gnutls->priv->need_finish_handshake)
+  if (!op_waiting && !priv->need_handshake &&
+      !priv->need_finish_handshake)
     io_waiting = TRUE;
   else
     io_waiting = FALSE;
-  g_mutex_unlock (&gnutls->priv->op_mutex);
+  g_mutex_unlock (&priv->op_mutex);
 
   if (op_waiting == gnutls_source->op_waiting &&
       io_waiting == gnutls_source->io_waiting)
@@ -853,33 +1256,41 @@ gnutls_source_sync (GTlsConnectionGnutlsSource *gnutls_source)
   if (gnutls_source->child_source)
     {
       g_source_remove_child_source ((GSource *)gnutls_source,
-                                   gnutls_source->child_source);
+                                    gnutls_source->child_source);
       g_source_unref (gnutls_source->child_source);
     }
 
   if (op_waiting)
-    gnutls_source->child_source = g_cancellable_source_new (gnutls->priv->waiting_for_op);
-  else if (io_waiting && G_IS_POLLABLE_INPUT_STREAM (gnutls_source->stream))
-    gnutls_source->child_source = g_pollable_input_stream_create_source (gnutls->priv->base_istream, NULL);
-  else if (io_waiting && G_IS_POLLABLE_OUTPUT_STREAM (gnutls_source->stream))
-    gnutls_source->child_source = g_pollable_output_stream_create_source (gnutls->priv->base_ostream, NULL);
+    gnutls_source->child_source = g_cancellable_source_new (priv->waiting_for_op);
+  else if (io_waiting && G_IS_DATAGRAM_BASED (gnutls_source->base))
+    gnutls_source->child_source = g_datagram_based_create_source (priv->base_socket, gnutls_source->condition, NULL);
+  else if (io_waiting && G_IS_POLLABLE_INPUT_STREAM (gnutls_source->base))
+    gnutls_source->child_source = g_pollable_input_stream_create_source (priv->base_istream, NULL);
+  else if (io_waiting && G_IS_POLLABLE_OUTPUT_STREAM (gnutls_source->base))
+    gnutls_source->child_source = g_pollable_output_stream_create_source (priv->base_ostream, NULL);
   else
     gnutls_source->child_source = g_timeout_source_new (0);
 
-  g_source_set_dummy_callback (gnutls_source->child_source);
+  g_source_set_callback (gnutls_source->child_source, dummy_callback, NULL, NULL);
   g_source_add_child_source ((GSource *)gnutls_source, gnutls_source->child_source);
 }
 
 static gboolean
 gnutls_source_dispatch (GSource     *source,
-                       GSourceFunc  callback,
-                       gpointer     user_data)
+                        GSourceFunc  callback,
+                        gpointer     user_data)
 {
-  GPollableSourceFunc func = (GPollableSourceFunc)callback;
+  GDatagramBasedSourceFunc datagram_based_func = (GDatagramBasedSourceFunc)callback;
+  GPollableSourceFunc pollable_func = (GPollableSourceFunc)callback;
   GTlsConnectionGnutlsSource *gnutls_source = (GTlsConnectionGnutlsSource *)source;
   gboolean ret;
 
-  ret = (*func) (gnutls_source->stream, user_data);
+  if (G_IS_DATAGRAM_BASED (gnutls_source->base))
+    ret = (*datagram_based_func) (G_DATAGRAM_BASED (gnutls_source->base),
+                                  gnutls_source->condition, user_data);
+  else
+    ret = (*pollable_func) (gnutls_source->base, user_data);
+
   if (ret)
     gnutls_source_sync (gnutls_source);
 
@@ -897,7 +1308,7 @@ gnutls_source_finalize (GSource *source)
 
 static gboolean
 g_tls_connection_gnutls_source_closure_callback (GObject  *stream,
-                                                gpointer  data)
+                                                 gpointer  data)
 {
   GClosure *closure = data;
 
@@ -919,7 +1330,35 @@ g_tls_connection_gnutls_source_closure_callback (GObject  *stream,
   return result;
 }
 
-static GSourceFuncs gnutls_source_funcs =
+static gboolean
+g_tls_connection_gnutls_source_dtls_closure_callback (GObject  *stream,
+                                                      GIOCondition condition,
+                                                      gpointer  data)
+{
+  GClosure *closure = data;
+
+  GValue param[2] = { G_VALUE_INIT, G_VALUE_INIT };
+  GValue result_value = G_VALUE_INIT;
+  gboolean result;
+
+  g_value_init (&result_value, G_TYPE_BOOLEAN);
+
+  g_value_init (&param[0], G_TYPE_DATAGRAM_BASED);
+  g_value_set_object (&param[0], stream);
+  g_value_init (&param[1], G_TYPE_IO_CONDITION);
+  g_value_set_flags (&param[1], condition);
+
+  g_closure_invoke (closure, &result_value, 2, param, NULL);
+
+  result = g_value_get_boolean (&result_value);
+  g_value_unset (&result_value);
+  g_value_unset (&param[0]);
+  g_value_unset (&param[1]);
+
+  return result;
+}
+
+static GSourceFuncs gnutls_tls_source_funcs =
 {
   gnutls_source_prepare,
   gnutls_source_check,
@@ -929,23 +1368,47 @@ static GSourceFuncs gnutls_source_funcs =
   (GSourceDummyMarshal)g_cclosure_marshal_generic
 };
 
+static GSourceFuncs gnutls_dtls_source_funcs =
+{
+  gnutls_source_prepare,
+  gnutls_source_check,
+  gnutls_source_dispatch,
+  gnutls_source_finalize,
+  (GSourceFunc)g_tls_connection_gnutls_source_dtls_closure_callback,
+  (GSourceDummyMarshal)g_cclosure_marshal_generic
+};
+
 GSource *
 g_tls_connection_gnutls_create_source (GTlsConnectionGnutls  *gnutls,
-                                      GIOCondition           condition,
-                                      GCancellable          *cancellable)
+                                       GIOCondition           condition,
+                                       GCancellable          *cancellable)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   GSource *source, *cancellable_source;
   GTlsConnectionGnutlsSource *gnutls_source;
 
-  source = g_source_new (&gnutls_source_funcs, sizeof (GTlsConnectionGnutlsSource));
+  if (g_tls_connection_gnutls_is_dtls (gnutls))
+    {
+      source = g_source_new (&gnutls_dtls_source_funcs,
+                             sizeof (GTlsConnectionGnutlsSource));
+    }
+  else
+    {
+      source = g_source_new (&gnutls_tls_source_funcs,
+                             sizeof (GTlsConnectionGnutlsSource));
+    }
   g_source_set_name (source, "GTlsConnectionGnutlsSource");
   gnutls_source = (GTlsConnectionGnutlsSource *)source;
   gnutls_source->gnutls = g_object_ref (gnutls);
   gnutls_source->condition = condition;
-  if (condition & G_IO_IN)
-    gnutls_source->stream = G_OBJECT (gnutls->priv->tls_istream);
-  else if (condition & G_IO_OUT)
-    gnutls_source->stream = G_OBJECT (gnutls->priv->tls_ostream);
+  if (g_tls_connection_gnutls_is_dtls (gnutls))
+    gnutls_source->base = G_OBJECT (gnutls);
+  else if (priv->tls_istream != NULL && condition & G_IO_IN)
+    gnutls_source->base = G_OBJECT (priv->tls_istream);
+  else if (priv->tls_ostream != NULL && condition & G_IO_OUT)
+    gnutls_source->base = G_OBJECT (priv->tls_ostream);
+  else
+    g_assert_not_reached ();
 
   gnutls_source->op_waiting = (gboolean) -1;
   gnutls_source->io_waiting = (gboolean) -1;
@@ -962,168 +1425,382 @@ g_tls_connection_gnutls_create_source (GTlsConnectionGnutls  *gnutls,
   return source;
 }
 
-static void
-set_gnutls_error (GTlsConnectionGnutls *gnutls,
-                 GError               *error)
+static GSource *
+g_tls_connection_gnutls_dtls_create_source (GDatagramBased  *datagram_based,
+                                            GIOCondition     condition,
+                                            GCancellable    *cancellable)
 {
-  /* We set EINTR rather than EAGAIN for G_IO_ERROR_WOULD_BLOCK so
-   * that GNUTLS_E_AGAIN only gets returned for gnutls-internal
-   * reasons, not for actual socket EAGAINs (and we have access
-   * to @error at the higher levels, so we can distinguish them
-   * that way later).
-   */
+  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
 
-  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-    gnutls_transport_set_errno (gnutls->priv->session, EINTR);
-  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
-    gnutls_transport_set_errno (gnutls->priv->session, EINTR);
-  else
-    gnutls_transport_set_errno (gnutls->priv->session, EIO);
+  return g_tls_connection_gnutls_create_source (gnutls, condition, cancellable);
 }
 
-static ssize_t
-g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t  transport_data,
-                                  void                   *buf,
-                                  size_t                  buflen)
+static GIOCondition
+g_tls_connection_gnutls_condition_check (GDatagramBased  *datagram_based,
+                                         GIOCondition     condition)
 {
-  GTlsConnectionGnutls *gnutls = transport_data;
-  ssize_t ret;
+  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
 
-  /* If gnutls->priv->read_error is non-%NULL when we're called, it means
-   * that an error previously occurred, but gnutls decided not to
-   * propagate it. So it's correct for us to just clear it. (Usually
-   * this means it ignored an EAGAIN after a short read, and now
-   * we'll return EAGAIN again, which it will obey this time.)
-   */
-  g_clear_error (&gnutls->priv->read_error);
+  return (g_tls_connection_gnutls_check (gnutls, condition)) ? condition : 0;
+}
 
-  ret = g_pollable_stream_read (G_INPUT_STREAM (gnutls->priv->base_istream),
-                               buf, buflen,
-                               gnutls->priv->read_blocking,
-                               gnutls->priv->read_cancellable,
-                               &gnutls->priv->read_error);
+static gboolean
+g_tls_connection_gnutls_condition_wait (GDatagramBased  *datagram_based,
+                                        GIOCondition     condition,
+                                        gint64           timeout,
+                                        GCancellable    *cancellable,
+                                        GError         **error)
+{
+  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  GPollFD fds[2];
+  guint n_fds;
+  gint result = 1;  /* if the loop is never entered, it’s as if we cancelled early */
+  gint64 start_time;
 
-  if (ret < 0)
-    set_gnutls_error (gnutls, gnutls->priv->read_error);
-#ifndef GNUTLS_E_PREMATURE_TERMINATION
-  else if (ret == 0)
-    gnutls->priv->eof = TRUE;
-#endif
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
 
-  return ret;
+  /* Convert from microseconds to milliseconds. */
+  if (timeout != -1)
+    timeout = timeout / 1000;
+
+  start_time = g_get_monotonic_time ();
+
+  g_cancellable_make_pollfd (priv->waiting_for_op, &fds[0]);
+  n_fds = 1;
+
+  if (g_cancellable_make_pollfd (cancellable, &fds[1]))
+    n_fds++;
+
+  while (!g_tls_connection_gnutls_condition_check (datagram_based, condition) &&
+         !g_cancellable_is_cancelled (cancellable))
+    {
+      result = g_poll (fds, n_fds, timeout);
+      if (result == 0)
+        break;
+      if (result != -1 || errno != EINTR)
+        continue;
+
+      if (timeout != -1)
+        {
+          timeout -= (g_get_monotonic_time () - start_time) / 1000;
+          if (timeout < 0)
+            timeout = 0;
+        }
+    }
+
+  if (n_fds > 1)
+    g_cancellable_release_fd (cancellable);
+
+  if (result == 0)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                           _("Socket I/O timed out"));
+      return FALSE;
+    }
+
+  return !g_cancellable_set_error_if_cancelled (cancellable, error);
+}
+
+static void
+set_gnutls_error (GTlsConnectionGnutls *gnutls,
+                  GError               *error)
+{
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  /* We set EINTR rather than EAGAIN for G_IO_ERROR_WOULD_BLOCK so
+   * that GNUTLS_E_AGAIN only gets returned for gnutls-internal
+   * reasons, not for actual socket EAGAINs (and we have access
+   * to @error at the higher levels, so we can distinguish them
+   * that way later).
+   */
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    gnutls_transport_set_errno (priv->session, EINTR);
+  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+    {
+      /* Return EAGAIN while handshaking so that GnuTLS handles retries for us
+       * internally in its handshaking code. */
+      if (priv->base_socket && priv->handshaking)
+        gnutls_transport_set_errno (priv->session, EAGAIN);
+      else
+        gnutls_transport_set_errno (priv->session, EINTR);
+    }
+  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+    gnutls_transport_set_errno (priv->session, EINTR);
+  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE))
+    gnutls_transport_set_errno (priv->session, EMSGSIZE);
+  else
+    gnutls_transport_set_errno (priv->session, EIO);
 }
 
 static ssize_t
-g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t  transport_data,
-                                  const void             *buf,
-                                  size_t                  buflen)
+g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t  transport_data,
+                                   void                   *buf,
+                                   size_t                  buflen)
 {
   GTlsConnectionGnutls *gnutls = transport_data;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   ssize_t ret;
 
-  /* See comment in pull_func. */
-  g_clear_error (&gnutls->priv->write_error);
+  /* If priv->read_error is non-%NULL when we're called, it means
+   * that an error previously occurred, but gnutls decided not to
+   * propagate it. So it's correct for us to just clear it. (Usually
+   * this means it ignored an EAGAIN after a short read, and now
+   * we'll return EAGAIN again, which it will obey this time.)
+   */
+  g_clear_error (&priv->read_error);
+
+  if (g_tls_connection_gnutls_is_dtls (gnutls))
+    {
+      GInputVector vector = { buf, buflen };
+      GInputMessage message = { NULL, &vector, 1, 0, 0, NULL, NULL };
+
+      ret = g_datagram_based_receive_messages (priv->base_socket,
+                                               &message, 1, 0,
+                                               priv->handshaking ? 0 : priv->read_timeout,
+                                               priv->read_cancellable,
+                                               &priv->read_error);
+
+      if (ret > 0)
+        ret = message.bytes_received;
+    }
+  else
+    {
+      ret = g_pollable_stream_read (G_INPUT_STREAM (priv->base_istream),
+                                    buf, buflen,
+                                    (priv->read_timeout != 0),
+                                    priv->read_cancellable,
+                                    &priv->read_error);
+    }
 
-  ret = g_pollable_stream_write (G_OUTPUT_STREAM (gnutls->priv->base_ostream),
-                                buf, buflen,
-                                gnutls->priv->write_blocking,
-                                gnutls->priv->write_cancellable,
-                                &gnutls->priv->write_error);
   if (ret < 0)
-    set_gnutls_error (gnutls, gnutls->priv->write_error);
+    set_gnutls_error (gnutls, priv->read_error);
 
   return ret;
 }
 
-static void
-handshake_thread (GTask        *task,
-                 gpointer      object,
-                 gpointer      task_data,
-                 GCancellable *cancellable)
+static ssize_t
+g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t  transport_data,
+                                   const void             *buf,
+                                   size_t                  buflen)
 {
-  GTlsConnectionGnutls *gnutls = object;
-  gboolean is_client;
-  GError *error = NULL;
-  int ret;
+  GTlsConnectionGnutls *gnutls = transport_data;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  ssize_t ret;
 
-  gnutls->priv->started_handshake = FALSE;
+  /* See comment in pull_func. */
+  g_clear_error (&priv->write_error);
 
-  if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE,
-                TRUE, cancellable, &error))
+  if (g_tls_connection_gnutls_is_dtls (gnutls))
     {
-      g_task_return_error (task, error);
-      return;
-    }
+      GOutputVector vector = { buf, buflen };
+      GOutputMessage message = { NULL, &vector, 1, 0, NULL, 0 };
 
-  g_clear_error (&gnutls->priv->handshake_error);
+      ret = g_datagram_based_send_messages (priv->base_socket,
+                                            &message, 1, 0,
+                                            priv->write_timeout,
+                                            priv->write_cancellable,
+                                            &priv->write_error);
 
-  is_client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
-
-  if (!is_client && gnutls->priv->ever_handshaked &&
-      !gnutls->priv->implicit_handshake)
+      if (ret > 0)
+        ret = message.bytes_sent;
+    }
+  else
     {
-      BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, TRUE, cancellable);
-      ret = gnutls_rehandshake (gnutls->priv->session);
-      END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
-                    _("Error performing TLS handshake: %s"), &error);
-
-      if (error)
-       {
-         g_task_return_error (task, error);
-         return;
-       }
+      ret = g_pollable_stream_write (G_OUTPUT_STREAM (priv->base_ostream),
+                                     buf, buflen,
+                                     (priv->write_timeout != 0),
+                                     priv->write_cancellable,
+                                     &priv->write_error);
     }
 
-  gnutls->priv->started_handshake = TRUE;
+  if (ret < 0)
+    set_gnutls_error (gnutls, priv->write_error);
 
-  g_clear_object (&gnutls->priv->peer_certificate);
-  gnutls->priv->peer_certificate_errors = 0;
+  return ret;
+}
 
-  g_tls_connection_gnutls_set_handshake_priority (gnutls);
+static ssize_t
+g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t  transport_data,
+                                       const giovec_t         *iov,
+                                       int                     iovcnt)
+{
+  GTlsConnectionGnutls *gnutls = transport_data;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  ssize_t ret;
+  GOutputMessage message = { NULL, };
+  GOutputVector *vectors;
 
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, TRUE, cancellable);
-  ret = gnutls_handshake (gnutls->priv->session);
-  END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
-                _("Error performing TLS handshake: %s"), &error);
+  /* This function should only be set if we’re using base_socket. */
+  g_assert (priv->base_socket != NULL);
 
-  if (error)
+  /* See comment in pull_func. */
+  g_clear_error (&priv->write_error);
+
+  /* this entire expression will be evaluated at compile time */
+  if (sizeof *iov == sizeof *vectors &&
+      sizeof iov->iov_base == sizeof vectors->buffer &&
+      G_STRUCT_OFFSET (giovec_t, iov_base) ==
+      G_STRUCT_OFFSET (GOutputVector, buffer) &&
+      sizeof iov->iov_len == sizeof vectors->size &&
+      G_STRUCT_OFFSET (giovec_t, iov_len) ==
+      G_STRUCT_OFFSET (GOutputVector, size))
+    /* ABI is compatible */
     {
-      g_task_return_error (task, error);
+      message.vectors = (GOutputVector *)iov;
+      message.num_vectors = iovcnt;
     }
   else
+    /* ABI is incompatible */
     {
-      gnutls->priv->ever_handshaked = TRUE;
-      g_task_return_boolean (task, TRUE);
+      gint i;
+
+      message.vectors = g_newa (GOutputVector, iovcnt);
+      for (i = 0; i < iovcnt; i++)
+        {
+          message.vectors[i].buffer = (void *)iov[i].iov_base;
+          message.vectors[i].size = iov[i].iov_len;
+        }
+      message.num_vectors = iovcnt;
+    }
+
+  ret = g_datagram_based_send_messages (priv->base_socket,
+                                        &message, 1, 0,
+                                        priv->write_timeout,
+                                        priv->write_cancellable,
+                                        &priv->write_error);
+
+  if (ret > 0)
+    ret = message.bytes_sent;
+  else if (ret < 0)
+    set_gnutls_error (gnutls, priv->write_error);
+
+  return ret;
+}
+
+static gboolean
+read_pollable_cb (GPollableInputStream *istream,
+                  gpointer              user_data)
+{
+  gboolean *read_done = user_data;
+
+  *read_done = TRUE;
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+read_datagram_based_cb (GDatagramBased *datagram_based,
+                        GIOCondition    condition,
+                        gpointer        user_data)
+{
+  gboolean *read_done = user_data;
+
+  *read_done = TRUE;
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+read_timeout_cb (gpointer user_data)
+{
+  gboolean *timed_out = user_data;
+
+  *timed_out = TRUE;
+
+  return G_SOURCE_REMOVE;
+}
+
+static int
+g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
+                                           unsigned int           ms)
+{
+  GTlsConnectionGnutls *gnutls = transport_data;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  /* Fast path. */
+  if (g_tls_connection_gnutls_base_check (gnutls, G_IO_IN) ||
+      g_cancellable_is_cancelled (priv->read_cancellable))
+    return 1;
+
+  /* If @ms is 0, GnuTLS wants an instant response, so there’s no need to
+   * construct and query a #GSource. */
+  if (ms > 0)
+    {
+      GMainContext *ctx = NULL;
+      GSource *read_source = NULL, *timeout_source = NULL;
+      gboolean read_done = FALSE, timed_out = FALSE;
+
+      ctx = g_main_context_new ();
+
+      /* Create a timeout source. */
+      timeout_source = g_timeout_source_new (ms);
+      g_source_set_callback (timeout_source, (GSourceFunc)read_timeout_cb,
+                             &timed_out, NULL);
+
+      /* Create a read source. We cannot use g_source_set_ready_time() on this
+       * to combine it with the @timeout_source, as that could mess with the
+       * internals of the #GDatagramBased’s #GSource implementation. */
+      if (g_tls_connection_gnutls_is_dtls (gnutls))
+        {
+          read_source = g_datagram_based_create_source (priv->base_socket, G_IO_IN, NULL);
+          g_source_set_callback (read_source, (GSourceFunc)read_datagram_based_cb,
+                                 &read_done, NULL);
+        }
+      else
+        {
+          read_source = g_pollable_input_stream_create_source (priv->base_istream, NULL);
+          g_source_set_callback (read_source, (GSourceFunc)read_pollable_cb,
+                                 &read_done, NULL);
+        }
+
+      g_source_attach (read_source, ctx);
+      g_source_attach (timeout_source, ctx);
+
+      while (!read_done && !timed_out)
+        g_main_context_iteration (ctx, TRUE);
+
+      g_source_destroy (read_source);
+      g_source_destroy (timeout_source);
+
+      g_main_context_unref (ctx);
+      g_source_unref (read_source);
+      g_source_unref (timeout_source);
+
+      /* If @read_source was dispatched due to cancellation, the resulting error
+       * will be handled in g_tls_connection_gnutls_pull_func(). */
+      if (g_tls_connection_gnutls_base_check (gnutls, G_IO_IN) ||
+          g_cancellable_is_cancelled (priv->read_cancellable))
+        return 1;
     }
+
+  return 0;
 }
 
 static GTlsCertificate *
 get_peer_certificate_from_session (GTlsConnectionGnutls *gnutls)
 {
-  GTlsCertificate *chain, *cert;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   const gnutls_datum_t *certs;
+  GTlsCertificateGnutls *chain;
   unsigned int num_certs;
-  int i;
 
-  certs = gnutls_certificate_get_peers (gnutls->priv->session, &num_certs);
+  certs = gnutls_certificate_get_peers (priv->session, &num_certs);
   if (!certs || !num_certs)
     return NULL;
 
-  chain = NULL;
-  for (i = num_certs - 1; i >= 0; i--)
-    {
-      cert = g_tls_certificate_gnutls_new (&certs[i], chain);
-      if (chain)
-       g_object_unref (chain);
-      chain = cert;
-    }
+  chain = g_tls_certificate_gnutls_build_chain (certs, num_certs, GNUTLS_X509_FMT_DER);
+  if (!chain)
+    return NULL;
 
-  return chain;
+  return G_TLS_CERTIFICATE (chain);
 }
 
 static GTlsCertificateFlags
 verify_peer_certificate (GTlsConnectionGnutls *gnutls,
-                        GTlsCertificate      *peer_certificate)
+                         GTlsCertificate      *peer_certificate)
 {
   GTlsConnection *conn = G_TLS_CONNECTION (gnutls);
   GSocketConnectable *peer_identity;
@@ -1132,10 +1809,13 @@ verify_peer_certificate (GTlsConnectionGnutls *gnutls,
   gboolean is_client;
 
   is_client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
-  if (is_client)
+
+  if (!is_client)
+    peer_identity = NULL;
+  else if (!g_tls_connection_gnutls_is_dtls (gnutls))
     peer_identity = g_tls_client_connection_get_server_identity (G_TLS_CLIENT_CONNECTION (gnutls));
   else
-    peer_identity = NULL;
+    peer_identity = g_dtls_client_connection_get_server_identity (G_DTLS_CLIENT_CONNECTION (gnutls));
 
   errors = 0;
 
@@ -1150,121 +1830,447 @@ verify_peer_certificate (GTlsConnectionGnutls *gnutls,
       GError *error = NULL;
 
       errors |= g_tls_database_verify_chain (database, peer_certificate,
-                                            is_client ?
-                                            G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER :
-                                            G_TLS_DATABASE_PURPOSE_AUTHENTICATE_CLIENT,
-                                            peer_identity,
-                                            g_tls_connection_get_interaction (conn),
-                                            G_TLS_DATABASE_VERIFY_NONE,
-                                            NULL, &error);
+                                             is_client ?
+                                             G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER :
+                                             G_TLS_DATABASE_PURPOSE_AUTHENTICATE_CLIENT,
+                                             peer_identity,
+                                             g_tls_connection_get_interaction (conn),
+                                             G_TLS_DATABASE_VERIFY_NONE,
+                                             NULL, &error);
       if (error)
-       {
-         g_warning ("failure verifying certificate chain: %s",
-                    error->message);
-         g_assert (errors != 0);
-         g_clear_error (&error);
-       }
+        {
+          g_warning ("failure verifying certificate chain: %s",
+                     error->message);
+          g_assert (errors != 0);
+          g_clear_error (&error);
+        }
     }
 
   return errors;
 }
 
+static void
+update_peer_certificate_and_compute_errors (GTlsConnectionGnutls *gnutls)
+{
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  /* This function must be called from the handshake context thread
+   * (probably the main thread, NOT the handshake thread) because it
+   * emits notifies that are application-visible.
+   *
+   * verify_certificate_mutex should be locked.
+   */
+  g_assert (priv->handshake_context);
+  g_assert (g_main_context_is_owner (priv->handshake_context));
+
+  g_clear_object (&priv->peer_certificate);
+  priv->peer_certificate_errors = 0;
+
+  if (gnutls_certificate_type_get (priv->session) == GNUTLS_CRT_X509)
+    {
+      priv->peer_certificate = get_peer_certificate_from_session (gnutls);
+      if (priv->peer_certificate)
+        priv->peer_certificate_errors = verify_peer_certificate (gnutls, priv->peer_certificate);
+    }
+
+  g_object_notify (G_OBJECT (gnutls), "peer-certificate");
+  g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors");
+}
+
 static gboolean
-accept_peer_certificate (GTlsConnectionGnutls *gnutls,
-                        GTlsCertificate      *peer_certificate,
-                        GTlsCertificateFlags  peer_certificate_errors)
+accept_or_reject_peer_certificate (gpointer user_data)
 {
-  gboolean accepted;
+  GTlsConnectionGnutls *gnutls = user_data;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  gboolean accepted = FALSE;
 
-  if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
+  g_assert (g_main_context_is_owner (priv->handshake_context));
+
+  g_mutex_lock (&priv->verify_certificate_mutex);
+
+  update_peer_certificate_and_compute_errors (gnutls);
+
+  if (G_IS_TLS_CLIENT_CONNECTION (gnutls) && priv->peer_certificate != NULL)
     {
-      GTlsCertificateFlags validation_flags =
-       g_tls_client_connection_get_validation_flags (G_TLS_CLIENT_CONNECTION (gnutls));
+      GTlsCertificateFlags validation_flags;
 
-      if ((peer_certificate_errors & validation_flags) == 0)
-       accepted = TRUE;
+      if (!g_tls_connection_gnutls_is_dtls (gnutls))
+        validation_flags =
+          g_tls_client_connection_get_validation_flags (G_TLS_CLIENT_CONNECTION (gnutls));
       else
-       {
-         accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls),
-                                                              peer_certificate,
-                                                              peer_certificate_errors);
-       }
+        validation_flags =
+          g_dtls_client_connection_get_validation_flags (G_DTLS_CLIENT_CONNECTION (gnutls));
+
+      if ((priv->peer_certificate_errors & validation_flags) == 0)
+        accepted = TRUE;
     }
-  else
+
+  if (!accepted)
     {
+      g_main_context_pop_thread_default (priv->handshake_context);
       accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls),
-                                                          peer_certificate,
-                                                          peer_certificate_errors);
+                                                           priv->peer_certificate,
+                                                           priv->peer_certificate_errors);
+      g_main_context_push_thread_default (priv->handshake_context);
     }
 
-  return accepted;
+  priv->peer_certificate_accepted = accepted;
+
+  /* This has to be the very last statement before signaling the
+   * condition variable because otherwise the code could spuriously
+   * wakeup and continue before we are done here.
+   */
+  priv->peer_certificate_examined = TRUE;
+
+  g_cond_signal (&priv->verify_certificate_condition);
+  g_mutex_unlock (&priv->verify_certificate_mutex);
+
+  g_object_notify (G_OBJECT (gnutls), "peer-certificate");
+  g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors");
+
+  return G_SOURCE_REMOVE;
+}
+
+static int
+verify_certificate_cb (gnutls_session_t session)
+{
+  GTlsConnectionGnutls *gnutls = gnutls_session_get_ptr (session);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  gboolean accepted;
+
+  g_mutex_lock (&priv->verify_certificate_mutex);
+  priv->peer_certificate_examined = FALSE;
+  priv->peer_certificate_accepted = FALSE;
+  g_mutex_unlock (&priv->verify_certificate_mutex);
+
+  /* Invoke the callback on the handshake context's thread. This is
+   * necessary because we need to ensure the accept-certificate signal
+   * is emitted on the original thread.
+   */
+  g_assert (priv->handshake_context);
+  g_main_context_invoke (priv->handshake_context, accept_or_reject_peer_certificate, gnutls);
+
+  /* We'll block the handshake thread until the original thread has
+   * decided whether to accept the certificate.
+   */
+  g_mutex_lock (&priv->verify_certificate_mutex);
+  while (!priv->peer_certificate_examined)
+    g_cond_wait (&priv->verify_certificate_condition, &priv->verify_certificate_mutex);
+  accepted = priv->peer_certificate_accepted;
+  g_mutex_unlock (&priv->verify_certificate_mutex);
+
+  /* Return 0 for the handshake to continue, non-zero to terminate. */
+  return !accepted;
+}
+
+static void
+handshake_thread (GTask        *task,
+                  gpointer      object,
+                  gpointer      task_data,
+                  GCancellable *cancellable)
+{
+  GTlsConnectionGnutls *gnutls = object;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  GError *error = NULL;
+  int ret;
+  gint64 start_time;
+  gint64 timeout;
+
+  /* A timeout, in microseconds, must be provided as a gint64* task_data. */
+  g_assert (task_data != NULL);
+
+  timeout = *((gint64 *)task_data);
+  start_time = g_get_monotonic_time ();
+  priv->started_handshake = FALSE;
+
+  if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE,
+                 timeout, cancellable, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_clear_error (&priv->handshake_error);
+
+  if (priv->ever_handshaked && !priv->implicit_handshake)
+    {
+      if (priv->rehandshake_mode != G_TLS_REHANDSHAKE_UNSAFELY &&
+          !gnutls_safe_renegotiation_status (priv->session))
+        {
+          g_task_return_new_error (task, G_TLS_ERROR, G_TLS_ERROR_MISC,
+                                   _("Peer does not support safe renegotiation"));
+          return;
+        }
+
+      if (!G_IS_TLS_CLIENT_CONNECTION (gnutls))
+        {
+          /* Adjust the timeout for the next operation in the sequence. */
+          if (timeout > 0)
+            {
+              unsigned int timeout_ms;
+
+              timeout -= (g_get_monotonic_time () - start_time);
+              if (timeout <= 0)
+                timeout = 1;
+
+              /* Convert from microseconds to milliseconds, but ensure the timeout
+               * remains positive. */
+              timeout_ms = (timeout + 999) / 1000;
+
+              gnutls_handshake_set_timeout (priv->session, timeout_ms);
+              gnutls_dtls_set_timeouts (priv->session, 1000 /* default */,
+                                        timeout_ms);
+            }
+
+          BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
+          ret = gnutls_rehandshake (priv->session);
+          END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
+                         _("Error performing TLS handshake"), &error);
+
+          if (error)
+            {
+              g_task_return_error (task, error);
+              return;
+            }
+        }
+    }
+
+  priv->started_handshake = TRUE;
+
+  if (!priv->ever_handshaked)
+    g_tls_connection_gnutls_set_handshake_priority (gnutls);
+
+  /* Adjust the timeout for the next operation in the sequence. */
+  if (timeout > 0)
+    {
+      unsigned int timeout_ms;
+
+      timeout -= (g_get_monotonic_time () - start_time);
+      if (timeout <= 0)
+        timeout = 1;
+
+      /* Convert from microseconds to milliseconds, but ensure the timeout
+       * remains positive. */
+      timeout_ms = (timeout + 999) / 1000;
+
+      gnutls_handshake_set_timeout (priv->session, timeout_ms);
+      gnutls_dtls_set_timeouts (priv->session, 1000 /* default */,
+                                timeout_ms);
+    }
+
+  BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
+  ret = gnutls_handshake (priv->session);
+  if (ret == GNUTLS_E_GOT_APPLICATION_DATA)
+    {
+      guint8 buf[1024];
+
+      /* Got app data while waiting for rehandshake; buffer it and try again */
+      ret = gnutls_record_recv (priv->session, buf, sizeof (buf));
+      if (ret > -1)
+        {
+          if (!priv->app_data_buf)
+            priv->app_data_buf = g_byte_array_new ();
+          g_byte_array_append (priv->app_data_buf, buf, ret);
+          ret = GNUTLS_E_AGAIN;
+        }
+    }
+  END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
+                 _("Error performing TLS handshake"), &error);
+
+  /* This calls the finish_handshake code of GTlsClientConnectionGnutls
+   * or GTlsServerConnectionGnutls. It has nothing to do with
+   * GTlsConnectionGnutls's own finish_handshake function, which still
+   * needs to be called at this point.
+   */
+  G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->finish_handshake (gnutls, &error);
+
+  if (error)
+    {
+      g_task_return_error (task, error);
+    }
+  else
+    {
+      priv->ever_handshaked = TRUE;
+      g_task_return_boolean (task, TRUE);
+    }
 }
 
 static void
 begin_handshake (GTlsConnectionGnutls *gnutls)
 {
+#if GLIB_CHECK_VERSION(2, 60, 0)
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+#if ENABLE(TIZEN_PERFORMANCE_TEST_LOG)
+  HWCLOCK_LOG("[BGN] gnutls_verify_peer");
+#endif
+
+  if (priv->advertised_protocols)
+    {
+      gnutls_datum_t *protocols;
+      int n_protos, i;
+
+      n_protos = g_strv_length (priv->advertised_protocols);
+      protocols = g_new (gnutls_datum_t, n_protos);
+      for (i = 0; priv->advertised_protocols[i]; i++)
+        {
+          protocols[i].size = strlen (priv->advertised_protocols[i]);
+          protocols[i].data = g_memdup (priv->advertised_protocols[i], protocols[i].size);
+        }
+      gnutls_alpn_set_protocols (priv->session, protocols, n_protos, 0);
+      g_free (protocols);
+    }
+#endif
+
+#if ENABLE(TIZEN_PERFORMANCE_TEST_LOG)
+  HWCLOCK_LOG("[END] gnutls_verify_peer");
+#endif
+
   G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->begin_handshake (gnutls);
 }
 
+#if GLIB_CHECK_VERSION(2, 60, 0)
+static void
+update_negotiated_protocol (GTlsConnectionGnutls *gnutls)
+{
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  gchar *orig_negotiated_protocol;
+  gnutls_datum_t protocol;
+
+  /*
+   * Preserve the prior negotiated protocol before clearing it
+   */
+  orig_negotiated_protocol = g_steal_pointer (&priv->negotiated_protocol);
+
+
+  if (gnutls_alpn_get_selected_protocol (priv->session, &protocol) == 0 && protocol.size > 0)
+    priv->negotiated_protocol = g_strndup ((gchar *)protocol.data, protocol.size);
+
+  /*
+   * Notify only if the negotiated protocol changed
+   */
+  if (g_strcmp0 (orig_negotiated_protocol, priv->negotiated_protocol) != 0)
+    g_object_notify (G_OBJECT (gnutls), "negotiated-protocol");
+
+  g_free (orig_negotiated_protocol);
+}
+#endif
+
 static gboolean
 finish_handshake (GTlsConnectionGnutls  *gnutls,
-                 GTask                 *task,
-                 GError               **error)
+                  GTask                 *task,
+                  GError               **error)
 {
-  GTlsCertificate *peer_certificate;
-  GTlsCertificateFlags peer_certificate_errors;
-
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   g_assert (error != NULL);
 
-  if (g_task_propagate_boolean (task, error) &&
-      gnutls_certificate_type_get (gnutls->priv->session) == GNUTLS_CRT_X509)
-    peer_certificate = get_peer_certificate_from_session (gnutls);
-  else
-    peer_certificate = NULL;
-
-  if (peer_certificate)
+  if (gnutls_session_is_resumed (priv->session))
     {
-      peer_certificate_errors = verify_peer_certificate (gnutls, peer_certificate);
-      if (!accept_peer_certificate (gnutls, peer_certificate,
-                                   peer_certificate_errors))
-       {
-         g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                              _("Unacceptable TLS certificate"));
-       }
-
-      gnutls->priv->peer_certificate = peer_certificate;
-      gnutls->priv->peer_certificate_errors = peer_certificate_errors;
-      g_object_notify (G_OBJECT (gnutls), "peer-certificate");
-      g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors");
+      /* Because this session was resumed, we skipped certificate
+       * verification on this handshake, so we missed our earlier
+       * chance to set peer_certificate and peer_certificate_errors.
+       * Do so here instead.
+       *
+       * The certificate has already been accepted, so we don't do
+       * anything with the result here.
+       */
+      g_mutex_lock (&priv->verify_certificate_mutex);
+      update_peer_certificate_and_compute_errors (gnutls);
+      priv->peer_certificate_examined = TRUE;
+      priv->peer_certificate_accepted = TRUE;
+      g_mutex_unlock (&priv->verify_certificate_mutex);
     }
-  else if (error && !*error && G_IS_TLS_CLIENT_CONNECTION (gnutls))
+
+  if (g_task_propagate_boolean (task, error) &&
+      priv->peer_certificate && !priv->peer_certificate_accepted)
     {
       g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                          _("Server did not return a valid TLS certificate"));
+                           _("Unacceptable TLS certificate"));
     }
 
-  G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->finish_handshake (gnutls, error);
+#if GLIB_CHECK_VERSION(2, 60, 0)
+  if (!*error && priv->advertised_protocols)
+    update_negotiated_protocol (gnutls);
+#endif
 
-  if (*error && gnutls->priv->started_handshake)
-    gnutls->priv->handshake_error = g_error_copy (*error);
+  if (*error && priv->started_handshake)
+    priv->handshake_error = g_error_copy (*error);
 
   return (*error == NULL);
 }
 
+static void
+sync_handshake_thread_completed (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  g_assert (g_main_context_is_owner (priv->handshake_context));
+
+  g_mutex_lock (&priv->op_mutex);
+  priv->sync_handshake_completed = TRUE;
+  g_mutex_unlock (&priv->op_mutex);
+
+  g_main_context_wakeup (priv->handshake_context);
+}
+
+static void
+crank_sync_handshake_context (GTlsConnectionGnutls *gnutls,
+                              GCancellable         *cancellable)
+{
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  /* need_finish_handshake will be set inside sync_handshake_thread_completed(),
+   * which should only ever be invoked while iterating the handshake context
+   * here. So need_finish_handshake should only change on this thread.
+   */
+  g_mutex_lock (&priv->op_mutex);
+  priv->sync_handshake_completed = FALSE;
+  while (!priv->sync_handshake_completed && !g_cancellable_is_cancelled (cancellable))
+    {
+      g_mutex_unlock (&priv->op_mutex);
+      g_main_context_iteration (priv->handshake_context, TRUE);
+      g_mutex_lock (&priv->op_mutex);
+    }
+  g_mutex_unlock (&priv->op_mutex);
+}
+
 static gboolean
 g_tls_connection_gnutls_handshake (GTlsConnection   *conn,
-                                  GCancellable     *cancellable,
-                                  GError          **error)
+                                   GCancellable     *cancellable,
+                                   GError          **error)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   GTask *task;
   gboolean success;
+  gint64 *timeout = NULL;
   GError *my_error = NULL;
 
-  task = g_task_new (conn, cancellable, NULL, NULL);
+  g_assert (priv->handshake_context == NULL);
+  priv->handshake_context = g_main_context_new ();
+
+  g_main_context_push_thread_default (priv->handshake_context);
+
   begin_handshake (gnutls);
-  g_task_run_in_thread_sync (task, handshake_thread);
+
+  task = g_task_new (conn, cancellable, sync_handshake_thread_completed, NULL);
+  g_task_set_source_tag (task, g_tls_connection_gnutls_handshake);
+  g_task_set_return_on_cancel (task, TRUE);
+
+  timeout = g_new0 (gint64, 1);
+  *timeout = -1;  /* blocking */
+  g_task_set_task_data (task, timeout, g_free);
+
+  g_task_run_in_thread (task, handshake_thread);
+  crank_sync_handshake_context (gnutls, cancellable);
+
   success = finish_handshake (gnutls, task, &my_error);
+
+  g_main_context_pop_thread_default (priv->handshake_context);
+  g_clear_pointer (&priv->handshake_context, g_main_context_unref);
   g_object_unref (task);
 
   yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE);
@@ -1274,6 +2280,15 @@ g_tls_connection_gnutls_handshake (GTlsConnection   *conn,
   return success;
 }
 
+static gboolean
+g_tls_connection_gnutls_dtls_handshake (GDtlsConnection       *conn,
+                                        GCancellable          *cancellable,
+                                        GError               **error)
+{
+  return g_tls_connection_gnutls_handshake (G_TLS_CONNECTION (conn),
+                                            cancellable, error);
+}
+
 /* In the async version we use two GTasks; one to run handshake_thread() and
  * then call handshake_thread_completed(), and a second to call the caller's
  * original callback after we call finish_handshake().
@@ -1281,49 +2296,102 @@ g_tls_connection_gnutls_handshake (GTlsConnection   *conn,
 
 static void
 handshake_thread_completed (GObject      *object,
-                           GAsyncResult *result,
-                           gpointer      user_data)
+                            GAsyncResult *result,
+                            gpointer      user_data)
 {
   GTask *caller_task = user_data;
   GTlsConnectionGnutls *gnutls = g_task_get_source_object (caller_task);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   GError *error = NULL;
-  gboolean success;
+  gboolean need_finish_handshake, success;
+
+  g_mutex_lock (&priv->op_mutex);
+  if (priv->need_finish_handshake)
+    {
+      need_finish_handshake = TRUE;
+      priv->need_finish_handshake = FALSE;
+    }
+  else
+    need_finish_handshake = FALSE;
+  g_mutex_unlock (&priv->op_mutex);
+
+  if (need_finish_handshake)
+    {
+      success = finish_handshake (gnutls, G_TASK (result), &error);
+      if (success)
+        g_task_return_boolean (caller_task, TRUE);
+      else
+        g_task_return_error (caller_task, error);
+    }
+  else if (priv->handshake_error)
+    g_task_return_error (caller_task, g_error_copy (priv->handshake_error));
+  else
+    g_task_return_boolean (caller_task, TRUE);
+
+  g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+  g_object_unref (caller_task);
+}
+
+static void
+async_handshake_thread (GTask        *task,
+                        gpointer      object,
+                        gpointer      task_data,
+                        GCancellable *cancellable)
+{
+  GTlsConnectionGnutls *gnutls = object;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  handshake_thread (task, object, task_data, cancellable);
 
-  success = finish_handshake (gnutls, G_TASK (result), &error);
-  yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE);
+  g_mutex_lock (&priv->op_mutex);
+  priv->need_finish_handshake = TRUE;
+  /* yield_op will clear handshaking too, but we don't want the
+   * connection to be briefly "handshaking && need_finish_handshake"
+   * after we unlock the mutex.
+   */
+  priv->handshaking = FALSE;
+  g_mutex_unlock (&priv->op_mutex);
 
-  if (success)
-    g_task_return_boolean (caller_task, TRUE);
-  else
-    g_task_return_error (caller_task, error);
-  g_object_unref (caller_task);
+  yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE);
 }
 
 static void
 g_tls_connection_gnutls_handshake_async (GTlsConnection       *conn,
-                                        int                   io_priority,
-                                        GCancellable         *cancellable,
-                                        GAsyncReadyCallback   callback,
-                                        gpointer              user_data)
+                                         int                   io_priority,
+                                         GCancellable         *cancellable,
+                                         GAsyncReadyCallback   callback,
+                                         gpointer              user_data)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (G_TLS_CONNECTION_GNUTLS (conn));
   GTask *thread_task, *caller_task;
+  gint64 *timeout = NULL;
+
+  g_assert (!priv->handshake_context);
+  priv->handshake_context = g_main_context_ref_thread_default ();
 
   caller_task = g_task_new (conn, cancellable, callback, user_data);
+  g_task_set_source_tag (caller_task, g_tls_connection_gnutls_handshake_async);
   g_task_set_priority (caller_task, io_priority);
 
   begin_handshake (G_TLS_CONNECTION_GNUTLS (conn));
 
   thread_task = g_task_new (conn, cancellable,
-                           handshake_thread_completed, caller_task);
+                            handshake_thread_completed, caller_task);
+  g_task_set_source_tag (thread_task, g_tls_connection_gnutls_handshake_async);
   g_task_set_priority (thread_task, io_priority);
-  g_task_run_in_thread (thread_task, handshake_thread);
+
+  timeout = g_new0 (gint64, 1);
+  *timeout = -1;  /* blocking */
+  g_task_set_task_data (thread_task, timeout, g_free);
+
+  g_task_run_in_thread (thread_task, async_handshake_thread);
   g_object_unref (thread_task);
 }
 
 static gboolean
 g_tls_connection_gnutls_handshake_finish (GTlsConnection       *conn,
-                                         GAsyncResult         *result,
-                                         GError              **error)
+                                          GAsyncResult         *result,
+                                          GError              **error)
 {
   g_return_val_if_fail (g_task_is_valid (result, conn), FALSE);
 
@@ -1331,82 +2399,235 @@ g_tls_connection_gnutls_handshake_finish (GTlsConnection       *conn,
 }
 
 static void
-implicit_handshake_completed (GObject      *object,
-                             GAsyncResult *result,
-                             gpointer      user_data)
+g_tls_connection_gnutls_dtls_handshake_async (GDtlsConnection       *conn,
+                                              int                    io_priority,
+                                              GCancellable          *cancellable,
+                                              GAsyncReadyCallback    callback,
+                                              gpointer               user_data)
 {
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
-
-  g_mutex_lock (&gnutls->priv->op_mutex);
-  gnutls->priv->need_finish_handshake = TRUE;
-  g_mutex_unlock (&gnutls->priv->op_mutex);
+  g_tls_connection_gnutls_handshake_async (G_TLS_CONNECTION (conn), io_priority,
+                                           cancellable, callback, user_data);
+}
 
-  yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE);
+static gboolean
+g_tls_connection_gnutls_dtls_handshake_finish (GDtlsConnection       *conn,
+                                               GAsyncResult          *result,
+                                               GError               **error)
+{
+  return g_tls_connection_gnutls_handshake_finish (G_TLS_CONNECTION (conn),
+                                                   result, error);
 }
 
 static gboolean
 do_implicit_handshake (GTlsConnectionGnutls  *gnutls,
-                      gboolean               blocking,
-                      GCancellable          *cancellable,
-                      GError               **error)
+                       gint64                 timeout,
+                       GCancellable          *cancellable,
+                       GError               **error)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  gint64 *thread_timeout = NULL;
+
   /* We have op_mutex */
 
-  gnutls->priv->implicit_handshake = g_task_new (gnutls, cancellable,
-                                                implicit_handshake_completed,
-                                                NULL);
+  g_assert (priv->handshake_context == NULL);
+  if (timeout != 0)
+    {
+      priv->handshake_context = g_main_context_new ();
+      g_main_context_push_thread_default (priv->handshake_context);
+    }
+  else
+    {
+      priv->handshake_context = g_main_context_ref_thread_default ();
+    }
+
+  g_assert (priv->implicit_handshake == NULL);
+  priv->implicit_handshake = g_task_new (gnutls, cancellable,
+                                         timeout ? sync_handshake_thread_completed : NULL,
+                                         NULL);
+  g_task_set_source_tag (priv->implicit_handshake,
+                         do_implicit_handshake);
+
+  thread_timeout = g_new0 (gint64, 1);
+  g_task_set_task_data (priv->implicit_handshake,
+                        thread_timeout, g_free);
 
   begin_handshake (gnutls);
 
-  if (blocking)
+  if (timeout != 0)
     {
       GError *my_error = NULL;
       gboolean success;
 
-      g_mutex_unlock (&gnutls->priv->op_mutex);
-      g_task_run_in_thread_sync (gnutls->priv->implicit_handshake,
-                                handshake_thread);
+      /* In the blocking case, run the handshake operation synchronously in
+       * another thread, and delegate handling the timeout to that thread; it
+       * should return G_IO_ERROR_TIMED_OUT iff (timeout > 0) and the operation
+       * times out. If (timeout < 0) it should block indefinitely until the
+       * operation is complete or errors. */
+      *thread_timeout = timeout;
+
+      g_mutex_unlock (&priv->op_mutex);
+
+      g_task_set_return_on_cancel (priv->implicit_handshake, TRUE);
+      g_task_run_in_thread (priv->implicit_handshake, handshake_thread);
+
+      crank_sync_handshake_context (gnutls, cancellable);
+
       success = finish_handshake (gnutls,
-                                 gnutls->priv->implicit_handshake,
-                                 &my_error);
-      g_clear_object (&gnutls->priv->implicit_handshake);
+                                  priv->implicit_handshake,
+                                  &my_error);
+
+      g_main_context_pop_thread_default (priv->handshake_context);
+      g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+      g_clear_object (&priv->implicit_handshake);
+
       yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE);
-      g_mutex_lock (&gnutls->priv->op_mutex);
+
+      g_mutex_lock (&priv->op_mutex);
 
       if (my_error)
-       g_propagate_error (error, my_error);
+        g_propagate_error (error, my_error);
       return success;
     }
   else
     {
-      g_task_run_in_thread (gnutls->priv->implicit_handshake,
-                           handshake_thread);
-
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
-                          _("Operation would block"));
-
+      /* In the non-blocking case, start the asynchronous handshake operation
+       * and return EWOULDBLOCK to the caller, who will handle polling for
+       * completion of the handshake and whatever operation they actually cared
+       * about. Run the actual operation as blocking in its thread. */
+      *thread_timeout = -1;  /* blocking */
+
+      g_task_run_in_thread (priv->implicit_handshake,
+                            async_handshake_thread);
+
+      /* Intentionally not translated because this is not a fatal error to be
+       * presented to the user, and to avoid this showing up in profiling. */
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
       return FALSE;
     }
 }
 
 gssize
 g_tls_connection_gnutls_read (GTlsConnectionGnutls  *gnutls,
-                             void                  *buffer,
-                             gsize                  count,
-                             gboolean               blocking,
-                             GCancellable          *cancellable,
-                             GError               **error)
+                              void                  *buffer,
+                              gsize                  count,
+                              gint64                 timeout,
+                              GCancellable          *cancellable,
+                              GError               **error)
+{
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  gssize ret;
+
+  if (priv->app_data_buf && !priv->handshaking)
+    {
+      ret = MIN (count, priv->app_data_buf->len);
+      memcpy (buffer, priv->app_data_buf->data, ret);
+      if (ret == priv->app_data_buf->len)
+        g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
+      else
+        g_byte_array_remove_range (priv->app_data_buf, 0, ret);
+      return ret;
+    }
+
+ again:
+  if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ,
+                 timeout, cancellable, error))
+    return -1;
+
+  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
+  ret = gnutls_record_recv (priv->session, buffer, count);
+  END_GNUTLS_IO (gnutls, G_IO_IN, ret, _("Error reading data from TLS socket"), 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 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)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  guint i;
   gssize ret;
+  gnutls_packet_t packet = { 0, };
+
+  /* Copy data out of the app data buffer first. */
+  if (priv->app_data_buf && !priv->handshaking)
+    {
+      ret = 0;
+
+      for (i = 0; i < num_vectors; i++)
+        {
+          gsize count;
+          GInputVector *vec = &vectors[i];
+
+          count = MIN (vec->size, priv->app_data_buf->len);
+          ret += count;
+
+          memcpy (vec->buffer, priv->app_data_buf->data, count);
+          if (count == priv->app_data_buf->len)
+            g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
+          else
+            g_byte_array_remove_range (priv->app_data_buf, 0, count);
+        }
+
+      return ret;
+    }
 
  again:
   if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ,
-                blocking, cancellable, error))
+                 timeout, cancellable, error))
     return -1;
 
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, blocking, cancellable);
-  ret = gnutls_record_recv (gnutls->priv->session, buffer, count);
-  END_GNUTLS_IO (gnutls, G_IO_IN, ret, _("Error reading data from TLS socket: %s"), error);
+  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
+
+  /* Receive the entire datagram (zero-copy). */
+  ret = gnutls_record_recv_packet (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"), error);
 
   yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ);
 
@@ -1418,25 +2639,176 @@ g_tls_connection_gnutls_read (GTlsConnectionGnutls  *gnutls,
     return -1;
 }
 
+static gint
+g_tls_connection_gnutls_receive_messages (GDatagramBased  *datagram_based,
+                                          GInputMessage   *messages,
+                                          guint            num_messages,
+                                          gint             flags,
+                                          gint64           timeout,
+                                          GCancellable    *cancellable,
+                                          GError         **error)
+{
+  GTlsConnectionGnutls *gnutls;
+  guint i;
+  GError *child_error = NULL;
+
+  gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
+
+  if (flags != G_SOCKET_MSG_NONE)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Receive flags are not supported"));
+      return -1;
+    }
+
+  for (i = 0; i < num_messages && child_error == NULL; i++)
+    {
+      GInputMessage *message = &messages[i];
+      gssize n_bytes_read;
+
+      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;
+      message->flags = G_SOCKET_MSG_NONE;
+      if (message->control_messages != NULL)
+        *message->control_messages = NULL;
+      message->num_control_messages = 0;
+
+      if (n_bytes_read > 0)
+        {
+          message->bytes_received = n_bytes_read;
+        }
+      else if (n_bytes_read == 0)
+        {
+          /* EOS. */
+          break;
+        }
+      else if (i > 0 &&
+               (g_error_matches (child_error,
+                                 G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) ||
+                g_error_matches (child_error,
+                                 G_IO_ERROR, G_IO_ERROR_TIMED_OUT)))
+        {
+          /* Blocked or timed out after receiving some messages successfully. */
+          g_clear_error (&child_error);
+          break;
+        }
+      else
+        {
+          /* Error, including G_IO_ERROR_WOULD_BLOCK or G_IO_ERROR_TIMED_OUT on
+           * the first message; or G_IO_ERROR_CANCELLED at any time. */
+          break;
+        }
+    }
+
+  if (child_error != NULL)
+    {
+      g_propagate_error (error, child_error);
+      return -1;
+    }
+
+  return i;
+}
+
 gssize
 g_tls_connection_gnutls_write (GTlsConnectionGnutls  *gnutls,
-                              const void            *buffer,
-                              gsize                  count,
-                              gboolean               blocking,
-                              GCancellable          *cancellable,
-                              GError               **error)
+                               const void            *buffer,
+                               gsize                  count,
+                               gint64                 timeout,
+                               GCancellable          *cancellable,
+                               GError               **error)
+{
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  gssize ret;
+
+ again:
+  if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE,
+                 timeout, cancellable, error))
+    return -1;
+
+  BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout, cancellable);
+  ret = gnutls_record_send (priv->session, buffer, count);
+  END_GNUTLS_IO (gnutls, G_IO_OUT, ret, _("Error writing data to TLS socket"), error);
+
+  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 gssize
+g_tls_connection_gnutls_write_message (GTlsConnectionGnutls  *gnutls,
+                                       GOutputVector         *vectors,
+                                       guint                  num_vectors,
+                                       gint64                 timeout,
+                                       GCancellable          *cancellable,
+                                       GError               **error)
 {
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
   gssize ret;
+  guint i;
+  gsize total_message_size;
 
  again:
   if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE,
-                blocking, cancellable, error))
+                 timeout, cancellable, error))
     return -1;
 
-  BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, blocking, cancellable);
-  ret = gnutls_record_send (gnutls->priv->session, buffer, count);
-  END_GNUTLS_IO (gnutls, G_IO_OUT, ret, _("Error writing data to TLS socket: %s"), error);
+  /* 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 (priv->base_socket != NULL &&
+      gnutls_dtls_get_data_mtu (priv->session) < total_message_size)
+    {
+      char *message;
+      guint mtu = gnutls_dtls_get_data_mtu (priv->session);
+
+      ret = GNUTLS_E_LARGE_PACKET;
+      message = g_strdup_printf("%s %s",
+                                ngettext ("Message of size %lu byte is too large for DTLS connection",
+                                          "Message of size %lu bytes is too large for DTLS connection", total_message_size),
+                                ngettext ("(maximum is %u byte)", "(maximum is %u bytes)", mtu));
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
+                   message,
+                   total_message_size,
+                   mtu);
+      g_free (message);
+
+      goto done;
+    }
+
+  /* Queue up the data from all the vectors. */
+  gnutls_record_cork (priv->session);
+
+  for (i = 0; i < num_vectors; i++)
+    {
+      ret = gnutls_record_send (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, cancellable);
+  ret = gnutls_record_uncork (priv->session, 0  /* flags */);
+  END_GNUTLS_IO (gnutls, G_IO_OUT, ret, _("Error writing data to TLS socket"), error);
 
+ done:
   yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE);
 
   if (ret >= 0)
@@ -1447,63 +2819,208 @@ g_tls_connection_gnutls_write (GTlsConnectionGnutls  *gnutls,
     return -1;
 }
 
+static gint
+g_tls_connection_gnutls_send_messages (GDatagramBased  *datagram_based,
+                                       GOutputMessage  *messages,
+                                       guint            num_messages,
+                                       gint             flags,
+                                       gint64           timeout,
+                                       GCancellable    *cancellable,
+                                       GError         **error)
+{
+  GTlsConnectionGnutls *gnutls;
+  guint i;
+  GError *child_error = NULL;
+
+  gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
+
+  if (flags != G_SOCKET_MSG_NONE)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Send flags are not supported"));
+      return -1;
+    }
+
+  for (i = 0; i < num_messages && child_error == NULL; i++)
+    {
+      GOutputMessage *message = &messages[i];
+      gssize n_bytes_sent;
+
+      n_bytes_sent = g_tls_connection_gnutls_write_message (gnutls,
+                                                            message->vectors,
+                                                            message->num_vectors,
+                                                            timeout,
+                                                            cancellable,
+                                                            &child_error);
+
+      if (n_bytes_sent >= 0)
+        {
+          message->bytes_sent = n_bytes_sent;
+        }
+      else if (i > 0 &&
+               (g_error_matches (child_error,
+                                 G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) ||
+                g_error_matches (child_error,
+                                 G_IO_ERROR, G_IO_ERROR_TIMED_OUT)))
+        {
+          /* Blocked or timed out after sending some messages successfully. */
+          g_clear_error (&child_error);
+          break;
+        }
+      else
+        {
+          /* Error, including G_IO_ERROR_WOULD_BLOCK or G_IO_ERROR_TIMED_OUT
+           * on the first message; or G_IO_ERROR_CANCELLED at any time. */
+          break;
+        }
+    }
+
+  if (child_error != NULL)
+    {
+      g_propagate_error (error, child_error);
+      return -1;
+    }
+
+  return i;
+}
+
 static GInputStream  *
 g_tls_connection_gnutls_get_input_stream (GIOStream *stream)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
 
-  return gnutls->priv->tls_istream;
+  return priv->tls_istream;
 }
 
 static GOutputStream *
 g_tls_connection_gnutls_get_output_stream (GIOStream *stream)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
 
-  return gnutls->priv->tls_ostream;
+  return priv->tls_ostream;
 }
 
-static gboolean
-g_tls_connection_gnutls_close (GIOStream     *stream,
-                              GCancellable  *cancellable,
-                              GError       **error)
+gboolean
+g_tls_connection_gnutls_close_internal (GIOStream     *stream,
+                                        GTlsDirection  direction,
+                                        gint64         timeout,
+                                        GCancellable  *cancellable,
+                                        GError       **error)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
-  gboolean success;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  GTlsConnectionGnutlsOp op;
+  gboolean success = TRUE;
   int ret = 0;
+  GError *gnutls_error = NULL, *stream_error = NULL;
 
-  if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_CLOSE,
-                TRUE, cancellable, error))
+  /* This can be called from g_io_stream_close(), g_input_stream_close(),
+   * g_output_stream_close() or g_tls_connection_close(). In all cases, we only
+   * do the gnutls_bye() for writing. The difference is how we set the flags on
+   * this class and how the underlying stream is closed.
+   */
+
+  g_return_val_if_fail (direction != G_TLS_DIRECTION_NONE, FALSE);
+
+  if (direction == G_TLS_DIRECTION_BOTH)
+    op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH;
+  else if (direction == G_TLS_DIRECTION_READ)
+    op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ;
+  else
+    op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE;
+
+  if (!claim_op (gnutls, op, timeout, cancellable, error))
     return FALSE;
 
-  if (gnutls->priv->closed)
+  if (priv->ever_handshaked && !priv->write_closed &&
+      direction & G_TLS_DIRECTION_WRITE)
     {
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
-                          _("Connection is already closed"));
-      yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_CLOSE);
-      return FALSE;
+      BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
+      ret = gnutls_bye (priv->session, GNUTLS_SHUT_WR);
+      END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
+                     _("Error performing TLS close"), &gnutls_error);
+
+      priv->write_closed = TRUE;
     }
 
-  if (gnutls->priv->ever_handshaked)
+  if (!priv->read_closed && direction & G_TLS_DIRECTION_READ)
+    priv->read_closed = TRUE;
+
+  /* Close the underlying streams. Do this even if the gnutls_bye() call failed,
+   * as the parent GIOStream will have set its internal closed flag and hence
+   * this implementation will never be called again. */
+  if (priv->base_io_stream != NULL)
     {
-      BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, TRUE, cancellable);
-      ret = gnutls_bye (gnutls->priv->session, GNUTLS_SHUT_WR);
-      END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
-                    _("Error performing TLS close: %s"), error);
+      if (direction == G_TLS_DIRECTION_BOTH)
+        success = g_io_stream_close (priv->base_io_stream,
+                                     cancellable, &stream_error);
+      else if (direction & G_TLS_DIRECTION_READ)
+        success = g_input_stream_close (g_io_stream_get_input_stream (priv->base_io_stream),
+                                        cancellable, &stream_error);
+      else if (direction & G_TLS_DIRECTION_WRITE)
+        success = g_output_stream_close (g_io_stream_get_output_stream (priv->base_io_stream),
+                                         cancellable, &stream_error);
+    }
+  else if (g_tls_connection_gnutls_is_dtls (gnutls))
+    {
+      /* We do not close underlying #GDatagramBaseds. There is no
+       * g_datagram_based_close() method since different datagram-based
+       * protocols vary wildly in how they close. */
+      success = TRUE;
+    }
+  else
+    {
+      g_assert_not_reached ();
     }
 
-  gnutls->priv->closed = TRUE;
+  yield_op (gnutls, op);
 
+  /* Propagate errors. */
   if (ret != 0)
     {
-      yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_CLOSE);
-      return FALSE;
+      g_propagate_error (error, gnutls_error);
+      g_clear_error (&stream_error);
+    }
+  else if (!success)
+    {
+      g_propagate_error (error, stream_error);
+      g_clear_error (&gnutls_error);
     }
 
-  success = g_io_stream_close (gnutls->priv->base_io_stream,
-                              cancellable, error);
-  yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_CLOSE);
-  return success;
+  return success && (ret == 0);
+}
+
+static gboolean
+g_tls_connection_gnutls_close (GIOStream     *stream,
+                               GCancellable  *cancellable,
+                               GError       **error)
+{
+  return g_tls_connection_gnutls_close_internal (stream,
+                                                 G_TLS_DIRECTION_BOTH,
+                                                 -1,  /* blocking */
+                                                 cancellable, error);
+}
+
+static gboolean
+g_tls_connection_gnutls_dtls_shutdown (GDtlsConnection  *conn,
+                                       gboolean          shutdown_read,
+                                       gboolean          shutdown_write,
+                                       GCancellable     *cancellable,
+                                       GError          **error)
+{
+  GTlsDirection direction = G_TLS_DIRECTION_NONE;
+
+  if (shutdown_read)
+    direction |= G_TLS_DIRECTION_READ;
+  if (shutdown_write)
+    direction |= G_TLS_DIRECTION_WRITE;
+
+  return g_tls_connection_gnutls_close_internal (G_IO_STREAM (conn),
+                                                 direction,
+                                                 -1,  /* blocking */
+                                                 cancellable, error);
 }
 
 /* We do async close as synchronous-in-a-thread so we don't need to
@@ -1512,95 +3029,112 @@ g_tls_connection_gnutls_close (GIOStream     *stream,
  */
 static void
 close_thread (GTask        *task,
-             gpointer      object,
-             gpointer      task_data,
-             GCancellable *cancellable)
+              gpointer      object,
+              gpointer      task_data,
+              GCancellable *cancellable)
 {
   GIOStream *stream = object;
+  GTlsDirection direction;
   GError *error = NULL;
 
-  if (!g_tls_connection_gnutls_close (stream, cancellable, &error))
+  direction = GPOINTER_TO_INT (g_task_get_task_data (task));
+
+  if (!g_tls_connection_gnutls_close_internal (stream, direction,
+                                               -1,  /* blocking */
+                                               cancellable, &error))
     g_task_return_error (task, error);
   else
     g_task_return_boolean (task, TRUE);
 }
 
 static void
-g_tls_connection_gnutls_close_async (GIOStream           *stream,
-                                    int                  io_priority,
-                                    GCancellable        *cancellable,
-                                    GAsyncReadyCallback  callback,
-                                    gpointer             user_data)
+g_tls_connection_gnutls_close_internal_async (GIOStream           *stream,
+                                              GTlsDirection        direction,
+                                              int                  io_priority,
+                                              GCancellable        *cancellable,
+                                              GAsyncReadyCallback  callback,
+                                              gpointer             user_data)
 {
   GTask *task;
 
   task = g_task_new (stream, cancellable, callback, user_data);
+  g_task_set_source_tag (task, g_tls_connection_gnutls_close_internal_async);
   g_task_set_priority (task, io_priority);
+  g_task_set_task_data (task, GINT_TO_POINTER (direction), NULL);
   g_task_run_in_thread (task, close_thread);
   g_object_unref (task);
 }
 
+static void
+g_tls_connection_gnutls_close_async (GIOStream           *stream,
+                                     int                  io_priority,
+                                     GCancellable        *cancellable,
+                                     GAsyncReadyCallback  callback,
+                                     gpointer             user_data)
+{
+  g_tls_connection_gnutls_close_internal_async (stream, G_TLS_DIRECTION_BOTH,
+                                                io_priority, cancellable,
+                                                callback, user_data);
+}
+
 static gboolean
 g_tls_connection_gnutls_close_finish (GIOStream           *stream,
-                                     GAsyncResult        *result,
-                                     GError             **error)
+                                      GAsyncResult        *result,
+                                      GError             **error)
 {
   g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
 
   return g_task_propagate_boolean (G_TASK (result), error);
 }
 
-#ifdef HAVE_PKCS11
-
-static P11KitPin*
-on_pin_prompt_callback (const char     *pinfile,
-                        P11KitUri      *pin_uri,
-                        const char     *pin_description,
-                        P11KitPinFlags  pin_flags,
-                        void           *callback_data)
+static void
+g_tls_connection_gnutls_dtls_shutdown_async (GDtlsConnection     *conn,
+                                             gboolean             shutdown_read,
+                                             gboolean             shutdown_write,
+                                             int                  io_priority,
+                                             GCancellable        *cancellable,
+                                             GAsyncReadyCallback  callback,
+                                             gpointer             user_data)
 {
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (callback_data);
-  GTlsInteractionResult result;
-  GTlsPasswordFlags flags = 0;
-  GTlsPassword *password;
-  P11KitPin *pin = NULL;
-  GError *error = NULL;
-
-  if (!gnutls->priv->interaction)
-    return NULL;
+  GTlsDirection direction = G_TLS_DIRECTION_NONE;
 
-  if (pin_flags & P11_KIT_PIN_FLAGS_RETRY)
-    flags |= G_TLS_PASSWORD_RETRY;
-  if (pin_flags & P11_KIT_PIN_FLAGS_MANY_TRIES)
-    flags |= G_TLS_PASSWORD_MANY_TRIES;
-  if (pin_flags & P11_KIT_PIN_FLAGS_FINAL_TRY)
-    flags |= G_TLS_PASSWORD_FINAL_TRY;
+  if (shutdown_read)
+    direction |= G_TLS_DIRECTION_READ;
+  if (shutdown_write)
+    direction |= G_TLS_DIRECTION_WRITE;
 
-  password = g_pkcs11_pin_new (flags, pin_description);
+  g_tls_connection_gnutls_close_internal_async (G_IO_STREAM (conn), direction,
+                                                io_priority, cancellable,
+                                                callback, user_data);
+}
 
-  result = g_tls_interaction_ask_password (gnutls->priv->interaction, password,
-                                           g_cancellable_get_current (), &error);
+static gboolean
+g_tls_connection_gnutls_dtls_shutdown_finish (GDtlsConnection  *conn,
+                                              GAsyncResult     *result,
+                                              GError          **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, conn), FALSE);
 
-  switch (result)
-    {
-    case G_TLS_INTERACTION_FAILED:
-      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-        g_warning ("couldn't ask for password: %s", error->message);
-      pin = NULL;
-      break;
-    case G_TLS_INTERACTION_UNHANDLED:
-      pin = NULL;
-      break;
-    case G_TLS_INTERACTION_HANDLED:
-      pin = g_pkcs11_pin_steal_internal (G_PKCS11_PIN (password));
-      break;
-    }
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
 
-  g_object_unref (password);
-  return pin;
+#if GLIB_CHECK_VERSION(2, 60, 0)
+static void
+g_tls_connection_gnutls_dtls_set_advertised_protocols (GDtlsConnection     *conn,
+                                                       const gchar * const *protocols)
+{
+  g_object_set (conn, "advertised-protocols", protocols, NULL);
 }
 
-#endif /* HAVE_PKCS11 */
+const gchar *
+g_tls_connection_gnutls_dtls_get_negotiated_protocol (GDtlsConnection *conn)
+{
+  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  return priv->negotiated_protocol;
+}
+#endif
 
 static void
 g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
@@ -1609,8 +3143,6 @@ g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
   GTlsConnectionClass *connection_class = G_TLS_CONNECTION_CLASS (klass);
   GIOStreamClass *iostream_class = G_IO_STREAM_CLASS (klass);
 
-  g_type_class_add_private (klass, sizeof (GTlsConnectionGnutlsPrivate));
-
   gobject_class->get_property = g_tls_connection_gnutls_get_property;
   gobject_class->set_property = g_tls_connection_gnutls_set_property;
   gobject_class->finalize     = g_tls_connection_gnutls_finalize;
@@ -1625,7 +3157,9 @@ g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
   iostream_class->close_async       = g_tls_connection_gnutls_close_async;
   iostream_class->close_finish      = g_tls_connection_gnutls_close_finish;
 
+  /* For GTlsConnection and GDtlsConnection: */
   g_object_class_override_property (gobject_class, PROP_BASE_IO_STREAM, "base-io-stream");
+  g_object_class_override_property (gobject_class, PROP_BASE_SOCKET, "base-socket");
   g_object_class_override_property (gobject_class, PROP_REQUIRE_CLOSE_NOTIFY, "require-close-notify");
   g_object_class_override_property (gobject_class, PROP_REHANDSHAKE_MODE, "rehandshake-mode");
   g_object_class_override_property (gobject_class, PROP_USE_SYSTEM_CERTDB, "use-system-certdb");
@@ -1634,6 +3168,10 @@ g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
   g_object_class_override_property (gobject_class, PROP_INTERACTION, "interaction");
   g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE, "peer-certificate");
   g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE_ERRORS, "peer-certificate-errors");
+#if GLIB_CHECK_VERSION(2, 60, 0)
+  g_object_class_override_property (gobject_class, PROP_ADVERTISED_PROTOCOLS, "advertised-protocols");
+  g_object_class_override_property (gobject_class, PROP_NEGOTIATED_PROTOCOL, "negotiated-protocol");
+#endif
 }
 
 static void
@@ -1641,3 +3179,81 @@ g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface)
 {
   iface->init = g_tls_connection_gnutls_initable_init;
 }
+
+static void
+g_tls_connection_gnutls_dtls_connection_iface_init (GDtlsConnectionInterface *iface)
+{
+  iface->handshake = g_tls_connection_gnutls_dtls_handshake;
+  iface->handshake_async = g_tls_connection_gnutls_dtls_handshake_async;
+  iface->handshake_finish = g_tls_connection_gnutls_dtls_handshake_finish;
+  iface->shutdown = g_tls_connection_gnutls_dtls_shutdown;
+  iface->shutdown_async = g_tls_connection_gnutls_dtls_shutdown_async;
+  iface->shutdown_finish = g_tls_connection_gnutls_dtls_shutdown_finish;
+#if GLIB_CHECK_VERSION(2, 60, 0)
+  iface->set_advertised_protocols = g_tls_connection_gnutls_dtls_set_advertised_protocols;
+  iface->get_negotiated_protocol = g_tls_connection_gnutls_dtls_get_negotiated_protocol;
+#endif
+}
+
+static void
+g_tls_connection_gnutls_datagram_based_iface_init (GDatagramBasedInterface *iface)
+{
+  iface->receive_messages = g_tls_connection_gnutls_receive_messages;
+  iface->send_messages = g_tls_connection_gnutls_send_messages;
+  iface->create_source = g_tls_connection_gnutls_dtls_create_source;
+  iface->condition_check = g_tls_connection_gnutls_condition_check;
+  iface->condition_wait = g_tls_connection_gnutls_condition_wait;
+}
+
+gboolean
+g_tls_connection_gnutls_request_certificate (GTlsConnectionGnutls  *gnutls,
+                                             GError               **error)
+{
+  GTlsInteractionResult res = G_TLS_INTERACTION_UNHANDLED;
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  GTlsInteraction *interaction;
+  GTlsConnection *conn;
+
+  g_return_val_if_fail (G_IS_TLS_CONNECTION_GNUTLS (gnutls), FALSE);
+
+  conn = G_TLS_CONNECTION (gnutls);
+
+  interaction = g_tls_connection_get_interaction (conn);
+  if (!interaction)
+    return FALSE;
+
+  res = g_tls_interaction_invoke_request_certificate (interaction, conn, 0,
+                                                      priv->read_cancellable, error);
+  return res != G_TLS_INTERACTION_FAILED;
+}
+
+void
+GTLS_DEBUG (gpointer    gnutls,
+            const char *message,
+            ...)
+{
+  char *result = NULL;
+  int ret;
+
+  g_assert (G_IS_TLS_CONNECTION (gnutls));
+
+  va_list args;
+  va_start (args, message);
+
+  ret = g_vasprintf (&result, message, args);
+  g_assert (ret > 0);
+
+  if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
+    g_printf ("CLIENT %p: ", gnutls);
+  else if (G_IS_TLS_SERVER_CONNECTION (gnutls))
+    g_printf ("SERVER %p: ", gnutls);
+  else
+    g_assert_not_reached ();
+
+  g_printf ("%s\n", result);
+
+  fflush (stdout);
+
+  g_free (result);
+  va_end (args);
+}