diff options
author | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2011-03-20 14:18:57 +0100 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2011-03-20 14:21:14 +0100 |
commit | 67f4dba6e394b599a1958db8d5c414d401e43382 (patch) | |
tree | 475d83bf7c78a0abefe5ee50d4143d1efd79426b /lib | |
parent | 84e265447ef5f6cada0db92b9f475ab83c2f25a9 (diff) | |
download | gnutls-67f4dba6e394b599a1958db8d5c414d401e43382.tar.gz |
Avoided waiting for peer's retransmission to ensure receipt of finished messages, and used a 'timer'-like to retransmit packets.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/gnutls_buffers.c | 14 | ||||
-rw-r--r-- | lib/gnutls_buffers.h | 2 | ||||
-rw-r--r-- | lib/gnutls_dtls.c | 61 | ||||
-rw-r--r-- | lib/gnutls_dtls.h | 66 | ||||
-rw-r--r-- | lib/gnutls_handshake.c | 11 | ||||
-rw-r--r-- | lib/gnutls_handshake.h | 5 | ||||
-rw-r--r-- | lib/gnutls_int.h | 8 | ||||
-rw-r--r-- | lib/gnutls_record.c | 78 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 2 | ||||
-rw-r--r-- | lib/system.c | 26 | ||||
-rw-r--r-- | lib/system.h | 2 |
11 files changed, 193 insertions, 82 deletions
diff --git a/lib/gnutls_buffers.c b/lib/gnutls_buffers.c index 31e56b5264..778af8d0bc 100644 --- a/lib/gnutls_buffers.c +++ b/lib/gnutls_buffers.c @@ -600,7 +600,7 @@ _gnutls_io_write_flush (gnutls_session_t session) * on timeout and a negative value on error. */ int -_gnutls_io_check_recv (gnutls_session_t session, void* data, size_t data_size, unsigned int ms) +_gnutls_io_check_recv (gnutls_session_t session, unsigned int ms) { gnutls_transport_ptr_t fd = session->internals.transport_send_ptr; int ret = 0; @@ -609,7 +609,7 @@ _gnutls_io_check_recv (gnutls_session_t session, void* data, size_t data_size, u session->internals.pull_func != system_read) return gnutls_assert_val(GNUTLS_E_PULL_ERROR); - ret = session->internals.pull_timeout_func(fd, data, data_size, ms); + ret = session->internals.pull_timeout_func(fd, ms); if (ret == -1) return gnutls_assert_val(GNUTLS_E_PULL_ERROR); @@ -740,6 +740,8 @@ parse_handshake_header (gnutls_session_t session, mbuffer_st* bufel, gnutls_hand return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); dataptr = _mbuffer_get_udata_ptr(bufel); + data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size; + /* if reading a client hello of SSLv2 */ if (!IS_DTLS(session) && htype == GNUTLS_HANDSHAKE_CLIENT_HELLO && bufel->htype == GNUTLS_HANDSHAKE_CLIENT_HELLO_V2) @@ -785,14 +787,16 @@ parse_handshake_header (gnutls_session_t session, mbuffer_st* bufel, gnutls_hand /* make the length offset */ if (hsk->end_offset > 0) hsk->end_offset--; - _gnutls_handshake_log ("HSK[%p]: %s was received. Length %d, frag offset %d, frag length: %d, sequence: %d\n", + _gnutls_handshake_log ("HSK[%p]: %s was received. Length %d[%d], frag offset %d, frag length: %d, sequence: %d\n", session, _gnutls_handshake2str (hsk->htype), - (int) hsk->length, hsk->start_offset, hsk->end_offset-hsk->start_offset+1, (int)hsk->sequence); + (int) hsk->length, (int)data_size, hsk->start_offset, hsk->end_offset-hsk->start_offset+1, (int)hsk->sequence); hsk->header_size = handshake_header_size; memcpy(hsk->header, _mbuffer_get_udata_ptr(bufel), handshake_header_size); - data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size; + if (hsk->length > 0 && + (hsk->end_offset-hsk->start_offset >= data_size)) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); if (hsk->length > 0 && (hsk->start_offset >= hsk->end_offset || hsk->end_offset-hsk->start_offset >= data_size || diff --git a/lib/gnutls_buffers.h b/lib/gnutls_buffers.h index 60d2e6442a..bc432bdb7e 100644 --- a/lib/gnutls_buffers.h +++ b/lib/gnutls_buffers.h @@ -79,7 +79,7 @@ _gnutls_handshake_io_recv_int (gnutls_session_t session, ssize_t _gnutls_io_write_flush (gnutls_session_t session); int -_gnutls_io_check_recv (gnutls_session_t session, void* data, size_t data_size, unsigned int ms); +_gnutls_io_check_recv (gnutls_session_t session, unsigned int ms); ssize_t _gnutls_handshake_io_write_flush (gnutls_session_t session); inline static void _gnutls_handshake_buffer_clear(handshake_buffer_st* hsk) diff --git a/lib/gnutls_dtls.c b/lib/gnutls_dtls.c index 335e237657..076be8c643 100644 --- a/lib/gnutls_dtls.c +++ b/lib/gnutls_dtls.c @@ -46,7 +46,7 @@ transmit_message (gnutls_session_t session, opaque *data, *mtu_data; int ret = 0; unsigned int offset, frag_len, data_size; - const uint mtu = gnutls_dtls_get_mtu(session) - DTLS_HANDSHAKE_HEADER_SIZE; + const uint mtu = gnutls_dtls_get_data_mtu(session) - DTLS_HANDSHAKE_HEADER_SIZE; if (bufel->type == GNUTLS_CHANGE_CIPHER_SPEC) { @@ -104,14 +104,13 @@ transmit_message (gnutls_session_t session, _gnutls_handshake2str (bufel->htype), bufel->htype, data_size, offset, frag_len); - /* FIXME: We should collaborate with the record layer to pack as - * many records possible into a single datagram. We should also - * tell the record layer which epoch to use for encryption. - */ ret = _gnutls_send_int (session, bufel->type, bufel->htype, bufel->epoch, mtu_data, DTLS_HANDSHAKE_HEADER_SIZE + frag_len, 0); if (ret < 0) - break; + { + gnutls_assert(); + break; + } } gnutls_free (mtu_data); @@ -119,11 +118,9 @@ transmit_message (gnutls_session_t session, return ret; } -static int drop_usage_count(gnutls_session_t session) +static int drop_usage_count(gnutls_session_t session, mbuffer_head_st *const send_buffer) { int ret; - mbuffer_head_st *const send_buffer = - &session->internals.handshake_send_buffer; mbuffer_st *cur; for (cur = send_buffer->head; @@ -137,8 +134,26 @@ static int drop_usage_count(gnutls_session_t session) return 0; } -#define MAX_TIMEOUT 60000 -#define FINISHED_TIMEOUT 3000 +#define RETRANSMIT_WINDOW 2 + +/* This function is to be called from record layer once + * a handshake replay is detected. It will make sure + * it transmits only once per few seconds. Otherwise + * it is the same as _dtls_transmit(). + */ +int _dtls_retransmit(gnutls_session_t session) +{ +time_t now = time(0); + + if (now - session->internals.dtls.last_retransmit > RETRANSMIT_WINDOW) + { + session->internals.dtls.last_retransmit = now; + return _dtls_transmit(session); + } + else + return 0; + +} /* This function transmits the flight that has been previously * buffered. @@ -175,28 +190,22 @@ int ret; return gnutls_assert_val(ret); /* last message in handshake -> no ack */ - if (last_type == GNUTLS_HANDSHAKE_FINISHED && - ((session->security_parameters.entity == GNUTLS_SERVER && session->internals.resumed == RESUME_FALSE) || - (session->security_parameters.entity == GNUTLS_CLIENT && session->internals.resumed == RESUME_TRUE))) + if (last_type == GNUTLS_HANDSHAKE_FINISHED && _dtls_is_async(session)) { - opaque c; - ret = _gnutls_io_check_recv(session, &c, 1, FINISHED_TIMEOUT); - if (ret == GNUTLS_E_TIMEDOUT) - ret = 0; - else if (ret >= 0) - { - if (c == GNUTLS_HANDSHAKE) /* retransmit */ - ret = GNUTLS_E_TIMEDOUT; - } + /* we cannot do anything here. We just return 0 and + * if a retransmission occurs because peer didn't receive it + * we rely on the record layer calling this function again. + */ + return 0; } else /* all other messages -> implicit ack (receive of next flight) */ { - ret = _gnutls_io_check_recv(session, NULL, 0, timeout); + ret = _gnutls_io_check_recv(session, timeout); } total_timeout += timeout; timeout *= 2; - timeout %= MAX_TIMEOUT; + timeout %= MAX_DTLS_TIMEOUT; if (total_timeout >= session->internals.dtls.total_timeout) { ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT); @@ -214,7 +223,7 @@ int ret; ret = 0; cleanup: - drop_usage_count(session); + drop_usage_count(session, send_buffer); _mbuffer_head_clear(send_buffer); /* SENDING -> WAITING state transition */ diff --git a/lib/gnutls_dtls.h b/lib/gnutls_dtls.h index ab4e4da6fa..f8436b3731 100644 --- a/lib/gnutls_dtls.h +++ b/lib/gnutls_dtls.h @@ -26,8 +26,74 @@ # define DTLS_H #include "gnutls_int.h" +#include "gnutls_buffers.h" +#include "gnutls_mbuffers.h" int _dtls_transmit(gnutls_session_t session); +int _dtls_retransmit(gnutls_session_t session); int _dtls_record_check(gnutls_session_t session, uint64 * _seq); +#define MAX_DTLS_TIMEOUT 60000 + +/* returns true or false depending on whether we need to + * handle asynchronously handshake data. + */ +inline static int _dtls_is_async(gnutls_session_t session) +{ + if ((session->security_parameters.entity == GNUTLS_SERVER && session->internals.resumed == RESUME_FALSE) || + (session->security_parameters.entity == GNUTLS_CLIENT && session->internals.resumed == RESUME_TRUE)) + return 1; + else + return 0; +} + +inline static void _dtls_async_timer_init(gnutls_session_t session) +{ + if (_dtls_is_async(session)) + { + _gnutls_dtls_log ("DTLS[%p]: Initializing timer for handshake state.\n", session); + session->internals.dtls.async_term = time(0) + MAX_DTLS_TIMEOUT/1000; + } + else + session->internals.dtls.async_term = 0; +} + +inline static void _dtls_async_timer_delete(gnutls_session_t session) +{ + if (session->internals.dtls.async_term != 0) + { + _gnutls_dtls_log ("DTLS[%p]: Deinitializing handshake state.\n", session); + _gnutls_handshake_io_buffer_clear (session); + session->internals.dtls.async_term = 0; /* turn off "timer" */ + } +} + +/* Checks whether it is time to terminate the timer + */ +inline static void _dtls_async_timer_check(gnutls_session_t session) +{ + if (!IS_DTLS(session)) + return; + + if (session->internals.dtls.async_term != 0) + { + time_t now = time(0); + + /* check if we need to expire the queued handshake data */ + if (now > session->internals.dtls.async_term) + { + _dtls_async_timer_delete(session); + } + } +} + +/* Returns non-zero if the async timer is active */ +inline static int _dtls_async_timer_active(gnutls_session_t session) +{ + if (!IS_DTLS(session)) + return 0; + + return session->internals.dtls.async_term; +} + #endif diff --git a/lib/gnutls_handshake.c b/lib/gnutls_handshake.c index 7e0608da3c..fb4c3c2f77 100644 --- a/lib/gnutls_handshake.c +++ b/lib/gnutls_handshake.c @@ -56,6 +56,7 @@ #include <auth_anon.h> /* for gnutls_anon_server_credentials_t */ #include <auth_psk.h> /* for gnutls_psk_server_credentials_t */ #include <random.h> +#include <gnutls_dtls.h> #ifdef HANDSHAKE_DEBUG #define ERR(x, y) _gnutls_handshake_log("HSK[%p]: %s (%d)\n", session, x,y) @@ -2603,7 +2604,15 @@ gnutls_handshake (gnutls_session_t session) STATE = STATE0; - _gnutls_handshake_io_buffer_clear (session); + if (IS_DTLS(session)==0) + { + _gnutls_handshake_io_buffer_clear (session); + } + else + { + _dtls_async_timer_init(session); + } + _gnutls_handshake_internal_state_clear (session); session->security_parameters.epoch_next++; diff --git a/lib/gnutls_handshake.h b/lib/gnutls_handshake.h index 6792bbe0ce..656c9093b8 100644 --- a/lib/gnutls_handshake.h +++ b/lib/gnutls_handshake.h @@ -23,6 +23,9 @@ * */ +#ifndef HANDSHAKE_H +#define HANDSHAKE_H + typedef enum Optional { OPTIONAL_PACKET, MANDATORY_PACKET } optional_t; @@ -63,3 +66,5 @@ void _gnutls_handshake_hash_buffers_clear (gnutls_session_t session); */ #define AGAIN(target) (STATE==target?1:0) #define AGAIN2(state, target) (state==target?1:0) + +#endif diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 1c18817571..4884888aa5 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -593,6 +593,14 @@ typedef struct uint64_t record_sw[DTLS_RECORD_WINDOW_SIZE]; int record_sw_size; + + /* timers to handle async handshake after gnutls_handshake() + * has terminated. Required to handle retransmissions. + */ + time_t async_term; + + /* last retransmission triggered by record layer */ + time_t last_retransmit; } dtls_st; diff --git a/lib/gnutls_record.c b/lib/gnutls_record.c index cc259c4d05..19f6aa3d1f 100644 --- a/lib/gnutls_record.c +++ b/lib/gnutls_record.c @@ -48,6 +48,19 @@ #include <gnutls_dtls.h> #include <gnutls_dh.h> +struct tls_record_st { + uint16_t header_size; + uint8_t version[2]; + uint64 sequence; /* DTLS */ + uint16_t length; + uint16_t packet_size; /* header_size + length */ + content_type_t type; + uint16_t epoch; /* valid in DTLS only */ + int v2:1; /* whether an SSLv2 client hello */ + /* the data */ +}; + + /** * gnutls_protocol_get_version: * @session: is a #gnutls_session_t structure. @@ -578,7 +591,7 @@ record_check_version (gnutls_session_t session, */ static int record_add_to_buffers (gnutls_session_t session, - content_type_t recv_type, content_type_t type, + struct tls_record_st *recv, content_type_t type, gnutls_handshake_description_t htype, uint64* seq, mbuffer_st* bufel) @@ -586,18 +599,22 @@ record_add_to_buffers (gnutls_session_t session, int ret; - if ((recv_type == type) + if ((recv->type == type) && (type == GNUTLS_APPLICATION_DATA || type == GNUTLS_CHANGE_CIPHER_SPEC || type == GNUTLS_HANDSHAKE)) { _gnutls_record_buffer_put (session, type, seq, bufel); + + /* if we received application data as expected then we + * deactivate the async timer */ + _dtls_async_timer_delete(session); } else { /* if the expected type is different than the received */ - switch (recv_type) + switch (recv->type) { case GNUTLS_ALERT: if (bufel->msg.size < 2) @@ -651,9 +668,15 @@ record_add_to_buffers (gnutls_session_t session, return GNUTLS_E_UNEXPECTED_PACKET; case GNUTLS_APPLICATION_DATA: + if (session->internals.initial_negotiation_completed == 0) + { + ret = gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); + goto cleanup; + } + /* even if data is unexpected put it into the buffer */ if ((ret = - _gnutls_record_buffer_put (session, recv_type, seq, + _gnutls_record_buffer_put (session, recv->type, seq, bufel)) < 0) { gnutls_assert (); @@ -678,6 +701,15 @@ record_add_to_buffers (gnutls_session_t session, break; case GNUTLS_HANDSHAKE: + /* In DTLS we might receive a handshake replay from the peer to indicate + * the our last TLS handshake messages were not received. + */ + if (_dtls_is_async(session) && _dtls_async_timer_active(session)) + { + ret = _dtls_retransmit(session); + goto cleanup; + } + /* This is legal if HELLO_REQUEST is received - and we are a client. * If we are a server, a client may initiate a renegotiation at any time. */ @@ -685,7 +717,7 @@ record_add_to_buffers (gnutls_session_t session, { gnutls_assert (); ret = - _gnutls_record_buffer_put (session, recv_type, seq, bufel); + _gnutls_record_buffer_put (session, recv->type, seq, bufel); if (ret < 0) { gnutls_assert (); @@ -708,7 +740,7 @@ record_add_to_buffers (gnutls_session_t session, _gnutls_record_log ("REC[%p]: Received Unknown packet %d expecting %d\n", - session, recv_type, type); + session, recv->type, type); gnutls_assert (); ret = GNUTLS_E_INTERNAL_ERROR; @@ -737,17 +769,6 @@ int ret; return ret; } -struct tls_record_st { - uint16_t header_size; - uint8_t version[2]; - uint64 sequence; /* DTLS */ - uint16_t length; - uint16_t packet_size; /* header_size + length */ - content_type_t type; - int v2:1; /* whether an SSLv2 client hello */ - /* the data */ -}; - /* Checks the record headers and returns the length, version and * content type. */ @@ -784,6 +805,7 @@ record_read_headers (gnutls_session_t session, * V2 compatibility is a mess. */ record->v2 = 1; + record->epoch = 0; _gnutls_record_log ("REC[%p]: SSL 2.0 %s packet received. Length: %d\n", session, @@ -799,14 +821,18 @@ record_read_headers (gnutls_session_t session, record->type = headers[0]; record->version[0] = headers[1]; record->version[1] = headers[2]; - + if(IS_DTLS(session)) { memcpy(record->sequence.i, &headers[3], 8); record->length = _gnutls_read_uint16 (&headers[11]); + record->epoch = _gnutls_read_uint16(record->sequence.i); } else - record->length = _gnutls_read_uint16 (&headers[3]); + { + record->length = _gnutls_read_uint16 (&headers[3]); + record->epoch = 0; + } _gnutls_record_log ("REC[%p]: SSL %d.%d %s packet received. Length: %d\n", session, (int)record->version[0], (int)record->version[1], @@ -850,9 +876,7 @@ gnutls_datum_t raw; /* raw headers */ /* Check if the DTLS epoch is valid */ if (IS_DTLS(session)) { - uint16_t epoch = _gnutls_read_uint16(record->sequence.i); - - if (_gnutls_epoch_is_valid(session, epoch) == 0) + if (_gnutls_epoch_is_valid(session, record->epoch) == 0) { _gnutls_audit_log("Discarded message[%u] with invalid epoch 0x%.2x%.2x.\n", (unsigned int)_gnutls_uint64touint32 (&record->sequence), (int)record->sequence.i[0], @@ -1049,7 +1073,7 @@ begin: decrypted->htype = -1; ret = - record_add_to_buffers (session, record.type, type, htype, + record_add_to_buffers (session, &record, type, htype, packet_sequence, decrypted); /* bufel is now either deinitialized or buffered somewhere else */ @@ -1142,6 +1166,8 @@ _gnutls_recv_int (gnutls_session_t session, content_type_t type, return GNUTLS_E_INVALID_SESSION; } + _dtls_async_timer_check(session); + /* If we have enough data in the cache do not bother receiving * a new packet. (in order to flush the cache) */ @@ -1220,9 +1246,9 @@ gnutls_record_send (gnutls_session_t session, const void *data, * initiated a handshake. In that case the server can only initiate a * handshake or terminate the connection. * - * Returns: the number of bytes received and zero on EOF. A negative - * error code is returned in case of an error. The number of bytes - * received might be less than @data_size. + * Returns: the number of bytes received and zero on EOF (for stream + * connections). A negative error code is returned in case of an error. + * The number of bytes received might be less than the requested @data_size. **/ ssize_t gnutls_record_recv (gnutls_session_t session, void *data, size_t data_size) diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index cc64d6290a..dfcf92a12e 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -1180,7 +1180,7 @@ extern "C" typedef ssize_t (*gnutls_push_func) (gnutls_transport_ptr_t, const void *, size_t); - typedef int (*gnutls_pull_timeout_func) (gnutls_transport_ptr_t, void*data, size_t size, unsigned int ms); + typedef int (*gnutls_pull_timeout_func) (gnutls_transport_ptr_t, unsigned int ms); typedef ssize_t (*gnutls_vec_push_func) (gnutls_transport_ptr_t, const giovec_t * iov, int iovcnt); diff --git a/lib/system.c b/lib/system.c index dd2d74fc20..120fc8b0ca 100644 --- a/lib/system.c +++ b/lib/system.c @@ -111,7 +111,7 @@ system_read_peek (gnutls_transport_ptr_t ptr, void *data, size_t data_size) * * Returns -1 on error, 0 on timeout. */ -int system_recv_timeout(gnutls_transport_ptr_t ptr, void* data, size_t data_size, unsigned int ms) +int system_recv_timeout(gnutls_transport_ptr_t ptr, unsigned int ms) { fd_set rfds; struct timeval tv; @@ -128,27 +128,11 @@ int ret, ret2; return ret; - if (data_size == 0) - { - ret2 = recv(GNUTLS_POINTER_TO_INT(ptr), NULL, 0, MSG_PEEK); - if (ret2 == -1) - return ret2; + ret2 = recv(GNUTLS_POINTER_TO_INT(ptr), NULL, 0, MSG_PEEK); + if (ret2 == -1) + return ret2; - return ret; - } - - /* only report ok if the next message is from the peer we expect - * from - */ - ret = recv(GNUTLS_POINTER_TO_INT(ptr), data, data_size, MSG_PEEK); - if (ret > 0) - { - return ret; - } - else - { - return -1; - } + return ret; } /* Thread stuff */ diff --git a/lib/system.h b/lib/system.h index bdda73e4ca..b1f556557a 100644 --- a/lib/system.h +++ b/lib/system.h @@ -8,7 +8,7 @@ #endif int system_errno (gnutls_transport_ptr_t); -int system_recv_timeout(gnutls_transport_ptr_t ptr,void*data, size_t, unsigned int ms); +int system_recv_timeout(gnutls_transport_ptr_t ptr, unsigned int ms); #ifdef _WIN32 ssize_t system_write (gnutls_transport_ptr_t ptr, const void *data, |