view libfdcore/cnxctx.c @ 1327:82b386714795

Set callback data also when only setting expire callback (and not answer callback as well). It is used when calling the expire callback, so not setting it makes no sense.
author Thomas Klausner <tk@giga.or.at>
date Mon, 27 Nov 2017 15:21:20 +0100
parents 7800e6f1919f
children b68fb21c2f72
line wrap: on
line source

/*********************************************************************************************************
* Software License Agreement (BSD License)                                                               *
* Author: Sebastien Decugis <sdecugis@freediameter.net>							 *
*													 *
* Copyright (c) 2015, 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.								 *
*********************************************************************************************************/

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

#include <net/if.h>
#include <ifaddrs.h> /* for getifaddrs */
#include <sys/uio.h> /* writev */

/* The maximum size of Diameter message we accept to receive (<= 2^24) to avoid too big mallocs in case of trashed headers */
#ifndef DIAMETER_MSG_SIZE_MAX
#define DIAMETER_MSG_SIZE_MAX	65535	/* in bytes */
#endif /* DIAMETER_MSG_SIZE_MAX */


/* Connections contexts (cnxctx) in freeDiameter are wrappers around the sockets and TLS operations .
 * They are used to hide the details of the processing to the higher layers of the daemon.
 * They are always oriented on connections (TCP or SCTP), connectionless modes (UDP or SCTP) are not supported.
 */

/* Lifetime of a cnxctx object:
 * 1) Creation
 *    a) a server socket:
 *       - create the object with fd_cnx_serv_tcp or fd_cnx_serv_sctp
 *       - start listening incoming connections: fd_cnx_serv_listen
 *       - accept new clients with fd_cnx_serv_accept.
 *    b) a client socket:
 *       - connect to a remote server with fd_cnx_cli_connect
 *
 * 2) Initialization
 *    - if TLS is started first, call fd_cnx_handshake
 *    - otherwise to receive clear messages, call fd_cnx_start_clear. fd_cnx_handshake can be called later.
 *
 * 3) Usage
 *    - fd_cnx_receive, fd_cnx_send : exchange messages on this connection (send is synchronous, receive is not, but blocking).
 *    - fd_cnx_recv_setaltfifo : when a message is received, the event is sent to an external fifo list. fd_cnx_receive does not work when the alt_fifo is set.
 *    - fd_cnx_getid : retrieve a descriptive string for the connection (for debug)
 *    - fd_cnx_getremoteid : identification of the remote peer (IP address or fqdn)
 *    - fd_cnx_getcred : get the remote peer TLS credentials, after handshake
 *
 * 4) End
 *    - fd_cnx_destroy
 */

/*******************************************/
/*     Creation of a connection object     */
/*******************************************/

/* Initialize a context structure */
static struct cnxctx * fd_cnx_init(int full)
{
	struct cnxctx * conn = NULL;

	TRACE_ENTRY("%d", full);

	CHECK_MALLOC_DO( conn = malloc(sizeof(struct cnxctx)), return NULL );
	memset(conn, 0, sizeof(struct cnxctx));

	if (full) {
		CHECK_FCT_DO( fd_fifo_new ( &conn->cc_incoming, 5 ), return NULL );
	}

	return conn;
}

#define CC_ID_HDR "{----} "

/* Create and bind a server socket to the given endpoint and port */
struct cnxctx * fd_cnx_serv_tcp(uint16_t port, int family, struct fd_endpoint * ep)
{
	struct cnxctx * cnx = NULL;
	sSS dummy;
	sSA * sa = (sSA *) &dummy;

	TRACE_ENTRY("%hu %d %p", port, family, ep);

	CHECK_PARAMS_DO( port, return NULL );
	CHECK_PARAMS_DO( ep || family, return NULL );
	CHECK_PARAMS_DO( (! family) || (family == AF_INET) || (family == AF_INET6), return NULL );
	CHECK_PARAMS_DO( (! ep) || (ep->ss.ss_family == AF_INET) || (ep->ss.ss_family == AF_INET6), return NULL );
	CHECK_PARAMS_DO( (! ep) || (!family) || (ep->ss.ss_family == family), return NULL );

	/* The connection object */
	CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL );

	/* Prepare the socket address information */
	if (ep) {
		memcpy(sa, &ep->ss, sizeof(sSS));
	} else {
		memset(&dummy, 0, sizeof(dummy));
		sa->sa_family = family;
	}
	if (sa->sa_family == AF_INET) {
		((sSA4 *)sa)->sin_port = htons(port);
		cnx->cc_family = AF_INET;
	} else {
		((sSA6 *)sa)->sin6_port = htons(port);
		cnx->cc_family = AF_INET6;
	}

	/* Create the socket */
	CHECK_FCT_DO( fd_tcp_create_bind_server( &cnx->cc_socket, sa, sSAlen(sa) ), goto error );

	/* Generate the name for the connection object */
	{
		char addrbuf[INET6_ADDRSTRLEN];
		int  rc;
		rc = getnameinfo(sa, sSAlen(sa), addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
		if (rc)
			snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc));
		snprintf(cnx->cc_id, sizeof(cnx->cc_id), CC_ID_HDR "TCP srv [%s]:%hu (%d)", addrbuf, port, cnx->cc_socket);
	}

	cnx->cc_proto = IPPROTO_TCP;

	return cnx;

error:
	fd_cnx_destroy(cnx);
	return NULL;
}

/* Same function for SCTP, with a list of local endpoints to bind to */
struct cnxctx * fd_cnx_serv_sctp(uint16_t port, struct fd_list * ep_list)
{
#ifdef DISABLE_SCTP
	TRACE_DEBUG(INFO, "This function should never been called when SCTP is disabled...");
	ASSERT(0);
	CHECK_FCT_DO( ENOTSUP, );
	return NULL;
#else /* DISABLE_SCTP */
	struct cnxctx * cnx = NULL;

	TRACE_ENTRY("%hu %p", port, ep_list);

	CHECK_PARAMS_DO( port, return NULL );

	/* The connection object */
	CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL );

	if (fd_g_config->cnf_flags.no_ip6) {
		cnx->cc_family = AF_INET;
	} else {
		cnx->cc_family = AF_INET6; /* can create socket for both IP and IPv6 */
	}
	
	/* Create the socket */
	CHECK_FCT_DO( fd_sctp_create_bind_server( &cnx->cc_socket, cnx->cc_family, ep_list, port ), goto error );

	/* Generate the name for the connection object */
	snprintf(cnx->cc_id, sizeof(cnx->cc_id), CC_ID_HDR "SCTP srv :%hu (%d)", port, cnx->cc_socket);

	cnx->cc_proto = IPPROTO_SCTP;

	return cnx;

error:
	fd_cnx_destroy(cnx);
	return NULL;
#endif /* DISABLE_SCTP */
}

/* Allow clients to connect on the server socket */
int fd_cnx_serv_listen(struct cnxctx * conn)
{
	CHECK_PARAMS( conn );

	switch (conn->cc_proto) {
		case IPPROTO_TCP:
			CHECK_FCT(fd_tcp_listen(conn->cc_socket));
			break;

#ifndef DISABLE_SCTP
		case IPPROTO_SCTP:
			CHECK_FCT(fd_sctp_listen(conn->cc_socket));
			break;
#endif /* DISABLE_SCTP */

		default:
			CHECK_PARAMS(0);
	}

	return 0;
}

/* Accept a client (blocking until a new client connects) -- cancelable */
struct cnxctx * fd_cnx_serv_accept(struct cnxctx * serv)
{
	struct cnxctx * cli = NULL;
	sSS ss;
	socklen_t ss_len = sizeof(ss);
	int cli_sock = 0;

	TRACE_ENTRY("%p", serv);
	CHECK_PARAMS_DO(serv, return NULL);
	
	/* Accept the new connection -- this is blocking until new client enters or until cancellation */
	CHECK_SYS_DO( cli_sock = accept(serv->cc_socket, (sSA *)&ss, &ss_len), return NULL );
	
	CHECK_MALLOC_DO( cli = fd_cnx_init(1), { shutdown(cli_sock, SHUT_RDWR); close(cli_sock); return NULL; } );
	cli->cc_socket = cli_sock;
	cli->cc_family = serv->cc_family;
	cli->cc_proto = serv->cc_proto;
	
	/* Set the timeout */
	fd_cnx_s_setto(cli->cc_socket);
	
	/* Generate the name for the connection object */
	{
		char addrbuf[INET6_ADDRSTRLEN];
		char portbuf[10];
		int  rc;
		
		rc = getnameinfo((sSA *)&ss, ss_len, addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), NI_NUMERICHOST | NI_NUMERICSERV);
		if (rc) {
			snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc));
			portbuf[0] = '\0';
		}
		
		/* Numeric values for debug... */
		snprintf(cli->cc_id, sizeof(cli->cc_id), CC_ID_HDR "%s from [%s]:%s (%d<-%d)", 
				IPPROTO_NAME(cli->cc_proto), addrbuf, portbuf, serv->cc_socket, cli->cc_socket);
		
		
		/* ...Name for log messages */
		rc = getnameinfo((sSA *)&ss, ss_len, cli->cc_remid, sizeof(cli->cc_remid), NULL, 0, 0);
		if (rc)
			snprintf(cli->cc_remid, sizeof(cli->cc_remid), "[err:%s]", gai_strerror(rc));
	}
	
	LOG_D("Incoming connection: '%s' <- '%s'   {%s}", fd_cnx_getid(serv), cli->cc_remid, cli->cc_id);

