# HG changeset patch # User Sebastien Decugis # Date 1370945609 -28800 # Node ID e1ced4db7f6747f542c1b27d7e589249edde20b1 # Parent b4a0f9ee312936a476f3b04cdcfb8e48f943f6af Backup work in progress on DTLS, not usable diff -r b4a0f9ee3129 -r e1ced4db7f67 CMakeLists.txt --- 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) diff -r b4a0f9ee3129 -r e1ced4db7f67 include/freeDiameter/libfdcore.h --- 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 #include #include +#ifdef GNUTLS_VERSION_300 +#include +#endif /* GNUTLS_VERSION_300 */ + /* GNUTLS version */ #ifndef GNUTLS_VERSION diff -r b4a0f9ee3129 -r e1ced4db7f67 libfdcore/CMakeLists.txt --- 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 diff -r b4a0f9ee3129 -r e1ced4db7f67 libfdcore/cnxctx.c --- 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 ) ); } } diff -r b4a0f9ee3129 -r e1ced4db7f67 libfdcore/cnxctx.h --- 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 */ diff -r b4a0f9ee3129 -r e1ced4db7f67 libfdcore/sctp_dtls.c --- /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 * +* * +* 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 */ + || (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; +}