forked from acouzens/open5gs
2046 lines
65 KiB
C
2046 lines
65 KiB
C
/*********************************************************************************************************
|
|
* Software License Agreement (BSD License) *
|
|
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
|
* *
|
|
* Copyright (c) 2015, WIDE Project and NICT *
|
|
* All rights reserved. *
|
|
* *
|
|
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
|
* permitted provided that the following conditions are met: *
|
|
* *
|
|
* * Redistributions of source code must retain the above *
|
|
* copyright notice, this list of conditions and the *
|
|
* following disclaimer. *
|
|
* *
|
|
* * Redistributions in binary form must reproduce the above *
|
|
* copyright notice, this list of conditions and the *
|
|
* following disclaimer in the documentation and/or other *
|
|
* materials provided with the distribution. *
|
|
* *
|
|
* * Neither the name of the WIDE Project or NICT nor the *
|
|
* names of its contributors may be used to endorse or *
|
|
* promote products derived from this software without *
|
|
* specific prior written permission of WIDE Project and *
|
|
* NICT. *
|
|
* *
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
|
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
|
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
|
*********************************************************************************************************/
|
|
|
|
#include "fdcore-internal.h"
|
|
#include "cnxctx.h"
|
|
|
|
#include <net/if.h>
|
|
#include <ifaddrs.h> /* for getifaddrs */
|
|
#include <sys/uio.h> /* writev */
|
|
|
|
/* The maximum size of Diameter message we accept to receive (<= 2^24) to avoid too big mallocs in case of trashed headers */
|
|
#ifndef DIAMETER_MSG_SIZE_MAX
|
|
#define DIAMETER_MSG_SIZE_MAX 65535 /* in bytes */
|
|
#endif /* DIAMETER_MSG_SIZE_MAX */
|
|
|
|
|
|
/* Connections contexts (cnxctx) in freeDiameter are wrappers around the sockets and TLS operations .
|
|
* They are used to hide the details of the processing to the higher layers of the daemon.
|
|
* They are always oriented on connections (TCP or SCTP), connectionless modes (UDP or SCTP) are not supported.
|
|
*/
|
|
|
|
/* Lifetime of a cnxctx object:
|
|
* 1) Creation
|
|
* a) a server socket:
|
|
* - create the object with fd_cnx_serv_tcp or fd_cnx_serv_sctp
|
|
* - start listening incoming connections: fd_cnx_serv_listen
|
|
* - accept new clients with fd_cnx_serv_accept.
|
|
* b) a client socket:
|
|
* - connect to a remote server with fd_cnx_cli_connect
|
|
*
|
|
* 2) Initialization
|
|
* - if TLS is started first, call fd_cnx_handshake
|
|
* - otherwise to receive clear messages, call fd_cnx_start_clear. fd_cnx_handshake can be called later.
|
|
*
|
|
* 3) Usage
|
|
* - fd_cnx_receive, fd_cnx_send : exchange messages on this connection (send is synchronous, receive is not, but blocking).
|
|
* - fd_cnx_recv_setaltfifo : when a message is received, the event is sent to an external fifo list. fd_cnx_receive does not work when the alt_fifo is set.
|
|
* - fd_cnx_getid : retrieve a descriptive string for the connection (for debug)
|
|
* - fd_cnx_getremoteid : identification of the remote peer (IP address or fqdn)
|
|
* - fd_cnx_getcred : get the remote peer TLS credentials, after handshake
|
|
*
|
|
* 4) End
|
|
* - fd_cnx_destroy
|
|
*/
|
|
|
|
/*******************************************/
|
|
/* Creation of a connection object */
|
|
/*******************************************/
|
|
|
|
/* Initialize a context structure */
|
|
static struct cnxctx * fd_cnx_init(int full)
|
|
{
|
|
struct cnxctx * conn = NULL;
|
|
|
|
TRACE_ENTRY("%d", full);
|
|
|
|
CHECK_MALLOC_DO( conn = malloc(sizeof(struct cnxctx)), return NULL );
|
|
memset(conn, 0, sizeof(struct cnxctx));
|
|
|
|
if (full) {
|
|
CHECK_FCT_DO( fd_fifo_new ( &conn->cc_incoming, 5 ), return NULL );
|
|
}
|
|
|
|
return conn;
|
|
}
|
|
|
|
#define CC_ID_HDR "{----} "
|
|
|
|
/* Create and bind a server socket to the given endpoint and port */
|
|
struct cnxctx * fd_cnx_serv_tcp(uint16_t port, int family, struct fd_endpoint * ep)
|
|
{
|
|
struct cnxctx * cnx = NULL;
|
|
sSS dummy;
|
|
sSA * sa = (sSA *) &dummy;
|
|
|
|
TRACE_ENTRY("%hu %d %p", port, family, ep);
|
|
|
|
CHECK_PARAMS_DO( port, return NULL );
|
|
CHECK_PARAMS_DO( ep || family, return NULL );
|
|
CHECK_PARAMS_DO( (! family) || (family == AF_INET) || (family == AF_INET6), return NULL );
|
|
CHECK_PARAMS_DO( (! ep) || (ep->ss.ss_family == AF_INET) || (ep->ss.ss_family == AF_INET6), return NULL );
|
|
CHECK_PARAMS_DO( (! ep) || (!family) || (ep->ss.ss_family == family), return NULL );
|
|
|
|
/* The connection object */
|
|
CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL );
|
|
|
|
/* Prepare the socket address information */
|
|
if (ep) {
|
|
memcpy(sa, &ep->ss, sizeof(sSS));
|
|
} else {
|
|
memset(&dummy, 0, sizeof(dummy));
|
|
sa->sa_family = family;
|
|
}
|
|
if (sa->sa_family == AF_INET) {
|
|
((sSA4 *)sa)->sin_port = htons(port);
|
|
cnx->cc_family = AF_INET;
|
|
} else {
|
|
((sSA6 *)sa)->sin6_port = htons(port);
|
|
cnx->cc_family = AF_INET6;
|
|
}
|
|
|
|
/* Create the socket */
|
|
CHECK_FCT_DO( fd_tcp_create_bind_server( &cnx->cc_socket, sa, sSAlen(sa) ), goto error );
|
|
|
|
/* Generate the name for the connection object */
|
|
{
|
|
char addrbuf[INET6_ADDRSTRLEN];
|
|
int rc;
|
|
rc = getnameinfo(sa, sSAlen(sa), addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
|
|
if (rc)
|
|
snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc));
|
|
snprintf(cnx->cc_id, sizeof(cnx->cc_id), CC_ID_HDR "TCP srv [%s]:%hu (%d)", addrbuf, port, cnx->cc_socket);
|
|
}
|
|
|
|
cnx->cc_proto = IPPROTO_TCP;
|
|
|
|
return cnx;
|
|
|
|
error:
|
|
fd_cnx_destroy(cnx);
|
|
return NULL;
|
|
}
|
|
|
|
/* Same function for SCTP, with a list of local endpoints to bind to */
|
|
struct cnxctx * fd_cnx_serv_sctp(uint16_t port, struct fd_list * ep_list)
|
|
{
|
|
#ifdef DISABLE_SCTP
|
|
TRACE_DEBUG(INFO, "This function should never been called when SCTP is disabled...");
|
|
ASSERT(0);
|
|
CHECK_FCT_DO( ENOTSUP, );
|
|
return NULL;
|
|
#else /* DISABLE_SCTP */
|
|
struct cnxctx * cnx = NULL;
|
|
|
|
TRACE_ENTRY("%hu %p", port, ep_list);
|
|
|
|
CHECK_PARAMS_DO( port, return NULL );
|
|
|
|
/* The connection object */
|
|
CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL );
|
|
|
|
if (fd_g_config->cnf_flags.no_ip6) {
|
|
cnx->cc_family = AF_INET;
|
|
} else {
|
|
cnx->cc_family = AF_INET6; /* can create socket for both IP and IPv6 */
|
|
}
|
|
|
|
/* Create the socket */
|
|
CHECK_FCT_DO( fd_sctp_create_bind_server( &cnx->cc_socket, cnx->cc_family, ep_list, port ), goto error );
|
|
|
|
/* Generate the name for the connection object */
|
|
snprintf(cnx->cc_id, sizeof(cnx->cc_id), CC_ID_HDR "SCTP srv :%hu (%d)", port, cnx->cc_socket);
|
|
|
|
cnx->cc_proto = IPPROTO_SCTP;
|
|
|
|
return cnx;
|
|
|
|
error:
|
|
fd_cnx_destroy(cnx);
|
|
return NULL;
|
|
#endif /* DISABLE_SCTP */
|
|
}
|
|
|
|
/* Allow clients to connect on the server socket */
|
|
int fd_cnx_serv_listen(struct cnxctx * conn)
|
|
{
|
|
CHECK_PARAMS( conn );
|
|
|
|
switch (conn->cc_proto) {
|
|
case IPPROTO_TCP:
|
|
CHECK_FCT(fd_tcp_listen(conn->cc_socket));
|
|
break;
|
|
|
|
#ifndef DISABLE_SCTP
|
|
case IPPROTO_SCTP:
|
|
CHECK_FCT(fd_sctp_listen(conn->cc_socket));
|
|
break;
|
|
#endif /* DISABLE_SCTP */
|
|
|
|
default:
|
|
CHECK_PARAMS(0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Accept a client (blocking until a new client connects) -- cancelable */
|
|
struct cnxctx * fd_cnx_serv_accept(struct cnxctx * serv)
|
|
{
|
|
struct cnxctx * cli = NULL;
|
|
sSS ss;
|
|
socklen_t ss_len = sizeof(ss);
|
|
int cli_sock = 0;
|
|
|
|
TRACE_ENTRY("%p", serv);
|
|
CHECK_PARAMS_DO(serv, return NULL);
|
|
|
|
/* Accept the new connection -- this is blocking until new client enters or until cancellation */
|
|
CHECK_SYS_DO( cli_sock = accept(serv->cc_socket, (sSA *)&ss, &ss_len), return NULL );
|
|
|
|
CHECK_MALLOC_DO( cli = fd_cnx_init(1), { shutdown(cli_sock, SHUT_RDWR); close(cli_sock); return NULL; } );
|
|
cli->cc_socket = cli_sock;
|
|
cli->cc_family = serv->cc_family;
|
|
cli->cc_proto = serv->cc_proto;
|
|
|
|
/* Set the timeout */
|
|
fd_cnx_s_setto(cli->cc_socket);
|
|
|
|
/* Generate the name for the connection object */
|
|
{
|
|
char addrbuf[INET6_ADDRSTRLEN];
|
|
char portbuf[10];
|
|
int rc;
|
|
|
|
rc = getnameinfo((sSA *)&ss, ss_len, addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), NI_NUMERICHOST | NI_NUMERICSERV);
|
|
if (rc) {
|
|
snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc));
|
|
portbuf[0] = '\0';
|
|
}
|
|
|
|
/* Numeric values for debug... */
|
|
snprintf(cli->cc_id, sizeof(cli->cc_id), CC_ID_HDR "%s from [%s]:%s (%d<-%d)",
|
|
IPPROTO_NAME(cli->cc_proto), addrbuf, portbuf, serv->cc_socket, cli->cc_socket);
|
|
|
|
|
|
/* ...Name for log messages */
|
|
rc = getnameinfo((sSA *)&ss, ss_len, cli->cc_remid, sizeof(cli->cc_remid), NULL, 0, 0);
|
|
if (rc)
|
|
snprintf(cli->cc_remid, sizeof(cli->cc_remid), "[err:%s]", gai_strerror(rc));
|
|
}
|
|
|
|
LOG_D("Incoming connection: '%s' <- '%s' {%s}", fd_cnx_getid(serv), cli->cc_remid, cli->cc_id);
|
|
|
|
#ifndef DISABLE_SCTP
|
|
/* SCTP-specific handlings */
|
|
if (cli->cc_proto == IPPROTO_SCTP) {
|
|
/* Retrieve the number of streams */
|
|
CHECK_FCT_DO( fd_sctp_get_str_info( cli->cc_socket, &cli->cc_sctp_para.str_in, &cli->cc_sctp_para.str_out, NULL ), {fd_cnx_destroy(cli); return NULL;} );
|
|
if (cli->cc_sctp_para.str_out < cli->cc_sctp_para.str_in)
|
|
cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_out;
|
|
else
|
|
cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_in;
|
|
|
|
LOG_A( "%s : client '%s' (SCTP:%d, %d/%d streams)", fd_cnx_getid(serv), fd_cnx_getid(cli), cli->cc_socket, cli->cc_sctp_para.str_in, cli->cc_sctp_para.str_out);
|
|
}
|
|
#endif /* DISABLE_SCTP */
|
|
|
|
return cli;
|
|
}
|
|
|
|
/* Client side: connect to a remote server -- cancelable */
|
|
struct cnxctx * fd_cnx_cli_connect_tcp(sSA * sa /* contains the port already */, socklen_t addrlen)
|
|
{
|
|
int sock = 0;
|
|
struct cnxctx * cnx = NULL;
|
|
char sa_buf[sSA_DUMP_STRLEN];
|
|
|
|
TRACE_ENTRY("%p %d", sa, addrlen);
|
|
CHECK_PARAMS_DO( sa && addrlen, return NULL );
|
|
|
|
fd_sa_sdump_numeric(sa_buf, sa);
|
|
|
|
LOG_D("Connecting to TCP %s...", sa_buf);
|
|
|
|
/* Create the socket and connect, which can take some time and/or fail */
|
|
{
|
|
int ret = fd_tcp_client( &sock, sa, addrlen );
|
|
if (ret != 0) {
|
|
LOG_D("TCP connection to %s failed: %s", sa_buf, strerror(ret));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Once the socket is created successfuly, prepare the remaining of the cnx */
|
|
CHECK_MALLOC_DO( cnx = fd_cnx_init(1), { shutdown(sock, SHUT_RDWR); close(sock); return NULL; } );
|
|
|
|
cnx->cc_socket = sock;
|
|
cnx->cc_family = sa->sa_family;
|
|
cnx->cc_proto = IPPROTO_TCP;
|
|
|
|
/* Set the timeout */
|
|
fd_cnx_s_setto(cnx->cc_socket);
|
|
|
|
/* Generate the names for the object */
|
|
{
|
|
int rc;
|
|
|
|
snprintf(cnx->cc_id, sizeof(cnx->cc_id), CC_ID_HDR "TCP,#%d->%s", cnx->cc_socket, sa_buf);
|
|
|
|
/* ...Name for log messages */
|
|
rc = getnameinfo(sa, addrlen, cnx->cc_remid, sizeof(cnx->cc_remid), NULL, 0, 0);
|
|
if (rc)
|
|
snprintf(cnx->cc_remid, sizeof(cnx->cc_remid), "[err:%s]", gai_strerror(rc));
|
|
}
|
|
|
|
LOG_A("TCP connection to %s succeed (socket:%d).", sa_buf, sock);
|
|
|
|
return cnx;
|
|
}
|
|
|
|
/* Same for SCTP, accepts a list of remote addresses to connect to (see sctp_connectx for how they are used) */
|
|
struct cnxctx * fd_cnx_cli_connect_sctp(int no_ip6, uint16_t port, struct fd_list * list)
|
|
{
|
|
#ifdef DISABLE_SCTP
|
|
TRACE_DEBUG(INFO, "This function should never be called when SCTP is disabled...");
|
|
ASSERT(0);
|
|
CHECK_FCT_DO( ENOTSUP, );
|
|
return NULL;
|
|
#else /* DISABLE_SCTP */
|
|
int sock = 0;
|
|
struct cnxctx * cnx = NULL;
|
|
char sa_buf[sSA_DUMP_STRLEN];
|
|
sSS primary;
|
|
|
|
TRACE_ENTRY("%p", list);
|
|
CHECK_PARAMS_DO( list && !FD_IS_LIST_EMPTY(list), return NULL );
|
|
|
|
fd_sa_sdump_numeric(sa_buf, &((struct fd_endpoint *)(list->next))->sa);
|
|
|
|
LOG_D("Connecting to SCTP %s:%hu...", sa_buf, port);
|
|
|
|
{
|
|
int ret = fd_sctp_client( &sock, no_ip6, port, list );
|
|
if (ret != 0) {
|
|
LOG_D("SCTP connection to [%s,...] failed: %s", sa_buf, strerror(ret));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Once the socket is created successfuly, prepare the remaining of the cnx */
|
|
CHECK_MALLOC_DO( cnx = fd_cnx_init(1), { shutdown(sock, SHUT_RDWR); close(sock); return NULL; } );
|
|
|
|
cnx->cc_socket = sock;
|
|
cnx->cc_family = no_ip6 ? AF_INET : AF_INET6;
|
|
cnx->cc_proto = IPPROTO_SCTP;
|
|
|
|
/* Set the timeout */
|
|
fd_cnx_s_setto(cnx->cc_socket);
|
|
|
|
/* Retrieve the number of streams and primary address */
|
|
CHECK_FCT_DO( fd_sctp_get_str_info( sock, &cnx->cc_sctp_para.str_in, &cnx->cc_sctp_para.str_out, &primary ), goto error );
|
|
if (cnx->cc_sctp_para.str_out < cnx->cc_sctp_para.str_in)
|
|
cnx->cc_sctp_para.pairs = cnx->cc_sctp_para.str_out;
|
|
else
|
|
cnx->cc_sctp_para.pairs = cnx->cc_sctp_para.str_in;
|
|
|
|
fd_sa_sdump_numeric(sa_buf, (sSA *)&primary);
|
|
|
|
/* Generate the names for the object */
|
|
{
|
|
int rc;
|
|
|
|
snprintf(cnx->cc_id, sizeof(cnx->cc_id), CC_ID_HDR "SCTP,#%d->%s", cnx->cc_socket, sa_buf);
|
|
|
|
/* ...Name for log messages */
|
|
rc = getnameinfo((sSA *)&primary, sSAlen(&primary), cnx->cc_remid, sizeof(cnx->cc_remid), NULL, 0, 0);
|
|
if (rc)
|
|
snprintf(cnx->cc_remid, sizeof(cnx->cc_remid), "[err:%s]", gai_strerror(rc));
|
|
}
|
|
|
|
LOG_A("SCTP connection to %s succeed (socket:%d, %d/%d streams).", sa_buf, sock, cnx->cc_sctp_para.str_in, cnx->cc_sctp_para.str_out);
|
|
|
|
return cnx;
|
|
|
|
error:
|
|
fd_cnx_destroy(cnx);
|
|
return NULL;
|
|
#endif /* DISABLE_SCTP */
|
|
}
|
|
|
|
/* Return a string describing the connection, for debug */
|
|
char * fd_cnx_getid(struct cnxctx * conn)
|
|
{
|
|
CHECK_PARAMS_DO( conn, return "" );
|
|
return conn->cc_id;
|
|
}
|
|
|
|
/* Return the protocol of a connection */
|
|
int fd_cnx_getproto(struct cnxctx * conn)
|
|
{
|
|
CHECK_PARAMS_DO( conn, return 0 );
|
|
return conn->cc_proto;
|
|
}
|
|
|
|
/* Set the hostname to check during handshake */
|
|
void fd_cnx_sethostname(struct cnxctx * conn, DiamId_t hn)
|
|
{
|
|
CHECK_PARAMS_DO( conn, return );
|
|
conn->cc_tls_para.cn = hn;
|
|
}
|
|
|
|
/* We share a lock with many threads but we hold it only very short time so it is OK */
|
|
static pthread_mutex_t state_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
uint32_t fd_cnx_getstate(struct cnxctx * conn)
|
|
{
|
|
uint32_t st;
|
|
CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
|
|
st = conn->cc_state;
|
|
CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
|
|
return st;
|
|
}
|
|
int fd_cnx_teststate(struct cnxctx * conn, uint32_t flag)
|
|
{
|
|
uint32_t st;
|
|
CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
|
|
st = conn->cc_state;
|
|
CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
|
|
return st & flag;
|
|
}
|
|
void fd_cnx_update_id(struct cnxctx * conn) {
|
|
if (conn->cc_state & CC_STATUS_CLOSING)
|
|
conn->cc_id[1] = 'C';
|
|
else
|
|
conn->cc_id[1] = '-';
|
|
|
|
if (conn->cc_state & CC_STATUS_ERROR)
|
|
conn->cc_id[2] = 'E';
|
|
else
|
|
conn->cc_id[2] = '-';
|
|
|
|
if (conn->cc_state & CC_STATUS_SIGNALED)
|
|
conn->cc_id[3] = 'S';
|
|
else
|
|
conn->cc_id[3] = '-';
|
|
|
|
if (conn->cc_state & CC_STATUS_TLS)
|
|
conn->cc_id[4] = 'T';
|
|
else
|
|
conn->cc_id[4] = '-';
|
|
}
|
|
void fd_cnx_addstate(struct cnxctx * conn, uint32_t orstate)
|
|
{
|
|
CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
|
|
conn->cc_state |= orstate;
|
|
fd_cnx_update_id(conn);
|
|
CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
|
|
}
|
|
void fd_cnx_setstate(struct cnxctx * conn, uint32_t abstate)
|
|
{
|
|
CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
|
|
conn->cc_state = abstate;
|
|
fd_cnx_update_id(conn);
|
|
CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
|
|
}
|
|
|
|
|
|
/* Return the TLS state of a connection */
|
|
int fd_cnx_getTLS(struct cnxctx * conn)
|
|
{
|
|
CHECK_PARAMS_DO( conn, return 0 );
|
|
return fd_cnx_teststate(conn, CC_STATUS_TLS);
|
|
}
|
|
|
|
/* Mark the connection to tell if OOO delivery is permitted (only for SCTP) */
|
|
int fd_cnx_unordered_delivery(struct cnxctx * conn, int is_allowed)
|
|
{
|
|
CHECK_PARAMS( conn );
|
|
conn->cc_sctp_para.unordered = is_allowed;
|
|
return 0;
|
|
}
|
|
|
|
/* Return true if the connection supports unordered delivery of messages */
|
|
int fd_cnx_is_unordered_delivery_supported(struct cnxctx * conn)
|
|
{
|
|
CHECK_PARAMS_DO( conn, return 0 );
|
|
#ifndef DISABLE_SCTP
|
|
if (conn->cc_proto == IPPROTO_SCTP)
|
|
return (conn->cc_sctp_para.str_out > 1);
|
|
#endif /* DISABLE_SCTP */
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Get the list of endpoints (IP addresses) of the local and remote peers on this connection */
|
|
int fd_cnx_getremoteeps(struct cnxctx * conn, struct fd_list * eps)
|
|
{
|
|
TRACE_ENTRY("%p %p", conn, eps);
|
|
CHECK_PARAMS(conn && eps);
|
|
|
|
/* Check we have a full connection object, not a listening socket (with no remote) */
|
|
CHECK_PARAMS( conn->cc_incoming );
|
|
|
|
/* Retrieve the peer endpoint(s) of the connection */
|
|
switch (conn->cc_proto) {
|
|
case IPPROTO_TCP: {
|
|
sSS ss;
|
|
socklen_t sl;
|
|
CHECK_FCT(fd_tcp_get_remote_ep(conn->cc_socket, &ss, &sl));
|
|
CHECK_FCT(fd_ep_add_merge( eps, (sSA *)&ss, sl, EP_FL_LL | EP_FL_PRIMARY ));
|
|
}
|
|
break;
|
|
|
|
#ifndef DISABLE_SCTP
|
|
case IPPROTO_SCTP: {
|
|
CHECK_FCT(fd_sctp_get_remote_ep(conn->cc_socket, eps));
|
|
}
|
|
break;
|
|
#endif /* DISABLE_SCTP */
|
|
|
|
default:
|
|
CHECK_PARAMS(0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Get a string describing the remote peer address (ip address or fqdn) */
|
|
char * fd_cnx_getremoteid(struct cnxctx * conn)
|
|
{
|
|
CHECK_PARAMS_DO( conn, return "" );
|
|
return conn->cc_remid;
|
|
}
|
|
|
|
static int fd_cnx_may_dtls(struct cnxctx * conn);
|
|
|
|
/* Get a short string representing the connection */
|
|
int fd_cnx_proto_info(struct cnxctx * conn, char * buf, size_t len)
|
|
{
|
|
CHECK_PARAMS( conn );
|
|
|
|
if (fd_cnx_teststate(conn, CC_STATUS_TLS)) {
|
|
snprintf(buf, len, "%s,%s,soc#%d", IPPROTO_NAME(conn->cc_proto), fd_cnx_may_dtls(conn) ? "DTLS" : "TLS", conn->cc_socket);
|
|
} else {
|
|
snprintf(buf, len, "%s,soc#%d", IPPROTO_NAME(conn->cc_proto), conn->cc_socket);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Retrieve a list of all IP addresses of the local system from the kernel, using getifaddrs */
|
|
int fd_cnx_get_local_eps(struct fd_list * list)
|
|
{
|
|
struct ifaddrs *iflist, *cur;
|
|
|
|
CHECK_SYS(getifaddrs(&iflist));
|
|
|
|
for (cur = iflist; cur != NULL; cur = cur->ifa_next) {
|
|
if (cur->ifa_flags & IFF_LOOPBACK)
|
|
continue;
|
|
|
|
if (cur->ifa_addr == NULL) /* may happen with ppp interfaces */
|
|
continue;
|
|
|
|
if (fd_g_config->cnf_flags.no_ip4 && (cur->ifa_addr->sa_family == AF_INET))
|
|
continue;
|
|
|
|
if (fd_g_config->cnf_flags.no_ip6 && (cur->ifa_addr->sa_family == AF_INET6))
|
|
continue;
|
|
|
|
CHECK_FCT(fd_ep_add_merge( list, cur->ifa_addr, sSAlen(cur->ifa_addr), EP_FL_LL ));
|
|
}
|
|
|
|
freeifaddrs(iflist);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**************************************/
|
|
/* Use of a connection object */
|
|
/**************************************/
|
|
|
|
/* An error occurred on the socket */
|
|
void fd_cnx_markerror(struct cnxctx * conn)
|
|
{
|
|
TRACE_ENTRY("%p", conn);
|
|
CHECK_PARAMS_DO( conn, goto fatal );
|
|
|
|
TRACE_DEBUG(FULL, "Error flag set for socket %d (%s, %s)", conn->cc_socket, conn->cc_id, conn->cc_remid);
|
|
|
|
/* Mark the error */
|
|
fd_cnx_addstate(conn, CC_STATUS_ERROR);
|
|
|
|
/* Report the error if not reported yet, and not closing */
|
|
if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING | CC_STATUS_SIGNALED )) {
|
|
TRACE_DEBUG(FULL, "Sending FDEVP_CNX_ERROR event");
|
|
CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), FDEVP_CNX_ERROR, 0, NULL), goto fatal);
|
|
fd_cnx_addstate(conn, CC_STATUS_SIGNALED);
|
|
}
|
|
|
|
return;
|
|
fatal:
|
|
/* An unrecoverable error occurred, stop the daemon */
|
|
ASSERT(0);
|
|
CHECK_FCT_DO(fd_core_shutdown(), );
|
|
}
|
|
|
|
/* Set the timeout option on the socket */
|
|
void fd_cnx_s_setto(int sock)
|
|
{
|
|
struct timeval tv;
|
|
|
|
/* Set a timeout on the socket so that in any case we are not stuck waiting for something */
|
|
memset(&tv, 0, sizeof(tv));
|
|
tv.tv_usec = 100000L; /* 100ms, to react quickly to head-of-the-line blocking. */
|
|
CHECK_SYS_DO( setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), );
|
|
CHECK_SYS_DO( setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), );
|
|
}
|
|
|
|
|
|
#ifdef GNUTLS_VERSION_300
|
|
/* The pull_timeout function for gnutls */
|
|
static int fd_cnx_s_select (struct cnxctx * conn, unsigned int ms)
|
|
{
|
|
fd_set rfds;
|
|
struct timeval tv;
|
|
|
|
FD_ZERO (&rfds);
|
|
FD_SET (conn->cc_socket, &rfds);
|
|
|
|
tv.tv_sec = ms / 1000;
|
|
tv.tv_usec = (ms * 1000) % 1000000;
|
|
|
|
return select (conn->cc_socket + 1, &rfds, NULL, NULL, &tv);
|
|
}
|
|
#endif /* GNUTLS_VERSION_300 */
|
|
|
|
/* A recv-like function, taking a cnxctx object instead of socket as entry. We use it to quickly react to timeouts without traversing GNUTLS wrapper each time */
|
|
ssize_t fd_cnx_s_recv(struct cnxctx * conn, void *buffer, size_t length)
|
|
{
|
|
ssize_t ret = 0;
|
|
int timedout = 0;
|
|
again:
|
|
ret = recv(conn->cc_socket, buffer, length, 0);
|
|
/* Handle special case of timeout / interrupts */
|
|
if ((ret < 0) && ((errno == EAGAIN) || (errno == EINTR))) {
|
|
pthread_testcancel();
|
|
if (! fd_cnx_teststate(conn, CC_STATUS_CLOSING ))
|
|
goto again; /* don't care, just ignore */
|
|
if (!timedout) {
|
|
timedout ++; /* allow for one timeout while closing */
|
|
goto again;
|
|
}
|
|
}
|
|
|
|
/* Mark the error */
|
|
if (ret <= 0) {
|
|
CHECK_SYS_DO(ret, /* continue, this is only used to log the error here */);
|
|
fd_cnx_markerror(conn);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Send */
|
|
static ssize_t fd_cnx_s_sendv(struct cnxctx * conn, const struct iovec * iov, int iovcnt)
|
|
{
|
|
ssize_t ret = 0;
|
|
struct timespec ts, now;
|
|
CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &ts), return -1 );
|
|
again:
|
|
ret = writev(conn->cc_socket, iov, iovcnt);
|
|
/* Handle special case of timeout */
|
|
if ((ret < 0) && ((errno == EAGAIN) || (errno == EINTR))) {
|
|
ret = -errno;
|
|
pthread_testcancel();
|
|
|
|
/* Check how much time we were blocked for this sending. */
|
|
CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &now), return -1 );
|
|
if ( ((now.tv_sec - ts.tv_sec) * 1000 + ((now.tv_nsec - ts.tv_nsec) / 1000000L)) > MAX_HOTL_BLOCKING_TIME) {
|
|
LOG_D("Unable to send any data for %dms, closing the connection", MAX_HOTL_BLOCKING_TIME);
|
|
} else if (! fd_cnx_teststate(conn, CC_STATUS_CLOSING )) {
|
|
goto again; /* don't care, just ignore */
|
|
}
|
|
|
|
/* propagate the error */
|
|
errno = -ret;
|
|
ret = -1;
|
|
CHECK_SYS_DO(ret, /* continue */);
|
|
}
|
|
|
|
/* Mark the error */
|
|
if (ret <= 0)
|
|
fd_cnx_markerror(conn);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Send, for older GNUTLS */
|
|
#ifndef GNUTLS_VERSION_212
|
|
static ssize_t fd_cnx_s_send(struct cnxctx * conn, const void *buffer, size_t length)
|
|
{
|
|
struct iovec iov;
|
|
iov.iov_base = (void *)buffer;
|
|
iov.iov_len = length;
|
|
return fd_cnx_s_sendv(conn, &iov, 1);
|
|
}
|
|
#endif /* GNUTLS_VERSION_212 */
|
|
|
|
#define ALIGNOF(t) ((char *)(&((struct { char c; t _h; } *)0)->_h) - (char *)0) /* Could use __alignof__(t) on some systems but this is more portable probably */
|
|
#define PMDL_PADDED(len) ( ((len) + ALIGNOF(struct fd_msg_pmdl) - 1) & ~(ALIGNOF(struct fd_msg_pmdl) - 1) )
|
|
|
|
size_t fd_msg_pmdl_sizewithoverhead(size_t datalen)
|
|
{
|
|
return PMDL_PADDED(datalen) + sizeof(struct fd_msg_pmdl);
|
|
}
|
|
|
|
struct fd_msg_pmdl * fd_msg_pmdl_get_inbuf(uint8_t * buf, size_t datalen)
|
|
{
|
|
return (struct fd_msg_pmdl *)(buf + PMDL_PADDED(datalen));
|
|
}
|
|
|
|
static int fd_cnx_init_msg_buffer(uint8_t * buffer, size_t expected_len, struct fd_msg_pmdl ** pmdl)
|
|
{
|
|
*pmdl = fd_msg_pmdl_get_inbuf(buffer, expected_len);
|
|
fd_list_init(&(*pmdl)->sentinel, NULL);
|
|
CHECK_POSIX(pthread_mutex_init(&(*pmdl)->lock, NULL) );
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t * fd_cnx_alloc_msg_buffer(size_t expected_len, struct fd_msg_pmdl ** pmdl)
|
|
{
|
|
uint8_t * ret = NULL;
|
|
|
|
CHECK_MALLOC_DO( ret = malloc( fd_msg_pmdl_sizewithoverhead(expected_len) ), return NULL );
|
|
CHECK_FCT_DO( fd_cnx_init_msg_buffer(ret, expected_len, pmdl), {free(ret); return NULL;} );
|
|
return ret;
|
|
}
|
|
|
|
#ifndef DISABLE_SCTP /* WE use this function only in SCTP code */
|
|
static uint8_t * fd_cnx_realloc_msg_buffer(uint8_t * buffer, size_t expected_len, struct fd_msg_pmdl ** pmdl)
|
|
{
|
|
uint8_t * ret = NULL;
|
|
|
|
CHECK_MALLOC_DO( ret = realloc( buffer, fd_msg_pmdl_sizewithoverhead(expected_len) ), return NULL );
|
|
CHECK_FCT_DO( fd_cnx_init_msg_buffer(ret, expected_len, pmdl), {free(ret); return NULL;} );
|
|
return ret;
|
|
}
|
|
#endif /* DISABLE_SCTP */
|
|
|
|
static void free_rcvdata(void * arg)
|
|
{
|
|
struct fd_cnx_rcvdata * data = arg;
|
|
struct fd_msg_pmdl * pmdl = fd_msg_pmdl_get_inbuf(data->buffer, data->length);
|
|
(void) pthread_mutex_destroy(&pmdl->lock);
|
|
free(data->buffer);
|
|
}
|
|
|
|
/* Receiver thread (TCP & noTLS) : incoming message is directly saved into the target queue */
|
|
static void * rcvthr_notls_tcp(void * arg)
|
|
{
|
|
struct cnxctx * conn = arg;
|
|
|
|
TRACE_ENTRY("%p", arg);
|
|
CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto out);
|
|
|
|
/* Set the thread name */
|
|
{
|
|
char buf[48];
|
|
snprintf(buf, sizeof(buf), "Receiver (%d) TCP/noTLS)", conn->cc_socket);
|
|
fd_log_threadname ( buf );
|
|
}
|
|
|
|
ASSERT( conn->cc_proto == IPPROTO_TCP );
|
|
ASSERT( ! fd_cnx_teststate(conn, CC_STATUS_TLS ) );
|
|
ASSERT( fd_cnx_target_queue(conn) );
|
|
|
|
/* Receive from a TCP connection: we have to rebuild the message boundaries */
|
|
do {
|
|
uint8_t header[4];
|
|
struct fd_cnx_rcvdata rcv_data;
|
|
struct fd_msg_pmdl *pmdl=NULL;
|
|
ssize_t ret = 0;
|
|
size_t received = 0;
|
|
|
|
do {
|
|
ret = fd_cnx_s_recv(conn, &header[received], sizeof(header) - received);
|
|
if (ret <= 0) {
|
|
goto out; /* Stop the thread, the event was already sent */
|
|
}
|
|
|
|
received += ret;
|
|
|
|
if (header[0] != DIAMETER_VERSION)
|
|
break; /* No need to wait for 4 bytes in this case */
|
|
} while (received < sizeof(header));
|
|
|
|
rcv_data.length = ((size_t)header[1] << 16) + ((size_t)header[2] << 8) + (size_t)header[3];
|
|
|
|
/* Check the received word is a valid begining of a Diameter message */
|
|
if ((header[0] != DIAMETER_VERSION) /* defined in <libfdproto.h> */
|
|
|| (rcv_data.length > DIAMETER_MSG_SIZE_MAX)) { /* to avoid too big mallocs */
|
|
/* The message is suspect */
|
|
LOG_E( "Received suspect header [ver: %d, size: %zd] from '%s', assuming disconnection", (int)header[0], rcv_data.length, conn->cc_remid);
|
|
fd_cnx_markerror(conn);
|
|
goto out; /* Stop the thread, the recipient of the event will cleanup */
|
|
}
|
|
|
|
/* Ok, now we can really receive the data */
|
|
CHECK_MALLOC_DO( rcv_data.buffer = fd_cnx_alloc_msg_buffer( rcv_data.length, &pmdl ), goto fatal );
|
|
memcpy(rcv_data.buffer, header, sizeof(header));
|
|
|
|
while (received < rcv_data.length) {
|
|
pthread_cleanup_push(free_rcvdata, &rcv_data); /* In case we are canceled, clean the partialy built buffer */
|
|
ret = fd_cnx_s_recv(conn, rcv_data.buffer + received, rcv_data.length - received);
|
|
pthread_cleanup_pop(0);
|
|
|
|
if (ret <= 0) {
|
|
free_rcvdata(&rcv_data);
|
|
goto out;
|
|
}
|
|
received += ret;
|
|
}
|
|
|
|
fd_hook_call(HOOK_DATA_RECEIVED, NULL, NULL, &rcv_data, pmdl);
|
|
|
|
/* We have received a complete message, pass it to the daemon */
|
|
CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), FDEVP_CNX_MSG_RECV, rcv_data.length, rcv_data.buffer),
|
|
{
|
|
free_rcvdata(&rcv_data);
|
|
goto fatal;
|
|
} );
|
|
|
|
} while (conn->cc_loop);
|
|
|
|
out:
|
|
TRACE_DEBUG(FULL, "Thread terminated");
|
|
return NULL;
|
|
|
|
fatal:
|
|
/* An unrecoverable error occurred, stop the daemon */
|
|
CHECK_FCT_DO(fd_core_shutdown(), );
|
|
goto out;
|
|
}
|
|
|
|
#ifndef DISABLE_SCTP
|
|
/* Receiver thread (SCTP & noTLS) : incoming message is directly saved into cc_incoming, no need to care for the stream ID */
|
|
static void * rcvthr_notls_sctp(void * arg)
|
|
{
|
|
struct cnxctx * conn = arg;
|
|
struct fd_cnx_rcvdata rcv_data;
|
|
int event;
|
|
|
|
TRACE_ENTRY("%p", arg);
|
|
CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto fatal);
|
|
|
|
/* Set the thread name */
|
|
{
|
|
char buf[48];
|
|
snprintf(buf, sizeof(buf), "Receiver (%d) SCTP/noTLS)", conn->cc_socket);
|
|
fd_log_threadname ( buf );
|
|
}
|
|
|
|
ASSERT( conn->cc_proto == IPPROTO_SCTP );
|
|
ASSERT( ! fd_cnx_teststate(conn, CC_STATUS_TLS ) );
|
|
ASSERT( fd_cnx_target_queue(conn) );
|
|
|
|
do {
|
|
struct fd_msg_pmdl *pmdl=NULL;
|
|
CHECK_FCT_DO( fd_sctp_recvmeta(conn, NULL, &rcv_data.buffer, &rcv_data.length, &event), goto fatal );
|
|
if (event == FDEVP_CNX_ERROR) {
|
|
fd_cnx_markerror(conn);
|
|
goto out;
|
|
}
|
|
|
|
if (event == FDEVP_CNX_SHUTDOWN) {
|
|
/* Just ignore the notification for now, we will get another error later anyway */
|
|
continue;
|
|
}
|
|
|
|
if (event == FDEVP_CNX_MSG_RECV) {
|
|
CHECK_MALLOC_DO( rcv_data.buffer = fd_cnx_realloc_msg_buffer(rcv_data.buffer, rcv_data.length, &pmdl), goto fatal );
|
|
fd_hook_call(HOOK_DATA_RECEIVED, NULL, NULL, &rcv_data, pmdl);
|
|
}
|
|
CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), event, rcv_data.length, rcv_data.buffer), goto fatal );
|
|
|
|
} while (conn->cc_loop || (event != FDEVP_CNX_MSG_RECV));
|
|
|
|
out:
|
|
TRACE_DEBUG(FULL, "Thread terminated");
|
|
return NULL;
|
|
|
|
fatal:
|
|
/* An unrecoverable error occurred, stop the daemon */
|
|
CHECK_FCT_DO(fd_core_shutdown(), );
|
|
goto out;
|
|
}
|
|
#endif /* DISABLE_SCTP */
|
|
|
|
/* Start receving messages in clear (no TLS) on the connection */
|
|
int fd_cnx_start_clear(struct cnxctx * conn, int loop)
|
|
{
|
|
TRACE_ENTRY("%p %i", conn, loop);
|
|
|
|
CHECK_PARAMS( conn && fd_cnx_target_queue(conn) && (!fd_cnx_teststate(conn, CC_STATUS_TLS)) && (!conn->cc_loop));
|
|
|
|
/* Release resources in case of a previous call was already made */
|
|
CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */);
|
|
|
|
/* Save the loop request */
|
|
conn->cc_loop = loop;
|
|
|
|
switch (conn->cc_proto) {
|
|
case IPPROTO_TCP:
|
|
/* Start the tcp_notls thread */
|
|
CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_notls_tcp, conn ) );
|
|
break;
|
|
#ifndef DISABLE_SCTP
|
|
case IPPROTO_SCTP:
|
|
/* Start the tcp_notls thread */
|
|
CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_notls_sctp, conn ) );
|
|
break;
|
|
#endif /* DISABLE_SCTP */
|
|
default:
|
|
TRACE_DEBUG(INFO, "Unknown protocol: %d", conn->cc_proto);
|
|
ASSERT(0);
|
|
return ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Returns 0 on error, received data size otherwise (always >= 0). This is not used for DTLS-protected associations. */
|
|
static ssize_t fd_tls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz)
|
|
{
|
|
ssize_t ret;
|
|
again:
|
|
CHECK_GNUTLS_DO( ret = gnutls_record_recv(session, data, sz),
|
|
{
|
|
switch (ret) {
|
|
case GNUTLS_E_REHANDSHAKE:
|
|
if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING)) {
|
|
CHECK_GNUTLS_DO( ret = gnutls_handshake(session),
|
|
{
|
|
if (TRACE_BOOL(INFO)) {
|
|
fd_log_debug("TLS re-handshake failed on socket %d (%s) : %s", conn->cc_socket, conn->cc_id, gnutls_strerror(ret));
|
|
}
|
|
goto end;
|
|
} );
|
|
}
|
|
|
|
case GNUTLS_E_AGAIN:
|
|
case GNUTLS_E_INTERRUPTED:
|
|
if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING))
|
|
goto again;
|
|
TRACE_DEBUG(FULL, "Connection is closing, so abord gnutls_record_recv now.");
|
|
break;
|
|
|
|
case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
|
|
/* The connection is closed */
|
|
TRACE_DEBUG(FULL, "Got 0 size while reading the socket, probably connection closed...");
|
|
break;
|
|
|
|
default:
|
|
if (gnutls_error_is_fatal (ret) == 0) {
|
|
LOG_N("Ignoring non-fatal GNU TLS error: %s", gnutls_strerror (ret));
|
|
goto again;
|
|
}
|
|
LOG_E("Fatal GNUTLS error: %s", gnutls_strerror (ret));
|
|
}
|
|
} );
|
|
|
|
if (ret == 0)
|
|
CHECK_GNUTLS_DO( gnutls_bye(session, GNUTLS_SHUT_RDWR), );
|
|
|
|
end:
|
|
if (ret <= 0)
|
|
fd_cnx_markerror(conn);
|
|
return ret;
|
|
}
|
|
|
|
/* Wrapper around gnutls_record_send to handle some error codes. This is also used for DTLS-protected associations */
|
|
static ssize_t fd_tls_send_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz)
|
|
{
|
|
ssize_t ret;
|
|
struct timespec ts, now;
|
|
CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &ts), return -1 );
|
|
again:
|
|
CHECK_GNUTLS_DO( ret = gnutls_record_send(session, data, sz),
|
|
{
|
|
pthread_testcancel();
|
|
switch (ret) {
|
|
case GNUTLS_E_REHANDSHAKE:
|
|
if (!fd_cnx_teststate(conn, CC_STATUS_CLOSING)) {
|
|
CHECK_GNUTLS_DO( ret = gnutls_handshake(session),
|
|
{
|
|
if (TRACE_BOOL(INFO)) {
|
|
fd_log_debug("TLS re-handshake failed on socket %d (%s) : %s", conn->cc_socket, conn->cc_id, gnutls_strerror(ret));
|
|
}
|
|
goto end;
|
|
} );
|
|
}
|
|
|
|
case GNUTLS_E_AGAIN:
|
|
case GNUTLS_E_INTERRUPTED:
|
|
CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &now), return -1 );
|
|
if ( ((now.tv_sec - ts.tv_sec) * 1000 + ((now.tv_nsec - ts.tv_nsec) / 1000000L)) > MAX_HOTL_BLOCKING_TIME) {
|
|
LOG_D("Unable to send any data for %dms, closing the connection", MAX_HOTL_BLOCKING_TIME);
|
|
} else if (! fd_cnx_teststate(conn, CC_STATUS_CLOSING )) {
|
|
goto again;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (gnutls_error_is_fatal (ret) == 0) {
|
|
LOG_N("Ignoring non-fatal GNU TLS error: %s", gnutls_strerror (ret));
|
|
goto again;
|
|
}
|
|
LOG_E("Fatal GNUTLS error: %s", gnutls_strerror (ret));
|
|
}
|
|
} );
|
|
end:
|
|
if (ret <= 0)
|
|
fd_cnx_markerror(conn);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* The function that receives TLS data and re-builds a Diameter message -- it exits only on error or cancelation */
|
|
/* For the case of DTLS, since we are not using SCTP_UNORDERED, the messages over a single stream are ordered.
|
|
Furthermore, as long as messages are shorter than the MTU [2^14 = 16384 bytes], they are delivered in a single
|
|
record, as far as I understand.
|
|
For larger messages, however, it is possible that pieces of messages coming from different streams can get interleaved.
|
|
As a result, we do not use the following function for DTLS reception, because we use the sequence number to rebuild the
|
|
messages. */
|
|
int fd_tls_rcvthr_core(struct cnxctx * conn, gnutls_session_t session)
|
|
{
|
|
/* No guarantee that GnuTLS preserves the message boundaries, so we re-build it as in TCP. */
|
|
do {
|
|
uint8_t header[4];
|
|
struct fd_cnx_rcvdata rcv_data;
|
|
struct fd_msg_pmdl *pmdl=NULL;
|
|
ssize_t ret = 0;
|
|
size_t received = 0;
|
|
|
|
do {
|
|
ret = fd_tls_recv_handle_error(conn, session, &header[received], sizeof(header) - received);
|
|
if (ret <= 0) {
|
|
/* The connection is closed */
|
|
goto out;
|
|
}
|
|
received += ret;
|
|
} while (received < sizeof(header));
|
|
|
|
rcv_data.length = ((size_t)header[1] << 16) + ((size_t)header[2] << 8) + (size_t)header[3];
|
|
|
|
/* Check the received word is a valid beginning of a Diameter message */
|
|
if ((header[0] != DIAMETER_VERSION) /* defined in <libfreeDiameter.h> */
|
|
|| (rcv_data.length > DIAMETER_MSG_SIZE_MAX)) { /* to avoid too big mallocs */
|
|
/* The message is suspect */
|
|
LOG_E( "Received suspect header [ver: %d, size: %zd] from '%s', assume disconnection", (int)header[0], rcv_data.length, conn->cc_remid);
|
|
fd_cnx_markerror(conn);
|
|
goto out;
|
|
}
|
|
|
|
/* Ok, now we can really receive the data */
|
|
CHECK_MALLOC( rcv_data.buffer = fd_cnx_alloc_msg_buffer( rcv_data.length, &pmdl ) );
|
|
memcpy(rcv_data.buffer, header, sizeof(header));
|
|
|
|
while (received < rcv_data.length) {
|
|
pthread_cleanup_push(free_rcvdata, &rcv_data); /* In case we are canceled, clean the partialy built buffer */
|
|
ret = fd_tls_recv_handle_error(conn, session, rcv_data.buffer + received, rcv_data.length - received);
|
|
pthread_cleanup_pop(0);
|
|
|
|
if (ret <= 0) {
|
|
free_rcvdata(&rcv_data);
|
|
goto out;
|
|
}
|
|
received += ret;
|
|
}
|
|
|
|
fd_hook_call(HOOK_DATA_RECEIVED, NULL, NULL, &rcv_data, pmdl);
|
|
|
|
/* We have received a complete message, pass it to the daemon */
|
|
CHECK_FCT_DO( ret = fd_event_send( fd_cnx_target_queue(conn), FDEVP_CNX_MSG_RECV, rcv_data.length, rcv_data.buffer),
|
|
{
|
|
free_rcvdata(&rcv_data);
|
|
CHECK_FCT_DO(fd_core_shutdown(), );
|
|
return ret;
|
|
} );
|
|
|
|
} while (1);
|
|
|
|
out:
|
|
return ENOTCONN;
|
|
}
|
|
|
|
/* Receiver thread (TLS & 1 stream SCTP or TCP) */
|
|
static void * rcvthr_tls_single(void * arg)
|
|
{
|
|
struct cnxctx * conn = arg;
|
|
|
|
TRACE_ENTRY("%p", arg);
|
|
CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), return NULL );
|
|
|
|
/* Set the thread name */
|
|
{
|
|
char buf[48];
|
|
snprintf(buf, sizeof(buf), "Receiver (%d) TLS/single stream", conn->cc_socket);
|
|
fd_log_threadname ( buf );
|
|
}
|
|
|
|
ASSERT( fd_cnx_teststate(conn, CC_STATUS_TLS) );
|
|
ASSERT( fd_cnx_target_queue(conn) );
|
|
|
|
/* The next function only returns when there is an error on the socket */
|
|
CHECK_FCT_DO(fd_tls_rcvthr_core(conn, conn->cc_tls_para.session), /* continue */);
|
|
|
|
TRACE_DEBUG(FULL, "Thread terminated");
|
|
return NULL;
|
|
}
|
|
|
|
/* Prepare a gnutls session object for handshake */
|
|
int fd_tls_prepare(gnutls_session_t * session, int mode, int dtls, char * priority, void * alt_creds)
|
|
{
|
|
if (dtls) {
|
|
LOG_E("DTLS sessions not yet supported");
|
|
return ENOTSUP;
|
|
}
|
|
|
|
/* Create the session context */
|
|
CHECK_GNUTLS_DO( gnutls_init (session, mode), return ENOMEM );
|
|
|
|
/* Set the algorithm suite */
|
|
if (priority) {
|
|
const char * errorpos;
|
|
CHECK_GNUTLS_DO( gnutls_priority_set_direct( *session, priority, &errorpos ),
|
|
{ TRACE_DEBUG(INFO, "Error in priority string '%s' at position: '%s'", priority, errorpos); return EINVAL; } );
|
|
} else {
|
|
CHECK_GNUTLS_DO( gnutls_priority_set( *session, fd_g_config->cnf_sec_data.prio_cache ), return EINVAL );
|
|
}
|
|
|
|
/* Set the credentials of this side of the connection */
|
|
CHECK_GNUTLS_DO( gnutls_credentials_set (*session, GNUTLS_CRD_CERTIFICATE, alt_creds ?: fd_g_config->cnf_sec_data.credentials), return EINVAL );
|
|
|
|
/* Request the remote credentials as well */
|
|
if (mode == GNUTLS_SERVER) {
|
|
gnutls_certificate_server_set_request (*session, GNUTLS_CERT_REQUIRE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef GNUTLS_VERSION_300
|
|
|
|
/* Verify remote credentials after successful handshake (return 0 if OK, EINVAL otherwise) */
|
|
int fd_tls_verify_credentials(gnutls_session_t session, struct cnxctx * conn, int verbose)
|
|
{
|
|
int i, ret = 0;
|
|
unsigned int gtret;
|
|
const gnutls_datum_t *cert_list;
|
|
unsigned int cert_list_size;
|
|
gnutls_x509_crt_t cert;
|
|
time_t now;
|
|
|
|
TRACE_ENTRY("%p %d", conn, verbose);
|
|
CHECK_PARAMS(conn);
|
|
|
|
/* Trace the session information -- http://www.gnu.org/software/gnutls/manual/gnutls.html#Obtaining-session-information */
|
|
#ifdef DEBUG
|
|
if (verbose) {
|
|
const char *tmp;
|
|
gnutls_kx_algorithm_t kx;
|
|
gnutls_credentials_type_t cred;
|
|
|
|
LOG_D("TLS Session information for connection '%s':", conn->cc_id);
|
|
|
|
/* print the key exchange's algorithm name */
|
|
GNUTLS_TRACE( kx = gnutls_kx_get (session) );
|
|
GNUTLS_TRACE( tmp = gnutls_kx_get_name (kx) );
|
|
LOG_D("\t - Key Exchange: %s", tmp);
|
|
|
|
/* Check the authentication type used and switch
|
|
* to the appropriate. */
|
|
GNUTLS_TRACE( cred = gnutls_auth_get_type (session) );
|
|
switch (cred)
|
|
{
|
|
case GNUTLS_CRD_IA:
|
|
LOG_D("\t - TLS/IA session");
|
|
break;
|
|
|
|
case GNUTLS_CRD_PSK:
|
|
/* This returns NULL in server side. */
|
|
if (gnutls_psk_client_get_hint (session) != NULL)
|
|
LOG_D("\t - PSK authentication. PSK hint '%s'",
|
|
gnutls_psk_client_get_hint (session));
|
|
/* This returns NULL in client side. */
|
|
if (gnutls_psk_server_get_username (session) != NULL)
|
|
LOG_D("\t - PSK authentication. Connected as '%s'",
|
|
gnutls_psk_server_get_username (session));
|
|
break;
|
|
|
|
case GNUTLS_CRD_ANON: /* anonymous authentication */
|
|
LOG_D("\t - Anonymous DH using prime of %d bits",
|
|
gnutls_dh_get_prime_bits (session));
|
|
break;
|
|
|
|
case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */
|
|
/* Check if we have been using ephemeral Diffie-Hellman. */
|
|
if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) {
|
|
LOG_D("\t - Ephemeral DH using prime of %d bits",
|
|
gnutls_dh_get_prime_bits (session));
|
|
}
|
|
break;
|
|
#ifdef ENABLE_SRP
|
|
case GNUTLS_CRD_SRP:
|
|
LOG_D("\t - SRP session with username %s",
|
|
gnutls_srp_server_get_username (session));
|
|
break;
|
|
#endif /* ENABLE_SRP */
|
|
|
|
default:
|
|
fd_log_debug("\t - Different type of credentials for the session (%d).", cred);
|
|
break;
|
|
|
|
}
|
|
|
|
/* print the protocol's name (ie TLS 1.0) */
|
|
tmp = gnutls_protocol_get_name (gnutls_protocol_get_version (session));
|
|
LOG_D("\t - Protocol: %s", tmp);
|
|
|
|
/* print the certificate type of the peer. ie X.509 */
|
|
tmp = gnutls_certificate_type_get_name (gnutls_certificate_type_get (session));
|
|
LOG_D("\t - Certificate Type: %s", tmp);
|
|
|
|
/* print the compression algorithm (if any) */
|
|
tmp = gnutls_compression_get_name (gnutls_compression_get (session));
|
|
LOG_D("\t - Compression: %s", tmp);
|
|
|
|
/* print the name of the cipher used. ie 3DES. */
|
|
tmp = gnutls_cipher_get_name (gnutls_cipher_get (session));
|
|
LOG_D("\t - Cipher: %s", tmp);
|
|
|
|
/* Print the MAC algorithms name. ie SHA1 */
|
|
tmp = gnutls_mac_get_name (gnutls_mac_get (session));
|
|
LOG_D("\t - MAC: %s", tmp);
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
/* First, use built-in verification */
|
|
CHECK_GNUTLS_DO( gnutls_certificate_verify_peers2 (session, >ret), return EINVAL );
|
|
if (gtret) {
|
|
LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
|
|
if (gtret & GNUTLS_CERT_INVALID)
|
|
LOG_E(" - The certificate is not trusted (unknown CA? expired?)");
|
|
if (gtret & GNUTLS_CERT_REVOKED)
|
|
LOG_E(" - The certificate has been revoked.");
|
|
if (gtret & GNUTLS_CERT_SIGNER_NOT_FOUND)
|
|
LOG_E(" - The certificate hasn't got a known issuer.");
|
|
if (gtret & GNUTLS_CERT_SIGNER_NOT_CA)
|
|
LOG_E(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints.");
|
|
if (gtret & GNUTLS_CERT_INSECURE_ALGORITHM)
|
|
LOG_E(" - The certificate signature uses a weak algorithm.");
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Code from http://www.gnu.org/software/gnutls/manual/gnutls.html#Verifying-peer_0027s-certificate */
|
|
if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) {
|
|
LOG_E("TLS: Remote peer did not present a certificate, other mechanisms are not supported yet. socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
|
|
return EINVAL;
|
|
}
|
|
|
|
GNUTLS_TRACE( cert_list = gnutls_certificate_get_peers (session, &cert_list_size) );
|
|
if (cert_list == NULL)
|
|
return EINVAL;
|
|
|
|
now = time(NULL);
|
|
|
|
#ifdef DEBUG
|
|
char serial[40];
|
|
char dn[128];
|
|
size_t size;
|
|
unsigned int algo, bits;
|
|
time_t expiration_time, activation_time;
|
|
|
|
LOG_D("TLS Certificate information for connection '%s' (%d certs provided):", conn->cc_id, cert_list_size);
|
|
for (i = 0; i < cert_list_size; i++)
|
|
{
|
|
|
|
CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return EINVAL);
|
|
CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER), return EINVAL);
|
|
|
|
LOG_A(" Certificate %d info:", i);
|
|
|
|
GNUTLS_TRACE( expiration_time = gnutls_x509_crt_get_expiration_time (cert) );
|
|
GNUTLS_TRACE( activation_time = gnutls_x509_crt_get_activation_time (cert) );
|
|
|
|
LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate is valid since: %.24s", ctime (&activation_time));
|
|
LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate expires: %.24s", ctime (&expiration_time));
|
|
|
|
/* Print the serial number of the certificate. */
|
|
size = sizeof (serial);
|
|
gnutls_x509_crt_get_serial (cert, serial, &size);
|
|
|
|
{
|
|
int j;
|
|
char buf[1024];
|
|
snprintf(buf, sizeof(buf), "\t - Certificate serial number: ");
|
|
for (j = 0; j < size; j++) {
|
|
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%02hhx", serial[j]);
|
|
}
|
|
LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "%s", buf);
|
|
}
|
|
|
|
/* Extract some of the public key algorithm's parameters */
|
|
GNUTLS_TRACE( algo = gnutls_x509_crt_get_pk_algorithm (cert, &bits) );
|
|
LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate public key: %s",
|
|
gnutls_pk_algorithm_get_name (algo));
|
|
|
|
/* Print the version of the X.509 certificate. */
|
|
LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Certificate version: #%d",
|
|
gnutls_x509_crt_get_version (cert));
|
|
|
|
size = sizeof (dn);
|
|
GNUTLS_TRACE( gnutls_x509_crt_get_dn (cert, dn, &size) );
|
|
LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - DN: %s", dn);
|
|
|
|
size = sizeof (dn);
|
|
GNUTLS_TRACE( gnutls_x509_crt_get_issuer_dn (cert, dn, &size) );
|
|
LOG( i ? FD_LOG_ANNOYING : FD_LOG_DEBUG, "\t - Issuer's DN: %s", dn);
|
|
|
|
GNUTLS_TRACE( gnutls_x509_crt_deinit (cert) );
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
/* Check validity of all the certificates */
|
|
for (i = 0; i < cert_list_size; i++)
|
|
{
|
|
time_t deadline;
|
|
|
|
CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return EINVAL);
|
|
CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER), return EINVAL);
|
|
|
|
GNUTLS_TRACE( deadline = gnutls_x509_crt_get_expiration_time(cert) );
|
|
if ((deadline != (time_t)-1) && (deadline < now)) {
|
|
LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
|
|
LOG_E(" - The certificate %d in the chain is expired", i);
|
|
ret = EINVAL;
|
|
}
|
|
|
|
GNUTLS_TRACE( deadline = gnutls_x509_crt_get_activation_time(cert) );
|
|
if ((deadline != (time_t)-1) && (deadline > now)) {
|
|
LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
|
|
LOG_E(" - The certificate %d in the chain is not yet activated", i);
|
|
ret = EINVAL;
|
|
}
|
|
|
|
if ((i == 0) && (conn->cc_tls_para.cn)) {
|
|
if (!gnutls_x509_crt_check_hostname (cert, conn->cc_tls_para.cn)) {
|
|
LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
|
|
LOG_E(" - The certificate hostname does not match '%s'", conn->cc_tls_para.cn);
|
|
ret = EINVAL;
|
|
}
|
|
}
|
|
|
|
GNUTLS_TRACE( gnutls_x509_crt_deinit (cert) );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#else /* GNUTLS_VERSION_300 */
|
|
|
|
/* Verify remote credentials DURING handshake (return gnutls status) */
|
|
int fd_tls_verify_credentials_2(gnutls_session_t session)
|
|
{
|
|
/* inspired from gnutls 3.x guidelines */
|
|
unsigned int status;
|
|
const gnutls_datum_t *cert_list = NULL;
|
|
unsigned int cert_list_size;
|
|
gnutls_x509_crt_t cert;
|
|
struct cnxctx * conn;
|
|
int hostname_verified = 0;
|
|
|
|
TRACE_ENTRY("%p", session);
|
|
|
|
/* get the associated connection */
|
|
conn = gnutls_session_get_ptr (session);
|
|
|
|
/* Trace the session information -- http://www.gnu.org/software/gnutls/manual/gnutls.html#Obtaining-session-information */
|
|
#ifdef DEBUG
|
|
const char *tmp;
|
|
gnutls_credentials_type_t cred;
|
|
gnutls_kx_algorithm_t kx;
|
|
int dhe, ecdh;
|
|
|
|
dhe = ecdh = 0;
|
|
|
|
LOG_A("TLS Session information for connection '%s':", conn->cc_id);
|
|
|
|
/* print the key exchange's algorithm name
|
|
*/
|
|
GNUTLS_TRACE( kx = gnutls_kx_get (session) );
|
|
GNUTLS_TRACE( tmp = gnutls_kx_get_name (kx) );
|
|
LOG_D("\t- Key Exchange: %s", tmp);
|
|
|
|
/* Check the authentication type used and switch
|
|
* to the appropriate.
|
|
*/
|
|
GNUTLS_TRACE( cred = gnutls_auth_get_type (session) );
|
|
switch (cred)
|
|
{
|
|
case GNUTLS_CRD_IA:
|
|
LOG_D("\t - TLS/IA session");
|
|
break;
|
|
|
|
|
|
#ifdef ENABLE_SRP
|
|
case GNUTLS_CRD_SRP:
|
|
LOG_D("\t - SRP session with username %s",
|
|
gnutls_srp_server_get_username (session));
|
|
break;
|
|
#endif
|
|
|
|
case GNUTLS_CRD_PSK:
|
|
/* This returns NULL in server side.
|
|
*/
|
|
if (gnutls_psk_client_get_hint (session) != NULL)
|
|
LOG_D("\t - PSK authentication. PSK hint '%s'",
|
|
gnutls_psk_client_get_hint (session));
|
|
/* This returns NULL in client side.
|
|
*/
|
|
if (gnutls_psk_server_get_username (session) != NULL)
|
|
LOG_D("\t - PSK authentication. Connected as '%s'",
|
|
gnutls_psk_server_get_username (session));
|
|
|
|
if (kx == GNUTLS_KX_ECDHE_PSK)
|
|
ecdh = 1;
|
|
else if (kx == GNUTLS_KX_DHE_PSK)
|
|
dhe = 1;
|
|
break;
|
|
|
|
case GNUTLS_CRD_ANON: /* anonymous authentication */
|
|
LOG_D("\t - Anonymous DH using prime of %d bits",
|
|
gnutls_dh_get_prime_bits (session));
|
|
if (kx == GNUTLS_KX_ANON_ECDH)
|
|
ecdh = 1;
|
|
else if (kx == GNUTLS_KX_ANON_DH)
|
|
dhe = 1;
|
|
break;
|
|
|
|
case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */
|
|
|
|
/* Check if we have been using ephemeral Diffie-Hellman.
|
|
*/
|
|
if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS)
|
|
dhe = 1;
|
|
else if (kx == GNUTLS_KX_ECDHE_RSA || kx == GNUTLS_KX_ECDHE_ECDSA)
|
|
ecdh = 1;
|
|
|
|
/* Now print some info on the remote certificate */
|
|
if (gnutls_certificate_type_get (session) == GNUTLS_CRT_X509) {
|
|
gnutls_datum_t cinfo;
|
|
|
|
cert_list = gnutls_certificate_get_peers (session, &cert_list_size);
|
|
|
|
LOG_D("\t Peer provided %d certificates.", cert_list_size);
|
|
|
|
if (cert_list_size > 0)
|
|
{
|
|
int ret;
|
|
|
|
/* we only print information about the first certificate.
|
|
*/
|
|
gnutls_x509_crt_init (&cert);
|
|
|
|
gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER);
|
|
|
|
LOG_A("\t Certificate info:");
|
|
|
|
/* This is the preferred way of printing short information about
|
|
a certificate. */
|
|
|
|
ret = gnutls_x509_crt_print (cert, GNUTLS_CRT_PRINT_ONELINE, &cinfo);
|
|
if (ret == 0)
|
|
{
|
|
LOG_D("\t\t%s", cinfo.data);
|
|
gnutls_free (cinfo.data);
|
|
}
|
|
|
|
if (conn->cc_tls_para.cn) {
|
|
if (!gnutls_x509_crt_check_hostname (cert, conn->cc_tls_para.cn)) {
|
|
LOG_E("\tTLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
|
|
LOG_E("\t - The certificate hostname does not match '%s'", conn->cc_tls_para.cn);
|
|
gnutls_x509_crt_deinit (cert);
|
|
return GNUTLS_E_CERTIFICATE_ERROR;
|
|
}
|
|
|
|
}
|
|
|
|
hostname_verified = 1;
|
|
|
|
gnutls_x509_crt_deinit (cert);
|
|
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
LOG_E("\t - unknown session type (%d)", cred);
|
|
|
|
} /* switch */
|
|
|
|
if (ecdh != 0)
|
|
LOG_D("\t - Ephemeral ECDH using curve %s",
|
|
gnutls_ecc_curve_get_name (gnutls_ecc_curve_get (session)));
|
|
else if (dhe != 0)
|
|
LOG_D("\t - Ephemeral DH using prime of %d bits",
|
|
gnutls_dh_get_prime_bits (session));
|
|
|
|
/* print the protocol's name (ie TLS 1.0)
|
|
*/
|
|
tmp = gnutls_protocol_get_name (gnutls_protocol_get_version (session));
|
|
LOG_D("\t - Protocol: %s", tmp);
|
|
|
|
/* print the certificate type of the peer.
|
|
* ie X.509
|
|
*/
|
|
tmp = gnutls_certificate_type_get_name (gnutls_certificate_type_get (session));
|
|
LOG_D("\t - Certificate Type: %s", tmp);
|
|
|
|
/* print the compression algorithm (if any)
|
|
*/
|
|
tmp = gnutls_compression_get_name (gnutls_compression_get (session));
|
|
LOG_D("\t - Compression: %s", tmp);
|
|
|
|
/* print the name of the cipher used.
|
|
* ie 3DES.
|
|
*/
|
|
tmp = gnutls_cipher_get_name (gnutls_cipher_get (session));
|
|
LOG_D("\t - Cipher: %s", tmp);
|
|
|
|
/* Print the MAC algorithms name.
|
|
* ie SHA1
|
|
*/
|
|
tmp = gnutls_mac_get_name (gnutls_mac_get (session));
|
|
LOG_D("\t - MAC: %s", tmp);
|
|
|
|
#endif /* DEBUG */
|
|
|
|
/* This verification function uses the trusted CAs in the credentials
|
|
* structure. So you must have installed one or more CA certificates.
|
|
*/
|
|
CHECK_GNUTLS_DO( gnutls_certificate_verify_peers2 (session, &status), return GNUTLS_E_CERTIFICATE_ERROR );
|
|
if (status & GNUTLS_CERT_INVALID) {
|
|
LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
|
|
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
|
|
LOG_E(" - The certificate hasn't got a known issuer.");
|
|
|
|
if (status & GNUTLS_CERT_REVOKED)
|
|
LOG_E(" - The certificate has been revoked.");
|
|
|
|
if (status & GNUTLS_CERT_EXPIRED)
|
|
LOG_E(" - The certificate has expired.");
|
|
|
|
if (status & GNUTLS_CERT_NOT_ACTIVATED)
|
|
LOG_E(" - The certificate is not yet activated.");
|
|
}
|
|
if (status & GNUTLS_CERT_INVALID)
|
|
{
|
|
return GNUTLS_E_CERTIFICATE_ERROR;
|
|
}
|
|
|
|
/* Up to here the process is the same for X.509 certificates and
|
|
* OpenPGP keys. From now on X.509 certificates are assumed. This can
|
|
* be easily extended to work with openpgp keys as well.
|
|
*/
|
|
if ((!hostname_verified) && (conn->cc_tls_para.cn)) {
|
|
if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) {
|
|
LOG_E("TLS: Remote credentials are not x509, rejected on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
|
|
return GNUTLS_E_CERTIFICATE_ERROR;
|
|
}
|
|
|
|
CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return GNUTLS_E_CERTIFICATE_ERROR );
|
|
|
|
cert_list = gnutls_certificate_get_peers (session, &cert_list_size);
|
|
CHECK_PARAMS_DO( cert_list, return GNUTLS_E_CERTIFICATE_ERROR );
|
|
|
|
CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER), return GNUTLS_E_CERTIFICATE_ERROR );
|
|
|
|
if (!gnutls_x509_crt_check_hostname (cert, conn->cc_tls_para.cn)) {
|
|
LOG_E("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :", conn->cc_socket, conn->cc_remid, conn->cc_id);
|
|
LOG_E(" - The certificate hostname does not match '%s'", conn->cc_tls_para.cn);
|
|
gnutls_x509_crt_deinit (cert);
|
|
return GNUTLS_E_CERTIFICATE_ERROR;
|
|
}
|
|
|
|
gnutls_x509_crt_deinit (cert);
|
|
}
|
|
|
|
/* notify gnutls to continue handshake normally */
|
|
return 0;
|
|
}
|
|
|
|
#endif /* GNUTLS_VERSION_300 */
|
|
|
|
static int fd_cnx_may_dtls(struct cnxctx * conn) {
|
|
#ifndef DISABLE_SCTP
|
|
if ((conn->cc_proto == IPPROTO_SCTP) && (conn->cc_tls_para.algo == ALGO_HANDSHAKE_DEFAULT))
|
|
return 1;
|
|
#endif /* DISABLE_SCTP */
|
|
return 0;
|
|
}
|
|
|
|
#ifndef DISABLE_SCTP
|
|
static int fd_cnx_uses_dtls(struct cnxctx * conn) {
|
|
return fd_cnx_may_dtls(conn) && (fd_cnx_teststate(conn, CC_STATUS_TLS));
|
|
}
|
|
#endif /* DISABLE_SCTP */
|
|
|
|
/* TLS handshake a connection; no need to have called start_clear before. Reception is active if handhsake is successful */
|
|
int fd_cnx_handshake(struct cnxctx * conn, int mode, int algo, char * priority, void * alt_creds)
|
|
{
|
|
int dtls = 0;
|
|
|
|
TRACE_ENTRY( "%p %d %d %p %p", conn, mode, algo, priority, alt_creds);
|
|
CHECK_PARAMS( conn && (!fd_cnx_teststate(conn, CC_STATUS_TLS)) && ( (mode == GNUTLS_CLIENT) || (mode == GNUTLS_SERVER) ) && (!conn->cc_loop) );
|
|
|
|
/* Save the mode */
|
|
conn->cc_tls_para.mode = mode;
|
|
conn->cc_tls_para.algo = algo;
|
|
|
|
/* Cancel receiving thread if any -- it should already be terminated anyway, we just release the resources */
|
|
CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */);
|
|
|
|
/* Once TLS handshake is done, we don't stop after the first message */
|
|
conn->cc_loop = 1;
|
|
|
|
dtls = fd_cnx_may_dtls(conn);
|
|
|
|
/* Prepare the master session credentials and priority */
|
|
CHECK_FCT( fd_tls_prepare(&conn->cc_tls_para.session, mode, dtls, priority, alt_creds) );
|
|
|
|
/* Special case: multi-stream TLS is not natively managed in GNU TLS, we use a wrapper library */
|
|
if ((!dtls) && (conn->cc_sctp_para.pairs > 1)) {
|
|
#ifdef DISABLE_SCTP
|
|
ASSERT(0);
|
|
CHECK_FCT( ENOTSUP );
|
|
#else /* DISABLE_SCTP */
|
|
/* Initialize the wrapper, start the demux thread */
|
|
CHECK_FCT( fd_sctp3436_init(conn) );
|
|
#endif /* DISABLE_SCTP */
|
|
} else {
|
|
/* Set the transport pointer passed to push & pull callbacks */
|
|
GNUTLS_TRACE( gnutls_transport_set_ptr( conn->cc_tls_para.session, (gnutls_transport_ptr_t) conn ) );
|
|
|
|
/* Set the push and pull callbacks */
|
|
if (!dtls) {
|
|
#ifdef GNUTLS_VERSION_300
|
|
GNUTLS_TRACE( gnutls_transport_set_pull_timeout_function( conn->cc_tls_para.session, (void *)fd_cnx_s_select ) );
|
|
#endif /* GNUTLS_VERSION_300 */
|
|
GNUTLS_TRACE( gnutls_transport_set_pull_function(conn->cc_tls_para.session, (void *)fd_cnx_s_recv) );
|
|
#ifndef GNUTLS_VERSION_212
|
|
GNUTLS_TRACE( gnutls_transport_set_push_function(conn->cc_tls_para.session, (void *)fd_cnx_s_send) );
|
|
#else /* GNUTLS_VERSION_212 */
|
|
GNUTLS_TRACE( gnutls_transport_set_vec_push_function(conn->cc_tls_para.session, (void *)fd_cnx_s_sendv) );
|
|
#endif /* GNUTLS_VERSION_212 */
|
|
} else {
|
|
TODO("DTLS push/pull functions");
|
|
return ENOTSUP;
|
|
}
|
|
}
|
|
|
|
/* additional initialization for gnutls 3.x */
|
|
#ifdef GNUTLS_VERSION_300
|
|
/* the verify function has already been set in the global initialization in config.c */
|
|
|
|
/* fd_tls_verify_credentials_2 uses the connection */
|
|
gnutls_session_set_ptr (conn->cc_tls_para.session, (void *) conn);
|
|
|
|
if ((conn->cc_tls_para.cn != NULL) && (mode == GNUTLS_CLIENT)) {
|
|
/* this might allow virtual hosting on the remote peer */
|
|
CHECK_GNUTLS_DO( gnutls_server_name_set (conn->cc_tls_para.session, GNUTLS_NAME_DNS, conn->cc_tls_para.cn, strlen(conn->cc_tls_para.cn)), /* ignore failure */);
|
|
}
|
|
|
|
#endif /* GNUTLS_VERSION_300 */
|
|
|
|
#ifdef GNUTLS_VERSION_310
|
|
GNUTLS_TRACE( gnutls_handshake_set_timeout( conn->cc_tls_para.session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT));
|
|
#endif /* GNUTLS_VERSION_310 */
|
|
|
|
/* Mark the connection as protected from here, so that the gnutls credentials will be freed */
|
|
fd_cnx_addstate(conn, CC_STATUS_TLS);
|
|
|
|
/* Handshake master session */
|
|
{
|
|
int ret;
|
|
|
|
CHECK_GNUTLS_DO( ret = gnutls_handshake(conn->cc_tls_para.session),
|
|
{
|
|
if (TRACE_BOOL(INFO)) {
|
|
fd_log_debug("TLS Handshake failed on socket %d (%s) : %s", conn->cc_socket, conn->cc_id, gnutls_strerror(ret));
|
|
}
|
|
fd_cnx_markerror(conn);
|
|
return EINVAL;
|
|
} );
|
|
|
|
#ifndef GNUTLS_VERSION_300
|
|
/* Now verify the remote credentials are valid -- only simple tests here */
|
|
CHECK_FCT_DO( fd_tls_verify_credentials(conn->cc_tls_para.session, conn, 1),
|
|
{
|
|
CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_RDWR), );
|
|
fd_cnx_markerror(conn);
|
|
return EINVAL;
|
|
});
|
|
#endif /* GNUTLS_VERSION_300 */
|
|
}
|
|
|
|
/* Multi-stream TLS: handshake other streams as well */
|
|
if ((!dtls) && (conn->cc_sctp_para.pairs > 1)) {
|
|
#ifndef DISABLE_SCTP
|
|
/* Start reading the messages from the master session. That way, if the remote peer closed, we are not stuck inside handshake */
|
|
CHECK_FCT(fd_sctp3436_startthreads(conn, 0));
|
|
|
|
/* Resume all additional sessions from the master one. */
|
|
CHECK_FCT(fd_sctp3436_handshake_others(conn, priority, alt_creds));
|
|
|
|
/* Start decrypting the messages from all threads and queuing them in target queue */
|
|
CHECK_FCT(fd_sctp3436_startthreads(conn, 1));
|
|
#endif /* DISABLE_SCTP */
|
|
} else {
|
|
/* Start decrypting the data */
|
|
if (!dtls) {
|
|
CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_tls_single, conn ) );
|
|
} else {
|
|
TODO("Signal the dtls_push function that multiple streams can be used from this point.");
|
|
TODO("Create DTLS rcvthr (must reassembly based on seq numbers & stream id ?)");
|
|
return ENOTSUP;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Retrieve TLS credentials of the remote peer, after handshake */
|
|
int fd_cnx_getcred(struct cnxctx * conn, const gnutls_datum_t **cert_list, unsigned int *cert_list_size)
|
|
{
|
|
TRACE_ENTRY("%p %p %p", conn, cert_list, cert_list_size);
|
|
CHECK_PARAMS( conn && fd_cnx_teststate(conn, CC_STATUS_TLS) && cert_list && cert_list_size );
|
|
|
|
/* This function only works for X.509 certificates. */
|
|
CHECK_PARAMS( gnutls_certificate_type_get (conn->cc_tls_para.session) == GNUTLS_CRT_X509 );
|
|
|
|
GNUTLS_TRACE( *cert_list = gnutls_certificate_get_peers (conn->cc_tls_para.session, cert_list_size) );
|
|
if (*cert_list == NULL) {
|
|
TRACE_DEBUG(INFO, "No certificate was provided by remote peer / an error occurred.");
|
|
return EINVAL;
|
|
}
|
|
|
|
TRACE_DEBUG( FULL, "Saved certificate chain (%d certificates) in peer structure.", *cert_list_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Receive next message. if timeout is not NULL, wait only until timeout. This function only pulls from a queue, mgr thread is filling that queue aynchrounously. */
|
|
/* if the altfifo has been set on this conn object, this function must not be called */
|
|
int fd_cnx_receive(struct cnxctx * conn, struct timespec * timeout, unsigned char **buf, size_t * len)
|
|
{
|
|
int ev;
|
|
size_t ev_sz;
|
|
void * ev_data;
|
|
|
|
TRACE_ENTRY("%p %p %p %p", conn, timeout, buf, len);
|
|
CHECK_PARAMS(conn && (conn->cc_socket > 0) && buf && len);
|
|
CHECK_PARAMS(conn->cc_rcvthr != (pthread_t)NULL);
|
|
CHECK_PARAMS(conn->cc_alt == NULL);
|
|
|
|
/* Now, pull the first event */
|
|
get_next:
|
|
if (timeout) {
|
|
CHECK_FCT( fd_event_timedget(conn->cc_incoming, timeout, FDEVP_PSM_TIMEOUT, &ev, &ev_sz, &ev_data) );
|
|
} else {
|
|
CHECK_FCT( fd_event_get(conn->cc_incoming, &ev, &ev_sz, &ev_data) );
|
|
}
|
|
|
|
switch (ev) {
|
|
case FDEVP_CNX_MSG_RECV:
|
|
/* We got one */
|
|
*len = ev_sz;
|
|
*buf = ev_data;
|
|
return 0;
|
|
|
|
case FDEVP_PSM_TIMEOUT:
|
|
TRACE_DEBUG(FULL, "Timeout event received");
|
|
return ETIMEDOUT;
|
|
|
|
case FDEVP_CNX_EP_CHANGE:
|
|
/* We ignore this event */
|
|
goto get_next;
|
|
|
|
case FDEVP_CNX_ERROR:
|
|
TRACE_DEBUG(FULL, "Received ERROR event on the connection");
|
|
return ENOTCONN;
|
|
}
|
|
|
|
TRACE_DEBUG(INFO, "Received unexpected event %d (%s)", ev, fd_pev_str(ev));
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Where the events are sent */
|
|
struct fifo * fd_cnx_target_queue(struct cnxctx * conn)
|
|
{
|
|
struct fifo *q;
|
|
CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
|
|
q = conn->cc_alt ?: conn->cc_incoming;
|
|
CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
|
|
return q;
|
|
}
|
|
|
|
/* Set an alternate FIFO list to send FDEVP_CNX_* events to */
|
|
int fd_cnx_recv_setaltfifo(struct cnxctx * conn, struct fifo * alt_fifo)
|
|
{
|
|
int ret;
|
|
TRACE_ENTRY( "%p %p", conn, alt_fifo );
|
|
CHECK_PARAMS( conn && alt_fifo && conn->cc_incoming );
|
|
|
|
/* The magic function does it all */
|
|
CHECK_POSIX_DO( pthread_mutex_lock(&state_lock), { ASSERT(0); } );
|
|
CHECK_FCT_DO( ret = fd_fifo_move( conn->cc_incoming, alt_fifo, &conn->cc_alt ), );
|
|
CHECK_POSIX_DO( pthread_mutex_unlock(&state_lock), { ASSERT(0); } );
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Send function when no multi-stream is involved, or sending on stream #0 (send() always use stream 0)*/
|
|
static int send_simple(struct cnxctx * conn, unsigned char * buf, size_t len)
|
|
{
|
|
ssize_t ret;
|
|
size_t sent = 0;
|
|
TRACE_ENTRY("%p %p %zd", conn, buf, len);
|
|
do {
|
|
if (fd_cnx_teststate(conn, CC_STATUS_TLS)) {
|
|
CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_tls_para.session, buf + sent, len - sent), );
|
|
} else {
|
|
struct iovec iov;
|
|
iov.iov_base = buf + sent;
|
|
iov.iov_len = len - sent;
|
|
CHECK_SYS_DO( ret = fd_cnx_s_sendv(conn, &iov, 1), );
|
|
}
|
|
if (ret <= 0)
|
|
return ENOTCONN;
|
|
|
|
sent += ret;
|
|
} while ( sent < len );
|
|
return 0;
|
|
}
|
|
|
|
/* Send a message -- this is synchronous -- and we assume it's never called by several threads at the same time (on the same conn), so we don't protect. */
|
|
int fd_cnx_send(struct cnxctx * conn, unsigned char * buf, size_t len)
|
|
{
|
|
TRACE_ENTRY("%p %p %zd", conn, buf, len);
|
|
|
|
CHECK_PARAMS(conn && (conn->cc_socket > 0) && (! fd_cnx_teststate(conn, CC_STATUS_ERROR)) && buf && len);
|
|
|
|
TRACE_DEBUG(FULL, "Sending %zdb %sdata on connection %s", len, fd_cnx_teststate(conn, CC_STATUS_TLS) ? "TLS-protected ":"", conn->cc_id);
|
|
|
|
switch (conn->cc_proto) {
|
|
case IPPROTO_TCP:
|
|
CHECK_FCT( send_simple(conn, buf, len) );
|
|
break;
|
|
|
|
#ifndef DISABLE_SCTP
|
|
case IPPROTO_SCTP: {
|
|
int dtls = fd_cnx_uses_dtls(conn);
|
|
if (!dtls) {
|
|
int stream = 0;
|
|
if (conn->cc_sctp_para.unordered) {
|
|
int limit;
|
|
if (fd_cnx_teststate(conn, CC_STATUS_TLS))
|
|
limit = conn->cc_sctp_para.pairs;
|
|
else
|
|
limit = conn->cc_sctp_para.str_out;
|
|
|
|
if (limit > 1) {
|
|
conn->cc_sctp_para.next += 1;
|
|
conn->cc_sctp_para.next %= limit;
|
|
stream = conn->cc_sctp_para.next;
|
|
}
|
|
}
|
|
|
|
if (stream == 0) {
|
|
/* We can use default function, it sends over stream #0 */
|
|
CHECK_FCT( send_simple(conn, buf, len) );
|
|
} else {
|
|
if (!fd_cnx_teststate(conn, CC_STATUS_TLS)) {
|
|
struct iovec iov;
|
|
iov.iov_base = buf;
|
|
iov.iov_len = len;
|
|
|
|
CHECK_SYS_DO( fd_sctp_sendstrv(conn, stream, &iov, 1), { fd_cnx_markerror(conn); return ENOTCONN; } );
|
|
} else {
|
|
/* push the data to the appropriate session */
|
|
ssize_t ret;
|
|
size_t sent = 0;
|
|
ASSERT(conn->cc_sctp3436_data.array != NULL);
|
|
do {
|
|
CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_sctp3436_data.array[stream].session, buf + sent, len - sent), );
|
|
if (ret <= 0)
|
|
return ENOTCONN;
|
|
|
|
sent += ret;
|
|
} while ( sent < len );
|
|
}
|
|
}
|
|
} else {
|
|
/* DTLS */
|
|
/* Multistream is handled at lower layer in the push/pull function */
|
|
CHECK_FCT( send_simple(conn, buf, len) );
|
|
}
|
|
}
|
|
break;
|
|
#endif /* DISABLE_SCTP */
|
|
|
|
default:
|
|
TRACE_DEBUG(INFO, "Unknown protocol: %d", conn->cc_proto);
|
|
ASSERT(0);
|
|
return ENOTSUP; /* or EINVAL... */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**************************************/
|
|
/* Destruction of connection */
|
|
/**************************************/
|
|
|
|
/* Destroy a conn structure, and shutdown the socket */
|
|
void fd_cnx_destroy(struct cnxctx * conn)
|
|
{
|
|
TRACE_ENTRY("%p", conn);
|
|
|
|
CHECK_PARAMS_DO(conn, return);
|
|
|
|
fd_cnx_addstate(conn, CC_STATUS_CLOSING);
|
|
|
|
/* Initiate shutdown of the TLS session(s): call gnutls_bye(WR), then read until error */
|
|
if (fd_cnx_teststate(conn, CC_STATUS_TLS)) {
|
|
#ifndef DISABLE_SCTP
|
|
int dtls = fd_cnx_uses_dtls(conn);
|
|
if ((!dtls) && (conn->cc_sctp_para.pairs > 1)) {
|
|
if (! fd_cnx_teststate(conn, CC_STATUS_ERROR )) {
|
|
/* Bye on master session */
|
|
CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) );
|
|
}
|
|
|
|
if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) {
|
|
/* and other stream pairs */
|
|
fd_sctp3436_bye(conn);
|
|
}
|
|
|
|
if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) {
|
|
/* Now wait for all decipher threads to terminate */
|
|
fd_sctp3436_waitthreadsterm(conn);
|
|
} else {
|
|
/* Abord the threads, the connection is dead already */
|
|
fd_sctp3436_stopthreads(conn);
|
|
}
|
|
|
|
/* Deinit gnutls resources */
|
|
fd_sctp3436_gnutls_deinit_others(conn);
|
|
if (conn->cc_tls_para.session) {
|
|
GNUTLS_TRACE( gnutls_deinit(conn->cc_tls_para.session) );
|
|
conn->cc_tls_para.session = NULL;
|
|
}
|
|
|
|
/* Destroy the wrapper (also stops the demux thread) */
|
|
fd_sctp3436_destroy(conn);
|
|
|
|
} else {
|
|
#endif /* DISABLE_SCTP */
|
|
/* We are TLS, but not using the sctp3436 wrapper layer */
|
|
if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) {
|
|
/* Master session */
|
|
CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) );
|
|
}
|
|
|
|
if (! fd_cnx_teststate(conn, CC_STATUS_ERROR ) ) {
|
|
/* In this case, just wait for thread rcvthr_tls_single to terminate */
|
|
if (conn->cc_rcvthr != (pthread_t)NULL) {
|
|
CHECK_POSIX_DO( pthread_join(conn->cc_rcvthr, NULL), /* continue */ );
|
|
conn->cc_rcvthr = (pthread_t)NULL;
|
|
}
|
|
} else {
|
|
/* Cancel the receiver thread in case it did not already terminate */
|
|
CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ );
|
|
}
|
|
|
|
/* Free the resources of the TLS session */
|
|
if (conn->cc_tls_para.session) {
|
|
GNUTLS_TRACE( gnutls_deinit(conn->cc_tls_para.session) );
|
|
conn->cc_tls_para.session = NULL;
|
|
}
|
|
#ifndef DISABLE_SCTP
|
|
}
|
|
#endif /* DISABLE_SCTP */
|
|
}
|
|
|
|
/* Terminate the thread in case it is not done yet -- is there any such case left ?*/
|
|
CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ );
|
|
|
|
/* Shut the connection down */
|
|
if (conn->cc_socket > 0) {
|
|
shutdown(conn->cc_socket, SHUT_RDWR);
|
|
close(conn->cc_socket);
|
|
conn->cc_socket = -1;
|
|
}
|
|
|
|
/* Empty and destroy FIFO list */
|
|
if (conn->cc_incoming) {
|
|
fd_event_destroy( &conn->cc_incoming, free );
|
|
}
|
|
|
|
/* Free the object */
|
|
free(conn);
|
|
|
|
/* Done! */
|
|
return;
|
|
}
|