#ifndef DISABLE_SCTP
	/* SCTP-specific handlings */
	if (cli->cc_proto == IPPROTO_SCTP) {
		/* Retrieve the number of streams */
		CHECK_FCT_DO( fd_sctp_get_str_info( cli->cc_socket, &cli->cc_sctp_para.str_in, &cli->cc_sctp_para.str_out, NULL ), {fd_cnx_destroy(cli); return NULL;} );
		if (cli->cc_sctp_para.str_out < cli->cc_sctp_para.str_in)
			cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_out;
		else
			cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_in;
		
		LOG_A( "%s : client '%s' (SCTP:%d, %d/%d streams)", fd_cnx_getid(serv), fd_cnx_getid(cli), cli->cc_socket, cli->cc_sctp_para.str_in, cli->cc_sctp_para.str_out);
	}
#endif /* DISABLE_SCTP */

	return cli;
}

/* Client side: connect to a remote server -- cancelable */
struct cnxctx * fd_cnx_cli_connect_tcp(sSA * sa /* contains the port already */, socklen_t addrlen)
{
	int sock = 0;
	struct cnxctx * cnx = NULL;
	char sa_buf[sSA_DUMP_STRLEN];
	
	TRACE_ENTRY("%p %d", sa, addrlen);
	CHECK_PARAMS_DO( sa && addrlen, return NULL );
	
	fd_sa_sdump_numeric(sa_buf, sa);
	
	LOG_D("Connecting to TCP %s...", sa_buf);
	
	/* Create the socket and connect, which can take some time and/or fail */
	{
		int ret = fd_tcp_client( &sock, sa, addrlen );
		if (ret != 0) {
			LOG_D("TCP connection to %s failed: %s", sa_buf, strerror(ret));
			return NULL;
		}
	}
	
	/* Once the socket is created successfuly, prepare the remaining of the cnx */
	CHECK_MALLOC_DO( cnx = fd_cnx_init(1), { shutdown(sock, SHUT_RDWR); close(sock); return NULL; } );
	
	cnx->cc_socket = sock;
	cnx->cc_family = sa->sa_family;
	cnx->cc_proto  = IPPROTO_TCP;
	
	/* Set the timeout */
	fd_cnx_s_setto(cnx->cc_socket);
	
	/* Generate the names for the object */
	{
		int  rc;
		
		snprintf(cnx->cc_id, sizeof(cnx->cc_id), CC_ID_HDR "TCP,#%d->%s", cnx->cc_socket, sa_buf);
		
		/* ...Name for log messages */
		rc = getnameinfo(sa, addrlen, cnx->cc_remid, sizeof(cnx->cc_remid), NULL, 0, 0);
		if (rc)
			snprintf(cnx->cc_remid, sizeof(cnx->cc_remid), "[err:%s]", gai_strerror(rc));
	}
	
	LOG_A("TCP connection to %s succeed (socket:%d).", sa_buf, sock);
	
	return cnx;
}

/* Same for SCTP, accepts a list of remote addresses to connect to (see sctp_connectx for how they are used) */
struct cnxctx * fd_cnx_cli_connect_sctp(int no_ip6, uint16_t port, struct fd_list * list)
{
#ifdef DISABLE_SCTP
	TRACE_DEBUG(INFO, "This function should never be called when SCTP is disabled...");
	ASSERT(0);
	CHECK_FCT_DO( ENOTSUP, );
	return NULL;
#else /* DISABLE_SCTP */
	int sock = 0;
	struct cnxctx * cnx = NULL;
	char sa_buf[sSA_DUMP_STRLEN];
	sSS primary;
	
	TRACE_ENTRY("%p", list);
	CHECK_PARAMS_DO( list && !FD_IS_LIST_EMPTY(list), return NULL );
	
	fd_sa_sdump_numeric(sa_buf, &((struct fd_endpoint *)(list->next))->sa);
	
	LOG_D("Connecting to SCTP %s:%hu...", sa_buf, port);
	
	{
		int ret = fd_sctp_client( &sock, no_ip6, port, list );
		if (ret != 0) {
			LOG_D("SCTP connection to [%s,...] failed: %s", sa_buf, strerror(ret));
			return NULL;
		}
	}
	
	/* Once the socket is created successfuly, prepare the remaining of the cnx */
	CHECK_MALLOC_DO( cnx = fd_cnx_init(1), { shutdown(sock, SHUT_RDWR); close(sock); return NULL; } );
	
	cnx->cc_socket = sock;
	cnx->cc_family = no_ip6 ? AF_INET : AF_INET6;
	cnx->cc_proto  = IPPROTO_SCTP;
	
	/* Set the timeout */
	fd_cnx_s_setto(cnx->cc_socket);
	
	/* Retrieve the number of streams and primary address */
	CHECK_FCT_DO( fd_sctp_get_str_info( sock, &cnx->cc_sctp_para.str_in, &cnx->cc_sctp_para.str_out, &primary ), goto error );
	if (cnx->cc_sctp_para.str_out < cnx->cc_sctp_para.str_in)
		cnx->cc_sctp_para.pairs = cnx->cc_sctp_para.str_out;
	else
		cnx->cc_sctp_para.pairs = cnx->cc_sctp_para.str_in;
	
	fd_sa_sdump_numeric(sa_buf, (sSA *)&primary);
	
	/* Generate the names for the object */
	{
		int  rc;
		
		snprintf(cnx->cc_id, sizeof(cnx->cc_id), CC_ID_HDR "SCTP,#%d->%s", cnx->cc_socket, sa_buf);
		
		/* ...Name for log messages */
		rc = getnameinfo((sSA *)&primary, sSAlen(&primary), cnx->cc_remid, sizeof(cnx->cc_remid), NULL, 0, 0);
		if (rc)
			snprintf(cnx->cc_remid, sizeof(cnx->cc_remid), "[err:%s]", gai_strerror(rc));
	}
	
	LOG_A("SCTP connection to %s succeed (socket:%d, %d/%d streams).", sa_buf, sock, cnx->cc_sctp_para.str_in, cnx->cc_sctp_para.str_out);
	
	return cnx;

error:
	fd_cnx_destroy(cnx);
	return NULL;
#endif /* DISABLE_SCTP */
}

/* Return a string describing the connection, for debug */
char * fd_cnx_getid(struct cnxctx * conn)
{
	CHECK_PARAMS_DO( conn, return "" );
	return conn->cc_id;
}

/* Return the protocol of a connection */
int fd_cnx_getproto(struct cnxctx * conn)
{
	CHECK_PARAMS_DO( conn, return 0 );
	return conn->cc_proto;
}

/* Set the hostname to check during handshake */
void fd_cnx_sethostname(struct cnxctx * conn, DiamId_t hn)
{
	CHECK_PARAMS_DO( conn, return );
	conn->cc_tls_para.cn = hn;
}

/* We share a lock with many threads but we hold it only very short time so it is OK */
static pthread_mutex_t state_lock = PTHREAD_MUTEX_INITIALIZER;
uint32_t fd_cnx_getstate(struct cnxctx * conn)
{
	uint32_t st;
	CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
	st = conn->cc_state;
	CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
	return st;
}
int  fd_cnx_teststate(struct cnxctx * conn, uint32_t flag)
{
	uint32_t st;
	CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
	st = conn->cc_state;
	CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
	return st & flag;
}
void fd_cnx_update_id(struct cnxctx * conn) {
	if (conn->cc_state & CC_STATUS_CLOSING)
		conn->cc_id[1] = 'C';
	else
		conn->cc_id[1] = '-';
	
	if (conn->cc_state & CC_STATUS_ERROR)
		conn->cc_id[2] = 'E';
	else
		conn->cc_id[2] = '-';
	
	if (conn->cc_state & CC_STATUS_SIGNALED)
		conn->cc_id[3] = 'S';
	else
		conn->cc_id[3] = '-';
	
	if (conn->cc_state & CC_STATUS_TLS)
		conn->cc_id[4] = 'T';
	else
		conn->cc_id[4] = '-';
}
void fd_cnx_addstate(struct cnxctx * conn, uint32_t orstate)
{
	CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
	conn->cc_state |= orstate;
	fd_cnx_update_id(conn);
	CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
}
void fd_cnx_setstate(struct cnxctx * conn, uint32_t abstate)
{
	CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
	conn->cc_state = abstate;
	fd_cnx_update_id(conn);
	CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
}


/* Return the TLS state of a connection */
int fd_cnx_getTLS(struct cnxctx * conn)
{
	CHECK_PARAMS_DO( conn, return 0 );
	return fd_cnx_teststate(conn, CC_STATUS_TLS);
}

/* Mark the connection to tell if OOO delivery is permitted (only for SCTP) */
int fd_cnx_unordered_delivery(struct cnxctx * conn, int is_allowed)
{
	CHECK_PARAMS( conn );
	conn->cc_sctp_para.unordered = is_allowed;
	return 0;
}

/* Return true if the connection supports unordered delivery of messages */
int fd_cnx_is_unordered_delivery_supported(struct cnxctx * conn)
{
	CHECK_PARAMS_DO( conn, return 0 );
	#ifndef DISABLE_SCTP
	if (conn->cc_proto == IPPROTO_SCTP)
		return (conn->cc_sctp_para.str_out > 1);
	#endif /* DISABLE_SCTP */
	return 0;
}


