resetting manifest requested domain to floor
[platform/upstream/openconnect.git] / dtls.c
diff --git a/dtls.c b/dtls.c
index 097cd47..9c21a3f 100644 (file)
--- a/dtls.c
+++ b/dtls.c
@@ -1,7 +1,7 @@
 /*
  * OpenConnect (SSL + DTLS) VPN client
  *
- * Copyright © 2008-2010 Intel Corporation.
+ * Copyright © 2008-2012 Intel Corporation.
  *
  * Author: David Woodhouse <dwmw2@infradead.org>
  *
  *   Boston, MA 02110-1301 USA
  */
 
-#define _BSD_SOURCE
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netdb.h>
 #include <unistd.h>
 #include <netinet/in.h>
-#include <openssl/err.h>
-#include <openssl/ssl.h>
 #include <fcntl.h>
 #include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
 
 #include "openconnect-internal.h"
 
-#ifdef HAVE_DTLS1_STOP_TIMER
-/* OpenSSL doesn't deliberately export this, but we need it to
-   workaround a DTLS bug in versions < 1.0.0e */
-extern void dtls1_stop_timer (SSL *);
-#endif
-
 static unsigned char nybble(unsigned char n)
 {
        if      (n >= '0' && n <= '9') return n - '0';
@@ -55,7 +49,8 @@ unsigned char unhex(const char *data)
        return (nybble(data[0]) << 4) | nybble(data[1]);
 }
 
-#ifdef SSL_OP_CISCO_ANYCONNECT
+#ifdef HAVE_DTLS
+
 #if 0
 /*
  * Useful for catching test cases, where we want everything to be
@@ -110,48 +105,23 @@ int RAND_bytes(char *buf, int len)
  * their clients use anyway.
  */
 
-int connect_dtls_socket(struct openconnect_info *vpninfo)
+#if defined (DTLS_OPENSSL)
+#define DTLS_SEND SSL_write
+#define DTLS_RECV SSL_read
+
+#ifdef HAVE_DTLS1_STOP_TIMER
+/* OpenSSL doesn't deliberately export this, but we need it to
+   workaround a DTLS bug in versions < 1.0.0e */
+extern void dtls1_stop_timer (SSL *);
+#endif
+
+static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
 {
        STACK_OF(SSL_CIPHER) *ciphers;
        method_const SSL_METHOD *dtls_method;
        SSL_CIPHER *dtls_cipher;
        SSL *dtls_ssl;
        BIO *dtls_bio;
-       int dtls_fd;
-
-       if (!vpninfo->dtls_addr) {
-               vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n"));
-               vpninfo->dtls_attempt_period = 0;
-               return -EINVAL;
-       }
-
-       if (!vpninfo->dtls_cipher) {
-               /* We probably didn't offer it any ciphers it liked */
-               vpn_progress(vpninfo, PRG_ERR, _("Server offered no DTLS cipher option\n"));
-               vpninfo->dtls_attempt_period = 0;
-               return -EINVAL;
-       }
-
-       if (vpninfo->proxy) {
-               /* XXX: Theoretically, SOCKS5 proxies can do UDP too */
-               vpn_progress(vpninfo, PRG_ERR, _("No DTLS when connected via proxy\n"));
-               vpninfo->dtls_attempt_period = 0;
-               return -EINVAL;
-       }
-
-       dtls_fd = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
-       if (dtls_fd < 0) {
-               perror(_("Open UDP socket for DTLS:"));
-               return -EINVAL;
-       }
-
-       if (connect(dtls_fd, vpninfo->dtls_addr, vpninfo->peer_addrlen)) {
-               perror(_("UDP (DTLS) connect:\n"));
-               close(dtls_fd);
-               return -EINVAL;
-       }
-
-       fcntl(dtls_fd, F_SETFD, FD_CLOEXEC);
 
        if (!vpninfo->dtls_ctx) {
                dtls_method = DTLSv1_client_method();
@@ -159,6 +129,7 @@ int connect_dtls_socket(struct openconnect_info *vpninfo)
                if (!vpninfo->dtls_ctx) {
                        vpn_progress(vpninfo, PRG_ERR,
                                     _("Initialise DTLSv1 CTX failed\n"));
+                       openconnect_report_ssl_errors(vpninfo);
                        vpninfo->dtls_attempt_period = 0;
                        return -EINVAL;
                }
@@ -230,29 +201,16 @@ int connect_dtls_socket(struct openconnect_info *vpninfo)
                return -EINVAL;
        }
 
-       /* Go Go Go! */
        dtls_bio = BIO_new_socket(dtls_fd, BIO_NOCLOSE);
+       /* Set non-blocking */
+       BIO_set_nbio(dtls_bio, 1);
        SSL_set_bio(dtls_ssl, dtls_bio, dtls_bio);
 
        SSL_set_options(dtls_ssl, SSL_OP_CISCO_ANYCONNECT);
 
-       /* Set non-blocking */
-       BIO_set_nbio(SSL_get_rbio(dtls_ssl), 1);
-       BIO_set_nbio(SSL_get_wbio(dtls_ssl), 1);
-
-       fcntl(dtls_fd, F_SETFL, fcntl(dtls_fd, F_GETFL) | O_NONBLOCK);
-
-       vpninfo->new_dtls_fd = dtls_fd;
        vpninfo->new_dtls_ssl = dtls_ssl;
 
-       if (vpninfo->select_nfds <= dtls_fd)
-               vpninfo->select_nfds = dtls_fd + 1;
-
-       FD_SET(dtls_fd, &vpninfo->select_rfds);
-       FD_SET(dtls_fd, &vpninfo->select_efds);
-
-       time(&vpninfo->new_dtls_started);
-       return dtls_try_handshake(vpninfo);
+       return 0;
 }
 
 int dtls_try_handshake(struct openconnect_info *vpninfo)
