resetting manifest requested domain to floor
[platform/upstream/openconnect.git] / dtls.c
diff --git a/dtls.c b/dtls.c
index be29c95..9c21a3f 100644 (file)
--- a/dtls.c
+++ b/dtls.c
@@ -1,21 +1,25 @@
 /*
- * Open AnyConnect (SSL + DTLS) client
+ * OpenConnect (SSL + DTLS) VPN client
  *
- * © 2008 David Woodhouse <dwmw2@infradead.org>
+ * Copyright © 2008-2012 Intel Corporation.
  *
- * Permission to use, copy, modify, and/or distribute this software
- * for any purpose with or without fee is hereby granted, provided
- * that the above copyright notice and this permission notice appear
- * in all copies.
+ * Author: David Woodhouse <dwmw2@infradead.org>
  *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
- * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
- * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
- * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
- * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to:
+ *
+ *   Free Software Foundation, Inc.
+ *   51 Franklin Street, Fifth Floor,
+ *   Boston, MA 02110-1301 USA
  */
 
 #include <errno.h>
 #include <sys/socket.h>
 #include <netdb.h>
 #include <unistd.h>
-#include <openssl/err.h>
+#include <netinet/in.h>
 #include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "openconnect-internal.h"
+
+static unsigned char nybble(unsigned char n)
+{
+       if      (n >= '0' && n <= '9') return n - '0';
+       else if (n >= 'A' && n <= 'F') return n - ('A' - 10);
+       else if (n >= 'a' && n <= 'f') return n - ('a' - 10);
+       return 0;
+}
 
-#include "anyconnect.h"
+unsigned char unhex(const char *data)
+{
+       return (nybble(data[0]) << 4) | nybble(data[1]);
+}
+
+#ifdef HAVE_DTLS
+
+#if 0
+/*
+ * Useful for catching test cases, where we want everything to be
+ * reproducible.  *NEVER* do this in the wild.
+ */
+time_t time(time_t *t)
+{
+       time_t x = 0x3ab2d948;
+       if (t) *t = x;
+       return x;
+}
+
+int RAND_pseudo_bytes(char *buf, int len)
+{
+       memset(buf, 0x5a, len);
+       printf("FAKE PSEUDO RANDOM!\n");
+       return 1;
+
+}
+int RAND_bytes(char *buf, int len)
+{
+       static int foo = 0x5b;
+       printf("FAKE RANDOM!\n");
+       memset(buf, foo, len);
+       return 1;
+}
+#endif
 
 /*
  * The master-secret is generated randomly by the client. The server
@@ -39,8 +90,8 @@
  * was in OpenSSL 0.9.8e -- it includes backports of some later
  * OpenSSL patches.
  *
- * The openssl/ directory of this source tree should contain both a 
- * small patch against OpenSSL 0.9.8e to make it support Cisco's 
+ * The openssl/ directory of this source tree should contain both a
+ * small patch against OpenSSL 0.9.8e to make it support Cisco's
  * snapshot of the protocol, and a larger patch against newer OpenSSL
  * which gives us an option to use the old protocol again.
  *
  * number seen in the ClientHello. But although I can make the handshake
  * complete by hacking tls1_mac() to use the _old_ protocol version
  * number when calculating the MAC, the server still seems to be ignoring
- * my subsequent data packets.
- */   
+ * my subsequent data packets. So we use the old protocol, which is what
+ * their clients use anyway.
+ */
 
-static unsigned char nybble(unsigned char n)
-{
-       if      (n >= '0' && n <= '9') return n - '0';
-       else if (n >= 'A' && n <= 'F') return n - ('A' - 10);
-       else if (n >= 'a' && n <= 'f') return n - ('a' - 10);
-       return 0;
-}
+#if defined (DTLS_OPENSSL)
+#define DTLS_SEND SSL_write
+#define DTLS_RECV SSL_read
 