/* Get the list of endpoints (IP addresses) of the local and remote peers on this connection */
int fd_cnx_getremoteeps(struct cnxctx * conn, struct fd_list * eps)
{
	TRACE_ENTRY("%p %p", conn, eps);
	CHECK_PARAMS(conn && eps);
	
	/* Check we have a full connection object, not a listening socket (with no remote) */
	CHECK_PARAMS( conn->cc_incoming );

	/* Retrieve the peer endpoint(s) of the connection */
	switch (conn->cc_proto) {
		case IPPROTO_TCP: {
			sSS ss;
			socklen_t sl;
			CHECK_FCT(fd_tcp_get_remote_ep(conn->cc_socket, &ss, &sl));
			CHECK_FCT(fd_ep_add_merge( eps, (sSA *)&ss, sl, EP_FL_LL | EP_FL_PRIMARY ));
		}
		break;

		#ifndef DISABLE_SCTP
		case IPPROTO_SCTP: {
			CHECK_FCT(fd_sctp_get_remote_ep(conn->cc_socket, eps));
		}
		break;
		#endif /* DISABLE_SCTP */

		default:
			CHECK_PARAMS(0);
	}

	return 0;
}

/* Get a string describing the remote peer address (ip address or fqdn) */
char * fd_cnx_getremoteid(struct cnxctx * conn)
{
	CHECK_PARAMS_DO( conn, return "" );
	return conn->cc_remid;
}

static int fd_cnx_may_dtls(struct cnxctx * conn);

/* Get a short string representing the connection */
int fd_cnx_proto_info(struct cnxctx * conn, char * buf, size_t len) 
{
	CHECK_PARAMS( conn );
	
	if (fd_cnx_teststate(conn, CC_STATUS_TLS)) {
		snprintf(buf, len, "%s,%s,soc#%d", IPPROTO_NAME(conn->cc_proto), fd_cnx_may_dtls(conn) ? "DTLS" : "TLS", conn->cc_socket);
	} else {
		snprintf(buf, len, "%s,soc#%d", IPPROTO_NAME(conn->cc_proto), conn->cc_socket);
	}
	
	return 0;
}

/* Retrieve a list of all IP addresses of the local system from the kernel, using getifaddrs */
int fd_cnx_get_local_eps(struct fd_list * list)
{
	struct ifaddrs *iflist, *cur;
	
	CHECK_SYS(getifaddrs(&iflist));
	
	for (cur = iflist; cur != NULL; cur = cur->ifa_next) {
		if (cur->ifa_flags & IFF_LOOPBACK)
			continue;
		
		if (cur->ifa_addr == NULL) /* may happen with ppp interfaces */
			continue;
		
		if (fd_g_config->cnf_flags.no_ip4 && (cur->ifa_addr->sa_family == AF_INET))
			continue;
		
		if (fd_g_config->cnf_flags.no_ip6 && (cur->ifa_addr->sa_family == AF_INET6))
			continue;
		
		CHECK_FCT(fd_ep_add_merge( list, cur->ifa_addr, sSAlen(cur->ifa_addr), EP_FL_LL ));
	}
	
	freeifaddrs(iflist);
	
	return 0;
}


/**************************************/
/*     Use of a connection object     */
/**************************************/

/* An error occurred on the socket */
void fd_cnx_markerror(struct cnxctx * conn)
{
	TRACE_ENTRY("%p", conn);
	CHECK_PARAMS_DO( conn, goto fatal );
	
	TRACE_DEBUG(FULL, "Error flag set for socket %d (%s, %s)", conn->cc_socket, conn->cc_id, conn->cc_remid);

	/* Mark the error */
	fd_cnx_addstate(conn, CC_STATUS_ERROR);
	
	/* Report the error if not reported yet, and not closing */
	if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING | CC_STATUS_SIGNALED ))  {
		TRACE_DEBUG(FULL, "Sending FDEVP_CNX_ERROR event");
		CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), FDEVP_CNX_ERROR, 0, NULL), goto fatal);
		fd_cnx_addstate(conn, CC_STATUS_SIGNALED);
	}
	
	return;
fatal:
	/* An unrecoverable error occurred, stop the daemon */
	ASSERT(0);
	CHECK_FCT_DO(fd_core_shutdown(), );	
}

/* Set the timeout option on the socket */
void fd_cnx_s_setto(int sock) 
{
	struct timeval tv;
	
	/* Set a timeout on the socket so that in any case we are not stuck waiting for something */
	memset(&tv, 0, sizeof(tv));
	tv.tv_usec = 100000L;	/* 100ms, to react quickly to head-of-the-line blocking. */
	CHECK_SYS_DO( setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),  );
	CHECK_SYS_DO( setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),  );
}


#ifdef GNUTLS_VERSION_300
/* The pull_timeout function for gnutls */
static int fd_cnx_s_select (struct cnxctx * conn, unsigned int ms)
{
	fd_set rfds;
	struct timeval tv;
	
	FD_ZERO (&rfds);
	FD_SET (conn->cc_socket, &rfds);
	
	tv.tv_sec = ms / 1000;
	tv.tv_usec = (ms * 1000) % 1000000;
	
	return select (conn->cc_socket + 1, &rfds, NULL, NULL, &tv);
}		
#endif /* GNUTLS_VERSION_300 */

/* A recv-like function, taking a cnxctx object instead of socket as entry. We use it to quickly react to timeouts without traversing GNUTLS wrapper each time */
ssize_t fd_cnx_s_recv(struct cnxctx * conn, void *buffer, size_t length)
{
	ssize_t ret = 0;
	int timedout = 0;
again:
	ret = recv(conn->cc_socket, buffer, length, 0);
	/* Handle special case of timeout / interrupts */
	if ((ret < 0) && ((errno == EAGAIN) || (errno == EINTR))) {
		pthread_testcancel();
		if (! fd_cnx_teststate(conn, CC_STATUS_CLOSING ))
			goto again; /* don't care, just ignore */
		if (!timedout) {
			timedout ++; /* allow for one timeout while closing */
			goto again;
		}
	}
	
	/* Mark the error */
	if (ret <= 0) {
		CHECK_SYS_DO(ret, /* continue, this is only used to log the error here */);
		fd_cnx_markerror(conn);
	}
	
	return ret;
}

/* Send */
static ssize_t fd_cnx_s_sendv(struct cnxctx * conn, const struct iovec * iov, int iovcnt)
{
	ssize_t ret = 0;
	struct timespec ts, now;
	CHECK_SYS_DO(  clock_gettime(CLOCK_REALTIME, &ts), return -1 );
again:
	ret = writev(conn->cc_socket, iov, iovcnt);
	/* Handle special case of timeout */
	if ((ret < 0) && ((errno == EAGAIN) || (errno == EINTR))) {
		ret = -errno;
		pthread_testcancel();
		
		/* Check how much time we were blocked for this sending. */
		CHECK_SYS_DO(  clock_gettime(CLOCK_REALTIME, &now), return -1 );
		if ( ((now.tv_sec - ts.tv_sec) * 1000 + ((now.tv_nsec - ts.tv_nsec) / 1000000L)) > MAX_HOTL_BLOCKING_TIME) {
			LOG_D("Unable to send any data for %dms, closing the connection", MAX_HOTL_BLOCKING_TIME);
		} else if (! fd_cnx_teststate(conn, CC_STATUS_CLOSING )) {
			goto again; /* don't care, just ignore */
		}
		
		/* propagate the error */
		errno = -ret;
		ret = -1;
		CHECK_SYS_DO(ret, /* continue */);
	}
	
	/* Mark the error */
	if (ret <= 0)
		fd_cnx_markerror(conn);
	
	return ret;
}

/* Send, for older GNUTLS */
#ifndef GNUTLS_VERSION_212
static ssize_t fd_cnx_s_send(struct cnxctx * conn, const void *buffer, size_t length)
{
	struct iovec iov;
	iov.iov_base = (void *)buffer;
	iov.iov_len  = length;
	return fd_cnx_s_sendv(conn, &iov, 1);
}
#endif /* GNUTLS_VERSION_212 */

#define ALIGNOF(t) ((char *)(&((struct { char c; t _h; } *)0)->_h) - (char *)0)  /* Could use __alignof__(t) on some systems but this is more portable probably */
#define PMDL_PADDED(len) ( ((len) + ALIGNOF(struct fd_msg_pmdl) - 1) & ~(ALIGNOF(struct fd_msg_pmdl) - 1) )

size_t fd_msg_pmdl_sizewithoverhead(size_t datalen)
{
	return PMDL_PADDED(datalen) + sizeof(struct fd_msg_pmdl);
}

struct fd_msg_pmdl * fd_msg_pmdl_get_inbuf(uint8_t * buf, size_t datalen)
{
	return (struct fd_msg_pmdl *)(buf + PMDL_PADDED(datalen));
} 

static int fd_cnx_init_msg_buffer(uint8_t * buffer, size_t expected_len, struct fd_msg_pmdl ** pmdl)
{
	*pmdl = fd_msg_pmdl_get_inbuf(buffer, expected_len);
	fd_list_init(&(*pmdl)->sentinel, NULL);
	CHECK_POSIX(pthread_mutex_init(&(*pmdl)->lock, NULL) );
	return 0;
}

static uint8_t * fd_cnx_alloc_msg_buffer(size_t expected_len, struct fd_msg_pmdl ** pmdl)
{
	uint8_t * ret = NULL;
	
	CHECK_MALLOC_DO(  ret = malloc( fd_msg_pmdl_sizewithoverhead(expected_len) ), return NULL );
	CHECK_FCT_DO( fd_cnx_init_msg_buffer(ret, expected_len, pmdl), {free(ret); return NULL;} );
	return ret;
}

