gnutls-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

TLS over multi-stream SCTP, a wrapper...


From: Sebastien Decugis
Subject: TLS over multi-stream SCTP, a wrapper...
Date: Fri, 15 Aug 2008 17:45:44 +0900
User-agent: Thunderbird 2.0.0.12 (Windows/20080213)

Hello,

Following a design idea from Nikos Mavrogiannopoulos (thanks again), I have written a wrapper around the GNU TLS library to achieve TLS protection over a multi-stream SCTP connection.

The basic idea is to replace the transport functions used by gnutls for transport, and use an object to aggregate all the sessions and other data. One thread is receiving data from the socket, and queueing this data in per-stream FIFO lists (demultiplxing step). Then the gnutls "pull" function will pick data from the appropriate FIFO list, and actually decrypt this data. This is done by a separate thread (one per stream). The decrypted data is queued in another FIFO list, from which the user can retrieve the received data. See the header file and the comments at the top of the files for more information.

I know this wrapper is not very performant at session initiation because it does a full handshake on each pair of stream, and does not parallelize this process. This can be easily improved, but makes it more complex to debug.

I have compiled and tested this wrapper in my Linux environment ( libgnutls distribution release libgnutls13 2.0.4-1ubuntu2.1 ) and it seems to work properly. There will propably need some minor changes (gnutls_kx_set_priority -> gnutls_set_priority for example) to adapt it to the latest gnutls releases.

A test program is embedded withing the source code. To compile it, one has to define the STANDALONE_WRAPPER symbol. For example, to compile the file (under Linux Ubuntu) I use (content of the COMPIL file):
gcc -o sctp_tls -DSTANDALONE_WRAPPER -lgnutls -pthread gnutls_sctp_wrapper.c

Then simply run the ./sctp_tls program to create a SCTP socket on localhost:4433 and exchange TLS-protected data over it.

Please feel free to contact me if you have any question with regards to this peace of code.

As a final note, this wrapper was initially written as part of an open-source Diameter daemon implementation, called "waaad". See [1] if you are interested in this implementation, or want to find a more recent version of the wrapper source code.

I hope this code will be useful to other people as well. If you make improvements to this code and can share it under the same licence, please send me a note / patch.

Best regards,
Sebastien Decugis.

[1] http://aaa.koganei.wide.ad.jp/
/*********************************************************************************************************
* Software License Agreement (BSD License)                                      
                         *
* Author: Sebastien Decugis <address@hidden>                                    
                 *
*                                                                               
                         *
* 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 */
/*********************************************************************************************************
* Software License Agreement (BSD License)                                      
                         *
* Author: Sebastien Decugis <address@hidden>                                    
                 *
*                                                                               
                         *
* 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 _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 */



#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 */
gcc -o sctp_tls -DSTANDALONE_WRAPPER -lgnutls -pthread gnutls_sctp_wrapper.c

reply via email to

[Prev in Thread] Current Thread [Next in Thread]