-static unsigned char hex(const char *data)
-{
-       return (nybble(data[0]) << 4) | nybble(data[1]);
-}
+#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 connect_dtls_socket(struct anyconnect_info *vpninfo, SSL **ret_ssl,
-                              int *ret_fd)
+static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
 {
-       SSL_METHOD *dtls_method;
-       SSL_CTX *dtls_ctx;
-       SSL_SESSION *dtls_session;
-       SSL_CIPHER *https_cipher;
+       STACK_OF(SSL_CIPHER) *ciphers;
+       method_const SSL_METHOD *dtls_method;
+       SSL_CIPHER *dtls_cipher;
        SSL *dtls_ssl;
        BIO *dtls_bio;
-       int dtls_fd;
-       int ret;
 
-       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_ctx) {
+               dtls_method = DTLSv1_client_method();
+               vpninfo->dtls_ctx = SSL_CTX_new(dtls_method);
+               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;
+               }
+
+               /* If we don't readahead, then we do short reads and throw
+                  away the tail of data packets. */
+               SSL_CTX_set_read_ahead(vpninfo->dtls_ctx, 1);
+
+               if (!SSL_CTX_set_cipher_list(vpninfo->dtls_ctx, vpninfo->dtls_cipher)) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Set DTLS cipher list failed\n"));
+                       SSL_CTX_free(vpninfo->dtls_ctx);
+                       vpninfo->dtls_ctx = NULL;
+                       vpninfo->dtls_attempt_period = 0;
+                       return -EINVAL;
+               }
        }
-       
-       if (connect(dtls_fd, vpninfo->peer_addr, vpninfo->peer_addrlen)) {
-               perror("UDP (DTLS) connect:\n");
-               close(dtls_fd);
-               return -EINVAL;
+
+       if (!vpninfo->dtls_session) {
+               /* We're going to "resume" a session which never existed. Fake it... */
+               vpninfo->dtls_session = SSL_SESSION_new();
+               if (!vpninfo->dtls_session) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Initialise DTLSv1 session failed\n"));
+                       vpninfo->dtls_attempt_period = 0;
+                       return -EINVAL;
+               }
+               vpninfo->dtls_session->ssl_version = 0x0100; /* DTLS1_BAD_VER */
        }
 
-       fcntl(dtls_fd, F_SETFD, FD_CLOEXEC);
+       /* Do this every time; it may have changed due to a rekey */
+       vpninfo->dtls_session->master_key_length = sizeof(vpninfo->dtls_secret);
+       memcpy(vpninfo->dtls_session->master_key, vpninfo->dtls_secret,
+              sizeof(vpninfo->dtls_secret));
 
-       dtls_method = DTLSv1_client_method();
-       dtls_ctx = SSL_CTX_new(dtls_method);
-       SSL_CTX_set_read_ahead(dtls_ctx, 1);
-       https_cipher = SSL_get_current_cipher(vpninfo->https_ssl);
+       vpninfo->dtls_session->session_id_length = sizeof(vpninfo->dtls_session_id);
+       memcpy(vpninfo->dtls_session->session_id, vpninfo->dtls_session_id,
+              sizeof(vpninfo->dtls_session_id));
 
-       dtls_ssl = SSL_new(dtls_ctx);
+       dtls_ssl = SSL_new(vpninfo->dtls_ctx);
        SSL_set_connect_state(dtls_ssl);
-       SSL_set_cipher_list(dtls_ssl, SSL_CIPHER_get_name(https_cipher));
 
-       if (verbose)
-               printf("SSL_SESSION is %zd bytes\n", sizeof(*dtls_session));
-       /* We're going to "resume" a session which never existed. Fake it... */
-       dtls_session = SSL_SESSION_new();
+       ciphers = SSL_get_ciphers(dtls_ssl);
+       if (sk_SSL_CIPHER_num(ciphers) != 1) {
+               vpn_progress(vpninfo, PRG_ERR, _("Not precisely one DTLS cipher\n"));
+               SSL_CTX_free(vpninfo->dtls_ctx);
+               SSL_free(dtls_ssl);
+               SSL_SESSION_free(vpninfo->dtls_session);
+               vpninfo->dtls_ctx = NULL;
+               vpninfo->dtls_session = NULL;
+               vpninfo->dtls_attempt_period = 0;
+               return -EINVAL;
+       }
+       dtls_cipher = sk_SSL_CIPHER_value(ciphers, 0);
+
+       /* Set the appropriate cipher on our session to be resumed */
+       vpninfo->dtls_session->cipher = dtls_cipher;
+       vpninfo->dtls_session->cipher_id = dtls_cipher->id;
+
+       /* Add the generated session to the SSL */
+       if (!SSL_set_session(dtls_ssl, vpninfo->dtls_session)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("SSL_set_session() failed with old protocol version 0x%x\n"
+                              "Are you using a version of OpenSSL older than 0.9.8m?\n"
+                              "See http://rt.openssl.org/Ticket/Display.html?id=1751\n"
+                              "Use the --no-dtls command line option to avoid this message\n"),
+                            vpninfo->dtls_session->ssl_version);
+               vpninfo->dtls_attempt_period = 0;
+               return -EINVAL;
+       }
+
+       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);
 