#ifndef DISABLE_SCTP /* WE use this function only in SCTP code */
static uint8_t * fd_cnx_realloc_msg_buffer(uint8_t * buffer, size_t expected_len, struct fd_msg_pmdl ** pmdl)
{
	uint8_t * ret = NULL;
	
	CHECK_MALLOC_DO(  ret = realloc( buffer, fd_msg_pmdl_sizewithoverhead(expected_len) ), return NULL );
	CHECK_FCT_DO( fd_cnx_init_msg_buffer(ret, expected_len, pmdl), {free(ret); return NULL;} );
	return ret;
}
#endif /* DISABLE_SCTP */

static void free_rcvdata(void * arg) 
{
	struct fd_cnx_rcvdata * data = arg;
	struct fd_msg_pmdl * pmdl = fd_msg_pmdl_get_inbuf(data->buffer, data->length);
	(void) pthread_mutex_destroy(&pmdl->lock);
	free(data->buffer);
}

/* Receiver thread (TCP & noTLS) : incoming message is directly saved into the target queue */
static void * rcvthr_notls_tcp(void * arg)
{
	struct cnxctx * conn = arg;
	
	TRACE_ENTRY("%p", arg);
	CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto out);
	
	/* Set the thread name */
	{
		char buf[48];
		snprintf(buf, sizeof(buf), "Receiver (%d) TCP/noTLS)", conn->cc_socket);
		fd_log_threadname ( buf );
	}
	
	ASSERT( conn->cc_proto == IPPROTO_TCP );
	ASSERT( ! fd_cnx_teststate(conn, CC_STATUS_TLS ) );
	ASSERT( fd_cnx_target_queue(conn) );
	
	/* Receive from a TCP connection: we have to rebuild the message boundaries */
	do {
		uint8_t header[4];
		struct fd_cnx_rcvdata rcv_data;
		struct fd_msg_pmdl *pmdl=NULL;
		ssize_t ret = 0;
		size_t	received = 0;

		do {
			ret = fd_cnx_s_recv(conn, &header[received], sizeof(header) - received);
			if (ret <= 0) {
				goto out; /* Stop the thread, the event was already sent */
			}

			received += ret;
			
			if (header[0] != DIAMETER_VERSION)
				break; /* No need to wait for 4 bytes in this case */
		} while (received < sizeof(header));

		rcv_data.length = ((size_t)header[1] << 16) + ((size_t)header[2] << 8) + (size_t)header[3];

		/* Check the received word is a valid begining of a Diameter message */
		if ((header[0] != DIAMETER_VERSION)	/* defined in <libfdproto.h> */
		   || (rcv_data.length > DIAMETER_MSG_SIZE_MAX)) { /* to avoid too big mallocs */
			/* The message is suspect */
			LOG_E( "Received suspect header [ver: %d, size: %zd] from '%s', assuming disconnection", (int)header[0], rcv_data.length, conn->cc_remid);
			fd_cnx_markerror(conn);
			goto out; /* Stop the thread, the recipient of the event will cleanup */
		}

		/* Ok, now we can really receive the data */
		CHECK_MALLOC_DO(  rcv_data.buffer = fd_cnx_alloc_msg_buffer( rcv_data.length, &pmdl ), goto fatal );
		memcpy(rcv_data.buffer, header, sizeof(header));

		while (received < rcv_data.length) {
			pthread_cleanup_push(free_rcvdata, &rcv_data); /* In case we are canceled, clean the partialy built buffer */
			ret = fd_cnx_s_recv(conn, rcv_data.buffer + received, rcv_data.length - received);
			pthread_cleanup_pop(0);

			if (ret <= 0) {
				free_rcvdata(&rcv_data);
				goto out;
			}
			received += ret;
		}
		
		fd_hook_call(HOOK_DATA_RECEIVED, NULL, NULL, &rcv_data, pmdl);
		
		/* We have received a complete message, pass it to the daemon */
		CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), FDEVP_CNX_MSG_RECV, rcv_data.length, rcv_data.buffer), 
			{ 
				free_rcvdata(&rcv_data);
				goto fatal; 
			} );
		
	} while (conn->cc_loop);
	
out:
	TRACE_DEBUG(FULL, "Thread terminated");	
	return NULL;
	
fatal:
	/* An unrecoverable error occurred, stop the daemon */
	CHECK_FCT_DO(fd_core_shutdown(), );
	goto out;
}

#ifndef DISABLE_SCTP
/* Receiver thread (SCTP & noTLS) : incoming message is directly saved into cc_incoming, no need to care for the stream ID */
static void * rcvthr_notls_sctp(void * arg)
{
	struct cnxctx * conn = arg;
	struct fd_cnx_rcvdata rcv_data;
	int	  event;
	
	TRACE_ENTRY("%p", arg);
	CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto fatal);
	
	/* Set the thread name */
	{
		char buf[48];
		snprintf(buf, sizeof(buf), "Receiver (%d) SCTP/noTLS)", conn->cc_socket);
		fd_log_threadname ( buf );
	}
	
	ASSERT( conn->cc_proto == IPPROTO_SCTP );
	ASSERT( ! fd_cnx_teststate(conn, CC_STATUS_TLS ) );
	ASSERT( fd_cnx_target_queue(conn) );
	
	do {
		struct fd_msg_pmdl *pmdl=NULL;
		CHECK_FCT_DO( fd_sctp_recvmeta(conn, NULL, &rcv_data.buffer, &rcv_data.length, &event), goto fatal );
		if (event == FDEVP_CNX_ERROR) {
			fd_cnx_markerror(conn);
			goto out;
		}
		
		if (event == FDEVP_CNX_SHUTDOWN) {
			/* Just ignore the notification for now, we will get another error later anyway */
			continue;
		}

		if (event == FDEVP_CNX_MSG_RECV) {
			CHECK_MALLOC_DO( rcv_data.buffer = fd_cnx_realloc_msg_buffer(rcv_data.buffer, rcv_data.length, &pmdl), goto fatal );
			fd_hook_call(HOOK_DATA_RECEIVED, NULL, NULL, &rcv_data, pmdl);
		}
		CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), event, rcv_data.length, rcv_data.buffer), goto fatal );
		
	} while (conn->cc_loop || (event != FDEVP_CNX_MSG_RECV));
	
out:
	TRACE_DEBUG(FULL, "Thread terminated");	
	return NULL;

fatal:
	/* An unrecoverable error occurred, stop the daemon */
	CHECK_FCT_DO(fd_core_shutdown(), );
	goto out;
}
#endif /* DISABLE_SCTP */

/* Start receving messages in clear (no TLS) on the connection */
int fd_cnx_start_clear(struct cnxctx * conn, int loop)
{
	TRACE_ENTRY("%p %i", conn, loop);
	
	CHECK_PARAMS( conn && fd_cnx_target_queue(conn) && (!fd_cnx_teststate(conn, CC_STATUS_TLS)) && (!conn->cc_loop));
	
	/* Release resources in case of a previous call was already made */
	CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */);
	
	/* Save the loop request */
	conn->cc_loop = loop;
	
	switch (conn->cc_proto) {
		case IPPROTO_TCP:
			/* Start the tcp_notls thread */
			CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_notls_tcp, conn ) );
			break;
#ifndef DISABLE_SCTP
		case IPPROTO_SCTP:
			/* Start the tcp_notls thread */
			CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_notls_sctp, conn ) );
			break;
#endif /* DISABLE_SCTP */
		default:
			TRACE_DEBUG(INFO, "Unknown protocol: %d", conn->cc_proto);
			ASSERT(0);
			return ENOTSUP;
	}
			
	return 0;
}




/* Returns 0 on error, received data size otherwise (always >= 0). This is not used for DTLS-protected associations. */
static ssize_t fd_tls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz)
{
	ssize_t ret;
again:	
	CHECK_GNUTLS_DO( ret = gnutls_record_recv(session, data, sz), 
		{
			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;
				
				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;
}

/* Wrapper around gnutls_record_send to handle some error codes. This is also used for DTLS-protected associations */
static ssize_t fd_tls_send_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz)
{
	ssize_t ret;
	struct timespec ts, now;
	CHECK_SYS_DO(  clock_gettime(CLOCK_REALTIME, &ts), return -1 );
again:	
	CHECK_GNUTLS_DO( ret = gnutls_record_send(session, data, sz),
		{
			pthread_testcancel();
			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:
					CHECK_SYS_DO(  clock_gettime(CLOCK_REALTIME, &now), return -1 );
					if ( ((now.tv_sec - ts.tv_sec) * 1000 + ((now.tv_nsec - ts.tv_nsec) / 1000000L)) > MAX_HOTL_BLOCKING_TIME) {
						LOG_D("Unable to send any data for %dms, closing the connection", MAX_HOTL_BLOCKING_TIME);
					} else if (! fd_cnx_teststate(conn, CC_STATUS_CLOSING )) {
						goto again;
					}
					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));
			}
		} );
end:	
	if (ret <= 0)
		fd_cnx_markerror(conn);
		
	return ret;
}


