# HG changeset patch # User Sebastien Decugis # Date 1371694829 -28800 # Node ID cf9bad611f90695f164e56ac9053656cbd0166d3 # Parent 33b94b5b828939f34886ee877b3f8138fe68d885# Parent 65c6460f60f2f6d496a443f566e02bc3f2e08a69 Merge with freeDiameter-proposed diff -r 65c6460f60f2 -r cf9bad611f90 .hgtags --- a/.hgtags Wed Jun 19 10:20:47 2013 +0800 +++ b/.hgtags Thu Jun 20 10:20:29 2013 +0800 @@ -63,6 +63,4 @@ d00b5914351e83fa90b2ea09f8c51cd82b312157 proposed_merged d00b5914351e83fa90b2ea09f8c51cd82b312157 proposed_merged fb9282c75ec51eb43690413c5b66a3192dfc79d3 proposed_merged -fb9282c75ec51eb43690413c5b66a3192dfc79d3 proposed_merged -043b894b0511b6beb155576e9e2c509a21be8360 proposed_merged -08e72b858f03a77869792cb8ad8e0acbc83c2590 1.2.0-rc2 +56c36d1007b4eb1108b7a3702589c9cbb2aeeb1f dtls_dev diff -r 65c6460f60f2 -r cf9bad611f90 CMakeLists.txt --- a/CMakeLists.txt Wed Jun 19 10:20:47 2013 +0800 +++ b/CMakeLists.txt Thu Jun 20 10:20: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 65c6460f60f2 -r cf9bad611f90 cmake/Modules/FindGnuTLS.cmake --- a/cmake/Modules/FindGnuTLS.cmake Wed Jun 19 10:20:47 2013 +0800 +++ b/cmake/Modules/FindGnuTLS.cmake Thu Jun 20 10:20:29 2013 +0800 @@ -1,13 +1,11 @@ # - Find gnutls -# Find the native GNUTLS includes and library +# Find the native GNUTLS includes and library. Version 3.0.0 at least is required # # GNUTLS_FOUND - True if gnutls found. # GNUTLS_INCLUDE_DIR - where to find gnutls.h, etc. # GNUTLS_LIBRARIES - List of libraries when using gnutls. -# GNUTLS_VERSION_210 - true if GnuTLS version is >= 2.10.0 (does not require additional separate gcrypt initialization) -# GNUTLS_VERSION_212 - true if GnuTLS version is >= 2.12.0 (supports gnutls_transport_set_vec_push_function) -# GNUTLS_VERSION_300 - true if GnuTLS version is >= 3.00.0 (x509 verification functions changed) # GNUTLS_VERSION_310 - true if GnuTLS version is >= 3.01.0 (stabilization branch with new APIs) +# GNUTLS_VERSION_322 - true if GnuTLS version is >= 3.2.2 (DTLS over SCTP improvements) if (GNUTLS_INCLUDE_DIR AND GNUTLS_LIBRARIES) set(GNUTLS_FIND_QUIETLY TRUE) @@ -46,19 +44,19 @@ IF( NOT( "${GNUTLS_VERSION_TEST_FOR}" STREQUAL "${GNUTLS_LIBRARY}" )) INCLUDE (CheckLibraryExists) MESSAGE(STATUS "Checking GNUTLS version") - UNSET(GNUTLS_VERSION_210) - UNSET(GNUTLS_VERSION_210 CACHE) - UNSET(GNUTLS_VERSION_212) - UNSET(GNUTLS_VERSION_212 CACHE) UNSET(GNUTLS_VERSION_300) UNSET(GNUTLS_VERSION_300 CACHE) UNSET(GNUTLS_VERSION_310) UNSET(GNUTLS_VERSION_310 CACHE) + UNSET(GNUTLS_VERSION_322) + UNSET(GNUTLS_VERSION_322 CACHE) GET_FILENAME_COMPONENT(GNUTLS_PATH ${GNUTLS_LIBRARY} PATH) - CHECK_LIBRARY_EXISTS(gnutls gnutls_hash ${GNUTLS_PATH} GNUTLS_VERSION_210) - CHECK_LIBRARY_EXISTS(gnutls gnutls_transport_set_vec_push_function ${GNUTLS_PATH} GNUTLS_VERSION_212) CHECK_LIBRARY_EXISTS(gnutls gnutls_x509_trust_list_verify_crt ${GNUTLS_PATH} GNUTLS_VERSION_300) + IF(NOT GNUTLS_VERSION_300) + MESSAGE(FATAL_ERROR "GnuTLS found but version is too old, need 3.x at least for DTLS support") + ENDIF(NOT GNUTLS_VERSION_300) CHECK_LIBRARY_EXISTS(gnutls gnutls_handshake_set_timeout ${GNUTLS_PATH} GNUTLS_VERSION_310) + CHECK_LIBRARY_EXISTS(gnutls gnutls_handshake_set_hook_function ${GNUTLS_PATH} GNUTLS_VERSION_322) SET( GNUTLS_VERSION_TEST_FOR ${GNUTLS_LIBRARY} CACHE INTERNAL "Version the test was made against" ) ENDIF (NOT( "${GNUTLS_VERSION_TEST_FOR}" STREQUAL "${GNUTLS_LIBRARY}" )) ENDIF(GNUTLS_FOUND) diff -r 65c6460f60f2 -r cf9bad611f90 doc/freediameter.conf.sample --- a/doc/freediameter.conf.sample Wed Jun 19 10:20:47 2013 +0800 +++ b/doc/freediameter.conf.sample Thu Jun 20 10:20:29 2013 +0800 @@ -34,6 +34,13 @@ # Default: 5658. Use 0 to disable. #SecPort = 5658; +# freeDiameter now supports DTLS over SCTP (RFC6083) instead of TLS over SCTP (RFC3436), +# as specified in RFC6733. If you need compatibility with older implementation that use TLS over SCTP, you +# can open an additional SCTP server port using TLS/SCTP by specifying the following parameter. +# Note that no TCP server is started on the following port. +# Default: 0 (disabled). Use 3869 for compatibility with freeDiameter < 1.2.0. +#SctpSec3436 = 0; + # Use RFC3588 method for TLS protection, where TLS is negociated after CER/CEA exchange is completed # on the unsecure connection. The alternative is RFC6733 mechanism, where TLS protects also the # CER/CEA exchange on a dedicated secure port. @@ -230,7 +237,8 @@ #ConnectPeer = "diameterid" [ { parameter1; parameter2; ...} ] ; # Parameters that can be specified in the peer's parameter list: # No_TCP; No_SCTP; No_IP; No_IPv6; Prefer_TCP; TLS_old_method; -# No_TLS; # assume transparent security instead of TLS. DTLS is not supported yet (will change in future versions). +# No_TLS; # assume transparent security instead of TLS. +# SctpSec3436; # Use TLS/SCTP instead of DTLS/SCTP to protect SCTP associations with this peer (not recommended). # Port = 5658; # The port to connect to # TcTimer = 30; # TwTimer = 30; diff -r 65c6460f60f2 -r cf9bad611f90 freeDiameterd/main.c --- a/freeDiameterd/main.c Wed Jun 19 10:20:47 2013 +0800 +++ b/freeDiameterd/main.c Thu Jun 20 10:20:29 2013 +0800 @@ -51,7 +51,10 @@ /* gnutls debug */ static void fd_gnutls_debug(int level, const char * str) { - fd_log_debug(" [gnutls:%d] %s", level, str); + int len = strlen(str); + if (str[len-1] == '\n') + len--; + fd_log_debug(" [gnutls:%d] %.*s", level, len, str); } diff -r 65c6460f60f2 -r cf9bad611f90 include/freeDiameter/freeDiameter-host.h.in --- a/include/freeDiameter/freeDiameter-host.h.in Wed Jun 19 10:20:47 2013 +0800 +++ b/include/freeDiameter/freeDiameter-host.h.in Thu Jun 20 10:20:29 2013 +0800 @@ -60,10 +60,8 @@ #cmakedefine DIAMID_IDNA_REJECT #cmakedefine DISABLE_PEER_EXPIRY #cmakedefine WORKAROUND_ACCEPT_INVALID_VSAI -#cmakedefine GNUTLS_VERSION_210 -#cmakedefine GNUTLS_VERSION_212 -#cmakedefine GNUTLS_VERSION_300 #cmakedefine GNUTLS_VERSION_310 +#cmakedefine GNUTLS_VERSION_322 #cmakedefine ERRORS_ON_TODO #cmakedefine DEBUG diff -r 65c6460f60f2 -r cf9bad611f90 include/freeDiameter/libfdcore.h --- a/include/freeDiameter/libfdcore.h Wed Jun 19 10:20:47 2013 +0800 +++ b/include/freeDiameter/libfdcore.h Thu Jun 20 10:20:29 2013 +0800 @@ -44,6 +44,8 @@ #include #include #include +#include + /* GNUTLS version */ #ifndef GNUTLS_VERSION @@ -165,9 +167,7 @@ /* GNUTLS server credential(s) */ gnutls_certificate_credentials_t credentials; /* contains local cert + trust anchors */ - #ifdef GNUTLS_VERSION_300 gnutls_x509_trust_list_t trustlist; /* the logic to check local certificate has changed */ - #endif /* GNUTLS_VERSION_300 */ } cnf_sec_data; diff -r 65c6460f60f2 -r cf9bad611f90 include/freeDiameter/libfdproto.h --- a/include/freeDiameter/libfdproto.h Wed Jun 19 10:20:47 2013 +0800 +++ b/include/freeDiameter/libfdproto.h Thu Jun 20 10:20:29 2013 +0800 @@ -129,6 +129,7 @@ /* DEBUG */ /*============================================================*/ +#define LOGS_VALIGN /* * FUNCTION: fd_log @@ -296,7 +297,11 @@ /* In DEBUG mode, we add meta-information along each trace. This makes multi-threading problems easier to debug. */ #ifdef DEBUG -# define STD_TRACE_FMT_STRING "pid:%s in %s@%s:%d: " +# ifdef LOGS_VALIGN +# define STD_TRACE_FMT_STRING "pid:%-25.25s in %25.25s@%-15.15s:%-4d: " +# else /* LOGS_VALIGN */ +# define STD_TRACE_FMT_STRING "pid:%s in %s@%s:%d: " +# endif /* LOGS_VALIGN */ # define STD_TRACE_FMT_ARGS , ((char *)pthread_getspecific(fd_log_thname) ?: "unnamed"), __PRETTY_FUNCTION__, __STRIPPED_FILE__, __LINE__ #else /* DEBUG */ # define STD_TRACE_FMT_STRING "" diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/CMakeLists.txt --- a/libfdcore/CMakeLists.txt Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/CMakeLists.txt Thu Jun 20 10:20: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 65c6460f60f2 -r cf9bad611f90 libfdcore/cnxctx.c --- a/libfdcore/cnxctx.c Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/cnxctx.c Thu Jun 20 10:20:29 2013 +0800 @@ -88,6 +88,7 @@ CHECK_MALLOC_DO( conn = malloc(sizeof(struct cnxctx)), return NULL ); memset(conn, 0, sizeof(struct cnxctx)); + fd_list_init(&conn->cc_sctp_dtls_data.chunks, NULL); if (full) { CHECK_FCT_DO( fd_fifo_new ( &conn->cc_incoming, 5 ), return NULL ); @@ -630,7 +631,6 @@ } -#ifdef GNUTLS_VERSION_300 /* The pull_timeout function for gnutls */ static int fd_cnx_s_select (struct cnxctx * conn, unsigned int ms) { @@ -645,7 +645,6 @@ return select (conn->cc_socket + 1, &rfds, NULL, NULL, &tv); } -#endif /* GNUTLS_VERSION_300 */ /* A recv-like function, taking a cnxctx object instead of socket as entry. We use it to quickly react to timeouts without traversing GNUTLS wrapper each time */ ssize_t fd_cnx_s_recv(struct cnxctx * conn, void *buffer, size_t length) @@ -708,17 +707,6 @@ return ret; } -/* Send, for older GNUTLS */ -#ifndef GNUTLS_VERSION_212 -static ssize_t fd_cnx_s_send(struct cnxctx * conn, const void *buffer, size_t length) -{ - struct iovec iov; - iov.iov_base = (void *)buffer; - iov.iov_len = length; - return fd_cnx_s_sendv(conn, &iov, 1); -} -#endif /* GNUTLS_VERSION_212 */ - #define ALIGNOF(t) ((char *)(&((struct { char c; t _h; } *)0)->_h) - (char *)0) /* Could use __alignof__(t) on some systems but this is more portable probably */ #define PMDL_PADDED(len) ( ((len) + ALIGNOF(struct fd_msg_pmdl) - 1) & ~(ALIGNOF(struct fd_msg_pmdl) - 1) ) @@ -946,7 +934,7 @@ /* Returns 0 on error, received data size otherwise (always >= 0). This is not used for DTLS-protected associations. */ -static ssize_t fd_tls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz) +ssize_t fd_tls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz) { ssize_t ret; again: @@ -976,6 +964,14 @@ TRACE_DEBUG(FULL, "Got 0 size while reading the socket, probably connection closed..."); break; + case GNUTLS_E_WARNING_ALERT_RECEIVED: + LOG_N("Received TLS WARNING ALERT: %s", gnutls_alert_get_name(gnutls_alert_get(session)) ?: ""); + goto again; + + case GNUTLS_E_FATAL_ALERT_RECEIVED: + LOG_E("Received TLS FATAL ALERT: %s", gnutls_alert_get_name(gnutls_alert_get(session)) ?: ""); + break; + default: if (gnutls_error_is_fatal (ret) == 0) { LOG_N("Ignoring non-fatal GNU TLS error: %s", gnutls_strerror (ret)); @@ -995,7 +991,7 @@ } /* Wrapper around gnutls_record_send to handle some error codes. This is also used for DTLS-protected associations */ -static ssize_t fd_tls_send_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz) +ssize_t fd_tls_send_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz) { ssize_t ret; struct timespec ts, now; @@ -1043,13 +1039,7 @@ /* The function that receives TLS data and re-builds a Diameter message -- it exits only on error or cancelation */ -/* For the case of DTLS, since we are not using SCTP_UNORDERED, the messages over a single stream are ordered. - Furthermore, as long as messages are shorter than the MTU [2^14 = 16384 bytes], they are delivered in a single - record, as far as I understand. - For larger messages, however, it is possible that pieces of messages coming from different streams can get interleaved. - As a result, we do not use the following function for DTLS reception, because we use the sequence number to rebuild the - messages. */ -int fd_tls_rcvthr_core(struct cnxctx * conn, gnutls_session_t session) +int fd_tls_rcvthr_core(struct cnxctx * conn, gnutls_session_t session, int dtls) { /* No guarantee that GnuTLS preserves the message boundaries, so we re-build it as in TCP. */ do { @@ -1060,7 +1050,10 @@ size_t received = 0; do { - ret = fd_tls_recv_handle_error(conn, session, &header[received], sizeof(header) - received); + if (!dtls) + ret = fd_tls_recv_handle_error(conn, session, &header[received], sizeof(header) - received); + else + ret = fd_dtls_recv_handle_error(conn, session, &header[received], sizeof(header) - received); if (ret <= 0) { /* The connection is closed */ goto out; @@ -1085,7 +1078,10 @@ 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); + if (!dtls) + ret = fd_tls_recv_handle_error(conn, session, rcv_data.buffer + received, rcv_data.length - received); + else + ret = fd_dtls_recv_handle_error(conn, session, rcv_data.buffer + received, rcv_data.length - received); pthread_cleanup_pop(0); if (ret <= 0) { @@ -1130,8 +1126,8 @@ ASSERT( fd_cnx_target_queue(conn) ); /* The next function only returns when there is an error on the socket */ - CHECK_FCT_DO(fd_tls_rcvthr_core(conn, conn->cc_tls_para.session), /* continue */); - + CHECK_FCT_DO(fd_tls_rcvthr_core(conn, conn->cc_tls_para.session, 0), /* continue */); + TRACE_DEBUG(FULL, "Thread terminated"); return NULL; } @@ -1139,13 +1135,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) { @@ -1155,6 +1146,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 ); @@ -1167,232 +1163,6 @@ return 0; } -#ifndef GNUTLS_VERSION_300 - -/* Verify remote credentials after successful handshake (return 0 if OK, EINVAL otherwise) */ -int fd_tls_verify_credentials(gnutls_session_t session, struct cnxctx * conn, int verbose) -{ - int i, ret = 0; - unsigned int gtret; - const gnutls_datum_t *cert_list; - unsigned int cert_list_size; - gnutls_x509_crt_t cert; - time_t now; - - TRACE_ENTRY("%p %d", conn, verbose); - CHECK_PARAMS(conn); - - /* Trace the session information -- http://www.gnu.org/software/gnutls/manual/gnutls.html#Obtaining-session-information */ - #ifdef DEBUG - if (verbose) { - const char *tmp; - gnutls_kx_algorithm_t kx; - gnutls_credentials_type_t cred; - - LOG_A("TLS Session information for connection '%s':", conn->cc_id); - - /* print the key exchange's algorithm name */ - GNUTLS_TRACE( kx = gnutls_kx_get (session) ); - GNUTLS_TRACE( tmp = gnutls_kx_get_name (kx) ); - LOG_A("\t - Key Exchange: %s", tmp); - - /* Check the authentication type used and switch - * to the appropriate. */ - GNUTLS_TRACE( cred = gnutls_auth_get_type (session) ); - switch (cred) - { - case GNUTLS_CRD_IA: - LOG_A("\t - TLS/IA session"); - break; - - case GNUTLS_CRD_PSK: - /* This returns NULL in server side. */ - if (gnutls_psk_client_get_hint (session) != NULL) - LOG_A("\t - PSK authentication. PSK hint '%s'", - gnutls_psk_client_get_hint (session)); - /* This returns NULL in client side. */ - if (gnutls_psk_server_get_username (session) != NULL) - LOG_A("\t - PSK authentication. Connected as '%s'", - gnutls_psk_server_get_username (session)); - break; - - case GNUTLS_CRD_ANON: /* anonymous authentication */ - LOG_A("\t - Anonymous DH using prime of %d bits", - gnutls_dh_get_prime_bits (session)); - break; - - case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */ - /* Check if we have been using ephemeral Diffie-Hellman. */ - if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) { - LOG_A("\t - Ephemeral DH using prime of %d bits", - gnutls_dh_get_prime_bits (session)); - } - break; -#ifdef ENABLE_SRP - case GNUTLS_CRD_SRP: - LOG_A("\t - SRP session with username %s", - gnutls_srp_server_get_username (session)); - break; -#endif /* ENABLE_SRP */ - - default: - fd_log_debug("\t - Different type of credentials for the session (%d).", cred); - break; - - } - - /* print the protocol's name (ie TLS 1.0) */ - tmp = gnutls_protocol_get_name (gnutls_protocol_get_version (session)); - LOG_A("\t - Protocol: %s", tmp); - - /* print the certificate type of the peer. ie X.509 */ - tmp = gnutls_certificate_type_get_name (gnutls_certificate_type_get (session)); - LOG_A("\t - Certificate Type: %s", tmp); - - /* print the compression algorithm (if any) */ - tmp = gnutls_compression_get_name (gnutls_compression_get (session)); - LOG_A("\t - Compression: %s", tmp); - - /* print the name of the cipher used. ie 3DES. */ - tmp = gnutls_cipher_get_name (gnutls_cipher_get (session)); - LOG_A("\t - Cipher: %s", tmp); - - /* Print the MAC algorithms name. ie SHA1 */ - tmp = gnutls_mac_get_name (gnutls_mac_get (session)); - LOG_A("\t - MAC: %s", tmp); - } - #endif /* DEBUG */ - - /* First, use built-in verification */ - CHECK_GNUTLS_DO( gnutls_certificate_verify_peers2 (session, >ret), return EINVAL ); - if (gtret) { - if (TRACE_BOOL(INFO)) { - fd_log_debug("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id); - if (gtret & GNUTLS_CERT_INVALID) - fd_log_debug(" - The certificate is not trusted (unknown CA? expired?)"); - if (gtret & GNUTLS_CERT_REVOKED) - fd_log_debug(" - The certificate has been revoked."); - if (gtret & GNUTLS_CERT_SIGNER_NOT_FOUND) - fd_log_debug(" - The certificate hasn't got a known issuer."); - if (gtret & GNUTLS_CERT_SIGNER_NOT_CA) - fd_log_debug(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints."); - if (gtret & GNUTLS_CERT_INSECURE_ALGORITHM) - fd_log_debug(" - The certificate signature uses a weak algorithm."); - } - return EINVAL; - } - - /* Code from http://www.gnu.org/software/gnutls/manual/gnutls.html#Verifying-peer_0027s-certificate */ - if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) - return EINVAL; - - GNUTLS_TRACE( cert_list = gnutls_certificate_get_peers (session, &cert_list_size) ); - if (cert_list == NULL) - return EINVAL; - - now = time(NULL); - - #ifdef DEBUG - char serial[40]; - char dn[128]; - size_t size; - unsigned int algo, bits; - time_t expiration_time, activation_time; - - LOG_D("TLS Certificate information for connection '%s' (%d certs provided):", conn->cc_id, cert_list_size); - for (i = 0; i < cert_list_size; i++) - { - - CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return EINVAL); - CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER), return EINVAL); - - LOG_A(" Certificate %d info:", i); - - GNUTLS_TRACE( expiration_time = gnutls_x509_crt_get_expiration_time (cert) ); - GNUTLS_TRACE( activation_time = gnutls_x509_crt_get_activation_time (cert) ); - - LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate is valid since: %.24s", ctime (&activation_time)); - LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate expires: %.24s", ctime (&expiration_time)); - - /* Print the serial number of the certificate. */ - size = sizeof (serial); - gnutls_x509_crt_get_serial (cert, serial, &size); - - { - int j; - char buf[1024]; - snprintf(buf, sizeof(buf), "\t - Certificate serial number: "); - for (j = 0; j < size; j++) { - snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%02hhx", serial[j]); - } - LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "%s", buf); - } - - /* Extract some of the public key algorithm's parameters */ - GNUTLS_TRACE( algo = gnutls_x509_crt_get_pk_algorithm (cert, &bits) ); - LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate public key: %s", - gnutls_pk_algorithm_get_name (algo)); - - /* Print the version of the X.509 certificate. */ - LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate version: #%d", - gnutls_x509_crt_get_version (cert)); - - size = sizeof (dn); - GNUTLS_TRACE( gnutls_x509_crt_get_dn (cert, dn, &size) ); - LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - DN: %s", dn); - - size = sizeof (dn); - GNUTLS_TRACE( gnutls_x509_crt_get_issuer_dn (cert, dn, &size) ); - LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Issuer's DN: %s", dn); - - GNUTLS_TRACE( gnutls_x509_crt_deinit (cert) ); - } - #endif /* DEBUG */ - - /* Check validity of all the certificates */ - for (i = 0; i < cert_list_size; i++) - { - time_t deadline; - - CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return EINVAL); - CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER), return EINVAL); - - GNUTLS_TRACE( deadline = gnutls_x509_crt_get_expiration_time(cert) ); - if ((deadline != (time_t)-1) && (deadline < now)) { - if (TRACE_BOOL(INFO)) { - fd_log_debug("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id); - fd_log_debug(" - The certificate %d in the chain is expired", i); - } - ret = EINVAL; - } - - GNUTLS_TRACE( deadline = gnutls_x509_crt_get_activation_time(cert) ); - if ((deadline != (time_t)-1) && (deadline > now)) { - if (TRACE_BOOL(INFO)) { - fd_log_debug("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id); - fd_log_debug(" - The certificate %d in the chain is not yet activated", i); - } - ret = EINVAL; - } - - if ((i == 0) && (conn->cc_tls_para.cn)) { - if (!gnutls_x509_crt_check_hostname (cert, conn->cc_tls_para.cn)) { - if (TRACE_BOOL(INFO)) { - fd_log_debug("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id); - fd_log_debug(" - The certificate hostname does not match '%s'", conn->cc_tls_para.cn); - } - ret = EINVAL; - } - } - - GNUTLS_TRACE( gnutls_x509_crt_deinit (cert) ); - } - - return ret; -} - -#else /* GNUTLS_VERSION_300 */ - /* Verify remote credentials DURING handshake (return gnutls status) */ int fd_tls_verify_credentials_2(gnutls_session_t session) { @@ -1626,8 +1396,6 @@ return 0; } -#endif /* GNUTLS_VERSION_300 */ - static int fd_cnx_may_dtls(struct cnxctx * conn) { #ifndef DISABLE_SCTP if ((conn->cc_proto == IPPROTO_SCTP) && (conn->cc_tls_para.algo == ALGO_HANDSHAKE_DEFAULT)) @@ -1675,30 +1443,19 @@ 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) { - #ifdef GNUTLS_VERSION_300 + /* 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 ) ); + GNUTLS_TRACE( gnutls_transport_set_pull_timeout_function( conn->cc_tls_para.session, (void *)fd_cnx_s_select ) ); - #endif /* GNUTLS_VERSION_300 */ GNUTLS_TRACE( gnutls_transport_set_pull_function(conn->cc_tls_para.session, (void *)fd_cnx_s_recv) ); - #ifndef GNUTLS_VERSION_212 - GNUTLS_TRACE( gnutls_transport_set_push_function(conn->cc_tls_para.session, (void *)fd_cnx_s_send) ); - #else /* GNUTLS_VERSION_212 */ 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) ); } } - /* additional initialization for gnutls 3.x */ - #ifdef GNUTLS_VERSION_300 - /* the verify function has already been set in the global initialization in config.c */ - /* fd_tls_verify_credentials_2 uses the connection */ gnutls_session_set_ptr (conn->cc_tls_para.session, (void *) conn); @@ -1707,8 +1464,6 @@ CHECK_GNUTLS_DO( gnutls_server_name_set (conn->cc_tls_para.session, GNUTLS_NAME_DNS, conn->cc_tls_para.cn, strlen(conn->cc_tls_para.cn)), /* ignore failure */); } - #endif /* GNUTLS_VERSION_300 */ - #ifdef GNUTLS_VERSION_310 GNUTLS_TRACE( gnutls_handshake_set_timeout( conn->cc_tls_para.session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT)); #endif /* GNUTLS_VERSION_310 */ @@ -1729,15 +1484,6 @@ return EINVAL; } ); - #ifndef GNUTLS_VERSION_300 - /* Now verify the remote credentials are valid -- only simple tests here */ - CHECK_FCT_DO( fd_tls_verify_credentials(conn->cc_tls_para.session, conn, 1), - { - CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_RDWR), ); - fd_cnx_markerror(conn); - return EINVAL; - }); - #endif /* GNUTLS_VERSION_300 */ } /* Multi-stream TLS: handshake other streams as well */ @@ -1757,9 +1503,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 ) ); } } @@ -1938,8 +1682,7 @@ } } else { /* DTLS */ - /* Multistream is handled at lower layer in the push/pull function */ - CHECK_FCT( send_simple(conn, buf, len) ); + CHECK_FCT( fd_sctp_dtls_send(conn, buf, len) ); } } break; diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/cnxctx.h --- a/libfdcore/cnxctx.h Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/cnxctx.h Thu Jun 20 10:20:29 2013 +0800 @@ -76,11 +76,20 @@ uint16_t str_out; /* Out streams */ uint16_t str_in; /* In streams */ uint16_t pairs; /* max number of pairs ( = min(in, out)) */ - uint16_t next; /* # of stream the next message will be sent to */ + uint16_t next; /* # of the stream the next message will be sent to */ int unordered; /* boolean telling if use of streams > 0 is permitted */ } cc_sctp_para; + + /* For DTLS over SCTP */ + struct { + /* This structure will be changed if we implement a different algorithm to reassemble the messages */ + uint8_t nextseq[8]; /* the next sequence number we expect to be delivered to upper layer. Can be overwriten if a new epoch is received. */ + uint8_t validseq[8]; /* last seq number that was actually decrypted, so we can trust this value. */ + struct fd_list chunks; /* store the chunks with greater seq numbers received on SCTP socket for delayed delivery. */ + size_t offset; /* how much data of the current chunk has already been passed to gnutls */ + } cc_sctp_dtls_data; - /* If both conditions */ + /* For TLS over SCTP */ struct { struct sctp3436_ctx *array; /* an array of cc_sctp_para.pairs elements -- the #0 is special (session is outside)*/ struct sr_store *sess_store; /* Session data of the master session, to resume the children sessions */ @@ -100,11 +109,9 @@ void fd_cnx_s_setto(int sock); /* TLS */ -int fd_tls_rcvthr_core(struct cnxctx * conn, gnutls_session_t session); +int fd_tls_rcvthr_core(struct cnxctx * conn, gnutls_session_t session, int dtls); int fd_tls_prepare(gnutls_session_t * session, int mode, int dtls, char * priority, void * alt_creds); -#ifndef GNUTLS_VERSION_300 -int fd_tls_verify_credentials(gnutls_session_t session, struct cnxctx * conn, int verbose); -#endif /* GNUTLS_VERSION_300 */ +ssize_t fd_tls_send_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz); /* TCP */ int fd_tcp_create_bind_server( int * sock, sSA * sa, socklen_t salen ); @@ -124,6 +131,13 @@ 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); +int fd_sctp_dtls_send(struct cnxctx * conn, unsigned char * buf, size_t len); +ssize_t fd_dtls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz); +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 65c6460f60f2 -r cf9bad611f90 libfdcore/config.c --- a/libfdcore/config.c Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/config.c Thu Jun 20 10:20:29 2013 +0800 @@ -74,9 +74,7 @@ /* TLS parameters */ CHECK_GNUTLS_DO( gnutls_certificate_allocate_credentials (&fd_g_config->cnf_sec_data.credentials), return ENOMEM ); CHECK_GNUTLS_DO( gnutls_dh_params_init (&fd_g_config->cnf_sec_data.dh_cache), return ENOMEM ); -#ifdef GNUTLS_VERSION_300 CHECK_GNUTLS_DO( gnutls_x509_trust_list_init(&fd_g_config->cnf_sec_data.trustlist, 0), return ENOMEM ); -#endif /* GNUTLS_VERSION_300 */ return 0; } @@ -186,7 +184,6 @@ return 0; } -#ifdef GNUTLS_VERSION_300 /* inspired from GnuTLS manual */ static int fd_conf_print_details_func (gnutls_x509_crt_t cert, gnutls_x509_crt_t issuer, gnutls_x509_crl_t crl, @@ -229,11 +226,7 @@ return 0; } -#endif /* GNUTLS_VERSION_300 */ -#ifndef GNUTLS_VERSION_300 -GCC_DIAG_OFF("-Wdeprecated-declarations") -#endif /* !GNUTLS_VERSION_300 */ /* Parse the configuration file (using the yacc parser) */ int fd_conf_parse() { @@ -391,11 +384,7 @@ CHECK_MALLOC( certs = calloc(cert_max, sizeof(gnutls_x509_crt_t)) ); CHECK_GNUTLS_DO( gnutls_x509_crt_list_import(certs, &cert_max, &certfile, GNUTLS_X509_FMT_PEM, - #ifdef GNUTLS_VERSION_300 GNUTLS_X509_CRT_LIST_FAIL_IF_UNSORTED - #else /* GNUTLS_VERSION_300 */ - 0 - #endif /* GNUTLS_VERSION_300 */ ), { TRACE_ERROR("Failed to import the data from file '%s'", fd_g_config->cnf_sec_data.cert_file); @@ -408,9 +397,7 @@ /* Now, verify the list against the local CA and CRL */ - #ifdef GNUTLS_VERSION_300 - - /* We use the trust list for this purpose */ + /* We use the trust list for this purpose */ { unsigned int output; @@ -459,72 +446,6 @@ } - - #else /* GNUTLS_VERSION_300 */ - - /* GnuTLS 2.x way of checking certificates */ - { - gnutls_x509_crt_t * CA_list; - int CA_list_length; - - gnutls_x509_crl_t * CRL_list; - int CRL_list_length; - - unsigned int verify; - time_t now; - GNUTLS_TRACE( gnutls_certificate_get_x509_cas (fd_g_config->cnf_sec_data.credentials, &CA_list, (unsigned int *) &CA_list_length) ); - GNUTLS_TRACE( gnutls_certificate_get_x509_crls (fd_g_config->cnf_sec_data.credentials, &CRL_list, (unsigned int *) &CRL_list_length) ); - CHECK_GNUTLS_DO( gnutls_x509_crt_list_verify(certs, cert_max, CA_list, CA_list_length, CRL_list, CRL_list_length, 0, &verify), - { - TRACE_ERROR("Failed to verify the local certificate '%s' against local credentials. Please check your certificate is valid.", fd_g_config->cnf_sec_data.cert_file); - return EINVAL; - } ); - - if (verify) { - fd_log_debug("TLS: Local certificate chain '%s' is invalid :", fd_g_config->cnf_sec_data.cert_file); - if (verify & GNUTLS_CERT_INVALID) - TRACE_ERROR(" - The certificate is not trusted (unknown CA? expired?)"); - if (verify & GNUTLS_CERT_REVOKED) - TRACE_ERROR(" - The certificate has been revoked."); - if (verify & GNUTLS_CERT_SIGNER_NOT_FOUND) - TRACE_ERROR(" - The certificate hasn't got a known issuer."); - if (verify & GNUTLS_CERT_SIGNER_NOT_CA) - TRACE_ERROR(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints."); - if (verify & GNUTLS_CERT_INSECURE_ALGORITHM) - TRACE_ERROR(" - The certificate signature uses a weak algorithm."); - return EINVAL; - } - - /* Check the local Identity is valid with the certificate */ - if (!gnutls_x509_crt_check_hostname (certs[0], fd_g_config->cnf_diamid)) { - TRACE_ERROR("TLS: Local certificate '%s' is invalid :", fd_g_config->cnf_sec_data.cert_file); - TRACE_ERROR(" - The certificate hostname does not match '%s'", fd_g_config->cnf_diamid); - return EINVAL; - } - - /* Check validity of all the certificates in the chain */ - now = time(NULL); - for (i = 0; i < cert_max; i++) - { - time_t deadline; - - GNUTLS_TRACE( deadline = gnutls_x509_crt_get_expiration_time(certs[i]) ); - if ((deadline != (time_t)-1) && (deadline < now)) { - TRACE_ERROR("TLS: Local certificate chain '%s' is invalid :", fd_g_config->cnf_sec_data.cert_file); - TRACE_ERROR(" - The certificate %d in the chain is expired", i); - return EINVAL; - } - - GNUTLS_TRACE( deadline = gnutls_x509_crt_get_activation_time(certs[i]) ); - if ((deadline != (time_t)-1) && (deadline > now)) { - TRACE_ERROR("TLS: Local certificate chain '%s' is invalid :", fd_g_config->cnf_sec_data.cert_file); - TRACE_ERROR(" - The certificate %d in the chain is not yet activated", i); - return EINVAL; - } - } - } - #endif /* GNUTLS_VERSION_300 */ - /* Everything checked OK, free the certificate list */ for (i = 0; i < cert_max; i++) { @@ -532,10 +453,8 @@ } free(certs); - #ifdef GNUTLS_VERSION_300 /* Use certificate verification during the handshake */ gnutls_certificate_set_verify_function (fd_g_config->cnf_sec_data.credentials, fd_tls_verify_credentials_2); - #endif /* GNUTLS_VERSION_300 */ } @@ -596,9 +515,6 @@ return 0; } -#ifndef GNUTLS_VERSION_300 -GCC_DIAG_ON("-Wdeprecated-declarations") -#endif /* !GNUTLS_VERSION_300 */ /* Destroy contents of fd_g_config structure */ @@ -610,9 +526,7 @@ return 0; /* Free the TLS parameters */ -#ifdef GNUTLS_VERSION_300 gnutls_x509_trust_list_deinit(fd_g_config->cnf_sec_data.trustlist, 1); -#endif /* GNUTLS_VERSION_300 */ gnutls_priority_deinit(fd_g_config->cnf_sec_data.prio_cache); gnutls_dh_params_deinit(fd_g_config->cnf_sec_data.dh_cache); gnutls_certificate_free_credentials(fd_g_config->cnf_sec_data.credentials); diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/core.c --- a/libfdcore/core.c Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/core.c Thu Jun 20 10:20:29 2013 +0800 @@ -43,11 +43,6 @@ static struct fd_config g_conf; struct fd_config * fd_g_config = NULL; -/* gcrypt functions to support posix threads */ -#ifndef GNUTLS_VERSION_210 -GCRY_THREAD_OPTION_PTHREAD_IMPL; -#endif /* GNUTLS_VERSION_210 */ - /* Thread that process incoming events on the main queue -- and terminates the framework when requested */ static pthread_t core_runner = (pthread_t)NULL; @@ -188,20 +183,12 @@ LOG_N("libfdproto '%s' initialized.", fd_libproto_version); /* Initialize gcrypt and gnutls */ - #ifndef GNUTLS_VERSION_210 - GNUTLS_TRACE( (void) gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread) ); - GNUTLS_TRACE( (void) gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0) ); - #endif /* GNUTLS_VERSION_210 */ CHECK_GNUTLS_DO( gnutls_global_init(), return EINVAL ); if ( ! gnutls_check_version(GNUTLS_VERSION) ) { TRACE_ERROR( "The GNUTLS library is too old; found '%s', need '" GNUTLS_VERSION "'", gnutls_check_version(NULL)); return EINVAL; } else { - #ifdef GNUTLS_VERSION_210 TRACE_DEBUG(INFO, "libgnutls '%s' initialized.", gnutls_check_version(NULL) ); - #else /* GNUTLS_VERSION_210 */ - TRACE_DEBUG(INFO, "libgnutls '%s', libgcrypt '%s', initialized.", gnutls_check_version(NULL), gcry_check_version(NULL) ); - #endif /* GNUTLS_VERSION_210 */ } /* Initialize the config with default values */ diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/fdcore-internal.h --- a/libfdcore/fdcore-internal.h Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/fdcore-internal.h Thu Jun 20 10:20:29 2013 +0800 @@ -359,9 +359,7 @@ int fd_cnx_recv_setaltfifo(struct cnxctx * conn, struct fifo * alt_fifo); /* send FDEVP_CNX_MSG_RECV event to the fifo list */ int fd_cnx_send(struct cnxctx * conn, unsigned char * buf, size_t len); void fd_cnx_destroy(struct cnxctx * conn); -#ifdef GNUTLS_VERSION_300 int fd_tls_verify_credentials_2(gnutls_session_t session); -#endif /* GNUTLS_VERSION_300 */ /* Internal calls of the hook mechanism */ void fd_hook_call(enum fd_hook_type type, struct msg * msg, struct fd_peer * peer, void * other, struct fd_msg_pmdl * pmdl); diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/fdd.l --- a/libfdcore/fdd.l Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/fdd.l Thu Jun 20 10:20:29 2013 +0800 @@ -245,7 +245,7 @@ (?i:"Realm") { return REALM; } (?i:"Port") { return PORT; } (?i:"SecPort") { return SECPORT; } - /* (?i:"SctpSec3436") { return SEC3436; } */ +(?i:"SctpSec3436") { return SEC3436; } (?i:"No_IPv6") { return NOIP6; } (?i:"No_IP") { return NOIP; } (?i:"No_TCP") { return NOTCP; } diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/fdd.y --- a/libfdcore/fdd.y Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/fdd.y Thu Jun 20 10:20:29 2013 +0800 @@ -562,7 +562,7 @@ yyerror (&yylloc, conf, "Error on file name"); YYERROR; } - #ifdef GNUTLS_VERSION_300 + { /* We import these CA in the trust list */ gnutls_x509_crt_t * calist; @@ -580,7 +580,7 @@ CHECK_GNUTLS_DO( gnutls_x509_trust_list_add_cas (fd_g_config->cnf_sec_data.trustlist, calist, cacount, 0), { yyerror (&yylloc, conf, "Error saving CA in trust list."); YYERROR; } ); } - #endif /* GNUTLS_VERSION_300 */ + fclose(fd); conf->cnf_sec_data.ca_file = $3; CHECK_GNUTLS_DO( conf->cnf_sec_data.ca_file_nr += gnutls_certificate_set_x509_trust_file( @@ -602,7 +602,7 @@ yyerror (&yylloc, conf, "Error on file name"); YYERROR; } - #ifdef GNUTLS_VERSION_300 + { /* We import these CRL in the trust list */ gnutls_x509_crl_t * crllist; @@ -621,7 +621,7 @@ 0), { yyerror (&yylloc, conf, "Error importing CRL in trust list."); YYERROR; } ); } - #endif /* GNUTLS_VERSION_300 */ + fclose(fd); conf->cnf_sec_data.crl_file = $3; CHECK_GNUTLS_DO( gnutls_certificate_set_x509_crl_file( diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/p_cnx.c --- a/libfdcore/p_cnx.c Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/p_cnx.c Thu Jun 20 10:20:29 2013 +0800 @@ -291,7 +291,7 @@ /* Handshake if needed (secure port) */ if (nc->dotls) { CHECK_FCT_DO( fd_cnx_handshake(cnx, GNUTLS_CLIENT, - ALGO_HANDSHAKE_3436, + (peer->p_hdr.info.config.pic_flags.sctpsec == PI_SCTPSEC_3436) ? ALGO_HANDSHAKE_3436 : ALGO_HANDSHAKE_DEFAULT, peer->p_hdr.info.config.pic_priority, NULL), { /* Handshake failed ... */ diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/sctp3436.c --- a/libfdcore/sctp3436.c Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/sctp3436.c Thu Jun 20 10:20:29 2013 +0800 @@ -152,7 +152,7 @@ } /* The next function loops while there is no error */ - CHECK_FCT_DO(fd_tls_rcvthr_core(cnx, ctx->strid ? ctx->session : cnx->cc_tls_para.session), /* continue */); + CHECK_FCT_DO(fd_tls_rcvthr_core(cnx, ctx->strid ? ctx->session : cnx->cc_tls_para.session, 0), /* continue */); error: fd_cnx_markerror(cnx); TRACE_DEBUG(FULL, "Thread terminated"); @@ -163,7 +163,6 @@ /* push / pull */ /*************************************************************/ -#ifdef GNUTLS_VERSION_300 /* Check if data is available for gnutls on a given context */ static int sctp3436_pull_timeout(gnutls_transport_ptr_t tr, unsigned int ms) { @@ -192,24 +191,8 @@ return ret; } -#endif /* GNUTLS_VERSION_300 */ /* Send data over the connection, called by gnutls */ -#ifndef GNUTLS_VERSION_212 -static ssize_t sctp3436_push(gnutls_transport_ptr_t tr, const void * data, size_t len) -{ - struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) tr; - struct iovec iov; - - TRACE_ENTRY("%p %p %zd", tr, data, len); - CHECK_PARAMS_DO( tr && data, { errno = EINVAL; return -1; } ); - - iov.iov_base = (void *)data; - iov.iov_len = len; - - return fd_sctp_sendstrv(ctx->parent, ctx->strid, &iov, 1); -} -#else /* GNUTLS_VERSION_212 */ static ssize_t sctp3436_pushv(gnutls_transport_ptr_t tr, const giovec_t * iov, int iovcnt) { struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) tr; @@ -219,7 +202,6 @@ return fd_sctp_sendstrv(ctx->parent, ctx->strid, (const struct iovec *)iov, iovcnt); } -#endif /* GNUTLS_VERSION_212 */ /* Retrieve data received on a stream and already demultiplexed */ static ssize_t sctp3436_pull(gnutls_transport_ptr_t tr, void * buf, size_t len) @@ -270,36 +252,18 @@ } /* Set the parameters of a session to use the appropriate fifo and stream information */ -#ifndef GNUTLS_VERSION_300 -GCC_DIAG_OFF("-Wdeprecated-declarations") -#endif /* !GNUTLS_VERSION_300 */ static void set_sess_transport(gnutls_session_t session, struct sctp3436_ctx *ctx) { /* Set the transport pointer passed to push & pull callbacks */ GNUTLS_TRACE( gnutls_transport_set_ptr( session, (gnutls_transport_ptr_t) ctx ) ); - /* 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 */ + /* Set the push and pull callbacks */ GNUTLS_TRACE( gnutls_transport_set_pull_timeout_function( session, sctp3436_pull_timeout ) ); -#endif /* GNUTLS_VERSION_300 */ - - /* Set the push and pull callbacks */ GNUTLS_TRACE( gnutls_transport_set_pull_function(session, sctp3436_pull) ); -#ifndef GNUTLS_VERSION_212 - GNUTLS_TRACE( gnutls_transport_set_push_function(session, sctp3436_push) ); -#else /* GNUTLS_VERSION_212 */ GNUTLS_TRACE( gnutls_transport_set_vec_push_function(session, sctp3436_pushv) ); -#endif /* GNUTLS_VERSION_212 */ return; } -#ifndef GNUTLS_VERSION_300 -GCC_DIAG_ON("-Wdeprecated-declarations") -#endif /* !GNUTLS_VERSION_300 */ /*************************************************************/ /* Session resuming support */ @@ -531,12 +495,6 @@ CHECK_GNUTLS_DO( gnutls_handshake( ctx->session ), return NULL); GNUTLS_TRACE( resumed = gnutls_session_is_resumed(ctx->session) ); - #ifndef GNUTLS_VERSION_300 - if (!resumed) { - /* Check the credentials here also */ - CHECK_FCT_DO( fd_tls_verify_credentials(ctx->session, ctx->parent, 0), return NULL ); - } - #endif /* GNUTLS_VERSION_300 */ if (TRACE_BOOL(FULL)) { if (resumed) { fd_log_debug("Session was resumed successfully on stream %hu (conn: '%s')", ctx->strid, fd_cnx_getid(ctx->parent)); @@ -619,10 +577,6 @@ /* Set credentials and priority */ CHECK_FCT( fd_tls_prepare(&conn->cc_sctp3436_data.array[i].session, conn->cc_tls_para.mode, 0, priority, alt_creds) ); - /* additional initialization for gnutls 3.x */ - #ifdef GNUTLS_VERSION_300 - /* the verify function has already been set in the global initialization in config.c */ - /* fd_tls_verify_credentials_2 uses the connection */ gnutls_session_set_ptr (conn->cc_sctp3436_data.array[i].session, (void *) conn); @@ -631,8 +585,6 @@ CHECK_GNUTLS_DO( gnutls_server_name_set (conn->cc_sctp3436_data.array[i].session, GNUTLS_NAME_DNS, conn->cc_tls_para.cn, strlen(conn->cc_tls_para.cn)), /* ignore failure */); } - #endif /* GNUTLS_VERSION_300 */ - #ifdef GNUTLS_VERSION_310 GNUTLS_TRACE( gnutls_handshake_set_timeout( conn->cc_sctp3436_data.array[i].session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT)); #endif /* GNUTLS_VERSION_310 */ diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/sctp_dtls.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libfdcore/sctp_dtls.c Thu Jun 20 10:20:29 2013 +0800 @@ -0,0 +1,712 @@ +/********************************************************************************************************* +* 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" + + +/* In DTLS over SCTP, all the DTLS internal messages (handshake, etc) must be sent over stream 0 so that we are sure they are received in order. + Since we need to distinguish different DTLS payloads, we need some knowledge of DTLS protocol here. + We will then chose the stream within our "push" function called by GNUTLS. + */ +#define DTLS_TYPE_OFFSET 0 /* The TYPE byte is the first in a DTLS packet */ +#define DTLS_TYPE_application_data 23 /* This is the value when the DTLS packet contains DATA (i.e. Diameter payload in our case) */ +#define DTLS_SEQ_OFFSET 3 /* The SEQUENCE bytes come after type and proto version */ + + +#define DTLS_SCTP_MTU 1<<14 /* as per RFC 6083 */ + + +/* The DTLS MTU is limited to 2^14, but Diameter messages can be larger. It means we MUST handle Diameter messages reassembly here; and this is not simple. + +There are two ways to deal with this problem: + +- first solution is to force ordering when parsing all the datagrams received (as SCTP guarantees we will receive them), + so we are guaranteed to reconstruct the stream of data in the same order as it was sent, and we can process the received data the same way as TCP. + * pros: very robust, does not depend on how the remote side is sending the data (assuming they do not interleave chunks of diameter messages, we'd have no solution otherwise) + * cons: less efficient than the next solution, as on the receiving side we cannot parse new payloads until all the previous ones are received. + It defeats some of the benefits of the partial ordering of SCTP. + +- second solution is to make sure the fragmented payloads are sent over the same stream (which are always ordered) and rebuild the messages per stream. + * pros: enables to process complete messages received on other streams while waiting for some chunks (similar to non-DTLS situation, except that in that case SCTP handles the fragmentation) + * cons: we must be sure the sending side is actually sending pieces of a message on the same stream. And the processing on receiving side is more complex. + +We'd have actually more solutions, for example storing the message hop-by-hop id in the snd_ppid field of SCTP header, but this would work only in front of freeDiameter. + +Here is an illustration of the two solutions: +we assume 3 streams S1,S2,S3 and 4 messages, message M1 of 2^14 + 2^13 (=24576) bytes and 3 messages M2,M3,M4 of 2^12 (=4096) bytes to send from peer A to peer B. + +Peer A calls fd_cnx_send() 4 times with the 4 messages M1,M2,M3,M4, which in turn calls gnutls_record_send(), which generates the chunks C1...C5 below: + C1: gnutls_record_send(M1) -> returns 2^14 since the complete record exceed the MTU. + C2: gnutls_record_send(M1+2^14) -> returns the remaining 2^13 + C3: gnutls_record_send(M2) + C4: gnutls_record_send(M3) + C5: gnutls_record_send(M4) + +*** Solution 1) + +Implementing the first solution above, the chunks are sent as follows (assuming round-robin sending over the streams): +C1 over S1, +C2 over S2, +C3 over S3, +C4 over S1, +C5 over S2. + +Given the size of the chunks, they might be delivered in the following order on the receiving side: +C3 +C2 +C5 +C1 +C4 + +This means we have to store C3, C2 and C5 until C1 is received, then we can process C1,C2,C3, +and again wait for C4 before processing C4 and C5, while C3, C4 and C5 are totally independent +and could be processed directly after being received. + + +*** Solution 2) + +Here the partial ordering is enforced, so the sending side MUST send C1 and C2 over the same stream, e.g.: +C1 over S1, +C2 over S1, +C3 over S2, +C4 over S3, +C5 over S1. + +On the receiving side, given the sizes of the message, we might receive the chunks in the following order: +C3 +C4 +C1 +C2 +C5 + +We can process C3 and C4 as soon as they are received, then C1 is stored (when decrypted we can see it is a partial chunk) +until the remaining payload is received; however we can continue to process the data received over other streams without delay. + + +*** What we do here. + +freeDiameter implements the Solution 2 on the sending side (no additional cost), via fd_sctp_dtls_send() below. + +On the receiving side, we implement Solution 1 at the moment (safe). We do it at the lowest layer, before passing the data to GNUTLS. +This way, we can catch all sequence numbers easily. +Note however we have no way to handle cleanly the change of ephoch in case of cipher change (this is unclear in RFC6083 as well) + +We'll see later if it makes sense to implement solution 2. +How to decide if we can use it? one way could be to start doing solution 1, +and when a large record is received check if the chunks were received on the same stream or not. + +Implementation of solution 2 is difficult because we need to pass the stream information through GNU TLS and there is no easy way to do it. + +*/ + +/* Retrieve the next data from the socket. Returns 0 if no payload data is available, >0 otherwise, and <0 in case of error */ +static int get_next_data_from_socket(struct cnxctx * conn, uint16_t *strid, uint8_t ** buf, size_t *len) +{ + int got_data = 0; + int event; + CHECK_FCT_DO( fd_sctp_recvmeta(conn, strid, buf, len, &event), return -1 ); + switch (event) { + case FDEVP_CNX_MSG_RECV: + got_data = 1; + LOG_A("Received DTLS data, len %zd, type %hhd, Seq %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx, Stream %hu", + *len, (*buf)[0], + (*buf)[3],(*buf)[4],(*buf)[5],(*buf)[6], (*buf)[7],(*buf)[8],(*buf)[9],(*buf)[10], + *strid); + break; + + case FDEVP_CNX_EP_CHANGE: + /* Send this event to the target queue */ + CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), event, *len, *buf), return -1 ); + break; + + case FDEVP_CNX_SHUTDOWN: + /* Just ignore the notification for now, we will get another error later anyway */ + break; + + case FDEVP_CNX_ERROR: + default: + return -1; + } + + return got_data; +} + +/* Count the number of records received in a chunk (including partial) and increment the nextseq field accordingly */ +static void update_nextseq_from_records(struct cnxctx * conn, uint8_t * buf, size_t len) +{ + size_t offset = 0; + uint16_t next_record_len; + int i; + + + while (offset + 13 <= len) { + next_record_len = (buf[offset+11] << 8) + buf[offset+12]; + LOG_A("update_nextseq_from_records off:%zd Type %hhd, Ver:%02hhx.%02hhx, Len:%d, Seq:%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", + offset, + buf[offset], buf[offset+1], buf[offset+2], (((int)buf[offset+11])<<8)+((int)buf[offset+12]), + buf[offset+3],buf[offset+4],buf[offset+5],buf[offset+6],buf[offset+7],buf[offset+8],buf[offset+9],buf[offset+10] + ); + + if (memcmp(buf + offset + DTLS_SEQ_OFFSET, conn->cc_sctp_dtls_data.nextseq, 8) != 0) { + /* The next record is not the one we expect in sequence. Is it a new epoch ? */ + uint8_t newepoch[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + if (conn->cc_sctp_dtls_data.nextseq[1] != 0xFF) { + newepoch[0] = conn->cc_sctp_dtls_data.nextseq[0]; newepoch[1] = conn->cc_sctp_dtls_data.nextseq[1] + 1; + } else if (conn->cc_sctp_dtls_data.nextseq[0] != 0xFF) { + newepoch[0] =conn->cc_sctp_dtls_data.nextseq[0] + 1; newepoch[1] = 0; + } else { + LOG_F("Epoch field wrapped, can this happen ???"); + ASSERT(0); TODO("FFS"); + } + + if (memcmp(buf + offset + DTLS_SEQ_OFFSET, newepoch, 8) == 0) { + /* Yes, this is a new epoch record, store this as next seq and continue */ + memcpy(conn->cc_sctp_dtls_data.nextseq, newepoch, 8); + } else { + LOG_E("buf seq: %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", buf[offset +3], buf[offset +4], buf[offset +5], buf[offset +6], buf[offset +7], buf[offset +8], buf[offset +9], buf[offset +10]); + LOG_E("nextseq: %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", conn->cc_sctp_dtls_data.nextseq[0], conn->cc_sctp_dtls_data.nextseq[1], conn->cc_sctp_dtls_data.nextseq[2], conn->cc_sctp_dtls_data.nextseq[3], conn->cc_sctp_dtls_data.nextseq[4], conn->cc_sctp_dtls_data.nextseq[5], conn->cc_sctp_dtls_data.nextseq[6], conn->cc_sctp_dtls_data.nextseq[7]); + TODO("This should not be happening..."); + ASSERT(0); + } + } + + /* increment seq number */ + for (i = 7; i>=3; i--) { + if (conn->cc_sctp_dtls_data.nextseq[i] == 0xFF) { + conn->cc_sctp_dtls_data.nextseq[i] = 0; + } else { + conn->cc_sctp_dtls_data.nextseq[i] ++; + break; + } + } + if (i==2) { + LOG_F("Sequence_number field wrapped, can this happen ???"); + ASSERT(0); TODO("FFS"); + } + + offset += (size_t)next_record_len + 13; + } +} + +/***************************************************************************************************/ +/* Helper functions to reorder the received chunks by sequence number */ +/***************************************************************************************************/ + +struct chunk { + struct fd_list chain; /* link in the ordered list of chunks */ + uint8_t seq[8]; /* epoch + sequence number */ + uint8_t * buffer; /* the data */ + size_t len; /* length of the buffer */ + uint16_t stream; /* which stream the chunk was received on */ + /* We could also add a timestamp here */ +}; + +/* Inserts new buffer received from the connection in the list of chunks */ +static int chunk_insert(struct cnxctx * conn, uint16_t streamid, uint8_t *buffer, size_t len) +{ + struct chunk * new; + struct fd_list * li; + uint8_t * newseq; + + /* Check the new sequence is >= what we processed in upper layer */ + newseq = buffer + DTLS_SEQ_OFFSET; + if (memcmp(newseq, conn->cc_sctp_dtls_data.validseq, 8) < 0) { + LOG_E("Received DTLS packet with smaller sequence number than already processed, discarded. FFS."); + free(buffer); + return 0; + } + + /* Create a new chunk structure to store this chunk */ + CHECK_MALLOC( new = malloc(sizeof(struct chunk)) ); + memset(new, 0, sizeof(struct chunk)); + fd_list_init(&new->chain, new); + memcpy(&new->seq, newseq, 8); + new->buffer = buffer; + new->len = len; + new->stream = streamid; + + /* Insert this new structure in the list attached to the connection */ + for (li = conn->cc_sctp_dtls_data.chunks.prev; li != &conn->cc_sctp_dtls_data.chunks; li = li->prev) { + int cmp = memcmp(new->seq, ((struct chunk *)li->o)->seq, 8); + if (cmp < 0) continue; + if (cmp == 0) { + /* discard repeated seq */ + LOG_E("Received DTLS packet with duplicate sequence number, discarded. FFS."); + free(buffer); + free(new); + return 0; + } + break; + } + /* special case: if we are already delivering partially the first chunk, we do insert only after this one */ + if (conn->cc_sctp_dtls_data.offset && (li == &conn->cc_sctp_dtls_data.chunks)) + li = li->next; + + fd_list_insert_after(li, &new->chain); + + return 0; + +} + +/* Retrieve data from the list of chunks. Returns 0 if no data is ready for upper layer, the available length otherwise (up to upperlen) */ +static size_t chunk_retrieve(struct cnxctx * conn, void * upperbuf, size_t upperlen, int probeonly) +{ + struct chunk * next; + int cmp; + size_t ret = 0; +redo: + if (FD_IS_LIST_EMPTY(&conn->cc_sctp_dtls_data.chunks)) { + return 0; + } + + next = conn->cc_sctp_dtls_data.chunks.next->o; + + /* If we are already delivering this chunk, just continue until complete */ + if (conn->cc_sctp_dtls_data.offset != 0) { + if (probeonly) + return 1; + + ret = next->len - conn->cc_sctp_dtls_data.offset; + if (upperlen < ret) + ret = upperlen; + + memcpy(upperbuf, next->buffer + conn->cc_sctp_dtls_data.offset, ret); + conn->cc_sctp_dtls_data.offset += ret; + if (conn->cc_sctp_dtls_data.offset == next->len) { + /* we delivered the complete chunk, now we can remove it */ + conn->cc_sctp_dtls_data.offset = 0; + free(next->buffer); + fd_list_unlink(&next->chain); + free(next); + } + return ret; + } + + cmp = memcmp(next->seq, conn->cc_sctp_dtls_data.nextseq, 8); + if (cmp < 0) { + cmp = memcmp(next->seq, conn->cc_sctp_dtls_data.validseq, 8); + if (cmp < 0) { + /* This is old stuff or invalid stuff, discard */ + LOG_E("Unqueued DTLS packet with old sequence number, discarding."); + free(next->buffer); + fd_list_unlink(&next->chain); + free(next); + goto redo; + } + /* If the first chunk in our list has a smaller seq number than what we already delivered, we pass it above (to prevent possible DoS by sending forged sequence numbers) */ + if (probeonly) + return 1; + + ret = next->len; + if (upperlen < ret) { + ret = upperlen; + memcpy(upperbuf, next->buffer, ret); + conn->cc_sctp_dtls_data.offset = ret; + } else { + memcpy(upperbuf, next->buffer, ret); + free(next->buffer); + fd_list_unlink(&next->chain); + free(next); + } + LOG_A("Unqueueing (old) chunk with seq number %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", + next->seq[0],next->seq[1],next->seq[2],next->seq[3],next->seq[4],next->seq[5],next->seq[6],next->seq[7]); + return ret; + } + if (cmp > 0) { + /* is this the first message of a new epoch ? */ + uint8_t newepoch[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + if (next->seq[1] != 0xFF) { + newepoch[0] = next->seq[0]; newepoch[1] = next->seq[1] + 1; + } else if (next->seq[0] != 0xFF) { + newepoch[0] = next->seq[0] + 1; newepoch[1] = 0; + } else { + LOG_F("Epoch field wrapped, can this happen ???"); + ASSERT(0); TODO("FFS"); + } + + if (memcmp(newepoch, next->seq, 8) == 0) { + /* Bingo, this is the first message of the new epoch. We update our nextseq accordingly */ + if (probeonly) + return 1; + memcpy(conn->cc_sctp_dtls_data.nextseq, newepoch, 8); + update_nextseq_from_records(conn, next->buffer, next->len); + + LOG_A("Unqueueing chunk with seq number %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx (epoch change)", + next->seq[0],next->seq[1],next->seq[2],next->seq[3],next->seq[4],next->seq[5],next->seq[6],next->seq[7]); + ret = next->len; + if (upperlen < ret) { + ret = upperlen; + memcpy(upperbuf, next->buffer, ret); + conn->cc_sctp_dtls_data.offset = ret; + } else { + memcpy(upperbuf, next->buffer, ret); + free(next->buffer); + fd_list_unlink(&next->chain); + free(next); + } + return ret; + } + + /* otherwise, we don't return this data */ + return 0; + } + + /* next is the next chunk expected on this connection */ + if (probeonly) + return 1; + + /* We increment the next seq by the number or records found in this chunk */ + update_nextseq_from_records(conn, next->buffer, next->len); + + /* And we deliver this to upper layer */ + LOG_A("Unqueueing chunk: Type %hhd, Ver:%02hhx.%02hhx, Seq:%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", + next->buffer[0], next->buffer[1], next->buffer[2], + next->seq[0],next->seq[1],next->seq[2],next->seq[3],next->seq[4],next->seq[5],next->seq[6],next->seq[7]); + ret = next->len; + if (upperlen < ret) { + ret = upperlen; + memcpy(upperbuf, next->buffer, ret); + conn->cc_sctp_dtls_data.offset = ret; + } else { + memcpy(upperbuf, next->buffer, ret); + free(next->buffer); + fd_list_unlink(&next->chain); + free(next); + } + return ret; +} + +/* returns positive value if data is available for upper layer, 0 if the time is elapsed */ +static int chunk_select(struct cnxctx * conn, unsigned int ms) +{ + fd_set rfds; + struct timespec absend, inter; + int ret; + uint8_t * buf; + size_t len; + uint16_t strid; + + /* absolute time we will timeout */ + CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &absend), return -1 ); + absend.tv_sec += ((ms + (absend.tv_nsec / 1000000L)) / 1000); + absend.tv_nsec = ( ms * 1000000L + absend.tv_nsec ) % 1000000000L; + + do { + /* Check if we have available data in the list of chunks */ + if (chunk_retrieve(conn, NULL, 0, 1) > 0) + return 1; + + /* otherwise we need to retrieve more data from the socket, so we select */ + + FD_ZERO (&rfds); + FD_SET (conn->cc_socket, &rfds); + + /* We wait until absend only */ + CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &inter), return -1 ); + if (inter.tv_nsec <= absend.tv_nsec) { + if (inter.tv_sec > absend.tv_sec) { + inter.tv_sec = 0; inter.tv_nsec = 0; + } else { + inter.tv_sec = absend.tv_sec - inter.tv_sec; + inter.tv_nsec = absend.tv_nsec - inter.tv_nsec; + } + } else { + if (inter.tv_sec >= absend.tv_sec) { + inter.tv_sec = 0; inter.tv_nsec = 0; + } else { + inter.tv_sec = absend.tv_sec - inter.tv_sec - 1; + inter.tv_nsec = 1000000000L - inter.tv_nsec + absend.tv_nsec; + } + } + + /* Now, wait for new data on the socket */ + ret = pselect (conn->cc_socket + 1, &rfds, NULL, NULL, &inter, NULL); + if (ret <= 0) + break; /* no data was received, we can return */ + + /* We got data, get it and insert in the list of chunks */ + ret = get_next_data_from_socket(conn, &strid, &buf, &len); + if (ret < 0) + break; + if (ret == 0) + continue; + + CHECK_FCT_DO( chunk_insert(conn, strid, buf, len), return -1 ); + /* and loop */ + } while (1); + + return ret; +} + +/***************************************************************************************************/ +/* Functions "under" GNU TLS */ +/***************************************************************************************************/ + +/* Send data over the connection, called by gnutls. This function checks the type of DTLS packet and sends +all non-application data over stream 0 (to enforce ordering) and application data over the stream set by +upper layer in conn->cc_sctp_para.next */ +static ssize_t sctp_dtls_pushv(gnutls_transport_ptr_t tr, const giovec_t * iov, int iovcnt) +{ + struct cnxctx * conn = (struct cnxctx *)tr; + uint16_t stream = 0; + + + TRACE_ENTRY("%p %p %d", tr, iov, iovcnt); + CHECK_PARAMS_DO( tr && iov, { errno = EINVAL; return -1; } ); + + if ((conn->cc_sctp_para.unordered != 0) + && (iovcnt > 0) + && (iov->iov_len > 0) + && (((uint8_t *)iov->iov_base)[DTLS_TYPE_OFFSET] == DTLS_TYPE_application_data)) { + /* Data is sent over different streams, if allowed */ + stream = conn->cc_sctp_para.next; + } + + if ((iovcnt > 0) && (iov->iov_len > 10)) { + LOG_A("Sending DTLS data, type %hhd, Seq %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx, Stream %hu", + ((uint8_t *)iov->iov_base)[0], + ((uint8_t *)iov->iov_base)[3],((uint8_t *)iov->iov_base)[4],((uint8_t *)iov->iov_base)[5],((uint8_t *)iov->iov_base)[6], + ((uint8_t *)iov->iov_base)[7],((uint8_t *)iov->iov_base)[8],((uint8_t *)iov->iov_base)[9],((uint8_t *)iov->iov_base)[10], + stream); + } else { + LOG_A("Sending DTLS data, {iovcnt=%d, iov->iov_len=%zd}, Stream %hu", + iovcnt, ((iovcnt>0) ? iov->iov_len : 0), stream); + } + + return fd_sctp_sendstrv(conn, stream, (const struct iovec *)iov, iovcnt); +} + +/* Check if data is available for gnutls on a given connection. */ +static int sctp_dtls_pull_timeout(gnutls_transport_ptr_t tr, unsigned int ms) +{ + struct cnxctx * conn = (struct cnxctx *)tr; + return chunk_select(conn, ms); +} + + +/* This function returns only ordered data to the upper layer */ +static ssize_t sctp_dtls_pull(gnutls_transport_ptr_t tr, void * gnutlsbuf, size_t gnutlslen) +{ + struct cnxctx * conn = (struct cnxctx *)tr; + ssize_t ret = 0; + + while ( (ret = chunk_retrieve(conn,gnutlsbuf,gnutlslen,0)) == 0) { + + /* No partial data, read the next SCTP record */ + int stop = 0; + uint8_t * buf; + size_t len; + uint16_t strid; + do { + stop = get_next_data_from_socket(conn, &strid, &buf, &len); + if (stop < 0) + goto out; + } while (!stop); + + CHECK_FCT_DO( chunk_insert(conn, strid, buf, len), goto out ); + } + +out: + return ret; + +} + + +/***************************************************************************************************/ +/* Functions "above" GNU TLS */ +/***************************************************************************************************/ + +/* Set the parameters of a session to use the cnxctx object */ +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 ) ); + + /* in 3.0 we have to provide the pull_timeout callback */ + GNUTLS_TRACE( gnutls_transport_set_pull_timeout_function( session, sctp_dtls_pull_timeout ) ); + + /* Set the push and pull callbacks */ + GNUTLS_TRACE( gnutls_transport_set_pull_function(session, sctp_dtls_pull) ); + GNUTLS_TRACE( gnutls_transport_set_vec_push_function(session, sctp_dtls_pushv) ); + + return 0; +} + + +/* 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_TRACE( gnutls_dtls_set_mtu(session, DTLS_SCTP_MTU)); + + GNUTLS_TRACE( gnutls_dtls_set_timeouts(session, 70000, 60000)); /* Set retrans > total so that there is no retransmission, since SCTP is reliable */ + +#ifdef GNUTLS_VERSION_322 + TODO("Disable replay protection"); + TODO("Register hook on the Finish message to change SCTP_AUTH active key on the socket"); +#endif /* GNUTLS_VERSION_322 */ + + return 0; + +} + +/* the following function is actually almost same as fd_tls_recv_handle_error at the moment, since all handling is done under gnutls */ +ssize_t fd_dtls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz) +{ + ssize_t ret; +again: + CHECK_GNUTLS_DO( ret = gnutls_record_recv_seq(session, data, sz, conn->cc_sctp_dtls_data.validseq), + { + 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; + + case GNUTLS_E_WARNING_ALERT_RECEIVED: + LOG_N("Received TLS WARNING ALERT: %s", gnutls_alert_get_name(gnutls_alert_get(session)) ?: ""); + 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_FATAL_ALERT_RECEIVED: + LOG_E("Received TLS FATAL ALERT: %s", gnutls_alert_get_name(gnutls_alert_get(session)) ?: ""); + 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. Very similar to fd_tls_rcvthr_core in this version */ +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 */ + CHECK_FCT_DO(fd_tls_rcvthr_core(conn, conn->cc_tls_para.session, 1), /* continue */); + + TRACE_DEBUG(FULL, "Thread terminated"); + return NULL; +} + + + +/* Send a new Diameter message over the association */ +int fd_sctp_dtls_send(struct cnxctx * conn, unsigned char * buf, size_t len) +{ + ssize_t ret; + size_t sent = 0; + size_t maxlen = gnutls_dtls_get_data_mtu(conn->cc_tls_para.session); + TRACE_ENTRY("%p %p %zd", conn, buf, len); + + CHECK_PARAMS(conn); + + /* First, decide which stream this data will be sent to */ + 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; + } + + /* Now send the data over this stream. Do it in a loop in case the length is larger than the MTU */ + do { + size_t tosend = len - sent; + if (tosend > maxlen) + tosend = maxlen; + CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_tls_para.session, buf + sent, tosend), ); + if (ret <= 0) + return ENOTCONN; + + sent += ret; + } while ( sent < len ); + return 0; +} diff -r 65c6460f60f2 -r cf9bad611f90 libfdcore/server.c --- a/libfdcore/server.c Wed Jun 19 10:20:47 2013 +0800 +++ b/libfdcore/server.c Thu Jun 20 10:20:29 2013 +0800 @@ -53,7 +53,7 @@ struct cnxctx * conn; /* server connection context (listening socket) */ int proto; /* IPPROTO_TCP or IPPROTO_SCTP */ - int secur; /* TLS is started immediatly after connection ? 0: no; 1: RFU; 2: yes (TLS/TCP or TLS/SCTP) */ + int secur; /* TLS is started immediatly after connection ? 0: no; 1: yes (TLS/TCP or DTLS/SCTP); 2: yes (TLS/TCP or TLS/SCTP) */ pthread_t thr; /* The thread waiting for new connections (will store the data in the clients fifo) */ enum s_state state; /* state of the thread */ @@ -359,19 +359,19 @@ /* Create the server on secure port */ if (fd_g_config->cnf_port_tls) { - CHECK_MALLOC( s = new_serv(IPPROTO_SCTP, 2 /* Change when DTLS is introduced */) ); + CHECK_MALLOC( s = new_serv(IPPROTO_SCTP, 1) ); CHECK_MALLOC( s->conn = fd_cnx_serv_sctp(fd_g_config->cnf_port_tls, empty_conf_ep ? NULL : &fd_g_config->cnf_endpoints) ); fd_list_insert_before( &FD_SERVERS, &s->chain ); CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) ); } /* Create the other server on 3436 secure port */ - /*if (fd_g_config->cnf_port_3436) { + if (fd_g_config->cnf_port_3436) { CHECK_MALLOC( s = new_serv(IPPROTO_SCTP, 2) ); CHECK_MALLOC( s->conn = fd_cnx_serv_sctp(fd_g_config->cnf_port_3436, empty_conf_ep ? NULL : &fd_g_config->cnf_endpoints) ); fd_list_insert_before( &FD_SERVERS, &s->chain ); CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) ); - }*/ + } #endif /* DISABLE_SCTP */ } diff -r 65c6460f60f2 -r cf9bad611f90 tests/testcnx.c --- a/tests/testcnx.c Wed Jun 19 10:20:47 2013 +0800 +++ b/tests/testcnx.c Thu Jun 20 10:20:29 2013 +0800 @@ -614,7 +614,6 @@ GNUTLS_X509_FMT_PEM), ); CHECK( 1, ret ); - #ifdef GNUTLS_VERSION_300 { /* We import these CA in the trust list */ gnutls_x509_crt_t * calist; @@ -631,9 +630,6 @@ /* Use certificate verification during the handshake */ gnutls_certificate_set_verify_function (fd_g_config->cnf_sec_data.credentials, fd_tls_verify_credentials_2); - #endif /* GNUTLS_VERSION_300 */ - - /* Set the server credentials (in config) */ CHECK_GNUTLS_DO( ret = gnutls_certificate_set_x509_key_mem( fd_g_config->cnf_sec_data.credentials, &server_cert,