@@ -260,7 +218,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        int ret = SSL_do_handshake(vpninfo->new_dtls_ssl);
 
        if (ret == 1) {
-               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection\n"));
+               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using OpenSSL)\n"));
 
                if (vpninfo->dtls_ssl) {
                        /* We are replacing an old connection */
@@ -301,19 +259,39 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
                                     _("Your OpenSSL is older than the one you built against, so DTLS may fail!"));
                }
 #elif defined (HAVE_DTLS1_STOP_TIMER)
+               /*
+                * This works for any normal OpenSSL that supports
+                * Cisco DTLS compatibility (0.9.8m to 1.0.0d inclusive,
+                * and even later versions although it isn't needed there.
+                */
                dtls1_stop_timer(vpninfo->dtls_ssl);
-#else
-               /* Debian restricts visibility of dtls1_stop_timer()
-                  so do it manually. Thankfully this *should* work,
-                  from 0.9.8m to 1.0.0d inclusive, and we don't have
-                  to worry about future changes because we don't do
-                  this for 1.0.0e and above anyway */
+#elif defined (BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT)
+               /*
+                * Debian restricts visibility of dtls1_stop_timer()
+                * so do it manually. This version also works on all
+                * sane versions of OpenSSL:
+                */
                memset (&(vpninfo->dtls_ssl->d1->next_timeout), 0,
                        sizeof((vpninfo->dtls_ssl->d1->next_timeout)));
                vpninfo->dtls_ssl->d1->timeout_duration = 1;
                BIO_ctrl(SSL_get_rbio(vpninfo->dtls_ssl),
                         BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0,
                         &(vpninfo->dtls_ssl->d1->next_timeout));
+#elif defined (BIO_CTRL_DGRAM_SET_TIMEOUT)
+               /*
+                * OK, here it gets more fun... this shoul handle the case
+                * of older OpenSSL which has the Cisco DTLS compatibility
+                * backported, but *not* the fix for RT#1922.
+                */
+               BIO_ctrl(SSL_get_rbio(vpninfo->dtls_ssl),
+                        BIO_CTRL_DGRAM_SET_TIMEOUT, 0, NULL);
+#else
+               /*
+                * And if they don't have any of the above, they probably
+                * don't have RT#1829 fixed either, but that's OK because
+                * that's the "fix" that *introduces* the timeout we're
+                * trying to disable. So do nothing...
+                */
 #endif
                return 0;
        }
@@ -326,7 +304,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        }
 
        vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %d\n"), ret);
-       report_ssl_errors(vpninfo);
+       openconnect_report_ssl_errors(vpninfo);
 
        /* Kill the new (failed) connection... */
        SSL_free(vpninfo->new_dtls_ssl);