/* The function that receives TLS data and re-builds a Diameter message -- it exits only on error or cancelation */
/* 	   For the case of DTLS, since we are not using SCTP_UNORDERED, the messages over a single stream are ordered.
	   Furthermore, as long as messages are shorter than the MTU [2^14 = 16384 bytes], they are delivered in a single
	   record, as far as I understand. 
	   For larger messages, however, it is possible that pieces of messages coming from different streams can get interleaved. 
	   As a result, we do not use the following function for DTLS reception, because we use the sequence number to rebuild the
	   messages. */
int fd_tls_rcvthr_core(struct cnxctx * conn, gnutls_session_t session)
{
	ssize_t ret = 0;
	/* No guarantee that GnuTLS preserves the message boundaries, so we re-build it as in TCP. */
	do {
		uint8_t header[4];
		struct fd_cnx_rcvdata rcv_data;
		struct fd_msg_pmdl *pmdl=NULL;
		size_t	received = 0;
		ret = 0;

		do {
			ret = fd_tls_recv_handle_error(conn, session, &header[received], sizeof(header) - received);
			if (ret <= 0) {
				/* The connection is closed */
				goto out;
			}
			received += ret;
		} while (received < sizeof(header));

		rcv_data.length = ((size_t)header[1] << 16) + ((size_t)header[2] << 8) + (size_t)header[3];

		/* Check the received word is a valid beginning of a Diameter message */
		if ((header[0] != DIAMETER_VERSION)	/* defined in <libfreeDiameter.h> */
		   || (rcv_data.length > DIAMETER_MSG_SIZE_MAX)) { /* to avoid too big mallocs */
			/* The message is suspect */
			LOG_E( "Received suspect header [ver: %d, size: %zd] from '%s', assume disconnection", (int)header[0], rcv_data.length, conn->cc_remid);
			fd_cnx_markerror(conn);
			goto out;
		}

		/* Ok, now we can really receive the data */
		CHECK_MALLOC(  rcv_data.buffer = fd_cnx_alloc_msg_buffer( rcv_data.length, &pmdl ) );
		memcpy(rcv_data.buffer, header, sizeof(header));

		while (received < rcv_data.length) {
			pthread_cleanup_push(free_rcvdata, &rcv_data); /* In case we are canceled, clean the partialy built buffer */
			ret = fd_tls_recv_handle_error(conn, session, rcv_data.buffer + received, rcv_data.length - received);
			pthread_cleanup_pop(0);

			if (ret <= 0) {
				free_rcvdata(&rcv_data);
				goto out;
			}
			received += ret;
		}
		
		fd_hook_call(HOOK_DATA_RECEIVED, NULL, NULL, &rcv_data, pmdl);
		
		/* We have received a complete message, pass it to the daemon */
		CHECK_FCT_DO( ret = fd_event_send( fd_cnx_target_queue(conn), FDEVP_CNX_MSG_RECV, rcv_data.length, rcv_data.buffer), 
			{ 
				free_rcvdata(&rcv_data);
				CHECK_FCT_DO(fd_core_shutdown(), );
				return ret; 
			} );
		
	} while (1);
	
out:
	return (ret == 0) ? 0 : ENOTCONN;
}

/* Receiver thread (TLS & 1 stream SCTP or TCP)  */
static void * rcvthr_tls_single(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) TLS/single stream", 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), /* continue */);

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

/* Prepare a gnutls session object for handshake */
int fd_tls_prepare(gnutls_session_t * session, int mode, int dtls, char * priority, void * alt_creds)
{
	if (dtls) {
		LOG_E("DTLS sessions not yet supported");
		return ENOTSUP;
	}

	/* Create the session context */
	CHECK_GNUTLS_DO( gnutls_init (session, mode), return ENOMEM );

	/* Set the algorithm suite */
	if (priority) {
		const char * errorpos;
		CHECK_GNUTLS_DO( gnutls_priority_set_direct( *session, priority, &errorpos ), 
			{ TRACE_DEBUG(INFO, "Error in priority string '%s' at position: '%s'", priority, errorpos); return EINVAL; } );
	} else {
		CHECK_GNUTLS_DO( gnutls_priority_set( *session, fd_g_config->cnf_sec_data.prio_cache ), return EINVAL );
	}

	/* Set the credentials of this side of the connection */
	CHECK_GNUTLS_DO( gnutls_credentials_set (*session, GNUTLS_CRD_CERTIFICATE, alt_creds ?: fd_g_config->cnf_sec_data.credentials), return EINVAL );

	/* Request the remote credentials as well */
	if (mode == GNUTLS_SERVER) {
		gnutls_certificate_server_set_request (*session, GNUTLS_CERT_REQUIRE);
	}
		
	return 0;
}

#ifndef GNUTLS_VERSION_300

/* Verify remote credentials after successful handshake (return 0 if OK, EINVAL otherwise) */
int fd_tls_verify_credentials(gnutls_session_t session, struct cnxctx * conn, int verbose)
{
	int i, ret = 0;
	unsigned int gtret;
	const gnutls_datum_t *cert_list;
	unsigned int cert_list_size;
	gnutls_x509_crt_t cert;
	time_t now;
	
	TRACE_ENTRY("%p %d", conn, verbose);
	CHECK_PARAMS(conn);
	
	/* Trace the session information -- http://www.gnu.org/software/gnutls/manual/gnutls.html#Obtaining-session-information */
	#ifdef DEBUG
	if (verbose) {
		const char *tmp;
		gnutls_kx_algorithm_t kx;
  		gnutls_credentials_type_t cred;
		
		LOG_D("TLS Session information for connection '%s':", conn->cc_id);

		/* print the key exchange's algorithm name */
		GNUTLS_TRACE( kx = gnutls_kx_get (session) );
		GNUTLS_TRACE( tmp = gnutls_kx_get_name (kx) );
		LOG_D("\t - Key Exchange: %s", tmp);

		/* Check the authentication type used and switch
		* to the appropriate. */
		GNUTLS_TRACE( cred = gnutls_auth_get_type (session) );
		switch (cred)
		{
			case GNUTLS_CRD_IA:
				LOG_D("\t - TLS/IA session");
				break;

			case GNUTLS_CRD_PSK:
				/* This returns NULL in server side. */
				if (gnutls_psk_client_get_hint (session) != NULL)
					LOG_D("\t - PSK authentication. PSK hint '%s'",
						gnutls_psk_client_get_hint (session));
				/* This returns NULL in client side. */
				if (gnutls_psk_server_get_username (session) != NULL)
					LOG_D("\t - PSK authentication. Connected as '%s'",
						gnutls_psk_server_get_username (session));
				break;

			case GNUTLS_CRD_ANON:	/* anonymous authentication */
				LOG_D("\t - Anonymous DH using prime of %d bits",
					gnutls_dh_get_prime_bits (session));
				break;

			case GNUTLS_CRD_CERTIFICATE:	/* certificate authentication */
				/* Check if we have been using ephemeral Diffie-Hellman. */
				if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) {
					LOG_D("\t - Ephemeral DH using prime of %d bits",
						gnutls_dh_get_prime_bits (session));
				}
				break;
#ifdef ENABLE_SRP				
			case GNUTLS_CRD_SRP:
				LOG_D("\t - SRP session with username %s",
					gnutls_srp_server_get_username (session));
				break;
#endif /* ENABLE_SRP */

			default:
				fd_log_debug("\t - Different type of credentials for the session (%d).", cred);
				break;

		}

		/* print the protocol's name (ie TLS 1.0) */
		tmp = gnutls_protocol_get_name (gnutls_protocol_get_version (session));
		LOG_D("\t - Protocol: %s", tmp);

		/* print the certificate type of the peer. ie X.509 */
		tmp = gnutls_certificate_type_get_name (gnutls_certificate_type_get (session));
		LOG_D("\t - Certificate Type: %s", tmp);

		/* print the compression algorithm (if any) */
		tmp = gnutls_compression_get_name (gnutls_compression_get (session));
		LOG_D("\t - Compression: %s", tmp);

		/* print the name of the cipher used. ie 3DES. */
		tmp = gnutls_cipher_get_name (gnutls_cipher_get (session));
		LOG_D("\t - Cipher: %s", tmp);

		/* Print the MAC algorithms name. ie SHA1 */
		tmp = gnutls_mac_get_name (gnutls_mac_get (session));
		LOG_D("\t - MAC: %s", tmp);
	}
	#endif /* DEBUG */
	
	/* First, use built-in verification */
	CHECK_GNUTLS_DO( gnutls_certificate_verify_peers2 (session, &gtret), return EINVAL );
	if (gtret) {
		LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
		if (gtret & GNUTLS_CERT_INVALID)
			LOG_E(" - The certificate is not trusted (unknown CA? expired?)");
		if (gtret & GNUTLS_CERT_REVOKED)
			LOG_E(" - The certificate has been revoked.");
		if (gtret & GNUTLS_CERT_SIGNER_NOT_FOUND)
			LOG_E(" - The certificate hasn't got a known issuer.");
		if (gtret & GNUTLS_CERT_SIGNER_NOT_CA)
			LOG_E(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints.");
		if (gtret & GNUTLS_CERT_INSECURE_ALGORITHM)
			LOG_E(" - The certificate signature uses a weak algorithm.");
		return EINVAL;
	}
	
	/* Code from http://www.gnu.org/software/gnutls/manual/gnutls.html#Verifying-peer_0027s-certificate */
	if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) {
		LOG_E("TLS: Remote peer did not present a certificate, other mechanisms are not supported yet. socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
		return EINVAL;
	}
	
	GNUTLS_TRACE( cert_list = gnutls_certificate_get_peers (session, &cert_list_size) );
	if (cert_list == NULL)
		return EINVAL;
	
	now = time(NULL);
	
	#ifdef DEBUG
		char serial[40];
		char dn[128];
		size_t size;
		unsigned int algo, bits;
		time_t expiration_time, activation_time;
		
		LOG_D("TLS Certificate information for connection '%s' (%d certs provided):", conn->cc_id, cert_list_size);
		for (i = 0; i < cert_list_size; i++)
		{

			CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return EINVAL);
			CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER), return EINVAL);
		
			LOG_A(" Certificate %d info:", i);

			GNUTLS_TRACE( expiration_time = gnutls_x509_crt_get_expiration_time (cert) );
			GNUTLS_TRACE( activation_time = gnutls_x509_crt_get_activation_time (cert) );

			LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate is valid since: %.24s", ctime (&activation_time));
			LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate expires: %.24s", ctime (&expiration_time));

			/* Print the serial number of the certificate. */
			size = sizeof (serial);
			gnutls_x509_crt_get_serial (cert, serial, &size);
			
			{
				int j;
				char buf[1024];
				snprintf(buf, sizeof(buf), "\t - Certificate serial number: ");
				for (j = 0; j < size; j++) {
					snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%02hhx", serial[j]);
				}
				LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "%s", buf);
			}

			/* Extract some of the public key algorithm's parameters */
			GNUTLS_TRACE( algo = gnutls_x509_crt_get_pk_algorithm (cert, &bits) );
			LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate public key: %s",
			      gnutls_pk_algorithm_get_name (algo));

			/* Print the version of the X.509 certificate. */
			LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate version: #%d",
			      gnutls_x509_crt_get_version (cert));

			size = sizeof (dn);
			GNUTLS_TRACE( gnutls_x509_crt_get_dn (cert, dn, &size) );
			LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - DN: %s", dn);

			size = sizeof (dn);
			GNUTLS_TRACE( gnutls_x509_crt_get_issuer_dn (cert, dn, &size) );
			LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Issuer's DN: %s", dn);

			GNUTLS_TRACE( gnutls_x509_crt_deinit (cert) );
		}
	#endif /* DEBUG */

	/* Check validity of all the certificates */
	for (i = 0; i < cert_list_size; i++)
	{
		time_t deadline;
		
		CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return EINVAL);
		CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER), return EINVAL);
		
		GNUTLS_TRACE( deadline = gnutls_x509_crt_get_expiration_time(cert) );
		if ((deadline != (time_t)-1) && (deadline < now)) {
			LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
			LOG_E(" - The certificate %d in the chain is expired", i);
			ret = EINVAL;
		}
		
		GNUTLS_TRACE( deadline = gnutls_x509_crt_get_activation_time(cert) );
		if ((deadline != (time_t)-1) && (deadline > now)) {
			LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
			LOG_E(" - The certificate %d in the chain is not yet activated", i);
			ret = EINVAL;
		}
		
		if ((i == 0) && (conn->cc_tls_para.cn)) {
			if (!gnutls_x509_crt_check_hostname (cert, conn->cc_tls_para.cn)) {
				LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
				LOG_E(" - The certificate hostname does not match '%s'", conn->cc_tls_para.cn);
				ret = EINVAL;
			}
		}
		
		GNUTLS_TRACE( gnutls_x509_crt_deinit (cert) );
	}

	return ret;
}

