| /********************************************************************************************************* |
| * 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; |
| } |