From 2c700089af2282812a7f86ac73c0d6a629a837ea Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 2 Feb 2015 21:11:43 +0000 Subject: [PATCH] Implement ESP keepalive and periodic reconnect attempt Signed-off-by: David Woodhouse --- esp.c | 80 ++++++++++++++++++++++++++++++++++-------- oncp.c | 27 +++++++++++--- openconnect-internal.h | 1 + 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/esp.c b/esp.c index f4c40816..6e76a7ec 100644 --- a/esp.c +++ b/esp.c @@ -165,6 +165,19 @@ static int esp_send_probes(struct openconnect_info *vpninfo) struct pkt *pkt; int pktlen; + if (vpninfo->dtls_fd == -1) { + int fd = udp_connect(vpninfo); + if (fd < 0) + return fd; + + /* We are not connected until we get an ESP packet back */ + vpninfo->dtls_state = DTLS_SLEEPING; + vpninfo->dtls_fd = fd; + monitor_fd_new(vpninfo, dtls); + monitor_read_fd(vpninfo, dtls); + monitor_except_fd(vpninfo, dtls); + } + pkt = malloc(sizeof(*pkt) + 1 + vpninfo->pkt_trailer); if (!pkt) return -ENOMEM; @@ -180,33 +193,28 @@ static int esp_send_probes(struct openconnect_info *vpninfo) send(vpninfo->dtls_fd, (void *)&pkt->esp, pktlen, 0); free(pkt); - time(&vpninfo->new_dtls_started); + + vpninfo->dtls_times.last_tx = time(&vpninfo->new_dtls_started); return 0; }; int esp_setup(struct openconnect_info *vpninfo, int dtls_attempt_period) { - int fd; - if (vpninfo->dtls_state == DTLS_DISABLED || vpninfo->dtls_state == DTLS_NOSECRET) return -EINVAL; - fd = udp_connect(vpninfo); - if (fd < 0) - return fd; + if (vpninfo->esp_ssl_fallback) + vpninfo->dtls_times.dpd = vpninfo->esp_ssl_fallback; + else + vpninfo->dtls_times.dpd = dtls_attempt_period; + + vpninfo->dtls_attempt_period = dtls_attempt_period; print_esp_keys(vpninfo, _("incoming"), &vpninfo->esp_in[vpninfo->current_esp_in]); print_esp_keys(vpninfo, _("outgoing"), &vpninfo->esp_out); - /* We are not connected until we get an ESP packet back */ - vpninfo->dtls_state = DTLS_SLEEPING; - vpninfo->dtls_fd = fd; - monitor_fd_new(vpninfo, dtls); - monitor_read_fd(vpninfo, dtls); - monitor_except_fd(vpninfo, dtls); - esp_send_probes(vpninfo); return 0; @@ -220,6 +228,19 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) 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) { + 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; struct pkt *pkt; @@ -279,11 +300,13 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) } /* XXX: Actually check the padding bytes too. */ pkt->len = len - 2 - pkt->data[len - 2]; + 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; @@ -317,6 +340,31 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) 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; @@ -337,9 +385,12 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) _("Failed to send ESP packet: %s\n"), strerror(errno)); } - } else + } 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? */ } @@ -360,6 +411,7 @@ void esp_close(struct openconnect_info *vpninfo) unmonitor_write_fd(vpninfo, dtls); unmonitor_except_fd(vpninfo, dtls); } + vpninfo->dtls_state = DTLS_SLEEPING; } void esp_shutdown(struct openconnect_info *vpninfo) diff --git a/oncp.c b/oncp.c index 2e65b902..04b90bff 100644 --- a/oncp.c +++ b/oncp.c @@ -962,6 +962,18 @@ static const struct pkt esp_enable_pkt = { .len = 13 }; +int queue_esp_control(struct openconnect_info *vpninfo, int enable) +{ + struct pkt *new = malloc(sizeof(*new) + 13); + if (!new) + return -ENOMEM; + + memcpy(new, &esp_enable_pkt, sizeof(*new) + 13); + new->data[12] = enable; + queue_packet(&vpninfo->oncp_control_queue, new); + return 0; +} + static int check_kmp_header(struct openconnect_info *vpninfo, unsigned char *bytes, int pktlen) { if (pktlen < 20 || memcmp(bytes, kmp_head, sizeof(kmp_head)) || @@ -1641,6 +1653,17 @@ int oncp_mainloop(struct openconnect_info *vpninfo, int *timeout) if (vpninfo->dtls_state == DTLS_CONNECTING) vpninfo->dtls_state = DTLS_CONNECTED; } else { + /* Only set the ESP state to connected and actually start + sending packets on it once the enable message has been + *sent* over the TCP channel. */ + if (vpninfo->dtls_state == DTLS_CONNECTING && + vpninfo->current_ssl_pkt->len == 13 && + load_be16(&vpninfo->current_ssl_pkt->oncp.hdr[8]) == 0x12f && + vpninfo->current_ssl_pkt->data[12]) { + vpn_progress(vpninfo, PRG_TRACE, + _("Sent ESP enable control packet\n")); + vpninfo->dtls_state = DTLS_CONNECTED; + } free(vpninfo->current_ssl_pkt); } vpninfo->current_ssl_pkt = NULL; @@ -1720,10 +1743,6 @@ int oncp_mainloop(struct openconnect_info *vpninfo, int *timeout) if (vpninfo->current_ssl_pkt) goto handle_outgoing; - if (vpninfo->dtls_state == DTLS_CONNECTING) { - vpninfo->current_ssl_pkt = (struct pkt *)&esp_enable_pkt; - goto handle_outgoing; - } /* Service outgoing packet queue, if no DTLS */ while (vpninfo->dtls_state != DTLS_CONNECTED && (vpninfo->current_ssl_pkt = dequeue_packet(&vpninfo->outgoing_queue))) { diff --git a/openconnect-internal.h b/openconnect-internal.h index f6932f1a..d1c73425 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -764,6 +764,7 @@ int decompress_and_queue_packet(struct openconnect_info *vpninfo, int compress_packet(struct openconnect_info *vpninfo, int compr_type, struct pkt *this); /* oncp.c */ +int queue_esp_control(struct openconnect_info *vpninfo, int enable); int oncp_obtain_cookie(struct openconnect_info *vpninfo); int oncp_connect(struct openconnect_info *vpninfo); int oncp_mainloop(struct openconnect_info *vpninfo, int *timeout);