#else /* GNUTLS_VERSION_300 */

/* Verify remote credentials DURING handshake (return gnutls status) */
int fd_tls_verify_credentials_2(gnutls_session_t session)
{
	/* inspired from gnutls 3.x guidelines */
	unsigned int status;
	const gnutls_datum_t *cert_list = NULL;
	unsigned int cert_list_size;
	gnutls_x509_crt_t cert;
	struct cnxctx * conn;
	int hostname_verified = 0;

	TRACE_ENTRY("%p", session);
	
	/* get the associated connection */
	conn = gnutls_session_get_ptr (session);
	
	/* Trace the session information -- http://www.gnu.org/software/gnutls/manual/gnutls.html#Obtaining-session-information */
#ifdef DEBUG
		const char *tmp;
		gnutls_credentials_type_t cred;
		gnutls_kx_algorithm_t kx;
		int dhe, ecdh;

		dhe = ecdh = 0;

		LOG_A("TLS Session information for connection '%s':", conn->cc_id);
		
		/* print the key exchange's algorithm name
		*/
		GNUTLS_TRACE( kx = gnutls_kx_get (session) );
		GNUTLS_TRACE( tmp = gnutls_kx_get_name (kx) );
		LOG_D("\t- Key Exchange: %s", tmp);

		/* Check the authentication type used and switch
		* to the appropriate.
		*/
		GNUTLS_TRACE( cred = gnutls_auth_get_type (session) );
		switch (cred)
		{
			case GNUTLS_CRD_IA:
				LOG_D("\t - TLS/IA session");
				break;


			#ifdef ENABLE_SRP
			case GNUTLS_CRD_SRP:
				LOG_D("\t - SRP session with username %s",
					gnutls_srp_server_get_username (session));
				break;
			#endif

			case GNUTLS_CRD_PSK:
				/* This returns NULL in server side.
				*/
				if (gnutls_psk_client_get_hint (session) != NULL)
					LOG_D("\t - PSK authentication. PSK hint '%s'",
						gnutls_psk_client_get_hint (session));
				/* This returns NULL in client side.
				*/
				if (gnutls_psk_server_get_username (session) != NULL)
					LOG_D("\t - PSK authentication. Connected as '%s'",
						gnutls_psk_server_get_username (session));

				if (kx == GNUTLS_KX_ECDHE_PSK)
					ecdh = 1;
				else if (kx == GNUTLS_KX_DHE_PSK)
					dhe = 1;
				break;

			case GNUTLS_CRD_ANON:      /* anonymous authentication */
				LOG_D("\t - Anonymous DH using prime of %d bits",
					gnutls_dh_get_prime_bits (session));
				if (kx == GNUTLS_KX_ANON_ECDH)
					ecdh = 1;
				else if (kx == GNUTLS_KX_ANON_DH)
					dhe = 1;
				break;

			case GNUTLS_CRD_CERTIFICATE:       /* certificate authentication */

				/* Check if we have been using ephemeral Diffie-Hellman.
				*/
				if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS)
					dhe = 1;
				else if (kx == GNUTLS_KX_ECDHE_RSA || kx == GNUTLS_KX_ECDHE_ECDSA)
					ecdh = 1;
				
				/* Now print some info on the remote certificate */
				if (gnutls_certificate_type_get (session) == GNUTLS_CRT_X509) {
					gnutls_datum_t cinfo;

					cert_list = gnutls_certificate_get_peers (session, &cert_list_size);

					LOG_D("\t Peer provided %d certificates.", cert_list_size);

					if (cert_list_size > 0)
					{
						int ret;

						/* we only print information about the first certificate.
						*/
						gnutls_x509_crt_init (&cert);

						gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER);

												LOG_A("\t Certificate info:");

						/* This is the preferred way of printing short information about
						 a certificate. */

						ret = gnutls_x509_crt_print (cert, GNUTLS_CRT_PRINT_ONELINE, &cinfo);
						if (ret == 0)
						{
						  LOG_D("\t\t%s", cinfo.data);
						  gnutls_free (cinfo.data);
						}
						
						if (conn->cc_tls_para.cn) {
							if (!gnutls_x509_crt_check_hostname (cert, conn->cc_tls_para.cn)) {
								LOG_E("\tTLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
								LOG_E("\t - The certificate hostname does not match '%s'", conn->cc_tls_para.cn);
								gnutls_x509_crt_deinit (cert);
								return GNUTLS_E_CERTIFICATE_ERROR;
							}
							
						}

						hostname_verified = 1;

						gnutls_x509_crt_deinit (cert);

					}
    				}
				break;

			default:
				LOG_E("\t - unknown session type (%d)", cred);

		}                           /* switch */

		if (ecdh != 0)
			LOG_D("\t - Ephemeral ECDH using curve %s",
				gnutls_ecc_curve_get_name (gnutls_ecc_curve_get (session)));
		else if (dhe != 0)
			LOG_D("\t - Ephemeral DH using prime of %d bits",
				gnutls_dh_get_prime_bits (session));

		/* print the protocol's name (ie TLS 1.0) 
		*/
		tmp = gnutls_protocol_get_name (gnutls_protocol_get_version (session));
		LOG_D("\t - Protocol: %s", tmp);

		/* print the certificate type of the peer.
		* ie X.509
		*/
		tmp = gnutls_certificate_type_get_name (gnutls_certificate_type_get (session));
		LOG_D("\t - Certificate Type: %s", tmp);

		/* print the compression algorithm (if any)
		*/
		tmp = gnutls_compression_get_name (gnutls_compression_get (session));
		LOG_D("\t - Compression: %s", tmp);

		/* print the name of the cipher used.
		* ie 3DES.
		*/
		tmp = gnutls_cipher_get_name (gnutls_cipher_get (session));
		LOG_D("\t - Cipher: %s", tmp);

		/* Print the MAC algorithms name.
		* ie SHA1
		*/
		tmp = gnutls_mac_get_name (gnutls_mac_get (session));
		LOG_D("\t - MAC: %s", tmp);
	