-       dtls_session->ssl_version = 0x0100; // DTLS1_BAD_VER
+       SSL_set_options(dtls_ssl, SSL_OP_CISCO_ANYCONNECT);
 
-       dtls_session->master_key_length = sizeof(vpninfo->dtls_secret);
-       memcpy(dtls_session->master_key, vpninfo->dtls_secret,
-              sizeof(vpninfo->dtls_secret));
+       vpninfo->new_dtls_ssl = dtls_ssl;
 
-       dtls_session->session_id_length = sizeof(vpninfo->dtls_session_id);
-       memcpy(dtls_session->session_id, vpninfo->dtls_session_id,
-              sizeof(vpninfo->dtls_session_id));
+       return 0;
+}
 
-       dtls_session->cipher = https_cipher;
-       dtls_session->cipher_id = https_cipher->id;
+int dtls_try_handshake(struct openconnect_info *vpninfo)
+{
+       int ret = SSL_do_handshake(vpninfo->new_dtls_ssl);
 
-       /* Having faked a session, add it to the CTX and the SSL */
-       if (!SSL_set_session(dtls_ssl, dtls_session)) {
-               printf("SSL_set_session() failed with old protocol version 0x%x\n", dtls_session->ssl_version);
-               printf("Trying the official version %x\n", 0xfeff);
-               dtls_session->ssl_version = 0xfeff;
-               if (!SSL_set_session(dtls_ssl, dtls_session)) {
-                       printf("SSL_set_session() failed still. Is your build ABI-compatible with your libssl?\n");
-                       return -EINVAL;
+       if (ret == 1) {
+               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using OpenSSL)\n"));
+
+               if (vpninfo->dtls_ssl) {
+                       /* We are replacing an old connection */
+                       SSL_free(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);
+
+               /* From about 8.4.1(11) onwards, the ASA seems to get
+                  very unhappy if we resend ChangeCipherSpec messages
+                  after the initial setup. This was "fixed" in OpenSSL
+                  1.0.0e for RT#2505, but it's not clear if that was
+                  the right fix. What happens if the original packet
+                  *does* get lost? Surely we *wanted* the retransmits,
+                  because without them the server will never be able
+                  to decrypt anything we send?
+                  Oh well, our retransmitted packets upset the server
+                  because we don't get the Cisco-compatibility right
+                  (this is one of the areas in which Cisco's DTLS differs
+                  from the RFC4347 spec), and DPD should help us notice
+                  if *nothing* is getting through. */
+#if OPENSSL_VERSION_NUMBER >= 0x1000005fL
+               /* OpenSSL 1.0.0e or above doesn't resend anyway; do nothing.
+                  However, if we were *built* against 1.0.0e or newer, but at
+                  runtime we find that we are being run against an older 
+                  version, warn about it. */
+               if (SSLeay() < 0x1000005fL) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("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);
+#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;
        }
-       if (!SSL_CTX_add_session(dtls_ctx, dtls_session))
-               printf("SSL_CTX_add_session() failed\n");
 
+       ret = SSL_get_error(vpninfo->new_dtls_ssl, ret);
+       if (ret == SSL_ERROR_WANT_WRITE || ret == SSL_ERROR_WANT_READ) {
+               if (time(NULL) < vpninfo->new_dtls_started + 5)
+                       return 0;
+               vpn_progress(vpninfo, PRG_TRACE, _("DTLS handshake timed out\n"));
+       }
 
-       /* Go Go Go! */
-       dtls_bio = BIO_new_socket(dtls_fd, BIO_NOCLOSE);
-       SSL_set_bio(dtls_ssl, dtls_bio, dtls_bio);
+       vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %d\n"), ret);
+       openconnect_report_ssl_errors(vpninfo);
+
+       /* Kill the new (failed) connection... */
+       SSL_free(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) {
+               SSL_free(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;
+       }
 
-#ifndef SSL_OP_CISCO_ANYCONNECT
-#define SSL_OP_CISCO_ANYCONNECT 0x8000
-#endif
-       SSL_set_options(dtls_ssl, SSL_OP_CISCO_ANYCONNECT);
-       ret = SSL_do_handshake(dtls_ssl);
-       
-       if (ret != 1) {
-               fprintf(stderr, "DTLS connection returned %d\n", ret);
-               if (ret < 0)
-                       fprintf(stderr, "DTLS handshake error: %d\n", SSL_get_error(dtls_ssl, ret));
-               ERR_print_errors_fp(stderr);
-               SSL_free(dtls_ssl);
-               SSL_CTX_free(dtls_ctx);
-               close(dtls_fd);
+       time(&vpninfo->new_dtls_started);
+       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;
        }
 
-       BIO_set_nbio(SSL_get_rbio(dtls_ssl),1);
-       BIO_set_nbio(SSL_get_wbio(dtls_ssl),1);
+       vpninfo->new_dtls_ssl = dtls_ssl;
+       return 0;
+}
 
-       fcntl(dtls_fd, F_SETFL, fcntl(dtls_fd, F_GETFL) | O_NONBLOCK);
+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
 
-       *ret_fd = dtls_fd;
-       *ret_ssl = dtls_ssl;
+               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using GnuTLS)\n"));
 
-       return 0;
+               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
 
-static int dtls_rekey(struct anyconnect_info *vpninfo)
+int connect_dtls_socket(struct openconnect_info *vpninfo)
 {
-       SSL *dtls_ssl;
-       int dtls_fd;
+       int dtls_fd, ret;
+
+       if (!vpninfo->dtls_addr) {
+               vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n"));
+               vpninfo->dtls_attempt_period = 0;
+               return -EINVAL;
+       }
 
-       /* To rekey, we just 'resume' the session again */
-       if (connect_dtls_socket(vpninfo, &dtls_ssl, &dtls_fd))
+       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;
+       }
 
-       vpninfo->pfds[vpninfo->dtls_pfd].fd = dtls_fd;
+       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;
+       }
 
-       SSL_free(vpninfo->dtls_ssl);
-       close(vpninfo->dtls_fd);
+       dtls_fd = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+       if (dtls_fd < 0) {
+               perror(_("Open UDP socket for DTLS:"));
+               return -EINVAL;
+       }
 
-       vpninfo->dtls_ssl = dtls_ssl;
-       vpninfo->dtls_fd = dtls_fd;
+       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;
+               }
 
-       return 0;
+               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);
+               FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
+               vpninfo->dtls_ssl = NULL;
+               vpninfo->dtls_fd = -1;
+       }
+
+       return connect_dtls_socket(vpninfo);
 }
 
