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; } }
void esp_close(struct openconnect_info *vpninfo) { /* We close and reopen the socket in case we roamed and our local IP address has changed. */ if (vpninfo->dtls_fd != -1) { closesocket(vpninfo->dtls_fd); unmonitor_read_fd(vpninfo, dtls); unmonitor_write_fd(vpninfo, dtls); unmonitor_except_fd(vpninfo, dtls); vpninfo->dtls_fd = -1; } vpninfo->dtls_state = DTLS_SLEEPING; }
int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) { struct esp *esp = &vpninfo->esp_in[vpninfo->current_esp_in]; struct esp *old_esp = &vpninfo->esp_in[vpninfo->current_esp_in ^ 1]; struct pkt *this; int work_done = 0; int ret; if (vpninfo->dtls_state == DTLS_SLEEPING) { int when = vpninfo->new_dtls_started + vpninfo->dtls_attempt_period - time(NULL); if (when <= 0 || vpninfo->dtls_need_reconnect) { vpn_progress(vpninfo, PRG_DEBUG, _("Send ESP probes\n")); esp_send_probes(vpninfo); when = vpninfo->dtls_attempt_period; } if (*timeout > when * 1000) *timeout = when * 1000; } if (vpninfo->dtls_fd == -1) return 0; while (1) { int len = vpninfo->ip_info.mtu + vpninfo->pkt_trailer; int i; struct pkt *pkt; if (!vpninfo->dtls_pkt) { vpninfo->dtls_pkt = malloc(sizeof(struct pkt) + len); if (!vpninfo->dtls_pkt) { vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n")); break; } } pkt = vpninfo->dtls_pkt; len = recv(vpninfo->dtls_fd, (void *)&pkt->esp, len + sizeof(pkt->esp), 0); if (len <= 0) break; vpn_progress(vpninfo, PRG_TRACE, _("Received ESP packet of %d bytes\n"), len); work_done = 1; if (len <= sizeof(pkt->esp) + 12) continue; len -= sizeof(pkt->esp) + 12; pkt->len = len; if (pkt->esp.spi == esp->spi) { if (decrypt_esp_packet(vpninfo, esp, pkt)) continue; } else if (pkt->esp.spi == old_esp->spi && ntohl(pkt->esp.seq) + esp->seq < vpninfo->old_esp_maxseq) { vpn_progress(vpninfo, PRG_TRACE, _("Consider SPI 0x%x, seq %u against outgoing ESP setup\n"), (unsigned)ntohl(old_esp->spi), (unsigned)ntohl(pkt->esp.seq)); if (decrypt_esp_packet(vpninfo, old_esp, pkt)) continue; } else { vpn_progress(vpninfo, PRG_DEBUG, _("Received ESP packet with invalid SPI 0x%08x\n"), (unsigned)ntohl(pkt->esp.spi)); continue; } if (pkt->data[len - 1] != 0x04 && pkt->data[len - 1] != 0x29 && pkt->data[len - 1] != 0x05) { vpn_progress(vpninfo, PRG_ERR, _("Received ESP packet with unrecognised payload type %02x\n"), pkt->data[len-1]); continue; } if (len <= 2 + pkt->data[len - 2]) { vpn_progress(vpninfo, PRG_ERR, _("Invalid padding length %02x in ESP\n"), pkt->data[len - 2]); continue; } pkt->len = len - 2 - pkt->data[len - 2]; for (i = 0 ; i < pkt->data[len - 2]; i++) { if (pkt->data[pkt->len + i] != i + 1) break; /* We can't just 'continue' here because it * would only break out of this 'for' loop */ } if (i != pkt->data[len - 2]) { vpn_progress(vpninfo, PRG_ERR, _("Invalid padding bytes in ESP\n")); continue; /* We can here, though */ } vpninfo->dtls_times.last_rx = time(NULL); if (pkt->len == 1 && pkt->data[0] == 0) { if (vpninfo->dtls_state == DTLS_SLEEPING) { vpn_progress(vpninfo, PRG_INFO, _("ESP session established with server\n")); queue_esp_control(vpninfo, 1); vpninfo->dtls_state = DTLS_CONNECTING; } continue; } if (pkt->data[len - 1] == 0x05) { struct pkt *newpkt = malloc(sizeof(*pkt) + vpninfo->ip_info.mtu + vpninfo->pkt_trailer); int newlen = vpninfo->ip_info.mtu; if (!newpkt) { vpn_progress(vpninfo, PRG_ERR, _("Failed to allocate memory to decrypt ESP packet\n")); continue; } if (av_lzo1x_decode(newpkt->data, &newlen, pkt->data, &pkt->len) || pkt->len) { vpn_progress(vpninfo, PRG_ERR, _("LZO decompression of ESP packet failed\n")); free(newpkt); continue; } newpkt->len = vpninfo->ip_info.mtu - newlen; vpn_progress(vpninfo, PRG_TRACE, _("LZO decompressed %d bytes into %d\n"), len - 2 - pkt->data[len-2], newpkt->len); queue_packet(&vpninfo->incoming_queue, newpkt); } else { queue_packet(&vpninfo->incoming_queue, pkt); vpninfo->dtls_pkt = NULL; } } if (vpninfo->dtls_state != DTLS_CONNECTED) return 0; switch (keepalive_action(&vpninfo->dtls_times, timeout)) { case KA_REKEY: vpn_progress(vpninfo, PRG_ERR, _("Rekey not implemented for ESP\n")); break; case KA_DPD_DEAD: vpn_progress(vpninfo, PRG_ERR, _("ESP detected dead peer\n")); queue_esp_control(vpninfo, 0); esp_close(vpninfo); esp_send_probes(vpninfo); return 1; case KA_DPD: vpn_progress(vpninfo, PRG_DEBUG, _("Send ESP probes for DPD\n")); esp_send_probes(vpninfo); work_done = 1; break; case KA_KEEPALIVE: vpn_progress(vpninfo, PRG_ERR, _("Keepalive not implemented for ESP\n")); break; case KA_NONE: break; } unmonitor_write_fd(vpninfo, dtls); while ((this = dequeue_packet(&vpninfo->outgoing_queue))) { int len; len = encrypt_esp_packet(vpninfo, this); if (len > 0) { ret = send(vpninfo->dtls_fd, (void *)&this->esp, len, 0); if (ret < 0) { /* Not that this is likely to happen with UDP, but... */ if (errno == ENOBUFS || errno == EAGAIN || errno == EWOULDBLOCK) { monitor_write_fd(vpninfo, dtls); /* XXX: Keep the packet somewhere? */ free(this); return work_done; } else { /* A real error in sending. Fall back to TCP? */ vpn_progress(vpninfo, PRG_ERR, _("Failed to send ESP packet: %s\n"), strerror(errno)); } } else { vpninfo->dtls_times.last_tx = time(NULL); vpn_progress(vpninfo, PRG_TRACE, _("Sent ESP packet of %d bytes\n"), len); } } else { /* XXX: Fall back to TCP transport? */ } free(this); work_done = 1; } return work_done; }
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; }