--- apps/s_cb.c 2010-01-06 22:25:22.000000000 +0100 +++ apps/s_cb.c 2010-04-02 17:40:09.000000000 +0200 @@ -597,6 +597,24 @@ } } } + + if (content_type == 24) /* Heartbeat */ + { + str_details1 = ", Heartbeat"; + + if (len > 0) + { + switch (((const unsigned char*)buf)[0]) + { + case 1: + str_details1 = ", HeartbeatRequest"; + break; + case 2: + str_details1 = ", HeartbeatResponse"; + break; + } + } + } } BIO_printf(bio, "%s %s%s [length %04lx]%s%s\n", str_write_p, str_version, str_content_type, (unsigned long)len, str_details1, str_details2); --- apps/s_client.c 2009-12-16 21:28:28.000000000 +0100 +++ apps/s_client.c 2010-04-02 17:40:09.000000000 +0200 @@ -1563,7 +1563,13 @@ goto shut; } - if ((!c_ign_eof) && (cbuf[0] == 'R')) + if ((SSL_version(con) == DTLS1_VERSION) && (!c_ign_eof) && (cbuf[0] == 'B')) + { + BIO_printf(bio_err,"HEARTBEATING\n"); + DTLSv1_send_heartbeat(con); + cbuf_len=0; + } + else if ((!c_ign_eof) && (cbuf[0] == 'R')) { BIO_printf(bio_err,"RENEGOTIATING\n"); SSL_renegotiate(con); --- apps/s_server.c 2010-01-28 18:52:18.000000000 +0100 +++ apps/s_server.c 2010-04-02 17:40:09.000000000 +0200 @@ -2002,6 +2002,14 @@ goto err; } + if ((SSL_version(con) == DTLS1_VERSION) && (buf[0] == 'B') && + ((buf[1] == '\n') || (buf[1] == '\r'))) + { + BIO_printf(bio_err,"HEARTBEATING\n"); + DTLSv1_send_heartbeat(con); + i=0; + continue; + } if ((buf[0] == 'r') && ((buf[1] == '\n') || (buf[1] == '\r'))) { --- ssl/d1_both.c 2010-03-25 00:16:49.000000000 +0100 +++ ssl/d1_both.c 2010-04-02 17:40:09.000000000 +0200 @@ -935,7 +935,7 @@ return code; } - if ( ! SSL_in_init(s)) /* done, no need to send a retransmit */ + if ( ! SSL_in_init(s) && !s->d1->heartbeat_inflight) /* done, no need to send a retransmit */ { BIO_set_flags(SSL_get_rbio(s), BIO_FLAGS_READ); return code; --- ssl/d1_clnt.c 2010-01-26 20:46:29.000000000 +0100 +++ ssl/d1_clnt.c 2010-04-02 17:40:09.000000000 +0200 @@ -164,6 +164,17 @@ s->in_handshake++; if (!SSL_in_init(s) || SSL_in_before(s)) SSL_clear(s); + /* If we're awaiting a HeartbeatResponse, pretend we + * already got and don't await it anymore, because + * Heartbeats don't make sense during handshakes anyway. + */ + if (s->d1->heartbeat_inflight) + { + dtls1_stop_timer(s); + s->d1->heartbeat_inflight = 0; + s->d1->heartbeat_seq++; + } + for (;;) { state=s->state; --- ssl/d1_lib.c 2009-12-08 12:38:17.000000000 +0100 +++ ssl/d1_lib.c 2010-04-02 17:40:09.000000000 +0200 @@ -207,6 +207,9 @@ case DTLS_CTRL_LISTEN: ret = dtls1_listen(s, parg); break; + case DTLS_CTRL_SEND_HEARTBEAT: + ret = dtls1_send_heartbeat(s, 0); + break; default: ret = ssl3_ctrl(s, cmd, larg, parg); @@ -348,8 +351,16 @@ state->timeout.read_timeouts = 1; } - dtls1_start_timer(s); - return dtls1_retransmit_buffered_messages(s); + if (s->d1->heartbeat_inflight) + { + s->d1->heartbeat_inflight = 0; + return dtls1_send_heartbeat(s, 0); + } + else + { + dtls1_start_timer(s); + return dtls1_retransmit_buffered_messages(s); + } } static void get_current_time(struct timeval *t) --- ssl/d1_pkt.c 2009-10-04 18:52:35.000000000 +0200 +++ ssl/d1_pkt.c 2010-04-02 17:40:09.000000000 +0200 @@ -881,6 +881,78 @@ dest = s->d1->alert_fragment; dest_len = &s->d1->alert_fragment_len; } + else if (rr->type == DTLS1_RT_HEARTBEAT) + { + unsigned char *p = &rr->data[0], *payload; + unsigned short hbtype; + unsigned int length, padding; + + /* Read type and length first */ + hbtype = *p++ & 0xff; + n2s(p, length); + payload = p; + p += length; + n2s(p, padding); + + if (s->msg_callback) + s->msg_callback(0, s->version, DTLS1_RT_HEARTBEAT, + &rr->data[0], 5 + length + padding, + s, s->msg_callback_arg); + + if (hbtype == DTLS1_HB_REQUEST) + { + unsigned char *buffer, *bp; + int r; + + /* Allocate memory for the response, size is payload + * length plus 1 byte type and 2 bytes length + * and 2 bytes length + */ + buffer = OPENSSL_malloc(length + 5); + bp = buffer; + + /* Enter response type, length and copy payload */ + *bp++ = DTLS1_HB_RESPONSE & 0xff; + s2n(length, bp); + memcpy(bp, payload, length); + bp += length; + s2n(0, bp); + + r = do_dtls1_write(s, DTLS1_RT_HEARTBEAT, buffer, 5 + length, 0); + + if (r >= 0 && s->msg_callback) + s->msg_callback(1, s->version, DTLS1_RT_HEARTBEAT, + buffer, 5 + length, + s, s->msg_callback_arg); + + OPENSSL_free(buffer); + + if (r < 0) + return r; + } + else if (hbtype == DTLS1_HB_RESPONSE) + { + unsigned int seq; + + /* We only send sequence numbers (2 bytes unsigned int), + * so we expect nothing else and just try to read */ + n2s(payload, seq); + + if (length == 2 && seq == s->d1->heartbeat_seq) + { + dtls1_stop_timer(s); + s->d1->heartbeat_seq++; + s->d1->heartbeat_inflight = 0; + } + } + + /* Exit and notify application to read again */ + rr->length = 0; + s->rwstate=SSL_READING; + BIO_clear_retry_flags(SSL_get_rbio(s)); + BIO_set_retry_read(SSL_get_rbio(s)); + return(-1); + } /* else it's a CCS message, or application data or wrong */ else if (rr->type != SSL3_RT_CHANGE_CIPHER_SPEC) { @@ -1773,3 +1845,69 @@ { memset(&(s->d1->timeout), 0x00, sizeof(struct dtls1_timeout_st)); } + +int +dtls1_send_heartbeat(SSL *s, unsigned int padding) + { + unsigned char *buf, *p; + int length = 2, ret; + + /* Only send if peer supports and accepts heartbeats... */ + if (s->d1->dtlsext_heartbeat != 1) + { + SSLerr(SSL_F_DTLS1_SEND_HEARTBEAT,SSL_R_DTLS_HEARTBEAT_PEER_DOESNT_ACCEPT); + return -1; + } + + /* ...and there is none in flight yet... */ + if (s->d1->heartbeat_inflight) + { + SSLerr(SSL_F_DTLS1_SEND_HEARTBEAT,SSL_R_DTLS_HEARTBEAT_INFLIGHT); + return -1; + } + + /* ...and no handshake in progress. */ + if (SSL_in_init(s) || s->in_handshake) + { + SSLerr(SSL_F_DTLS1_SEND_HEARTBEAT,SSL_R_UNEXPECTED_MESSAGE); + return -1; + } + + /* Check if padding is too long, payload and padding + * must not exceed 2^14 - 5 = 16379 bytes in total. + * We just use a sequence number with 2 bytes as payload. + */ + OPENSSL_assert(2 + padding <= 16379); + + /* Create HeartBeat message, we just use a sequence number + * as payload to distuingish different messages. + * - Request Type, 1 byte + * - Payload Length, 2 bytes (unsigned int) + * - Payload, the sequence number (2 bytes uint) + * - Padding Length, 2 bytes (unsigned int) + * - Padding + */ + buf = OPENSSL_malloc(1 + 2 + length + 2 + padding); + p = buf; + *p++ = DTLS1_HB_REQUEST & 0xff; + s2n(length, p); + s2n(s->d1->heartbeat_seq, p); + s2n(padding, p); + memset(p, 0, padding); + + ret = do_dtls1_write(s, DTLS1_RT_HEARTBEAT, buf, 5 + length + padding, 0); + if (ret >= 0) + { + if (s->msg_callback) + s->msg_callback(1, s->version, DTLS1_RT_HEARTBEAT, + buf, 5 + length + padding, + s, s->msg_callback_arg); + + dtls1_start_timer(s); + s->d1->heartbeat_inflight = 1; + } + + OPENSSL_free(buf); + + return ret; + } --- ssl/d1_srvr.c 2010-02-01 17:49:42.000000000 +0100 +++ ssl/d1_srvr.c 2010-04-02 17:40:09.000000000 +0200 @@ -170,6 +170,17 @@ return(-1); } + /* If we're awaiting a HeartbeatResponse, pretend we + * already got and don't await it anymore, because + * Heartbeats don't make sense during handshakes anyway. + */ + if (s->d1->heartbeat_inflight) + { + dtls1_stop_timer(s); + s->d1->heartbeat_inflight = 0; + s->d1->heartbeat_seq++; + } + for (;;) { state=s->state; --- ssl/dtls1.h 2009-09-09 19:05:42.000000000 +0200 +++ ssl/dtls1.h 2010-04-02 17:40:09.000000000 +0200 @@ -105,7 +105,8 @@ #define DTLS1_AL_HEADER_LENGTH 2 #endif - +#define DTLSEXT_TYPE_heartbeat 40 + typedef struct dtls1_bitmap_st { unsigned long map; /* track 32 packets on 32-bit systems @@ -226,7 +227,7 @@ struct dtls1_timeout_st timeout; - /* Indicates when the last handshake msg sent will timeout */ + /* Indicates when the last handshake msg or heartbeat sent will timeout */ struct timeval next_timeout; /* Timeout duration */ @@ -241,6 +242,11 @@ unsigned int retransmitting; unsigned int change_cipher_spec_ok; + + unsigned int dtlsext_heartbeat; + + unsigned int heartbeat_seq; + unsigned int heartbeat_inflight; } DTLS1_STATE; --- ssl/ssl.h 2010-01-06 18:37:38.000000000 +0100 +++ ssl/ssl.h 2010-04-02 17:40:09.000000000 +0200 @@ -1413,6 +1413,7 @@ #define DTLS_CTRL_GET_TIMEOUT 73 #define DTLS_CTRL_HANDLE_TIMEOUT 74 #define DTLS_CTRL_LISTEN 75 +#define DTLS_CTRL_SEND_HEARTBEAT 80 #define SSL_CTRL_GET_RI_SUPPORT 76 #define SSL_CTRL_CLEAR_OPTIONS 77 @@ -1424,6 +1425,8 @@ SSL_ctrl(ssl,DTLS_CTRL_HANDLE_TIMEOUT,0, NULL) #define DTLSv1_listen(ssl, peer) \ SSL_ctrl(ssl,DTLS_CTRL_LISTEN,0, (void *)peer) +#define DTLSv1_send_heartbeat(ssl) \ + SSL_ctrl(ssl,DTLS_CTRL_SEND_HEARTBEAT,0, NULL) #define SSL_session_reused(ssl) \ SSL_ctrl((ssl),SSL_CTRL_GET_SESSION_REUSED,0,NULL) @@ -1848,6 +1851,7 @@ #define SSL_F_DTLS1_SEND_SERVER_HELLO 266 #define SSL_F_DTLS1_SEND_SERVER_KEY_EXCHANGE 267 #define SSL_F_DTLS1_WRITE_APP_DATA_BYTES 268 +#define SSL_F_DTLS1_SEND_HEARTBEAT 269 #define SSL_F_GET_CLIENT_FINISHED 105 #define SSL_F_GET_CLIENT_HELLO 106 #define SSL_F_GET_CLIENT_MASTER_KEY 107 @@ -2253,6 +2257,8 @@ #define SSL_R_TLS_INVALID_ECPOINTFORMAT_LIST 157 #define SSL_R_TLS_PEER_DID_NOT_RESPOND_WITH_CERTIFICATE_LIST 233 #define SSL_R_TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG 234 +#define SSL_R_DTLS_HEARTBEAT_PEER_DOESNT_ACCEPT 1500 +#define SSL_R_DTLS_HEARTBEAT_INFLIGHT 1501 #define SSL_R_TRIED_TO_USE_UNSUPPORTED_CIPHER 235 #define SSL_R_UNABLE_TO_DECODE_DH_CERTS 236 #define SSL_R_UNABLE_TO_DECODE_ECDH_CERTS 313 --- ssl/ssl3.h 2010-01-06 18:37:38.000000000 +0100 +++ ssl/ssl3.h 2010-04-02 17:40:09.000000000 +0200 @@ -322,10 +322,14 @@ #define SSL3_RT_ALERT 21 #define SSL3_RT_HANDSHAKE 22 #define SSL3_RT_APPLICATION_DATA 23 +#define DTLS1_RT_HEARTBEAT 24 #define SSL3_AL_WARNING 1 #define SSL3_AL_FATAL 2 +#define DTLS1_HB_REQUEST 1 +#define DTLS1_HB_RESPONSE 2 + #define SSL3_AD_CLOSE_NOTIFY 0 #define SSL3_AD_UNEXPECTED_MESSAGE 10 /* fatal */ #define SSL3_AD_BAD_RECORD_MAC 20 /* fatal */ --- ssl/ssl_err.c 2010-01-06 18:37:38.000000000 +0100 +++ ssl/ssl_err.c 2010-04-02 17:40:09.000000000 +0200 @@ -103,6 +103,7 @@ {ERR_FUNC(SSL_F_DTLS1_SEND_SERVER_HELLO), "DTLS1_SEND_SERVER_HELLO"}, {ERR_FUNC(SSL_F_DTLS1_SEND_SERVER_KEY_EXCHANGE), "DTLS1_SEND_SERVER_KEY_EXCHANGE"}, {ERR_FUNC(SSL_F_DTLS1_WRITE_APP_DATA_BYTES), "DTLS1_WRITE_APP_DATA_BYTES"}, +{ERR_FUNC(SSL_F_DTLS1_SEND_HEARTBEAT), "SSL_F_DTLS1_SEND_HEARTBEAT"}, {ERR_FUNC(SSL_F_GET_CLIENT_FINISHED), "GET_CLIENT_FINISHED"}, {ERR_FUNC(SSL_F_GET_CLIENT_HELLO), "GET_CLIENT_HELLO"}, {ERR_FUNC(SSL_F_GET_CLIENT_MASTER_KEY), "GET_CLIENT_MASTER_KEY"}, @@ -511,6 +512,8 @@ {ERR_REASON(SSL_R_TLS_INVALID_ECPOINTFORMAT_LIST),"tls invalid ecpointformat list"}, {ERR_REASON(SSL_R_TLS_PEER_DID_NOT_RESPOND_WITH_CERTIFICATE_LIST),"tls peer did not respond with certificate list"}, {ERR_REASON(SSL_R_TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG),"tls rsa encrypted value length is wrong"}, +{ERR_REASON(SSL_R_DTLS_HEARTBEAT_PEER_DOESNT_ACCEPT),"peer does not accept heartbearts"}, +{ERR_REASON(SSL_R_DTLS_HEARTBEAT_INFLIGHT),"heartbeat already in flight"}, {ERR_REASON(SSL_R_TRIED_TO_USE_UNSUPPORTED_CIPHER),"tried to use unsupported cipher"}, {ERR_REASON(SSL_R_UNABLE_TO_DECODE_DH_CERTS),"unable to decode dh certs"}, {ERR_REASON(SSL_R_UNABLE_TO_DECODE_ECDH_CERTS),"unable to decode ecdh certs"}, --- ssl/ssl_locl.h 2009-12-08 12:38:18.000000000 +0100 +++ ssl/ssl_locl.h 2010-04-02 17:40:09.000000000 +0200 @@ -944,6 +944,7 @@ long dtls1_default_timeout(void); struct timeval* dtls1_get_timeout(SSL *s, struct timeval* timeleft); int dtls1_handle_timeout(SSL *s); +int dtls1_send_heartbeat(SSL *s, unsigned int padding); const SSL_CIPHER *dtls1_get_cipher(unsigned int u); void dtls1_start_timer(SSL *s); void dtls1_stop_timer(SSL *s); --- ssl/t1_lib.c 2010-02-17 19:38:10.000000000 +0100 +++ ssl/t1_lib.c 2010-04-02 17:40:09.000000000 +0200 @@ -494,6 +494,17 @@ i2d_X509_EXTENSIONS(s->tlsext_ocsp_exts, &ret); } + if (s->version == DTLS1_VERSION) + { + /* Add heartbeat extension */ + s2n(DTLSEXT_TYPE_heartbeat,ret); + s2n(1,ret); + /* We allow the peer to send heartbeats, there is no API yet + * to let the application choose. + */ + *(ret++) = 0x01 & 0xff; + } + if ((extdatalen = ret-p-2)== 0) return p; @@ -618,6 +629,17 @@ } + if (s->version == DTLS1_VERSION) + { + /* Add heartbeat extension */ + s2n(DTLSEXT_TYPE_heartbeat,ret); + s2n(1,ret); + /* We allow the peer to send heartbeats, there is no API yet + * to let the application choose. + */ + *(ret++) = 0x01 & 0xff; + } + if ((extdatalen = ret-p-2)== 0) return p; @@ -958,6 +980,18 @@ else s->tlsext_status_type = -1; } + else if (type == DTLSEXT_TYPE_heartbeat) + { + switch(data[0] & 0xff) + { + case 0x01: /* Client allows us to send heartbeats */ + s->d1->dtlsext_heartbeat = 1; + break; + case 0x02: /* Client doesn't accept heatbeats */ + s->d1->dtlsext_heartbeat = 2; + break; + } + } /* session ticket processed earlier */ data+=size; @@ -1016,6 +1050,18 @@ } tlsext_servername = 1; } + else if (type == DTLSEXT_TYPE_heartbeat) + { + switch(data[0] & 0xff) + { + case 0x01: /* Client allows us to send heartbeats */ + s->d1->dtlsext_heartbeat = 1; + break; + case 0x02: /* Client doesn't accept heatbeats */ + s->d1->dtlsext_heartbeat = 2; + break; + } + } #ifndef OPENSSL_NO_EC else if (type == TLSEXT_TYPE_ec_point_formats &&