#endif /* DEBUG */		

	/* This verification function uses the trusted CAs in the credentials
	* structure. So you must have installed one or more CA certificates.
	*/
	CHECK_GNUTLS_DO( gnutls_certificate_verify_peers2 (session, &status), return GNUTLS_E_CERTIFICATE_ERROR );
	if (status & GNUTLS_CERT_INVALID) {
		LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
		if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
			LOG_E(" - The certificate hasn't got a known issuer.");

		if (status & GNUTLS_CERT_REVOKED)
			LOG_E(" - The certificate has been revoked.");

		if (status & GNUTLS_CERT_EXPIRED)
			LOG_E(" - The certificate has expired.");

		if (status & GNUTLS_CERT_NOT_ACTIVATED)
			LOG_E(" - The certificate is not yet activated.");
	}	
	if (status & GNUTLS_CERT_INVALID)
	{
		return GNUTLS_E_CERTIFICATE_ERROR;
	}
	
	/* Up to here the process is the same for X.509 certificates and
	* OpenPGP keys. From now on X.509 certificates are assumed. This can
	* be easily extended to work with openpgp keys as well.
	*/
	if ((!hostname_verified) && (conn->cc_tls_para.cn)) {
		if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) {
			LOG_E("TLS: Remote credentials are not x509, rejected on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
			return GNUTLS_E_CERTIFICATE_ERROR;
		}

		CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return GNUTLS_E_CERTIFICATE_ERROR );

		cert_list = gnutls_certificate_get_peers (session, &cert_list_size);
		CHECK_PARAMS_DO( cert_list, return GNUTLS_E_CERTIFICATE_ERROR );

		CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER), return GNUTLS_E_CERTIFICATE_ERROR );

		if (!gnutls_x509_crt_check_hostname (cert, conn->cc_tls_para.cn)) {
			LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
			LOG_E(" - The certificate hostname does not match '%s'", conn->cc_tls_para.cn);
			gnutls_x509_crt_deinit (cert);
			return GNUTLS_E_CERTIFICATE_ERROR;
		}

		gnutls_x509_crt_deinit (cert);
	}

	/* notify gnutls to continue handshake normally */
	return 0;
}

#endif /* GNUTLS_VERSION_300 */

static int fd_cnx_may_dtls(struct cnxctx * conn) {
#ifndef DISABLE_SCTP
	if ((conn->cc_proto == IPPROTO_SCTP) && (conn->cc_tls_para.algo == ALGO_HANDSHAKE_DEFAULT))
		return 1;
#endif /* DISABLE_SCTP */
	return 0;
}

#ifndef DISABLE_SCTP
static int fd_cnx_uses_dtls(struct cnxctx * conn) {
	return fd_cnx_may_dtls(conn) && (fd_cnx_teststate(conn, CC_STATUS_TLS));
}
#endif /* DISABLE_SCTP */

/* TLS handshake a connection; no need to have called start_clear before. Reception is active if handhsake is successful */
int fd_cnx_handshake(struct cnxctx * conn, int mode, int algo, char * priority, void * alt_creds)
{
	int dtls = 0;
	
	TRACE_ENTRY( "%p %d %d %p %p", conn, mode, algo, priority, alt_creds);
	CHECK_PARAMS( conn && (!fd_cnx_teststate(conn, CC_STATUS_TLS)) && ( (mode == GNUTLS_CLIENT) || (mode == GNUTLS_SERVER) ) && (!conn->cc_loop) );

	/* Save the mode */
	conn->cc_tls_para.mode = mode;
	conn->cc_tls_para.algo = algo;
	
	/* Cancel receiving thread if any -- it should already be terminated anyway, we just release the resources */
	CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */);
	
	/* Once TLS handshake is done, we don't stop after the first message */
	conn->cc_loop = 1;
	
	dtls = fd_cnx_may_dtls(conn);
	
	/* Prepare the master session credentials and priority */
	CHECK_FCT( fd_tls_prepare(&conn->cc_tls_para.session, mode, dtls, priority, alt_creds) );

	/* Special case: multi-stream TLS is not natively managed in GNU TLS, we use a wrapper library */
	if ((!dtls) && (conn->cc_sctp_para.pairs > 1)) {
#ifdef DISABLE_SCTP
		ASSERT(0);
		CHECK_FCT( ENOTSUP );
#else /* DISABLE_SCTP */
		/* Initialize the wrapper, start the demux thread */
		CHECK_FCT( fd_sctp3436_init(conn) );
#endif /* DISABLE_SCTP */
	} else {
		/* Set the transport pointer passed to push & pull callbacks */
		GNUTLS_TRACE( gnutls_transport_set_ptr( conn->cc_tls_para.session, (gnutls_transport_ptr_t) conn ) );

		/* Set the push and pull callbacks */
		if (!dtls) {
			#ifdef GNUTLS_VERSION_300
			GNUTLS_TRACE( gnutls_transport_set_pull_timeout_function( conn->cc_tls_para.session, (void *)fd_cnx_s_select ) );
			#endif /* GNUTLS_VERSION_300 */
			GNUTLS_TRACE( gnutls_transport_set_pull_function(conn->cc_tls_para.session, (void *)fd_cnx_s_recv) );
			#ifndef GNUTLS_VERSION_212
			GNUTLS_TRACE( gnutls_transport_set_push_function(conn->cc_tls_para.session, (void *)fd_cnx_s_send) );
			#else /* GNUTLS_VERSION_212 */
			GNUTLS_TRACE( gnutls_transport_set_vec_push_function(conn->cc_tls_para.session, (void *)fd_cnx_s_sendv) );
			#endif /* GNUTLS_VERSION_212 */
		} else {
			TODO("DTLS push/pull functions");
			return ENOTSUP;
		}
	}
	
	/* additional initialization for gnutls 3.x */
	#ifdef GNUTLS_VERSION_300
		/* the verify function has already been set in the global initialization in config.c */
	
	/* fd_tls_verify_credentials_2 uses the connection */
	gnutls_session_set_ptr (conn->cc_tls_para.session, (void *) conn);
	
	if ((conn->cc_tls_para.cn != NULL) && (mode == GNUTLS_CLIENT)) {
		/* this might allow virtual hosting on the remote peer */
		CHECK_GNUTLS_DO( gnutls_server_name_set (conn->cc_tls_para.session, GNUTLS_NAME_DNS, conn->cc_tls_para.cn, strlen(conn->cc_tls_para.cn)), /* ignore failure */);
	}
	
	#endif /* GNUTLS_VERSION_300 */

	#ifdef GNUTLS_VERSION_310
	GNUTLS_TRACE( gnutls_handshake_set_timeout( conn->cc_tls_para.session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT));
	#endif /* GNUTLS_VERSION_310 */
	
	/* Mark the connection as protected from here, so that the gnutls credentials will be freed */
	fd_cnx_addstate(conn, CC_STATUS_TLS);
	
	/* Handshake master session */
	{
		int ret;
	
		CHECK_GNUTLS_DO( ret = gnutls_handshake(conn->cc_tls_para.session),
			{
				if (TRACE_BOOL(INFO)) {
					fd_log_debug("TLS Handshake failed on socket %d (%s) : %s", conn->cc_socket, conn->cc_id, gnutls_strerror(ret));
				}
				fd_cnx_markerror(conn);
				return EINVAL;
			} );

		#ifndef GNUTLS_VERSION_300
		/* Now verify the remote credentials are valid -- only simple tests here */
		CHECK_FCT_DO( fd_tls_verify_credentials(conn->cc_tls_para.session, conn, 1), 
			{  
				CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_RDWR),  );
				fd_cnx_markerror(conn);
				return EINVAL;
			});
		#endif /* GNUTLS_VERSION_300 */
	}
	
	/* Multi-stream TLS: handshake other streams as well */
	if ((!dtls) && (conn->cc_sctp_para.pairs > 1)) {
#ifndef DISABLE_SCTP
		/* Start reading the messages from the master session. That way, if the remote peer closed, we are not stuck inside handshake */
		CHECK_FCT(fd_sctp3436_startthreads(conn, 0));

		/* Resume all additional sessions from the master one. */
		CHECK_FCT(fd_sctp3436_handshake_others(conn, priority, alt_creds));

		/* Start decrypting the messages from all threads and queuing them in target queue */
		CHECK_FCT(fd_sctp3436_startthreads(conn, 1));
#endif /* DISABLE_SCTP */
	} else {
		/* Start decrypting the data */
		if (!dtls) {
			CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_tls_single, conn ) );
		} else {
			TODO("Signal the dtls_push function that multiple streams can be used from this point.");
			TODO("Create DTLS rcvthr (must reassembly based on seq numbers & stream id ?)");
			return ENOTSUP;
		}
	}
	
	return 0;
}

/* Retrieve TLS credentials of the remote peer, after handshake */
int fd_cnx_getcred(struct cnxctx * conn, const gnutls_datum_t **cert_list, unsigned int *cert_list_size)
{
	TRACE_ENTRY("%p %p %p", conn, cert_list, cert_list_size);
	CHECK_PARAMS( conn && fd_cnx_teststate(conn, CC_STATUS_TLS) && cert_list && cert_list_size );
	
	/* This function only works for X.509 certificates. */
	CHECK_PARAMS( gnutls_certificate_type_get (conn->cc_tls_para.session) == GNUTLS_CRT_X509 );
	
	GNUTLS_TRACE( *cert_list = gnutls_certificate_get_peers (conn->cc_tls_para.session, cert_list_size) );
	if (*cert_list == NULL) {
		TRACE_DEBUG(INFO, "No certificate was provided by remote peer / an error occurred.");
		return EINVAL;
	}

	TRACE_DEBUG( FULL, "Saved certificate chain (%d certificates) in peer structure.", *cert_list_size);
	
	return 0;
}