@@ -353,10 +331,265 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        return -EINVAL;
 }
 
+#elif defined (DTLS_GNUTLS)
+#include <gnutls/dtls.h>
+
+struct {
+       const char *name;
+       gnutls_cipher_algorithm_t cipher;
+       gnutls_mac_algorithm_t mac;
+       const char *prio;
+} gnutls_dtls_ciphers[] = {
+       { "AES128-SHA", GNUTLS_CIPHER_AES_128_CBC, GNUTLS_MAC_SHA1,
+         "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-128-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION" },
+       { "DES-CBC3-SHA", GNUTLS_CIPHER_3DES_CBC, GNUTLS_MAC_SHA1,
+         "NONE:+VERS-DTLS0.9:+COMP-NULL:+3DES-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION" },
+};
+
+#define DTLS_SEND gnutls_record_send
+#define DTLS_RECV gnutls_record_recv
+static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
+{
+       gnutls_session_t dtls_ssl;
+       gnutls_datum_t master_secret, session_id;
+       int err;
+       int cipher;
+
+       for (cipher = 0; cipher < sizeof(gnutls_dtls_ciphers)/sizeof(gnutls_dtls_ciphers[0]); cipher++) {
+               if (!strcmp(vpninfo->dtls_cipher, gnutls_dtls_ciphers[cipher].name))
+                       goto found_cipher;
+       }
+       vpn_progress(vpninfo, PRG_ERR, _("Unknown DTLS parameters for requested CipherSuite '%s'\n"),
+                    vpninfo->dtls_cipher);
+       vpninfo->dtls_attempt_period = 0;
+
+       return -EINVAL;
+
+ found_cipher:
+       gnutls_init(&dtls_ssl, GNUTLS_CLIENT|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK);
+       err = gnutls_priority_set_direct(dtls_ssl,
+                                        gnutls_dtls_ciphers[cipher].prio,
+                                        NULL);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to set DTLS priority: %s\n"),
+                            gnutls_strerror(err));
+               gnutls_deinit(dtls_ssl);
+               vpninfo->dtls_attempt_period = 0;
+               return -EINVAL;
+       }
+       gnutls_transport_set_ptr(dtls_ssl,
+                                (gnutls_transport_ptr_t)(long) dtls_fd);
+       gnutls_record_disable_padding(dtls_ssl);
+       master_secret.data = vpninfo->dtls_secret;
+       master_secret.size = sizeof(vpninfo->dtls_secret);
+       session_id.data = vpninfo->dtls_session_id;
+       session_id.size = sizeof(vpninfo->dtls_session_id);
+       err = gnutls_session_set_premaster(dtls_ssl, GNUTLS_CLIENT, GNUTLS_DTLS0_9,
+                                          GNUTLS_KX_RSA, gnutls_dtls_ciphers[cipher].cipher,
+                                          gnutls_dtls_ciphers[cipher].mac, GNUTLS_COMP_NULL,
+                                          &master_secret, &session_id);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to set DTLS session parameters: %s\n"),
+                            gnutls_strerror(err));
+               gnutls_deinit(dtls_ssl);
+               vpninfo->dtls_attempt_period = 0;
+               return -EINVAL;
+       }
+
+       vpninfo->new_dtls_ssl = dtls_ssl;
+       return 0;
+}
+
+int dtls_try_handshake(struct openconnect_info *vpninfo)
+{
+       int err = gnutls_handshake(vpninfo->new_dtls_ssl);
+
+       if (!err) {
+#ifdef HAVE_GNUTLS_DTLS_SET_DATA_MTU
+               /* Make sure GnuTLS's idea of the MTU is sufficient to take
+                  a full VPN MTU (with 1-byte header) in a data record. */
+               err = gnutls_dtls_set_data_mtu(vpninfo->new_dtls_ssl, vpninfo->actual_mtu + 1);
+               if (err) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to set DTLS MTU: %s\n"),
+                                    gnutls_strerror(err));
+                       goto error;
+               }
+#else
+               /* If we don't have gnutls_dtls_set_data_mtu() then make sure
+                  we leave enough headroom by adding the worst-case overhead.
+                  We only support AES128-CBC and DES-CBC3-SHA anyway, so
+                  working out the worst case isn't hard. */
+               gnutls_dtls_set_mtu(vpninfo->new_dtls_ssl,
+                                   vpninfo->actual_mtu + 1 /* packet + header */
+                                   + 13 /* DTLS header */
+                                   + 20 /* biggest supported MAC (SHA1) */
+                                   + 16 /* biggest supported IV (AES-128) */
+                                   + 16 /* max padding */);
+#endif
+
+               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using GnuTLS)\n"));
+
+               if (vpninfo->dtls_ssl) {
+                       /* We are replacing an old connection */
+                       gnutls_deinit(vpninfo->dtls_ssl);
+                       close(vpninfo->dtls_fd);
+                       FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
+                       FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
+                       FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
+               }
+               vpninfo->dtls_ssl = vpninfo->new_dtls_ssl;
+               vpninfo->dtls_fd = vpninfo->new_dtls_fd;
+
+               vpninfo->new_dtls_ssl = NULL;
+               vpninfo->new_dtls_fd = -1;
+
+               vpninfo->dtls_times.last_rx = vpninfo->dtls_times.last_tx = time(NULL);
+
+               /* XXX: For OpenSSL we explicitly prevent retransmits here. */
+               return 0;
+       }
+
+       if (err == GNUTLS_E_AGAIN) {
+               if (time(NULL) < vpninfo->new_dtls_started + 5)
+                       return 0;
+               vpn_progress(vpninfo, PRG_TRACE, _("DTLS handshake timed out\n"));
+       }
+
+       vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"),
+                    gnutls_strerror(err));
+
+       goto error;
+ error:
+       /* Kill the new (failed) connection... */
+       gnutls_deinit(vpninfo->new_dtls_ssl);
+       FD_CLR(vpninfo->new_dtls_fd, &vpninfo->select_rfds);
+       FD_CLR(vpninfo->new_dtls_fd, &vpninfo->select_efds);
+       close(vpninfo->new_dtls_fd);
+       vpninfo->new_dtls_ssl = NULL;
+       vpninfo->new_dtls_fd = -1;
+
+       /* ... and kill the old one too. The only time there'll be a valid
+          existing session is when it was a rekey, and in that case it's
+          time for the old one to die. */
+       if (vpninfo->dtls_ssl) {
+               gnutls_deinit(vpninfo->dtls_ssl);
+               close(vpninfo->dtls_fd);
+               FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
+               FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
+               FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
+               vpninfo->dtls_ssl = NULL;
+               vpninfo->dtls_fd = -1;
+       }
+
+       time(&vpninfo->new_dtls_started);
+       return -EINVAL;
+}
+#endif
+
+int connect_dtls_socket(struct openconnect_info *vpninfo)
+{
+       int dtls_fd, ret;
+
+       if (!vpninfo->dtls_addr) {
+               vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n"));
+               vpninfo->dtls_attempt_period = 0;
+               return -EINVAL;
+       }
+
+       if (!vpninfo->dtls_cipher) {
+               /* We probably didn't offer it any ciphers it liked */
+               vpn_progress(vpninfo, PRG_ERR, _("Server offered no DTLS cipher option\n"));
+               vpninfo->dtls_attempt_period = 0;
+               return -EINVAL;
+       }
+
+       if (vpninfo->proxy) {
+               /* XXX: Theoretically, SOCKS5 proxies can do UDP too */
+               vpn_progress(vpninfo, PRG_ERR, _("No DTLS when connected via proxy\n"));
+               vpninfo->dtls_attempt_period = 0;
+               return -EINVAL;
+       }
+
+       dtls_fd = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+       if (dtls_fd < 0) {
+               perror(_("Open UDP socket for DTLS:"));
+               return -EINVAL;
+       }
+
+       if (vpninfo->dtls_local_port) {
+               union {
+                       struct sockaddr_in in;
+                       struct sockaddr_in6 in6;
+               } dtls_bind_addr;
+               int dtls_bind_addrlen;
+               memset(&dtls_bind_addr, 0, sizeof(dtls_bind_addr));
+
+               if (vpninfo->peer_addr->sa_family == AF_INET) {
+                       struct sockaddr_in *addr = &dtls_bind_addr.in;
+                       dtls_bind_addrlen = sizeof(*addr);
+                       addr->sin_family = AF_INET;
+                       addr->sin_addr.s_addr = INADDR_ANY;
+                       addr->sin_port = htons(vpninfo->dtls_local_port);
+               } else if (vpninfo->peer_addr->sa_family == AF_INET6) {
+                       struct sockaddr_in6 *addr = &dtls_bind_addr.in6;
+                       dtls_bind_addrlen = sizeof(*addr);
+                       addr->sin6_family = AF_INET6;
+                       addr->sin6_addr = in6addr_any;
+                       addr->sin6_port = htons(vpninfo->dtls_local_port);
+               } else {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Unknown protocol family %d. Cannot do DTLS\n"),
+                                    vpninfo->peer_addr->sa_family);
+                       vpninfo->dtls_attempt_period = 0;
+                       close(dtls_fd);
+                       return -EINVAL;
+               }
+
+               if (bind(dtls_fd, (struct sockaddr *)&dtls_bind_addr, dtls_bind_addrlen)) {
+                       perror(_("Bind UDP socket for DTLS"));
+                       close(dtls_fd);
+                       return -EINVAL;
+               }
+       }
+
+       if (connect(dtls_fd, vpninfo->dtls_addr, vpninfo->peer_addrlen)) {
+               perror(_("UDP (DTLS) connect:\n"));
+               close(dtls_fd);
+               return -EINVAL;
+       }
+
+       fcntl(dtls_fd, F_SETFD, FD_CLOEXEC);
+       fcntl(dtls_fd, F_SETFL, fcntl(dtls_fd, F_GETFL) | O_NONBLOCK);
+
+       ret = start_dtls_handshake(vpninfo, dtls_fd);
+       if (ret) {
+               close(dtls_fd);
+               return ret;
+       }
+
+       vpninfo->new_dtls_fd = dtls_fd;
+       if (vpninfo->select_nfds <= dtls_fd)
+               vpninfo->select_nfds = dtls_fd + 1;
+
+       FD_SET(dtls_fd, &vpninfo->select_rfds);
+       FD_SET(dtls_fd, &vpninfo->select_efds);
+
+       time(&vpninfo->new_dtls_started);
+
+       return dtls_try_handshake(vpninfo);
+}
+
 static int dtls_restart(struct openconnect_info *vpninfo)
 {
        if (vpninfo->dtls_ssl) {
+#if defined (DTLS_OPENSSL)
                SSL_free(vpninfo->dtls_ssl);
+#elif defined (DTLS_GNUTLS)
+               gnutls_deinit(vpninfo->dtls_ssl);
+#endif
                close(vpninfo->dtls_fd);
                FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
                FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
@@ -374,6 +607,15 @@ int setup_dtls(struct openconnect_info *vpninfo)
        struct vpn_option *dtls_opt = vpninfo->dtls_options;
        int dtls_port = 0;
 
+#if defined (OPENCONNECT_GNUTLS) && defined (DTLS_OPENSSL)
+       /* If we're using GnuTLS for authentication but OpenSSL for DTLS,
+          we'll need to initialise OpenSSL now... */
+       SSL_library_init ();
+       ERR_clear_error ();
+       SSL_load_error_strings ();
+       OpenSSL_add_all_algorithms ();
+#endif
+
        while (dtls_opt) {
                vpn_progress(vpninfo, PRG_TRACE,
                             _("DTLS option %s : %s\n"),
@@ -431,14 +673,29 @@ int setup_dtls(struct openconnect_info *vpninfo)
        return 0;
 }
 
+static struct pkt *dtls_pkt;
+
 int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
 {
-       unsigned char buf[2000];
-       int len;
        int work_done = 0;
        char magic_pkt;
 
-       while ( (len = SSL_read(vpninfo->dtls_ssl, buf, sizeof(buf))) > 0 ) {
+       while (1) {
+               int len = vpninfo->actual_mtu;
+               unsigned char *buf;
+
+               if (!dtls_pkt) {
+                       dtls_pkt = malloc(sizeof(struct pkt) + len);
+                       if (!dtls_pkt) {
+                               vpn_progress(vpninfo, PRG_ERR, "Allocation failed\n");
+                               break;
+                       }
+               }
+
+               buf = dtls_pkt->data - 1;
+               len = DTLS_RECV(vpninfo->dtls_ssl, buf, len + 1);
+               if (len <= 0)
+                       break;
 
                vpn_progress(vpninfo, PRG_TRACE,
                             _("Received DTLS packet 0x%02x of %d bytes\n"),
@@ -448,7 +705,9 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
 
                switch(buf[0]) {
                case AC_PKT_DATA:
-                       queue_new_packet(&vpninfo->incoming_queue, buf+1, len-1);
+                       dtls_pkt->len = len - 1;
+                       queue_packet(&vpninfo->incoming_queue, dtls_pkt);
+                       dtls_pkt = NULL;
                        work_done = 1;
                        break;
 
@@ -457,7 +716,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
 
                        /* FIXME: What if the packet doesn't get through? */
                        magic_pkt = AC_PKT_DPD_RESP;
-                       if (SSL_write(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
+                       if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
                                vpn_progress(vpninfo, PRG_ERR,
                                             _("Failed to send DPD response. Expect disconnect\n"));
                        continue;
@@ -519,7 +778,10 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
                vpn_progress(vpninfo, PRG_TRACE, _("Send DTLS DPD\n"));
 
                magic_pkt = AC_PKT_DPD_OUT;
-               SSL_write(vpninfo->dtls_ssl, &magic_pkt, 1);
+               if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to send DPD request. Expect disconnect\n"));
+
                /* last_dpd will just have been set */
                vpninfo->dtls_times.last_tx = vpninfo->dtls_times.last_dpd;
                work_done = 1;
@@ -534,7 +796,9 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
                vpn_progress(vpninfo, PRG_TRACE, _("Send DTLS Keepalive\n"));
 
                magic_pkt = AC_PKT_KEEPALIVE;
-               SSL_write(vpninfo->dtls_ssl, &magic_pkt, 1);
+               if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to send keepalive request. Expect disconnect\n"));
                time(&vpninfo->dtls_times.last_tx);
                work_done = 1;
                break;
@@ -554,6 +818,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
                /* One byte of header */
                this->hdr[7] = AC_PKT_DATA;
 
+#if defined(DTLS_OPENSSL)
                ret = SSL_write(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
                if (ret <= 0) {
                        ret = SSL_get_error(vpninfo->dtls_ssl, ret);
@@ -564,28 +829,42 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
                                vpn_progress(vpninfo, PRG_ERR,
                                             _("DTLS got write error %d. Falling back to SSL\n"),
                                             ret);
-                               report_ssl_errors(vpninfo);
+                               openconnect_report_ssl_errors(vpninfo);
+                               dtls_restart(vpninfo);
+                               vpninfo->outgoing_queue = this;
+                               vpninfo->outgoing_qlen++;
+                       }
+                       return 1;
+               }
+#elif defined (DTLS_GNUTLS)
+               ret = gnutls_record_send(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
+               if (ret <= 0) {
+                       if (ret != GNUTLS_E_AGAIN) {
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("DTLS got write error: %s. Falling back to SSL\n"),
+                                            gnutls_strerror(ret));
                                dtls_restart(vpninfo);
                                vpninfo->outgoing_queue = this;
                                vpninfo->outgoing_qlen++;
                        }
                        return 1;
                }
+#endif
                time(&vpninfo->dtls_times.last_tx);
                vpn_progress(vpninfo, PRG_TRACE,
-                            _("Sent DTLS packet of %d bytes; SSL_write() returned %d\n"),
+                            _("Sent DTLS packet of %d bytes; DTLS send returned %d\n"),
                             this->len, ret);
                free(this);
        }
 
        return work_done;
 }
-#else /* No DTLS support in OpenSSL */
-#warning Your version of OpenSSL does not seem to support Cisco DTLS compatibility
+#else /* !HAVE_DTLS */
+#warning Your SSL library does not seem to support Cisco DTLS compatibility
  int setup_dtls(struct openconnect_info *vpninfo)
 {
        vpn_progress(vpninfo, PRG_ERR,
-                    _("Built against OpenSSL with no Cisco DTLS support\n"));
+                    _("Built against SSL library with no Cisco DTLS support\n"));
        return -EINVAL;
 }
 #endif