view libfdcore/sctp_dtls.c @ 1219:8802d3fa5876

Got successful handshake over DTLS with this version
author Sebastien Decugis <sdecugis@freediameter.net>
date Tue, 18 Jun 2013 15:51:11 +0800
parents 1e8267ad057c
children 5d0d300a7cd9
line wrap: on
line source

/*********************************************************************************************************
* Software License Agreement (BSD License)                                                               *
* Author: Sebastien Decugis <sdecugis@freediameter.net>							 *
*													 *
* Copyright (c) 2013, WIDE Project and NICT								 *
* All rights reserved.											 *
* 													 *
* Redistribution and use of this software in source and binary forms, with or without modification, are  *
* permitted provided that the following conditions are met:						 *
* 													 *
* * Redistributions of source code must retain the above 						 *
*   copyright notice, this list of conditions and the 							 *
*   following disclaimer.										 *
*    													 *
* * Redistributions in binary form must reproduce the above 						 *
*   copyright notice, this list of conditions and the 							 *
*   following disclaimer in the documentation and/or other						 *
*   materials provided with the distribution.								 *
* 													 *
* * Neither the name of the WIDE Project or NICT nor the 						 *
*   names of its contributors may be used to endorse or 						 *
*   promote products derived from this software without 						 *
*   specific prior written permission of WIDE Project and 						 *
*   NICT.												 *
* 													 *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
*********************************************************************************************************/

/* This file contains the code for DTLS over multi-stream SCTP implementation */

#include "fdcore-internal.h"
#include "cnxctx.h"


/* In DTLS over SCTP, all the DTLS internal messages (handshake, etc) must be sent over stream 0 so that we are sure they are received in order.
 Since we need to distinguish different DTLS payloads, we need some knowledge of DTLS protocol here. 
 We will then chose the stream within our "push" function called by GNUTLS.
 */
#define DTLS_TYPE_OFFSET		0	/* The TYPE byte is the first in a DTLS packet */
#define DTLS_TYPE_application_data	23	/* This is the value when the DTLS packet contains DATA (i.e. Diameter payload in our case) */
#define DTLS_SEQ_OFFSET			3	/* The SEQUENCE bytes come after type and proto version */


#define DTLS_SCTP_MTU	2^14 /* as per RFC 6083 */


