changeset 1188:e1ced4db7f67

Backup work in progress on DTLS, not usable
author Sebastien Decugis <sdecugis@freediameter.net>
date Tue, 11 Jun 2013 18:13:29 +0800
parents b4a0f9ee3129
children 76ac4bb75f0e
files CMakeLists.txt include/freeDiameter/libfdcore.h libfdcore/CMakeLists.txt libfdcore/cnxctx.c libfdcore/cnxctx.h libfdcore/sctp_dtls.c
diffstat 6 files changed, 337 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Jun 10 12:05:38 2013 +0800
+++ b/CMakeLists.txt	Tue Jun 11 18:13:29 2013 +0800
@@ -84,6 +84,7 @@
 ENDIF(APPLE) 
 
 # Location for the include files
+SET(CMAKE_include_directories_BEFORE ON)
 INCLUDE_DIRECTORIES(include)
 INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}/include)
 SUBDIRS(include/freeDiameter)
--- a/include/freeDiameter/libfdcore.h	Mon Jun 10 12:05:38 2013 +0800
+++ b/include/freeDiameter/libfdcore.h	Tue Jun 11 18:13:29 2013 +0800
@@ -44,6 +44,10 @@
 #include <freeDiameter/libfdproto.h>
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
+#ifdef GNUTLS_VERSION_300
+#include <gnutls/dtls.h>
+#endif /* GNUTLS_VERSION_300 */
+
 
 /* GNUTLS version */
 #ifndef GNUTLS_VERSION
--- a/libfdcore/CMakeLists.txt	Mon Jun 10 12:05:38 2013 +0800
+++ b/libfdcore/CMakeLists.txt	Tue Jun 11 18:13:29 2013 +0800
@@ -38,7 +38,7 @@
 	)
 
 IF(NOT DISABLE_SCTP)
-	SET(FDCORE_SRC ${FDCORE_SRC} sctp.c sctp3436.c)
+	SET(FDCORE_SRC ${FDCORE_SRC} sctp.c sctp3436.c sctp_dtls.c)
 ENDIF(NOT DISABLE_SCTP)
 
 SET(FDCORE_GEN_SRC
--- a/libfdcore/cnxctx.c	Mon Jun 10 12:05:38 2013 +0800
+++ b/libfdcore/cnxctx.c	Tue Jun 11 18:13:29 2013 +0800
@@ -1136,13 +1136,8 @@
 /* Prepare a gnutls session object for handshake */
 int fd_tls_prepare(gnutls_session_t * session, int mode, int dtls, char * priority, void * alt_creds)
 {
-	if (dtls) {
-		LOG_E("DTLS sessions not yet supported");
-		return ENOTSUP;
-	}
-
 	/* Create the session context */
-	CHECK_GNUTLS_DO( gnutls_init (session, mode), return ENOMEM );
+	CHECK_GNUTLS_DO( gnutls_init (session, mode | (dtls ? GNUTLS_DATAGRAM : 0 )), return ENOMEM );
 
 	/* Set the algorithm suite */
 	if (priority) {
@@ -1152,6 +1147,11 @@
 	} else {
 		CHECK_GNUTLS_DO( gnutls_priority_set( *session, fd_g_config->cnf_sec_data.prio_cache ), return EINVAL );
 	}
+	
+	/* Set DTLS-specific parameters */
+	if (dtls) {
+		CHECK_FCT_DO( fd_sctp_dtls_prepare(*session), return EINVAL);
+	}
 
 	/* Set the credentials of this side of the connection */
 	CHECK_GNUTLS_DO( gnutls_credentials_set (*session, GNUTLS_CRD_CERTIFICATE, alt_creds ?: fd_g_config->cnf_sec_data.credentials), return EINVAL );
@@ -1670,11 +1670,11 @@
 		CHECK_FCT( fd_sctp3436_init(conn) );
 #endif /* DISABLE_SCTP */
 	} else {
-		/* Set the transport pointer passed to push & pull callbacks */
-		GNUTLS_TRACE( gnutls_transport_set_ptr( conn->cc_tls_para.session, (gnutls_transport_ptr_t) conn ) );
-
 		/* Set the push and pull callbacks */
 		if (!dtls) {
+			/* Set the transport pointer passed to push & pull callbacks */
+			GNUTLS_TRACE( gnutls_transport_set_ptr( conn->cc_tls_para.session, (gnutls_transport_ptr_t) conn ) );
+
 			#ifdef GNUTLS_VERSION_300
 			GNUTLS_TRACE( gnutls_transport_set_pull_timeout_function( conn->cc_tls_para.session, (void *)fd_cnx_s_select ) );
 			#endif /* GNUTLS_VERSION_300 */
@@ -1685,8 +1685,7 @@
 			GNUTLS_TRACE( gnutls_transport_set_vec_push_function(conn->cc_tls_para.session, (void *)fd_cnx_s_sendv) );
 			#endif /* GNUTLS_VERSION_212 */
 		} else {
-			TODO("DTLS push/pull functions");
-			return ENOTSUP;
+			CHECK_FCT( fd_sctp_dtls_settransport(conn->cc_tls_para.session, conn) );
 		}
 	}
 	
@@ -1752,9 +1751,7 @@
 		if (!dtls) {
 			CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_tls_single, conn ) );
 		} else {
-			TODO("Signal the dtls_push function that multiple streams can be used from this point.");
-			TODO("Create DTLS rcvthr (must reassembly based on seq numbers & stream id ?)");
-			return ENOTSUP;
+			CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, fd_sctp_dtls_rcvthr, conn ) );
 		}
 	}
 	
