* 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;
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);
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)
/* 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 ||
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)
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)
{
_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);
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;
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.
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);
ret = 0;
cleanup:
- drop_usage_count(session);
+ drop_usage_count(session, send_buffer);
_mbuffer_head_clear(send_buffer);
/* SENDING -> WAITING state transition */
# 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
#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)
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++;
*
*/
+#ifndef HANDSHAKE_H
+#define HANDSHAKE_H
+
typedef enum Optional
{ OPTIONAL_PACKET, MANDATORY_PACKET } optional_t;
*/
#define AGAIN(target) (STATE==target?1:0)
#define AGAIN2(state, target) (state==target?1:0)
+
+#endif
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;
#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.
*/
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)
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)
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 ();
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.
*/
{
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 ();
_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;
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.
*/
* 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,
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],
/* 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],
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 */
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)
*/
* 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)
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);
*
* 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;
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 */
#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,
int sd, err;
char buffer[MAX_BUF + 1];
char portname[16] = { 0 };
+ int yes;
printf ("Resolving '%s'...\n", hostname);
/* get server name */
exit (1);
}
+ if (hints.ai_socktype == SOCK_DGRAM)
+ {
+ yes = 1;
+ if (setsockopt (sd, IPPROTO_IP, IP_DF,
+ (const void *) &yes, sizeof (yes)) < 0)
+ {
+ perror ("setsockopt(IP_DF) failed");
+ }
+ }
+
hd->secure = 0;
hd->fd = sd;
hd->hostname = strdup (hostname);
#include <netdb.h>
#include <unistd.h>
#include <netinet/in.h>
+#include <netinet/ip.h>
#include <signal.h>
#ifdef _WIN32
#include <io.h>
continue;
}
}
-
+ else
+ {
+ yes = 1;
+ if (setsockopt (s, IPPROTO_IP, IP_DF,
+ (const void *) &yes, sizeof (yes)) < 0)
+ {
+ perror ("setsockopt(IP_DF) failed");
+ }
+ }
+
if (bind (s, ptr->ai_addr, ptr->ai_addrlen) < 0)
{
perror ("bind() failed");
socklen_t cli_addr_size;
} priv_data_st;
-static int pull_timeout_func(gnutls_transport_ptr_t ptr, void* data, size_t data_size, unsigned int ms);
+static int pull_timeout_func(gnutls_transport_ptr_t ptr, unsigned int ms);
static ssize_t push_func (gnutls_transport_ptr_t p, const void * data, size_t size);
static ssize_t pull_func(gnutls_transport_ptr_t p, void * data, size_t size);
/* Wait for data to be received within a timeout period in milliseconds
*/
-static int pull_timeout_func(gnutls_transport_ptr_t ptr, void* data, size_t data_size, unsigned int ms)
+static int pull_timeout_func(gnutls_transport_ptr_t ptr, unsigned int ms)
{
fd_set rfds;
struct timeval tv;
if (ret <= 0)
return ret;
- if (data_size == 0)
- {
- data = &c;
- data_size = 1;
- }
-
/* only report ok if the next message is from the peer we expect
* from
*/
cli_addr_size = sizeof(cli_addr);
- ret = recvfrom(priv->fd, data, data_size, MSG_PEEK, (struct sockaddr*)&cli_addr, &cli_addr_size);
+ ret = recvfrom(priv->fd, &c, 1, MSG_PEEK, (struct sockaddr*)&cli_addr, &cli_addr_size);
if (ret > 0)
{
if (cli_addr_size == priv->cli_addr_size && memcmp(&cli_addr, priv->cli_addr, sizeof(cli_addr))==0)