int connect_dtls_socket(struct openconnect_info *vpninfo) { 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(); vpninfo->dtls_ctx = SSL_CTX_new(dtls_method); if (!vpninfo->dtls_ctx) { vpn_progress(vpninfo, PRG_ERR, _("Initialise DTLSv1 CTX failed\n")); 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 (!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 */ } /* 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)); 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(vpninfo->dtls_ctx); SSL_set_connect_state(dtls_ssl); 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; } /* Go Go Go! */ dtls_bio = BIO_new_socket(dtls_fd, BIO_NOCLOSE); 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); }
int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) { int work_done = 0; char magic_pkt; if (vpninfo->dtls_state == DTLS_CONNECTING) { dtls_try_handshake(vpninfo); return 0; } if (vpninfo->dtls_state == DTLS_SLEEPING) { int when = vpninfo->new_dtls_started + vpninfo->dtls_attempt_period - time(NULL); if (when <= 0) { vpn_progress(vpninfo, PRG_DEBUG, _("Attempt new DTLS connection\n")); connect_dtls_socket(vpninfo); } else if ((when * 1000) < *timeout) { *timeout = when * 1000; } return 0; } while (1) { int len = vpninfo->ip_info.mtu; unsigned char *buf; if (!dtls_pkt || len > dtls_pkt_max) { realloc_inplace(dtls_pkt, sizeof(struct pkt) + len); if (!dtls_pkt) { vpn_progress(vpninfo, PRG_ERR, "Allocation failed\n"); break; } dtls_pkt_max = len; } 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 AC_PKT_DATA: dtls_pkt->len = len - 1; queue_packet(&vpninfo->incoming_queue, dtls_pkt); dtls_pkt = NULL; work_done = 1; break; case AC_PKT_DPD_OUT: vpn_progress(vpninfo, PRG_DEBUG, _("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_DEBUG, _("Got DTLS DPD response\n")); break; case AC_PKT_KEEPALIVE: vpn_progress(vpninfo, PRG_DEBUG, _("Got DTLS Keepalive\n")); break; default: 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; } } } 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) 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) { 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 (ret == SSL_ERROR_WANT_WRITE) { monitor_write_fd(vpninfo, dtls); vpninfo->outgoing_queue = this; vpninfo->outgoing_qlen++; } 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); vpninfo->outgoing_queue = this; vpninfo->outgoing_qlen++; work_done = 1; } return work_done; } #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_reconnect(vpninfo); vpninfo->outgoing_queue = this; vpninfo->outgoing_qlen++; work_done = 1; } else if (gnutls_record_get_direction(vpninfo->dtls_ssl)) { monitor_write_fd(vpninfo, dtls); vpninfo->outgoing_queue = this; vpninfo->outgoing_qlen++; } 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; }
int connect_dtls_socket(struct openconnect_info *vpninfo) { int dtls_fd, ret, sndbuf; 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->protect_socket) vpninfo->protect_socket(vpninfo->cbdata, dtls_fd); sndbuf = vpninfo->ip_info.mtu * 2; setsockopt(dtls_fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); 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); }
int connect_dtls_socket(struct openconnect_info *vpninfo) { int dtls_fd, ret, sndbuf; /* 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 = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP); if (dtls_fd < 0) { perror(_("Open UDP socket for DTLS:")); return -EINVAL; } if (vpninfo->protect_socket) vpninfo->protect_socket(vpninfo->cbdata, dtls_fd); sndbuf = vpninfo->ip_info.mtu * 2; setsockopt(dtls_fd, SOL_SOCKET, SO_SNDBUF, (void *)&sndbuf, sizeof(sndbuf)); 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; closesocket(dtls_fd); return -EINVAL; } if (bind(dtls_fd, (struct sockaddr *)&dtls_bind_addr, dtls_bind_addrlen)) { perror(_("Bind UDP socket for DTLS")); closesocket(dtls_fd); return -EINVAL; } } if (connect(dtls_fd, vpninfo->dtls_addr, vpninfo->peer_addrlen)) { perror(_("UDP (DTLS) connect:\n")); closesocket(dtls_fd); return -EINVAL; } set_fd_cloexec(dtls_fd); set_sock_nonblock(dtls_fd); 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); }