/* Receive next message. if timeout is not NULL, wait only until timeout. This function only pulls from a queue, mgr thread is filling that queue aynchrounously. */
/* if the altfifo has been set on this conn object, this function must not be called */
int fd_cnx_receive(struct cnxctx * conn, struct timespec * timeout, unsigned char **buf, size_t * len)
{
	int    ev;
	size_t ev_sz;
	void * ev_data;
	
	TRACE_ENTRY("%p %p %p %p", conn, timeout, buf, len);
	CHECK_PARAMS(conn && (conn->cc_socket > 0) && buf && len);
	CHECK_PARAMS(conn->cc_rcvthr != (pthread_t)NULL);
	CHECK_PARAMS(conn->cc_alt == NULL);

	/* Now, pull the first event */
get_next:
	if (timeout) {
		CHECK_FCT( fd_event_timedget(conn->cc_incoming, timeout, FDEVP_PSM_TIMEOUT, &ev, &ev_sz, &ev_data) );
	} else {
		CHECK_FCT( fd_event_get(conn->cc_incoming, &ev, &ev_sz, &ev_data) );
	}
	
	switch (ev) {
		case FDEVP_CNX_MSG_RECV:
			/* We got one */
			*len = ev_sz;
			*buf = ev_data;
			return 0;
			
		case FDEVP_PSM_TIMEOUT:
			TRACE_DEBUG(FULL, "Timeout event received");
			return ETIMEDOUT;
			
		case FDEVP_CNX_EP_CHANGE:
			/* We ignore this event */
			goto get_next;
			
		case FDEVP_CNX_ERROR:
			TRACE_DEBUG(FULL, "Received ERROR event on the connection");
			return ENOTCONN;
	}
	
	TRACE_DEBUG(INFO, "Received unexpected event %d (%s)", ev, fd_pev_str(ev));
	return EINVAL;
}

/* Where the events are sent */
struct fifo * fd_cnx_target_queue(struct cnxctx * conn)
{
	struct fifo *q;
	CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
	q = conn->cc_alt ?: conn->cc_incoming;
	CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
	return q;
}

/* Set an alternate FIFO list to send FDEVP_CNX_* events to */
int fd_cnx_recv_setaltfifo(struct cnxctx * conn, struct fifo * alt_fifo)
{
	int ret;
	TRACE_ENTRY( "%p %p", conn, alt_fifo );
	CHECK_PARAMS( conn && alt_fifo && conn->cc_incoming );
	
	/* The magic function does it all */
	CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
	CHECK_FCT_DO( ret = fd_fifo_move( conn->cc_incoming, alt_fifo, &conn->cc_alt ), );
	CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
	
	return ret;
}

/* Send function when no multi-stream is involved, or sending on stream #0 (send() always use stream 0)*/
static int send_simple(struct cnxctx * conn, unsigned char * buf, size_t len)
{
	ssize_t ret;
	size_t sent = 0;
	TRACE_ENTRY("%p %p %zd", conn, buf, len);
	do {
		if (fd_cnx_teststate(conn, CC_STATUS_TLS)) {
			CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_tls_para.session, buf + sent, len - sent),  );
		} else {
			struct iovec iov;
			iov.iov_base = buf + sent;
			iov.iov_len  = len - sent;
			CHECK_SYS_DO( ret = fd_cnx_s_sendv(conn, &iov, 1), );
		}
		if (ret <= 0)
			return ENOTCONN;
		
		sent += ret;
	} while ( sent < len );
	return 0;
}

/* Send a message -- this is synchronous -- and we assume it's never called by several threads at the same time (on the same conn), so we don't protect. */
int fd_cnx_send(struct cnxctx * conn, unsigned char * buf, size_t len)
{
	TRACE_ENTRY("%p %p %zd", conn, buf, len);
	
	CHECK_PARAMS(conn && (conn->cc_socket > 0) && (! fd_cnx_teststate(conn, CC_STATUS_ERROR)) && buf && len);

	TRACE_DEBUG(FULL, "Sending %zdb %sdata on connection %s", len, fd_cnx_teststate(conn, CC_STATUS_TLS) ? "TLS-protected ":"", conn->cc_id);
	
	switch (conn->cc_proto) {
		case IPPROTO_TCP:
			CHECK_FCT( send_simple(conn, buf, len) );
			break;
		
#ifndef DISABLE_SCTP
		case IPPROTO_SCTP: {
			int dtls = fd_cnx_uses_dtls(conn);
			if (!dtls) {
				int stream = 0;
				if (conn->cc_sctp_para.unordered) {
					int limit;
					if (fd_cnx_teststate(conn, CC_STATUS_TLS))
						limit = conn->cc_sctp_para.pairs;
					else
						limit = conn->cc_sctp_para.str_out;
					
					if (limit > 1) {
						conn->cc_sctp_para.next += 1;
						conn->cc_sctp_para.next %= limit;
						stream = conn->cc_sctp_para.next;
					}
				}
				
				if (stream == 0) {
					/* We can use default function, it sends over stream #0 */
					CHECK_FCT( send_simple(conn, buf, len) );
				} else {
					if (!fd_cnx_teststate(conn, CC_STATUS_TLS)) {
						struct iovec iov;
						iov.iov_base = buf;
						iov.iov_len  = len;
						
						CHECK_SYS_DO( fd_sctp_sendstrv(conn, stream, &iov, 1), { fd_cnx_markerror(conn); return ENOTCONN; } );
					} else {
						/* push the data to the appropriate session */
						ssize_t ret;
						size_t sent = 0;
						ASSERT(conn->cc_sctp3436_data.array != NULL);
						do {
							CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_sctp3436_data.array[stream].session, buf + sent, len - sent), );
							if (ret <= 0)
								return ENOTCONN;

							sent += ret;
						} while ( sent < len );
					}
				}
			} else {
				/* DTLS */
				/* Multistream is handled at lower layer in the push/pull function */
				CHECK_FCT( send_simple(conn, buf, len) );
			}
		}
		break;
#endif /* DISABLE_SCTP */
	
		default:
			TRACE_DEBUG(INFO, "Unknown protocol: %d", conn->cc_proto);
			ASSERT(0);
			return ENOTSUP;	/* or EINVAL... */
	}
	
	return 0;
}


/**************************************/
/*     Destruction of connection      */
/**************************************/

/* Destroy a conn structure, and shutdown the socket */
void fd_cnx_destroy(struct cnxctx * conn)
{
	TRACE_ENTRY("%p", conn);
	
	CHECK_PARAMS_DO(conn, return);
	
	fd_cnx_addstate(conn, CC_STATUS_CLOSING);
	
	/* Initiate shutdown of the TLS session(s): call gnutls_bye(WR), then read until error */
	if (fd_cnx_teststate(conn, CC_STATUS_TLS)) {
#ifndef DISABLE_SCTP
		int dtls = fd_cnx_uses_dtls(conn);
		if ((!dtls) && (conn->cc_sctp_para.pairs > 1)) {
			if (! fd_cnx_teststate(conn, CC_STATUS_ERROR )) {
				/* Bye on master session */
				CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) );
			}

			if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) {
				/* and other stream pairs */
				fd_sctp3436_bye(conn);
			}

			if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) {
				/* Now wait for all decipher threads to terminate */
				fd_sctp3436_waitthreadsterm(conn);
			} else {
				/* Abord the threads, the connection is dead already */
				fd_sctp3436_stopthreads(conn);
			}

			/* Deinit gnutls resources */
			fd_sctp3436_gnutls_deinit_others(conn);
			if (conn->cc_tls_para.session) {
				GNUTLS_TRACE( gnutls_deinit(conn->cc_tls_para.session) );
				conn->cc_tls_para.session = NULL;
			}
			
			/* Destroy the wrapper (also stops the demux thread) */
			fd_sctp3436_destroy(conn);

		} else {
#endif /* DISABLE_SCTP */
		/* We are TLS, but not using the sctp3436 wrapper layer */
			if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) {
				/* Master session */
				CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) );
			}

			if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) {
				/* In this case, just wait for thread rcvthr_tls_single to terminate */
				if (conn->cc_rcvthr != (pthread_t)NULL) {
					CHECK_POSIX_DO(  pthread_join(conn->cc_rcvthr, NULL), /* continue */  );
					conn->cc_rcvthr = (pthread_t)NULL;
				}
			} else {
				/* Cancel the receiver thread in case it did not already terminate */
				CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ );
			}
			
			/* Free the resources of the TLS session */
			if (conn->cc_tls_para.session) {
				GNUTLS_TRACE( gnutls_deinit(conn->cc_tls_para.session) );
				conn->cc_tls_para.session = NULL;
			}
#ifndef DISABLE_SCTP
		}
#endif /* DISABLE_SCTP */
	}
	
	/* Terminate the thread in case it is not done yet -- is there any such case left ?*/
	CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ );
		
	/* Shut the connection down */
	if (conn->cc_socket > 0) {
		shutdown(conn->cc_socket, SHUT_RDWR);
		close(conn->cc_socket);
		conn->cc_socket = -1;
	}
	
	/* Empty and destroy FIFO list */
	if (conn->cc_incoming) {
		fd_event_destroy( &conn->cc_incoming, free );
	}
	
	/* Free the object */
	free(conn);
	
	/* Done! */
	return;
}
"Welcome to our mercurial repository"