This code will need to be made more generic before it works with OpenSSL. For now, just skip it on OpenSSL builds so that we can compile again. Signed-off-by: Kevin Cernekee <cernekee at gmail.com> --- dtls.c | 1020 ++++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 509 insertions(+), 511 deletions(-) diff --git a/dtls.c b/dtls.c index 7dc6ba9..f35d1a5 100644 --- a/dtls.c +++ b/dtls.c @@ -466,8 +466,6 @@ void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *b # define GNUTLS_CIPHER_CHACHA20_POLY1305 23 #endif -static void detect_mtu(struct openconnect_info *vpninfo); - struct { const char *name; gnutls_protocol_t version; @@ -613,179 +611,443 @@ static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) return 0; } -static int dtls_try_handshake(struct openconnect_info *vpninfo) -{ - int err = gnutls_handshake(vpninfo->dtls_ssl); - char *str; - - 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->dtls_ssl, vpninfo->ip_info.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->dtls_ssl, - vpninfo->ip_info.mtu + 1 /* packet + header */ - + 13 /* DTLS header */ - + 20 /* biggest supported MAC (SHA1) */ - + 16 /* biggest supported IV (AES-128) */ - + 16 /* max padding */); +/* Old glibc doesn't define that */ +#if defined(__linux__) && !defined(IPV6_PATHMTU) +# define IPV6_PATHMTU 61 #endif - vpninfo->dtls_state = DTLS_CONNECTED; - str = get_gnutls_cipher(vpninfo->dtls_ssl); - if (str) { - const char *c; - vpn_progress(vpninfo, PRG_INFO, - _("Established DTLS connection (using GnuTLS). Ciphersuite %s.\n"), - str); - gnutls_free(str); - c = openconnect_get_dtls_compression(vpninfo); - if (c) { - vpn_progress(vpninfo, PRG_INFO, - _("DTLS connection compression using %s.\n"), c); - } - } - - vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx = - vpninfo->dtls_times.last_tx = time(NULL); - - detect_mtu(vpninfo); - /* XXX: For OpenSSL we explicitly prevent retransmits here. */ - return 0; - } +static int is_cancelled(struct openconnect_info *vpninfo) +{ + fd_set rd_set; + int maxfd = 0; + FD_ZERO(&rd_set); + cmd_fd_set(vpninfo, &rd_set, &maxfd); - if (err == GNUTLS_E_AGAIN) { - if (time(NULL) < vpninfo->new_dtls_started + 12) - return 0; - vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n")); + if (is_cancel_pending(vpninfo, &rd_set)) { + vpn_progress(vpninfo, PRG_ERR, _("SSL operation cancelled\n")); + return -EINTR; } - vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"), - gnutls_strerror(err)); - if (err == GNUTLS_E_PUSH_ERROR) - vpn_progress(vpninfo, PRG_ERR, - _("(Is a firewall preventing you from sending UDP packets?)\n")); - error: - dtls_close(vpninfo); - - vpninfo->dtls_state = DTLS_SLEEPING; - time(&vpninfo->new_dtls_started); - return -EINVAL; + return 0; } -void dtls_shutdown(struct openconnect_info *vpninfo) +static +void ms_sleep(unsigned ms) { - dtls_close(vpninfo); + struct timespec tv; + + tv.tv_sec = ms/1000; + tv.tv_nsec = (ms%1000) * 1000 * 1000; + + nanosleep(&tv, NULL); } -#endif -static int connect_dtls_socket(struct openconnect_info *vpninfo) +#define MTU_ID_SIZE 4 +#define MTU_FAIL_CONT { \ + cur--; \ + max = cur; \ + continue; \ + } + +/* Performs a binary search to detect MTU. + * @buf: is preallocated with MTU size + * @id: a unique ID for our DPD exchange + * + * Returns: new MTU or 0 + */ +static int detect_mtu_ipv4(struct openconnect_info *vpninfo, unsigned char *buf) { - int dtls_fd, ret; + int max, min, cur, ret; + int max_tries = 100; /* maximum number of loops in bin search */ + int max_recv_loops = 40; /* 4 secs max */ + char id[MTU_ID_SIZE]; - /* Sanity check for the removal of new_dtls_{fd,ssl} */ - if (vpninfo->dtls_fd != -1) { - vpn_progress(vpninfo, PRG_ERR, _("DTLS connection attempted with an existing fd\n")); - vpninfo->dtls_attempt_period = 0; - return -EINVAL; - } + cur = max = vpninfo->ip_info.mtu; + min = vpninfo->ip_info.mtu/2; - if (!vpninfo->dtls_addr) { - vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n")); - vpninfo->dtls_attempt_period = 0; - return -EINVAL; - } + vpn_progress(vpninfo, PRG_DEBUG, + _("Initiating IPv4 MTU detection (min=%d, max=%d)\n"), min, max); - 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; - } + while (max > min) { + if (max_tries-- <= 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Too long time in MTU detect loop; bailing out.\n")); + goto fail; + } - 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; - } + /* generate unique ID */ + if (gnutls_rnd(GNUTLS_RND_NONCE, id, sizeof(id)) < 0) + goto fail; - dtls_fd = udp_connect(vpninfo); - if (dtls_fd < 0) - return -EINVAL; + cur = (min + max + 1) / 2; + buf[0] = AC_PKT_DPD_OUT; + memcpy(&buf[1], id, sizeof(id)); + do { + if (is_cancelled(vpninfo)) + goto fail; - ret = start_dtls_handshake(vpninfo, dtls_fd); - if (ret) { - closesocket(dtls_fd); - return ret; - } + ret = DTLS_SEND(vpninfo->dtls_ssl, buf, cur+1); + if (ret < 0) + ms_sleep(100); + } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); - vpninfo->dtls_state = DTLS_CONNECTING; + if (ret != cur+1) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to send DPD request (%d): %s\n"), cur, gnutls_strerror(ret)); + MTU_FAIL_CONT; + } - vpninfo->dtls_fd = dtls_fd; - monitor_fd_new(vpninfo, dtls); - monitor_read_fd(vpninfo, dtls); - monitor_except_fd(vpninfo, dtls); + reread: + memset(buf, 0, sizeof(id)+1); + do { + if (is_cancelled(vpninfo)) + goto fail; - time(&vpninfo->new_dtls_started); + ret = DTLS_RECV(vpninfo->dtls_ssl, buf, cur + 1); + if (ret < 0) + ms_sleep(100); - return dtls_try_handshake(vpninfo); -} + if (max_recv_loops-- <= 0) + goto fail; + } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); -void dtls_close(struct openconnect_info *vpninfo) -{ - if (vpninfo->dtls_ssl) { - DTLS_FREE(vpninfo->dtls_ssl); - closesocket(vpninfo->dtls_fd); - unmonitor_read_fd(vpninfo, dtls); - unmonitor_write_fd(vpninfo, dtls); - unmonitor_except_fd(vpninfo, dtls); - vpninfo->dtls_ssl = NULL; - vpninfo->dtls_fd = -1; + if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(buf) != 0))) { + vpn_progress(vpninfo, PRG_DEBUG, + _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]); + goto reread; /* resend */ + } + + if (ret <= 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to recv DPD request (%d): %s\n"), cur, gnutls_strerror(ret)); + MTU_FAIL_CONT; + } + + /* If we reached the max, success */ + if (cur == max) { + break; + } + min = cur; } -} -static int dtls_reconnect(struct openconnect_info *vpninfo) -{ - dtls_close(vpninfo); - vpninfo->dtls_state = DTLS_SLEEPING; - return connect_dtls_socket(vpninfo); + return cur; + fail: + return 0; } -int dtls_setup(struct openconnect_info *vpninfo, int dtls_attempt_period) +#if defined(IPPROTO_IPV6) +/* Verifies whether current MTU is ok, or detects new MTU using IPv6's ICMP6 messages + * @buf: is preallocated with MTU size + * @id: a unique ID for our DPD exchange + * + * Returns: new MTU or 0 + */ +static int detect_mtu_ipv6(struct openconnect_info *vpninfo, unsigned char *buf) { - struct oc_vpn_option *dtls_opt = vpninfo->dtls_options; - int dtls_port = 0; - - if (vpninfo->dtls_state == DTLS_DISABLED) - return -EINVAL; + int max, cur, ret; + int max_resends = 5; /* maximum number of resends */ + int max_recv_loops = 40; /* 4 secs max */ + char id[MTU_ID_SIZE]; - vpninfo->dtls_attempt_period = dtls_attempt_period; - if (!dtls_attempt_period) - return 0; + cur = max = vpninfo->ip_info.mtu; -#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 + vpn_progress(vpninfo, PRG_DEBUG, + _("Initiating IPv6 MTU detection\n")); - while (dtls_opt) { + do { + /* generate unique ID */ + if (gnutls_rnd(GNUTLS_RND_NONCE, id, sizeof(id)) < 0) + goto fail; + + buf[0] = AC_PKT_DPD_OUT; + memcpy(&buf[1], id, sizeof(id)); + do { + if (is_cancelled(vpninfo)) + goto fail; + + ret = DTLS_SEND(vpninfo->dtls_ssl, buf, cur+1); + if (ret < 0) + ms_sleep(100); + } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + if (ret != cur+1) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to send DPD request (%d): %s\n"), cur, gnutls_strerror(ret)); + goto mtu6_fail; + } + + reread: + memset(buf, 0, sizeof(id)+1); + do { + if (is_cancelled(vpninfo)) + goto fail; + + ret = DTLS_RECV(vpninfo->dtls_ssl, buf, cur + 1); + if (ret < 0) + ms_sleep(100); + + if (max_recv_loops-- <= 0) + goto fail; + } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + /* timeout, probably our original request was lost, + * let's resend the DPD */ + if (ret == GNUTLS_E_TIMEDOUT) + continue; + + /* something unexpected was received, let's ignore it */ + if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(buf) != 0))) { + vpn_progress(vpninfo, PRG_DEBUG, + _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]); + goto reread; + } + + /* we received what we expected, move on */ + break; + } while(max_resends-- > 0); + + /* If we received back our DPD packet, do nothing; otherwise, + * attempt to get MTU from the ICMP6 packet we received */ + if (ret <= 0) { + struct ip6_mtuinfo mtuinfo; + socklen_t len = sizeof(mtuinfo); + max = 0; + vpn_progress(vpninfo, PRG_ERR, + _("Failed to recv DPD request (%d): %s\n"), cur, gnutls_strerror(ret)); + mtu6_fail: + if (getsockopt(vpninfo->dtls_fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) >= 0) { + max = mtuinfo.ip6m_mtu; + if (max >= 0 && max < cur) { + gnutls_dtls_set_mtu(vpninfo->dtls_ssl, max); + cur = gnutls_dtls_get_data_mtu(vpninfo->dtls_ssl) - /*ipv6*/40 - /*udp*/20 - /*oc dtls*/1; + } + } + } + + return cur; + fail: + return 0; +} +#endif + +static void detect_mtu(struct openconnect_info *vpninfo) +{ + int mtu = vpninfo->ip_info.mtu; + int prev_mtu = vpninfo->ip_info.mtu; + unsigned char *buf; + + if (vpninfo->ip_info.mtu < 1+MTU_ID_SIZE) + return; + + /* detect MTU */ + buf = calloc(1, 1 + vpninfo->ip_info.mtu); + if (!buf) { + vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n")); + return; + } + + /* enforce a timeout in receiving */ + gnutls_record_set_timeout(vpninfo->dtls_ssl, 2400); + if (vpninfo->peer_addr->sa_family == AF_INET) { /* IPv4 */ + mtu = detect_mtu_ipv4(vpninfo, buf); + if (mtu == 0) + goto skip_mtu; +#if defined(IPPROTO_IPV6) + } else if (vpninfo->peer_addr->sa_family == AF_INET6) { /* IPv6 */ + mtu = detect_mtu_ipv6(vpninfo, buf); + if (mtu == 0) + goto skip_mtu; +#endif + } + + vpninfo->ip_info.mtu = mtu; + if (prev_mtu != vpninfo->ip_info.mtu) { + vpn_progress(vpninfo, PRG_INFO, + _("Detected MTU of %d bytes (was %d)\n"), vpninfo->ip_info.mtu, prev_mtu); + } else { + vpn_progress(vpninfo, PRG_DEBUG, + _("No change in MTU after detection (was %d)\n"), prev_mtu); + } + + skip_mtu: + gnutls_record_set_timeout(vpninfo->dtls_ssl, 0); + free(buf); +} + +static int dtls_try_handshake(struct openconnect_info *vpninfo) +{ + int err = gnutls_handshake(vpninfo->dtls_ssl); + char *str; + + 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->dtls_ssl, vpninfo->ip_info.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->dtls_ssl, + vpninfo->ip_info.mtu + 1 /* packet + header */ + + 13 /* DTLS header */ + + 20 /* biggest supported MAC (SHA1) */ + + 16 /* biggest supported IV (AES-128) */ + + 16 /* max padding */); +#endif + + vpninfo->dtls_state = DTLS_CONNECTED; + str = get_gnutls_cipher(vpninfo->dtls_ssl); + if (str) { + const char *c; + vpn_progress(vpninfo, PRG_INFO, + _("Established DTLS connection (using GnuTLS). Ciphersuite %s.\n"), + str); + gnutls_free(str); + c = openconnect_get_dtls_compression(vpninfo); + if (c) { + vpn_progress(vpninfo, PRG_INFO, + _("DTLS connection compression using %s.\n"), c); + } + } + + vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx = + vpninfo->dtls_times.last_tx = time(NULL); + + detect_mtu(vpninfo); + /* XXX: For OpenSSL we explicitly prevent retransmits here. */ + return 0; + } + + if (err == GNUTLS_E_AGAIN) { + if (time(NULL) < vpninfo->new_dtls_started + 12) + return 0; + vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n")); + } + + vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"), + gnutls_strerror(err)); + if (err == GNUTLS_E_PUSH_ERROR) + vpn_progress(vpninfo, PRG_ERR, + _("(Is a firewall preventing you from sending UDP packets?)\n")); + error: + dtls_close(vpninfo); + + vpninfo->dtls_state = DTLS_SLEEPING; + time(&vpninfo->new_dtls_started); + return -EINVAL; +} + +void dtls_shutdown(struct openconnect_info *vpninfo) +{ + dtls_close(vpninfo); +} +#endif + +static int connect_dtls_socket(struct openconnect_info *vpninfo) +{ + int dtls_fd, ret; + + /* Sanity check for the removal of new_dtls_{fd,ssl} */ + if (vpninfo->dtls_fd != -1) { + vpn_progress(vpninfo, PRG_ERR, _("DTLS connection attempted with an existing fd\n")); + vpninfo->dtls_attempt_period = 0; + return -EINVAL; + } + + 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 = udp_connect(vpninfo); + if (dtls_fd < 0) + return -EINVAL; + + + ret = start_dtls_handshake(vpninfo, dtls_fd); + if (ret) { + closesocket(dtls_fd); + return ret; + } + + vpninfo->dtls_state = DTLS_CONNECTING; + + vpninfo->dtls_fd = dtls_fd; + monitor_fd_new(vpninfo, dtls); + monitor_read_fd(vpninfo, dtls); + monitor_except_fd(vpninfo, dtls); + + time(&vpninfo->new_dtls_started); + + return dtls_try_handshake(vpninfo); +} + +void dtls_close(struct openconnect_info *vpninfo) +{ + if (vpninfo->dtls_ssl) { + DTLS_FREE(vpninfo->dtls_ssl); + closesocket(vpninfo->dtls_fd); + unmonitor_read_fd(vpninfo, dtls); + unmonitor_write_fd(vpninfo, dtls); + unmonitor_except_fd(vpninfo, dtls); + vpninfo->dtls_ssl = NULL; + vpninfo->dtls_fd = -1; + } +} + +static int dtls_reconnect(struct openconnect_info *vpninfo) +{ + dtls_close(vpninfo); + vpninfo->dtls_state = DTLS_SLEEPING; + return connect_dtls_socket(vpninfo); +} + +int dtls_setup(struct openconnect_info *vpninfo, int dtls_attempt_period) +{ + struct oc_vpn_option *dtls_opt = vpninfo->dtls_options; + int dtls_port = 0; + + if (vpninfo->dtls_state == DTLS_DISABLED) + return -EINVAL; + + vpninfo->dtls_attempt_period = dtls_attempt_period; + if (!dtls_attempt_period) + return 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_DEBUG, _("DTLS option %s : %s\n"), dtls_opt->option, dtls_opt->value); @@ -933,402 +1195,138 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) break; } else { unknown_pkt: - vpninfo->quit_reason = "Unknown packet received"; - return 1; - } - - } - } - - switch (keepalive_action(&vpninfo->dtls_times, timeout)) { - case KA_REKEY: { - int ret; - - vpn_progress(vpninfo, PRG_INFO, _("DTLS rekey due\n")); - - if (vpninfo->dtls_times.rekey_method == REKEY_SSL) { - time(&vpninfo->new_dtls_started); - vpninfo->dtls_state = DTLS_CONNECTING; - ret = dtls_try_handshake(vpninfo); - if (ret) { - vpn_progress(vpninfo, PRG_ERR, _("DTLS Rehandshake failed; reconnecting.\n")); - return connect_dtls_socket(vpninfo); - } - } - - return 1; - } - - 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); - return 1; - - case KA_DPD: - vpn_progress(vpninfo, PRG_DEBUG, _("Send DTLS DPD\n")); - - 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")); - - /* last_dpd will just have been set */ - vpninfo->dtls_times.last_tx = vpninfo->dtls_times.last_dpd; - work_done = 1; - break; - - case KA_KEEPALIVE: - /* No need to send an explicit keepalive - if we have real data to send */ - if (vpninfo->outgoing_queue.head) - break; - - vpn_progress(vpninfo, PRG_DEBUG, _("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: - ; - } - - /* Service outgoing packet queue */ - unmonitor_write_fd(vpninfo, dtls); - while (vpninfo->outgoing_queue.head) { - struct pkt *this = dequeue_packet(&vpninfo->outgoing_queue); - struct pkt *send_pkt = this; - int ret; - - /* One byte of header */ - this->cstp.hdr[7] = AC_PKT_DATA; - - /* We can compress into vpninfo->deflate_pkt unless CSTP - * currently has a compressed packet pending ? which it - * shouldn't if DTLS is active. */ - if (vpninfo->dtls_compr && - vpninfo->current_ssl_pkt != vpninfo->deflate_pkt && - !compress_packet(vpninfo, vpninfo->dtls_compr, this)) { - send_pkt = vpninfo->deflate_pkt; - send_pkt->cstp.hdr[7] = AC_PKT_COMPRESSED; - } - -#if defined(DTLS_OPENSSL) - ret = SSL_write(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->len + 1); - if (ret <= 0) { - ret = SSL_get_error(vpninfo->dtls_ssl, ret); - - if (ret == SSL_ERROR_WANT_WRITE) { - monitor_write_fd(vpninfo, dtls); - requeue_packet(&vpninfo->outgoing_queue, this); - } else if (ret != SSL_ERROR_WANT_READ) { - /* If it's a real error, kill the DTLS connection and - requeue the packet to be sent over SSL */ - vpn_progress(vpninfo, PRG_ERR, - _("DTLS got write error %d. Falling back to SSL\n"), - ret); - openconnect_report_ssl_errors(vpninfo); - dtls_reconnect(vpninfo); - requeue_packet(&vpninfo->outgoing_queue, this); - work_done = 1; - } - return work_done; - } -#elif defined(DTLS_GNUTLS) - ret = gnutls_record_send(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->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_reconnect(vpninfo); - work_done = 1; - } else { - /* Wake me up when it becomes writeable */ - monitor_write_fd(vpninfo, dtls); - } - - requeue_packet(&vpninfo->outgoing_queue, this); - return work_done; - } -#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; -} - -/* Old glibc doesn't define that */ -#if defined(__linux__) && !defined(IPV6_PATHMTU) -# define IPV6_PATHMTU 61 -#endif - -static int is_cancelled(struct openconnect_info *vpninfo) -{ - fd_set rd_set; - int maxfd = 0; - FD_ZERO(&rd_set); - cmd_fd_set(vpninfo, &rd_set, &maxfd); - - if (is_cancel_pending(vpninfo, &rd_set)) { - vpn_progress(vpninfo, PRG_ERR, _("SSL operation cancelled\n")); - return -EINTR; - } - - return 0; -} - -static -void ms_sleep(unsigned ms) -{ - struct timespec tv; - - tv.tv_sec = ms/1000; - tv.tv_nsec = (ms%1000) * 1000 * 1000; - - nanosleep(&tv, NULL); -} - -#define MTU_ID_SIZE 4 -#define MTU_FAIL_CONT { \ - cur--; \ - max = cur; \ - continue; \ - } - -/* Performs a binary search to detect MTU. - * @buf: is preallocated with MTU size - * @id: a unique ID for our DPD exchange - * - * Returns: new MTU or 0 - */ -static int detect_mtu_ipv4(struct openconnect_info *vpninfo, unsigned char *buf) -{ - int max, min, cur, ret; - int max_tries = 100; /* maximum number of loops in bin search */ - int max_recv_loops = 40; /* 4 secs max */ - char id[MTU_ID_SIZE]; - - cur = max = vpninfo->ip_info.mtu; - min = vpninfo->ip_info.mtu/2; - - vpn_progress(vpninfo, PRG_DEBUG, - _("Initiating IPv4 MTU detection (min=%d, max=%d)\n"), min, max); - - while (max > min) { - if (max_tries-- <= 0) { - vpn_progress(vpninfo, PRG_ERR, - _("Too long time in MTU detect loop; bailing out.\n")); - goto fail; - } - - /* generate unique ID */ - if (gnutls_rnd(GNUTLS_RND_NONCE, id, sizeof(id)) < 0) - goto fail; - - cur = (min + max + 1) / 2; - buf[0] = AC_PKT_DPD_OUT; - memcpy(&buf[1], id, sizeof(id)); - - do { - if (is_cancelled(vpninfo)) - goto fail; - - ret = DTLS_SEND(vpninfo->dtls_ssl, buf, cur+1); - if (ret < 0) - ms_sleep(100); - } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + vpninfo->quit_reason = "Unknown packet received"; + return 1; + } - if (ret != cur+1) { - vpn_progress(vpninfo, PRG_ERR, - _("Failed to send DPD request (%d): %s\n"), cur, gnutls_strerror(ret)); - MTU_FAIL_CONT; } + } - reread: - memset(buf, 0, sizeof(id)+1); - do { - if (is_cancelled(vpninfo)) - goto fail; - - ret = DTLS_RECV(vpninfo->dtls_ssl, buf, cur + 1); - if (ret < 0) - ms_sleep(100); - - if (max_recv_loops-- <= 0) - goto fail; - } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + switch (keepalive_action(&vpninfo->dtls_times, timeout)) { + case KA_REKEY: { + int ret; - if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(buf) != 0))) { - vpn_progress(vpninfo, PRG_DEBUG, - _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]); - goto reread; /* resend */ - } + vpn_progress(vpninfo, PRG_INFO, _("DTLS rekey due\n")); - if (ret <= 0) { - vpn_progress(vpninfo, PRG_ERR, - _("Failed to recv DPD request (%d): %s\n"), cur, gnutls_strerror(ret)); - MTU_FAIL_CONT; + if (vpninfo->dtls_times.rekey_method == REKEY_SSL) { + time(&vpninfo->new_dtls_started); + vpninfo->dtls_state = DTLS_CONNECTING; + ret = dtls_try_handshake(vpninfo); + if (ret) { + vpn_progress(vpninfo, PRG_ERR, _("DTLS Rehandshake failed; reconnecting.\n")); + return connect_dtls_socket(vpninfo); + } } - /* If we reached the max, success */ - if (cur == max) { - break; - } - min = cur; + return 1; } - return cur; - fail: - return 0; -} - -#if defined(IPPROTO_IPV6) -/* Verifies whether current MTU is ok, or detects new MTU using IPv6's ICMP6 messages - * @buf: is preallocated with MTU size - * @id: a unique ID for our DPD exchange - * - * Returns: new MTU or 0 - */ -static int detect_mtu_ipv6(struct openconnect_info *vpninfo, unsigned char *buf) -{ - int max, cur, ret; - int max_resends = 5; /* maximum number of resends */ - int max_recv_loops = 40; /* 4 secs max */ - char id[MTU_ID_SIZE]; + 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); + return 1; - cur = max = vpninfo->ip_info.mtu; + case KA_DPD: + vpn_progress(vpninfo, PRG_DEBUG, _("Send DTLS DPD\n")); - vpn_progress(vpninfo, PRG_DEBUG, - _("Initiating IPv6 MTU detection\n")); + 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")); - do { - /* generate unique ID */ - if (gnutls_rnd(GNUTLS_RND_NONCE, id, sizeof(id)) < 0) - goto fail; + /* last_dpd will just have been set */ + vpninfo->dtls_times.last_tx = vpninfo->dtls_times.last_dpd; + work_done = 1; + break; - buf[0] = AC_PKT_DPD_OUT; - memcpy(&buf[1], id, sizeof(id)); - do { - if (is_cancelled(vpninfo)) - goto fail; + case KA_KEEPALIVE: + /* No need to send an explicit keepalive + if we have real data to send */ + if (vpninfo->outgoing_queue.head) + break; - ret = DTLS_SEND(vpninfo->dtls_ssl, buf, cur+1); - if (ret < 0) - ms_sleep(100); - } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + vpn_progress(vpninfo, PRG_DEBUG, _("Send DTLS Keepalive\n")); - if (ret != cur+1) { + magic_pkt = AC_PKT_KEEPALIVE; + if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1) vpn_progress(vpninfo, PRG_ERR, - _("Failed to send DPD request (%d): %s\n"), cur, gnutls_strerror(ret)); - goto mtu6_fail; - } - - reread: - memset(buf, 0, sizeof(id)+1); - do { - if (is_cancelled(vpninfo)) - goto fail; + _("Failed to send keepalive request. Expect disconnect\n")); + time(&vpninfo->dtls_times.last_tx); + work_done = 1; + break; - ret = DTLS_RECV(vpninfo->dtls_ssl, buf, cur + 1); - if (ret < 0) - ms_sleep(100); + case KA_NONE: + ; + } - if (max_recv_loops-- <= 0) - goto fail; - } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + /* Service outgoing packet queue */ + unmonitor_write_fd(vpninfo, dtls); + while (vpninfo->outgoing_queue.head) { + struct pkt *this = dequeue_packet(&vpninfo->outgoing_queue); + struct pkt *send_pkt = this; + int ret; - /* timeout, probably our original request was lost, - * let's resend the DPD */ - if (ret == GNUTLS_E_TIMEDOUT) - continue; + /* One byte of header */ + this->cstp.hdr[7] = AC_PKT_DATA; - /* something unexpected was received, let's ignore it */ - if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(buf) != 0))) { - vpn_progress(vpninfo, PRG_DEBUG, - _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]); - goto reread; + /* We can compress into vpninfo->deflate_pkt unless CSTP + * currently has a compressed packet pending ? which it + * shouldn't if DTLS is active. */ + if (vpninfo->dtls_compr && + vpninfo->current_ssl_pkt != vpninfo->deflate_pkt && + !compress_packet(vpninfo, vpninfo->dtls_compr, this)) { + send_pkt = vpninfo->deflate_pkt; + send_pkt->cstp.hdr[7] = AC_PKT_COMPRESSED; } - /* we received what we expected, move on */ - break; - } while(max_resends-- > 0); +#if defined(DTLS_OPENSSL) + ret = SSL_write(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->len + 1); + if (ret <= 0) { + ret = SSL_get_error(vpninfo->dtls_ssl, ret); - /* If we received back our DPD packet, do nothing; otherwise, - * attempt to get MTU from the ICMP6 packet we received */ - if (ret <= 0) { - struct ip6_mtuinfo mtuinfo; - socklen_t len = sizeof(mtuinfo); - max = 0; - vpn_progress(vpninfo, PRG_ERR, - _("Failed to recv DPD request (%d): %s\n"), cur, gnutls_strerror(ret)); - mtu6_fail: - if (getsockopt(vpninfo->dtls_fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) >= 0) { - max = mtuinfo.ip6m_mtu; - if (max >= 0 && max < cur) { - gnutls_dtls_set_mtu(vpninfo->dtls_ssl, max); - cur = gnutls_dtls_get_data_mtu(vpninfo->dtls_ssl) - /*ipv6*/40 - /*udp*/20 - /*oc dtls*/1; + if (ret == SSL_ERROR_WANT_WRITE) { + monitor_write_fd(vpninfo, dtls); + requeue_packet(&vpninfo->outgoing_queue, this); + } else if (ret != SSL_ERROR_WANT_READ) { + /* If it's a real error, kill the DTLS connection and + requeue the packet to be sent over SSL */ + vpn_progress(vpninfo, PRG_ERR, + _("DTLS got write error %d. Falling back to SSL\n"), + ret); + openconnect_report_ssl_errors(vpninfo); + dtls_reconnect(vpninfo); + requeue_packet(&vpninfo->outgoing_queue, this); + work_done = 1; } + return work_done; } - } - - return cur; - fail: - return 0; -} -#endif - -static void detect_mtu(struct openconnect_info *vpninfo) -{ - int mtu = vpninfo->ip_info.mtu; - int prev_mtu = vpninfo->ip_info.mtu; - unsigned char *buf; - - if (vpninfo->ip_info.mtu < 1+MTU_ID_SIZE) - return; - - /* detect MTU */ - buf = calloc(1, 1 + vpninfo->ip_info.mtu); - if (!buf) { - vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n")); - return; - } +#elif defined(DTLS_GNUTLS) + ret = gnutls_record_send(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->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_reconnect(vpninfo); + work_done = 1; + } else { + /* Wake me up when it becomes writeable */ + monitor_write_fd(vpninfo, dtls); + } - /* enforce a timeout in receiving */ - gnutls_record_set_timeout(vpninfo->dtls_ssl, 2400); - if (vpninfo->peer_addr->sa_family == AF_INET) { /* IPv4 */ - mtu = detect_mtu_ipv4(vpninfo, buf); - if (mtu == 0) - goto skip_mtu; -#if defined(IPPROTO_IPV6) - } else if (vpninfo->peer_addr->sa_family == AF_INET6) { /* IPv6 */ - mtu = detect_mtu_ipv6(vpninfo, buf); - if (mtu == 0) - goto skip_mtu; + requeue_packet(&vpninfo->outgoing_queue, this); + return work_done; + } #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); } - vpninfo->ip_info.mtu = mtu; - if (prev_mtu != vpninfo->ip_info.mtu) { - vpn_progress(vpninfo, PRG_INFO, - _("Detected MTU of %d bytes (was %d)\n"), vpninfo->ip_info.mtu, prev_mtu); - } else { - vpn_progress(vpninfo, PRG_DEBUG, - _("No change in MTU after detection (was %d)\n"), prev_mtu); - } - - skip_mtu: - gnutls_record_set_timeout(vpninfo->dtls_ssl, 0); - free(buf); + return work_done; } #else /* !HAVE_DTLS */ -- 1.9.1