From 45f9c27486b92b71ae4fb591969478aa64a99805 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Wed, 14 Apr 2021 11:22:39 +0100 Subject: [PATCH] Fix timeout handling for DTLS handshake retries We weren't actually waking the mainloop up when the TLS library wanted to (re)send a DTLS handshake. Plumb the timeout pointer through dtls_try_handshake() to fix that. Also feed it to dtls_reconnect(), which helps us ensure that we wake up after dtls_attempt_period() to try again. Export dtls_reconnect() while we're at it, since PPP will want to be able to invoke it. Signed-off-by: David Woodhouse --- dtls.c | 24 ++++++++++++------------ gnutls-dtls.c | 16 ++++++++++++++-- openconnect-internal.h | 3 ++- openssl-dtls.c | 23 ++++++++++++++++++++--- 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/dtls.c b/dtls.c index d28f0078..fa1bb981 100644 --- a/dtls.c +++ b/dtls.c @@ -96,7 +96,7 @@ char *openconnect_bin2base64(const char *prefix, const uint8_t *data, unsigned l return p; } -static int connect_dtls_socket(struct openconnect_info *vpninfo) +static int connect_dtls_socket(struct openconnect_info *vpninfo, int *timeout) { int dtls_fd, ret; @@ -147,7 +147,7 @@ static int connect_dtls_socket(struct openconnect_info *vpninfo) time(&vpninfo->new_dtls_started); - return dtls_try_handshake(vpninfo); + return dtls_try_handshake(vpninfo, timeout); } void dtls_close(struct openconnect_info *vpninfo) @@ -164,7 +164,7 @@ void dtls_close(struct openconnect_info *vpninfo) vpninfo->dtls_state = DTLS_SLEEPING; } -static int dtls_reconnect(struct openconnect_info *vpninfo) +int dtls_reconnect(struct openconnect_info *vpninfo, int *timeout) { dtls_close(vpninfo); @@ -172,7 +172,7 @@ static int dtls_reconnect(struct openconnect_info *vpninfo) return -EINVAL; vpninfo->dtls_state = DTLS_SLEEPING; - return connect_dtls_socket(vpninfo); + return connect_dtls_socket(vpninfo, timeout); } int dtls_setup(struct openconnect_info *vpninfo) @@ -190,7 +190,7 @@ int dtls_setup(struct openconnect_info *vpninfo) if (vpninfo->dtls_times.rekey <= 0) vpninfo->dtls_times.rekey_method = REKEY_NONE; - if (connect_dtls_socket(vpninfo)) + if (connect_dtls_socket(vpninfo, NULL)) return -EINVAL; vpn_progress(vpninfo, PRG_DEBUG, @@ -239,12 +239,12 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) if (vpninfo->dtls_need_reconnect) { vpninfo->dtls_need_reconnect = 0; - dtls_reconnect(vpninfo); + dtls_reconnect(vpninfo, timeout); return 1; } if (vpninfo->dtls_state == DTLS_CONNECTING) { - dtls_try_handshake(vpninfo); + dtls_try_handshake(vpninfo, timeout); vpninfo->delay_tunnel_reason = "DTLS MTU detection"; return 0; } @@ -254,7 +254,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) if (when <= 0) { vpn_progress(vpninfo, PRG_DEBUG, _("Attempt new DTLS connection\n")); - if (connect_dtls_socket(vpninfo) < 0) + if (connect_dtls_socket(vpninfo, timeout) < 0) *timeout = 1000; } else if ((when * 1000) < *timeout) { *timeout = when * 1000; @@ -349,10 +349,10 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) if (vpninfo->dtls_times.rekey_method == REKEY_SSL) { time(&vpninfo->new_dtls_started); vpninfo->dtls_state = DTLS_CONNECTING; - ret = dtls_try_handshake(vpninfo); + ret = dtls_try_handshake(vpninfo, timeout); if (ret) { vpn_progress(vpninfo, PRG_ERR, _("DTLS Rehandshake failed; reconnecting.\n")); - return connect_dtls_socket(vpninfo); + return connect_dtls_socket(vpninfo, timeout); } } @@ -362,7 +362,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) 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_reconnect(vpninfo); + dtls_reconnect(vpninfo, timeout); return 1; case KA_DPD: @@ -431,7 +431,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) if (ret < 0) { /* If it's a real error, kill the DTLS connection so the requeued packet will be sent over SSL */ - dtls_reconnect(vpninfo); + dtls_reconnect(vpninfo, timeout); work_done = 1; } return work_done; diff --git a/gnutls-dtls.c b/gnutls-dtls.c index 2aa7fd77..f14a6880 100644 --- a/gnutls-dtls.c +++ b/gnutls-dtls.c @@ -397,7 +397,7 @@ int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) return 0; } -int dtls_try_handshake(struct openconnect_info *vpninfo) +int dtls_try_handshake(struct openconnect_info *vpninfo, int *timeout) { int err = gnutls_handshake(vpninfo->dtls_ssl); char *str; @@ -476,8 +476,18 @@ int dtls_try_handshake(struct openconnect_info *vpninfo) } if (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) { - if (time(NULL) < vpninfo->new_dtls_started + 12) + int quit_time = vpninfo->new_dtls_started + 12 - time(NULL); + if (quit_time > 0) { + if (timeout) { + unsigned next_resend = gnutls_dtls_get_timeout(vpninfo->dtls_ssl); + if (next_resend && *timeout > next_resend) + *timeout = next_resend; + + if (*timeout > quit_time * 1000) + *timeout = quit_time * 1000; + } return 0; + } vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n")); } @@ -491,6 +501,8 @@ int dtls_try_handshake(struct openconnect_info *vpninfo) vpninfo->dtls_state = DTLS_SLEEPING; time(&vpninfo->new_dtls_started); + if (timeout && *timeout > vpninfo->dtls_attempt_period * 1000) + *timeout = vpninfo->dtls_attempt_period * 1000; return -EINVAL; } diff --git a/openconnect-internal.h b/openconnect-internal.h index 84cbf746..3f9699b3 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -948,7 +948,7 @@ int create_wintun(struct openconnect_info *vpninfo); /* {gnutls,openssl}-dtls.c */ int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd); -int dtls_try_handshake(struct openconnect_info *vpninfo); +int dtls_try_handshake(struct openconnect_info *vpninfo, int *timeout); unsigned dtls_set_mtu(struct openconnect_info *vpninfo, unsigned mtu); void dtls_ssl_free(struct openconnect_info *vpninfo); @@ -957,6 +957,7 @@ void destroy_eap_ttls(struct openconnect_info *vpninfo, void *sess); /* dtls.c */ int dtls_setup(struct openconnect_info *vpninfo); +int dtls_reconnect(struct openconnect_info *vpninfo, int *timeout); int udp_tos_update(struct openconnect_info *vpninfo, struct pkt *pkt); int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable); void dtls_close(struct openconnect_info *vpninfo); diff --git a/openssl-dtls.c b/openssl-dtls.c index 90cbdc62..d47dd701 100644 --- a/openssl-dtls.c +++ b/openssl-dtls.c @@ -553,7 +553,7 @@ int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) return 0; } -int dtls_try_handshake(struct openconnect_info *vpninfo) +int dtls_try_handshake(struct openconnect_info *vpninfo, int *timeout) { int ret = SSL_do_handshake(vpninfo->dtls_ssl); @@ -683,9 +683,24 @@ int dtls_try_handshake(struct openconnect_info *vpninfo) ret = SSL_get_error(vpninfo->dtls_ssl, ret); if (ret == SSL_ERROR_WANT_WRITE || ret == SSL_ERROR_WANT_READ) { - static int badossl_bitched = 0; - if (time(NULL) < vpninfo->new_dtls_started + 12) + int quit_time = vpninfo->new_dtls_started + 12 - time(NULL); + + if (quit_time > 0) { + if (timeout) { + struct timeval tv; + if (SSL_ctrl(vpninfo->dtls_ssl, DTLS_CTRL_GET_TIMEOUT, 0, &tv)) { + unsigned timeout_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + if (*timeout > timeout_ms) + *timeout = timeout_ms; + } + + if (*timeout > quit_time * 1000) + *timeout = quit_time * 1000; + } return 0; + } + + static int badossl_bitched = 0; if (((OPENSSL_VERSION_NUMBER >= 0x100000b0L && OPENSSL_VERSION_NUMBER <= 0x100000c0L) || \ (OPENSSL_VERSION_NUMBER >= 0x10001040L && OPENSSL_VERSION_NUMBER <= 0x10001060L) || \ OPENSSL_VERSION_NUMBER == 0x10002000L) && !badossl_bitched) { @@ -705,6 +720,8 @@ int dtls_try_handshake(struct openconnect_info *vpninfo) vpninfo->dtls_state = DTLS_SLEEPING; time(&vpninfo->new_dtls_started); + if (timeout && *timeout > vpninfo->dtls_attempt_period * 1000) + *timeout = vpninfo->dtls_attempt_period * 1000; return -EINVAL; } -- 2.50.1