-int setup_dtls(struct anyconnect_info *vpninfo)
+
+int setup_dtls(struct openconnect_info *vpninfo)
 {
        struct vpn_option *dtls_opt = vpninfo->dtls_options;
-       int sessid_found = 0;
        int dtls_port = 0;
-       int i;
+
+#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) {
-               if (verbose)
-                       printf("DTLS option %s : %s\n", dtls_opt->option, dtls_opt->value);
-
-               if (!strcmp(dtls_opt->option, "X-DTLS-Session-ID")) {
-                       if (strlen(dtls_opt->value) != 64) {
-                               fprintf(stderr, "X-DTLS-Session-ID not 64 characters\n");
-                               fprintf(stderr, "Is: %s\n", dtls_opt->value);
-                               return -EINVAL;
-                       }
-                       for (i = 0; i < 64; i += 2)
-                               vpninfo->dtls_session_id[i/2] = hex(dtls_opt->value + i);
-                       sessid_found = 1;
-               } else if (!strcmp(dtls_opt->option + 7, "Port")) {
+               vpn_progress(vpninfo, PRG_TRACE,
+                            _("DTLS option %s : %s\n"),
+                            dtls_opt->option, dtls_opt->value);
+
+               if (!strcmp(dtls_opt->option + 7, "Port")) {
                        dtls_port = atol(dtls_opt->value);
                } else if (!strcmp(dtls_opt->option + 7, "Keepalive")) {
-                       vpninfo->dtls_keepalive = atol(dtls_opt->value);
+                       vpninfo->dtls_times.keepalive = atol(dtls_opt->value);
                } else if (!strcmp(dtls_opt->option + 7, "DPD")) {
-                       vpninfo->dtls_dpd = atol(dtls_opt->value);
+                       int j = atol(dtls_opt->value);
+                       if (j && (!vpninfo->dtls_times.dpd || j < vpninfo->dtls_times.dpd))
+                               vpninfo->dtls_times.dpd = j;
                } else if (!strcmp(dtls_opt->option + 7, "Rekey-Time")) {
-                       vpninfo->dtls_rekey = atol(dtls_opt->value);
+                       vpninfo->dtls_times.rekey = atol(dtls_opt->value);
+               } else if (!strcmp(dtls_opt->option + 7, "CipherSuite")) {
+                       vpninfo->dtls_cipher = strdup(dtls_opt->value);
                }
-                       
+
                dtls_opt = dtls_opt->next;
        }
-       if (!sessid_found || !dtls_port)
+       if (!dtls_port) {
+               vpninfo->dtls_attempt_period = 0;
                return -EINVAL;
+       }
+
+       vpninfo->dtls_addr = malloc(vpninfo->peer_addrlen);
+       if (!vpninfo->dtls_addr) {
+               vpninfo->dtls_attempt_period = 0;
+               return -ENOMEM;
+       }
+       memcpy(vpninfo->dtls_addr, vpninfo->peer_addr, vpninfo->peer_addrlen);
 
        if (vpninfo->peer_addr->sa_family == AF_INET) {
-               struct sockaddr_in *sin = (void *)vpninfo->peer_addr;
+               struct sockaddr_in *sin = (void *)vpninfo->dtls_addr;
                sin->sin_port = htons(dtls_port);
        } else if (vpninfo->peer_addr->sa_family == AF_INET6) {
-               struct sockaddr_in6 *sin = (void *)vpninfo->peer_addr;
+               struct sockaddr_in6 *sin = (void *)vpninfo->dtls_addr;
                sin->sin6_port = htons(dtls_port);
        } else {
-               fprintf(stderr, "Unknown protocol family %d. Cannot do DTLS\n",
-                       vpninfo->peer_addr->sa_family);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unknown protocol family %d. Cannot do DTLS\n"),
+                            vpninfo->peer_addr->sa_family);
+               vpninfo->dtls_attempt_period = 0;
                return -EINVAL;
        }
 
