blob: c80a4978003caa21849d288d0a9ebeab396458d1 [file] [log] [blame]
/*********************************************************************************************************
* 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;
}