/* The DTLS MTU is limited to 2^14, but Diameter messages can be larger. It means we MUST handle Diameter messages reassembly here; and this is not simple.

There are two ways to deal with this problem: 

- first solution is to force ordering when parsing all the datagrams received (as SCTP guarantees we will receive them), 
     so we are guaranteed to reconstruct the stream of data in the same order as it was sent, and we can process the received data the same way as TCP.
  * pros: very robust, does not depend on how the remote side is sending the data (assuming they do not interleave chunks of diameter messages, we'd have no solution otherwise)
  * cons: less efficient than the next solution, as on the receiving side we cannot parse new payloads until all the previous ones are received. 
          It defeats some of the benefits of the partial ordering of SCTP.
	  
- second solution is to make sure the fragmented payloads are sent over the same stream (which are always ordered) and rebuild the messages per stream. 
  * pros: enables to process complete messages received on other streams while waiting for some chunks (similar to non-DTLS situation, except that in that case SCTP handles the fragmentation)
  * cons: we must be sure the sending side is actually sending pieces of a message on the same stream. And the processing on receiving side is more complex.
  
We'd have actually more solutions, for example storing the message hop-by-hop id in the snd_ppid field of SCTP header, but this would work only in front of freeDiameter.

Here is an illustration of the two solutions: 
we assume 3 streams S1,S2,S3 and 4 messages, message M1 of 2^14 + 2^13 (=24576) bytes and 3 messages M2,M3,M4 of 2^12 (=4096) bytes to send from peer A to peer B.

Peer A calls fd_cnx_send() 4 times with the 4 messages M1,M2,M3,M4, which in turn calls gnutls_record_send(), which generates the chunks C1...C5 below:
	C1: gnutls_record_send(M1) -> returns 2^14 since the complete record exceed the MTU.
	C2: gnutls_record_send(M1+2^14) -> returns the remaining 2^13
	C3: gnutls_record_send(M2)
	C4: gnutls_record_send(M3)
	C5: gnutls_record_send(M4)

*** Solution 1)

Implementing the first solution above, the chunks are sent as follows (assuming round-robin sending over the streams): 
C1 over S1, 
C2 over S2, 
C3 over S3, 
C4 over S1, 
C5 over S2.

Given the size of the chunks, they might be delivered in the following order on the receiving side:
C3
C2
C5
C1
C4

This means we have to store C3, C2 and C5 until C1 is received, then we can process C1,C2,C3, 
and again wait for C4 before processing C4 and C5, while C3, C4 and C5 are totally independent 
and could be processed directly after being received.


*** Solution 2)

Here the partial ordering is enforced, so the sending side MUST send C1 and C2 over the same stream, e.g.:
C1 over S1, 
C2 over S1, 
C3 over S2, 
C4 over S3, 
C5 over S1.

On the receiving side, given the sizes of the message, we might receive the chunks in the following order:
C3
C4
C1
C2
C5

We can process C3 and C4 as soon as they are received, then C1 is stored (when decrypted we can see it is a partial chunk)
until the remaining payload is received; however we can continue to process the data received over other streams without delay.


*** What we do here.

freeDiameter implements the Solution 2 on the sending side (no additional cost), via fd_sctp_dtls_send() below.

On the receiving side, we implement Solution 1 at the moment (safe).  We do it at the lowest layer, before passing the data to GNUTLS. 
This way, we can catch all sequence numbers easily. 
Note however we have no way to handle cleanly the change of ephoch in case of cipher change (this is unclear in RFC6083 as well)

We'll see later if it makes sense to implement solution 2. 
How to decide if we can use it? one way could be to start doing solution 1, 
and when a large record is received check if the chunks were received on the same stream or not.

Implementation of solution 2 is difficult because we need to pass the stream information through GNU TLS and there is no easy way to do it.

*/

/* Retrieve the next data from the socket. Returns 0 if no payload data is available, >0 otherwise, and <0 in case of error */
static int get_next_data_from_socket(struct cnxctx * conn, uint16_t *strid, uint8_t ** buf, size_t *len)
{
	int got_data = 0;
	int event;
	CHECK_FCT_DO( fd_sctp_recvmeta(conn, strid, buf, len, &event), return -1 );
	switch (event) {
		case FDEVP_CNX_MSG_RECV:
			got_data = 1;
			LOG_A("Received DTLS data, type %hhd, Seq %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx, Stream %hu",
				(*buf)[0],
				(*buf)[3],(*buf)[4],(*buf)[5],(*buf)[6], (*buf)[7],(*buf)[8],(*buf)[9],(*buf)[10],
				*strid);
			break;

		case FDEVP_CNX_EP_CHANGE:
			/* Send this event to the target queue */
			CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), event, *len, *buf), return -1 );
			break;

		case FDEVP_CNX_SHUTDOWN:
			/* Just ignore the notification for now, we will get another error later anyway */
			break;

		case FDEVP_CNX_ERROR:
		default:
			return -1;
	}
	
	return got_data;
}