-       if (connect_dtls_socket(vpninfo, &vpninfo->dtls_ssl, &vpninfo->dtls_fd))
+       if (connect_dtls_socket(vpninfo))
                return -EINVAL;
 
-       vpninfo->dtls_pfd = vpn_add_pollfd(vpninfo, vpninfo->dtls_fd,
-                                          POLLIN|POLLHUP|POLLERR);
-       vpninfo->last_dtls_rekey = vpninfo->last_dtls_rx =
-               vpninfo->last_dtls_tx = time(NULL);
-
-       if (verbose)
-               printf("DTLS connected. DPD %d, Keepalive %d\n",
-                      vpninfo->dtls_dpd, vpninfo->dtls_keepalive);
+       vpn_progress(vpninfo, PRG_TRACE,
+                    _("DTLS connected. DPD %d, Keepalive %d\n"),
+                    vpninfo->dtls_times.dpd, vpninfo->dtls_times.keepalive);
 
        return 0;
 }
 
-int dtls_mainloop(struct anyconnect_info *vpninfo, int *timeout)
+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 ) {
-               if (verbose)
-                       printf("Received DTLS packet 0x%02x of %d bytes\n",
-                              len, buf[0]);
+       while (1) {
+               int len = vpninfo->actual_mtu;
+               unsigned char *buf;
 
-               vpninfo->last_dtls_rx = time(NULL);
+               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"),
+                            buf[0], len);
+
+               vpninfo->dtls_times.last_rx = time(NULL);
 
                switch(buf[0]) {
-               case 0:
-                       queue_new_packet(&vpninfo->incoming_queue, AF_INET, buf+1, len-1);
+               case AC_PKT_DATA:
+                       dtls_pkt->len = len - 1;
+                       queue_packet(&vpninfo->incoming_queue, dtls_pkt);
+                       dtls_pkt = NULL;
                        work_done = 1;
                        break;
 
-               case 4: /* DPD response */
-                       if (verbose)
-                               printf("Got DTLS DPD response\n");
+               case AC_PKT_DPD_OUT:
+                       vpn_progress(vpninfo, PRG_TRACE, _("Got DTLS DPD request\n"));
+
+                       /* FIXME: What if the packet doesn't get through? */
+                       magic_pkt = AC_PKT_DPD_RESP;
+                       if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Failed to send DPD response. Expect disconnect\n"));
+                       continue;
+
+               case AC_PKT_DPD_RESP:
+                       vpn_progress(vpninfo, PRG_TRACE, _("Got DTLS DPD response\n"));
+                       break;
+
+               case AC_PKT_KEEPALIVE:
+                       vpn_progress(vpninfo, PRG_TRACE, _("Got DTLS Keepalive\n"));
                        break;
 
                default:
-                       fprintf(stderr, "Unknown DTLS packet type %02x\n", buf[0]);
-                       vpninfo->quit_reason = "Unknown packet received";
-                       return 1;
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Unknown DTLS packet type %02x, len %d\n"),
+                                    buf[0], len);
+                       if (1) {
+                               /* Some versions of OpenSSL have bugs with receiving out-of-order
+                                * packets. Not only do they wrongly decide to drop packets if
+                                * two packets get swapped in transit, but they also _fail_ to
+                                * drop the packet in non-blocking mode; instead they return
+                                * the appropriate length of garbage. So don't abort... for now. */
+                               break;
+                       } else {
+                               vpninfo->quit_reason = "Unknown packet received";
+                               return 1;
+                       }
+
                }
        }
