Mercurial > hg > waaad
changeset 127:6e655f5ae656
Wrapper functions and test program for doing TLS over SCTP.
author | Sebastien Decugis <sdecugis@nict.go.jp> |
---|---|
date | Fri, 15 Aug 2008 17:53:04 +0900 |
parents | b82a482f0ed8 |
children | 21a5b37c6f35 |
files | extensions/sec_tls_gnutls/Makefile.am extensions/sec_tls_gnutls/gnutls_sctp_wrapper.c extensions/sec_tls_gnutls/gnutls_sctp_wrapper.h extensions/sec_tls_gnutls/sec_tls_gnutls.h |
diffstat | 4 files changed, 2665 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/extensions/sec_tls_gnutls/Makefile.am Fri Aug 15 17:52:26 2008 +0900 +++ b/extensions/sec_tls_gnutls/Makefile.am Fri Aug 15 17:53:04 2008 +0900 @@ -4,9 +4,20 @@ AM_CPPFLAGS = -I$(srcdir)/../../include pkglib_LTLIBRARIES = sec_tls_gnutls.la -sec_tls_gnutls_la_LDFLAGS = -module $(GNUTLS) +sec_tls_gnutls_la_LDFLAGS = -module $(LIBGNUTLS) sec_tls_gnutls_la_SOURCES = sec_tls_gnutls.c \ + gnutls_sctp_wrapper.c \ todo.c +check_PROGRAMS = sctp_tls +TESTS = $(check_PROGRAMS) +# Standalone test program for TLS over SCTP +sctp_tls_SOURCES = gnutls_sctp_wrapper.c +# sctp_tls_CPPFLAGS = -DSTANDALONE_WRAPPER -DDEBUG_SCTP +sctp_tls_CPPFLAGS = -DSTANDALONE_WRAPPER +sctp_tls_LDFLAGS = $(LIBGNUTLS) + +# For development, it is convernient to have this program built also: +noinst_PROGRAMS = sctp_tls
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extensions/sec_tls_gnutls/gnutls_sctp_wrapper.c Fri Aug 15 17:53:04 2008 +0900 @@ -0,0 +1,2332 @@ +/********************************************************************************************************* +* Software License Agreement (BSD License) * +* Author: Sebastien Decugis <sdecugis@nict.go.jp> * +* * +* Copyright (c) 2008, 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. * +*********************************************************************************************************/ + + +/* + * Here are some design insights on the wrapper layer: + * + * One thread (demux) is actually receiving messages and notifications from the socket object. + * When a message is received, along with the stream it is received from, it is saved + * in the buffer queue (FIFO dynamic lists of buffers) corresponding to this stream. + * + * The "pull" callback that the gnutls library expects for transport will actually pull from the + * buffer queue. Conditional variables are used to block and wakeup when messages are received on + * the appropriate stream. + * + * The "push" callback is more straight-forward. + * + * Once the handshake is complete, one thread per stream is created and looping on the + * gnutls_record_recv function, using the appropriate gnutls session. When a message is + * received successfuly, it is queued in the global FIFO for decrypted messages. The + * tls_sctpw_recv function picks up messages from this queue. + */ + + +#include <pthread.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netinet/sctp.h> +#include <sys/uio.h> + +#ifdef STANDALONE_WRAPPER + +# include "gnutls_sctp_wrapper.h" + +/* define to anything to issue debug messages */ +static int is_client = 0; + +# define TRACE_DEBUG(level,format,args... ) \ + printf("%s( %-15s): " format "\n", is_client?" [CLI] ":"[SRV] ", __FUNCTION__, ## args); +/* define to anything to issue trace messages */ +# define TRACE_ENTRY(_format,_args... ) \ + TRACE_DEBUG(1, "Enter (" _format ")" , ##_args ); + +#else /* STANDALONE_WRAPPER */ + +# include "sec_tls_gnutls.h" + +#endif /* STANDALONE_WRAPPER */ + +/**************************************************************************** +***************************************************************************** +** Data structures (internal to this module) ** +***************************************************************************** +****************************************************************************/ + +/* A buffer received on the socket, with its meta-data */ +typedef struct { + unsigned char * data; /* Pointer to the allocated data */ + size_t len; /* Length of the data */ + size_t offset; /* start of unread data */ + int streamid;/* The stream id on which the buffer was received */ +} buffer_t; + +/* An item from a buffer queue list */ +typedef struct _bqi { + buffer_t buf; /* The actual buffer data and meta-data */ + struct _bqi *next; /* Next buffer in the queue */ + struct _bqi *prev; /* Previous buffer in the queue */ +} bqi_t; + +/* A buffer queue head */ +typedef struct { + bqi_t sentinel; /* a dummy element to initialize the chain */ + pthread_mutex_t mtx; /* mutex to protect the list */ + pthread_cond_t cond; /* condvar to signal when new message is posted */ + int count; /* The number of messages in the queue */ +} bq_t; + +/* forward declaration */ +struct _tls_sctpw_ctx; + +/* For each pair of streams in the association, we need the following data: */ +typedef struct { + int stream; /* The stream id of this object. This is also the index in the array */ + struct _tls_sctpw_ctx *ctx; /* Pointer to the head of the context object */ + bq_t queue; /* buffer queue of the encrypted chunks of data received and demux'd */ + gnutls_session_t session;/* The gnutls session corresponding to this pair of streams. */ + pthread_t th; /* After handshake, the thread that receives and decrypts the data */ + int th_run; /* Is the thread created? */ +} pairinfo_t; + +/* State of the wrapper context */ +typedef enum { + SCTPW_CTX_INIT, /* The context has been initialized */ + SCTPW_CTX_OPEN, /* Handshake has been done */ + SCTPW_CTX_BROKEN/* An error occurred on the socket */ +} ctx_state; + +/* Header of the context structure, that contain the data unique to each association */ +typedef struct { + int sock; /* The SCTP socket */ + int nbstrm; /* Number of streams to use */ + int strauto;/* id of the stream to use when "-1" is specified */ + int side; /* 0: server, !0: client */ + ctx_state state; /* The state of this context */ + pthread_t demux; /* The demux thread */ + bq_t rcvd; /* queue of the deciphered chunks of data */ +} ctx_hdr_t; + +/* Now the definition of the wrapper context, corresponding to opaque type tls_sctpw_ctx: */ +typedef struct _tls_sctpw_ctx { + ctx_hdr_t hdr; /* Header of the structure */ + pairinfo_t pairs[]; /* array of hdr.nbstrm * pairinfo_t structures */ +} tls_sctpw_ctx_t; + + +/**************************************************************************** +***************************************************************************** +** TLS over SCTP wrapper library -- to use GNU TLS over SCTP ** +***************************************************************************** +****************************************************************************/ + +/***** Macro to use when dumping sockaddr ******/ +/* char addrbuf[INET6_ADDRLEN] must exist in the context + * sa is an address of a sockaddr + * res is a char * that points to the string in the end. + */ +#define DUMP_SA( sa, res ) { \ + struct sockaddr * _sa_ = (struct sockaddr *)(sa); \ + struct sockaddr_in * _sin_ = (struct sockaddr_in *) (sa); \ + struct sockaddr_in6 * _sin6_ = (struct sockaddr_in6 *) (sa); \ + switch (_sa_->sa_family) { \ + case AF_INET: \ + res = (char *)inet_ntop(AF_INET, \ + &(_sin_->sin_addr.s_addr), \ + addrbuf, \ + sizeof(addrbuf)); \ + break; \ + case AF_INET6: \ + res = (char *)inet_ntop(AF_INET6, \ + &(_sin6_->sin6_addr.s6_addr), \ + addrbuf, \ + sizeof(addrbuf)); \ + break; \ + default: \ + snprintf(addrbuf, \ + sizeof(addrbuf), \ + "Unknown AF: %d", \ + _sa_->sa_family); \ + res = (char *)addrbuf; \ + } \ +} + + +/***** Buffer Queues Management ******/ + +#define BQI_LINK_INIT( _item_ ) { \ + ((bqi_t *)( _item_ ))->next = (bqi_t *)( _item_ ); \ + ((bqi_t *)( _item_ ))->prev = (bqi_t *)( _item_ ); \ +} + +#define BQ_PUSH( bq, bqi ) { \ + bqi_t * _sent_ = &(((bq_t *)( bq ))->sentinel); \ + bqi_t * _new_ = (bqi_t *)( bqi ); \ + _new_->next = _sent_; \ + _new_->prev = _sent_->prev; \ + _sent_->prev->next = _new_; \ + _sent_->prev=_new_; \ + ((bq_t *)( bq ))->count++; \ +} +#define BQ_POP( bq, bqi ) { \ + bqi_t * _rem_ = (bqi_t *)( bqi ); \ + _rem_->next->prev = _rem_->prev; \ + _rem_->prev->next = _rem_->next; \ + BQI_LINK_INIT(_rem_); \ + ((bq_t *)( bq ))->count--; \ +} + +/* Initialize a queue */ +static int bq_init(bq_t * bq) +{ + int ret = 0; + + memset(bq, 0, sizeof(bq_t)); + BQI_LINK_INIT( &bq->sentinel ); + + ret = pthread_mutex_init(&bq->mtx, NULL); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_mutex_init failed: %s", strerror(ret)); + return ret; + } + + ret = pthread_cond_init(&bq->cond, NULL); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_cond_init failed: %s", strerror(ret)); + return ret; + } + + return 0; +} + +/* in case of cancellation */ +static void bq_cleanup(void * _bq) +{ + int ret; + bq_t * bq = (bq_t *)_bq; + + TRACE_ENTRY( "%p", bq ); + + /* Now unlock the queue, and we're done */ + ret = pthread_mutex_unlock( &bq->mtx ); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_mutex_unlock failed: %s", strerror(ret)); + } + + /* End of cleanup handler */ + return; +} + +/* Post a message in a queue (we use directly a bqi_t to avoid re-allocation) */ +static int bq_post(bq_t * bq, bqi_t *buf) +{ + int ret = 0; + + if ((bq == NULL) || (buf == NULL)) { + TRACE_DEBUG(INFO, "Invalid parameters"); + return EINVAL; + } + + /* lock the queue */ + ret = pthread_mutex_lock(&(bq->mtx)); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_mutex_lock failed: %s", strerror(ret)); + return ret; + } + + /* Push the element */ + BQ_PUSH(bq, buf); + + /* Signal the condition */ + ret = pthread_cond_signal(&(bq->cond)); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_cond_signal failed: %s", strerror(ret)); + return ret; + } + + /* unlock the queue */ + ret = pthread_mutex_unlock(&(bq->mtx)); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_mutex_unlock failed: %s", strerror(ret)); + return ret; + } + + return 0; +} + +/* Wait and retrieve a complete message from a queue */ +static int bq_get(bq_t * bq, bqi_t **buf) +{ + int ret = 0; + + if ((bq == NULL) || (buf == NULL)) { + TRACE_DEBUG(INFO, "Invalid parameters"); + return EINVAL; + } + + /* lock the queue */ + ret = pthread_mutex_lock(&(bq->mtx)); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_mutex_lock failed: %s", strerror(ret)); + return ret; + } + +recheck: + if (bq->count > 0) { + *buf = bq->sentinel.next; + BQ_POP( bq, *buf ); + } else { + /* wait for a message to be posted */ + pthread_cleanup_push(bq_cleanup, bq); + ret = pthread_cond_wait(&(bq->cond), &(bq->mtx)); + pthread_cleanup_pop(0); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_cond_wait failed: %s", strerror(ret)); + return ret; + } + /* Need to recheck in case of spurious wake up */ + goto recheck; + } + + /* unlock the queue */ + ret = pthread_mutex_unlock(&(bq->mtx)); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_mutex_unlock failed: %s", strerror(ret)); + return ret; + } + + return 0; +} + +/* Read data into a provided buffer (len is in/out parameter) */ +static int bq_read(bq_t * bq, void * data, size_t *len) +{ + int ret = 0; + bqi_t *bqi = NULL; + + if ((bq == NULL) || (data == NULL) || (len == NULL)) { + TRACE_DEBUG(INFO, "Invalid parameters"); + return EINVAL; + } + + /* lock the queue */ + ret = pthread_mutex_lock(&(bq->mtx)); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_mutex_lock failed: %s", strerror(ret)); + return ret; + } + +recheck: + if (bq->count > 0) { + /* Now we have data to read */ + bqi = bq->sentinel.next; + if (*len >= bqi->buf.len - bqi->buf.offset) { + /* we can read the whole item at once */ + BQ_POP( bq, bqi ); + memcpy(data, bqi->buf.data + bqi->buf.offset, bqi->buf.len - bqi->buf.offset); + *len = bqi->buf.len - bqi->buf.offset; + /* free the bqi, we don't need it anymore */ + free(bqi->buf.data); + free(bqi); + } else { + /* supplied buffer is too small, read only partial buffer */ + memcpy(data, bqi->buf.data + bqi->buf.offset, *len); + bqi->buf.offset += *len; + } + } else { + /* wait for a message to be posted */ + pthread_cleanup_push(bq_cleanup, bq); + ret = pthread_cond_wait(&(bq->cond), &(bq->mtx)); + pthread_cleanup_pop(0); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_cond_wait failed: %s", strerror(ret)); + return ret; + } + /* Need to recheck in case of spurious wake up */ + goto recheck; + } + + /* unlock the queue */ + ret = pthread_mutex_unlock(&(bq->mtx)); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_mutex_unlock failed: %s", strerror(ret)); + return ret; + } + + return 0; +} + +/* Destroy a queue (must be empty) */ +static int bq_fini(bq_t * bq) +{ + int ret; + + if (bq == NULL) { + TRACE_DEBUG(INFO, "Invalid parameters"); + return EINVAL; + } + + while (bq->count) { + bqi_t * del = NULL; + ret = bq_get(bq, &del); + if (ret != 0) { + TRACE_DEBUG(INFO, "bq_get failed: %s", strerror(ret)); + return ret; + } + free(del->buf.data); + free(del); + } + + ret = pthread_cond_destroy(&(bq->cond)); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_cond_destroy failed: %s", strerror(ret)); + return ret; + } + + ret = pthread_mutex_destroy(&(bq->mtx)); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_mutex_destroy failed: %s", strerror(ret)); + return ret; + } + + return 0; +} + +/***** Push and pull functions ******/ + +/* Send data over the connection, called by gnutls */ +static ssize_t sctpw_push (gnutls_transport_ptr_t pair, const void * data, size_t len) +{ + int ret = 0; + int sock; + int stream; + + /* TRACE_ENTRY("%p %p %d", pair, data, len); */ + + if ((pair == NULL) || (data == NULL)) { + TRACE_DEBUG(INFO, "Received invalid NULL parameter"); + return -1; + } + + sock = ((pairinfo_t *)pair)->ctx->hdr.sock; + stream = ((pairinfo_t *)pair)->stream; + + return tls_sctp_send(sock, stream, data, len); +} + +/* Retrieve data received on a stream and demultiplexed already */ +static ssize_t sctpw_pull (gnutls_transport_ptr_t pair, void * buf, size_t buflen) +{ + int ret = 0; + size_t len = buflen; + + /* TRACE_ENTRY("%p %p %d", pair, buf, buflen); */ + + ret = bq_read( &(((pairinfo_t *)pair)->queue), buf, &len ); + if (ret != 0) { + TRACE_DEBUG(INFO, "bq_read failed: %s", strerror(ret)); + return -1; + } + + return len; +} + +/* Read data on the socket and queue it to the appropriate buffer queue */ +static void * sctpw_demux (void * arg) +{ + tls_sctpw_ctx_t * ctx = (tls_sctpw_ctx_t *) arg; + ssize_t ret; + int reti; + tls_sctp_recvevents event; + bqi_t * new; + + /* loop forever; the thread will be canceled when we want to terminate */ + while (1) { + /* Prepare a structure to save the buffer */ + new = (bqi_t *) malloc(sizeof(bqi_t)); + if (new == NULL) { + TRACE_DEBUG(INFO, "malloc failed"); + return NULL; + } + memset(new, 0, sizeof(bqi_t)); + BQI_LINK_INIT(new); + + /* Now receive data from the SCTP socket */ + ret = tls_sctp_recv( ctx->hdr.sock, &event, &(new->buf.streamid), (void **)&(new->buf.data), &(new->buf.len), 4096 ); + if (ret <= 0) + free(new); + + if (ret < 0) { + /* The socket is no more valid */ + TRACE_DEBUG(INFO, "tls_sctp_recv failed"); + + /* we should signal a control thread here */ + + return NULL; + } + + if ((ret == 0) && ((event == RCV_ERROR) || (event == RCV_SHUTDOWN))) { + /* The socket is no more valid */ + TRACE_DEBUG(INFO, "tls_sctp_recv received a termination notification"); + + /* we should signal a control thread here */ + + return NULL; + } + + if (ret == 0) + continue; + + if (new->buf.streamid >= ctx->hdr.nbstrm) { + TRACE_DEBUG(INFO, "Received data on a non-protected stream, discarding."); + free(new); + continue; + } + + /* ok we have received a real message, demux and save it for sctpw_pull */ + reti = bq_post( &(ctx->pairs[new->buf.streamid].queue), new); + if (reti != 0) { + TRACE_DEBUG(INFO, "bq_post failed"); + + /* we should signal a control thread here */ + + return NULL; + } + } + return NULL; +} + +/* Receive data from a pair of streams, once TLS handshake is done */ +#define BUF_SIZE 2048 +static void * sctpw_recv_data (void * arg) +{ + int ret = 0; + pairinfo_t * pair = (pairinfo_t *)arg; + ctx_hdr_t * hdr; + bqi_t * new; + + hdr = &(pair->ctx->hdr); + + while (1) { + new = (bqi_t *) malloc(sizeof(bqi_t)); + if (new == NULL) { + TRACE_DEBUG(INFO, "Memory allocation failed"); + goto term; + } + memset(new, 0, sizeof(bqi_t)); + BQI_LINK_INIT( new ); + new->buf.streamid = pair->stream; + + new->buf.data = malloc( BUF_SIZE ); + if (new->buf.data == NULL) { + TRACE_DEBUG(INFO, "Memory allocation failed"); + goto term; + } + + memset(new->buf.data, 0, BUF_SIZE ); + + ret = gnutls_record_recv ( pair->session, new->buf.data, BUF_SIZE ); + if (ret <= 0) { + TRACE_DEBUG(INFO, "gnutls_record_recv failed (%d)", ret); + goto term; + } + + new->buf.len = ret; + + /* Save the decrypted data in hdr->rcvd */ + ret = bq_post( &(hdr->rcvd), new); + if (ret != 0) { + TRACE_DEBUG(INFO, "bq_post failed: %s", strerror(ret)); + goto term; + } + + } +term: + /* We should signal a control thread here */ + + hdr->state = SCTPW_CTX_BROKEN; + return NULL; +} + +/* terminating / destroying a session */ +static int _tls_sctpw_end( tls_sctpw_ctx ** sctx, int graceful ) +{ + int ret = 0; + int i; + tls_sctpw_ctx_t * _sctx; + + TRACE_ENTRY("%p", sctx); + + /* check parameter. for close call, only OPEN objects are valid */ + if ((sctx == NULL) || (*sctx == NULL)) { + TRACE_DEBUG(INFO, "Invalid parameter"); + return EINVAL; + } + _sctx = (tls_sctpw_ctx_t *) *sctx; + + if (graceful && (_sctx->hdr.state != SCTPW_CTX_OPEN)) { + TRACE_DEBUG(INFO, "Invalid parameter"); + return EINVAL; + } + + /* First, stop the demux thread */ + if (_sctx->hdr.state != SCTPW_CTX_BROKEN) { + ret = pthread_cancel(_sctx->hdr.demux); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_cancel failed: %s", strerror(ret)); + return ret; + } + } + + ret = pthread_join(_sctx->hdr.demux, NULL); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_join failed: %s", strerror(ret)); + return ret; + } + + /* for each pair */ + for ( i= 0; i < _sctx->hdr.nbstrm; i++) { + /* Shutdown the GNU TLS session */ + if (graceful) { + ret = gnutls_bye(_sctx->pairs[i].session, GNUTLS_SHUT_WR); + if (ret != GNUTLS_E_SUCCESS) { + TRACE_DEBUG(INFO, "gnutls_bye returned: %s", gnutls_strerror(ret)); + /* we continue anyway */ + } + } + + /* Terminate (cancel) the thread */ + if (_sctx->pairs[i].th_run) { + ret = pthread_cancel(_sctx->pairs[i].th); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_cancel failed: %s", strerror(ret)); + return ret; + } + + ret = pthread_join(_sctx->pairs[i].th, NULL); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_join failed: %s", strerror(ret)); + return ret; + } + + _sctx->pairs[i].th_run = 0; + + /* Destroy the gnutls session */ + gnutls_deinit (_sctx->pairs[i].session); + } + + /* Empty and destroy the queue */ + ret = bq_fini( &(_sctx->pairs[i].queue) ); + if (ret != 0) { + TRACE_DEBUG(INFO, "bq_fini failed: %s", strerror(ret)); + return ret; + } + } + + /* Destroy the global objects */ + ret = bq_fini(&(_sctx->hdr.rcvd) ); + if (ret != 0) { + TRACE_DEBUG(INFO, "bq_fini failed: %s", strerror(ret)); + return ret; + } + + free(_sctx); + *sctx = NULL; + + return 0; +} + +/********************** + * External functions + *********************/ + +/* Initialize a wrapper context */ +int tls_sctpw_init( int sock, int streams, tls_sctpw_ctx ** sctx, int client, gnutls_credentials_type_t credtype, void ** creds, int (*priocb)(gnutls_session_t *) ) +{ + int ret = 0, i; + tls_sctpw_ctx_t * _sctx = NULL; + + TRACE_ENTRY("%d %d %p %d", sock, streams, sctx, client); + + /* Check parameters */ + if ((sock < 0) || (streams < 0) || (sctx == NULL)) { + TRACE_DEBUG(INFO, "Invalid parameters"); + return EINVAL; + } + + /* Allocate storage for the new context object */ + _sctx = (tls_sctpw_ctx_t *) malloc( sizeof(ctx_hdr_t) + streams * sizeof(pairinfo_t) ); + if (_sctx == NULL) { + ret = errno; + TRACE_DEBUG(INFO, "Memory allocation failed: %s", strerror(ret)); + return ret; + } + + memset(_sctx, 0, sizeof(ctx_hdr_t) + streams * sizeof(pairinfo_t)); + + /* Initialize the header */ + _sctx->hdr.sock = sock; + _sctx->hdr.nbstrm = streams; + _sctx->hdr.side = client; + _sctx->hdr.state = SCTPW_CTX_INIT; + /* _sctx->hdr.demux The demux thread is created in the end of this function */ + ret = bq_init( &(_sctx->hdr.rcvd) ); + if (ret != 0) { + TRACE_DEBUG(INFO, "bq_init failed: %s", strerror(ret)); + free(_sctx); + return ret; + } + + /* Now initialize all pairs */ + for (i = 0; i < streams; i++) { + _sctx->pairs[i].stream = i; + _sctx->pairs[i].ctx = _sctx; + + ret = bq_init( &(_sctx->pairs[i].queue) ); + if (ret != 0) { + TRACE_DEBUG(INFO, "bq_init failed: %s", strerror(ret)); + free(_sctx); + return ret; + } + + /* Now initialize the GNU TLS session for this pair of streams */ + ret = gnutls_init (&(_sctx->pairs[i].session), client ? GNUTLS_CLIENT : GNUTLS_SERVER); + if (ret != GNUTLS_E_SUCCESS) { + TRACE_DEBUG(INFO, "gnutls_init failed: %d", ret); + return ret; + } + + /* The priority setting of the session */ + if (priocb != NULL) { + ret = (*priocb)(&(_sctx->pairs[i].session)); + if (ret != 0) { + TRACE_DEBUG(INFO, "Setting priority with callback failed: %d", ret); + return ret; + } + } else { + ret = gnutls_set_default_priority (_sctx->pairs[i].session); + if (ret != GNUTLS_E_SUCCESS) { + TRACE_DEBUG(INFO, "gnutls_set_default_priority failed: %d", ret); + return ret; + } + } + + /* The credentials settings of the session */ + ret = gnutls_credentials_set (_sctx->pairs[i].session, credtype, *creds); + if (ret != GNUTLS_E_SUCCESS) { + TRACE_DEBUG(INFO, "gnutls_credentials_set failed: %d", ret); + return ret; + } + + /* The transport settings of the session */ + gnutls_transport_set_ptr (_sctx->pairs[i].session, (gnutls_transport_ptr_t) &(_sctx->pairs[i])); + gnutls_transport_set_lowat(_sctx->pairs[i].session, 0 ); + gnutls_transport_set_pull_function(_sctx->pairs[i].session, sctpw_pull); + gnutls_transport_set_push_function(_sctx->pairs[i].session, sctpw_push); + + /* The thread will be created at handshake time */ + } + + /* Now create the demux thread */ + ret = pthread_create( &(_sctx->hdr.demux), NULL, sctpw_demux, (void *)_sctx ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Unable to create a thread: %s", strerror(ret)); + return ret; + } + + *sctx = (tls_sctpw_ctx *) _sctx; + + return 0; +} + +/* Handshake on all streams */ +int tls_sctpw_handshake( tls_sctpw_ctx * sctx, int (*checkcb)(gnutls_session_t session, void * parm), void * parm ) +{ + int ret = 0; + int i; + + tls_sctpw_ctx_t * _sctx = (tls_sctpw_ctx_t *) sctx; + + TRACE_ENTRY("%p %p %p", sctx, checkcb, parm); + + if ((sctx == NULL) || (_sctx->hdr.state != SCTPW_CTX_INIT)) { + TRACE_DEBUG(INFO, "Invalid parameter"); + return EINVAL; + } + + /* For each pair of stream -- we could create separate threads to process all handshakes in parallel */ + for ( i= 0; i < _sctx->hdr.nbstrm; i++) { + /* handshake */ + ret = gnutls_handshake( _sctx->pairs[i].session ); + if (ret < 0) { + TRACE_DEBUG(INFO, "Handshake failed on stream %d: %s", i, gnutls_strerror(ret)); + return ret; + } + + TRACE_DEBUG(FULL, "Handskake complete on stream pair %d", i); + + if (checkcb != NULL) { + /* Now verifying the credentials */ + ret = (*checkcb)(_sctx->pairs[i].session, parm); + if (ret != 0) { + TRACE_DEBUG(INFO, "The callback to verify the credentials returned an error: %d", ret); + return ret; + } + } + + /* create the thread that receives the data */ + ret = pthread_create( &(_sctx->pairs[i].th), NULL, sctpw_recv_data, (void *)&(_sctx->pairs[i])); + if (ret != 0) { + TRACE_DEBUG(INFO, "pthread_create failed: %s", strerror(ret)); + return ret; + } + _sctx->pairs[i].th_run = 1; + } + + /* Mark the state as open now */ + _sctx->hdr.state = SCTPW_CTX_OPEN; + + return 0; +} + +/* Send data protected with TLS */ +int tls_sctpw_send( tls_sctpw_ctx * sctx, int stream, unsigned char * data, size_t len ) +{ + int ret = 0; + ssize_t sent; + size_t rem = len; + tls_sctpw_ctx_t * _sctx = (tls_sctpw_ctx_t *) sctx; + + TRACE_ENTRY("%p %d %p %d", sctx, stream, data, len); + + /* check parameters */ + if ((sctx == NULL) || (_sctx->hdr.state != SCTPW_CTX_OPEN) + || (stream >= _sctx->hdr.nbstrm)) { + TRACE_DEBUG(INFO, "Invalid parameter"); + return EINVAL; + } + + /* For automatic selection of a stream */ + if (stream < 0) { + stream = _sctx->hdr.strauto; + } + /* This is not protected against multithreading, but it is not very important... */ + _sctx->hdr.strauto = ( stream + 1 ) % _sctx->hdr.nbstrm; + + /* now send the data */ + while (rem) { + sent = gnutls_record_send(_sctx->pairs[stream].session, (const void *) data + (len - rem), rem); + if (sent < 0) { + TRACE_DEBUG(INFO, "gnutls_record_send returned %d: %s", sent, gnutls_strerror(sent)); + return sent; + } + rem -= sent; + } + + return 0; +} + +/* Receive protected data */ +int tls_sctpw_recv( tls_sctpw_ctx * sctx, int *stream, void ** data, size_t *len ) +{ + int ret = 0; + + tls_sctpw_ctx_t * _sctx = (tls_sctpw_ctx_t *) sctx; + bqi_t * got = NULL; + + TRACE_ENTRY("%p %p %p %p", sctx, stream, data, len); + + /* check parameters */ + if ((data == NULL) || (len == NULL) || (stream == NULL) || + (sctx == NULL) || (_sctx->hdr.state != SCTPW_CTX_OPEN)) { + TRACE_DEBUG(INFO, "Invalid parameter"); + return EINVAL; + } + + ret = bq_get( &(_sctx->hdr.rcvd), &got ); + if (ret != 0) { + TRACE_DEBUG(INFO, "bq_get failed: %s", strerror(ret)); + return ret; + } + + *stream = got->buf.streamid; + *len = got->buf.len; + *data = got->buf.data; + free(got); + + return 0; +} + +int tls_sctpw_close( tls_sctpw_ctx ** sctx ) +{ + TRACE_ENTRY("%p", sctx); + return _tls_sctpw_end(sctx, 1); +} + +int tls_sctpw_destroy( tls_sctpw_ctx ** sctx ) +{ + TRACE_ENTRY("%p", sctx); + return _tls_sctpw_end(sctx, 0); +} + + + + + + + + + + + + + + +/**************************************************************************** +***************************************************************************** +** TLS over SCTP wrapper test program ** +***************************************************************************** +****************************************************************************/ +#ifdef STANDALONE_WRAPPER + +#include <signal.h> + +#define TEST_PORT 4433 +#define TEST_SRV_STR 4 +#define TEST_CLI_STR 6 + +#define MSG0 "cli -> srv, stream 0" +#define MSG1 "srv -> cli, stream 2" +#define MSG2 "cli -> srv, stream -1" +#define MSG3 "srv -> cli, stream 3" + +static int set_dh_prio(gnutls_session_t * session) +{ + const int kx_prio[] = { GNUTLS_KX_ANON_DH, 0 }; + + gnutls_set_default_priority (*session); + gnutls_kx_set_priority (*session, kx_prio); + + if (!is_client) + gnutls_dh_set_prime_bits (*session, 1024); + + return 0; +} + +/* The client function */ +int client_process() +{ + int ret; + struct sockaddr_in sin; + int streams = TEST_CLI_STR; + int sock; + tls_sctpw_ctx *tls_ctx = NULL; + gnutls_anon_client_credentials_t anoncred; + + TRACE_ENTRY(); + + ret = gnutls_anon_allocate_client_credentials (&anoncred); + if (ret != GNUTLS_E_SUCCESS) { + TRACE_DEBUG(INFO,"Unable to initialize client credentials: %s", gnutls_strerror(ret)); + return -1; + } + + /* Prepare the server information */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT); + inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr.s_addr); + + /* Now connect */ + TRACE_DEBUG(FULL, "Connecting to server ... "); + ret = tls_sctp_connect( &sock, (struct sockaddr *)&sin, sizeof(sin), &streams ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Failed to connect to server: %s", strerror(ret)); + return -1; + } + + /* SCTP socket is ready */ + TRACE_DEBUG(FULL, "Connected; TLS can be run over %d streams", streams); + + /* test exchanging data without TLS */ + TRACE_DEBUG(FULL, "Sending data over SCTP streams 0 and 1 to test basic communication..."); + ret = tls_sctp_send(sock, 0, MSG0, strlen(MSG0)); + if (ret != strlen(MSG0)) { + TRACE_DEBUG(INFO, "Partial send of data: %d / %d", ret, strlen(MSG0)); + return -1; + } + + ret = tls_sctp_send(sock, 1, MSG2, strlen(MSG2)); + if (ret != strlen(MSG2)) { + TRACE_DEBUG(INFO, "Partial send of data: %d / %d", ret, strlen(MSG0)); + return -1; + } + + /* Initialize the wrapper context */ + TRACE_DEBUG(FULL, "Data sent; now creating wrapper context ..."); + ret = tls_sctpw_init( sock, streams, &tls_ctx, 1, GNUTLS_CRD_ANON, (void **)&anoncred, set_dh_prio ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Unable to create wrapper context: %s", strerror(ret)); + return -1; + } + + /* TLS handshake */ + TRACE_DEBUG(FULL, "Wrapper created, now initiating handshake ..."); + ret = tls_sctpw_handshake( tls_ctx, NULL, NULL ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Handshake failed!"); + return -1; + } + + /* Send first message */ + TRACE_DEBUG(FULL, "Handshake complete, now sending data over stream 0..."); + ret = tls_sctpw_send( tls_ctx, 0, MSG0, strlen(MSG0) ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Sending data over encrypted link failed!"); + return -1; + } + + /* To be continued... */ + sleep (2); + + /* Done, free the wrapper context */ + TRACE_DEBUG(FULL, "Test complete, now terminating the wrapper context..."); + ret = tls_sctpw_close( &tls_ctx ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Failed to close the wrapper: %s", strerror(ret)); + return -1; + } + + /* and close the socket */ + TRACE_DEBUG(FULL,"Closing the socket ..."); + ret = close(sock); + if (ret != 0) { + TRACE_DEBUG(FULL, "Failed to close the socket!"); + return -1; + } + + return 0; +} + +/* The server function */ +int server_process() +{ + TRACE_ENTRY(); + + int sock; + int skcli; + int streams; + int ret = 0; + tls_sctpw_ctx *tls_ctx = NULL; + gnutls_dh_params_t dh_params; + gnutls_anon_server_credentials_t anoncred; + + /* Initialize credentials */ + ret = gnutls_anon_allocate_server_credentials (&anoncred); + if (ret != GNUTLS_E_SUCCESS) { + TRACE_DEBUG(INFO,"Unable to initialize server credentials: %s", gnutls_strerror(ret)); + return -1; + } + ret = gnutls_dh_params_init (&dh_params); + if (ret != GNUTLS_E_SUCCESS) { + TRACE_DEBUG(INFO,"Unable to initialize Diffie-Hellman parameters: %s", gnutls_strerror(ret)); + return -1; + } + ret = gnutls_dh_params_generate2 (dh_params, 1024); + if (ret != GNUTLS_E_SUCCESS) { + TRACE_DEBUG(INFO,"Unable to generate Diffie-Hellman parameters: %s", gnutls_strerror(ret)); + return -1; + } + gnutls_anon_set_server_dh_params (anoncred, dh_params); + + /* Create the server socket */ + TRACE_DEBUG(FULL, "Creating server socket on port %d with %d streams ...", TEST_PORT, TEST_SRV_STR); + ret = tls_sctp_create_server( &sock, TEST_PORT, TEST_SRV_STR ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Failed to create server: %s", strerror(ret)); + return -1; + } + + /* Accept a client connection. */ + TRACE_DEBUG(FULL, "Socket created, now waiting for a client connection ..."); + ret = tls_sctp_accept( sock, &skcli, &streams ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Failed to accept connection: %s", strerror(ret)); + return -1; + } + + /* Connection with a client is ready */ + TRACE_DEBUG(FULL, "Client connected, TLS can be run over %d streams", streams); + + /* test exchanging data without TLS */ + { + TRACE_DEBUG(FULL, "Receiving data over SCTP streams 0 and 1 to test basic communication..."); + /* Client: + ret = tls_sctp_send(sock, 0, MSG0, strlen(MSG0)); + ret = tls_sctp_send(sock, 1, MSG2, strlen(MSG2)); + */ + tls_sctp_recvevents event; + int strid; + char * recvd; + size_t len; + int nmsg = 0; + + while (nmsg != 3) { + ret = tls_sctp_recv(skcli, &event, &strid, (void **)&recvd, &len, 5); + if (ret < 0) { + TRACE_DEBUG(INFO,"tls_sctp_recv: %d", ret); + return -1; + } + + if (ret == 0) { + TRACE_DEBUG(INFO,"tls_sctp_recv: event %d", event); + continue; + } + + if ((strid == 0) && (memcmp(recvd, MSG0, strlen(MSG0)) == 0)) + nmsg |= 1; + if ((strid == 1) && (memcmp(recvd, MSG2, strlen(MSG2)) == 0)) + nmsg |= 2; + + free(recvd); + } + } + + /* Now initialize the wrapper object */ + TRACE_DEBUG(FULL, "Data received; now creating wrapper context ..."); + ret = tls_sctpw_init( skcli, streams, &tls_ctx, 0, GNUTLS_CRD_ANON, (void **)&anoncred, set_dh_prio ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Unable to create wrapper context: %s", strerror(ret)); + return -1; + } + + /* TLS Handshake */ + TRACE_DEBUG(FULL, "Wrapper created, now initiating handshake ..."); + ret = tls_sctpw_handshake( tls_ctx, NULL, NULL ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Handshake failed!"); + return -1; + } + + /* Receive the first message sent by the client */ + { + void * buf; + int str; + size_t len; + + TRACE_DEBUG(FULL, "Handshake complete, now receiving TLS-protected data..."); + ret = tls_sctpw_recv( tls_ctx, &str, &buf, &len ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Failed to receive data: %s", strerror(ret)); + return -1; + } + + if ((str != 0) || (strcmp((char *)buf, MSG0))) { + TRACE_DEBUG(INFO, "Received bad data (%d bytes over stream %d)...", len, str); + return EBADMSG; + } + } + + /* Now send our first message */ + TRACE_DEBUG(FULL, "Now sending data over stream 2 to client ..."); + ret = tls_sctpw_send( tls_ctx, 2, MSG1, strlen(MSG1) ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Sending data over encrypted link failed!"); + return -1; + } + + /* To be continued... */ + sleep (2); + + /* Finished, free the wrapper context */ + TRACE_DEBUG(FULL, "Test complete, now terminating the wrapper context..."); + ret = tls_sctpw_close( &tls_ctx ); + if (ret != 0) { + TRACE_DEBUG(INFO, "Failed to close the wrapper: %s", strerror(ret)); + return -1; + } + + /* and close the socket */ + TRACE_DEBUG(FULL,"Closing the connected socket ..."); + ret = close(skcli); + if (ret != 0) { + TRACE_DEBUG(FULL, "Failed to close the socket!"); + return -1; + } + + /* and close the server */ + TRACE_DEBUG(FULL,"Closing the server socket ..."); + ret = close(sock); + if (ret != 0) { + TRACE_DEBUG(FULL, "Failed to close the socket!"); + return -1; + } + + return 0; +} + +GCRY_THREAD_OPTION_PTHREAD_IMPL; + +static void my_gnutls_log(int level, const char * str) +{ + printf("%s[ gnutls * %d ] : %s", is_client?" [CLI] ":"[SRV] ", level, str); + return; +} + +/* Main */ +int main(int argc, char * argv[]) +{ + int ret = 0; + pid_t client; + + TRACE_ENTRY("%d %p", argc, argv); + + /* Initialize gnutls */ + // gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + gnutls_global_init (); + gnutls_global_set_log_function(my_gnutls_log); + gnutls_global_set_log_level(0); + + client = fork(); + if (client == 0) { + /* Client process */ + is_client = 1; + /* Wait for the server to start */ + sleep(2); + /* Then start the client */ + return client_process(); + } else { + if (client == (pid_t) -1) { + TRACE_DEBUG(INFO, "Fork failed!"); + return -1; + } + + /* Set a timeout just in case */ + alarm(20); + + /* server process */ + ret = server_process(); + + TRACE_DEBUG(FULL, "Server process termination code: %d", ret); + + if (ret != 0) { + /* Let's kill the client */ + kill(client, SIGTERM); + } + + /* Wait for child to terminate */ + wait(&ret); + + TRACE_DEBUG(FULL, "Client process termination code: %x", ret); + } + + TRACE_DEBUG(INFO, "%s is terminated.", argv[0]); + return 0; +} + +#endif /* STANDALONE_WRAPPER */ + + + + + + + + + + + + +/**************************************************************************** +***************************************************************************** +** SCTP helper functions, to manage SCTP connections ** +***************************************************************************** +****************************************************************************/ +#ifndef SKIP_SCTP_WRAPPERS /* In case it is defined elsewhere */ + +/* #define DEBUG_SCTP */ + +/* Set socket options */ +static int _tls_sctp_sockopt(int sk, uint16_t nstreams) +{ + int ret = 0; + socklen_t sz; + + /* Subscribe to all notifications */ + { + struct sctp_event_subscribe event; + + memset(&event, 0, sizeof(event)); + event.sctp_data_io_event = 1; /* to receive the stream ID in SCTP_SNDRCV ancilliary data on message reception */ + event.sctp_association_event = 1; /* new or closed assotiations (mostly for one-to-many style sockets) */ + event.sctp_address_event = 1; /* address changes */ + event.sctp_send_failure_event = 1; /* delivery failures */ + event.sctp_peer_error_event = 1; /* remote peer sends an error */ + event.sctp_shutdown_event = 1; /* peer has sent a SHUTDOWN */ + event.sctp_partial_delivery_event = 1; /* a partial delivery is aborted, probably indicating the connection is being shutdown */ + event.sctp_adaptation_layer_event = 1; /* adaptation layer notifications */ + // event.sctp_authentication_event = 1; /* when new key is made active */ + + /* Set the option to the socket */ + ret = setsockopt(sk, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(event)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SCTP_EVENTS options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + #if 0 + sz = sizeof(event); + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_EVENTS, &event, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_EVENTS values: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(event)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(event)); + return ret; + } + #endif /* 0 */ + + TRACE_DEBUG(FULL, "SCTP_EVENTS sctp_data_io_event : %hhu", event.sctp_data_io_event); + TRACE_DEBUG(FULL, "SCTP_EVENTS sctp_association_event : %hhu", event.sctp_association_event); + TRACE_DEBUG(FULL, "SCTP_EVENTS sctp_address_event : %hhu", event.sctp_address_event); + TRACE_DEBUG(FULL, "SCTP_EVENTS sctp_send_failure_event : %hhu", event.sctp_send_failure_event); + TRACE_DEBUG(FULL, "SCTP_EVENTS sctp_peer_error_event : %hhu", event.sctp_peer_error_event); + TRACE_DEBUG(FULL, "SCTP_EVENTS sctp_shutdown_event : %hhu", event.sctp_shutdown_event); + TRACE_DEBUG(FULL, "SCTP_EVENTS sctp_partial_delivery_event : %hhu", event.sctp_partial_delivery_event); + TRACE_DEBUG(FULL, "SCTP_EVENTS sctp_adaptation_layer_event : %hhu", event.sctp_adaptation_layer_event); + // TRACE_DEBUG(FULL, "SCTP_EVENTS sctp_authentication_event : %hhu", event.sctp_authentication_event); +#endif /* DEBUG_SCTP */ + + } + + /* Set the INIT parameters, such as number of streams */ + { + struct sctp_initmsg init; + memset(&init, 0, sizeof(init)); + +#ifdef DEBUG_SCTP + sz = sizeof(init); + + /* Read socket defaults */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG, &init, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_INITMSG values: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(init)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(init)); + return ret; + } + TRACE_DEBUG(FULL, "Def SCTP_INITMSG sinit_num_ostreams : %hu", init.sinit_num_ostreams); + TRACE_DEBUG(FULL, "Def SCTP_INITMSG sinit_max_instreams : %hu", init.sinit_max_instreams); + TRACE_DEBUG(FULL, "Def SCTP_INITMSG sinit_max_attempts : %hu", init.sinit_max_attempts); + TRACE_DEBUG(FULL, "Def SCTP_INITMSG sinit_max_init_timeo : %hu", init.sinit_max_init_timeo); +#endif /* DEBUG_SCTP */ + + /* Set the init options -- need to receive SCTP_COMM_UP to confirm the requested parameters */ + init.sinit_num_ostreams = nstreams; /* desired number of outgoing streams */ + init.sinit_max_instreams = nstreams; /* limit for the number of incoming streams */ + + /* Set the option to the socket */ + ret = setsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG, &init, sizeof(init)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SCTP_INITMSG options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Check new values */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG, &init, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_INITMSG values: %s", strerror(ret)); + return ret; + } + TRACE_DEBUG(FULL, "New SCTP_INITMSG sinit_num_ostreams : %hu", init.sinit_num_ostreams); + TRACE_DEBUG(FULL, "New SCTP_INITMSG sinit_max_instreams : %hu", init.sinit_max_instreams); + TRACE_DEBUG(FULL, "New SCTP_INITMSG sinit_max_attempts : %hu", init.sinit_max_attempts); + TRACE_DEBUG(FULL, "New SCTP_INITMSG sinit_max_init_timeo : %hu", init.sinit_max_init_timeo); +#endif /* DEBUG_SCTP */ + } + + /* Set the SCTP_DISABLE_FRAGMENTS option, required for TLS */ + #ifdef SCTP_DISABLE_FRAGMENTS + { + int nofrag; + +#ifdef DEBUG_SCTP + sz = sizeof(nofrag); + /* Read socket defaults */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &nofrag, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_DISABLE_FRAGMENTS value: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(nofrag)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(nofrag)); + return ret; + } + TRACE_DEBUG(FULL, "Def SCTP_DISABLE_FRAGMENTS value : %s", nofrag ? "true" : "false"); +#endif /* DEBUG_SCTP */ + + nofrag = 0; /* We turn ON the fragmentation */ + + /* Set the option to the socket */ + ret = setsockopt(sk, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &nofrag, sizeof(nofrag)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SCTP_DISABLE_FRAGMENTS options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Check new values */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &nofrag, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_DISABLE_FRAGMENTS value: %s", strerror(ret)); + return ret; + } + TRACE_DEBUG(FULL, "New SCTP_DISABLE_FRAGMENTS value : %s", nofrag ? "true" : "false"); +#endif /* DEBUG_SCTP */ + } + #else /* SCTP_DISABLE_FRAGMENTS */ + # error "TLS requires support of SCTP_DISABLE_FRAGMENTS" + #endif /* SCTP_DISABLE_FRAGMENTS */ + + + /* Set the RETRANSMIT parameters */ + #ifdef SCTP_RTOINFO + { + struct sctp_rtoinfo rtoinfo; + memset(&rtoinfo, 0, sizeof(rtoinfo)); + +#ifdef DEBUG_SCTP + sz = sizeof(rtoinfo); + /* Read socket defaults */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_RTOINFO, &rtoinfo, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_RTOINFO values: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(rtoinfo)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(rtoinfo)); + return ret; + } + TRACE_DEBUG(FULL, "Def SCTP_RTOINFO srto_initial : %u", rtoinfo.srto_initial); + TRACE_DEBUG(FULL, "Def SCTP_RTOINFO srto_max : %u", rtoinfo.srto_max); + TRACE_DEBUG(FULL, "Def SCTP_RTOINFO srto_min : %u", rtoinfo.srto_min); +#endif /* DEBUG_SCTP */ + + /* rtoinfo.srto_assoc_id; */ /* ignored on 1-to-1 sockets */ + rtoinfo.srto_initial = 0; /* Initial retransmit timer (in ms) (0: do not change) */ + rtoinfo.srto_max = 0; /* Maximum retransmit timer (in ms) (0: do not change) */ + rtoinfo.srto_min = 0; /* Minimum retransmit timer (in ms) (0: do not change) */ + + /* Set the option to the socket */ + ret = setsockopt(sk, IPPROTO_SCTP, SCTP_RTOINFO, &rtoinfo, sizeof(rtoinfo)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SCTP_RTOINFO options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Check new values */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_RTOINFO, &rtoinfo, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_RTOINFO values: %s", strerror(ret)); + return ret; + } + TRACE_DEBUG(FULL, "New SCTP_RTOINFO srto_initial : %u", rtoinfo.srto_initial); + TRACE_DEBUG(FULL, "New SCTP_RTOINFO srto_max : %u", rtoinfo.srto_max); + TRACE_DEBUG(FULL, "New SCTP_RTOINFO srto_min : %u", rtoinfo.srto_min); +#endif /* DEBUG_SCTP */ + } + #else /* SCTP_RTOINFO */ + TRACE_DEBUG(FULL, "Skipping SCTP_RTOINFO"); + #endif /* SCTP_RTOINFO */ + + /* Set the ASSOCIATION parameters */ + #ifdef SCTP_ASSOCINFO + { + struct sctp_assocparams assoc; + memset(&assoc, 0, sizeof(assoc)); + +#ifdef DEBUG_SCTP + sz = sizeof(assoc); + /* Read socket defaults */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_ASSOCINFO, &assoc, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_ASSOCINFO values: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(assoc)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(assoc)); + return ret; + } + TRACE_DEBUG(FULL, "Def SCTP_ASSOCINFO sasoc_asocmaxrxt : %hu", assoc.sasoc_asocmaxrxt); + TRACE_DEBUG(FULL, "Def SCTP_ASSOCINFO sasoc_number_peer_destinations : %hu", assoc.sasoc_number_peer_destinations); + TRACE_DEBUG(FULL, "Def SCTP_ASSOCINFO sasoc_peer_rwnd : %u" , assoc.sasoc_peer_rwnd); + TRACE_DEBUG(FULL, "Def SCTP_ASSOCINFO sasoc_local_rwnd : %u" , assoc.sasoc_local_rwnd); + TRACE_DEBUG(FULL, "Def SCTP_ASSOCINFO sasoc_cookie_life : %u" , assoc.sasoc_cookie_life); +#endif /* DEBUG_SCTP */ + + /* assoc.sasoc_assoc_id; */ /* ignored on 1-to-1 sockets */ + assoc.sasoc_asocmaxrxt = 0; /* Maximum retransmission attempts */ + /* Following more read-only : + sasoc_number_peer_destinations + sasoc_peer_rwnd + sasoc_local_rwnd + */ + assoc.sasoc_cookie_life = 0; /* Lifetime for cookies */ + + /* Set the option to the socket */ + ret = setsockopt(sk, IPPROTO_SCTP, SCTP_ASSOCINFO, &assoc, sizeof(assoc)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SCTP_ASSOCINFO options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Check new values */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_ASSOCINFO, &assoc, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_ASSOCINFO values: %s", strerror(ret)); + return ret; + } + TRACE_DEBUG(FULL, "New SCTP_ASSOCINFO sasoc_asocmaxrxt : %hu", assoc.sasoc_asocmaxrxt); + TRACE_DEBUG(FULL, "New SCTP_ASSOCINFO sasoc_number_peer_destinations : %hu", assoc.sasoc_number_peer_destinations); + TRACE_DEBUG(FULL, "New SCTP_ASSOCINFO sasoc_peer_rwnd : %u" , assoc.sasoc_peer_rwnd); + TRACE_DEBUG(FULL, "New SCTP_ASSOCINFO sasoc_local_rwnd : %u" , assoc.sasoc_local_rwnd); + TRACE_DEBUG(FULL, "New SCTP_ASSOCINFO sasoc_cookie_life : %u" , assoc.sasoc_cookie_life); +#endif /* DEBUG_SCTP */ + } + #else /* SCTP_ASSOCINFO */ + TRACE_DEBUG(FULL, "Skipping SCTP_ASSOCINFO"); + #endif /* SCTP_ASSOCINFO */ + + + /* The SO_LINGER option will be reset if we want to perform SCTP ABORT */ + #ifdef SO_LINGER + { + struct linger linger; + memset(&linger, 0, sizeof(linger)); + +#ifdef DEBUG_SCTP + sz = sizeof(linger); + /* Read socket defaults */ + ret = getsockopt(sk, SOL_SOCKET, SO_LINGER, &linger, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SO_LINGER values: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(linger)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(linger)); + return ret; + } + TRACE_DEBUG(FULL, "Def SO_LINGER l_onoff : %d", linger.l_onoff); + TRACE_DEBUG(FULL, "Def SO_LINGER l_linger : %d", linger.l_linger); +#endif /* DEBUG_SCTP */ + + linger.l_onoff = 0; /* Do not activate the linger */ + linger.l_linger = 0; /* Return immediately when closing (=> abort) */ + + /* Set the option */ + ret = setsockopt(sk, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SO_LINGER options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Check new values */ + ret = getsockopt(sk, SOL_SOCKET, SO_LINGER, &linger, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SO_LINGER values: %s", strerror(ret)); + return ret; + } + TRACE_DEBUG(FULL, "New SO_LINGER l_onoff : %d", linger.l_onoff); + TRACE_DEBUG(FULL, "New SO_LINGER l_linger : %d", linger.l_linger); +#endif /* DEBUG_SCTP */ + } + #else /* SO_LINGER */ + TRACE_DEBUG(FULL, "Skipping SO_LINGER"); + #endif /* SO_LINGER */ + + /* Set the NODELAY option (Nagle-like algorithm) */ + #ifdef SCTP_NODELAY + { + int nodelay; + +#ifdef DEBUG_SCTP + sz = sizeof(nodelay); + /* Read socket defaults */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_NODELAY value: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(nodelay)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(nodelay)); + return ret; + } + TRACE_DEBUG(FULL, "Def SCTP_NODELAY value : %s", nodelay ? "true" : "false"); +#endif /* DEBUG_SCTP */ + + nodelay = 0; /* We turn ON the Nagle algorithm */ + + /* Set the option to the socket */ + ret = setsockopt(sk, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SCTP_NODELAY options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Check new values */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_NODELAY value: %s", strerror(ret)); + return ret; + } + TRACE_DEBUG(FULL, "New SCTP_NODELAY value : %s", nodelay ? "true" : "false"); +#endif /* DEBUG_SCTP */ + } + #else /* SCTP_NODELAY */ + TRACE_DEBUG(FULL, "Skipping SCTP_NODELAY"); + #endif /* SCTP_NODELAY */ + + /* Set the interleaving option */ + #ifdef SCTP_FRAGMENT_INTERLEAVE + { + int interleave; + +#ifdef DEBUG_SCTP + sz = sizeof(interleave); + /* Read socket defaults */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &interleave, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_FRAGMENT_INTERLEAVE value: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(interleave)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(interleave)); + return ret; + } + TRACE_DEBUG(FULL, "Def SCTP_FRAGMENT_INTERLEAVE value : %d", interleave); +#endif /* DEBUG_SCTP */ + + interleave = 2; /* Allow partial delivery on several streams at the same time, since we are stream-aware in TLS */ + + /* Set the option to the socket */ + ret = setsockopt(sk, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &interleave, sizeof(interleave)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SCTP_FRAGMENT_INTERLEAVE options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Check new values */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &interleave, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_FRAGMENT_INTERLEAVE value: %s", strerror(ret)); + return ret; + } + TRACE_DEBUG(FULL, "New SCTP_FRAGMENT_INTERLEAVE value : %d", interleave); +#endif /* DEBUG_SCTP */ + } + #else /* SCTP_FRAGMENT_INTERLEAVE */ + TRACE_DEBUG(FULL, "Skipping SCTP_FRAGMENT_INTERLEAVE"); + #endif /* SCTP_FRAGMENT_INTERLEAVE */ + + /* Set the ASCONF option */ + #ifdef SCTP_AUTO_ASCONF + { + int asconf; + +#ifdef DEBUG_SCTP + sz = sizeof(asconf); + /* Read socket defaults */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_AUTO_ASCONF, &asconf, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_AUTO_ASCONF value: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(asconf)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(asconf)); + return ret; + } + TRACE_DEBUG(FULL, "Def SCTP_I_WANT_MAPPED_V4_ADDR value : %s", asconf ? "true" : "false"); +#endif /* DEBUG_SCTP */ + + asconf = 1; /* allow automatic use of added or removed addresses in the association */ + + /* Set the option to the socket */ + ret = setsockopt(sk, IPPROTO_SCTP, SCTP_AUTO_ASCONF, &asconf, sizeof(asconf)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SCTP_AUTO_ASCONF options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Check new values */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_AUTO_ASCONF, &asconf, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_AUTO_ASCONF value: %s", strerror(ret)); + return ret; + } + TRACE_DEBUG(FULL, "New SCTP_AUTO_ASCONF value : %s", asconf ? "true" : "false"); +#endif /* DEBUG_SCTP */ + } + #else /* SCTP_AUTO_ASCONF */ + TRACE_DEBUG(FULL, "Skipping SCTP_AUTO_ASCONF"); + #endif /* SCTP_AUTO_ASCONF */ + + /* Set the v4 mapped addresses option */ + #ifdef SCTP_I_WANT_MAPPED_V4_ADDR + { + int v4mapped; + +#ifdef DEBUG_SCTP + sz = sizeof(v4mapped); + /* Read socket defaults */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, &v4mapped, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_I_WANT_MAPPED_V4_ADDR value: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(v4mapped)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(v4mapped)); + return ret; + } + TRACE_DEBUG(FULL, "Def SCTP_I_WANT_MAPPED_V4_ADDR value : %s", v4mapped ? "true" : "false"); +#endif /* DEBUG_SCTP */ + + v4mapped = 0; /* We don't want v4 mapped addresses */ + v4mapped = 1; /* but we have to, otherwise the bind fails under linux currently ... */ + + /* Set the option to the socket */ + ret = setsockopt(sk, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, &v4mapped, sizeof(v4mapped)); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to set the socket SCTP_I_WANT_MAPPED_V4_ADDR options: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Check new values */ + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, &v4mapped, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_I_WANT_MAPPED_V4_ADDR value: %s", strerror(ret)); + return ret; + } + TRACE_DEBUG(FULL, "New SCTP_I_WANT_MAPPED_V4_ADDR value : %s", v4mapped ? "true" : "false"); +#endif /* DEBUG_SCTP */ + } + #else /* SCTP_I_WANT_MAPPED_V4_ADDR */ + TRACE_DEBUG(FULL, "Skipping SCTP_I_WANT_MAPPED_V4_ADDR"); + #endif /* SCTP_I_WANT_MAPPED_V4_ADDR */ + + + /* Other settable options (draft-ietf-tsvwg-sctpsocket-17): + SO_RCVBUF size of receiver window + SO_SNDBUF size of pending data to send + SCTP_AUTOCLOSE for one-to-many only + SCTP_SET_PEER_PRIMARY_ADDR ask remote peer to use this local address as primary + SCTP_PRIMARY_ADDR use this address as primary locally + SCTP_ADAPTATION_LAYER set adaptation layer indication + SCTP_PEER_ADDR_PARAMS control heartbeat per peer address + SCTP_DEFAULT_SEND_PARAM parameters for the sendto() call + SCTP_MAXSEG max size of fragmented segments -- bound to PMTU + SCTP_AUTH_CHUNK request authentication of some type of chunk + SCTP_HMAC_IDENT authentication algorithms + SCTP_AUTH_KEY set a shared key + SCTP_AUTH_ACTIVE_KEY set the active key + SCTP_AUTH_DELETE_KEY remove a key + SCTP_AUTH_DEACTIVATE_KEY will not use that key anymore + SCTP_DELAYED_SACK control delayed acks + SCTP_PARTIAL_DELIVERY_POINT control partial delivery size + SCTP_USE_EXT_RCVINFO use extended receive info structure (information about the following message) + SCTP_MAX_BURST number of packets that can be burst emitted + SCTP_CONTEXT save a context information along with the association. + SCTP_EXPLICIT_EOR enable sending one message across several send calls + SCTP_REUSE_PORT share one listening port with several sockets + + read-only options: + SCTP_STATUS retrieve info such as number of streams, pending packets, state, ... + SCTP_GET_PEER_ADDR_INFO get information about a specific peer address of the association. + SCTP_PEER_AUTH_CHUNKS list of chunks the remote peer wants authenticated + SCTP_LOCAL_AUTH_CHUNKS list of chunks the local peer wants authenticated + SCTP_GET_ASSOC_NUMBER number of associations in a one-to-many socket + SCTP_GET_ASSOC_ID_LIST list of these associations + */ + + return 0; +} + +/********************** + * External functions + *********************/ + +/* Create a server listening SCTP socket */ +int tls_sctp_create_server( int * sock, uint16_t port, int streams ) +{ +/* + * -> Create the socket : int socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP); + * -> Set options (streams,...): setsockopt(SCTP_INITMSG, ...) + * Some options are mandatory for TLS, such as SCTP_DISABLE_FRAGMENTS=OFF + * Some others are useful, such as SCTP_EVENTS. + * -> (server) Bind the socket : int bind( sd, addr, addrlen); + * -> and/or : int sctp_bindx( sd, addrs, addrcnt, flags); + * -> (server) Listen for cnx : int listen( sd, backlog); + */ + int ret = 0; + int sk; + struct sockaddr_in sin; + + TRACE_ENTRY("%p %hd %d", sock, port, streams); + + /* Check parameters */ + if (sock == NULL) { + TRACE_DEBUG(INFO, "Invalid parameter"); + return EINVAL; + } + + /* Create the socket */ + sk = socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP); + if (sk == -1) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to create SCTP socket: %s", strerror(ret)); + return ret; + } + + /* Subscribe to notifications on this socket, so all children socket will inherit the options */ + ret = _tls_sctp_sockopt(sk, (uint16_t) streams); + if (ret != 0) { + TRACE_DEBUG(INFO, "Unable to set all the socket options: %s", strerror(ret)); + return ret; + } + + /* Address and port where we are listening */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = INADDR_ANY; + + /* Bind the socket -- wildcard will allow multihoming to be activated. */ + ret = bind(sk, (struct sockaddr *)&sin, sizeof(sin)); + /* note: if more precise binding is needed, use sctp_bindx function */ + if (ret == -1) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to bind the socket: %s", strerror(ret)); + return ret; + } + +#ifdef DEBUG_SCTP + /* Debug: show all local listening addresses */ + { + struct sockaddr *ss, *cur; + int i, sz; + sz = sctp_getladdrs(sk, 0, &ss); + if (sz == -1) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to retrieve list of local addresses: %s", strerror(ret)); + return ret; + } + cur = ss; + for (i=0; i<sz; i++) { + switch (cur->sa_family) { + case AF_INET: + { + char buf[INET_ADDRSTRLEN]; + struct sockaddr_in *sin = (struct sockaddr_in *)cur; + cur = (struct sockaddr *)(sin + 1); + i++; + + TRACE_DEBUG(FULL, "Bound on local IP: %s", inet_ntop(AF_INET, &sin->sin_addr.s_addr, buf, sizeof(buf))); + } + break; + + case AF_INET6: + { + char buf[INET6_ADDRSTRLEN]; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)cur; + cur = (struct sockaddr *)(sin6 + 1); + i++; + + TRACE_DEBUG(FULL, "Bound on local IPv6: %s", inet_ntop(AF_INET6, &sin6->sin6_addr.s6_addr, buf, sizeof(buf))); + } + break; + + default: + TRACE_DEBUG(FULL, "Bound on unknown address family: %d", cur->sa_family); + i=sz; + } + } + + sctp_freeladdrs(ss); + } +#endif /* DEBUG_SCTP */ + + /* Listen, to enable remote peers to connect */ + ret = listen(sk, 2); + if (ret == -1) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to listen on the socket: %s", strerror(ret)); + return ret; + } + + *sock = sk; + return 0; +} + +/* Accept client on a server listening SCTP socket */ +int tls_sctp_accept( int sock, int * new_sd, int *streams ) +{ + int ret = 0; + int skcli; + struct sctp_status status; + socklen_t sz = sizeof(status); + + TRACE_ENTRY("%i %p %p", sock, new_sd, streams); + + /* Check params */ + if ((new_sd == NULL) || (streams == NULL)) { + TRACE_DEBUG(INFO, "Invalid parameter"); + return EINVAL; + } + + /* Now wait for a client connection */ + TRACE_DEBUG(FULL, "Waiting for client connection..."); + skcli = accept(sock, NULL, NULL); + if (skcli == -1) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to accept incoming connection: %s", strerror(ret)); + return ret; + } + + /* We now have a client connected, get some information */ + + /* Read the association parameters */ + memset(&status, 0, sizeof(status)); + ret = getsockopt(skcli, IPPROTO_SCTP, SCTP_STATUS, &status, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_STATUS value: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(status)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(status)); + return ret; + } +#ifdef DEBUG_SCTP + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_state : %i" , status.sstat_state); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_rwnd : %u" , status.sstat_rwnd); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_unackdata : %hu", status.sstat_unackdata); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_penddata : %hu", status.sstat_penddata); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_instrms : %hu", status.sstat_instrms); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_outstrms : %hu", status.sstat_outstrms); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_fragmentation_point : %u" , status.sstat_fragmentation_point); + { + char *str, addrbuf[INET6_ADDRSTRLEN]; + DUMP_SA( &status.sstat_primary.spinfo_address, str ); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_primary.spinfo_address : %s" , str); + } + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_primary.spinfo_state : %d" , status.sstat_primary.spinfo_state); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_primary.spinfo_cwnd : %u" , status.sstat_primary.spinfo_cwnd); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_primary.spinfo_srtt : %u" , status.sstat_primary.spinfo_srtt); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_primary.spinfo_rto : %u" , status.sstat_primary.spinfo_rto); + TRACE_DEBUG(FULL, "Accepted Cli SCTP_STATUS sstat_primary.spinfo_mtu : %u" , status.sstat_primary.spinfo_mtu); +#endif /* DEBUG_SCTP */ + + if (status.sstat_instrms > status.sstat_outstrms) + *streams = status.sstat_outstrms; + else + *streams = status.sstat_instrms; + + *new_sd = skcli; + + return 0; +} + +/* Connect client to a remote server listening SCTP socket */ +int tls_sctp_connect( int *sock, const struct sockaddr *addr, socklen_t addrlen, int *streams ) +{ + int ret = 0; + int sk; + struct sctp_status status; + socklen_t sz = sizeof(status); + + TRACE_ENTRY("%p %p %d %p", sock, addr, addrlen, streams); + + /* Check parameters */ + if ((sock == NULL) || (addr == NULL) || (streams == NULL)) { + TRACE_DEBUG(INFO, "Invalid parameter"); + return EINVAL; + } + + /* Create the socket */ + sk = socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP); + if (sk == -1) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to create SCTP socket: %s", strerror(ret)); + return ret; + } + + /* Subscribe to notifications on this socket */ + ret = _tls_sctp_sockopt(sk, (uint16_t) *streams); + if (ret != 0) { + TRACE_DEBUG(INFO, "Unable to set all the socket options: %s", strerror(ret)); + return ret; + } + + /* Now connect to the server */ + { + char *str, addrbuf[INET6_ADDRSTRLEN]; + DUMP_SA( addr, str ); + TRACE_DEBUG(FULL, "Connection to %s ..." , str); + } + + ret = connect(sk, addr, addrlen); + if (ret == -1) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to connect to server: %s", strerror(ret)); + return ret; + } + + *sock = sk; + + /* Now that we are connected, retrieve the number of streams */ + + /* Read the association parameters */ + memset(&status, 0, sizeof(status)); + ret = getsockopt(sk, IPPROTO_SCTP, SCTP_STATUS, &status, &sz); + if (ret != 0) { + ret = errno; + TRACE_DEBUG(INFO, "Unable to get the socket SCTP_STATUS value: %s", strerror(ret)); + return ret; + } + if (sz != sizeof(status)) + { + ret = ENOTSUP; + TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(status)); + return ret; + } +#ifdef DEBUG_SCTP + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_state : %i" , status.sstat_state); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_rwnd : %u" , status.sstat_rwnd); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_unackdata : %hu", status.sstat_unackdata); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_penddata : %hu", status.sstat_penddata); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_instrms : %hu", status.sstat_instrms); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_outstrms : %hu", status.sstat_outstrms); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_fragmentation_point : %u" , status.sstat_fragmentation_point); + { + char *str, addrbuf[INET6_ADDRSTRLEN]; + DUMP_SA( &status.sstat_primary.spinfo_address, str ); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_primary.spinfo_address : %s" , str); + } + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_primary.spinfo_state : %d" , status.sstat_primary.spinfo_state); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_primary.spinfo_cwnd : %u" , status.sstat_primary.spinfo_cwnd); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_primary.spinfo_srtt : %u" , status.sstat_primary.spinfo_srtt); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_primary.spinfo_rto : %u" , status.sstat_primary.spinfo_rto); + TRACE_DEBUG(FULL, "Cli SCTP_STATUS sstat_primary.spinfo_mtu : %u" , status.sstat_primary.spinfo_mtu); +#endif /* DEBUG_SCTP */ + + if (status.sstat_instrms > status.sstat_outstrms) + *streams = status.sstat_outstrms; + else + *streams = status.sstat_instrms; + + return 0; +} + + +/* wrapper to sendmsg */ +ssize_t tls_sctp_send(int socket, int streamid, const void * data, size_t len) +{ + struct msghdr mhdr; + struct iovec iov; + struct { + struct cmsghdr hdr; + struct sctp_sndrcvinfo sndrcv; + } anci; + + memset(&mhdr, 0, sizeof(mhdr)); + memset(&iov, 0, sizeof(iov)); + memset(&anci, 0, sizeof(anci)); + + /* IO Vector: message data */ + iov.iov_base = (unsigned char *)data; + iov.iov_len = len; + + /* Anciliary data: specify SCTP stream */ + anci.hdr.cmsg_len = sizeof(anci); + anci.hdr.cmsg_level = IPPROTO_SCTP; + anci.hdr.cmsg_type = SCTP_SNDRCV; + anci.sndrcv.sinfo_stream = (uint16_t) streamid; + /* note : we can store some data also in .sinfo_ppid (for remote peer) and .sinfo_context (for errors) */ + + /* We don't use mhdr.msg_name here; it could be used to specify an address different of the primary */ + + mhdr.msg_iov = &iov; + mhdr.msg_iovlen = 1; + + mhdr.msg_control = &anci; + mhdr.msg_controllen = sizeof(anci); + + TRACE_DEBUG(FULL, "Sending SCTP packet, len: %d, stream %d", len, streamid); + + return sendmsg(socket, &mhdr, 0); +} + +#define CMSG_BUF_LEN 1024 + +/* wrapper to recvmsg */ +ssize_t tls_sctp_recv(int socket, tls_sctp_recvevents * event, int * streamid, void ** data, size_t *len, size_t init) +{ + ssize_t ret = 0; + struct msghdr mhdr; + char ancidata[ CMSG_BUF_LEN ]; /* Prepare to receive lot of anciliary data ... this probably may be shrinked */ + struct cmsghdr *hdr; + struct sctp_sndrcvinfo *sndrcv; + struct iovec iov; + size_t sz = init; + + *event = RCV_NO_EVENT; + *streamid = -1; + *data = NULL; + *len = 0; + + *data = malloc(sz); + if (*data == NULL) { + TRACE_DEBUG(INFO, "Malloc failed"); + return -1; + } + + memset(&mhdr, 0, sizeof(mhdr)); + mhdr.msg_iov = &iov; + mhdr.msg_iovlen = 1; + + mhdr.msg_control = &ancidata; + mhdr.msg_controllen = sizeof(ancidata); + + /* We will loop while all data is not received. */ +incomplete: + + /* the new data will be received following the preceding */ + memset(&iov, 0, sizeof(iov)); + iov.iov_base = ((unsigned char *)*data) + *len ; + iov.iov_len = sz - *len; + + ret = recvmsg(socket, &mhdr, 0); + if (ret <= 0) { + TRACE_DEBUG(INFO, "recvmsg return %d, errno: %s", ret, strerror(errno)); + return ret; + } + + /* we have received new data */ + *len += ret; + + /* loop if we have not received a full message yet */ + if ( (mhdr.msg_flags & MSG_EOR) == 0 ) { + /* if buffer is full */ + if (*len == sz) { + + sz *=2; + + /* enlarge the buffer to receive more data */ + *data = realloc(*data, sz); + if (*data == NULL) { + TRACE_DEBUG(INFO, "Malloc failed"); + return -1; + } + } + goto incomplete; + } + + /* Now we have a complete message in *data */ + + /* Now handle notifications */ + if (mhdr.msg_flags & MSG_NOTIFICATION) { + char *str, addrbuf[INET6_ADDRSTRLEN]; + union sctp_notification * notif = (union sctp_notification *) *data; + switch (notif->sn_header.sn_type) { + case SCTP_ASSOC_CHANGE: + TRACE_DEBUG(FULL, "Received SCTP_ASSOC_CHANGE notification"); + TRACE_DEBUG(FULL, " state : %hu", notif->sn_assoc_change.sac_state); + TRACE_DEBUG(FULL, " error : %hu", notif->sn_assoc_change.sac_error); + TRACE_DEBUG(FULL, " instr : %hu", notif->sn_assoc_change.sac_inbound_streams); + TRACE_DEBUG(FULL, " outstr : %hu", notif->sn_assoc_change.sac_outbound_streams); + + *event = RCV_CHANGE; + break; + + case SCTP_PEER_ADDR_CHANGE: + TRACE_DEBUG(FULL, "Received SCTP_PEER_ADDR_CHANGE notification"); + DUMP_SA( &(notif->sn_paddr_change.spc_aaddr), str ); + TRACE_DEBUG(FULL, " intf_change : %s", str); + TRACE_DEBUG(FULL, " state : %d", notif->sn_paddr_change.spc_state); + TRACE_DEBUG(FULL, " error : %d", notif->sn_paddr_change.spc_error); + + *event = RCV_CHANGE; + break; + + case SCTP_SEND_FAILED: + TRACE_DEBUG(FULL, "Received SCTP_SEND_FAILED notification"); + TRACE_DEBUG(FULL, " len : %hu", notif->sn_send_failed.ssf_length); + TRACE_DEBUG(FULL, " err : %d", notif->sn_send_failed.ssf_error); + + *event = RCV_ERROR; + break; + + case SCTP_REMOTE_ERROR: + TRACE_DEBUG(FULL, "Received SCTP_REMOTE_ERROR notification"); + TRACE_DEBUG(FULL, " err : %hu", ntohs(notif->sn_remote_error.sre_error)); + TRACE_DEBUG(FULL, " len : %hu", ntohs(notif->sn_remote_error.sre_length)); + + *event = RCV_ERROR; + break; + + case SCTP_SHUTDOWN_EVENT: + TRACE_DEBUG(FULL, "Received SCTP_SHUTDOWN_EVENT notification"); + + *event = RCV_SHUTDOWN; + break; + + default: + TRACE_DEBUG(FULL, "Received unknown notification %d", notif->sn_header.sn_type); + } + + /* Free the data buffer and return 0 */ + free(*data); + *data = NULL; + *len = 0; + return 0; + } + + /* Now handle the anciliary data */ + for (hdr = CMSG_FIRSTHDR(&mhdr); hdr; hdr = CMSG_NXTHDR(&mhdr, hdr)) { + + /* We deal only we anciliary data at SCTP level */ + if (hdr->cmsg_level != IPPROTO_SCTP) { + TRACE_DEBUG(FULL, "Received some anciliary data at level %d, skipped", hdr->cmsg_level); + continue; + } + + switch (hdr->cmsg_type) { + case SCTP_SNDRCV: + sndrcv = (struct sctp_sndrcvinfo *) CMSG_DATA(hdr); +#ifdef DEBUG_SCTP + TRACE_DEBUG(FULL, "Anciliary block IPPROTO_SCTP / SCTP_SNDRCV"); + TRACE_DEBUG(FULL, " sinfo_stream : %hu", sndrcv->sinfo_stream); + TRACE_DEBUG(FULL, " sinfo_ssn : %hu", sndrcv->sinfo_ssn); + TRACE_DEBUG(FULL, " sinfo_flags : %hu", sndrcv->sinfo_flags); + // TRACE_DEBUG(FULL, " sinfo_pr_policy : %hu", sndrcv->sinfo_pr_policy); + TRACE_DEBUG(FULL, " sinfo_ppid : %u" , sndrcv->sinfo_ppid); + TRACE_DEBUG(FULL, " sinfo_context : %u" , sndrcv->sinfo_context); + // TRACE_DEBUG(FULL, " sinfo_pr_value : %u" , sndrcv->sinfo_pr_value); + TRACE_DEBUG(FULL, " sinfo_tsn : %u" , sndrcv->sinfo_tsn); + TRACE_DEBUG(FULL, " sinfo_cumtsn : %u" , sndrcv->sinfo_cumtsn); +#endif /* DEBUG_SCTP */ + + *streamid = (int) sndrcv->sinfo_stream; + break; + + default: + TRACE_DEBUG(FULL, "Anciliary block IPPROTO_SCTP / %d, skipped", hdr->cmsg_type); + } + } + + /* Okay, we have received a normal message */ + TRACE_DEBUG(FULL, "Received SCTP packet, len: %d, stream %d", *len, *streamid); + + return *len; +} + +#endif /* SKIP_SCTP_WRAPPERS */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extensions/sec_tls_gnutls/gnutls_sctp_wrapper.h Fri Aug 15 17:53:04 2008 +0900 @@ -0,0 +1,319 @@ +/********************************************************************************************************* +* Software License Agreement (BSD License) * +* Author: Sebastien Decugis <sdecugis@nict.go.jp> * +* * +* Copyright (c) 2008, 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. * +*********************************************************************************************************/ + +/* + * Wrapper functions to use GNU TLS library to protect multi-stream SCTP association. + * + * + * This wrapper creates N+1 threads, where N is the number of streams in the association. + * + * To use this wrapper one must: + * - first, create a one-to-one SCTP association with another peer (draft-ietf-tsvwg-sctpsocket-17): + * -> Create the socket : int socket(PF_INET6, SOCK_STREAM, IPPROTO_SCTP); + * -> Set options (streams,...): setsockopt(SCTP_INITMSG, ...) + * Some options are mandatory for TLS, such as SCTP_DISABLE_FRAGMENTS=OFF + * Some others are useful, such as SCTP_EVENTS. + * -> (server) Bind the socket : int bind( sd, addr, addrlen); + * -> and/or : int sctp_bindx( sd, addrs, addrcnt, flags); + * -> (server) Listen for cnx : int listen( sd, backlog); + * -> (server) Accept clients : new_sd = accept( sd, *addr, *addrlen); + * -> (client) Connect to serv : int connect( sd, addr, addrlen); + * Note that the helper functions tls_sctp_create_server / tls_sctp_accept / tls_sctp_connect can be used for this. + * + * - Initialize the wrapper context for this association: + * int tls_sctpw_init ( new_sd, streams, &psctx ); + * + * - Handshake. One may need to change content to adapt to one's need for security (authentication of certificates, ...) + * int tls_sctpw_handshake( tls_sctpw_ctx * sctx ); + * + * - Send and receive data on the association, protected with TLS. + * int tls_sctpw_send( tls_sctpw_ctx * sctx, int stream, void * data, size_t len ); + * int tls_sctpw_recv( tls_sctpw_ctx * sctx, int *stream, void ** data, size_t *len ); + * + * - Terminate after use, either properly or on error. + * int tls_sctpw_close( tls_sctpw_ctx ** sctx ); + * int tls_sctpw_destroy( tls_sctpw_ctx ** sctx ); + * + * - Finally, close or abord the SCTP association + * int close(int sd); + * see SO_LINGER sockopt to abord... + */ + +#ifndef _SCTP_WRAPPER_H +#define _SCTP_WRAPPER_H + +#include <sys/socket.h> +#include <sys/types.h> + +/**************************************************************************** +* SCTP helper functions, to manage SCTP connections * +****************************************************************************/ +/* + * FUNCTION: tls_sctp_create_server + * + * PARAMETERS: + * sock : (out) On return, the identifier of the bound socket is stored here. + * port : (in) The port on which the server is listening for connections. + * streams : (in) The maximum number of pairs of streams that we will use. + * + * DESCRIPTION: + * Creates a listening SCTP bound socket, with some default options. + * + * RETURN VALUE: + * 0 : The socket has been created, bound, and is listening for connections. + * !0 : an error occurred (standard errno are returned, such as ENOTSUP) + */ +int tls_sctp_create_server( int * sock, uint16_t port, int streams ); + +/* + * FUNCTION: tls_sctp_accept + * + * PARAMETERS: + * sock : The listening server socket from which to accept clients. + * new_sd : on return, the new socket for this client. + * streams : the number of streams that can be used for TLS with this client. + * + * DESCRIPTION: + * Accepts an incoming connection and get the number of bi-lateral streams. + * + * RETURN VALUE: + * 0 : A new client has arrived. + * !0 : an error occurred (standard errno are returned) + */ +int tls_sctp_accept( int sock, int * new_sd, int *streams ); + +/* + * FUNCTION: tls_sctp_connect + * + * PARAMETERS: + * sock : The client socket is stored here on return. + * addr : address of the server, that is passed to connect() function. + * addrlen : length of addr structure. + * streams : (in/out) the number or streams desired / usable for TLS. + * + * DESCRIPTION: + * Connect to a server and retrieve the number of usable pairs of streams. + * + * RETURN VALUE: + * 0 : The client is connected. + * !0 : an error occurred (standard errno are returned) + */ +int tls_sctp_connect( int *sock, const struct sockaddr *addr, socklen_t addrlen, int *streams ); + +/* + * FUNCTION: tls_sctp_send + * + * PARAMETERS: + * socket : The socket on which to send a message. + * streamid: The stream on which the message must be sent. + * data : pointer to the data that must be sent. + * len : length of the data buffer. + * + * DESCRIPTION: + * Send some data over an SCTP association on a given stream. + * The return values and error codes are the same as sendmsg(). + * + * RETURN VALUE: + * -1 : error (errno is set) + * !-1 : number of bytes sent. + */ +ssize_t tls_sctp_send(int socket, int streamid, const void * data, size_t len); + +/* Some events that may be received as notifications in next function */ +typedef enum { + RCV_NO_EVENT = 0, /* no special event was received */ + RCV_CHANGE, /* Notified of a change on association or address */ + RCV_ERROR, /* Notified of an error in sending or remote handling */ + RCV_SHUTDOWN, /* Notified of a shutdown */ + RCV_MAX /* the last defined value */ +} tls_sctp_recvevents; + +/* + * FUNCTION: tls_sctp_recv + * + * PARAMETERS: + * socket : The socket on which to receive the next message. + * event : if an event is received, it is stored here. + * streamid: if relevant, the stream id on which data or event is received. + * data : a buffer continaing the received data. + * len : the size of the data buffer + * init : the initial size of the buffer + * + * DESCRIPTION: + * Receive data and notifications on a SCTP socket. + * The data buffer is allocated and must be freed after use. + * The return values and error codes are the same as recvmsg(). + * + * RETURN VALUE: + * -1 : an error occurred + * 0 : an event was received or shutdown is in progress. + * >0 : length of data written in the data buffer. + */ +ssize_t tls_sctp_recv(int socket, tls_sctp_recvevents * event, int * streamid, void ** data, size_t *len, size_t init); + +#endif /* _SCTP_WRAPPER_H */ + +#ifndef _GNUTLS_SCTP_WRAPPER_H +#define _GNUTLS_SCTP_WRAPPER_H + +#include <gnutls/gnutls.h> +#include <gcrypt.h> + + +/**************************************************************************** +* TLS over SCTP wrapper library -- to use GNU TLS over SCTP * +****************************************************************************/ +/* A wrapper context object, to be used in the functions of this file */ +typedef void tls_sctpw_ctx; + +/* + * FUNCTION: tls_sctpw_init + * + * PARAMETERS: + * sock : The socket object representing the (one-to-one style) SCTP association. + * streams : The number of bi-lateral streams in this association. + * sctx : On success, the wrapper context is stored here. + * client : if 0, this side is server, otherwise this side is client + * credtype: type of credentials, GNUTLS_CRD_ANON, GNUTLS_CRD_SRP or GNUTLS_CRD_CERTIFICATE + * creds : the credentials to present to the remote peer + * priocb : optional, a callback to set the priority of algorithms in the session + * prototype is: int priocb (gnutls_session_t * session); + * + * DESCRIPTION: + * Creates a wrapper context for one SCTP multi-stream association. + * + * RETURN VALUE: + * 0 : The context has been created sucessfully. + * !0 : an error occurred (standard errno are returned, such as ENOMEM) + */ +int tls_sctpw_init( int sock, int streams, tls_sctpw_ctx ** sctx, int client, gnutls_credentials_type_t credtype, void ** creds, int (*priocb)(gnutls_session_t *) ); + +/* + * FUNCTION: tls_sctpw_handshake + * + * PARAMETERS: + * sctx : The wrapper context of the connection where handshake must be started. + * checkcb : a callback to verify the credentials of the remote peer. + * parm : argument to pass to the checkcb function + * + * DESCRIPTION: + * Will handshake on all pairs of streams of the association. + * A full handshake is done on each pair. This can be improved by resuming sessions + * on other streams after the first handshake is achieved. + * The checkcb callback can be provided to verify the credentials of the remote peer. + * the prototype is: + * int checkcb ( gnutls_session_t session, void * parm) + * The function must return 0 if credentials are valid, or a standard errno value otherwise. + * + * RETURN VALUE: + * 0 : Ok. + * !0 : An error occurred (standard errno are returned) + */ +int tls_sctpw_handshake( tls_sctpw_ctx * sctx, int (*checkcb)(gnutls_session_t session, void * parm), void * parm ); + +/* + * FUNCTION: tls_sctpw_send + * + * PARAMETERS: + * sctx : The wrapper context of the connection. + * stream : The stream id on which the data must be sent. + * data : The data that must be sent. + * len : the size of data to send. + * + * DESCRIPTION: + * Will send data over the association, protected by TLS. + * If the stream is "-1", the next stream for the previously sent message is used. + * Otherwise, the stream must be between 0 and the number of streams of the association. + * Note that despite we are using SCTP, the message boundaries are not preserved with TLS. + * + * RETURN VALUE: + * 0 : Ok. + * !0 : An error occurred (standard errno are returned) + */ +int tls_sctpw_send( tls_sctpw_ctx * sctx, int stream, unsigned char * data, size_t len ); + +/* + * FUNCTION: tls_sctpw_recv + * + * PARAMETERS: + * sctx : The wrapper context of the connection. + * stream : on return, the stream id on which the data was received. + * data : on return, pointer to received data. This must be freed after use. + * len : on return, the length of the received data. + * + * DESCRIPTION: + * Receive data from any stream on an SCTP association. + * The buffer is dynamically allocated and must be freed after use. + * + * RETURN VALUE: + * 0 : Ok. + * !0 : An error occurred (standard errno are returned) + */ +int tls_sctpw_recv( tls_sctpw_ctx * sctx, int *stream, void ** data, size_t *len ); + +/* + * FUNCTION: tls_sctpw_close + * + * PARAMETERS: + * sctx : The wrapper context of the connection. + * + * DESCRIPTION: + * Terminates the TLS sessions properly, then free the wrapper resources. + * + * RETURN VALUE: + * 0 : Ok. + * !0 : An error occurred (standard errno are returned) + */ +int tls_sctpw_close( tls_sctpw_ctx ** sctx ); + +/* + * FUNCTION: tls_sctpw_destroy + * + * PARAMETERS: + * sctx : The wrapper context of the connection. + * + * DESCRIPTION: + * Terminates the TLS sessions abruptly, and free the wrapper resources. + * Use this when the connection is broken and no data can be exchanged anymore with remote peer. + * + * RETURN VALUE: + * 0 : Ok. + * !0 : An error occurred (standard errno are returned) + */ +int tls_sctpw_destroy( tls_sctpw_ctx ** sctx ); + + + +#endif /* _GNUTLS_SCTP_WRAPPER_H */
--- a/extensions/sec_tls_gnutls/sec_tls_gnutls.h Fri Aug 15 17:52:26 2008 +0900 +++ b/extensions/sec_tls_gnutls/sec_tls_gnutls.h Fri Aug 15 17:53:04 2008 +0900 @@ -45,6 +45,8 @@ #include <string.h> +#include "gnutls_sctp_wrapper.h" + /* The value of the Inband-Security-Id that this extension provides */ #define SEC_TLS_INBAND_SECURITY_ID 1