/* Count the number of records received in a chunk (including partial) and increment the nextseq field accordingly */
static void update_nextseq_from_records(struct cnxctx * conn, uint8_t * buf, size_t len)
{
	size_t offset = 0;
	uint16_t next_record_len;
	int i;
	
	
	while (offset + 13 <= len) {
		next_record_len = (buf[offset+11] << 8) + buf[offset+12];
		LOG_A("update_nextseq_from_records off:%zd Type %hhd, Ver:%02hhx.%02hhx, Seq:%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx",
			offset,
			buf[offset], buf[offset+1], buf[offset+2], 
			buf[offset+3],buf[offset+4],buf[offset+5],buf[offset+6],buf[offset+7],buf[offset+8],buf[offset+9],buf[offset+10]
			);
		
		if (memcmp(buf + offset + DTLS_SEQ_OFFSET, conn->cc_sctp_dtls_data.nextseq, 8) != 0) {
			/* The next record is not the one we expect in sequence. Is it a new epoch ? */
			uint8_t newepoch[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
			if (conn->cc_sctp_dtls_data.nextseq[1] != 0xFF) {
				newepoch[0] = conn->cc_sctp_dtls_data.nextseq[0]; newepoch[1] = conn->cc_sctp_dtls_data.nextseq[1] + 1;
			} else if (conn->cc_sctp_dtls_data.nextseq[0] != 0xFF) {
				newepoch[0] =conn->cc_sctp_dtls_data.nextseq[0] + 1; newepoch[1] = 0;
			} else {
				LOG_F("Epoch field wrapped, can this happen ???");
				ASSERT(0); TODO("FFS");
			}
			
			if (memcmp(buf + offset + DTLS_SEQ_OFFSET, newepoch, 8) == 0) {
				/* Yes, this is a new epoch record, store this as next seq and continue */
				memcpy(conn->cc_sctp_dtls_data.nextseq, newepoch, 8);
			} else {
				LOG_E("buf seq: %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", buf[offset +3], buf[offset +4], buf[offset +5], buf[offset +6], buf[offset +7], buf[offset +8], buf[offset +9], buf[offset +10]);
				LOG_E("nextseq: %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", conn->cc_sctp_dtls_data.nextseq[0], conn->cc_sctp_dtls_data.nextseq[1], conn->cc_sctp_dtls_data.nextseq[2], conn->cc_sctp_dtls_data.nextseq[3], conn->cc_sctp_dtls_data.nextseq[4], conn->cc_sctp_dtls_data.nextseq[5], conn->cc_sctp_dtls_data.nextseq[6], conn->cc_sctp_dtls_data.nextseq[7]);
				TODO("This should not be happening...");
				ASSERT(0);
			}
		}
		
		/* increment seq number */
		for (i = 7; i>=3; i--) {
			if (conn->cc_sctp_dtls_data.nextseq[i] == 0xFF) {
				conn->cc_sctp_dtls_data.nextseq[i] = 0;
			} else {
				conn->cc_sctp_dtls_data.nextseq[i] ++;
				break;
			}
		}
		if (i==2) {
			LOG_F("Sequence_number field wrapped, can this happen ???");
			ASSERT(0); TODO("FFS");
		}
		
		offset += (size_t)next_record_len + 13;
	}
}

/***************************************************************************************************/
/* Helper functions to reorder the received chunks by sequence number                              */
/***************************************************************************************************/

struct chunk {
	struct fd_list	chain;	/* link in the ordered list of chunks */
	uint8_t		seq[8];	/* epoch + sequence number */
	uint8_t *	buffer; /* the data */
	size_t		len;	/* length of the buffer */
	uint16_t	stream; /* which stream the chunk was received on */
	/* We could also add a timestamp here */
};

/* Inserts new buffer received from the connection in the list of chunks */
static int chunk_insert(struct cnxctx * conn, uint16_t streamid, uint8_t *buffer, size_t len)
{
	struct chunk * new;
	struct fd_list * li;
	uint8_t * newseq;
	
	/* Check the new sequence is >= what we processed in upper layer */
	newseq = buffer + DTLS_SEQ_OFFSET;
	if (memcmp(newseq, conn->cc_sctp_dtls_data.validseq, 8) < 0) {
		LOG_E("Received DTLS packet with smaller sequence number than already processed, discarded. FFS.");
		free(buffer);
		return 0;
	}
	
	/* Create a new chunk structure to store this chunk */
	CHECK_MALLOC( new = malloc(sizeof(struct chunk)) );
	memset(new, 0, sizeof(struct chunk));
	fd_list_init(&new->chain, new);
	memcpy(&new->seq, newseq, 8);
	new->buffer = buffer;
	new->len = len;
	new->stream = streamid;
	
	/* Insert this new structure in the list attached to the connection */
	for (li = conn->cc_sctp_dtls_data.chunks.prev; li != &conn->cc_sctp_dtls_data.chunks; li = li->prev) {
		int cmp = memcmp(new->seq, ((struct chunk *)li->o)->seq, 8);
		if (cmp < 0) continue;
		if (cmp == 0) {
			/* discard repeated seq */
			LOG_E("Received DTLS packet with duplicate sequence number, discarded. FFS.");
			free(buffer);
			free(new);
			return 0;
		}
		break;
	}
	/* special case: if we are already delivering partially the first chunk, we do insert only after this one */
	if (conn->cc_sctp_dtls_data.offset && (li == &conn->cc_sctp_dtls_data.chunks))
		li = li->next;
	
	fd_list_insert_after(li, &new->chain);
	
	return 0;
	
}

/* Retrieve data from the list of chunks. Returns 0 if no data is ready for upper layer, the available length otherwise (up to upperlen) */
static size_t chunk_retrieve(struct cnxctx * conn, void * upperbuf, size_t upperlen, int probeonly) 
{
	struct chunk * next;
	int cmp;
	size_t ret = 0;
redo:	
	if (FD_IS_LIST_EMPTY(&conn->cc_sctp_dtls_data.chunks)) {
		return 0;
	}
	
	next = conn->cc_sctp_dtls_data.chunks.next->o;
	
	/* If we are already delivering this chunk, just continue until complete */
	if (conn->cc_sctp_dtls_data.offset != 0) {
		if (probeonly)
			return 1;
		
		ret = next->len - conn->cc_sctp_dtls_data.offset;
		if (upperlen < ret)
			ret = upperlen;
		
		memcpy(upperbuf, next->buffer + conn->cc_sctp_dtls_data.offset, ret);
		conn->cc_sctp_dtls_data.offset += ret;
		if (conn->cc_sctp_dtls_data.offset == next->len) {
			/* we delivered the complete chunk, now we can remove it */
			conn->cc_sctp_dtls_data.offset = 0;
			free(next->buffer);
			fd_list_unlink(&next->chain);
			free(next);
		}
		return ret;
	}
	
	cmp = memcmp(next->seq, conn->cc_sctp_dtls_data.nextseq, 8);
	if (cmp < 0) {
		cmp = memcmp(next->seq, conn->cc_sctp_dtls_data.validseq, 8);
		if (cmp < 0) {
			/* This is old stuff or invalid stuff, discard */
			LOG_E("Unqueued DTLS packet with old sequence number, discarding.");
			free(next->buffer);
			fd_list_unlink(&next->chain);
			free(next);
			goto redo;
		}
		/* If the first chunk in our list has a smaller seq number than what we already delivered, we pass it above (to prevent possible DoS by sending forged sequence numbers) */
		if (probeonly)
			return 1;
		
		ret = next->len;
		if (upperlen < ret) {
			ret = upperlen;
			memcpy(upperbuf, next->buffer, ret);
			conn->cc_sctp_dtls_data.offset = ret;
		} else {
			memcpy(upperbuf, next->buffer, ret);
			free(next->buffer);
			fd_list_unlink(&next->chain);
			free(next);
		}
		LOG_A("Unqueueing (old) chunk with seq number %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", 
			next->seq[0],next->seq[1],next->seq[2],next->seq[3],next->seq[4],next->seq[5],next->seq[6],next->seq[7]);
		return ret;
	}
	if (cmp > 0) {
		/* is this the first message of a new epoch ? */
		uint8_t newepoch[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
		if (next->seq[1] != 0xFF) {
			newepoch[0] = next->seq[0]; newepoch[1] = next->seq[1] + 1;
		} else if (next->seq[0] != 0xFF) {
			newepoch[0] = next->seq[0] + 1; newepoch[1] = 0;
		} else {
			LOG_F("Epoch field wrapped, can this happen ???");
			ASSERT(0); TODO("FFS");
		}
		
		if (memcmp(newepoch, next->seq, 8) == 0) {
			/* Bingo, this is the first message of the new epoch. We update our nextseq accordingly */
			if (probeonly)
				return 1;
			memcpy(conn->cc_sctp_dtls_data.nextseq, newepoch, 8);
			update_nextseq_from_records(conn, next->buffer, next->len);
			
			LOG_A("Unqueueing chunk with seq number %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx (epoch change)", 
				next->seq[0],next->seq[1],next->seq[2],next->seq[3],next->seq[4],next->seq[5],next->seq[6],next->seq[7]);
			ret = next->len;
			if (upperlen < ret) {
				ret = upperlen;
				memcpy(upperbuf, next->buffer, ret);
				conn->cc_sctp_dtls_data.offset = ret;
			} else {
				memcpy(upperbuf, next->buffer, ret);
				free(next->buffer);
				fd_list_unlink(&next->chain);
				free(next);
			}
			return ret;
		}
			
		/* otherwise, we don't return this data */
		return 0;
	}
	
	/* next is the next chunk expected on this connection */
	if (probeonly)
		return 1;
	
	/* We increment the next seq by the number or records found in this chunk */
	update_nextseq_from_records(conn, next->buffer, next->len);
	
	/* And we deliver this to upper layer */
	LOG_A("Unqueueing chunk: Type %hhd, Ver:%02hhx.%02hhx, Seq:%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", 
		next->buffer[0], next->buffer[1], next->buffer[2], 
		next->seq[0],next->seq[1],next->seq[2],next->seq[3],next->seq[4],next->seq[5],next->seq[6],next->seq[7]);
	ret = next->len;
	if (upperlen < ret) {
		ret = upperlen;
		memcpy(upperbuf, next->buffer, ret);
		conn->cc_sctp_dtls_data.offset = ret;
	} else {
		memcpy(upperbuf, next->buffer, ret);
		free(next->buffer);
		fd_list_unlink(&next->chain);
		free(next);
	}
	return ret;
}

/* returns positive value if data is available for upper layer, 0 if the time is elapsed */
static int chunk_select(struct cnxctx * conn, unsigned int ms)
{
	fd_set rfds;
	struct timespec absend, inter;
	int ret;
	uint8_t * buf;
	size_t 	  len;
	uint16_t  strid;

	/* absolute time we will timeout */
	CHECK_SYS_DO(  clock_gettime(CLOCK_REALTIME, &absend), return -1 );
	absend.tv_sec += ((ms + (absend.tv_nsec / 1000000L)) / 1000);
	absend.tv_nsec = ( ms * 1000000L + absend.tv_nsec ) % 1000000000L;
	
	do {
		/* Check if we have available data in the list of chunks */
		if (chunk_retrieve(conn, NULL, 0, 1) > 0)
			return 1;
	
		/* otherwise we need to retrieve more data from the socket, so we select */

		FD_ZERO (&rfds);
		FD_SET (conn->cc_socket, &rfds);
		
		/* We wait until absend only */
		CHECK_SYS_DO(  clock_gettime(CLOCK_REALTIME, &inter), return -1 );
		if (inter.tv_nsec <= absend.tv_nsec) {
			if (inter.tv_sec > absend.tv_sec) {
				inter.tv_sec = 0; inter.tv_nsec = 0;
			} else {
				inter.tv_sec = absend.tv_sec - inter.tv_sec;
				inter.tv_nsec = absend.tv_nsec - inter.tv_nsec;
			}
		} else {
			if (inter.tv_sec >= absend.tv_sec) {
				inter.tv_sec = 0; inter.tv_nsec = 0;
			} else {
				inter.tv_sec = absend.tv_sec - inter.tv_sec - 1;
				inter.tv_nsec = 1000000000L - inter.tv_nsec + absend.tv_nsec;
			}
		}

		/* Now, wait for new data on the socket */
		ret = pselect (conn->cc_socket + 1, &rfds, NULL, NULL, &inter, NULL);
		if (ret <= 0)
			break; /* no data was received, we can return */
		
		/* We got data, get it and insert in the list of chunks  */
		ret = get_next_data_from_socket(conn, &strid, &buf, &len);
		if (ret < 0)
			break;
		if (ret == 0)
			continue;
		
		CHECK_FCT_DO( chunk_insert(conn, strid, buf, len), return -1 );
		/* and loop */
	} while (1);
	
	return ret;
}

/***************************************************************************************************/
/* Functions "under" GNU TLS                                                                       */
/***************************************************************************************************/

/* Send data over the connection, called by gnutls. This function checks the type of DTLS packet and sends
all non-application data over stream 0 (to enforce ordering) and application data over the stream set by
upper layer in conn->cc_sctp_para.next */
static ssize_t sctp_dtls_pushv(gnutls_transport_ptr_t tr, const giovec_t * iov, int iovcnt)
{
	struct cnxctx * conn = (struct cnxctx *)tr;
	uint16_t stream = 0;
	
	
	TRACE_ENTRY("%p %p %d", tr, iov, iovcnt);
	CHECK_PARAMS_DO( tr && iov, { errno = EINVAL; return -1; } );
	
	if ((conn->cc_sctp_para.unordered != 0) 
	&& (iovcnt > 0) 
	&& (iov->iov_len > 0) 
	&& (((uint8_t *)iov->iov_base)[DTLS_TYPE_OFFSET] == DTLS_TYPE_application_data)) {
		/* Data is sent over different streams, if allowed */
		stream = conn->cc_sctp_para.next;
	}
	
	if ((iovcnt > 0) && (iov->iov_len > 10)) {
		LOG_A("Sending DTLS data, type %hhd, Seq %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx, Stream %hu",
			((uint8_t *)iov->iov_base)[0],
			((uint8_t *)iov->iov_base)[3],((uint8_t *)iov->iov_base)[4],((uint8_t *)iov->iov_base)[5],((uint8_t *)iov->iov_base)[6],
			((uint8_t *)iov->iov_base)[7],((uint8_t *)iov->iov_base)[8],((uint8_t *)iov->iov_base)[9],((uint8_t *)iov->iov_base)[10],
			stream);	
	} else {
		LOG_A("Sending DTLS data, {iovcnt=%d, iov->iov_len=%zd}, Stream %hu",
			iovcnt, ((iovcnt>0) ? iov->iov_len : 0), stream);	
	}
	
	return fd_sctp_sendstrv(conn, stream, (const struct iovec *)iov, iovcnt);
}

#ifndef GNUTLS_VERSION_212
/* compatibility wrapper for older GNUTLS that does not support the vector_push */
static ssize_t sctp_dtls_push(gnutls_transport_ptr_t tr, const void * data, size_t len)
{
	giovec_t iov;
	iov.iov_base = (void *)data;
	iov.iov_len  = len;
	return sctp_dtls_pushv(tr, &iov, 1);
}
#endif /*  GNUTLS_VERSION_212 */

#ifdef GNUTLS_VERSION_300
/* Check if data is available for gnutls on a given connection.  */
static int sctp_dtls_pull_timeout(gnutls_transport_ptr_t tr, unsigned int ms)
{
	struct cnxctx * conn = (struct cnxctx *)tr;
	return chunk_select(conn, ms);
}
#endif /* GNUTLS_VERSION_300 */


/* This function returns only ordered data to the upper layer */
static ssize_t sctp_dtls_pull(gnutls_transport_ptr_t tr, void * gnutlsbuf, size_t gnutlslen)
{
	struct cnxctx * conn = (struct cnxctx *)tr;
	ssize_t ret = 0;
	
	while ( (ret = chunk_retrieve(conn,gnutlsbuf,gnutlslen,0)) == 0) {
		
		/* No partial data, read the next SCTP record */
		int       stop = 0;
		uint8_t * buf;
		size_t 	  len;
		uint16_t  strid;
		do {
			stop = get_next_data_from_socket(conn, &strid, &buf, &len);
			if (stop < 0)
				goto out;
		} while (!stop);
		
		CHECK_FCT_DO( chunk_insert(conn, strid, buf, len), goto out );
	}
	
out:
	return ret;

}


/***************************************************************************************************/
/* Functions "above" GNU TLS                                                                       */
/***************************************************************************************************/

/* Set the parameters of a session to use the cnxctx object */
#ifndef GNUTLS_VERSION_300
GCC_DIAG_OFF("-Wdeprecated-declarations")
#endif /* !GNUTLS_VERSION_300 */
int fd_sctp_dtls_settransport(gnutls_session_t session, struct cnxctx * conn)
{
	/* Set the transport pointer passed to push & pull callbacks */
	GNUTLS_TRACE( gnutls_transport_set_ptr( session, (gnutls_transport_ptr_t) conn ) );
	
	/* Reset the low water value, since we don't use sockets */
#ifndef GNUTLS_VERSION_300
	/* starting version 2.12, this call is not needed */
	GNUTLS_TRACE( gnutls_transport_set_lowat( session, 0 ) );
#else  /* GNUTLS_VERSION_300 */
	/* but in 3.0 we have to provide the pull_timeout callback */
	GNUTLS_TRACE( gnutls_transport_set_pull_timeout_function( session, sctp_dtls_pull_timeout ) );
#endif /* GNUTLS_VERSION_300 */
	
	/* Set the push and pull callbacks */
	GNUTLS_TRACE( gnutls_transport_set_pull_function(session, sctp_dtls_pull) );
#ifndef GNUTLS_VERSION_212
	GNUTLS_TRACE( gnutls_transport_set_push_function(session, sctp_dtls_push) );
#else /* GNUTLS_VERSION_212 */
	GNUTLS_TRACE( gnutls_transport_set_vec_push_function(session, sctp_dtls_pushv) );
#endif /* GNUTLS_VERSION_212 */

	return 0;
}
#ifndef GNUTLS_VERSION_300
GCC_DIAG_ON("-Wdeprecated-declarations")
#endif /* !GNUTLS_VERSION_300 */




/* Set additional session parameters before handshake. The GNUTLS_DATAGRAM is already set in fd_tls_prepare */
int fd_sctp_dtls_prepare(gnutls_session_t session)
{
	/* We do not use cookies at the moment. Not sure it is useful or not */
	/* TODO("Cookie exchange?"); */
	/* gnutls_dtls_prestate_set (session, &prestate); */

	gnutls_dtls_set_mtu(session, DTLS_SCTP_MTU);

	gnutls_dtls_set_timeouts(session, 70000, 60000); /* Set retrans > total so that there is no retransmission, since SCTP is reliable */

#ifdef GNUTLS_VERSION_320
	TODO("Disable replay protection");
	TODO("Register hook on the Finish message to change SCTP_AUTH active key on the socket");
#endif /* GNUTLS_VERSION_320 */
	
	return 0;
	
}

/* the following function is actually almost same as fd_tls_recv_handle_error at the moment, since all handling is done under gnutls */
ssize_t fd_dtls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz)
{
	ssize_t ret;
again:	
	CHECK_GNUTLS_DO( ret = gnutls_record_recv_seq(session, data, sz, conn->cc_sctp_dtls_data.validseq), 
		{
			switch (ret) {
				case GNUTLS_E_REHANDSHAKE: 
					if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING)) {
						CHECK_GNUTLS_DO( ret = gnutls_handshake(session),
							{
								if (TRACE_BOOL(INFO)) {
									fd_log_debug("TLS re-handshake failed on socket %d (%s) : %s", conn->cc_socket, conn->cc_id, gnutls_strerror(ret));
								}
								goto end;
							} );
					}

				case GNUTLS_E_AGAIN:
				case GNUTLS_E_INTERRUPTED:
					if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING))
						goto again;
					TRACE_DEBUG(FULL, "Connection is closing, so abord gnutls_record_recv now.");
					break;

				case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
					/* The connection is closed */
					TRACE_DEBUG(FULL, "Got 0 size while reading the socket, probably connection closed...");
					break;
				
				case GNUTLS_E_WARNING_ALERT_RECEIVED:
					LOG_N("Received TLS WARNING ALERT: %s", gnutls_alert_get_name(gnutls_alert_get(session)) ?: "<unknown alert>");
					if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING))
						goto again;
					TRACE_DEBUG(FULL, "Connection is closing, so abord gnutls_record_recv now.");
					break;
					
				case GNUTLS_E_FATAL_ALERT_RECEIVED:
					LOG_E("Received TLS FATAL ALERT: %s", gnutls_alert_get_name(gnutls_alert_get(session)) ?: "<unknown alert>");
					break;
					
				default:
					if (gnutls_error_is_fatal (ret) == 0) {
						LOG_N("Ignoring non-fatal GNU TLS error: %s", gnutls_strerror (ret));
						goto again;
					}
					LOG_E("Fatal GNUTLS error: %s", gnutls_strerror (ret));
			}
		} );
		
	if (ret == 0)
		CHECK_GNUTLS_DO( gnutls_bye(session, GNUTLS_SHUT_RDWR),  );
	
