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

#if 1 /* Open5GS : for SCTP patch */
#include "sctp-internal.h"
#endif

/* 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)) || (!defined(SCTP_SEND_FAILED_EVENT)) || (!defined(SCTP_NOTIFICATIONS_STOPPED_EVENT)))
# 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 */


DECLARE_FD_DUMP_PROTOTYPE(fd_sa_dump_array, sSA * saddrs, int saddrs_count)
{
	union {
		sSA	*sa;
		uint8_t *buf;
	} ptr;
	int i;
	int salen;

	FD_DUMP_HANDLE_OFFSET();

	ptr.sa = saddrs;
	for (i = 0; i < saddrs_count; i++) {
		salen = sSAlen(ptr.sa);
		if (salen == 0) {
			LOG_E("fd_sa_dump_array: Unknown sockaddr family");
			break;
		}
		if (i > 0) {
			CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " "), return NULL);
		}
		CHECK_MALLOC_DO( fd_sa_dump( FD_DUMP_STD_PARAMS, ptr.sa, NI_NUMERICHOST | NI_NUMERICSERV), return NULL);
		ptr.buf += salen;
	}
	return *buf;
}

/* 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(  sctp_opt_info(sk, 0, 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(  sctp_opt_info(sk, 0, 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(  sctp_opt_info(sk, 0, 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(  sctp_opt_info(sk, 0, 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 1 /* Open5GS : workaround for SCTP  */
		if (determine_sctp_sockopt_paddrparams_size() < 0) {
			LOG_E("Cannot determine SCTP_EVENTS socket option size");
			return -1;
		}
#endif
		
		if (TRACE_BOOL(ANNOYING)) {
			sz = sizeof(parms);

			/* Read socket defaults */
			CHECK_SYS(  sctp_opt_info(sk, 0, SCTP_PEER_ADDR_PARAMS, &parms, &sz)  );
#if 0 /* Open5GS : workaround for SCTP */
			if (sz != sizeof(parms))
#else
			if (sz != sizeof(parms) && sz != sctp_sockopt_paddrparams_size)
#endif
			{
				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 */
#if 0 /* Open5GS : workaround for SCTP  */
		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &parms, sizeof(parms)) );
#else
        CHECK_SYS(  sctp_setsockopt_paddrparams_workaround(sk, &parms) );
#endif
		
		if (TRACE_BOOL(ANNOYING)) {
			/* Check new values */
			CHECK_SYS(  sctp_opt_info(sk, 0, 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 */
#if 0 /* Open5GS : workaround for SCTP */
		CHECK_SYS(  setsockopt(sk, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(event)) );
#else
        CHECK_SYS(  sctp_setsockopt_event_subscribe_workaround(sk, &event) );
#endif
		
		if (TRACE_BOOL(ANNOYING)) {
			sz = sizeof(event);
			CHECK_SYS(  getsockopt(sk, IPPROTO_SCTP, SCTP_EVENTS, &event, &sz) );
#if 0 /* Open5GS : workaround for SCTP */
			if (sz != sizeof(event))
#else
			if (sz != sizeof(event) && sz != sctp_sockopt_event_subscribe_size)
#endif
			{
				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;
		}

		/* Debug: show bound addresses */
		{
			char * buf = NULL;
			size_t len = 0;
			CHECK_MALLOC_DO( fd_sa_dump_array( &buf, &len, 0, sar, count), );
			LOG_D("SCTP server binding local addresses: %s", buf);
			free(buf);
		}

		/* 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 */
	{
		sSA *sar = NULL;
		int sz = 0;
		char * buf = NULL;
		size_t len = 0;

		CHECK_SYS(  sz = sctp_getladdrs(*sock, 0, &sar)  );

		CHECK_MALLOC_DO( fd_sa_dump_array( &buf, &len, 0, sar, sz), );
		LOG_D("SCTP server locally bound addresses: %s", buf);
		sctp_freeladdrs(sar);
		free(buf);
	}

	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, struct fd_list * src_list )
{
	int family;
	union {
		uint8_t *buf;
		sSA	*sa;
	} sar;
	size_t size = 0;
	int count = 0;
	int ret;
	int bind_default = 1;	/* enable ASCONF in postbind */
	
	sar.buf = NULL;
	
	TRACE_ENTRY("%p %i %hu %p %p", sock, no_ip6, port, list, src_list);
	CHECK_PARAMS( sock && list && (!FD_IS_LIST_EMPTY(list)) );
	CHECK_PARAMS( !src_list || (src_list && (!FD_IS_LIST_EMPTY(src_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 );

	/* Bind to explicit source addresses if requested */
	if (src_list && !FD_IS_LIST_EMPTY(src_list)) {
		sSA * bindsar = NULL; /* array of addresses */
		size_t sz = 0; /* size of the array */
		int sarcount = 0; /* number of sock addr in the array */

		/* Create the array of configured addresses */
		CHECK_FCT_DO( ret = add_addresses_from_list_mask((void *)&bindsar, &sz, &sarcount, family, 0, src_list, EP_FL_CONF, EP_FL_CONF), goto out );

		if (sarcount) {
			char * buf = NULL;
			size_t len = 0;
			CHECK_MALLOC_DO( fd_sa_dump_array( &buf, &len, 0, bindsar, sarcount), goto out );
			LOG_A("SCTP client binding local addresses: %s", buf);
			free(buf);

			CHECK_SYS_DO( ret = sctp_bindx(*sock, bindsar, sarcount, SCTP_BINDX_ADD_ADDR), goto out );
		}

		/* Disable ASCONF option in postbind */
		bind_default = 0;

		/* We don't need bindsar anymore */
		free(bindsar);
	}

	/* 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 */
	{
		char * buf = NULL;
		size_t len = 0;
		CHECK_MALLOC_DO( fd_sa_dump_array( &buf, &len, 0, sar.sa, count), goto out );
		LOG_A("SCTP client connecting to addresses: %s", buf);
		free(buf);
	}

	/* 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, bind_default),
		{ 
			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, (sSA *)&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;
	sndrcv->sinfo_ppid = htonl(46);
#else /* OLD_SCTP_SOCKET_API */
	hdr->cmsg_type  = SCTP_SNDINFO;
	sndinf = (struct sctp_sndinfo *)CMSG_DATA(hdr);
	sndinf->snd_sid = strid;
	sndinf->snd_ppid = htonl(46);
#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;
		char sa_buf[sSA_DUMP_STRLEN];
		
		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");
				fd_sa_sdump_numeric(sa_buf, (sSA *)&(notif->sn_paddr_change.spc_aaddr));
				TRACE_DEBUG(ANNOYING, "    intf_change : %s", sa_buf);
				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
#ifdef SCTP_NOTIFICATIONS_STOPPED_EVENT
			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	/*  SCTP_NOTIFICATIONS_STOPPED_EVENT  */
#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;
}
