Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Asynchronous DTLS (re)connection
  • Loading branch information
David Woodhouse authored and David Woodhouse committed Oct 2, 2008
1 parent 3fe4002 commit 097ba42
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 63 deletions.
7 changes: 7 additions & 0 deletions anyconnect.h
Expand Up @@ -91,9 +91,12 @@ struct anyconnect_info {
z_stream deflate_strm;
uint32_t deflate_adler32;

int dtls_attempt_period;
time_t new_dtls_started;
int dtls_state;
SSL_CTX *dtls_ctx;
SSL *dtls_ssl;
SSL *new_dtls_ssl;
SSL_SESSION *dtls_session;
struct keepalive_info dtls_times;
unsigned char dtls_session_id[32];
Expand All @@ -114,8 +117,10 @@ struct anyconnect_info {
int tun_fd;
int ssl_fd;
int dtls_fd;
int new_dtls_fd;
int ssl_pfd;
int dtls_pfd;
int new_dtls_pfd;

struct pkt *incoming_queue;
struct pkt *outgoing_queue;
Expand Down Expand Up @@ -147,6 +152,8 @@ int tun_mainloop(struct anyconnect_info *vpninfo, int *timeout);
/* dtls.c */
int setup_dtls(struct anyconnect_info *vpninfo);
int dtls_mainloop(struct anyconnect_info *vpninfo, int *timeout);
int dtls_try_handshake(struct anyconnect_info *vpninfo);
int connect_dtls_socket(struct anyconnect_info *vpninfo);

/* ssl.c */
int make_ssl_connection(struct anyconnect_info *vpninfo);
Expand Down
149 changes: 97 additions & 52 deletions dtls.c
Expand Up @@ -50,7 +50,8 @@
* number seen in the ClientHello. But although I can make the handshake
* complete by hacking tls1_mac() to use the _old_ protocol version
* number when calculating the MAC, the server still seems to be ignoring
* my subsequent data packets.
* my subsequent data packets. So we use the old protocol, which is what
* their clients use anyway.
*/

static unsigned char nybble(unsigned char n)
Expand All @@ -66,15 +67,13 @@ static unsigned char hex(const char *data)
return (nybble(data[0]) << 4) | nybble(data[1]);
}

static int connect_dtls_socket(struct anyconnect_info *vpninfo, SSL **ret_ssl,
int *ret_fd)
int connect_dtls_socket(struct anyconnect_info *vpninfo)
{
SSL_METHOD *dtls_method;
SSL_CIPHER *https_cipher;
SSL *dtls_ssl;
BIO *dtls_bio;
int dtls_fd;
int ret;

dtls_fd = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
if (dtls_fd < 0) {
Expand Down Expand Up @@ -152,61 +151,96 @@ static int connect_dtls_socket(struct anyconnect_info *vpninfo, SSL **ret_ssl,
#define SSL_OP_CISCO_ANYCONNECT 0x8000
#endif
SSL_set_options(dtls_ssl, SSL_OP_CISCO_ANYCONNECT);
ret = SSL_do_handshake(dtls_ssl);

if (ret != 1) {
fprintf(stderr, "DTLS connection returned %d\n", ret);
if (ret < 0)
fprintf(stderr, "DTLS handshake error: %d\n", SSL_get_error(dtls_ssl, ret));
ERR_print_errors_fp(stderr);
SSL_free(dtls_ssl);
SSL_CTX_free(vpninfo->dtls_ctx);
vpninfo->dtls_ctx = NULL;
close(dtls_fd);
return -EINVAL;
}

/* 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->dtls_times.last_rekey = vpninfo->dtls_times.last_rx =
vpninfo->dtls_times.last_tx = time(NULL);

*ret_fd = dtls_fd;
*ret_ssl = dtls_ssl;
vpninfo->new_dtls_fd = dtls_fd;
vpninfo->new_dtls_ssl = dtls_ssl;
vpninfo->pfds[vpninfo->new_dtls_pfd].fd = vpninfo->new_dtls_fd;

return 0;
time(&vpninfo->new_dtls_started);
return dtls_try_handshake(vpninfo);
}

static int dtls_rekey(struct anyconnect_info *vpninfo)
int dtls_try_handshake(struct anyconnect_info *vpninfo)
{
SSL *dtls_ssl;
int dtls_fd;
int ret = SSL_do_handshake(vpninfo->new_dtls_ssl);

/* To rekey, we just 'resume' the session again */
if (connect_dtls_socket(vpninfo, &dtls_ssl, &dtls_fd)) {
/* Fall back to SSL */
if (ret == 1) {
printf("Established DTLS connection\n");

vpninfo->dtls_state = DTLS_RUNNING;

if (vpninfo->dtls_ssl) {
/* We are replacing an old connection */
SSL_free(vpninfo->dtls_ssl);
close(vpninfo->dtls_fd);
}
vpninfo->pfds[vpninfo->dtls_pfd].fd = vpninfo->new_dtls_fd;
vpninfo->dtls_ssl = vpninfo->new_dtls_ssl;
vpninfo->dtls_fd = vpninfo->new_dtls_fd;

vpninfo->pfds[vpninfo->new_dtls_pfd].fd = -1;
vpninfo->new_dtls_ssl = NULL;
vpninfo->new_dtls_fd = -1;

vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx =
vpninfo->dtls_times.last_tx = time(NULL);

return 0;
}

ret = SSL_get_error(vpninfo->new_dtls_ssl, ret);
if (ret == SSL_ERROR_WANT_WRITE || ret == SSL_ERROR_WANT_READ) {
if (time(NULL) < vpninfo->new_dtls_started + 5)
return 0;
if (verbose)
printf("DTLS handshake timed out\n");
} else if (verbose) {
fprintf(stderr, "DTLS handshake failed: %d\n", ret);
ERR_print_errors_fp(stderr);
}

/* Kill the new (failed) connection... */
SSL_free(vpninfo->new_dtls_ssl);
vpninfo->pfds[vpninfo->new_dtls_pfd].fd = -1;
close(vpninfo->new_dtls_fd);
vpninfo->new_dtls_ssl = NULL;
vpninfo->new_dtls_fd = -1;

/* ... and kill the old one too. The only time there'll be a valid
existing session is when it was a rekey, and in that case it's
time for the old one to die. */
if (vpninfo->dtls_ssl) {
SSL_free(vpninfo->dtls_ssl);
close(vpninfo->dtls_fd);
vpninfo->pfds[vpninfo->dtls_pfd].fd = -1;
vpninfo->dtls_ssl = NULL;
vpninfo->dtls_fd = -1;
return -EINVAL;
}

vpninfo->pfds[vpninfo->dtls_pfd].fd = dtls_fd;

SSL_free(vpninfo->dtls_ssl);
close(vpninfo->dtls_fd);
time(&vpninfo->new_dtls_started);
return -EINVAL;
}

vpninfo->dtls_ssl = dtls_ssl;
vpninfo->dtls_fd = dtls_fd;
static int dtls_restart(struct anyconnect_info *vpninfo)
{
if (vpninfo->dtls_ssl) {
SSL_free(vpninfo->dtls_ssl);
close(vpninfo->dtls_fd);
vpninfo->pfds[vpninfo->dtls_pfd].fd = -1;
vpninfo->dtls_ssl = NULL;
vpninfo->dtls_fd = -1;
}

return 0;
return connect_dtls_socket(vpninfo);
}


int setup_dtls(struct anyconnect_info *vpninfo)
{
struct vpn_option *dtls_opt = vpninfo->dtls_options;
Expand All @@ -232,7 +266,7 @@ int setup_dtls(struct anyconnect_info *vpninfo)
} else if (!strcmp(dtls_opt->option + 7, "Keepalive")) {
vpninfo->dtls_times.keepalive = atol(dtls_opt->value);
} else if (!strcmp(dtls_opt->option + 7, "DPD")) {
vpninfo->dtls_times.dpd = 10;//atol(dtls_opt->value);
vpninfo->dtls_times.dpd = atol(dtls_opt->value);
} else if (!strcmp(dtls_opt->option + 7, "Rekey-Time")) {
vpninfo->dtls_times.rekey = atol(dtls_opt->value);
}
Expand All @@ -254,11 +288,13 @@ int setup_dtls(struct anyconnect_info *vpninfo)
return -EINVAL;
}

if (connect_dtls_socket(vpninfo, &vpninfo->dtls_ssl, &vpninfo->dtls_fd))
return -EINVAL;

vpninfo->dtls_pfd = vpn_add_pollfd(vpninfo, vpninfo->dtls_fd,
vpninfo->dtls_pfd = vpn_add_pollfd(vpninfo, -1,
POLLIN|POLLHUP|POLLERR);
vpninfo->new_dtls_pfd = vpn_add_pollfd(vpninfo, -1,
POLLIN|POLLHUP|POLLERR);

if (connect_dtls_socket(vpninfo))
return -EINVAL;

if (verbose)
printf("DTLS connected. DPD %d, Keepalive %d\n",
Expand All @@ -279,7 +315,7 @@ int dtls_mainloop(struct anyconnect_info *vpninfo, int *timeout)
printf("Received DTLS packet 0x%02x of %d bytes\n",
buf[0], len);

//vpninfo->dtls_times.last_rx = time(NULL);
vpninfo->dtls_times.last_rx = time(NULL);

switch(buf[0]) {
case AC_PKT_DATA:
Expand Down Expand Up @@ -314,13 +350,12 @@ int dtls_mainloop(struct anyconnect_info *vpninfo, int *timeout)
}
}

if (verbose)
printf("Process DTLS keepalive...\n");
switch (keepalive_action(&vpninfo->dtls_times, timeout)) {
case KA_REKEY:
time(&vpninfo->dtls_times.last_rekey);
if (verbose)
printf("DTLS rekey due\n");
if (dtls_rekey(vpninfo)) {
if (connect_dtls_socket(vpninfo)) {
fprintf(stderr, "DTLS rekey failed\n");
return 1;
}
Expand All @@ -330,12 +365,9 @@ int dtls_mainloop(struct anyconnect_info *vpninfo, int *timeout)

case KA_DPD_DEAD:
fprintf(stderr, "DTLS Dead Peer Detection detected dead peer!\n");
if (dtls_rekey(vpninfo)) {
fprintf(stderr, "DTLS reconnect failed\n");
return 1;
}
work_done = 1;
break;
/* Fall back to SSL, and start a new DTLS connection */
dtls_restart(vpninfo);
return 1;

case KA_DPD:
if (verbose)
Expand Down Expand Up @@ -378,6 +410,19 @@ int dtls_mainloop(struct anyconnect_info *vpninfo, int *timeout)
this->hdr[7] = AC_PKT_DATA;

ret = SSL_write(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
if (ret <= 0) {
ret = SSL_get_error(vpninfo->dtls_ssl, ret);

/* If it's a real error, kill the DTLS connection and
requeue the packet to be sent over SSL */
if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE) {
fprintf(stderr, "DTLS got write error %d. Falling back to SSL\n", ret);
ERR_print_errors_fp(stderr);
dtls_restart(vpninfo);
vpninfo->outgoing_queue = this;
}
return 1;
}
time(&vpninfo->dtls_times.last_tx);
if (verbose) {
printf("Sent DTLS packet of %d bytes; SSL_write() returned %d\n",
Expand Down
3 changes: 2 additions & 1 deletion main.c
Expand Up @@ -105,10 +105,11 @@ int main(int argc, char **argv)

/* Set up some defaults */
vpninfo->ifname = "cisco0";
vpninfo->tun_fd = vpninfo->ssl_fd = vpninfo->dtls_fd = -1;
vpninfo->tun_fd = vpninfo->ssl_fd = vpninfo->dtls_fd = vpninfo->new_dtls_fd = -1;
vpninfo->useragent = "Open AnyConnect VPN Agent " ANYCONNECT_VERSION;
vpninfo->mtu = 1406;
vpninfo->deflate = 1;
vpninfo->dtls_attempt_period = 60;

if (RAND_bytes(vpninfo->dtls_secret, sizeof(vpninfo->dtls_secret)) != 1) {
fprintf(stderr, "Failed to initialise DTLS secret\n");
Expand Down
18 changes: 10 additions & 8 deletions mainloop.c
Expand Up @@ -84,7 +84,16 @@ int vpn_mainloop(struct anyconnect_info *vpninfo)
int did_work = 0;
int timeout = INT_MAX;

if (vpninfo->dtls_fd != -1)
if (vpninfo->new_dtls_ssl)
dtls_try_handshake(vpninfo);

if (!vpninfo->dtls_ssl && !vpninfo->new_dtls_ssl &&
vpninfo->new_dtls_started + vpninfo->dtls_attempt_period < time(NULL)) {
if (verbose)
printf("Attempt new DTLS connection\n");
connect_dtls_socket(vpninfo);
}
if (vpninfo->dtls_ssl)
did_work += dtls_mainloop(vpninfo, &timeout);

if (vpninfo->quit_reason)
Expand Down Expand Up @@ -169,8 +178,6 @@ int keepalive_action(struct keepalive_info *ka, int *timeout)
if (now >= due)
return KA_REKEY;

if (verbose)
printf("Rekey in %d seconds\n", (int)(due - now));
if (*timeout > (due - now) * 1000)
*timeout = (due - now) * 1000;
}
Expand All @@ -195,9 +202,6 @@ int keepalive_action(struct keepalive_info *ka, int *timeout)
ka->last_dpd = now;
return KA_DPD;
}

if (verbose)
printf("DPD in %d seconds\n", (int)(due - now));
if (*timeout > (due - now) * 1000)
*timeout = (due - now) * 1000;
}
Expand All @@ -211,8 +215,6 @@ int keepalive_action(struct keepalive_info *ka, int *timeout)
if (now >= due)
return KA_KEEPALIVE;

if (verbose)
printf("KA in %d seconds\n", (int)(due - now));
if (*timeout > (due - now) * 1000)
*timeout = (due - now) * 1000;
}
Expand Down
2 changes: 0 additions & 2 deletions ssl.c
Expand Up @@ -722,8 +722,6 @@ int ssl_mainloop(struct anyconnect_info *vpninfo, int *timeout)
goto handle_outgoing;
}

if (verbose)
printf("Process CSTP keepalive...\n");
switch (keepalive_action(&vpninfo->ssl_times, timeout)) {
case KA_REKEY:
/* Not that this will ever happen; we don't even process
Expand Down

0 comments on commit 097ba42

Please sign in to comment.