end:	
	if (ret <= 0)
		fd_cnx_markerror(conn);
	return ret;
}

/* Receiver thread that reassemble the decrypted messages (when size is > 2<<14) for upper layer. Very similar to fd_tls_rcvthr_core in this version */
void * fd_sctp_dtls_rcvthr(void * arg) {

	struct cnxctx * conn = arg;
	
	TRACE_ENTRY("%p", arg);
	CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), return NULL );
	
	/* Set the thread name */
	{
		char buf[48];
		snprintf(buf, sizeof(buf), "Receiver (%d) DTLS", conn->cc_socket);
		fd_log_threadname ( buf );
	}
	
	ASSERT( fd_cnx_teststate(conn, CC_STATUS_TLS) );
	ASSERT( fd_cnx_target_queue(conn) );
	
	/* The next function only returns when there is an error on the socket */	
	CHECK_FCT_DO(fd_tls_rcvthr_core(conn, conn->cc_tls_para.session, 1), /* continue */);

	TRACE_DEBUG(FULL, "Thread terminated");	
	return NULL;
}			

/* Send a new Diameter message over the association */
int fd_sctp_dtls_send(struct cnxctx * conn, unsigned char * buf, size_t len)
{
	ssize_t ret;
	size_t sent = 0;
	TRACE_ENTRY("%p %p %zd", conn, buf, len);
	
	CHECK_PARAMS(conn);
	
	/* First, decide which stream this data will be sent to */
	if (conn->cc_sctp_para.str_out > 32) {
		TODO("Limiting to 32 streams. Remove this limit when anti-replay is disabled");
		conn->cc_sctp_para.str_out = 32;
	}
	if (conn->cc_sctp_para.str_out > 1) {
		conn->cc_sctp_para.next += 1;
		conn->cc_sctp_para.next %= conn->cc_sctp_para.str_out;
	} else {
		conn->cc_sctp_para.next = 0;
	}
	
	/* Now send the data over this stream. Do it in a loop in case the length is larger than the MTU */
	do {
		CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_tls_para.session, buf + sent, len - sent),  );
		if (ret <= 0)
			return ENOTCONN;
		
		sent += ret;
	} while ( sent < len );
	return 0;
}
"Welcome to our mercurial repository"