Initial commit

Change-Id: I6a4444e3c193dae437cd7929f4c39aba7b749efa
diff --git a/libfdcore/sctp.c b/libfdcore/sctp.c
new file mode 100644
index 0000000..c80a497
--- /dev/null
+++ b/libfdcore/sctp.c
@@ -0,0 +1,1353 @@
+/*********************************************************************************************************
+* 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 <netinet/sctp.h>
+#include <sys/uio.h>
+
+/* Size of buffer to receive ancilliary data. May need to be enlarged if more sockopt are set... */
+#ifndef CMSG_BUF_LEN
+#define CMSG_BUF_LEN	1024
+#endif /* CMSG_BUF_LEN */
+
+/* Use old draft-ietf-tsvwg-sctpsocket-17 API ? If not defined, RFC6458 API will be used */
+/* #define OLD_SCTP_SOCKET_API */
+
+/* Automatically fallback to old API if some of the new symbols are not defined */
+#if (!defined(SCTP_CONNECTX_4_ARGS) || (!defined(SCTP_RECVRCVINFO)) || (!defined(SCTP_SNDINFO))) 
+# define OLD_SCTP_SOCKET_API
+#endif
+
+
+/* Temper with the retransmission timers to try and improve disconnection detection response? Undef this to keep the defaults of SCTP stack */
+#ifndef USE_DEFAULT_SCTP_RTX_PARAMS	/* make this a configuration option if useful */
+#define ADJUST_RTX_PARAMS
+#endif /* USE_DEFAULT_SCTP_RTX_PARAMS */
+
+/* Pre-binding socket options -- # streams read in config */
+static int fd_setsockopt_prebind(int sk)
+{
+	socklen_t sz;
+	
+	TRACE_ENTRY( "%d", sk);
+	
+	CHECK_PARAMS( sk > 0 );
+	
+	{
+		int reuse = 1;
+		CHECK_SYS(  setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))  );
+	}
+	
+#ifdef ADJUST_RTX_PARAMS
+	/* Set the retransmit parameters */
+	#ifdef SCTP_RTOINFO
+	{
+		struct sctp_rtoinfo rtoinfo;
+		memset(&rtoinfo, 0, sizeof(rtoinfo));
+
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(rtoinfo);
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_RTOINFO, &rtoinfo, &sz)  );
+			if (sz != sizeof(rtoinfo))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(rtoinfo));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_RTOINFO : srto_initial : %u", rtoinfo.srto_initial);
+			fd_log_debug( "                   srto_min     : %u", rtoinfo.srto_min);
+			fd_log_debug( "                   srto_max     : %u", rtoinfo.srto_max);
+		}
+
+		/* rtoinfo.srto_initial: Estimate of the RTT before it can be measured; keep the default value */
+		rtoinfo.srto_max = 5000; /* Maximum retransmit timer (in ms), we want fast retransmission time. */
+		rtoinfo.srto_min = 1000; /* Value under which the RTO does not descend, we set this value to not conflict with srto_max */
+
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_RTOINFO, &rtoinfo, sizeof(rtoinfo))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_RTOINFO, &rtoinfo, &sz)  );
+			fd_log_debug( "New SCTP_RTOINFO : srto_initial : %u", rtoinfo.srto_initial);
+			fd_log_debug( "                   srto_max     : %u", rtoinfo.srto_max);
+			fd_log_debug( "                   srto_min     : %u", rtoinfo.srto_min);
+		}
+	}
+	#else /* SCTP_RTOINFO */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_RTOINFO");
+	#endif /* SCTP_RTOINFO */
+	
+	/* Set the association parameters: max number of retransmits, ... */
+	#ifdef SCTP_ASSOCINFO
+	{
+		struct sctp_assocparams assoc;
+		memset(&assoc, 0, sizeof(assoc));
+
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(assoc);
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_ASSOCINFO, &assoc, &sz)  );
+			if (sz != sizeof(assoc))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(assoc));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_ASSOCINFO : sasoc_asocmaxrxt               : %hu", assoc.sasoc_asocmaxrxt);
+			fd_log_debug( "                     sasoc_number_peer_destinations : %hu", assoc.sasoc_number_peer_destinations);
+			fd_log_debug( "                     sasoc_peer_rwnd                : %u" , assoc.sasoc_peer_rwnd);
+			fd_log_debug( "                     sasoc_local_rwnd               : %u" , assoc.sasoc_local_rwnd);
+			fd_log_debug( "                     sasoc_cookie_life              : %u" , assoc.sasoc_cookie_life);
+		}
+
+		assoc.sasoc_asocmaxrxt = 4;	/* Maximum number of retransmission attempts: we want fast detection of errors */
+						/* Note that this must remain less than the sum of retransmission parameters of the different paths. */
+		
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_ASSOCINFO, &assoc, sizeof(assoc))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_ASSOCINFO, &assoc, &sz)  );
+			fd_log_debug( "New SCTP_ASSOCINFO : sasoc_asocmaxrxt               : %hu", assoc.sasoc_asocmaxrxt);
+			fd_log_debug( "                     sasoc_number_peer_destinations : %hu", assoc.sasoc_number_peer_destinations);
+			fd_log_debug( "                     sasoc_peer_rwnd                : %u" , assoc.sasoc_peer_rwnd);
+			fd_log_debug( "                     sasoc_local_rwnd               : %u" , assoc.sasoc_local_rwnd);
+			fd_log_debug( "                     sasoc_cookie_life              : %u" , assoc.sasoc_cookie_life);
+		}
+	}
+	#else /* SCTP_ASSOCINFO */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_ASSOCINFO");
+	#endif /* SCTP_ASSOCINFO */
+#endif /* ADJUST_RTX_PARAMS */
+	
+	/* Set the INIT parameters, such as number of streams */
+	#ifdef SCTP_INITMSG
+	{
+		struct sctp_initmsg init;
+		memset(&init, 0, sizeof(init));
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(init);
+
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG, &init, &sz)  );
+			if (sz != sizeof(init))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(init));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_INITMSG : sinit_num_ostreams   : %hu", init.sinit_num_ostreams);
+			fd_log_debug( "                   sinit_max_instreams  : %hu", init.sinit_max_instreams);
+			fd_log_debug( "                   sinit_max_attempts   : %hu", init.sinit_max_attempts);
+			fd_log_debug( "                   sinit_max_init_timeo : %hu", init.sinit_max_init_timeo);
+		}
+
+		/* Set the init options -- need to receive SCTP_COMM_UP to confirm the requested parameters, but we don't care (best effort) */
+		init.sinit_num_ostreams	  = fd_g_config->cnf_sctp_str;	/* desired number of outgoing streams */
+		init.sinit_max_init_timeo = CNX_TIMEOUT * 1000;
+
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG, &init, sizeof(init))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG, &init, &sz)  );
+			fd_log_debug( "New SCTP_INITMSG : sinit_num_ostreams   : %hu", init.sinit_num_ostreams);
+			fd_log_debug( "                   sinit_max_instreams  : %hu", init.sinit_max_instreams);
+			fd_log_debug( "                   sinit_max_attempts   : %hu", init.sinit_max_attempts);
+			fd_log_debug( "                   sinit_max_init_timeo : %hu", init.sinit_max_init_timeo);
+		}
+	}
+	#else /* SCTP_INITMSG */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_INITMSG");
+	#endif /* SCTP_INITMSG */
+	
+	/* The SO_LINGER option will be reset if we want to perform SCTP ABORT */
+	#ifdef SO_LINGER
+	{
+		struct linger linger;
+		memset(&linger, 0, sizeof(linger));
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(linger);
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, SOL_SOCKET, SO_LINGER, &linger, &sz)  );
+			if (sz != sizeof(linger))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(linger));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SO_LINGER : l_onoff  : %d", linger.l_onoff);
+			fd_log_debug( " 	       l_linger : %d", linger.l_linger);
+		}
+		
+		linger.l_onoff	= 0;	/* Do not activate the linger */
+		linger.l_linger = 0;	/* Ignored, but it would mean : Return immediately when closing (=> abort) (graceful shutdown in background) */
+		
+		/* Set the option */
+		CHECK_SYS(  setsockopt(sk, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, SOL_SOCKET, SO_LINGER, &linger, &sz)  );
+			fd_log_debug( "New SO_LINGER : l_onoff  : %d", linger.l_onoff);
+			fd_log_debug( "		  l_linger : %d", linger.l_linger);
+		}
+	}
+	#else /* SO_LINGER */
+	TRACE_DEBUG(ANNOYING, "Skipping SO_LINGER");
+	#endif /* SO_LINGER */
+	
+	/* Set the NODELAY option (Nagle-like algorithm) */
+	#ifdef SCTP_NODELAY
+	{
+		int nodelay;
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(nodelay);
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, &sz)  );
+			if (sz != sizeof(nodelay))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(nodelay));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_NODELAY value : %s", nodelay ? "true" : "false");
+		}
+
+		nodelay = 1;	/* We turn ON to disable the Nagle algorithm, so that packets are sent ASAP. */
+		
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, &sz)  );
+			fd_log_debug( "New SCTP_NODELAY value : %s", nodelay ? "true" : "false");
+		}
+	}
+	#else /* SCTP_NODELAY */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_NODELAY");
+	#endif /* SCTP_NODELAY */
+	
+	/*
+	   SO_RCVBUF			size of receiver window
+	   SO_SNDBUF			size of pending data to send
+	   SCTP_AUTOCLOSE		for one-to-many only
+	   SCTP_PRIMARY_ADDR		use this address as primary locally
+	   SCTP_ADAPTATION_LAYER	set adaptation layer indication, we don't use this 
+	*/
+	
+	/* Set the SCTP_DISABLE_FRAGMENTS option, required for TLS */
+	#ifdef SCTP_DISABLE_FRAGMENTS
+	{
+		int nofrag;
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(nofrag);
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &nofrag, &sz)  );
+			if (sz != sizeof(nofrag))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(nofrag));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_DISABLE_FRAGMENTS value : %s", nofrag ? "true" : "false");
+		}
+
+		nofrag = 0;	/* We turn ON the fragmentation, since Diameter messages & TLS messages can be quite large. */
+		
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &nofrag, sizeof(nofrag))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &nofrag, &sz)  );
+			fd_log_debug( "New SCTP_DISABLE_FRAGMENTS value : %s", nofrag ? "true" : "false");
+		}
+	}
+	#else /* SCTP_DISABLE_FRAGMENTS */
+	# error "TLS requires support of SCTP_DISABLE_FRAGMENTS"
+	#endif /* SCTP_DISABLE_FRAGMENTS */
+	
+	/* SCTP_PEER_ADDR_PARAMS	control heartbeat per peer address. We set it as a default for all addresses in the association; not sure if it works ... */
+	#ifdef SCTP_PEER_ADDR_PARAMS
+	{
+		struct sctp_paddrparams parms;
+		memset(&parms, 0, sizeof(parms));
+		
+		/* Some kernel versions need this to be set */
+		parms.spp_address.ss_family = AF_INET;
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(parms);
+
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &parms, &sz)  );
+			if (sz != sizeof(parms))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(parms));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_PEER_ADDR_PARAMS : spp_hbinterval    : %u",  parms.spp_hbinterval);
+			fd_log_debug( "                            spp_pathmaxrxt    : %hu", parms.spp_pathmaxrxt);
+			fd_log_debug( "                            spp_pathmtu       : %u",  parms.spp_pathmtu);
+			fd_log_debug( "                            spp_flags         : %x",  parms.spp_flags);
+			// fd_log_debug( "                            spp_ipv6_flowlabel: %u",  parms.spp_ipv6_flowlabel);
+			// fd_log_debug( "                            spp_ipv4_tos      : %hhu",parms.spp_ipv4_tos);
+		}
+
+		parms.spp_flags = SPP_HB_ENABLE;	/* Enable heartbeat for the association */
+		#ifdef SPP_PMTUD_ENABLE
+		parms.spp_flags |= SPP_PMTUD_ENABLE;	/* also enable path MTU discovery mechanism */
+		#endif /* SPP_PMTUD_ENABLE */
+		
+#ifdef ADJUST_RTX_PARAMS
+		parms.spp_hbinterval = 6000;		/* Send an heartbeat every 6 seconds to quickly start retransmissions */
+		/* parms.spp_pathmaxrxt : max nbr of restransmissions on this address. There is a relationship with sasoc_asocmaxrxt, so we leave the default here */
+#endif /* ADJUST_RTX_PARAMS */
+
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &parms, sizeof(parms)) );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &parms, &sz)  );
+			fd_log_debug( "New SCTP_PEER_ADDR_PARAMS : spp_hbinterval    : %u",  parms.spp_hbinterval);
+			fd_log_debug( "                            spp_pathmaxrxt    : %hu", parms.spp_pathmaxrxt);
+			fd_log_debug( "                            spp_pathmtu       : %u",  parms.spp_pathmtu);
+			fd_log_debug( "                            spp_flags         : %x",  parms.spp_flags);
+			// fd_log_debug( "                            spp_ipv6_flowlabel: %u",  parms.spp_ipv6_flowlabel);
+			// fd_log_debug( "                            spp_ipv4_tos      : %hhu",parms.spp_ipv4_tos);
+		}
+	}
+	#else /* SCTP_PEER_ADDR_PARAMS */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_PEER_ADDR_PARAMS");
+	#endif /* SCTP_PEER_ADDR_PARAMS */
+	
+	/*
+	   SCTP_DEFAULT_SEND_PARAM - DEPRECATED // parameters for the sendto() call, we don't use it.
+	*/
+
+	/* Subscribe to some notifications */
+#ifdef OLD_SCTP_SOCKET_API
+	#ifdef SCTP_EVENTS /* DEPRECATED */
+	{
+		struct sctp_event_subscribe event;
+
+		memset(&event, 0, sizeof(event));
+		event.sctp_data_io_event	= 1;	/* to receive the stream ID in SCTP_SNDRCV ancilliary data on message reception */
+		event.sctp_association_event	= 0;	/* new or closed associations (mostly for one-to-many style sockets) */
+		event.sctp_address_event	= 1;	/* address changes */
+		event.sctp_send_failure_event	= 1;	/* delivery failures */
+		event.sctp_peer_error_event	= 1;	/* remote peer sends an error */
+		event.sctp_shutdown_event	= 1;	/* peer has sent a SHUTDOWN */
+		event.sctp_partial_delivery_event = 1;	/* a partial delivery is aborted, probably indicating the connection is being shutdown */
+		// event.sctp_adaptation_layer_event = 0;	/* adaptation layer notifications */
+		// event.sctp_authentication_event = 0;	/* when new key is made active */
+
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(event)) );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(event);
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_EVENTS, &event, &sz) );
+			if (sz != sizeof(event))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(event));
+				return ENOTSUP;
+			}
+
+			fd_log_debug( "SCTP_EVENTS : sctp_data_io_event          : %hhu", event.sctp_data_io_event);
+			fd_log_debug( "       	     sctp_association_event      : %hhu", event.sctp_association_event);
+			fd_log_debug( "       	     sctp_address_event	         : %hhu", event.sctp_address_event);
+			fd_log_debug( "       	     sctp_send_failure_event     : %hhu", event.sctp_send_failure_event);
+			fd_log_debug( "       	     sctp_peer_error_event       : %hhu", event.sctp_peer_error_event);
+			fd_log_debug( "       	     sctp_shutdown_event	 : %hhu", event.sctp_shutdown_event);
+			fd_log_debug( "       	     sctp_partial_delivery_event : %hhu", event.sctp_partial_delivery_event);
+			fd_log_debug( "       	     sctp_adaptation_layer_event : %hhu", event.sctp_adaptation_layer_event);
+			// fd_log_debug( "             sctp_authentication_event    : %hhu", event.sctp_authentication_event);
+		}
+	}
+	#else /* SCTP_EVENTS */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_EVENTS");
+	#endif /* SCTP_EVENTS */
+#endif /*  OLD_SCTP_SOCKET_API */
+	
+	/* Set the v4 mapped addresses option */
+	#ifdef SCTP_I_WANT_MAPPED_V4_ADDR
+	if (!fd_g_config->cnf_flags.no_ip6) {
+		int v4mapped;
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(v4mapped);
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, &v4mapped, &sz)  );
+			if (sz != sizeof(v4mapped))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(v4mapped));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_I_WANT_MAPPED_V4_ADDR value : %s", v4mapped ? "true" : "false");
+		}
+
+		#ifndef SCTP_USE_MAPPED_ADDRESSES
+		v4mapped = 0;	/* We don't want v4 mapped addresses */
+		#else /* SCTP_USE_MAPPED_ADDRESSES */
+		v4mapped = 1;	/* but we may have to, otherwise the bind fails in some environments */
+		#endif /* SCTP_USE_MAPPED_ADDRESSES */
+		
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, &v4mapped, sizeof(v4mapped))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, &v4mapped, &sz)  );
+			fd_log_debug( "New SCTP_I_WANT_MAPPED_V4_ADDR value : %s", v4mapped ? "true" : "false");
+		}
+	} else {
+		TRACE_DEBUG(ANNOYING, "Skipping SCTP_I_WANT_MAPPED_V4_ADDR, since IPv6 disabled.");
+	}
+	#else /* SCTP_I_WANT_MAPPED_V4_ADDR */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_I_WANT_MAPPED_V4_ADDR");
+	#endif /* SCTP_I_WANT_MAPPED_V4_ADDR */
+	
+	/*
+	   SCTP_MAXSEG			max size of fragmented segments -- bound to PMTU
+	   SCTP_HMAC_IDENT		authentication algorithms
+	   SCTP_AUTH_ACTIVE_KEY		set the active key
+	   SCTP_DELAYED_SACK		control delayed acks
+	*/
+	
+	
+	/* Set the interleaving option */
+	#ifdef SCTP_FRAGMENT_INTERLEAVE
+	{
+		int interleave;
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(interleave);
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &interleave, &sz)  );
+			if (sz != sizeof(interleave))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(interleave));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_FRAGMENT_INTERLEAVE value : %d", interleave);
+		}
+
+		#if 0
+		interleave = 2;	/* Allow partial delivery on several streams at the same time, since we are stream-aware in our security modules */
+		#else /* 0 */
+		interleave = 1;	/* hmmm actually, we are not yet capable of handling this, and we don t need it. */
+		#endif /* 0 */
+		
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &interleave, sizeof(interleave))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &interleave, &sz)  );
+			fd_log_debug( "New SCTP_FRAGMENT_INTERLEAVE value : %d", interleave);
+		}
+	}
+	#else /* SCTP_FRAGMENT_INTERLEAVE */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_FRAGMENT_INTERLEAVE");
+	#endif /* SCTP_FRAGMENT_INTERLEAVE */
+	
+	/*
+	   SCTP_PARTIAL_DELIVERY_POINT	control partial delivery size
+	   SCTP_USE_EXT_RCVINFO	- DEPRECATED	use extended receive info structure (information about the next message if available)
+	 */
+	/* SCTP_AUTO_ASCONF is set by the postbind function */
+	/*
+	   SCTP_MAX_BURST		number of packets that can be burst emitted
+	   SCTP_CONTEXT			save a context information along with the association.
+	 */
+	 
+	/* SCTP_EXPLICIT_EOR: we assume implicit EOR in freeDiameter, so let's ensure this is known by the stack */
+	#ifdef SCTP_EXPLICIT_EOR
+	{
+		int bool;
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			sz = sizeof(bool);
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_EXPLICIT_EOR, &bool, &sz)  );
+			if (sz != sizeof(bool))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(bool));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_EXPLICIT_EOR value : %s", bool ? "true" : "false");
+		}
+
+		bool = 0;
+		
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_EXPLICIT_EOR, &bool, sizeof(bool))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_EXPLICIT_EOR, &bool, &sz)  );
+			fd_log_debug( "New SCTP_EXPLICIT_EOR value : %s", bool ? "true" : "false");
+		}
+	}
+	#else /* SCTP_EXPLICIT_EOR */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_EXPLICIT_EOR");
+	#endif /* SCTP_EXPLICIT_EOR */
+	
+	/*
+	   SCTP_REUSE_PORT		share one listening port with several sockets
+	*/
+	
+#ifndef OLD_SCTP_SOCKET_API
+	#ifdef SCTP_EVENT
+	{
+		/* Subscribe to the following events */
+		int events_I_want[] = {
+		#ifdef SCTP_ASSOC_CHANGE
+			/* SCTP_ASSOC_CHANGE, */
+		#endif 
+		#ifdef SCTP_PEER_ADDR_CHANGE
+			SCTP_PEER_ADDR_CHANGE,
+		#endif
+		#ifdef SCTP_REMOTE_ERROR
+			SCTP_REMOTE_ERROR,
+		#endif
+		#ifdef SCTP_SEND_FAILED_EVENT
+			SCTP_SEND_FAILED_EVENT,
+		#endif
+		#ifdef SCTP_SHUTDOWN_EVENT
+			SCTP_SHUTDOWN_EVENT,
+		#endif
+		#ifdef SCTP_ADAPTATION_INDICATION
+			/* SCTP_ADAPTATION_INDICATION, */
+		#endif
+		#ifdef SCTP_PARTIAL_DELIVERY_EVENT
+			/* SCTP_PARTIAL_DELIVERY_EVENT, */
+		#endif
+		#ifdef SCTP_AUTHENTICATION_EVENT
+			/* SCTP_AUTHENTICATION_EVENT, */
+		#endif
+		#ifdef SCTP_SENDER_DRY_EVENT
+			/* SCTP_SENDER_DRY_EVENT, */
+		#endif
+			0
+		};
+		int i;
+		
+		struct sctp_event event;
+		
+		for (i = 0; i < (sizeof(events_I_want) / sizeof(events_I_want[0]) - 1); i++) {
+			memset(&event, 0, sizeof(event));
+			event.se_type = events_I_want[i];
+			event.se_on = 1;
+			
+			/* Set the option to the socket */
+			CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)) );
+		}
+	}
+	#else /* SCTP_EVENT */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_EVENT");
+	#endif /* SCTP_EVENT */
+	
+	
+	#ifdef SCTP_RECVRCVINFO /* Replaces SCTP_SNDRCV */
+	{
+		int bool = 1;
+		
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_RECVRCVINFO, &bool, sizeof(bool))  );
+		
+	}
+	#else /* SCTP_RECVRCVINFO */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_RECVRCVINFO");
+	#endif /* SCTP_RECVRCVINFO */
+	
+	
+#endif /* OLD_SCTP_SOCKET_API */
+	
+	/* 
+		SCTP_RECVNXTINFO
+		
+		SCTP_DEFAULT_SNDINFO : send defaults
+		SCTP_DEFAULT_PRINFO  : default PR-SCTP
+	*/
+	
+
+	/* In case of no_ip4, force the v6only option */
+	#ifdef IPV6_V6ONLY
+	if (fd_g_config->cnf_flags.no_ip4) {
+		int opt = 1;
+		CHECK_SYS(setsockopt(sk, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)));
+	}
+	#endif /* IPV6_V6ONLY */
+	
+	return 0;
+}
+
+
+/* Post-binding socket options */
+static int fd_setsockopt_postbind(int sk, int bound_to_default)
+{
+	TRACE_ENTRY( "%d %d", sk, bound_to_default);
+	
+	CHECK_PARAMS( (sk > 0) );
+	
+	/* Set the ASCONF option */
+	#ifdef SCTP_AUTO_ASCONF
+	if (bound_to_default) {
+		int asconf;
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			socklen_t sz;
+
+			sz = sizeof(asconf);
+			/* Read socket defaults */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_AUTO_ASCONF, &asconf, &sz)  );
+			if (sz != sizeof(asconf))
+			{
+				TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(asconf));
+				return ENOTSUP;
+			}
+			fd_log_debug( "Def SCTP_AUTO_ASCONF value : %s", asconf ? "true" : "false");
+		}
+
+		asconf = 1;	/* allow automatic use of added or removed addresses in the association (for bound-all sockets) */
+		
+		/* Set the option to the socket */
+		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_AUTO_ASCONF, &asconf, sizeof(asconf))  );
+		
+		if (TRACE_BOOL(ANNOYING)) {
+			socklen_t sz = sizeof(asconf);
+			/* Check new values */
+			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_AUTO_ASCONF, &asconf, &sz)  );
+			fd_log_debug( "New SCTP_AUTO_ASCONF value : %s", asconf ? "true" : "false");
+		}
+	}
+	#else /* SCTP_AUTO_ASCONF */
+	TRACE_DEBUG(ANNOYING, "Skipping SCTP_AUTO_ASCONF");
+	#endif /* SCTP_AUTO_ASCONF */
+	
+	return 0;
+}
+
+/* Add addresses from a list to an array, with filter on the flags */
+static int add_addresses_from_list_mask(uint8_t ** array, size_t * size, int * addr_count, int target_family, uint16_t port, struct fd_list * list, uint32_t mask, uint32_t val)
+{
+	struct fd_list * li;
+	int to_add4 = 0;
+	int to_add6 = 0;
+	union {
+		uint8_t *buf;
+		sSA4	*sin;
+		sSA6	*sin6;
+	} ptr;
+	size_t sz;
+	
+	/* First, count the number of addresses to add */
+	for (li = list->next; li != list; li = li->next) {
+		struct fd_endpoint * ep = (struct fd_endpoint *) li;
+		
+		/* Do the flag match ? */
+		if ((val & mask) != (ep->flags & mask))
+			continue;
+		
+		if (ep->sa.sa_family == AF_INET) {
+			to_add4 ++;
+		} else {
+			to_add6 ++;
+		}
+	}
+	
+	if ((to_add4 + to_add6) == 0)
+		return 0; /* nothing to do */
+	
+	/* The size to add */
+	if (target_family == AF_INET) {
+		sz = to_add4 * sizeof(sSA4);
+	} else {
+		#ifndef SCTP_USE_MAPPED_ADDRESSES
+			sz = (to_add4 * sizeof(sSA4)) + (to_add6 * sizeof(sSA6));
+		#else /* SCTP_USE_MAPPED_ADDRESSES */
+			sz = (to_add4 + to_add6) * sizeof(sSA6);
+		#endif /* SCTP_USE_MAPPED_ADDRESSES */
+	}
+	
+	/* Now, (re)alloc the array to store the new addresses */
+	CHECK_MALLOC( *array = realloc(*array, *size + sz) );
+	
+	/* Finally, add the addresses */
+	for (li = list->next; li != list; li = li->next) {
+		struct fd_endpoint * ep = (struct fd_endpoint *) li;
+		
+		/* Skip v6 addresses for v4 socket */
+		if ((target_family == AF_INET) && (ep->sa.sa_family == AF_INET6))
+			continue;
+		
+		/* Are the flags matching ? */
+		if ((val & mask) != (ep->flags & mask))
+			continue;
+		
+		/* Size of the new SA we are adding (array may contain a mix of sockaddr_in and sockaddr_in6) */
+		#ifndef SCTP_USE_MAPPED_ADDRESSES
+		if (ep->sa.sa_family == AF_INET6)
+		#else /* SCTP_USE_MAPPED_ADDRESSES */
+		if (target_family == AF_INET6)
+		#endif /* SCTP_USE_MAPPED_ADDRESSES */
+			sz = sizeof(sSA6);
+		else
+			sz = sizeof(sSA4);
+		
+		/* Place where we add the new address */
+		ptr.buf = *array + *size; /* place of the new SA */
+		
+		/* Update other information */
+		*size += sz;
+		*addr_count += 1;
+		
+		/* And write the addr in the buffer */
+		if (sz == sizeof(sSA4)) {
+			memcpy(ptr.buf, &ep->sin, sz);
+			ptr.sin->sin_port = port;
+		} else {
+			if (ep->sa.sa_family == AF_INET) { /* We must map the address */ 
+				memset(ptr.buf, 0, sz);
+				ptr.sin6->sin6_family = AF_INET6;
+				IN6_ADDR_V4MAP( &ptr.sin6->sin6_addr.s6_addr, ep->sin.sin_addr.s_addr );
+			} else {
+				memcpy(ptr.sin6, &ep->sin6, sz);
+			}
+			ptr.sin6->sin6_port = port;
+		}
+	}
+	
+	return 0;
+}
+
+/* Create a socket server and bind it according to daemon s configuration */
+int fd_sctp_create_bind_server( int * sock, int family, struct fd_list * list, uint16_t port )
+{
+	int bind_default;
+	
+	TRACE_ENTRY("%p %i %p %hu", sock, family, list, port);
+	CHECK_PARAMS(sock);
+	
+	/* Create the socket */
+	CHECK_SYS( *sock = socket(family, SOCK_STREAM, IPPROTO_SCTP) );
+	
+	/* Set pre-binding socket options, including number of streams etc... */
+	CHECK_FCT( fd_setsockopt_prebind(*sock) );
+	
+	bind_default = (! list) || (FD_IS_LIST_EMPTY(list)) ;
+redo:
+	if ( bind_default ) {
+		/* Implicit endpoints : bind to default addresses */
+		union {
+			sSS  ss;
+			sSA  sa;
+			sSA4 sin;
+			sSA6 sin6;
+		} s;
+		
+		/* 0.0.0.0 and [::] are all zeros */
+		memset(&s, 0, sizeof(s));
+		
+		s.sa.sa_family = family;
+		
+		if (family == AF_INET)
+			s.sin.sin_port = htons(port);
+		else
+			s.sin6.sin6_port = htons(port);
+		
+		CHECK_SYS( bind(*sock, &s.sa, sSAlen(&s)) );
+		
+	} else {
+		/* Explicit endpoints to bind to from config */
+		
+		sSA * sar = NULL; /* array of addresses */
+		size_t sz = 0; /* size of the array */
+		int count = 0; /* number of sock addr in the array */
+		
+		/* Create the array of configured addresses */
+		CHECK_FCT( add_addresses_from_list_mask((void *)&sar, &sz, &count, family, htons(port), list, EP_FL_CONF, EP_FL_CONF) );
+		
+		if (!count) {
+			/* None of the addresses in the list came from configuration, we bind to default */
+			bind_default = 1;
+			goto redo;
+		}
+		
+		#if 0
+			union {
+				sSA	*sa;
+				uint8_t *buf;
+			} ptr;
+			int i;
+			ptr.sa = sar;
+			fd_log_debug("Calling sctp_bindx with the following address array:");
+			for (i = 0; i < count; i++) {
+				TRACE_sSA(FD_LOG_DEBUG, FULL, "    - ", ptr.sa, NI_NUMERICHOST | NI_NUMERICSERV, "" );
+				ptr.buf += (ptr.sa->sa_family == AF_INET) ? sizeof(sSA4) : sizeof(sSA6) ;
+			}
+		#endif
+		
+		/* Bind to this array */
+		CHECK_SYS(  sctp_bindx(*sock, sar, count, SCTP_BINDX_ADD_ADDR)  );
+		
+		/* We don't need sar anymore */
+		free(sar);
+	}
+	
+	/* Now, the server is bound, set remaining sockopt */
+	CHECK_FCT( fd_setsockopt_postbind(*sock, bind_default) );
+	
+	/* Debug: show all local listening addresses */
+	#if 0
+		sSA *sar;
+		union {
+			sSA	*sa;
+			uint8_t *buf;
+		} ptr;
+		int sz;
+		
+		CHECK_SYS(  sz = sctp_getladdrs(*sock, 0, &sar)  );
+		
+		fd_log_debug("SCTP server bound on :");
+		for (ptr.sa = sar; sz-- > 0; ptr.buf += (ptr.sa->sa_family == AF_INET) ? sizeof(sSA4) : sizeof(sSA6)) {
+			TRACE_sSA(FD_LOG_DEBUG, FULL, "    - ", ptr.sa, NI_NUMERICHOST | NI_NUMERICSERV, "" );
+		}
+		sctp_freeladdrs(sar);
+	#endif
+
+	return 0;
+}
+
+/* Allow clients connections on server sockets */
+int fd_sctp_listen( int sock )
+{
+	TRACE_ENTRY("%d", sock);
+	CHECK_SYS( listen(sock, 5) );
+	return 0;
+}
+
+/* Create a client socket and connect to remote server */
+int fd_sctp_client( int *sock, int no_ip6, uint16_t port, struct fd_list * list )
+{
+	int family;
+	union {
+		uint8_t *buf;
+		sSA	*sa;
+	} sar;
+	size_t size = 0;
+	int count = 0;
+	int ret;
+	
+	sar.buf = NULL;
+	
+	TRACE_ENTRY("%p %i %hu %p", sock, no_ip6, port, list);
+	CHECK_PARAMS( sock && list && (!FD_IS_LIST_EMPTY(list)) );
+	
+	if (no_ip6) {
+		family = AF_INET;
+	} else {
+		family = AF_INET6;
+	}
+	
+	/* Create the socket */
+	CHECK_SYS( *sock = socket(family, SOCK_STREAM, IPPROTO_SCTP) );
+	
+	/* Cleanup if we are cancelled */
+	pthread_cleanup_push(fd_cleanup_socket, sock);
+	
+	/* Set the socket options */
+	CHECK_FCT_DO( ret = fd_setsockopt_prebind(*sock), goto out );
+	
+	/* Create the array of addresses, add first the configured addresses, then the discovered, then the other ones */
+	CHECK_FCT_DO( ret = add_addresses_from_list_mask(&sar.buf, &size, &count, family, htons(port), list, EP_FL_CONF,              EP_FL_CONF	), goto out );
+	CHECK_FCT_DO( ret = add_addresses_from_list_mask(&sar.buf, &size, &count, family, htons(port), list, EP_FL_CONF | EP_FL_DISC, EP_FL_DISC	), goto out );
+	CHECK_FCT_DO( ret = add_addresses_from_list_mask(&sar.buf, &size, &count, family, htons(port), list, EP_FL_CONF | EP_FL_DISC, 0		), goto out );
+	
+	/* Try connecting */
+	LOG_A("Attempting SCTP connection (%d addresses attempted) ", count);
+		
+#if 0
+		/* Dump the SAs */
+		union {
+			uint8_t *buf;
+			sSA	*sa;
+			sSA4	*sin;
+			sSA6	*sin6;
+		} ptr;
+		int i;
+		ptr.buf = sar.buf;
+		for (i=0; i< count; i++) {
+			TRACE_sSA(FD_LOG_DEBUG, FULL, "  - ", ptr.sa, NI_NUMERICHOST | NI_NUMERICSERV, "" );
+			ptr.buf += (ptr.sa->sa_family == AF_INET) ? sizeof(sSA4) : sizeof(sSA6);
+		}
+#endif
+	
+	/* Bug in some Linux kernel, the sctp_connectx is not a cancellation point. To avoid blocking freeDiameter, we allow async cancel here */
+	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+#ifdef SCTP_CONNECTX_4_ARGS
+	ret = sctp_connectx(*sock, sar.sa, count, NULL);
+#else /* SCTP_CONNECTX_4_ARGS */
+	ret = sctp_connectx(*sock, sar.sa, count);
+#endif /* SCTP_CONNECTX_4_ARGS */
+	/* back to normal cancelation behabior */
+	pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
+	
+	if (ret < 0) {
+		ret = errno;
+		/* Some errors are expected, we log at different level */
+		LOG_A("sctp_connectx returned an error: %s", strerror(ret));
+		goto out;
+	}
+	
+	free(sar.buf); sar.buf = NULL;
+	
+	/* Set the remaining sockopts */
+	CHECK_FCT_DO( ret = fd_setsockopt_postbind(*sock, 1), 
+		{ 
+			CHECK_SYS_DO( shutdown(*sock, SHUT_RDWR), /* continue */ );
+		} );
+	
+out:
+	;
+	pthread_cleanup_pop(0);
+	
+	if (ret) {
+		if (*sock > 0) {
+			CHECK_SYS_DO( close(*sock), /* continue */ );
+			*sock = -1;
+		}
+		free(sar.buf);
+	}
+	return ret;
+}
+
+/* Retrieve streams information from a connected association -- optionally provide the primary address */
+int fd_sctp_get_str_info( int sock, uint16_t *in, uint16_t *out, sSS *primary )
+{
+	struct sctp_status status;
+	socklen_t sz = sizeof(status);
+	
+	TRACE_ENTRY("%d %p %p %p", sock, in, out, primary);
+	CHECK_PARAMS( (sock > 0) && in && out );
+	
+	/* Read the association parameters */
+	memset(&status, 0, sizeof(status));
+	CHECK_SYS(  getsockopt(sock, IPPROTO_SCTP, SCTP_STATUS, &status, &sz) );
+	if (sz != sizeof(status))
+	{
+		TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %zd", sz, sizeof(status));
+		return ENOTSUP;
+	}
+#if 0
+		char sa_buf[sSA_DUMP_STRLEN];
+		fd_sa_sdump_numeric(sa_buf, &status.sstat_primary.spinfo_address);
+		fd_log_debug( "SCTP_STATUS : sstat_state                  : %i" , status.sstat_state);
+		fd_log_debug( "              sstat_rwnd  	          : %u" , status.sstat_rwnd);
+		fd_log_debug( "		     sstat_unackdata	          : %hu", status.sstat_unackdata);
+		fd_log_debug( "		     sstat_penddata 	          : %hu", status.sstat_penddata);
+		fd_log_debug( "		     sstat_instrms  	          : %hu", status.sstat_instrms);
+		fd_log_debug( "		     sstat_outstrms 	          : %hu", status.sstat_outstrms);
+		fd_log_debug( "		     sstat_fragmentation_point    : %u" , status.sstat_fragmentation_point);
+		fd_log_debug( "		     sstat_primary.spinfo_address : %s" , sa_buf);
+		fd_log_debug( "		     sstat_primary.spinfo_state   : %d" , status.sstat_primary.spinfo_state);
+		fd_log_debug( "		     sstat_primary.spinfo_cwnd    : %u" , status.sstat_primary.spinfo_cwnd);
+		fd_log_debug( "		     sstat_primary.spinfo_srtt    : %u" , status.sstat_primary.spinfo_srtt);
+		fd_log_debug( "		     sstat_primary.spinfo_rto     : %u" , status.sstat_primary.spinfo_rto);
+		fd_log_debug( "		     sstat_primary.spinfo_mtu     : %u" , status.sstat_primary.spinfo_mtu);
+#endif /* 0 */
+	
+	*in = status.sstat_instrms;
+	*out = status.sstat_outstrms;
+	
+	if (primary)
+		memcpy(primary, &status.sstat_primary.spinfo_address, sizeof(sSS));
+	
+	return 0;
+}
+
+/* Get the list of remote endpoints of the socket */
+int fd_sctp_get_remote_ep(int sock, struct fd_list * list)
+{
+	union {
+		sSA	*sa;
+		uint8_t	*buf;
+	} ptr;
+	
+	sSA * data = NULL;
+	int count;
+	
+	TRACE_ENTRY("%d %p", sock, list);
+	CHECK_PARAMS(list);
+	
+	/* Read the list on the socket */
+	CHECK_SYS( count = sctp_getpaddrs(sock, 0, &data)  );
+	ptr.sa = data;
+	
+	while (count) {
+		socklen_t sl;
+		switch (ptr.sa->sa_family) {
+			case AF_INET:	sl = sizeof(sSA4); break;
+			case AF_INET6:	sl = sizeof(sSA6); break;
+			default:
+				TRACE_DEBUG(INFO, "Unknown address family returned in sctp_getpaddrs: %d, skip", ptr.sa->sa_family);
+				/* There is a bug in current Linux kernel: http://www.spinics.net/lists/linux-sctp/msg00760.html */
+				goto stop;
+		}
+				
+		CHECK_FCT( fd_ep_add_merge( list, ptr.sa, sl, EP_FL_LL ) );
+		ptr.buf += sl;
+		count --;
+	}
+stop:	
+	/* Free the list */
+	sctp_freepaddrs(data);
+	
+	/* Now get the primary address, the add function will take care of merging with existing entry */
+	{
+		 
+		struct sctp_status status;
+		socklen_t sz = sizeof(status);
+		int ret;
+		
+		memset(&status, 0, sizeof(status));
+		/* Attempt to use SCTP_STATUS message to retrieve the primary address */
+		CHECK_SYS_DO( ret = getsockopt(sock, IPPROTO_SCTP, SCTP_STATUS, &status, &sz), /* continue */);
+		if (sz != sizeof(status))
+			ret = -1;
+		sz = sizeof(sSS);
+		if (ret < 0)
+		{
+			/* Fallback to getsockname -- not recommended by draft-ietf-tsvwg-sctpsocket-19#section-7.4 */
+			CHECK_SYS(getpeername(sock, (sSA *)&status.sstat_primary.spinfo_address, &sz));
+		}
+			
+		CHECK_FCT( fd_ep_add_merge( list, (sSA *)&status.sstat_primary.spinfo_address, sz, EP_FL_PRIMARY ) );
+	}
+	
+	/* Done! */
+	return 0;
+}
+
+/* Send a vector over a specified stream */
+ssize_t fd_sctp_sendstrv(struct cnxctx * conn, uint16_t strid, const struct iovec *iov, int iovcnt)
+{
+	struct msghdr mhdr;
+	struct cmsghdr 		*hdr;
+#ifdef OLD_SCTP_SOCKET_API
+	struct sctp_sndrcvinfo	*sndrcv;
+	uint8_t anci[CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))];
+#else /* OLD_SCTP_SOCKET_API */
+	struct sctp_sndinfo 	*sndinf;
+	uint8_t anci[CMSG_SPACE(sizeof(struct sctp_sndinfo))];	
+#endif /* OLD_SCTP_SOCKET_API */
+	ssize_t ret;
+	struct timespec ts, now;
+	
+	TRACE_ENTRY("%p %hu %p %d", conn, strid, iov, iovcnt);
+	CHECK_PARAMS_DO(conn && iov && iovcnt, { errno = EINVAL; return -1; } );
+	CHECK_SYS_DO(  clock_gettime(CLOCK_REALTIME, &ts), return -1 );
+	
+	memset(&mhdr, 0, sizeof(mhdr));
+	memset(&anci, 0, sizeof(anci));
+	
+	/* Anciliary data: specify SCTP stream */
+	hdr = (struct cmsghdr *)anci;
+	hdr->cmsg_len   = sizeof(anci);
+	hdr->cmsg_level = IPPROTO_SCTP;
+#ifdef OLD_SCTP_SOCKET_API
+	hdr->cmsg_type  = SCTP_SNDRCV;
+	sndrcv = (struct sctp_sndrcvinfo *)CMSG_DATA(hdr);
+	sndrcv->sinfo_stream = strid;
+#else /* OLD_SCTP_SOCKET_API */
+	hdr->cmsg_type  = SCTP_SNDINFO;
+	sndinf = (struct sctp_sndinfo *)CMSG_DATA(hdr);
+	sndinf->snd_sid = strid;
+#endif /* OLD_SCTP_SOCKET_API */
+	/* note : we could store other data also, for example in .sinfo_ppid for remote peer or in .sinfo_context for errors. */
+	
+	/* We don't use mhdr.msg_name here; it could be used to specify an address different from the primary */
+	
+	mhdr.msg_iov    = (struct iovec *)iov;
+	mhdr.msg_iovlen = iovcnt;
+	
+	mhdr.msg_control    = anci;
+	mhdr.msg_controllen = sizeof(anci);
+	
+	TRACE_DEBUG(FULL, "Sending %d chunks of data (first:%zdb) on stream %hu of socket %d", iovcnt, iov[0].iov_len, strid, conn->cc_socket);
+again:	
+	ret = sendmsg(conn->cc_socket, &mhdr, 0);
+	/* Handle special case of timeout */
+	if ((ret < 0) && ((errno == EAGAIN) || (errno == EINTR))) {
+		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, ); /* for tracing error only */
+	
+	return ret;
+}
+
+/* Receive the next data from the socket, or next notification */
+int fd_sctp_recvmeta(struct cnxctx * conn, uint16_t * strid, uint8_t ** buf, size_t * len, int *event)
+{
+	ssize_t 		 ret = 0;
+	struct msghdr 		 mhdr;
+	char   			 ancidata[ CMSG_BUF_LEN ];
+	struct iovec 		 iov;
+	uint8_t			*data = NULL;
+	size_t 			 bufsz = 0, datasize = 0;
+	size_t			 mempagesz = sysconf(_SC_PAGESIZE); /* We alloc buffer by memory pages for efficiency */
+	int 			 timedout = 0;
+	
+	TRACE_ENTRY("%p %p %p %p %p", conn, strid, buf, len, event);
+	CHECK_PARAMS( conn && buf && len && event );
+	
+	/* Cleanup out parameters */
+	*buf = NULL;
+	*len = 0;
+	*event = 0;
+	
+	/* Prepare header for receiving message */
+	memset(&mhdr, 0, sizeof(mhdr));
+	mhdr.msg_iov    = &iov;
+	mhdr.msg_iovlen = 1;
+	mhdr.msg_control    = &ancidata;
+	mhdr.msg_controllen = sizeof(ancidata);
+	
+next_message:
+	datasize = 0;
+	
+	/* We will loop while all data is not received. */
+incomplete:
+	while (datasize >= bufsz ) {
+		/* The buffer is full, enlarge it */
+		bufsz += mempagesz;
+		CHECK_MALLOC( data = realloc(data, bufsz ) );
+	}
+	/* the new data will be received following the preceding */
+	memset(&iov,  0, sizeof(iov));
+	iov.iov_base = data + datasize ;
+	iov.iov_len  = bufsz - datasize;
+
+	/* Receive data from the socket */
+again:
+	pthread_cleanup_push(free, data);
+	ret = recvmsg(conn->cc_socket, &mhdr, 0);
+	pthread_testcancel();
+	pthread_cleanup_pop(0);
+	
+	/* First, handle timeouts (same as fd_cnx_s_recv) */
+	if ((ret < 0) && ((errno == EAGAIN) || (errno == EINTR))) {
+		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;
+		}
+		/* fallback to normal handling */
+	}
+	
+	/* Handle errors */
+	if (ret <= 0) { /* Socket timedout, closed, or an error occurred */
+		CHECK_SYS_DO(ret, /* to log in case of error */);
+		free(data);
+		*event = FDEVP_CNX_ERROR;
+		return 0;
+	}
+	
+	/* Update the size of data we received */
+	datasize += ret;
+
+	/* SCTP provides an indication when we received a full record; loop if it is not the case */
+	if ( ! (mhdr.msg_flags & MSG_EOR) ) {
+		goto incomplete;
+	}
+	
+	/* Handle the case where the data received is a notification */
+	if (mhdr.msg_flags & MSG_NOTIFICATION) {
+		union sctp_notification * notif = (union sctp_notification *) data;
+		
+		TRACE_DEBUG(FULL, "Received %zdb data of notification on socket %d", datasize, conn->cc_socket);
+	
+		switch (notif->sn_header.sn_type) {
+			
+			case SCTP_ASSOC_CHANGE: /* We should not receive these as we did not subscribe for it */
+				TRACE_DEBUG(FULL, "Received SCTP_ASSOC_CHANGE notification");
+				TRACE_DEBUG(ANNOYING, "    state : %hu", notif->sn_assoc_change.sac_state);
+				TRACE_DEBUG(ANNOYING, "    error : %hu", notif->sn_assoc_change.sac_error);
+				TRACE_DEBUG(ANNOYING, "    instr : %hu", notif->sn_assoc_change.sac_inbound_streams);
+				TRACE_DEBUG(ANNOYING, "   outstr : %hu", notif->sn_assoc_change.sac_outbound_streams);
+				
+				*event = FDEVP_CNX_EP_CHANGE;
+				break;
+	
+			case SCTP_PEER_ADDR_CHANGE:
+				TRACE_DEBUG(FULL, "Received SCTP_PEER_ADDR_CHANGE notification");
+				/* TRACE_sSA(FD_LOG_DEBUG, ANNOYING, "    intf_change : ", &(notif->sn_paddr_change.spc_aaddr), NI_NUMERICHOST | NI_NUMERICSERV, "" ); */
+				TRACE_DEBUG(ANNOYING, "          state : %d", notif->sn_paddr_change.spc_state);
+				TRACE_DEBUG(ANNOYING, "          error : %d", notif->sn_paddr_change.spc_error);
+				
+				*event = FDEVP_CNX_EP_CHANGE;
+				break;
+	
+			case SCTP_REMOTE_ERROR:
+				TRACE_DEBUG(FULL, "Received SCTP_REMOTE_ERROR notification");
+				TRACE_DEBUG(ANNOYING, "    err : %hu", ntohs(notif->sn_remote_error.sre_error));
+				TRACE_DEBUG(ANNOYING, "    len : %hu", ntohs(notif->sn_remote_error.sre_length));
+				
+				*event = FDEVP_CNX_ERROR;
+				break;
+	
+#ifdef OLD_SCTP_SOCKET_API
+			case SCTP_SEND_FAILED:
+				TRACE_DEBUG(FULL, "Received SCTP_SEND_FAILED notification");
+				TRACE_DEBUG(ANNOYING, "    len : %hu", notif->sn_send_failed.ssf_length);
+				TRACE_DEBUG(ANNOYING, "    err : %d",  notif->sn_send_failed.ssf_error);
+				
+				*event = FDEVP_CNX_ERROR;
+				break;
+#else /*  OLD_SCTP_SOCKET_API */
+			case SCTP_SEND_FAILED_EVENT:
+				TRACE_DEBUG(FULL, "Received SCTP_SEND_FAILED_EVENT notification");
+				*event = FDEVP_CNX_ERROR;
+				break;
+#endif	/*  OLD_SCTP_SOCKET_API */		
+			case SCTP_SHUTDOWN_EVENT:
+				TRACE_DEBUG(FULL, "Received SCTP_SHUTDOWN_EVENT notification");
+				
+				*event = FDEVP_CNX_SHUTDOWN;
+				break;
+				
+#ifndef OLD_SCTP_SOCKET_API
+			case SCTP_NOTIFICATIONS_STOPPED_EVENT:
+				TRACE_DEBUG(INFO, "Received SCTP_NOTIFICATIONS_STOPPED_EVENT notification, marking the association in error state");
+				*event = FDEVP_CNX_ERROR;
+				break;
+#endif	/*  OLD_SCTP_SOCKET_API */		
+			
+			default:	
+				TRACE_DEBUG(FULL, "Received unknown notification %d, ignored", notif->sn_header.sn_type);
+				goto next_message;
+		}
+		
+		free(data);
+		return 0;
+	}
+	
+	/* From this point, we have received a message */
+	*event = FDEVP_CNX_MSG_RECV;
+	*buf = data;
+	*len = datasize;
+	
+	if (strid) {
+		struct cmsghdr 		*hdr;
+#ifdef OLD_SCTP_SOCKET_API
+		struct sctp_sndrcvinfo	*sndrcv;
+#else /*  OLD_SCTP_SOCKET_API */
+		struct sctp_rcvinfo	*rcvinf;
+#endif /*  OLD_SCTP_SOCKET_API */
+		
+		/* Handle the anciliary data */
+		for (hdr = CMSG_FIRSTHDR(&mhdr); hdr; hdr = CMSG_NXTHDR(&mhdr, hdr)) {
+
+			/* We deal only with anciliary data at SCTP level */
+			if (hdr->cmsg_level != IPPROTO_SCTP) {
+				TRACE_DEBUG(FULL, "Received some anciliary data at level %d, skipped", hdr->cmsg_level);
+				continue;
+			}
+			
+#ifdef OLD_SCTP_SOCKET_API
+			/* Also only interested in SCTP_SNDRCV message for the moment */
+			if (hdr->cmsg_type != SCTP_SNDRCV) {
+				TRACE_DEBUG(FULL, "Anciliary block IPPROTO_SCTP / %d, skipped", hdr->cmsg_type);
+				continue;
+			}
+			
+			sndrcv = (struct sctp_sndrcvinfo *) CMSG_DATA(hdr);
+			if (TRACE_BOOL(ANNOYING)) {
+				fd_log_debug( "Anciliary block IPPROTO_SCTP / SCTP_SNDRCV");
+				fd_log_debug( "    sinfo_stream    : %hu", sndrcv->sinfo_stream);
+				fd_log_debug( "    sinfo_ssn       : %hu", sndrcv->sinfo_ssn);
+				fd_log_debug( "    sinfo_flags     : %hu", sndrcv->sinfo_flags);
+				/* fd_log_debug( "    sinfo_pr_policy : %hu", sndrcv->sinfo_pr_policy); */
+				fd_log_debug( "    sinfo_ppid      : %u" , sndrcv->sinfo_ppid);
+				fd_log_debug( "    sinfo_context   : %u" , sndrcv->sinfo_context);
+				/* fd_log_debug( "    sinfo_pr_value  : %u" , sndrcv->sinfo_pr_value); */
+				fd_log_debug( "    sinfo_tsn       : %u" , sndrcv->sinfo_tsn);
+				fd_log_debug( "    sinfo_cumtsn    : %u" , sndrcv->sinfo_cumtsn);
+			}
+
+			*strid = sndrcv->sinfo_stream;
+#else /*  OLD_SCTP_SOCKET_API */
+			/* Also only interested in SCTP_RCVINFO message for the moment */
+			if (hdr->cmsg_type != SCTP_RCVINFO) {
+				TRACE_DEBUG(FULL, "Anciliary block IPPROTO_SCTP / %d, skipped", hdr->cmsg_type);
+				continue;
+			}
+			
+			rcvinf = (struct sctp_rcvinfo *) CMSG_DATA(hdr);
+
+			*strid = rcvinf->rcv_sid;
+#endif /*  OLD_SCTP_SOCKET_API */
+			
+			
+		}
+		TRACE_DEBUG(FULL, "Received %zdb data on socket %d, stream %hu", datasize, conn->cc_socket, *strid);
+	} else {
+		TRACE_DEBUG(FULL, "Received %zdb data on socket %d (stream ignored)", datasize, conn->cc_socket);
+	}
+	
+	return 0;
+}