# HG changeset patch # User Sebastien Decugis # Date 1370415749 -28800 # Node ID 773498f595206e6c704acb9b552f6906352a8bd6 # Parent f5e9b53d6f863d94c5a9df79ec08c386b2bd67e2 Preparing for future DTLS/SCTP support diff -r f5e9b53d6f86 -r 773498f59520 doc/freediameter.conf.sample --- a/doc/freediameter.conf.sample Tue Jun 04 18:41:07 2013 +0800 +++ b/doc/freediameter.conf.sample Wed Jun 05 15:02:29 2013 +0800 @@ -33,6 +33,12 @@ # Default: 5658. Use 0 to disable. #SecPort = 5658; +# freeDiameter 1.2.0 introduces the support of 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 this method: +# 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. @@ -211,6 +217,7 @@ # 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 +# SctpSec3436; # Use TLS/SCTP instead of DTLS/SCTP to protect SCTP associations with this peer. # Port = 5658; # The port to connect to # TcTimer = 30; # TwTimer = 30; diff -r f5e9b53d6f86 -r 773498f59520 include/freeDiameter/libfdcore.h --- a/include/freeDiameter/libfdcore.h Tue Jun 04 18:41:07 2013 +0800 +++ b/include/freeDiameter/libfdcore.h Wed Jun 05 15:02:29 2013 +0800 @@ -128,6 +128,7 @@ uint16_t cnf_port; /* the local port for legacy Diameter (default: 3868) in host byte order */ uint16_t cnf_port_tls; /* the local port for Diameter/TLS (default: 5658) in host byte order */ + uint16_t cnf_port_3436; /* Open an additional server port to listen to old TLS/SCTP clients (RFC3436, freeDiameter versions < 1.2.0) */ uint16_t cnf_sctp_str; /* default max number of streams for SCTP associations (def: 30) */ struct fd_list cnf_endpoints; /* the local endpoints to bind the server to. list of struct fd_endpoint. default is empty (bind all). After servers are started, this is the actual list of endpoints including port information. */ struct fd_list cnf_apps; /* Applications locally supported (except relay, see flags). Use fd_disp_app_support to add one. list of struct fd_app. */ @@ -248,6 +249,9 @@ #define PI_SEC_NONE 1 /* Transparent security with this peer (IPsec) */ #define PI_SEC_TLS_OLD 2 /* Old TLS security (use Inband-Security-Id AVP during CER/CEA) */ /* Set sec = 3 to authorize use of (Inband-Security-Id == NONE) with this peer, sec = 2 only authorizing TLS */ + +#define PI_SCTPSEC_DEF 0 /* Use DTLS over SCTP to connect to this peer (default) */ +#define PI_SCTPSEC_3436 1 /* Use TLS over SCTP to connect to this peer (RFC3436) */ #define PI_EXP_NONE 0 /* the peer entry does not expire */ #define PI_EXP_INACTIVE 1 /* the peer entry expires (i.e. is deleted) after pi_lft seconds without activity */ @@ -267,6 +271,7 @@ unsigned pro4 :2; /* PI_P4_* */ unsigned alg :1; /* PI_ALGPREF_* */ unsigned sec :2; /* PI_SEC_* */ + unsigned sctpsec :1; /* PI_SCTPSEC_* */ unsigned exp :1; /* PI_EXP_* */ unsigned persist :1; /* PI_PRST_* */ diff -r f5e9b53d6f86 -r 773498f59520 libfdcore/CMakeLists.txt --- a/libfdcore/CMakeLists.txt Tue Jun 04 18:41:07 2013 +0800 +++ b/libfdcore/CMakeLists.txt Wed Jun 05 15:02:29 2013 +0800 @@ -38,7 +38,7 @@ ) IF(NOT DISABLE_SCTP) - SET(FDCORE_SRC ${FDCORE_SRC} sctp.c sctps.c) + SET(FDCORE_SRC ${FDCORE_SRC} sctp.c sctp3436.c) ENDIF(NOT DISABLE_SCTP) SET(FDCORE_GEN_SRC diff -r f5e9b53d6f86 -r 773498f59520 libfdcore/cnxctx.c --- a/libfdcore/cnxctx.c Tue Jun 04 18:41:07 2013 +0800 +++ b/libfdcore/cnxctx.c Wed Jun 05 15:02:29 2013 +0800 @@ -1533,7 +1533,7 @@ CHECK_FCT( ENOTSUP ); #else /* DISABLE_SCTP */ /* Initialize the wrapper, start the demux thread */ - CHECK_FCT( fd_sctps_init(conn) ); + CHECK_FCT( fd_sctp3436_init(conn) ); #endif /* DISABLE_SCTP */ } else { /* Set the transport pointer passed to push & pull callbacks */ @@ -1593,13 +1593,13 @@ if (conn->cc_sctp_para.pairs > 1) { #ifndef DISABLE_SCTP /* Start reading the messages from the master session. That way, if the remote peer closed, we are not stuck inside handshake */ - CHECK_FCT(fd_sctps_startthreads(conn, 0)); + CHECK_FCT(fd_sctp3436_startthreads(conn, 0)); /* Resume all additional sessions from the master one. */ - CHECK_FCT(fd_sctps_handshake_others(conn, priority, alt_creds)); + CHECK_FCT(fd_sctp3436_handshake_others(conn, priority, alt_creds)); /* Start decrypting the messages from all threads and queuing them in target queue */ - CHECK_FCT(fd_sctps_startthreads(conn, 1)); + CHECK_FCT(fd_sctp3436_startthreads(conn, 1)); #endif /* DISABLE_SCTP */ } else { /* Start decrypting the data */ @@ -1760,9 +1760,9 @@ /* push the record to the appropriate session */ ssize_t ret; size_t sent = 0; - ASSERT(conn->cc_sctps_data.array != NULL); + ASSERT(conn->cc_sctp3436_data.array != NULL); do { - CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_sctps_data.array[conn->cc_sctp_para.next].session, buf + sent, len - sent), ); + CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_sctp3436_data.array[conn->cc_sctp_para.next].session, buf + sent, len - sent), ); if (ret <= 0) return ENOTCONN; @@ -1809,30 +1809,30 @@ if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) { /* and other stream pairs */ - fd_sctps_bye(conn); + fd_sctp3436_bye(conn); } if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) { /* Now wait for all decipher threads to terminate */ - fd_sctps_waitthreadsterm(conn); + fd_sctp3436_waitthreadsterm(conn); } else { /* Abord the threads, the connection is dead already */ - fd_sctps_stopthreads(conn); + fd_sctp3436_stopthreads(conn); } /* Deinit gnutls resources */ - fd_sctps_gnutls_deinit_others(conn); + fd_sctp3436_gnutls_deinit_others(conn); if (conn->cc_tls_para.session) { GNUTLS_TRACE( gnutls_deinit(conn->cc_tls_para.session) ); conn->cc_tls_para.session = NULL; } /* Destroy the wrapper (also stops the demux thread) */ - fd_sctps_destroy(conn); + fd_sctp3436_destroy(conn); } else { #endif /* DISABLE_SCTP */ - /* We are TLS, but not using the sctps wrapper layer */ + /* We are TLS, but not using the sctp3436 wrapper layer */ if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) { /* Master session */ CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) ); diff -r f5e9b53d6f86 -r 773498f59520 libfdcore/cnxctx.h --- a/libfdcore/cnxctx.h Tue Jun 04 18:41:07 2013 +0800 +++ b/libfdcore/cnxctx.h Wed Jun 05 15:02:29 2013 +0800 @@ -77,9 +77,9 @@ /* If both conditions */ struct { - struct sctps_ctx *array; /* an array of cc_sctp_para.pairs elements -- the #0 is special (session is outside)*/ + 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 */ - } cc_sctps_data; + } cc_sctp3436_data; }; void fd_cnx_markerror(struct cnxctx * conn); @@ -120,7 +120,7 @@ int fd_sctp_recvmeta(struct cnxctx * conn, uint16_t * strid, uint8_t ** buf, size_t * len, int *event); /* TLS over SCTP (multi-stream) */ -struct sctps_ctx { +struct sctp3436_ctx { struct cnxctx *parent; /* for info such as socket, conn name, event list */ uint16_t strid; /* Stream # of this session */ struct fifo *raw_recv; /* Raw data received on this stream, for demux */ @@ -133,14 +133,14 @@ gnutls_session_t session; /* TLS context using this pair of streams -- except if strid == 0, in that case session is outside the array */ }; -int fd_sctps_init(struct cnxctx * conn); -int fd_sctps_handshake_others(struct cnxctx * conn, char * priority, void * alt_creds); -int fd_sctps_startthreads(struct cnxctx * conn, int others); -void fd_sctps_bye(struct cnxctx * conn); -void fd_sctps_waitthreadsterm(struct cnxctx * conn); -void fd_sctps_gnutls_deinit_others(struct cnxctx * conn); -void fd_sctps_stopthreads(struct cnxctx * conn); -void fd_sctps_destroy(struct cnxctx * conn); +int fd_sctp3436_init(struct cnxctx * conn); +int fd_sctp3436_handshake_others(struct cnxctx * conn, char * priority, void * alt_creds); +int fd_sctp3436_startthreads(struct cnxctx * conn, int others); +void fd_sctp3436_bye(struct cnxctx * conn); +void fd_sctp3436_waitthreadsterm(struct cnxctx * conn); +void fd_sctp3436_gnutls_deinit_others(struct cnxctx * conn); +void fd_sctp3436_stopthreads(struct cnxctx * conn); +void fd_sctp3436_destroy(struct cnxctx * conn); #endif /* DISABLE_SCTP */ diff -r f5e9b53d6f86 -r 773498f59520 libfdcore/fdd.l --- a/libfdcore/fdd.l Tue Jun 04 18:41:07 2013 +0800 +++ b/libfdcore/fdd.l Wed Jun 05 15:02:29 2013 +0800 @@ -245,6 +245,7 @@ (?i:"Realm") { return REALM; } (?i:"Port") { return PORT; } (?i:"SecPort") { return SECPORT; } +(?i:"SctpSec3436") { return SEC3436; } (?i:"No_IPv6") { return NOIP6; } (?i:"No_IP") { return NOIP; } (?i:"No_TCP") { return NOTCP; } diff -r f5e9b53d6f86 -r 773498f59520 libfdcore/fdd.y --- a/libfdcore/fdd.y Tue Jun 04 18:41:07 2013 +0800 +++ b/libfdcore/fdd.y Wed Jun 05 15:02:29 2013 +0800 @@ -97,6 +97,7 @@ %token REALM %token PORT %token SECPORT +%token SEC3436 %token NOIP %token NOIP6 %token NOTCP @@ -132,6 +133,7 @@ | conffile twtimer | conffile port | conffile secport + | conffile sec3436 | conffile sctpstreams | conffile listenon | conffile norelay @@ -205,6 +207,14 @@ } ; +sec3436: SEC3436 '=' INTEGER ';' + { + CHECK_PARAMS_DO( ($3 >= 0) && ($3 < 1<<16), + { yyerror (&yylloc, conf, "Invalid value"); YYERROR; } ); + conf->cnf_port_3436 = (uint16_t)$3; + } + ; + sctpstreams: SCTPSTREAMS '=' INTEGER ';' { CHECK_PARAMS_DO( ($3 > 0) && ($3 < 1<<16), @@ -452,6 +462,10 @@ { fddpi.config.pic_flags.sec |= PI_SEC_NONE; } + | peerparams SEC3436 ';' + { + fddpi.config.pic_flags.sctpsec |= PI_SCTPSEC_3436; + } | peerparams REALM '=' QSTRING ';' { fddpi.config.pic_realm = $4; diff -r f5e9b53d6f86 -r 773498f59520 libfdcore/sctp3436.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libfdcore/sctp3436.c Wed Jun 05 15:02:29 2013 +0800 @@ -0,0 +1,741 @@ +/********************************************************************************************************* +* 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 code for TLS over multi-stream SCTP wrapper implementation (GnuTLS does not support this) */ +/* See http://aaa.koganei.wide.ad.jp/blogs/index.php/waaad/2008/08/18/tls-over-sctp for history */ + +#include "fdcore-internal.h" +#include "cnxctx.h" + +#include +#include + +/* + +Architecture of this wrapper: + - we have several fifo queues (1 per stream pairs). + GnuTLS is configured to use custom push / pull functions: + - the pull function retrieves the data from the fifo queue corresponding to a stream #. + - the push function sends the data on a certain stream. + We also have a demux thread that reads the socket and store received data in the appropriate fifo + + We have one gnutls_session per stream pair, and as many streams that read the gnutls records and save incoming data to the target queue. + +This complexity is required because we cannot read a socket for a given stream only; we can only get the next message and find its stream. +*/ + +/* TODO: change this whole wrapper to DTLS which should not require many different threads */ + +/*************************************************************/ +/* threads */ +/*************************************************************/ + +/* Demux received data and store in the appropriate fifo */ +static void * demuxer(void * arg) +{ + struct cnxctx * conn = arg; + uint8_t * buf; + size_t bufsz; + int event; + uint16_t strid; + + TRACE_ENTRY("%p", arg); + CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto out); + + /* Set the thread name */ + { + char buf[48]; + snprintf(buf, sizeof(buf), "Demuxer (%d:%s)", conn->cc_socket, conn->cc_remid); + fd_log_threadname ( buf ); + } + + ASSERT( conn->cc_proto == IPPROTO_SCTP ); + ASSERT( fd_cnx_target_queue(conn) ); + ASSERT( conn->cc_sctp3436_data.array ); + + do { + CHECK_FCT_DO( fd_sctp_recvmeta(conn, &strid, &buf, &bufsz, &event), goto fatal ); + switch (event) { + case FDEVP_CNX_MSG_RECV: + /* Demux this message to the appropriate fifo, another thread will pull, gnutls process, and send to target queue */ + if (strid < conn->cc_sctp_para.pairs) { + CHECK_FCT_DO(fd_event_send(conn->cc_sctp3436_data.array[strid].raw_recv, event, bufsz, buf), goto fatal ); + } else { + TRACE_DEBUG(INFO, "Received packet (%zd bytes) on out-of-range stream #%d from %s, discarded.", bufsz, strid, conn->cc_remid); + free(buf); + } + 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, bufsz, buf), goto fatal ); + break; + + case FDEVP_CNX_ERROR: + goto out; + + case FDEVP_CNX_SHUTDOWN: + /* Just ignore the notification for now, we will get another error later anyway */ + continue; + + default: + goto fatal; + } + + } while (conn->cc_loop); + +out: + /* Signal termination of the connection to all decipher threads */ + for (strid = 0; strid < conn->cc_sctp_para.pairs; strid++) { + if (conn->cc_sctp3436_data.array[strid].raw_recv) { + CHECK_FCT_DO(fd_event_send(conn->cc_sctp3436_data.array[strid].raw_recv, FDEVP_CNX_ERROR, 0, NULL), goto fatal ); + } + } + fd_cnx_markerror(conn); + TRACE_DEBUG(FULL, "Thread terminated"); + return NULL; + +fatal: + /* An unrecoverable error occurred, stop the daemon */ + CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), ); + goto out; +} + +/* Decrypt the data received in this stream pair and store it in the target queue */ +static void * decipher(void * arg) +{ + struct sctp3436_ctx * ctx = arg; + struct cnxctx *cnx; + + TRACE_ENTRY("%p", arg); + CHECK_PARAMS_DO(ctx && ctx->raw_recv && ctx->parent, goto error); + cnx = ctx->parent; + ASSERT( fd_cnx_target_queue(cnx) ); + + /* Set the thread name */ + { + char buf[48]; + snprintf(buf, sizeof(buf), "Decipher (%hu@%d:%s)", ctx->strid, cnx->cc_socket, cnx->cc_remid); + fd_log_threadname ( buf ); + } + + /* 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 */); +error: + fd_cnx_markerror(cnx); + TRACE_DEBUG(FULL, "Thread terminated"); + return NULL; +} + +/*************************************************************/ +/* push / pull */ +/*************************************************************/ + +/* Send data over the connection, called by gnutls */ +static ssize_t sctp3436_push(gnutls_transport_ptr_t tr, const void * data, size_t len) +{ + struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) tr; + + TRACE_ENTRY("%p %p %zd", tr, data, len); + CHECK_PARAMS_DO( tr && data, { errno = EINVAL; return -1; } ); + + CHECK_FCT_DO( fd_sctp_sendstr(ctx->parent, ctx->strid, (uint8_t *)data, len), return -1 ); + + return len; +} + +/* Retrieve data received on a stream and already demultiplexed */ +static ssize_t sctp3436_pull(gnutls_transport_ptr_t tr, void * buf, size_t len) +{ + struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) tr; + size_t pulled = 0; + int emptied; + + TRACE_ENTRY("%p %p %zd", tr, buf, len); + CHECK_PARAMS_DO( tr && buf, { errno = EINVAL; goto error; } ); + + /* If we don't have data available now, pull new message from the fifo -- this is blocking (until the queue is destroyed) */ + if (!ctx->partial.buf) { + int ev; + CHECK_FCT_DO( errno = fd_event_get(ctx->raw_recv, &ev, &ctx->partial.bufsz, (void *)&ctx->partial.buf), goto error ); + if (ev == FDEVP_CNX_ERROR) { + /* Documentations says to return 0 on connection closed, but it does hang within gnutls_handshake */ + return -1; + } + } + + pulled = ctx->partial.bufsz - ctx->partial.offset; + if (pulled <= len) { + emptied = 1; + } else { + /* limit to the capacity of destination buffer */ + emptied = 0; + pulled = len; + } + + /* Store the data in the destination buffer */ + memcpy(buf, ctx->partial.buf + ctx->partial.offset, pulled); + + /* Free the buffer if we read all its content, and reset the partial structure */ + if (emptied) { + free(ctx->partial.buf); + memset(&ctx->partial, 0, sizeof(ctx->partial)); + } else { + ctx->partial.offset += pulled; + } + + /* We are done */ + return pulled; + +error: + gnutls_transport_set_errno (ctx->session, errno); + return -1; +} + +/* 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 ) ); +#endif /* GNUTLS_VERSION_300 */ + + /* Set the push and pull callbacks */ + GNUTLS_TRACE( gnutls_transport_set_pull_function(session, sctp3436_pull) ); + GNUTLS_TRACE( gnutls_transport_set_push_function(session, sctp3436_push) ); + + return; +} +#ifndef GNUTLS_VERSION_300 +GCC_DIAG_ON("-Wdeprecated-declarations") +#endif /* !GNUTLS_VERSION_300 */ + +/*************************************************************/ +/* Session resuming support */ +/*************************************************************/ + +struct sr_store { + struct fd_list list; /* list of sr_data, ordered by key.size then key.data */ + pthread_rwlock_t lock; + struct cnxctx *parent; + /* Add another list to chain in a global list to implement a garbage collector on sessions -- TODO if needed */ +}; + +/* Saved master session data for resuming sessions */ +struct sr_data { + struct fd_list chain; + gnutls_datum_t key; + gnutls_datum_t data; +}; + +/* Initialize the store area for a connection */ +static int store_init(struct cnxctx * conn) +{ + TRACE_ENTRY("%p", conn); + CHECK_PARAMS( conn && !conn->cc_sctp3436_data.sess_store ); + + CHECK_MALLOC( conn->cc_sctp3436_data.sess_store = malloc(sizeof(struct sr_store)) ); + memset(conn->cc_sctp3436_data.sess_store, 0, sizeof(struct sr_store)); + + fd_list_init(&conn->cc_sctp3436_data.sess_store->list, NULL); + CHECK_POSIX( pthread_rwlock_init(&conn->cc_sctp3436_data.sess_store->lock, NULL) ); + conn->cc_sctp3436_data.sess_store->parent = conn; + + return 0; +} + +/* Destroy the store area for a connection, and all its content */ +static void store_destroy(struct cnxctx * conn) +{ + /* Del all list entries */ + TRACE_ENTRY("%p", conn); + CHECK_PARAMS_DO( conn, return ); + + if (!conn->cc_sctp3436_data.sess_store) + return; + + CHECK_POSIX_DO( pthread_rwlock_destroy(&conn->cc_sctp3436_data.sess_store->lock), /* continue */ ); + + while (!FD_IS_LIST_EMPTY(&conn->cc_sctp3436_data.sess_store->list)) { + struct sr_data * sr = (struct sr_data *) conn->cc_sctp3436_data.sess_store->list.next; + fd_list_unlink( &sr->chain ); + free(sr->key.data); + free(sr->data.data); + free(sr); + } + + free(conn->cc_sctp3436_data.sess_store); + conn->cc_sctp3436_data.sess_store = NULL; + return; +} + +/* Search the position (or next if not found) of a key in a store */ +static struct fd_list * find_or_next(struct sr_store * sto, gnutls_datum_t key, int * match) +{ + struct fd_list * ret; + *match = 0; + + for (ret = sto->list.next; ret != &sto->list; ret = ret->next) { + int cmp = 0; + struct sr_data * sr = (struct sr_data *)ret; + + cmp = fd_os_cmp(key.data, key.size, sr->key.data, sr->key.size); + if (cmp > 0) + continue; + + if (cmp == 0) + *match = 1; + + break; + } + + return ret; +} + +/* Callbacks for the TLS server side of the connection, called during gnutls_handshake */ +static int sr_store (void *dbf, gnutls_datum_t key, gnutls_datum_t data) +{ + struct sr_store * sto = (struct sr_store *)dbf; + struct fd_list * li; + struct sr_data * sr; + int match = 0; + int ret = 0; + + TRACE_DEBUG( GNUTLS_DBG_LEVEL, "GNUTLS Callback: %s", __PRETTY_FUNCTION__ ); + CHECK_PARAMS_DO( sto && key.data && data.data, return -1 ); + + CHECK_POSIX_DO( pthread_rwlock_wrlock(&sto->lock), return -1 ); + TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Session store [key ", key.data, key.size, "]"); + + li = find_or_next(sto, key, &match); + if (match) { + sr = (struct sr_data *)li; + + /* Check the data is the same */ + if ((data.size != sr->data.size) || memcmp(data.data, sr->data.data, data.size)) { + TRACE_DEBUG(INFO, "GnuTLS tried to store a session with same key and different data!"); + TRACE_BUFFER(FD_LOG_DEBUG, INFO, "Session store [key ", key.data, key.size, "]"); + TRACE_BUFFER(FD_LOG_DEBUG, INFO, " -- old data [", sr->data.data, sr->data.size, "]"); + TRACE_BUFFER(FD_LOG_DEBUG, INFO, " -- new data [", data.data, data.size, "]"); + + ret = -1; + } else { + TRACE_DEBUG(GNUTLS_DBG_LEVEL, "GnuTLS tried to store a session with same key and same data, skipped."); + } + goto out; + } + + /* Create a new entry */ + CHECK_MALLOC_DO( sr = malloc(sizeof(struct sr_data)), { ret = -1; goto out; } ); + memset(sr, 0, sizeof(struct sr_data)); + + fd_list_init(&sr->chain, sr); + + CHECK_MALLOC_DO( sr->key.data = malloc(key.size), { ret = -1; goto out; } ); + sr->key.size = key.size; + memcpy(sr->key.data, key.data, key.size); + + CHECK_MALLOC_DO( sr->data.data = malloc(data.size), { ret = -1; goto out; } ); + sr->data.size = data.size; + memcpy(sr->data.data, data.data, data.size); + + /* Save this new entry in the list, we are done */ + fd_list_insert_before(li, &sr->chain); + +out: + CHECK_POSIX_DO( pthread_rwlock_unlock(&sto->lock), return -1 ); + return ret; +} + +static int sr_remove (void *dbf, gnutls_datum_t key) +{ + struct sr_store * sto = (struct sr_store *)dbf; + struct fd_list * li; + struct sr_data * sr; + int match = 0; + int ret = 0; + + TRACE_DEBUG( GNUTLS_DBG_LEVEL, "GNUTLS Callback: %s", __PRETTY_FUNCTION__ ); + CHECK_PARAMS_DO( sto && key.data, return -1 ); + + CHECK_POSIX_DO( pthread_rwlock_wrlock(&sto->lock), return -1 ); + TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Session delete [key ", key.data, key.size, "]"); + + li = find_or_next(sto, key, &match); + if (match) { + sr = (struct sr_data *)li; + + /* Destroy this data */ + fd_list_unlink(li); + free(sr->key.data); + free(sr->data.data); + free(sr); + } else { + /* It was not found */ + ret = -1; + } + + CHECK_POSIX_DO( pthread_rwlock_unlock(&sto->lock), return -1 ); + return ret; +} + +static gnutls_datum_t sr_fetch (void *dbf, gnutls_datum_t key) +{ + struct sr_store * sto = (struct sr_store *)dbf; + struct fd_list * li; + struct sr_data * sr; + int match = 0; + gnutls_datum_t res = { NULL, 0 }; + gnutls_datum_t error = { NULL, 0 }; + + TRACE_DEBUG( GNUTLS_DBG_LEVEL, "GNUTLS Callback: %s", __PRETTY_FUNCTION__ ); + CHECK_PARAMS_DO( sto && key.data, return error ); + + CHECK_POSIX_DO( pthread_rwlock_rdlock(&sto->lock), return error ); + TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Session fetch [key ", key.data, key.size, "]"); + + li = find_or_next(sto, key, &match); + if (match) { + sr = (struct sr_data *)li; + GNUTLS_TRACE( CHECK_MALLOC_DO(res.data = gnutls_malloc(sr->data.size), goto out ) ); + res.size = sr->data.size; + memcpy(res.data, sr->data.data, res.size); + } +out: + TRACE_DEBUG(GNUTLS_DBG_LEVEL, "Fetched (%p, %d) from store %p", res.data, res.size, sto); + CHECK_POSIX_DO( pthread_rwlock_unlock(&sto->lock), return error); + return res; +} + +/* Set the session pointer in a session object */ +static void set_resume_callbacks(gnutls_session_t session, struct cnxctx * conn) +{ + TRACE_ENTRY("%p", conn); + + GNUTLS_TRACE( gnutls_db_set_retrieve_function(session, sr_fetch)); + GNUTLS_TRACE( gnutls_db_set_remove_function (session, sr_remove)); + GNUTLS_TRACE( gnutls_db_set_store_function (session, sr_store)); + GNUTLS_TRACE( gnutls_db_set_ptr (session, conn->cc_sctp3436_data.sess_store)); + + return; +} + +/* The handshake is made in parallel in several threads to speed up */ +static void * handshake_resume_th(void * arg) +{ + struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) arg; + int resumed; + + TRACE_ENTRY("%p", arg); + + /* Set the thread name */ + { + char buf[48]; + snprintf(buf, sizeof(buf), "Handshake resume (%hu@%d)", ctx->strid, ctx->parent->cc_socket); + fd_log_threadname ( buf ); + } + + TRACE_DEBUG(FULL, "Starting TLS resumed handshake on stream %hu", ctx->strid); + + 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)); + } else { + fd_log_debug("Session was NOT resumed on stream %hu (full handshake) (conn: '%s')", ctx->strid, fd_cnx_getid(ctx->parent)); + } + } + + /* Finished, OK */ + return arg; +} + + +/*************************************************************/ +/* Exported functions */ +/*************************************************************/ + +/* Initialize the wrapper for the connection */ +int fd_sctp3436_init(struct cnxctx * conn) +{ + uint16_t i; + + TRACE_ENTRY("%p", conn); + CHECK_PARAMS( conn && (conn->cc_sctp_para.pairs > 1) && (!conn->cc_sctp3436_data.array) ); + + /* First, alloc the array and initialize the non-TLS data */ + CHECK_MALLOC( conn->cc_sctp3436_data.array = calloc(conn->cc_sctp_para.pairs, sizeof(struct sctp3436_ctx)) ); + for (i = 0; i < conn->cc_sctp_para.pairs; i++) { + conn->cc_sctp3436_data.array[i].parent = conn; + conn->cc_sctp3436_data.array[i].strid = i; + CHECK_FCT( fd_fifo_new(&conn->cc_sctp3436_data.array[i].raw_recv, 10) ); + } + + /* Set push/pull functions in the master session, using fifo in array[0] */ + set_sess_transport(conn->cc_tls_para.session, &conn->cc_sctp3436_data.array[0]); + + /* For server side, we also initialize the resuming capabilities */ + if (conn->cc_tls_para.mode == GNUTLS_SERVER) { + + /* Prepare the store for sessions data */ + CHECK_FCT( store_init(conn) ); + + /* Set the callbacks for resuming in the master session */ + set_resume_callbacks(conn->cc_tls_para.session, conn); + } + + /* Start the demux thread */ + CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, demuxer, conn ) ); + + return 0; +} + +/* Handshake other streams, after full handshake on the master session */ +int fd_sctp3436_handshake_others(struct cnxctx * conn, char * priority, void * alt_creds) +{ + uint16_t i; + int errors = 0; + gnutls_datum_t master_data; + + TRACE_ENTRY("%p %p", conn, priority); + CHECK_PARAMS( conn && (conn->cc_sctp_para.pairs > 1) && conn->cc_sctp3436_data.array ); + + /* Server side: we set all the parameters, the resume callback will take care of resuming the session */ + /* Client side: we duplicate the parameters of the master session, then set the transport pointer */ + + /* For client side, retrieve the master session parameters */ + if (conn->cc_tls_para.mode == GNUTLS_CLIENT) { + CHECK_GNUTLS_DO( gnutls_session_get_data2(conn->cc_tls_para.session, &master_data), return ENOMEM ); + /* For debug: */ + if (TRACE_BOOL(GNUTLS_DBG_LEVEL)) { + uint8_t id[256]; + size_t ids = sizeof(id); + CHECK_GNUTLS_DO( gnutls_session_get_id(conn->cc_tls_para.session, id, &ids), /* continue */ ); + TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Master session id: [", id, ids, "]"); + } + } + + /* Initialize the session objects and start the handshake in a separate thread */ + for (i = 1; i < conn->cc_sctp_para.pairs; i++) { + /* Set credentials and priority */ + CHECK_FCT( fd_tls_prepare(&conn->cc_sctp3436_data.array[i].session, conn->cc_tls_para.mode, 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); + + if ((conn->cc_tls_para.cn != NULL) && (conn->cc_tls_para.mode == GNUTLS_CLIENT)) { + /* this might allow virtual hosting on the remote peer */ + 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 */ + + /* For the client, copy data from master session; for the server, set session resuming pointers */ + if (conn->cc_tls_para.mode == GNUTLS_CLIENT) { + CHECK_GNUTLS_DO( gnutls_session_set_data(conn->cc_sctp3436_data.array[i].session, master_data.data, master_data.size), return ENOMEM ); + } else { + set_resume_callbacks(conn->cc_sctp3436_data.array[i].session, conn); + } + + /* Set transport parameters */ + set_sess_transport(conn->cc_sctp3436_data.array[i].session, &conn->cc_sctp3436_data.array[i]); + + /* Start the handshake thread */ + CHECK_POSIX( pthread_create( &conn->cc_sctp3436_data.array[i].thr, NULL, handshake_resume_th, &conn->cc_sctp3436_data.array[i] ) ); + } + + /* We can now release the memory of master session data if any */ + if (conn->cc_tls_para.mode == GNUTLS_CLIENT) { + GNUTLS_TRACE( gnutls_free(master_data.data) ); + } + + /* Now wait for all handshakes to finish */ + for (i = 1; i < conn->cc_sctp_para.pairs; i++) { + void * ret; + CHECK_POSIX( pthread_join(conn->cc_sctp3436_data.array[i].thr, &ret) ); + conn->cc_sctp3436_data.array[i].thr = (pthread_t) NULL; + if (ret == NULL) { + errors++; /* Handshake failed on this stream */ + } + } + + if (errors) { + TRACE_DEBUG(INFO, "Handshake failed on %d/%hd stream pairs", errors, conn->cc_sctp_para.pairs); + fd_cnx_markerror(conn); + return ENOTCONN; + } + + return 0; +} + +/* Receive messages from others ? all other stream pairs : the master pair */ +int fd_sctp3436_startthreads(struct cnxctx * conn, int others) +{ + uint16_t i; + + TRACE_ENTRY("%p", conn); + CHECK_PARAMS( conn && conn->cc_sctp3436_data.array ); + + if (others) { + for (i = 1; i < conn->cc_sctp_para.pairs; i++) { + + /* Start the decipher thread */ + CHECK_POSIX( pthread_create( &conn->cc_sctp3436_data.array[i].thr, NULL, decipher, &conn->cc_sctp3436_data.array[i] ) ); + } + } else { + CHECK_POSIX( pthread_create( &conn->cc_sctp3436_data.array[0].thr, NULL, decipher, &conn->cc_sctp3436_data.array[0] ) ); + } + return 0; +} + +/* Initiate a "bye" on all stream pairs */ +void fd_sctp3436_bye(struct cnxctx * conn) +{ + uint16_t i; + + CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return ); + + /* End all TLS sessions, in series (not as efficient as paralel, but simpler) */ + for (i = 1; i < conn->cc_sctp_para.pairs; i++) { + if ( ! fd_cnx_teststate(conn, CC_STATUS_ERROR)) { + CHECK_GNUTLS_DO( gnutls_bye(conn->cc_sctp3436_data.array[i].session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) ); + } + } +} + +/* After "bye" was sent on all streams, read from sessions until an error is received */ +void fd_sctp3436_waitthreadsterm(struct cnxctx * conn) +{ + uint16_t i; + + TRACE_ENTRY("%p", conn); + CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return ); + + for (i = 0; i < conn->cc_sctp_para.pairs; i++) { + if (conn->cc_sctp3436_data.array[i].thr != (pthread_t)NULL) { + CHECK_POSIX_DO( pthread_join(conn->cc_sctp3436_data.array[i].thr, NULL), /* continue */ ); + conn->cc_sctp3436_data.array[i].thr = (pthread_t)NULL; + } + } + return; +} + +/* Free gnutls resources of all sessions */ +void fd_sctp3436_gnutls_deinit_others(struct cnxctx * conn) +{ + uint16_t i; + + TRACE_ENTRY("%p", conn); + CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return ); + + for (i = 1; i < conn->cc_sctp_para.pairs; i++) { + if (conn->cc_sctp3436_data.array[i].session) { + GNUTLS_TRACE( gnutls_deinit(conn->cc_sctp3436_data.array[i].session) ); + conn->cc_sctp3436_data.array[i].session = NULL; + } + } +} + + +/* Stop all receiver threads */ +void fd_sctp3436_stopthreads(struct cnxctx * conn) +{ + uint16_t i; + + TRACE_ENTRY("%p", conn); + CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return ); + + for (i = 0; i < conn->cc_sctp_para.pairs; i++) { + CHECK_FCT_DO( fd_thr_term(&conn->cc_sctp3436_data.array[i].thr), /* continue */ ); + } + return; +} + +/* Destroy a wrapper context */ +void fd_sctp3436_destroy(struct cnxctx * conn) +{ + uint16_t i; + + CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return ); + + /* Terminate all receiving threads in case we did not do it yet */ + fd_sctp3436_stopthreads(conn); + + /* Now, stop the demux thread */ + CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ ); + + /* Free remaining data in the array */ + for (i = 0; i < conn->cc_sctp_para.pairs; i++) { + if (conn->cc_sctp3436_data.array[i].raw_recv) + fd_event_destroy( &conn->cc_sctp3436_data.array[i].raw_recv, free ); + free(conn->cc_sctp3436_data.array[i].partial.buf); + if (conn->cc_sctp3436_data.array[i].session) { + GNUTLS_TRACE( gnutls_deinit(conn->cc_sctp3436_data.array[i].session) ); + conn->cc_sctp3436_data.array[i].session = NULL; + } + } + + /* Free the array itself now */ + free(conn->cc_sctp3436_data.array); + conn->cc_sctp3436_data.array = NULL; + + /* Delete the store of sessions */ + store_destroy(conn); + + return ; +} diff -r f5e9b53d6f86 -r 773498f59520 libfdcore/sctps.c --- a/libfdcore/sctps.c Tue Jun 04 18:41:07 2013 +0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,741 +0,0 @@ -/********************************************************************************************************* -* 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 code for TLS over multi-stream SCTP wrapper implementation (GnuTLS does not support this) */ -/* See http://aaa.koganei.wide.ad.jp/blogs/index.php/waaad/2008/08/18/tls-over-sctp for history */ - -#include "fdcore-internal.h" -#include "cnxctx.h" - -#include -#include - -/* - -Architecture of this wrapper: - - we have several fifo queues (1 per stream pairs). - GnuTLS is configured to use custom push / pull functions: - - the pull function retrieves the data from the fifo queue corresponding to a stream #. - - the push function sends the data on a certain stream. - We also have a demux thread that reads the socket and store received data in the appropriate fifo - - We have one gnutls_session per stream pair, and as many streams that read the gnutls records and save incoming data to the target queue. - -This complexity is required because we cannot read a socket for a given stream only; we can only get the next message and find its stream. -*/ - -/* TODO: change this whole wrapper to DTLS which should not require many different threads */ - -/*************************************************************/ -/* threads */ -/*************************************************************/ - -/* Demux received data and store in the appropriate fifo */ -static void * demuxer(void * arg) -{ - struct cnxctx * conn = arg; - uint8_t * buf; - size_t bufsz; - int event; - uint16_t strid; - - TRACE_ENTRY("%p", arg); - CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto out); - - /* Set the thread name */ - { - char buf[48]; - snprintf(buf, sizeof(buf), "Demuxer (%d:%s)", conn->cc_socket, conn->cc_remid); - fd_log_threadname ( buf ); - } - - ASSERT( conn->cc_proto == IPPROTO_SCTP ); - ASSERT( fd_cnx_target_queue(conn) ); - ASSERT( conn->cc_sctps_data.array ); - - do { - CHECK_FCT_DO( fd_sctp_recvmeta(conn, &strid, &buf, &bufsz, &event), goto fatal ); - switch (event) { - case FDEVP_CNX_MSG_RECV: - /* Demux this message to the appropriate fifo, another thread will pull, gnutls process, and send to target queue */ - if (strid < conn->cc_sctp_para.pairs) { - CHECK_FCT_DO(fd_event_send(conn->cc_sctps_data.array[strid].raw_recv, event, bufsz, buf), goto fatal ); - } else { - TRACE_DEBUG(INFO, "Received packet (%zd bytes) on out-of-range stream #%d from %s, discarded.", bufsz, strid, conn->cc_remid); - free(buf); - } - 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, bufsz, buf), goto fatal ); - break; - - case FDEVP_CNX_ERROR: - goto out; - - case FDEVP_CNX_SHUTDOWN: - /* Just ignore the notification for now, we will get another error later anyway */ - continue; - - default: - goto fatal; - } - - } while (conn->cc_loop); - -out: - /* Signal termination of the connection to all decipher threads */ - for (strid = 0; strid < conn->cc_sctp_para.pairs; strid++) { - if (conn->cc_sctps_data.array[strid].raw_recv) { - CHECK_FCT_DO(fd_event_send(conn->cc_sctps_data.array[strid].raw_recv, FDEVP_CNX_ERROR, 0, NULL), goto fatal ); - } - } - fd_cnx_markerror(conn); - TRACE_DEBUG(FULL, "Thread terminated"); - return NULL; - -fatal: - /* An unrecoverable error occurred, stop the daemon */ - CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), ); - goto out; -} - -/* Decrypt the data received in this stream pair and store it in the target queue */ -static void * decipher(void * arg) -{ - struct sctps_ctx * ctx = arg; - struct cnxctx *cnx; - - TRACE_ENTRY("%p", arg); - CHECK_PARAMS_DO(ctx && ctx->raw_recv && ctx->parent, goto error); - cnx = ctx->parent; - ASSERT( fd_cnx_target_queue(cnx) ); - - /* Set the thread name */ - { - char buf[48]; - snprintf(buf, sizeof(buf), "Decipher (%hu@%d:%s)", ctx->strid, cnx->cc_socket, cnx->cc_remid); - fd_log_threadname ( buf ); - } - - /* 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 */); -error: - fd_cnx_markerror(cnx); - TRACE_DEBUG(FULL, "Thread terminated"); - return NULL; -} - -/*************************************************************/ -/* push / pull */ -/*************************************************************/ - -/* Send data over the connection, called by gnutls */ -static ssize_t sctps_push(gnutls_transport_ptr_t tr, const void * data, size_t len) -{ - struct sctps_ctx * ctx = (struct sctps_ctx *) tr; - - TRACE_ENTRY("%p %p %zd", tr, data, len); - CHECK_PARAMS_DO( tr && data, { errno = EINVAL; return -1; } ); - - CHECK_FCT_DO( fd_sctp_sendstr(ctx->parent, ctx->strid, (uint8_t *)data, len), return -1 ); - - return len; -} - -/* Retrieve data received on a stream and already demultiplexed */ -static ssize_t sctps_pull(gnutls_transport_ptr_t tr, void * buf, size_t len) -{ - struct sctps_ctx * ctx = (struct sctps_ctx *) tr; - size_t pulled = 0; - int emptied; - - TRACE_ENTRY("%p %p %zd", tr, buf, len); - CHECK_PARAMS_DO( tr && buf, { errno = EINVAL; goto error; } ); - - /* If we don't have data available now, pull new message from the fifo -- this is blocking (until the queue is destroyed) */ - if (!ctx->partial.buf) { - int ev; - CHECK_FCT_DO( errno = fd_event_get(ctx->raw_recv, &ev, &ctx->partial.bufsz, (void *)&ctx->partial.buf), goto error ); - if (ev == FDEVP_CNX_ERROR) { - /* Documentations says to return 0 on connection closed, but it does hang within gnutls_handshake */ - return -1; - } - } - - pulled = ctx->partial.bufsz - ctx->partial.offset; - if (pulled <= len) { - emptied = 1; - } else { - /* limit to the capacity of destination buffer */ - emptied = 0; - pulled = len; - } - - /* Store the data in the destination buffer */ - memcpy(buf, ctx->partial.buf + ctx->partial.offset, pulled); - - /* Free the buffer if we read all its content, and reset the partial structure */ - if (emptied) { - free(ctx->partial.buf); - memset(&ctx->partial, 0, sizeof(ctx->partial)); - } else { - ctx->partial.offset += pulled; - } - - /* We are done */ - return pulled; - -error: - gnutls_transport_set_errno (ctx->session, errno); - return -1; -} - -/* 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 sctps_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 ) ); -#endif /* GNUTLS_VERSION_300 */ - - /* Set the push and pull callbacks */ - GNUTLS_TRACE( gnutls_transport_set_pull_function(session, sctps_pull) ); - GNUTLS_TRACE( gnutls_transport_set_push_function(session, sctps_push) ); - - return; -} -#ifndef GNUTLS_VERSION_300 -GCC_DIAG_ON("-Wdeprecated-declarations") -#endif /* !GNUTLS_VERSION_300 */ - -/*************************************************************/ -/* Session resuming support */ -/*************************************************************/ - -struct sr_store { - struct fd_list list; /* list of sr_data, ordered by key.size then key.data */ - pthread_rwlock_t lock; - struct cnxctx *parent; - /* Add another list to chain in a global list to implement a garbage collector on sessions -- TODO if needed */ -}; - -/* Saved master session data for resuming sessions */ -struct sr_data { - struct fd_list chain; - gnutls_datum_t key; - gnutls_datum_t data; -}; - -/* Initialize the store area for a connection */ -static int store_init(struct cnxctx * conn) -{ - TRACE_ENTRY("%p", conn); - CHECK_PARAMS( conn && !conn->cc_sctps_data.sess_store ); - - CHECK_MALLOC( conn->cc_sctps_data.sess_store = malloc(sizeof(struct sr_store)) ); - memset(conn->cc_sctps_data.sess_store, 0, sizeof(struct sr_store)); - - fd_list_init(&conn->cc_sctps_data.sess_store->list, NULL); - CHECK_POSIX( pthread_rwlock_init(&conn->cc_sctps_data.sess_store->lock, NULL) ); - conn->cc_sctps_data.sess_store->parent = conn; - - return 0; -} - -/* Destroy the store area for a connection, and all its content */ -static void store_destroy(struct cnxctx * conn) -{ - /* Del all list entries */ - TRACE_ENTRY("%p", conn); - CHECK_PARAMS_DO( conn, return ); - - if (!conn->cc_sctps_data.sess_store) - return; - - CHECK_POSIX_DO( pthread_rwlock_destroy(&conn->cc_sctps_data.sess_store->lock), /* continue */ ); - - while (!FD_IS_LIST_EMPTY(&conn->cc_sctps_data.sess_store->list)) { - struct sr_data * sr = (struct sr_data *) conn->cc_sctps_data.sess_store->list.next; - fd_list_unlink( &sr->chain ); - free(sr->key.data); - free(sr->data.data); - free(sr); - } - - free(conn->cc_sctps_data.sess_store); - conn->cc_sctps_data.sess_store = NULL; - return; -} - -/* Search the position (or next if not found) of a key in a store */ -static struct fd_list * find_or_next(struct sr_store * sto, gnutls_datum_t key, int * match) -{ - struct fd_list * ret; - *match = 0; - - for (ret = sto->list.next; ret != &sto->list; ret = ret->next) { - int cmp = 0; - struct sr_data * sr = (struct sr_data *)ret; - - cmp = fd_os_cmp(key.data, key.size, sr->key.data, sr->key.size); - if (cmp > 0) - continue; - - if (cmp == 0) - *match = 1; - - break; - } - - return ret; -} - -/* Callbacks for the TLS server side of the connection, called during gnutls_handshake */ -static int sr_store (void *dbf, gnutls_datum_t key, gnutls_datum_t data) -{ - struct sr_store * sto = (struct sr_store *)dbf; - struct fd_list * li; - struct sr_data * sr; - int match = 0; - int ret = 0; - - TRACE_DEBUG( GNUTLS_DBG_LEVEL, "GNUTLS Callback: %s", __PRETTY_FUNCTION__ ); - CHECK_PARAMS_DO( sto && key.data && data.data, return -1 ); - - CHECK_POSIX_DO( pthread_rwlock_wrlock(&sto->lock), return -1 ); - TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Session store [key ", key.data, key.size, "]"); - - li = find_or_next(sto, key, &match); - if (match) { - sr = (struct sr_data *)li; - - /* Check the data is the same */ - if ((data.size != sr->data.size) || memcmp(data.data, sr->data.data, data.size)) { - TRACE_DEBUG(INFO, "GnuTLS tried to store a session with same key and different data!"); - TRACE_BUFFER(FD_LOG_DEBUG, INFO, "Session store [key ", key.data, key.size, "]"); - TRACE_BUFFER(FD_LOG_DEBUG, INFO, " -- old data [", sr->data.data, sr->data.size, "]"); - TRACE_BUFFER(FD_LOG_DEBUG, INFO, " -- new data [", data.data, data.size, "]"); - - ret = -1; - } else { - TRACE_DEBUG(GNUTLS_DBG_LEVEL, "GnuTLS tried to store a session with same key and same data, skipped."); - } - goto out; - } - - /* Create a new entry */ - CHECK_MALLOC_DO( sr = malloc(sizeof(struct sr_data)), { ret = -1; goto out; } ); - memset(sr, 0, sizeof(struct sr_data)); - - fd_list_init(&sr->chain, sr); - - CHECK_MALLOC_DO( sr->key.data = malloc(key.size), { ret = -1; goto out; } ); - sr->key.size = key.size; - memcpy(sr->key.data, key.data, key.size); - - CHECK_MALLOC_DO( sr->data.data = malloc(data.size), { ret = -1; goto out; } ); - sr->data.size = data.size; - memcpy(sr->data.data, data.data, data.size); - - /* Save this new entry in the list, we are done */ - fd_list_insert_before(li, &sr->chain); - -out: - CHECK_POSIX_DO( pthread_rwlock_unlock(&sto->lock), return -1 ); - return ret; -} - -static int sr_remove (void *dbf, gnutls_datum_t key) -{ - struct sr_store * sto = (struct sr_store *)dbf; - struct fd_list * li; - struct sr_data * sr; - int match = 0; - int ret = 0; - - TRACE_DEBUG( GNUTLS_DBG_LEVEL, "GNUTLS Callback: %s", __PRETTY_FUNCTION__ ); - CHECK_PARAMS_DO( sto && key.data, return -1 ); - - CHECK_POSIX_DO( pthread_rwlock_wrlock(&sto->lock), return -1 ); - TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Session delete [key ", key.data, key.size, "]"); - - li = find_or_next(sto, key, &match); - if (match) { - sr = (struct sr_data *)li; - - /* Destroy this data */ - fd_list_unlink(li); - free(sr->key.data); - free(sr->data.data); - free(sr); - } else { - /* It was not found */ - ret = -1; - } - - CHECK_POSIX_DO( pthread_rwlock_unlock(&sto->lock), return -1 ); - return ret; -} - -static gnutls_datum_t sr_fetch (void *dbf, gnutls_datum_t key) -{ - struct sr_store * sto = (struct sr_store *)dbf; - struct fd_list * li; - struct sr_data * sr; - int match = 0; - gnutls_datum_t res = { NULL, 0 }; - gnutls_datum_t error = { NULL, 0 }; - - TRACE_DEBUG( GNUTLS_DBG_LEVEL, "GNUTLS Callback: %s", __PRETTY_FUNCTION__ ); - CHECK_PARAMS_DO( sto && key.data, return error ); - - CHECK_POSIX_DO( pthread_rwlock_rdlock(&sto->lock), return error ); - TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Session fetch [key ", key.data, key.size, "]"); - - li = find_or_next(sto, key, &match); - if (match) { - sr = (struct sr_data *)li; - GNUTLS_TRACE( CHECK_MALLOC_DO(res.data = gnutls_malloc(sr->data.size), goto out ) ); - res.size = sr->data.size; - memcpy(res.data, sr->data.data, res.size); - } -out: - TRACE_DEBUG(GNUTLS_DBG_LEVEL, "Fetched (%p, %d) from store %p", res.data, res.size, sto); - CHECK_POSIX_DO( pthread_rwlock_unlock(&sto->lock), return error); - return res; -} - -/* Set the session pointer in a session object */ -static void set_resume_callbacks(gnutls_session_t session, struct cnxctx * conn) -{ - TRACE_ENTRY("%p", conn); - - GNUTLS_TRACE( gnutls_db_set_retrieve_function(session, sr_fetch)); - GNUTLS_TRACE( gnutls_db_set_remove_function (session, sr_remove)); - GNUTLS_TRACE( gnutls_db_set_store_function (session, sr_store)); - GNUTLS_TRACE( gnutls_db_set_ptr (session, conn->cc_sctps_data.sess_store)); - - return; -} - -/* The handshake is made in parallel in several threads to speed up */ -static void * handshake_resume_th(void * arg) -{ - struct sctps_ctx * ctx = (struct sctps_ctx *) arg; - int resumed; - - TRACE_ENTRY("%p", arg); - - /* Set the thread name */ - { - char buf[48]; - snprintf(buf, sizeof(buf), "Handshake resume (%hu@%d)", ctx->strid, ctx->parent->cc_socket); - fd_log_threadname ( buf ); - } - - TRACE_DEBUG(FULL, "Starting TLS resumed handshake on stream %hu", ctx->strid); - - 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)); - } else { - fd_log_debug("Session was NOT resumed on stream %hu (full handshake) (conn: '%s')", ctx->strid, fd_cnx_getid(ctx->parent)); - } - } - - /* Finished, OK */ - return arg; -} - - -/*************************************************************/ -/* Exported functions */ -/*************************************************************/ - -/* Initialize the wrapper for the connection */ -int fd_sctps_init(struct cnxctx * conn) -{ - uint16_t i; - - TRACE_ENTRY("%p", conn); - CHECK_PARAMS( conn && (conn->cc_sctp_para.pairs > 1) && (!conn->cc_sctps_data.array) ); - - /* First, alloc the array and initialize the non-TLS data */ - CHECK_MALLOC( conn->cc_sctps_data.array = calloc(conn->cc_sctp_para.pairs, sizeof(struct sctps_ctx)) ); - for (i = 0; i < conn->cc_sctp_para.pairs; i++) { - conn->cc_sctps_data.array[i].parent = conn; - conn->cc_sctps_data.array[i].strid = i; - CHECK_FCT( fd_fifo_new(&conn->cc_sctps_data.array[i].raw_recv, 10) ); - } - - /* Set push/pull functions in the master session, using fifo in array[0] */ - set_sess_transport(conn->cc_tls_para.session, &conn->cc_sctps_data.array[0]); - - /* For server side, we also initialize the resuming capabilities */ - if (conn->cc_tls_para.mode == GNUTLS_SERVER) { - - /* Prepare the store for sessions data */ - CHECK_FCT( store_init(conn) ); - - /* Set the callbacks for resuming in the master session */ - set_resume_callbacks(conn->cc_tls_para.session, conn); - } - - /* Start the demux thread */ - CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, demuxer, conn ) ); - - return 0; -} - -/* Handshake other streams, after full handshake on the master session */ -int fd_sctps_handshake_others(struct cnxctx * conn, char * priority, void * alt_creds) -{ - uint16_t i; - int errors = 0; - gnutls_datum_t master_data; - - TRACE_ENTRY("%p %p", conn, priority); - CHECK_PARAMS( conn && (conn->cc_sctp_para.pairs > 1) && conn->cc_sctps_data.array ); - - /* Server side: we set all the parameters, the resume callback will take care of resuming the session */ - /* Client side: we duplicate the parameters of the master session, then set the transport pointer */ - - /* For client side, retrieve the master session parameters */ - if (conn->cc_tls_para.mode == GNUTLS_CLIENT) { - CHECK_GNUTLS_DO( gnutls_session_get_data2(conn->cc_tls_para.session, &master_data), return ENOMEM ); - /* For debug: */ - if (TRACE_BOOL(GNUTLS_DBG_LEVEL)) { - uint8_t id[256]; - size_t ids = sizeof(id); - CHECK_GNUTLS_DO( gnutls_session_get_id(conn->cc_tls_para.session, id, &ids), /* continue */ ); - TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Master session id: [", id, ids, "]"); - } - } - - /* Initialize the session objects and start the handshake in a separate thread */ - for (i = 1; i < conn->cc_sctp_para.pairs; i++) { - /* Set credentials and priority */ - CHECK_FCT( fd_tls_prepare(&conn->cc_sctps_data.array[i].session, conn->cc_tls_para.mode, 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_sctps_data.array[i].session, (void *) conn); - - if ((conn->cc_tls_para.cn != NULL) && (conn->cc_tls_para.mode == GNUTLS_CLIENT)) { - /* this might allow virtual hosting on the remote peer */ - CHECK_GNUTLS_DO( gnutls_server_name_set (conn->cc_sctps_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_sctps_data.array[i].session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT)); - #endif /* GNUTLS_VERSION_310 */ - - /* For the client, copy data from master session; for the server, set session resuming pointers */ - if (conn->cc_tls_para.mode == GNUTLS_CLIENT) { - CHECK_GNUTLS_DO( gnutls_session_set_data(conn->cc_sctps_data.array[i].session, master_data.data, master_data.size), return ENOMEM ); - } else { - set_resume_callbacks(conn->cc_sctps_data.array[i].session, conn); - } - - /* Set transport parameters */ - set_sess_transport(conn->cc_sctps_data.array[i].session, &conn->cc_sctps_data.array[i]); - - /* Start the handshake thread */ - CHECK_POSIX( pthread_create( &conn->cc_sctps_data.array[i].thr, NULL, handshake_resume_th, &conn->cc_sctps_data.array[i] ) ); - } - - /* We can now release the memory of master session data if any */ - if (conn->cc_tls_para.mode == GNUTLS_CLIENT) { - GNUTLS_TRACE( gnutls_free(master_data.data) ); - } - - /* Now wait for all handshakes to finish */ - for (i = 1; i < conn->cc_sctp_para.pairs; i++) { - void * ret; - CHECK_POSIX( pthread_join(conn->cc_sctps_data.array[i].thr, &ret) ); - conn->cc_sctps_data.array[i].thr = (pthread_t) NULL; - if (ret == NULL) { - errors++; /* Handshake failed on this stream */ - } - } - - if (errors) { - TRACE_DEBUG(INFO, "Handshake failed on %d/%hd stream pairs", errors, conn->cc_sctp_para.pairs); - fd_cnx_markerror(conn); - return ENOTCONN; - } - - return 0; -} - -/* Receive messages from others ? all other stream pairs : the master pair */ -int fd_sctps_startthreads(struct cnxctx * conn, int others) -{ - uint16_t i; - - TRACE_ENTRY("%p", conn); - CHECK_PARAMS( conn && conn->cc_sctps_data.array ); - - if (others) { - for (i = 1; i < conn->cc_sctp_para.pairs; i++) { - - /* Start the decipher thread */ - CHECK_POSIX( pthread_create( &conn->cc_sctps_data.array[i].thr, NULL, decipher, &conn->cc_sctps_data.array[i] ) ); - } - } else { - CHECK_POSIX( pthread_create( &conn->cc_sctps_data.array[0].thr, NULL, decipher, &conn->cc_sctps_data.array[0] ) ); - } - return 0; -} - -/* Initiate a "bye" on all stream pairs */ -void fd_sctps_bye(struct cnxctx * conn) -{ - uint16_t i; - - CHECK_PARAMS_DO( conn && conn->cc_sctps_data.array, return ); - - /* End all TLS sessions, in series (not as efficient as paralel, but simpler) */ - for (i = 1; i < conn->cc_sctp_para.pairs; i++) { - if ( ! fd_cnx_teststate(conn, CC_STATUS_ERROR)) { - CHECK_GNUTLS_DO( gnutls_bye(conn->cc_sctps_data.array[i].session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) ); - } - } -} - -/* After "bye" was sent on all streams, read from sessions until an error is received */ -void fd_sctps_waitthreadsterm(struct cnxctx * conn) -{ - uint16_t i; - - TRACE_ENTRY("%p", conn); - CHECK_PARAMS_DO( conn && conn->cc_sctps_data.array, return ); - - for (i = 0; i < conn->cc_sctp_para.pairs; i++) { - if (conn->cc_sctps_data.array[i].thr != (pthread_t)NULL) { - CHECK_POSIX_DO( pthread_join(conn->cc_sctps_data.array[i].thr, NULL), /* continue */ ); - conn->cc_sctps_data.array[i].thr = (pthread_t)NULL; - } - } - return; -} - -/* Free gnutls resources of all sessions */ -void fd_sctps_gnutls_deinit_others(struct cnxctx * conn) -{ - uint16_t i; - - TRACE_ENTRY("%p", conn); - CHECK_PARAMS_DO( conn && conn->cc_sctps_data.array, return ); - - for (i = 1; i < conn->cc_sctp_para.pairs; i++) { - if (conn->cc_sctps_data.array[i].session) { - GNUTLS_TRACE( gnutls_deinit(conn->cc_sctps_data.array[i].session) ); - conn->cc_sctps_data.array[i].session = NULL; - } - } -} - - -/* Stop all receiver threads */ -void fd_sctps_stopthreads(struct cnxctx * conn) -{ - uint16_t i; - - TRACE_ENTRY("%p", conn); - CHECK_PARAMS_DO( conn && conn->cc_sctps_data.array, return ); - - for (i = 0; i < conn->cc_sctp_para.pairs; i++) { - CHECK_FCT_DO( fd_thr_term(&conn->cc_sctps_data.array[i].thr), /* continue */ ); - } - return; -} - -/* Destroy a wrapper context */ -void fd_sctps_destroy(struct cnxctx * conn) -{ - uint16_t i; - - CHECK_PARAMS_DO( conn && conn->cc_sctps_data.array, return ); - - /* Terminate all receiving threads in case we did not do it yet */ - fd_sctps_stopthreads(conn); - - /* Now, stop the demux thread */ - CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ ); - - /* Free remaining data in the array */ - for (i = 0; i < conn->cc_sctp_para.pairs; i++) { - if (conn->cc_sctps_data.array[i].raw_recv) - fd_event_destroy( &conn->cc_sctps_data.array[i].raw_recv, free ); - free(conn->cc_sctps_data.array[i].partial.buf); - if (conn->cc_sctps_data.array[i].session) { - GNUTLS_TRACE( gnutls_deinit(conn->cc_sctps_data.array[i].session) ); - conn->cc_sctps_data.array[i].session = NULL; - } - } - - /* Free the array itself now */ - free(conn->cc_sctps_data.array); - conn->cc_sctps_data.array = NULL; - - /* Delete the store of sessions */ - store_destroy(conn); - - return ; -}