--- a/libfdcore/cnxctx.h	Mon Jun 10 12:05:38 2013 +0800
+++ b/libfdcore/cnxctx.h	Tue Jun 11 18:13:29 2013 +0800
@@ -121,6 +121,11 @@
 ssize_t fd_sctp_sendstrv(struct cnxctx * conn, uint16_t strid, const struct iovec *iov, int iovcnt);
 int fd_sctp_recvmeta(struct cnxctx * conn, uint16_t * strid, uint8_t ** buf, size_t * len, int *event);
 
+/* DTLS over SCTP */
+int fd_sctp_dtls_prepare(gnutls_session_t session);
+int fd_sctp_dtls_settransport(gnutls_session_t session, struct cnxctx * conn);
+void * fd_sctp_dtls_rcvthr(void * arg);
+
 /* TLS over SCTP (multi-stream) */
 struct sctp3436_ctx {
 	struct cnxctx 	*parent; 	/* for info such as socket, conn name, event list */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libfdcore/sctp_dtls.c	Tue Jun 11 18:13:29 2013 +0800
@@ -0,0 +1,315 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License)                                                               *
+* Author: Sebastien Decugis <sdecugis@freediameter.net>							 *
+*													 *
+* Copyright (c) 2013, WIDE Project and NICT								 *
+* All rights reserved.											 *
+* 													 *
+* Redistribution and use of this software in source and binary forms, with or without modification, are  *
+* permitted provided that the following conditions are met:						 *
+* 													 *
+* * Redistributions of source code must retain the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer.										 *
+*    													 *
+* * Redistributions in binary form must reproduce the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer in the documentation and/or other						 *
+*   materials provided with the distribution.								 *
+* 													 *
+* * Neither the name of the WIDE Project or NICT nor the 						 *
+*   names of its contributors may be used to endorse or 						 *
+*   promote products derived from this software without 						 *
+*   specific prior written permission of WIDE Project and 						 *
+*   NICT.												 *
+* 													 *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
+*********************************************************************************************************/
+
+/* This file contains the code for DTLS over multi-stream SCTP implementation */
+
+#include "fdcore-internal.h"
+#include "cnxctx.h"
+
+
+#define DTLS_TYPE_application_data	23
+
+#ifdef GNUTLS_VERSION_300
+/* Check if data is available for gnutls on a given context */
+static int sctp_dtls_pull_timeout(gnutls_transport_ptr_t tr, unsigned int ms)
+{
+	struct cnxctx * conn = (struct cnxctx *)tr;
+	fd_set rfds;
+	struct timeval tv;
+
+	FD_ZERO (&rfds);
+	FD_SET (conn->cc_socket, &rfds);
+
+	tv.tv_sec = 0;
+	tv.tv_usec = ms * 1000;
+
+	while(tv.tv_usec >= 1000000)
+	{
+		tv.tv_usec -= 1000000;
+		tv.tv_sec++;
+	}
+
+	return select (conn->cc_socket + 1, &rfds, NULL, NULL, &tv);
+}
+#endif /* GNUTLS_VERSION_300 */
+
+/* Send data over the connection, called by gnutls */
+static ssize_t sctp_dtls_pushv(gnutls_transport_ptr_t tr, const giovec_t * iov, int iovcnt)
+{
+	struct cnxctx * conn = (struct cnxctx *)tr;
+	ssize_t ret;
+	
+	TRACE_ENTRY("%p %p %d", tr, iov, iovcnt);
+	CHECK_PARAMS_DO( tr && iov, { errno = EINVAL; return -1; } );
+	
+	/* If no unordered delivery is allowed, send over stream 0 always */
+	if (conn->cc_sctp_para.unordered == 0) {
+		ret = fd_sctp_sendstrv(conn, 0, (const struct iovec *)iov, iovcnt);
+	}
+	/* Otherwise, we need to check the type of record. */
+	else {
+		if ((iovcnt > 0) 
+		&& (iov->iov_len > 0) && 
+		(*((uint8_t *)iov->iov_base) == DTLS_TYPE_application_data)) {
+			/* Data is sent over different streams */
+			if (conn->cc_sctp_para.str_out > 32) {
+				TODO("Limiting to 32 streams. Remove this limit when anti-replay is disabled");
+				conn->cc_sctp_para.str_out = 32;
+			}
+			if (conn->cc_sctp_para.str_out > 1) {
+				conn->cc_sctp_para.next += 1;
+				conn->cc_sctp_para.next %= conn->cc_sctp_para.str_out;
+			} else {
+				conn->cc_sctp_para.next = 0;
+			}
+			ret = fd_sctp_sendstrv(conn, conn->cc_sctp_para.next, (const struct iovec *)iov, iovcnt);
+	
+		} else {
+		/* other TLS messages are always sent over stream 0 */
+			ret = fd_sctp_sendstrv(conn, 0, (const struct iovec *)iov, iovcnt);
+		}
+	}
+	
+	return ret;
+}
+
+#ifndef GNUTLS_VERSION_212
+static ssize_t sctp_dtls_push(gnutls_transport_ptr_t tr, const void * data, size_t len)
+{
+	giovec_t iov;
+	iov.iov_base = (void *)data;
+	iov.iov_len  = len;
+	return sctp_dtls_pushv(tr, &iov, 1);
+}
+#endif /*  GNUTLS_VERSION_212 */
+
+/* Retrieve data received on any stream */
+static ssize_t sctp_dtls_pull(gnutls_transport_ptr_t tr, void * buf, size_t len)
+{
+	struct cnxctx * conn = (struct cnxctx *)tr;
+
+	/* If needed we can use fd_sctp_recvmeta to retrieve more information here */
+	return fd_cnx_s_recv(conn, buf, len);
+}
+
+/* Set the parameters of a session to use the cnxctx object */
+#ifndef GNUTLS_VERSION_300
+GCC_DIAG_OFF("-Wdeprecated-declarations")
+#endif /* !GNUTLS_VERSION_300 */
+int fd_sctp_dtls_settransport(gnutls_session_t session, struct cnxctx * conn)
+{
+	/* Set the transport pointer passed to push & pull callbacks */
+	GNUTLS_TRACE( gnutls_transport_set_ptr( session, (gnutls_transport_ptr_t) conn ) );
+	
+	/* Reset the low water value, since we don't use sockets */
+#ifndef GNUTLS_VERSION_300
+	/* starting version 2.12, this call is not needed */
+	GNUTLS_TRACE( gnutls_transport_set_lowat( session, 0 ) );
+#else  /* GNUTLS_VERSION_300 */
+	/* but in 3.0 we have to provide the pull_timeout callback */
+	GNUTLS_TRACE( gnutls_transport_set_pull_timeout_function( session, sctp_dtls_pull_timeout ) );
+#endif /* GNUTLS_VERSION_300 */
+	
+	/* Set the push and pull callbacks */
+	GNUTLS_TRACE( gnutls_transport_set_pull_function(session, sctp_dtls_pull) );
+#ifndef GNUTLS_VERSION_212
+	GNUTLS_TRACE( gnutls_transport_set_push_function(session, sctp_dtls_push) );
+#else /* GNUTLS_VERSION_212 */
+	GNUTLS_TRACE( gnutls_transport_set_vec_push_function(session, sctp_dtls_pushv) );
+#endif /* GNUTLS_VERSION_212 */
+
+	return;
+}
+#ifndef GNUTLS_VERSION_300
+GCC_DIAG_ON("-Wdeprecated-declarations")
+#endif /* !GNUTLS_VERSION_300 */
+
+
+
+
+/* Set additional session parameters before handshake. The GNUTLS_DATAGRAM is already set in fd_tls_prepare */
+int fd_sctp_dtls_prepare(gnutls_session_t session)
+{
+	/* We do not use cookies at the moment. Not sure it is useful or not */
+	TODO("Cookie exchange?");
+	/* gnutls_dtls_prestate_set (session, &prestate); */
+
+	gnutls_dtls_set_mtu(session, 2^14 /* as per RFC 6083 */);
+
+	gnutls_dtls_set_timeouts(session, 70000, 60000); /* Set retrans > total so that there is no retransmission, since SCTP is reliable */
+
+#ifdef GNUTLS_VERSION_320
+	TODO("Disable replay protection");
+#endif /* GNUTLS_VERSION_320 */
+	
+	
+}
+
+
+static ssize_t fd_dtls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz, uint8_t seq[8])
+{
+	ssize_t ret;
+again:	
+	CHECK_GNUTLS_DO( ret = gnutls_record_recv_seq(session, data, sz, seq), 
+		{
+			switch (ret) {
+				case GNUTLS_E_REHANDSHAKE: 
+					if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING)) {
+						CHECK_GNUTLS_DO( ret = gnutls_handshake(session),
+							{
+								if (TRACE_BOOL(INFO)) {
+									fd_log_debug("TLS re-handshake failed on socket %d (%s) : %s", conn->cc_socket, conn->cc_id, gnutls_strerror(ret));
+								}
+								goto end;
+							} );
+					}
+
+				case GNUTLS_E_AGAIN:
+				case GNUTLS_E_INTERRUPTED:
+					if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING))
+						goto again;
+					TRACE_DEBUG(FULL, "Connection is closing, so abord gnutls_record_recv now.");
+					break;
+
+				case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
+					/* The connection is closed */
+					TRACE_DEBUG(FULL, "Got 0 size while reading the socket, probably connection closed...");
+					break;
+				
+				default:
+					if (gnutls_error_is_fatal (ret) == 0) {
+						LOG_N("Ignoring non-fatal GNU TLS error: %s", gnutls_strerror (ret));
+						goto again;
+					}
+					LOG_E("Fatal GNUTLS error: %s", gnutls_strerror (ret));
+			}
+		} );
+		
+	if (ret == 0)
+		CHECK_GNUTLS_DO( gnutls_bye(session, GNUTLS_SHUT_RDWR),  );
+	
+end:	
+	if (ret <= 0)
+		fd_cnx_markerror(conn);
+	return ret;
+}
+
+
+/* Receiver thread that reassemble the decrypted messages (when size is > 2<<14) for upper layer */
+void * fd_sctp_dtls_rcvthr(void * arg) {
+
+	struct cnxctx * conn = arg;
+	
+	TRACE_ENTRY("%p", arg);
+	CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), return NULL );
+	
+	/* Set the thread name */
+	{
+		char buf[48];
+		snprintf(buf, sizeof(buf), "Receiver (%d) DTLS", conn->cc_socket);
+		fd_log_threadname ( buf );
+	}
+	
+	ASSERT( fd_cnx_teststate(conn, CC_STATUS_TLS) );
+	ASSERT( fd_cnx_target_queue(conn) );
+
+	/* The next function only returns when there is an error on the socket */	
+	do {
+	
+		TODO("Reassemble the packets based on their sequence number & stream");
+		
+		
+#if 0	
+		
+		uint8_t header[4];
+		struct fd_cnx_rcvdata rcv_data;
+		struct fd_msg_pmdl *pmdl=NULL;
+		ssize_t ret = 0;
+		size_t	received = 0;
+		uint8_t seq[8];
+
+		do {
+			ret = fd_dtls_recv_handle_error(conn, session, &header[received], sizeof(header) - received, seq);
+			if (ret <= 0) {
+				/* The connection is closed */
+				goto out;
+			}
+			received += ret;
+		} while (received < sizeof(header));
+
+		rcv_data.length = ((size_t)header[1] << 16) + ((size_t)header[2] << 8) + (size_t)header[3];
+
+		/* Check the received word is a valid beginning of a Diameter message */
+		if ((header[0] != DIAMETER_VERSION)	/* defined in <libfreeDiameter.h> */
+		   || (rcv_data.length > DIAMETER_MSG_SIZE_MAX)) { /* to avoid too big mallocs */
+			/* The message is suspect */
+			LOG_E( "Received suspect header [ver: %d, size: %zd] from '%s', assume disconnection", (int)header[0], rcv_data.length, conn->cc_remid);
+			fd_cnx_markerror(conn);
+			goto out;
+		}
+
+		/* Ok, now we can really receive the data */
+		CHECK_MALLOC(  rcv_data.buffer = fd_cnx_alloc_msg_buffer( rcv_data.length, &pmdl ) );
+		memcpy(rcv_data.buffer, header, sizeof(header));
+
+		while (received < rcv_data.length) {
+			pthread_cleanup_push(free_rcvdata, &rcv_data); /* In case we are canceled, clean the partialy built buffer */
+			ret = fd_tls_recv_handle_error(conn, session, rcv_data.buffer + received, rcv_data.length - received);
+			pthread_cleanup_pop(0);
+
+			if (ret <= 0) {
+				free_rcvdata(&rcv_data);
+				goto out;
+			}
+			received += ret;
+		}
+		
+		fd_hook_call(HOOK_DATA_RECEIVED, NULL, NULL, &rcv_data, pmdl);
+		
+		/* We have received a complete message, pass it to the daemon */
+		CHECK_FCT_DO( ret = fd_event_send( fd_cnx_target_queue(conn), FDEVP_CNX_MSG_RECV, rcv_data.length, rcv_data.buffer), 
+			{ 
+				free_rcvdata(&rcv_data);
+				CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), );
+				return ret; 
+			} );
+#endif // 0	
+				
+	} while (1);
+	
+out:
+	TRACE_DEBUG(FULL, "Thread terminated");	
+	return NULL;
+}			
"Welcome to our mercurial repository"