-       while (vpninfo->outgoing_queue) {
-               struct pkt *this = vpninfo->outgoing_queue;
-               int ret;
 
-               vpninfo->outgoing_queue = this->next;
+       switch (keepalive_action(&vpninfo->dtls_times, timeout)) {
+       case KA_REKEY:
+               vpn_progress(vpninfo, PRG_INFO, _("DTLS rekey due\n"));
 
-               buf[0] = 0;
-               memcpy(buf + 1, this->data, this->len);
-               
-               ret = SSL_write(vpninfo->dtls_ssl, buf, this->len + 1);
-               vpninfo->last_dtls_tx = time(NULL);
-               if (verbose) {
-                       printf("Sent DTLS packet of %d bytes; SSL_write() returned %d\n",
-                              this->len, ret);
+               /* There ought to be a method of rekeying DTLS without tearing down
+                  the CSTP session and restarting, but we don't (yet) know it */
+               if (cstp_reconnect(vpninfo)) {
+                       vpn_progress(vpninfo, PRG_ERR, _("Reconnect failed\n"));
+                       vpninfo->quit_reason = "CSTP reconnect failed";
+                       return 1;
                }
-       }
 
-       /* DPD is bidirectional -- PKT 3 out, PKT 4 back */
-       if (vpninfo->dtls_dpd) {
-               time_t now = time(NULL);
-               time_t due = vpninfo->last_dtls_rx + vpninfo->dtls_dpd;
-               time_t overdue = vpninfo->last_dtls_rx + (5 * vpninfo->dtls_dpd);
-
-               /* If we already have DPD outstanding, don't flood */
-               if (vpninfo->last_dtls_dpd > vpninfo->last_dtls_rx)
-                       due = vpninfo->last_dtls_dpd + vpninfo->dtls_dpd;
-                       
-               if (now > overdue) {
-                       fprintf(stderr, "DTLS Dead Peer Detection detected dead peer!\n");
-                       /* Fall back to SSL */
-                       SSL_free(vpninfo->dtls_ssl);
-                       close(vpninfo->dtls_fd);
-                       vpninfo->dtls_ssl = NULL;
-                       vpninfo->dtls_fd = -1;
+               if (dtls_restart(vpninfo)) {
+                       vpn_progress(vpninfo, PRG_ERR, _("DTLS rekey failed\n"));
                        return 1;
                }
-               if (now >= due) {
-                       static unsigned char dtls_dpd_pkt[1] = { 3 };
-                       /* Haven't heard anything from the other end for a while.
-                          Check if it's still there */
-                       /* FIXME: If isn't, we should act on that */
-                       SSL_write(vpninfo->dtls_ssl, dtls_dpd_pkt, 1);
-                       vpninfo->last_dtls_tx = now;
-
-                       due = now + vpninfo->dtls_dpd;
-                       if (verbose)
-                               printf("Sent DTLS DPD\n");
-               }
+               work_done = 1;
+               break;
 
-               if (verbose)
-                       printf("Next DTLS DPD due in %ld seconds\n", (due - now));
-               if (*timeout > (due - now) * 1000)
-                       *timeout = (due - now) * 1000;
-       }
 
-       /* Keepalive is just client -> server */
-       if (vpninfo->dtls_keepalive) {
-               time_t now = time(NULL);
-               time_t due = vpninfo->last_dtls_tx + vpninfo->dtls_keepalive;
+       case KA_DPD_DEAD:
+               vpn_progress(vpninfo, PRG_ERR, _("DTLS Dead Peer Detection detected dead peer!\n"));
+               /* Fall back to SSL, and start a new DTLS connection */
+               dtls_restart(vpninfo);
+               return 1;
 
-               if (now >= due) {
-                       static unsigned char dtls_keepalive_pkt[1] = { 7 };
+       case KA_DPD:
+               vpn_progress(vpninfo, PRG_TRACE, _("Send DTLS DPD\n"));
 
-                       /* Send something (which is discarded), to keep
-                          the connection alive. */
-                       SSL_write(vpninfo->dtls_ssl, dtls_keepalive_pkt, 1);
-                       vpninfo->last_dtls_tx = now;
+               magic_pkt = AC_PKT_DPD_OUT;
+               if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to send DPD request. Expect disconnect\n"));
 
-                       due = now + vpninfo->dtls_keepalive;
-                       if (verbose)
-                               printf("Sent DTLS Keepalive\n");
-               }
+               /* last_dpd will just have been set */
+               vpninfo->dtls_times.last_tx = vpninfo->dtls_times.last_dpd;
+               work_done = 1;
+               break;
 
-               if (verbose)
-                       printf("Next DTLS Keepalive due in %ld seconds\n", (due - now));
-               if (*timeout > (due - now) * 1000)
-                       *timeout = (due - now) * 1000;
+       case KA_KEEPALIVE:
+               /* No need to send an explicit keepalive
+                  if we have real data to send */
+               if (vpninfo->outgoing_queue)
+                       break;
+
+               vpn_progress(vpninfo, PRG_TRACE, _("Send DTLS Keepalive\n"));
+
+               magic_pkt = AC_PKT_KEEPALIVE;
+               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;
+
+       case KA_NONE:
+               ;
        }
 
-       if (vpninfo->dtls_rekey) {
-               time_t now = time(NULL);
-               time_t due = vpninfo->last_dtls_rekey + vpninfo->dtls_rekey;
-
-               if (now >= due) {
-                       if (verbose)
-                               printf("DTLS rekey due\n");
-                       if (dtls_rekey(vpninfo)) {
-                               fprintf(stderr, "DTLS rekey failed\n");
-                               /* Fall back to SSL */
-                               SSL_free(vpninfo->dtls_ssl);
-                               close(vpninfo->dtls_fd);
-                               vpninfo->dtls_ssl = NULL;
-                               vpninfo->dtls_fd = -1;
-                               return 1;
+       /* Service outgoing packet queue */
+       while (vpninfo->outgoing_queue) {
+               struct pkt *this = vpninfo->outgoing_queue;
+               int ret;
+
+               vpninfo->outgoing_queue = this->next;
+               vpninfo->outgoing_qlen--;
+
+               /* 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);
+
+                       /* If it's a real error, kill the DTLS connection and
+                          requeue the packet to be sent over SSL */
+                       if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE) {
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("DTLS got write error %d. Falling back to SSL\n"),
+                                            ret);
+                               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++;
                        }
-                       vpninfo->last_dtls_rekey = time(NULL);
-                       due = vpninfo->last_dtls_rekey + vpninfo->dtls_rekey;
+                       return 1;
                }
-               if (verbose)
-                       printf("Next DTLS rekey due in %ld seconds\n", (due - now));
-               if (*timeout > (due - now) * 1000)
-                       *timeout = (due - now) * 1000;
+#endif
+               time(&vpninfo->dtls_times.last_tx);
+               vpn_progress(vpninfo, PRG_TRACE,
+                            _("Sent DTLS packet of %d bytes; DTLS send returned %d\n"),
+                            this->len, ret);
+               free(this);
        }
 
        return work_done;
 }
-
+#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 SSL library with no Cisco DTLS support\n"));
+       return -EINVAL;
+}
+#endif