Initial commit
Change-Id: I6a4444e3c193dae437cd7929f4c39aba7b749efa
diff --git a/libfdcore/CMakeLists.txt b/libfdcore/CMakeLists.txt
new file mode 100644
index 0000000..503437d
--- /dev/null
+++ b/libfdcore/CMakeLists.txt
@@ -0,0 +1,85 @@
+# The subproject name
+Project("freeDiameter core library" C)
+
+# Configuration for newer cmake
+cmake_policy(VERSION 2.6)
+if (POLICY CMP0022)
+ cmake_policy(SET CMP0022 OLD)
+endif (POLICY CMP0022)
+
+# Configuration parser
+BISON_FILE(fdd.y)
+FLEX_FILE(fdd.l)
+SET_SOURCE_FILES_PROPERTIES(lex.fdd.c fdd.tab.c PROPERTIES COMPILE_FLAGS "-I ${CMAKE_CURRENT_SOURCE_DIR}")
+
+# List of source files
+SET(FDCORE_SRC
+ fdcore-internal.h
+ apps.c
+ cnxctx.h
+ config.c
+ core.c
+ cnxctx.c
+ endpoints.c
+ events.c
+ extensions.c
+ fifo_stats.c
+ hooks.c
+ dict_base_proto.c
+ messages.c
+ queues.c
+ peers.c
+ p_ce.c
+ p_cnx.c
+ p_dw.c
+ p_dp.c
+ p_expiry.c
+ p_out.c
+ p_psm.c
+ p_sr.c
+ routing_dispatch.c
+ server.c
+ tcp.c
+ version.c
+ )
+
+IF(NOT DISABLE_SCTP)
+ SET(FDCORE_SRC ${FDCORE_SRC} sctp.c sctp3436.c)
+ENDIF(NOT DISABLE_SCTP)
+
+SET(FDCORE_GEN_SRC
+ lex.fdd.c
+ fdd.tab.c
+ fdd.tab.h
+ )
+
+# Save the list of files for the tests
+SET(FDCORE_SRC ${FDCORE_SRC} PARENT_SCOPE)
+SET(FDCORE_GEN_SRC ${FDCORE_GEN_SRC} PARENT_SCOPE)
+
+# Include path
+INCLUDE_DIRECTORIES(${LFDCORE_INCLUDES})
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
+
+# Build the executable
+ADD_LIBRARY(libfdcore SHARED ${FDCORE_SRC} ${FDCORE_GEN_SRC})
+ADD_DEPENDENCIES(libfdcore version_information)
+
+# Avoid the liblib name, and set the version
+SET_TARGET_PROPERTIES(libfdcore PROPERTIES
+ OUTPUT_NAME "fdcore"
+ SOVERSION ${FD_PROJECT_VERSION_API}
+ VERSION ${FD_PROJECT_VERSION_MAJOR}.${FD_PROJECT_VERSION_MINOR}.${FD_PROJECT_VERSION_REV}
+ LINK_INTERFACE_LIBRARIES "${LFDCORE_LINK_INTERFACES}")
+
+# The library itself needs other libraries
+LINK_DIRECTORIES(${CURRENT_BINARY_DIR}/../libfdproto)
+TARGET_LINK_LIBRARIES(libfdcore libfdproto ${LFDCORE_LIBS})
+
+
+####
+## INSTALL section ##
+
+INSTALL(TARGETS libfdcore
+ LIBRARY DESTINATION ${INSTALL_LIBRARY_SUFFIX}
+ COMPONENT freeDiameter-common)
diff --git a/libfdcore/apps.c b/libfdcore/apps.c
new file mode 100644
index 0000000..a0811d9
--- /dev/null
+++ b/libfdcore/apps.c
@@ -0,0 +1,152 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2011, 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"
+
+/* Merge information into a list of apps */
+int fd_app_merge(struct fd_list * list, application_id_t aid, vendor_id_t vid, int auth, int acct)
+{
+ struct fd_list * li;
+ int skip = 0;
+
+ /* List is ordered by appid. Avoid duplicates */
+ for (li = list; li->next != list; li = li->next) {
+ struct fd_app * na = (struct fd_app *)(li->next);
+ if (na->appid < aid)
+ continue;
+
+ if (na->appid > aid)
+ break;
+
+ /* Otherwise, we merge with existing entry -- ignore vendor id in this case */
+ skip = 1;
+
+ if (auth)
+ na->flags.auth = 1;
+ if (acct)
+ na->flags.acct = 1;
+ break;
+ }
+
+ if (!skip) {
+ struct fd_app * new = NULL;
+
+ CHECK_MALLOC( new = malloc(sizeof(struct fd_app)) );
+ memset(new, 0, sizeof(struct fd_app));
+ fd_list_init(&new->chain, NULL);
+ new->flags.auth = (auth ? 1 : 0);
+ new->flags.acct = (acct ? 1 : 0);
+ new->vndid = vid;
+ new->appid = aid;
+ fd_list_insert_after(li, &new->chain);
+ }
+
+ return 0;
+}
+
+/* Check if a given application id is in a list */
+int fd_app_check(struct fd_list * list, application_id_t aid, struct fd_app **detail)
+{
+ struct fd_list * li;
+
+ TRACE_ENTRY("%p %d %p", list, aid, detail);
+ CHECK_PARAMS(list && detail);
+
+ *detail = NULL;
+
+ /* Search in the list */
+ for (li = list->next; li != list; li = li->next) {
+ struct fd_app * a = (struct fd_app *)li;
+ if (a->appid < aid)
+ continue;
+
+ if (a->appid == aid)
+ *detail = a;
+ break;
+ }
+
+ return 0;
+}
+
+/* Check if two lists have at least one common application */
+int fd_app_check_common(struct fd_list * list1, struct fd_list * list2, int * common_found)
+{
+ struct fd_list * li1, *li2;
+
+ TRACE_ENTRY("%p %p %p", list1, list2, common_found);
+ CHECK_PARAMS( list1 && list2 && common_found );
+
+ /* Both lists are ordered, so advance both pointers at the same time */
+ for (li1 = list1->next, li2 = list2->next; (li1 != list1) && (li2 != list2); ) {
+ struct fd_app * a1 = (struct fd_app *)li1, *a2 = (struct fd_app *)li2;
+ if (a1->appid < a2->appid) {
+ li1 = li1->next;
+ continue;
+ }
+ if (a1->appid > a2->appid) {
+ li2 = li2->next;
+ continue;
+ }
+ /* They are equal, compare the applications */
+ if ((a1->flags.auth && a2->flags.auth) || (a1->flags.acct && a2->flags.acct)) {
+ /* found! */
+ *common_found = 1;
+ return 0;
+ }
+
+ /* This application is not common, advance both lists */
+ li1 = li1->next;
+ li2 = li2->next;
+ }
+
+ /* We did not find a common app */
+ *common_found = 0;
+ return 0;
+}
+
+/* Remove the apps from a list */
+int fd_app_empty(struct fd_list * list)
+{
+ TRACE_ENTRY("%p", list);
+ CHECK_PARAMS( list );
+
+ while (!FD_IS_LIST_EMPTY(list)) {
+ struct fd_list * li = list->next;
+ fd_list_unlink(li);
+ free(li);
+ }
+
+ return 0;
+}
diff --git a/libfdcore/cnxctx.c b/libfdcore/cnxctx.c
new file mode 100644
index 0000000..1fc336e
--- /dev/null
+++ b/libfdcore/cnxctx.c
@@ -0,0 +1,2045 @@
+/*********************************************************************************************************
+* 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;
+}
diff --git a/libfdcore/cnxctx.h b/libfdcore/cnxctx.h
new file mode 100644
index 0000000..dcc4bea
--- /dev/null
+++ b/libfdcore/cnxctx.h
@@ -0,0 +1,153 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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. *
+*********************************************************************************************************/
+
+/* This file contains the definitions for internal use in the connection context files */
+
+#ifndef _CNXCTX_H
+#define _CNXCTX_H
+
+/* Maximum time we allow a connection to be blocked because of head-of-the-line buffers. After this delay, connection is considered in error. */
+#define MAX_HOTL_BLOCKING_TIME 1000 /* ms */
+
+/* The connection context structure */
+struct cnxctx {
+ char cc_id[60]; /* The name of this connection. the first 5 chars are reserved for flags display (cc_state). */
+ char cc_remid[60]; /* Id of remote peer */
+
+ int cc_socket; /* The socket object of the connection -- <=0 if no socket is created */
+
+ int cc_family; /* AF_INET or AF_INET6 (mixed) */
+ int cc_proto; /* IPPROTO_TCP or IPPROTO_SCTP */
+
+ uint32_t cc_state; /* True if the object is being destroyed: we don't send events anymore. access with fd_cnx_getstate() */
+ #define CC_STATUS_CLOSING 1
+ #define CC_STATUS_ERROR 2
+ #define CC_STATUS_SIGNALED 4
+ #define CC_STATUS_TLS 8
+
+ pthread_t cc_rcvthr; /* thread for receiving messages on the connection */
+ int cc_loop; /* tell the thread if it loops or stops after the first message is received */
+
+ struct fifo * cc_incoming; /* FIFO queue of events received on the connection, FDEVP_CNX_* */
+ struct fifo * cc_alt; /* alternate fifo to send FDEVP_CNX_* events to. */
+
+ /* If cc_tls == true */
+ struct {
+ DiamId_t cn; /* If not NULL, remote certif will be checked to match this Common Name */
+ int mode; /* GNUTLS_CLIENT / GNUTLS_SERVER */
+ int algo; /* ALGO_HANDSHAKE_DEFAULT / ALGO_HANDSHAKE_3436 */
+ gnutls_session_t session; /* Session object (stream #0 in case of SCTP) */
+ } cc_tls_para;
+
+ /* If cc_proto == SCTP */
+ struct {
+ uint16_t str_out; /* Out streams */
+ uint16_t str_in; /* In streams */
+ uint16_t pairs; /* max number of pairs ( = min(in, out)) */
+ uint16_t next; /* # of stream the next message will be sent to */
+ int unordered; /* boolean telling if use of streams > 0 is permitted */
+ } cc_sctp_para;
+
+ /* If both conditions */
+ struct {
+ struct sctp3436_ctx *array; /* an array of cc_sctp_para.pairs elements -- the #0 is special (session is outside)*/
+ struct sr_store *sess_store; /* Session data of the master session, to resume the children sessions */
+ } cc_sctp3436_data;
+};
+
+void fd_cnx_markerror(struct cnxctx * conn);
+uint32_t fd_cnx_getstate(struct cnxctx * conn);
+int fd_cnx_teststate(struct cnxctx * conn, uint32_t flag);
+void fd_cnx_addstate(struct cnxctx * conn, uint32_t orstate);
+void fd_cnx_setstate(struct cnxctx * conn, uint32_t abstate);
+struct fifo * fd_cnx_target_queue(struct cnxctx * conn);
+
+
+/* Socket */
+ssize_t fd_cnx_s_recv(struct cnxctx * conn, void *buffer, size_t length);
+void fd_cnx_s_setto(int sock);
+
+/* TLS */
+int fd_tls_rcvthr_core(struct cnxctx * conn, gnutls_session_t session);
+int fd_tls_prepare(gnutls_session_t * session, int mode, int dtls, char * priority, void * alt_creds);
+#ifndef GNUTLS_VERSION_300
+int fd_tls_verify_credentials(gnutls_session_t session, struct cnxctx * conn, int verbose);
+#endif /* GNUTLS_VERSION_300 */
+
+/* TCP */
+int fd_tcp_create_bind_server( int * sock, sSA * sa, socklen_t salen );
+int fd_tcp_listen( int sock );
+int fd_tcp_client( int *sock, sSA * sa, socklen_t salen );
+int fd_tcp_get_local_ep(int sock, sSS * ss, socklen_t *sl);
+int fd_tcp_get_remote_ep(int sock, sSS * ss, socklen_t *sl);
+
+#ifndef DISABLE_SCTP
+/* SCTP */
+int fd_sctp_create_bind_server( int * sock, int family, struct fd_list * list, uint16_t port );
+int fd_sctp_listen( int sock );
+int fd_sctp_client( int *sock, int no_ip6, uint16_t port, struct fd_list * list );
+int fd_sctp_get_local_ep(int sock, struct fd_list * list);
+int fd_sctp_get_remote_ep(int sock, struct fd_list * list);
+int fd_sctp_get_str_info( int sock, uint16_t *in, uint16_t *out, sSS *primary );
+ssize_t fd_sctp_sendstrv(struct cnxctx * conn, uint16_t strid, const struct iovec *iov, int iovcnt);
+int fd_sctp_recvmeta(struct cnxctx * conn, uint16_t * strid, uint8_t ** buf, size_t * len, int *event);
+
+/* TLS over SCTP (multi-stream) */
+struct sctp3436_ctx {
+ struct cnxctx *parent; /* for info such as socket, conn name, event list */
+ uint16_t strid; /* Stream # of this session */
+ struct fifo *raw_recv; /* Raw data received on this stream, for demux */
+ struct {
+ uint8_t *buf;
+ size_t bufsz;
+ size_t offset;
+ } partial; /* If the pull function did not read the full content of first message in raw, it stores it here for next read call. */
+ pthread_t thr; /* Thread to decrypt raw data in this pair of streams */
+ gnutls_session_t session; /* TLS context using this pair of streams -- except if strid == 0, in that case session is outside the array */
+};
+
+int fd_sctp3436_init(struct cnxctx * conn);
+int fd_sctp3436_handshake_others(struct cnxctx * conn, char * priority, void * alt_creds);
+int fd_sctp3436_startthreads(struct cnxctx * conn, int others);
+void fd_sctp3436_bye(struct cnxctx * conn);
+void fd_sctp3436_waitthreadsterm(struct cnxctx * conn);
+void fd_sctp3436_gnutls_deinit_others(struct cnxctx * conn);
+void fd_sctp3436_stopthreads(struct cnxctx * conn);
+void fd_sctp3436_destroy(struct cnxctx * conn);
+
+#endif /* DISABLE_SCTP */
+
+#endif /* _CNXCTX_H */
+
diff --git a/libfdcore/config.c b/libfdcore/config.c
new file mode 100644
index 0000000..2a1a063
--- /dev/null
+++ b/libfdcore/config.c
@@ -0,0 +1,644 @@
+/*********************************************************************************************************
+* 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 <sys/stat.h>
+
+/* Configuration management */
+
+#ifndef GNUTLS_DEFAULT_PRIORITY
+# define GNUTLS_DEFAULT_PRIORITY "NORMAL"
+#endif /* GNUTLS_DEFAULT_PRIORITY */
+#ifndef GNUTLS_DEFAULT_DHBITS
+# define GNUTLS_DEFAULT_DHBITS 1024
+#endif /* GNUTLS_DEFAULT_DHBITS */
+
+/* Initialize the fd_g_config structure to default values -- it should already have been initialized to all-0 */
+int fd_conf_init()
+{
+ TRACE_ENTRY();
+
+ fd_g_config->cnf_eyec = EYEC_CONFIG;
+
+ fd_g_config->cnf_timer_tc = 30;
+ fd_g_config->cnf_timer_tw = 30;
+
+ fd_g_config->cnf_port = DIAMETER_PORT;
+ fd_g_config->cnf_port_tls = DIAMETER_SECURE_PORT;
+ fd_g_config->cnf_sctp_str = 30;
+ fd_g_config->cnf_thr_srv = 5;
+ fd_g_config->cnf_dispthr = 4;
+ fd_list_init(&fd_g_config->cnf_endpoints, NULL);
+ fd_list_init(&fd_g_config->cnf_apps, NULL);
+ #ifdef DISABLE_SCTP
+ fd_g_config->cnf_flags.no_sctp = 1;
+ #endif /* DISABLE_SCTP */
+
+ fd_g_config->cnf_orstateid = (uint32_t) time(NULL);
+
+ CHECK_FCT( fd_dict_init(&fd_g_config->cnf_dict) );
+ CHECK_FCT( fd_fifo_new(&fd_g_config->cnf_main_ev, 0) );
+
+ /* TLS parameters */
+ CHECK_GNUTLS_DO( gnutls_certificate_allocate_credentials (&fd_g_config->cnf_sec_data.credentials), return ENOMEM );
+ CHECK_GNUTLS_DO( gnutls_dh_params_init (&fd_g_config->cnf_sec_data.dh_cache), return ENOMEM );
+#ifdef GNUTLS_VERSION_300
+ CHECK_GNUTLS_DO( gnutls_x509_trust_list_init(&fd_g_config->cnf_sec_data.trustlist, 0), return ENOMEM );
+#endif /* GNUTLS_VERSION_300 */
+
+ return 0;
+}
+
+DECLARE_FD_DUMP_PROTOTYPE(fd_conf_dump)
+{
+ FD_DUMP_HANDLE_OFFSET();
+
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "freeDiameter configuration:\n"), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Default trace level .... : %+d\n", fd_g_debug_lvl), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Configuration file ..... : %s\n", fd_g_config->cnf_file), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Diameter Identity ...... : %s (l:%zi)\n", fd_g_config->cnf_diamid, fd_g_config->cnf_diamid_len), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Diameter Realm ......... : %s (l:%zi)\n", fd_g_config->cnf_diamrlm, fd_g_config->cnf_diamrlm_len), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Tc Timer ............... : %u\n", fd_g_config->cnf_timer_tc), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Tw Timer ............... : %u\n", fd_g_config->cnf_timer_tw), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Local port ............. : %hu\n", fd_g_config->cnf_port), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Local secure port ...... : %hu\n", fd_g_config->cnf_port_tls), return NULL);
+ if (fd_g_config->cnf_port_3436) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Local SCTP TLS port .... : %hu\n", fd_g_config->cnf_port_3436), return NULL);
+ }
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Number of SCTP streams . : %hu\n", fd_g_config->cnf_sctp_str), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Number of clients thr .. : %d\n", fd_g_config->cnf_thr_srv), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Number of app threads .. : %hu\n", fd_g_config->cnf_dispthr), return NULL);
+ if (FD_IS_LIST_EMPTY(&fd_g_config->cnf_endpoints)) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Local endpoints ........ : Default (use all available)\n"), return NULL);
+ } else {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Local endpoints ........ : "), return NULL);
+ CHECK_MALLOC_DO( fd_ep_dump( FD_DUMP_STD_PARAMS, 0, 0, &fd_g_config->cnf_endpoints ), return NULL);
+ }
+ if (FD_IS_LIST_EMPTY(&fd_g_config->cnf_apps)) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Local applications ..... : (none)"), return NULL);
+ } else {
+ struct fd_list * li = fd_g_config->cnf_apps.next;
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Local applications ..... : "), return NULL);
+ while (li != &fd_g_config->cnf_apps) {
+ struct fd_app * app = (struct fd_app *)li;
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "App: %u,%s%s,Vnd:%u\t",
+ app->appid,
+ app->flags.auth ? "Au" : "--",
+ app->flags.acct ? "Ac" : "--",
+ app->vndid), return NULL);
+ li = li->next;
+ }
+ }
+
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "\n Flags : - IP ........... : %s\n", fd_g_config->cnf_flags.no_ip4 ? "DISABLED" : "Enabled"), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - IPv6 ......... : %s\n", fd_g_config->cnf_flags.no_ip6 ? "DISABLED" : "Enabled"), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - Relay app .... : %s\n", fd_g_config->cnf_flags.no_fwd ? "DISABLED" : "Enabled"), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - TCP .......... : %s\n", fd_g_config->cnf_flags.no_tcp ? "DISABLED" : "Enabled"), return NULL);
+ #ifdef DISABLE_SCTP
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - SCTP ......... : DISABLED (at compilation)\n"), return NULL);
+ #else /* DISABLE_SCTP */
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - SCTP ......... : %s\n", fd_g_config->cnf_flags.no_sctp ? "DISABLED" : "Enabled"), return NULL);
+ #endif /* DISABLE_SCTP */
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - Pref. proto .. : %s\n", fd_g_config->cnf_flags.pr_tcp ? "TCP" : "SCTP"), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - TLS method ... : %s\n", fd_g_config->cnf_flags.tls_alg ? "INBAND" : "Separate port"), return NULL);
+
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " TLS : - Certificate .. : %s\n", fd_g_config->cnf_sec_data.cert_file ?: "(NONE)"), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - Private key .. : %s\n", fd_g_config->cnf_sec_data.key_file ?: "(NONE)"), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - CA (trust) ... : %s (%d certs)\n", fd_g_config->cnf_sec_data.ca_file ?: "(none)", fd_g_config->cnf_sec_data.ca_file_nr), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - CRL .......... : %s\n", fd_g_config->cnf_sec_data.crl_file ?: "(none)"), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - Priority ..... : %s\n", fd_g_config->cnf_sec_data.prio_string ?: "(default: '" GNUTLS_DEFAULT_PRIORITY "')"), return NULL);
+ if (fd_g_config->cnf_sec_data.dh_file) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - DH file ...... : %s\n", fd_g_config->cnf_sec_data.dh_file), return NULL);
+ } else {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " - DH bits ...... : %d\n", fd_g_config->cnf_sec_data.dh_bits ?: GNUTLS_DEFAULT_DHBITS), return NULL);
+ }
+
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " Origin-State-Id ........ : %u", fd_g_config->cnf_orstateid), return NULL);
+
+ return *buf;
+}
+
+/* read contents of a file opened in "rb" mode and alloc this data into a gnutls_datum_t (must be freed afterwards) */
+int fd_conf_stream_to_gnutls_datum(FILE * pemfile, gnutls_datum_t *out)
+{
+ size_t alloc = 0;
+
+ CHECK_PARAMS( pemfile && out );
+ memset(out, 0, sizeof(gnutls_datum_t));
+
+ do {
+ uint8_t * realloced = NULL;
+ size_t read = 0;
+
+ if (alloc < out->size + BUFSIZ + 1) {
+ alloc += alloc / 2 + BUFSIZ + 1;
+ CHECK_MALLOC_DO( realloced = realloc(out->data, alloc),
+ {
+ free(out->data);
+ return ENOMEM;
+ } )
+ out->data = realloced;
+ }
+
+ read = fread( out->data + out->size, 1, alloc - out->size - 1, pemfile );
+ out->size += read;
+
+ if (ferror(pemfile)) {
+ int err = errno;
+ TRACE_DEBUG(INFO, "An error occurred while reading file: %s", strerror(err));
+ return err;
+ }
+ } while (!feof(pemfile));
+
+ out->data[out->size] = '\0';
+ return 0;
+}
+
+#ifdef GNUTLS_VERSION_300
+/* inspired from GnuTLS manual */
+static int fd_conf_print_details_func (gnutls_x509_crt_t cert,
+ gnutls_x509_crt_t issuer, gnutls_x509_crl_t crl,
+ unsigned int verification_output)
+{
+ char name[512];
+ char issuer_name[512];
+ size_t name_size;
+ size_t issuer_name_size;
+
+ if (!TRACE_BOOL(GNUTLS_DBG_LEVEL))
+ return 0;
+
+ issuer_name_size = sizeof (issuer_name);
+ gnutls_x509_crt_get_issuer_dn (cert, issuer_name, &issuer_name_size);
+
+ name_size = sizeof (name);
+ gnutls_x509_crt_get_dn (cert, name, &name_size);
+
+ fd_log_debug("\tSubject: %s", name);
+ fd_log_debug("\tIssuer: %s", issuer_name);
+
+ if (issuer != NULL)
+ {
+ issuer_name_size = sizeof (issuer_name);
+ gnutls_x509_crt_get_dn (issuer, issuer_name, &issuer_name_size);
+
+ fd_log_debug("\tVerified against: %s", issuer_name);
+ }
+
+ if (crl != NULL)
+ {
+ issuer_name_size = sizeof (issuer_name);
+ gnutls_x509_crl_get_issuer_dn (crl, issuer_name, &issuer_name_size);
+
+ fd_log_debug("\tVerified against CRL of: %s", issuer_name);
+ }
+
+ fd_log_debug("\tVerification output: %x", verification_output);
+
+ return 0;
+}
+#endif /* GNUTLS_VERSION_300 */
+
+#ifndef GNUTLS_VERSION_300
+GCC_DIAG_OFF("-Wdeprecated-declarations")
+#endif /* !GNUTLS_VERSION_300 */
+/* Parse the configuration file (using the yacc parser) */
+int fd_conf_parse()
+{
+ extern FILE * fddin;
+ const char * orig = NULL;
+
+ /* Attempt to find the configuration file */
+ if (!fd_g_config->cnf_file)
+ fd_g_config->cnf_file = FD_DEFAULT_CONF_FILENAME;
+
+ fddin = fopen(fd_g_config->cnf_file, "r");
+ if ((fddin == NULL) && (*fd_g_config->cnf_file != '/')) {
+ char * new_cnf = NULL;
+ /* We got a relative path, attempt to add the default directory prefix */
+ orig = fd_g_config->cnf_file;
+ CHECK_MALLOC( new_cnf = malloc(strlen(orig) + strlen(DEFAULT_CONF_PATH) + 2) ); /* we will not free it, but not important */
+ sprintf( new_cnf, DEFAULT_CONF_PATH "/%s", orig );
+ fd_g_config->cnf_file = new_cnf;
+ fddin = fopen(fd_g_config->cnf_file, "r");
+ }
+ if (fddin == NULL) {
+ int ret = errno;
+ LOG_F("Unable to open configuration file for reading; tried the following locations: %s%s%s; Error: %s",
+ orig ?: "", orig? " and " : "", fd_g_config->cnf_file, strerror(ret));
+ return ret;
+ }
+
+ /* call yacc parser */
+ TRACE_DEBUG (FULL, "Parsing configuration file: %s", fd_g_config->cnf_file);
+ CHECK_FCT( fddparse(fd_g_config) );
+
+ /* close the file */
+ fclose(fddin);
+
+ /* Check that TLS private key was given */
+ if (! fd_g_config->cnf_sec_data.key_file) {
+ /* If TLS is not enabled, we allow empty TLS configuration */
+ if ((fd_g_config->cnf_port_tls == 0) && (fd_g_config->cnf_flags.tls_alg == 0)) {
+ LOG_N("TLS is disabled, this is *NOT* a recommended practice! Diameter protocol conveys highly sensitive information on your users.");
+ fd_g_config->cnf_sec_data.tls_disabled = 1;
+ } else {
+ LOG_F( "Missing private key configuration for TLS. Please provide the TLS_cred configuration directive.");
+ return EINVAL;
+ }
+ }
+
+ /* Resolve hostname if not provided */
+ if (fd_g_config->cnf_diamid == NULL) {
+ char buf[HOST_NAME_MAX + 1];
+ struct addrinfo hints, *info;
+ int ret;
+
+ /* local host name */
+ CHECK_SYS(gethostname(buf, sizeof(buf)));
+
+ /* get FQDN */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_CANONNAME;
+
+ ret = getaddrinfo(buf, NULL, &hints, &info);
+ if (ret != 0) {
+ TRACE_ERROR( "Error resolving local FQDN : '%s' : %s"
+ ". Please provide Identity in configuration file.",
+ buf, gai_strerror(ret));
+ return EINVAL;
+ }
+ fd_g_config->cnf_diamid = info->ai_canonname;
+ CHECK_FCT( fd_os_validate_DiameterIdentity(&fd_g_config->cnf_diamid, &fd_g_config->cnf_diamid_len, 1) );
+ freeaddrinfo(info);
+ } else {
+ CHECK_FCT( fd_os_validate_DiameterIdentity(&fd_g_config->cnf_diamid, &fd_g_config->cnf_diamid_len, 0) );
+ }
+
+ /* Handle the realm part */
+ if (fd_g_config->cnf_diamrlm == NULL) {
+ char * start = NULL;
+
+ /* Check the diameter identity is a fqdn */
+ start = strchr(fd_g_config->cnf_diamid, '.');
+ if ((start == NULL) || (start[1] == '\0')) {
+ TRACE_ERROR( "Unable to extract realm from the Identity '%s'."
+ " Please fix your Identity setting or provide Realm.",
+ fd_g_config->cnf_diamid);
+ return EINVAL;
+ }
+
+ fd_g_config->cnf_diamrlm = start + 1;
+ CHECK_FCT( fd_os_validate_DiameterIdentity(&fd_g_config->cnf_diamrlm, &fd_g_config->cnf_diamrlm_len, 1) );
+ } else {
+ CHECK_FCT( fd_os_validate_DiameterIdentity(&fd_g_config->cnf_diamrlm, &fd_g_config->cnf_diamrlm_len, 0) );
+ }
+
+ /* Validate some flags */
+ if (fd_g_config->cnf_flags.no_ip4 && fd_g_config->cnf_flags.no_ip6) {
+ TRACE_ERROR( "IP and IPv6 cannot be disabled at the same time.");
+ return EINVAL;
+ }
+ if (fd_g_config->cnf_flags.no_tcp && fd_g_config->cnf_flags.no_sctp) {
+ TRACE_ERROR( "TCP and SCTP cannot be disabled at the same time.");
+ return EINVAL;
+ }
+
+ /* Validate local endpoints */
+ if ((!FD_IS_LIST_EMPTY(&fd_g_config->cnf_endpoints)) && (fd_g_config->cnf_flags.no_ip4 || fd_g_config->cnf_flags.no_ip6)) {
+ struct fd_list * li;
+ for ( li = fd_g_config->cnf_endpoints.next; li != &fd_g_config->cnf_endpoints; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *)li;
+ if ( (fd_g_config->cnf_flags.no_ip4 && (ep->sa.sa_family == AF_INET))
+ ||(fd_g_config->cnf_flags.no_ip6 && (ep->sa.sa_family == AF_INET6)) ) {
+ char sa_buf[sSA_DUMP_STRLEN];;
+ li = li->prev;
+ fd_list_unlink(&ep->chain);
+ fd_sa_sdump_numeric(sa_buf, &ep->sa);
+ LOG_N("Info: Removing local address conflicting with the flags no_IP / no_IP6 : %s", sa_buf);
+ free(ep);
+ }
+ }
+ }
+
+ /* Configure TLS default parameters */
+ if ((!fd_g_config->cnf_sec_data.tls_disabled) && (!fd_g_config->cnf_sec_data.prio_string)) {
+ const char * err_pos = NULL;
+ CHECK_GNUTLS_DO( gnutls_priority_init(
+ &fd_g_config->cnf_sec_data.prio_cache,
+ GNUTLS_DEFAULT_PRIORITY,
+ &err_pos),
+ { TRACE_ERROR("Error in priority string at position : %s", err_pos); return EINVAL; } );
+ }
+
+ /* Verify that our certificate is valid -- otherwise remote peers will reject it */
+ if (!fd_g_config->cnf_sec_data.tls_disabled) {
+ int ret = 0, i;
+
+ gnutls_datum_t certfile;
+
+ gnutls_x509_crt_t * certs = NULL;
+ unsigned int cert_max = 0;
+
+
+ /* Read the certificate file */
+ FILE *stream = fopen (fd_g_config->cnf_sec_data.cert_file, "rb");
+ if (!stream) {
+ int err = errno;
+ TRACE_DEBUG(INFO, "An error occurred while opening '%s': %s", fd_g_config->cnf_sec_data.cert_file, strerror(err));
+ return err;
+ }
+ CHECK_FCT( fd_conf_stream_to_gnutls_datum(stream, &certfile) );
+ fclose(stream);
+
+ /* Import the certificate(s) */
+ GNUTLS_TRACE( ret = gnutls_x509_crt_list_import(NULL, &cert_max, &certfile, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED) );
+ if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ CHECK_GNUTLS_DO(ret, return EINVAL);
+ }
+
+ CHECK_MALLOC( certs = calloc(cert_max, sizeof(gnutls_x509_crt_t)) );
+ CHECK_GNUTLS_DO( gnutls_x509_crt_list_import(certs, &cert_max, &certfile, GNUTLS_X509_FMT_PEM,
+ #ifdef GNUTLS_VERSION_300
+ GNUTLS_X509_CRT_LIST_FAIL_IF_UNSORTED
+ #else /* GNUTLS_VERSION_300 */
+ 0
+ #endif /* GNUTLS_VERSION_300 */
+ ),
+ {
+ TRACE_ERROR("Failed to import the data from file '%s'", fd_g_config->cnf_sec_data.cert_file);
+ free(certfile.data);
+ return EINVAL;
+ } );
+ free(certfile.data);
+
+ ASSERT(cert_max >= 1);
+
+ /* Now, verify the list against the local CA and CRL */
+
+ #ifdef GNUTLS_VERSION_300
+
+ /* We use the trust list for this purpose */
+ {
+ unsigned int output;
+
+ gnutls_x509_trust_list_verify_named_crt (
+ fd_g_config->cnf_sec_data.trustlist,
+ certs[0],
+ fd_g_config->cnf_diamid,
+ fd_g_config->cnf_diamid_len,
+ 0,
+ &output,
+ fd_conf_print_details_func);
+
+ /* if this certificate is not explicitly trusted verify against CAs
+ */
+ if (output != 0)
+ {
+ gnutls_x509_trust_list_verify_crt (
+ fd_g_config->cnf_sec_data.trustlist,
+ certs,
+ cert_max,
+ 0,
+ &output,
+ fd_conf_print_details_func);
+ }
+
+ if (output & GNUTLS_CERT_INVALID)
+ {
+ fd_log_debug("TLS: Local certificate chain '%s' is invalid :", fd_g_config->cnf_sec_data.cert_file);
+ if (output & GNUTLS_CERT_SIGNER_NOT_FOUND)
+ TRACE_ERROR(" - The certificate hasn't got a known issuer. Did you forget to specify TLS_CA ?");
+ if (output & GNUTLS_CERT_SIGNER_NOT_CA)
+ TRACE_ERROR(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints.");
+ if (output & GNUTLS_CERT_NOT_ACTIVATED)
+ TRACE_ERROR(" - The certificate is not yet activated.");
+ if (output & GNUTLS_CERT_EXPIRED)
+ TRACE_ERROR(" - The certificate is expired.");
+ return EINVAL;
+ }
+
+ /* Now check the subject matches our hostname */
+ if (!gnutls_x509_crt_check_hostname (certs[0], fd_g_config->cnf_diamid))
+ {
+ TRACE_ERROR("TLS: The certificate owner does not match the hostname '%s'", fd_g_config->cnf_diamid);
+ return EINVAL;
+ }
+
+ }
+
+
+ #else /* GNUTLS_VERSION_300 */
+
+ /* GnuTLS 2.x way of checking certificates */
+ {
+ gnutls_x509_crt_t * CA_list;
+ int CA_list_length;
+
+ gnutls_x509_crl_t * CRL_list;
+ int CRL_list_length;
+
+ unsigned int verify;
+ time_t now;
+ GNUTLS_TRACE( gnutls_certificate_get_x509_cas (fd_g_config->cnf_sec_data.credentials, &CA_list, (unsigned int *) &CA_list_length) );
+ GNUTLS_TRACE( gnutls_certificate_get_x509_crls (fd_g_config->cnf_sec_data.credentials, &CRL_list, (unsigned int *) &CRL_list_length) );
+ CHECK_GNUTLS_DO( gnutls_x509_crt_list_verify(certs, cert_max, CA_list, CA_list_length, CRL_list, CRL_list_length, 0, &verify),
+ {
+ TRACE_ERROR("Failed to verify the local certificate '%s' against local credentials. Please check your certificate is valid.", fd_g_config->cnf_sec_data.cert_file);
+ return EINVAL;
+ } );
+
+ if (verify) {
+ fd_log_debug("TLS: Local certificate chain '%s' is invalid :", fd_g_config->cnf_sec_data.cert_file);
+ if (verify & GNUTLS_CERT_INVALID)
+ TRACE_ERROR(" - The certificate is not trusted (unknown CA? expired?)");
+ if (verify & GNUTLS_CERT_REVOKED)
+ TRACE_ERROR(" - The certificate has been revoked.");
+ if (verify & GNUTLS_CERT_SIGNER_NOT_FOUND)
+ TRACE_ERROR(" - The certificate hasn't got a known issuer.");
+ if (verify & GNUTLS_CERT_SIGNER_NOT_CA)
+ TRACE_ERROR(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints.");
+ if (verify & GNUTLS_CERT_INSECURE_ALGORITHM)
+ TRACE_ERROR(" - The certificate signature uses a weak algorithm.");
+ return EINVAL;
+ }
+
+ /* Check the local Identity is valid with the certificate */
+ if (!gnutls_x509_crt_check_hostname (certs[0], fd_g_config->cnf_diamid)) {
+ TRACE_ERROR("TLS: Local certificate '%s' is invalid :", fd_g_config->cnf_sec_data.cert_file);
+ TRACE_ERROR(" - The certificate hostname does not match '%s'", fd_g_config->cnf_diamid);
+ return EINVAL;
+ }
+
+ /* Check validity of all the certificates in the chain */
+ now = time(NULL);
+ for (i = 0; i < cert_max; i++)
+ {
+ time_t deadline;
+
+ GNUTLS_TRACE( deadline = gnutls_x509_crt_get_expiration_time(certs[i]) );
+ if ((deadline != (time_t)-1) && (deadline < now)) {
+ TRACE_ERROR("TLS: Local certificate chain '%s' is invalid :", fd_g_config->cnf_sec_data.cert_file);
+ TRACE_ERROR(" - The certificate %d in the chain is expired", i);
+ return EINVAL;
+ }
+
+ GNUTLS_TRACE( deadline = gnutls_x509_crt_get_activation_time(certs[i]) );
+ if ((deadline != (time_t)-1) && (deadline > now)) {
+ TRACE_ERROR("TLS: Local certificate chain '%s' is invalid :", fd_g_config->cnf_sec_data.cert_file);
+ TRACE_ERROR(" - The certificate %d in the chain is not yet activated", i);
+ return EINVAL;
+ }
+ }
+ }
+ #endif /* GNUTLS_VERSION_300 */
+
+ /* Everything checked OK, free the certificate list */
+ for (i = 0; i < cert_max; i++)
+ {
+ GNUTLS_TRACE( gnutls_x509_crt_deinit (certs[i]) );
+ }
+ free(certs);
+
+ #ifdef GNUTLS_VERSION_300
+ /* Use certificate verification during the handshake */
+ gnutls_certificate_set_verify_function (fd_g_config->cnf_sec_data.credentials, fd_tls_verify_credentials_2);
+ #endif /* GNUTLS_VERSION_300 */
+
+ }
+
+ /* gnutls_certificate_set_verify_limits -- so far the default values are fine... */
+
+ /* DH */
+ if (!fd_g_config->cnf_sec_data.tls_disabled) {
+ if (fd_g_config->cnf_sec_data.dh_file) {
+ gnutls_datum_t dhparams = { NULL, 0 };
+ size_t alloc = 0;
+ FILE *stream = fopen (fd_g_config->cnf_sec_data.dh_file, "rb");
+ if (!stream) {
+ int err = errno;
+ TRACE_DEBUG(INFO, "An error occurred while opening '%s': %s", fd_g_config->cnf_sec_data.dh_file, strerror(err));
+ return err;
+ }
+ do {
+ uint8_t * realloced = NULL;
+ size_t read = 0;
+
+ if (alloc < dhparams.size + BUFSIZ + 1) {
+ alloc += alloc / 2 + BUFSIZ + 1;
+ CHECK_MALLOC_DO( realloced = realloc(dhparams.data, alloc),
+ {
+ free(dhparams.data);
+ return ENOMEM;
+ } )
+ dhparams.data = realloced;
+ }
+
+ read = fread( dhparams.data + dhparams.size, 1, alloc - dhparams.size - 1, stream );
+ dhparams.size += read;
+
+ if (ferror(stream)) {
+ int err = errno;
+ TRACE_DEBUG(INFO, "An error occurred while reading '%s': %s", fd_g_config->cnf_sec_data.dh_file, strerror(err));
+ return err;
+ }
+ } while (!feof(stream));
+ dhparams.data[dhparams.size] = '\0';
+ fclose(stream);
+ CHECK_GNUTLS_DO( gnutls_dh_params_import_pkcs3(
+ fd_g_config->cnf_sec_data.dh_cache,
+ &dhparams,
+ GNUTLS_X509_FMT_PEM),
+ { TRACE_ERROR("Error in DH bits value : %d", fd_g_config->cnf_sec_data.dh_bits ?: GNUTLS_DEFAULT_DHBITS); return EINVAL; } );
+ free(dhparams.data);
+
+ } else {
+ LOG_D( "Generating fresh Diffie-Hellman parameters of size %d (this takes some time)... ", fd_g_config->cnf_sec_data.dh_bits ?: GNUTLS_DEFAULT_DHBITS);
+ CHECK_GNUTLS_DO( gnutls_dh_params_generate2(
+ fd_g_config->cnf_sec_data.dh_cache,
+ fd_g_config->cnf_sec_data.dh_bits ?: GNUTLS_DEFAULT_DHBITS),
+ { TRACE_ERROR("Error in DH bits value : %d", fd_g_config->cnf_sec_data.dh_bits ?: GNUTLS_DEFAULT_DHBITS); return EINVAL; } );
+ }
+
+ }
+
+ return 0;
+}
+#ifndef GNUTLS_VERSION_300
+GCC_DIAG_ON("-Wdeprecated-declarations")
+#endif /* !GNUTLS_VERSION_300 */
+
+
+/* Destroy contents of fd_g_config structure */
+int fd_conf_deinit()
+{
+ TRACE_ENTRY();
+
+ if (!fd_g_config)
+ return 0;
+
+ /* Free the TLS parameters */
+#ifdef GNUTLS_VERSION_300
+ gnutls_x509_trust_list_deinit(fd_g_config->cnf_sec_data.trustlist, 1);
+#endif /* GNUTLS_VERSION_300 */
+ gnutls_priority_deinit(fd_g_config->cnf_sec_data.prio_cache);
+ gnutls_dh_params_deinit(fd_g_config->cnf_sec_data.dh_cache);
+ gnutls_certificate_free_credentials(fd_g_config->cnf_sec_data.credentials);
+
+ free(fd_g_config->cnf_sec_data.cert_file); fd_g_config->cnf_sec_data.cert_file = NULL;
+ free(fd_g_config->cnf_sec_data.key_file); fd_g_config->cnf_sec_data.key_file = NULL;
+ free(fd_g_config->cnf_sec_data.ca_file); fd_g_config->cnf_sec_data.ca_file = NULL;
+ free(fd_g_config->cnf_sec_data.crl_file); fd_g_config->cnf_sec_data.crl_file = NULL;
+ free(fd_g_config->cnf_sec_data.prio_string); fd_g_config->cnf_sec_data.prio_string = NULL;
+ free(fd_g_config->cnf_sec_data.dh_file); fd_g_config->cnf_sec_data.dh_file = NULL;
+
+ /* Destroy dictionary */
+ CHECK_FCT_DO( fd_dict_fini(&fd_g_config->cnf_dict), );
+
+ /* Destroy the main event queue */
+ CHECK_FCT_DO( fd_fifo_del(&fd_g_config->cnf_main_ev), );
+
+ /* Destroy the local endpoints and applications */
+ CHECK_FCT_DO(fd_ep_filter(&fd_g_config->cnf_endpoints, 0 ), );
+ CHECK_FCT_DO(fd_app_empty(&fd_g_config->cnf_apps ), );
+
+ /* Destroy the local identity */
+ free(fd_g_config->cnf_diamid); fd_g_config->cnf_diamid = NULL;
+ free(fd_g_config->cnf_diamrlm); fd_g_config->cnf_diamrlm = NULL;
+
+ return 0;
+}
+
+
diff --git a/libfdcore/core.c b/libfdcore/core.c
new file mode 100644
index 0000000..23909b7
--- /dev/null
+++ b/libfdcore/core.c
@@ -0,0 +1,373 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+GCC_DIAG_OFF("-Wdeprecated-declarations")
+#include <gcrypt.h>
+GCC_DIAG_ON("-Wdeprecated-declarations")
+
+/* The static configuration structure */
+static struct fd_config g_conf;
+struct fd_config * fd_g_config = NULL;
+
+/* gcrypt functions to support posix threads */
+#ifndef GNUTLS_VERSION_210
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#endif /* GNUTLS_VERSION_210 */
+
+/* Thread that process incoming events on the main queue -- and terminates the framework when requested */
+static pthread_t core_runner = (pthread_t)NULL;
+
+/* Signal extensions when the framework is completely initialized (they are waiting in fd_core_waitstartcomplete()) */
+static enum core_state {
+ CORE_NOT_INIT, /* initial state */
+ CORE_LIBS_INIT, /* fd_core_initialize was called */
+ CORE_CONF_READY,/* Configuration was parsed, extensions are loaded */
+ CORE_RUNNING, /* Servers and clients are started, core_runner thread is running */
+ CORE_SHUTDOWN, /* The framework is terminating all objects */
+ CORE_TERM /* Shutdown complete. */
+} core_state = CORE_NOT_INIT;
+static pthread_mutex_t core_mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t core_cnd = PTHREAD_COND_INITIALIZER;
+
+static enum core_state core_state_get(void)
+{
+ enum core_state cur_state;
+ CHECK_POSIX_DO( pthread_mutex_lock( &core_mtx ), );
+ cur_state = core_state;
+ CHECK_POSIX_DO( pthread_mutex_unlock( &core_mtx ), );
+ return cur_state;
+}
+
+static void core_state_set(enum core_state newstate)
+{
+ CHECK_POSIX_DO( pthread_mutex_lock( &core_mtx ), );
+ LOG_D("Core state: %d -> %d", core_state, newstate);
+ core_state = newstate;
+ CHECK_POSIX_DO( pthread_cond_broadcast( &core_cnd ), );
+ CHECK_POSIX_DO( pthread_mutex_unlock( &core_mtx ), );
+}
+
+static int core_state_wait(enum core_state waitstate)
+{
+ int ret = 0;
+ CHECK_POSIX( pthread_mutex_lock( &core_mtx ));
+ pthread_cleanup_push( fd_cleanup_mutex, &core_mtx );
+ while (waitstate > core_state) {
+ CHECK_POSIX_DO(ret = pthread_cond_wait(&core_cnd, &core_mtx), break);
+ }
+ pthread_cleanup_pop( 0 );
+ CHECK_POSIX( pthread_mutex_unlock( &core_mtx ));
+ return ret;
+}
+
+static void core_shutdown(void)
+{
+ LOG_N( FD_PROJECT_BINARY " framework is stopping...");
+ fd_log_threadname("fD Core Shutdown");
+
+ /* cleanups */
+ CHECK_FCT_DO( fd_servers_stop(), /* Stop accepting new connections */ );
+ CHECK_FCT_DO( fd_rtdisp_cleanstop(), /* Stop dispatch thread(s) after a clean loop if possible */ );
+ CHECK_FCT_DO( fd_peer_fini(), /* Stop all connections */ );
+ CHECK_FCT_DO( fd_rtdisp_fini(), /* Stop routing threads and destroy routing queues */ );
+
+ CHECK_FCT_DO( fd_ext_term(), /* Cleanup all extensions */ );
+ CHECK_FCT_DO( fd_rtdisp_cleanup(), /* destroy remaining handlers */ );
+
+ GNUTLS_TRACE( gnutls_global_deinit() );
+
+ CHECK_FCT_DO( fd_conf_deinit(), );
+
+ CHECK_FCT_DO( fd_event_trig_fini(), );
+
+ fd_log_debug(FD_PROJECT_BINARY " framework is terminated.");
+
+ fd_libproto_fini();
+
+}
+
+
+static void * core_runner_thread(void * arg)
+{
+ fd_log_threadname("fD Core Runner");
+
+ core_state_wait(CORE_RUNNING);
+
+ /* Handle events incoming on the main event queue */
+ while (1) {
+ int code; size_t sz; void * data;
+ CHECK_FCT_DO( fd_event_get(fd_g_config->cnf_main_ev, &code, &sz, &data), break );
+ switch (code) {
+ case FDEV_TRIGGER:
+ {
+ int tv, *p;
+ CHECK_PARAMS_DO( sz == sizeof(int),
+ {
+ TRACE_DEBUG(NONE, "Internal error: got FDEV_TRIGGER without trigger value!");
+ ASSERT(0);
+ goto end;
+ } );
+ p = data;
+ tv = *p;
+ free(p);
+ CHECK_FCT_DO( fd_event_trig_call_cb(tv), goto end );
+ }
+ break;
+
+ case FDEV_TERMINATE_INT:
+ goto end;
+
+ default:
+ TRACE_DEBUG(INFO, "Unexpected event in the main event queue (%d), ignored.", code);
+ }
+ }
+
+end:
+ core_shutdown();
+
+ return NULL;
+}
+
+/*********************************/
+/* Public interfaces */
+
+/* Initialize the libfdcore internals. This also initializes libfdproto */
+int fd_core_initialize(void)
+{
+ int ret;
+
+ if (core_state_get() != CORE_NOT_INIT) {
+ fprintf(stderr, "fd_core_initialize() called more than once!\n");
+ return EINVAL;
+ }
+
+ /* Initialize the library -- must come first since it initializes the debug facility */
+ ret = fd_libproto_init();
+ if (ret != 0) {
+ fprintf(stderr, "Unable to initialize libfdproto: %s\n", strerror(ret));
+ return ret;
+ }
+
+ /* Name this thread */
+ fd_log_threadname("Main");
+
+ LOG_N("libfdproto '%s' initialized.", fd_libproto_version);
+
+ /* Initialize gcrypt and gnutls */
+ #ifndef GNUTLS_VERSION_210
+ GNUTLS_TRACE( (void) gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread) );
+ GNUTLS_TRACE( (void) gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0) );
+ #endif /* GNUTLS_VERSION_210 */
+ CHECK_GNUTLS_DO( gnutls_global_init(), return EINVAL );
+ if ( ! gnutls_check_version(GNUTLS_VERSION) ) {
+ TRACE_ERROR( "The GNUTLS library is too old; found '%s', need '" GNUTLS_VERSION "'", gnutls_check_version(NULL));
+ return EINVAL;
+ } else {
+ #ifdef GNUTLS_VERSION_210
+ TRACE_DEBUG(INFO, "libgnutls '%s' initialized.", gnutls_check_version(NULL) );
+ #else /* GNUTLS_VERSION_210 */
+ TRACE_DEBUG(INFO, "libgnutls '%s', libgcrypt '%s', initialized.", gnutls_check_version(NULL), gcry_check_version(NULL) );
+ #endif /* GNUTLS_VERSION_210 */
+ }
+
+ /* Initialize the config with default values */
+ memset(&g_conf, 0, sizeof(struct fd_config));
+ fd_g_config = &g_conf;
+ CHECK_FCT( fd_conf_init() );
+
+ /* Add definitions of the base protocol */
+ CHECK_FCT( fd_dict_base_protocol(fd_g_config->cnf_dict) );
+
+ /* Initialize some modules */
+ CHECK_FCT( fd_hooks_init() );
+ CHECK_FCT( fd_queues_init() );
+ CHECK_FCT( fd_sess_start() );
+ CHECK_FCT( fd_p_expi_init() );
+
+ core_state_set(CORE_LIBS_INIT);
+
+ LOG_N("libfdcore '%s' initialized.", fd_core_version);
+
+
+ /* Next thing is to parse the config, leave this for a different function */
+ return 0;
+}
+
+static pthread_mutex_t core_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Parse the freeDiameter.conf configuration file, load the extensions */
+static int fd_core_parseconf_int(const char * conffile)
+{
+ char * buf = NULL, *b;
+ size_t len = 0, offset=0;
+
+
+ TRACE_ENTRY("%p", conffile);
+
+ /* Conf file */
+ if (conffile)
+ fd_g_config->cnf_file = conffile; /* otherwise, we use the default name */
+
+ CHECK_FCT( fd_conf_parse() );
+
+ /* The following module use data from the configuration */
+ CHECK_FCT( fd_rtdisp_init() );
+
+ /* Now, load all dynamic extensions */
+ CHECK_FCT( fd_ext_load() );
+
+ /* Display configuration */
+ b = fd_conf_dump(&buf, &len, NULL);
+ LOG_SPLIT(FD_LOG_NOTICE, NULL, b ?: "<Error during configuration dump...>", NULL);
+
+ /* Display extensions status */
+ b = fd_ext_dump(&buf, &len, NULL);
+ LOG_SPLIT(FD_LOG_NOTICE, "Loaded extensions: ", b?:"<Error during extensions dump...>", NULL);
+
+ /* Display registered triggers for FDEV_TRIGGER */
+ b = fd_event_trig_dump(&buf, &len, &offset);
+ if (!b || offset) {
+ LOG_N("%s", b ?: "Error during triggers dump...");
+ }
+
+ free(buf);
+
+ /* Since some extensions might have modified the definitions from the dict_base_protocol, we only load the objects now */
+ CHECK_FCT( fd_msg_init() );
+
+ /* Ok, ready for next step */
+ core_state_set(CORE_CONF_READY);
+
+ return 0;
+}
+
+int fd_core_parseconf(const char * conffile)
+{
+ int ret;
+ CHECK_POSIX( pthread_mutex_lock(&core_lock) );
+ ret = fd_core_parseconf_int(conffile);
+ CHECK_POSIX( pthread_mutex_unlock(&core_lock) );
+ return ret;
+}
+
+
+/* For threads that would need to wait complete start of the framework (ex: in extensions) */
+int fd_core_waitstartcomplete(void)
+{
+ TRACE_ENTRY("");
+
+ return core_state_wait(CORE_RUNNING);
+}
+
+/* Start the server & client threads */
+static int fd_core_start_int(void)
+{
+ /* Start server threads */
+ CHECK_FCT( fd_servers_start() );
+
+ /* Start the peer state machines */
+ CHECK_FCT( fd_psm_start() );
+
+ /* Start the core runner thread that handles main events (until shutdown) */
+ CHECK_POSIX( pthread_create(&core_runner, NULL, core_runner_thread, NULL) );
+
+ /* Unlock threads waiting into fd_core_waitstartcomplete */
+ core_state_set(CORE_RUNNING);
+
+ /* Ok, everything is running now... */
+ return 0;
+}
+
+int fd_core_start(void)
+{
+ int ret;
+ CHECK_POSIX( pthread_mutex_lock(&core_lock) );
+ ret = fd_core_start_int();
+ CHECK_POSIX( pthread_mutex_unlock(&core_lock) );
+ return ret;
+}
+
+/* Initialize shutdown of the framework. This is not blocking. */
+int fd_core_shutdown(void)
+{
+ enum core_state cur_state = core_state_get();
+
+ LOG_F("Initiating freeDiameter shutdown sequence (%d)", cur_state);
+
+ if (cur_state < CORE_RUNNING) {
+ /* Calling application must make sure the initialization is not ongoing in a separate thread... */
+ if (pthread_mutex_lock(&core_lock) != 0) {
+ /* This function must not be called asynchronously from fd_core_parseconf / fd_core_start ! Please review your main app design */
+ ASSERT(0);
+ return EINVAL;
+ }
+ core_shutdown();
+ core_state_set(CORE_TERM);
+ pthread_mutex_unlock(&core_lock);
+ } else if (cur_state == CORE_RUNNING) {
+ core_state_set(CORE_SHUTDOWN);
+ CHECK_FCT( fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE_INT, 0, NULL) );
+ }
+
+ /* Other case, the framework is already shutting down */
+
+ return 0;
+}
+
+
+/* Wait for the shutdown to be complete -- this must be called after fd_core_shutdown to reclaim some resources. */
+int fd_core_wait_shutdown_complete(void)
+{
+ enum core_state cur_state = core_state_get();
+ void * th_ret = NULL;
+
+ CHECK_FCT(core_state_wait(CORE_SHUTDOWN));
+
+ if (cur_state == CORE_TERM)
+ return 0;
+
+ /* Just wait for core_runner_thread to complete and return gracefully */
+ CHECK_POSIX(pthread_join(core_runner, &th_ret));
+
+ core_state_set(CORE_TERM);
+
+ return 0;
+}
+
+
+
+
diff --git a/libfdcore/dict_base_proto.c b/libfdcore/dict_base_proto.c
new file mode 100644
index 0000000..5c023f6
--- /dev/null
+++ b/libfdcore/dict_base_proto.c
@@ -0,0 +1,3347 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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. *
+*********************************************************************************************************/
+
+/* Diameter Base protocol definitions.
+ */
+
+#include "fdcore-internal.h"
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+/* The pointer for the global dictionary (initialized from main) */
+struct dictionary * fd_g_dict = NULL;
+
+
+
+#define CHECK_dict_new( _type, _data, _parent, _ref ) \
+ CHECK_FCT( fd_dict_new( dict, (_type), (_data), (_parent), (_ref)) );
+
+#define CHECK_dict_search( _type, _criteria, _what, _result ) \
+ CHECK_FCT( fd_dict_search( dict, (_type), (_criteria), (_what), (_result), ENOENT) );
+
+struct local_rules_definition {
+ char *avp_name;
+ enum rule_position position;
+ int min;
+ int max;
+};
+
+#define RULE_ORDER( _position ) ((((_position) == RULE_FIXED_HEAD) || ((_position) == RULE_FIXED_TAIL)) ? 1 : 0 )
+
+#define PARSE_loc_rules( _rulearray, _parent) { \
+ int __ar; \
+ for (__ar=0; __ar < sizeof(_rulearray) / sizeof((_rulearray)[0]); __ar++) { \
+ struct dict_rule_data __data = { NULL, \
+ (_rulearray)[__ar].position, \
+ 0, \
+ (_rulearray)[__ar].min, \
+ (_rulearray)[__ar].max}; \
+ __data.rule_order = RULE_ORDER(__data.rule_position); \
+ CHECK_FCT( fd_dict_search( \
+ dict, \
+ DICT_AVP, \
+ AVP_BY_NAME, \
+ (_rulearray)[__ar].avp_name, \
+ &__data.rule_avp, 0 ) ); \
+ if ( !__data.rule_avp ) { \
+ TRACE_DEBUG(INFO, "AVP Not found: '%s'", (_rulearray)[__ar].avp_name ); \
+ return ENOENT; \
+ } \
+ CHECK_FCT_DO( fd_dict_new( dict, DICT_RULE, &__data, _parent, NULL), \
+ { \
+ TRACE_DEBUG(INFO, "Error on rule with AVP '%s'", \
+ (_rulearray)[__ar].avp_name ); \
+ return EINVAL; \
+ } ); \
+ } \
+}
+
+int fd_dict_base_protocol(struct dictionary * dict)
+{
+ TRACE_ENTRY("%p", dict);
+ CHECK_PARAMS(dict);
+
+ /* Vendors section */
+ {
+ /* The base RFC has no vendor information */
+ ;
+ }
+
+ /* Applications section */
+ {
+ /* base accounting application */
+ {
+ struct dict_application_data data = { 3, "Diameter Base Accounting" };
+ CHECK_dict_new( DICT_APPLICATION, &data, NULL, NULL);
+ }
+
+ /* relay application */
+ {
+ struct dict_application_data data = { 0xffffffff, "Relay" };
+ #if AI_RELAY != 0xffffffff
+ #error "AI_RELAY definition mismatch"
+ #endif
+ CHECK_dict_new( DICT_APPLICATION, &data , NULL, NULL);
+ }
+ }
+
+ /* Derived AVP types section */
+ {
+ /* Address */
+ {
+ /*
+ The Address format is derived from the OctetString AVP Base
+ Format. It is a discriminated union, representing, for example a
+ 32-bit (IPv4) [RFC791] or 128-bit (IPv6) [RFC4291] address, most
+ significant octet first. The first two octets of the Address AVP
+ represents the AddressType, which contains an Address Family
+ defined in [IANAADFAM]. The AddressType is used to discriminate
+ the content and format of the remaining octets.
+ */
+ struct dict_type_data data = { AVP_TYPE_OCTETSTRING, "Address" , fd_dictfct_Address_interpret , fd_dictfct_Address_encode, fd_dictfct_Address_dump };
+ CHECK_dict_new( DICT_TYPE, &data , NULL, NULL);
+ }
+
+ /* Time */
+ {
+ /*
+ The Time format is derived from the OctetString AVP Base Format.
+ The string MUST contain four octets, in the same format as the
+ first four bytes are in the NTP timestamp format. The NTP
+ Timestamp format is defined in chapter 3 of [RFC4330].
+
+ This represents the number of seconds since 0h on 1 January 1900
+ with respect to the Coordinated Universal Time (UTC).
+
+ On 6h 28m 16s UTC, 7 February 2036 the time value will overflow.
+ SNTP [RFC4330] describes a procedure to extend the time to 2104.
+ This procedure MUST be supported by all DIAMETER nodes.
+ */
+ struct dict_type_data data = { AVP_TYPE_OCTETSTRING, "Time" , fd_dictfct_Time_interpret , fd_dictfct_Time_encode, fd_dictfct_Time_dump };
+ CHECK_dict_new( DICT_TYPE, &data , NULL, NULL);
+ }
+
+ /* UTF8String */
+ {
+ /*
+ The UTF8String format is derived from the OctetString AVP Base
+ Format. This is a human readable string represented using the
+ ISO/IEC IS 10646-1 character set, encoded as an OctetString using
+ the UTF-8 [RFC3629] transformation format described in RFC 3629.
+
+ Since additional code points are added by amendments to the 10646
+ standard from time to time, implementations MUST be prepared to
+ encounter any code point from 0x00000001 to 0x7fffffff. Byte
+ sequences that do not correspond to the valid encoding of a code
+ point into UTF-8 charset or are outside this range are prohibited.
+
+ The use of control codes SHOULD be avoided. When it is necessary
+ to represent a new line, the control code sequence CR LF SHOULD be
+ used.
+
+ The use of leading or trailing white space SHOULD be avoided.
+
+ For code points not directly supported by user interface hardware
+ or software, an alternative means of entry and display, such as
+ hexadecimal, MAY be provided.
+
+ For information encoded in 7-bit US-ASCII, the UTF-8 charset is
+ identical to the US-ASCII charset.
+
+ UTF-8 may require multiple bytes to represent a single character /
+ code point; thus the length of an UTF8String in octets may be
+ different from the number of characters encoded.
+
+ Note that the AVP Length field of an UTF8String is measured in
+ octets, not characters.
+ */
+ struct dict_type_data data = { AVP_TYPE_OCTETSTRING, "UTF8String" , NULL , NULL , fd_dictfct_UTF8String_dump };
+ CHECK_dict_new( DICT_TYPE, &data , NULL, NULL);
+ }
+
+ /* DiameterIdentity */
+ {
+ /*
+ The DiameterIdentity format is derived from the OctetString AVP
+ Base Format.
+
+ DiameterIdentity = FQDN
+
+
+ DiameterIdentity value is used to uniquely identify a Diameter
+ node for purposes of duplicate connection and routing loop
+ detection.
+
+ The contents of the string MUST be the FQDN of the Diameter node.
+ If multiple Diameter nodes run on the same host, each Diameter
+ node MUST be assigned a unique DiameterIdentity. If a Diameter
+
+ node can be identified by several FQDNs, a single FQDN should be
+ picked at startup, and used as the only DiameterIdentity for that
+ node, whatever the connection it is sent on. Note that in this
+ document, DiameterIdentity is in ASCII form in order to be
+ compatible with existing DNS infrastructure. See Appendix D for
+ interactions between the Diameter protocol and Internationalized
+ Domain Name (IDNs).
+ */
+ struct dict_type_data data = { AVP_TYPE_OCTETSTRING, "DiameterIdentity" , NULL , NULL , fd_dictfct_UTF8String_dump };
+ CHECK_dict_new( DICT_TYPE, &data , NULL, NULL);
+ }
+
+ /* DiameterURI */
+ {
+ /*
+ The DiameterURI MUST follow the Uniform Resource Identifiers (URI)
+ syntax [RFC3986] rules specified below:
+
+ "aaa://" FQDN [ port ] [ transport ] [ protocol ]
+
+ ; No transport security
+
+ "aaas://" FQDN [ port ] [ transport ] [ protocol ]
+
+ ; Transport security used
+
+ FQDN = Fully Qualified Host Name
+
+ port = ":" 1*DIGIT
+
+ ; One of the ports used to listen for
+ ; incoming connections.
+ ; If absent,
+ ; the default Diameter port (3868) is
+ ; assumed.
+
+ transport = ";transport=" transport-protocol
+
+ ; One of the transports used to listen
+ ; for incoming connections. If absent,
+ ; the default SCTP [RFC2960] protocol is
+ ; assumed. UDP MUST NOT be used when
+ ; the aaa-protocol field is set to
+ ; diameter.
+
+ transport-protocol = ( "tcp" / "sctp" / "udp" )
+
+ protocol = ";protocol=" aaa-protocol
+
+ ; If absent, the default AAA protocol
+ ; is diameter.
+
+ aaa-protocol = ( "diameter" / "radius" / "tacacs+" )
+
+ The following are examples of valid Diameter host identities:
+
+ aaa://host.example.com;transport=tcp
+ aaa://host.example.com:6666;transport=tcp
+ aaa://host.example.com;protocol=diameter
+ aaa://host.example.com:6666;protocol=diameter
+ aaa://host.example.com:6666;transport=tcp;protocol=diameter
+ aaa://host.example.com:1813;transport=udp;protocol=radius
+ */
+ struct dict_type_data data = { AVP_TYPE_OCTETSTRING, "DiameterURI" , NULL , NULL , fd_dictfct_UTF8String_dump };
+ CHECK_dict_new( DICT_TYPE, &data , NULL, NULL);
+ }
+
+ /* Enumerated */
+ {
+ /*
+ Enumerated is derived from the Integer32 AVP Base Format. The
+ definition contains a list of valid values and their
+ interpretation and is described in the Diameter application
+ introducing the AVP.
+ */
+
+ /* We don't use a generic "Enumerated" type in freeDiameter. Instead, we define
+ * types of the form "Enumerated(<avpname>)" where <avpname> is replaced
+ * by the name of the AVP to which the type applies.
+ * Example: Enumerated(Disconnect-Cause)
+ */
+ ;
+ }
+
+ /* IPFilterRule */
+ {
+ /*
+ The IPFilterRule format is derived from the OctetString AVP Base
+ Format and uses the ASCII charset. The rule syntax is a modified
+ subset of ipfw(8) from FreeBSD. Packets may be filtered based on
+ the following information that is associated with it:
+
+ Direction (in or out)
+ Source and destination IP address (possibly masked)
+ Protocol
+ Source and destination port (lists or ranges)
+ TCP flags
+ IP fragment flag
+ IP options
+ ICMP types
+
+ Rules for the appropriate direction are evaluated in order, with
+ the first matched rule terminating the evaluation. Each packet is
+ evaluated once. If no rule matches, the packet is dropped if the
+ last rule evaluated was a permit, and passed if the last rule was
+ a deny.
+
+ IPFilterRule filters MUST follow the format:
+
+ action dir proto from src to dst [options]
+
+ (...skipped loooong explanation...)
+
+ There is one kind of packet that the access device MUST always
+ discard, that is an IP fragment with a fragment offset of one.
+ This is a valid packet, but it only has one use, to try to
+ circumvent firewalls.
+
+ An access device that is unable to interpret or apply a deny rule
+ MUST terminate the session. An access device that is unable to
+ interpret or apply a permit rule MAY apply a more restrictive
+ rule. An access device MAY apply deny rules of its own before the
+ supplied rules, for example to protect the access device owner's
+ infrastructure.
+ */
+ struct dict_type_data data = { AVP_TYPE_OCTETSTRING, "IPFilterRule" , NULL , NULL , fd_dictfct_UTF8String_dump };
+ CHECK_dict_new( DICT_TYPE, &data , NULL, NULL);
+ }
+ }
+
+ /* AVP section */
+ {
+ struct dict_object * Address_type;
+ struct dict_object * UTF8String_type;
+ struct dict_object * DiameterIdentity_type;
+ struct dict_object * DiameterURI_type;
+ struct dict_object * Time_type;
+
+ CHECK_dict_search( DICT_TYPE, TYPE_BY_NAME, "Address", &Address_type);
+ CHECK_dict_search( DICT_TYPE, TYPE_BY_NAME, "UTF8String", &UTF8String_type);
+ CHECK_dict_search( DICT_TYPE, TYPE_BY_NAME, "DiameterIdentity", &DiameterIdentity_type);
+ CHECK_dict_search( DICT_TYPE, TYPE_BY_NAME, "DiameterURI", &DiameterURI_type);
+ CHECK_dict_search( DICT_TYPE, TYPE_BY_NAME, "Time", &Time_type);
+
+ /* Vendor-Id */
+ {
+ /*
+ The Vendor-Id AVP (AVP Code 266) is of type Unsigned32 and contains
+ the IANA "SMI Network Management Private Enterprise Codes" [RFC3232]
+ value assigned to the vendor of the Diameter device. It is
+ envisioned that the combination of the Vendor-Id, Product-Name
+ (Section 5.3.7) and the Firmware-Revision (Section 5.3.4) AVPs may
+ provide useful debugging information.
+
+ A Vendor-Id value of zero in the CER or CEA messages is reserved and
+ indicates that this field is ignored.
+ */
+ struct dict_avp_data data = {
+ 266, /* Code */
+ #if AC_VENDOR_ID != 266
+ #error "AC_VENDOR_ID definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Vendor-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data, NULL, NULL);
+ }
+
+ /* Firmware-Revision */
+ {
+ /*
+ The Firmware-Revision AVP (AVP Code 267) is of type Unsigned32 and is
+ used to inform a Diameter peer of the firmware revision of the
+ issuing device.
+
+ For devices that do not have a firmware revision (general purpose
+ computers running Diameter software modules, for instance), the
+ revision of the Diameter software module may be reported instead.
+ */
+ struct dict_avp_data data = {
+ 267, /* Code */
+ #if AC_FIRMWARE_REVISION != 267
+ #error "AC_FIRMWARE_REVISION definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Firmware-Revision", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ 0, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Host-IP-Address */
+ {
+ /*
+ The Host-IP-Address AVP (AVP Code 257) is of type Address and is used
+ to inform a Diameter peer of the sender's IP address. All source
+ addresses that a Diameter node expects to use with SCTP [RFC2960]
+ MUST be advertised in the CER and CEA messages by including a
+ Host-IP- Address AVP for each address. This AVP MUST ONLY be used in
+ the CER and CEA messages.
+ */
+ struct dict_avp_data data = {
+ 257, /* Code */
+ #if AC_HOST_IP_ADDRESS != 257
+ #error "AC_HOST_IP_ADDRESS definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Host-IP-Address", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , Address_type, NULL);
+ }
+
+ /* Supported-Vendor-Id */
+ {
+ /*
+ The Supported-Vendor-Id AVP (AVP Code 265) is of type Unsigned32 and
+ contains the IANA "SMI Network Management Private Enterprise Codes"
+ [RFC3232] value assigned to a vendor other than the device vendor but
+ including the application vendor. This is used in the CER and CEA
+ messages in order to inform the peer that the sender supports (a
+ subset of) the vendor-specific AVPs defined by the vendor identified
+ in this AVP. The value of this AVP SHOULD NOT be set to zero.
+ Multiple instances of this AVP containing the same value SHOULD NOT
+ be sent.
+ */
+ struct dict_avp_data data = {
+ 265, /* Code */
+ #if AC_SUPPORTED_VENDOR_ID != 265
+ #error "AC_SUPPORTED_VENDOR_ID definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Supported-Vendor-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Product-Name */
+ {
+ /*
+ The Product-Name AVP (AVP Code 269) is of type UTF8String, and
+ contains the vendor assigned name for the product. The Product-Name
+ AVP SHOULD remain constant across firmware revisions for the same
+ product.
+ */
+ struct dict_avp_data data = {
+ 269, /* Code */
+ #if AC_PRODUCT_NAME != 269
+ #error "AC_PRODUCT_NAME definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Product-Name", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ 0, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , UTF8String_type, NULL);
+ }
+
+ /* Disconnect-Cause */
+ {
+ /*
+ The Disconnect-Cause AVP (AVP Code 273) is of type Enumerated. A
+ Diameter node MUST include this AVP in the Disconnect-Peer-Request
+ message to inform the peer of the reason for its intention to
+ shutdown the transport connection. The following values are
+ supported:
+
+ REBOOTING 0
+ A scheduled reboot is imminent. Receiver of DPR with above result
+ code MAY attempt reconnection.
+
+ BUSY 1
+ The peer's internal resources are constrained, and it has
+ determined that the transport connection needs to be closed.
+ Receiver of DPR with above result code SHOULD NOT attempt
+ reconnection.
+
+ DO_NOT_WANT_TO_TALK_TO_YOU 2
+ The peer has determined that it does not see a need for the
+ transport connection to exist, since it does not expect any
+ messages to be exchanged in the near future. Receiver of DPR
+ with above result code SHOULD NOT attempt reconnection.
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_INTEGER32, "Enumerated(Disconnect-Cause)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_0 = { "REBOOTING", { .i32 = 0 }};
+ struct dict_enumval_data t_1 = { "BUSY", { .i32 = 1 }};
+ struct dict_enumval_data t_2 = { "DO_NOT_WANT_TO_TALK_TO_YOU", { .i32 = 2 }};
+ struct dict_avp_data data = {
+ 273, /* Code */
+ #if AC_DISCONNECT_CAUSE != 273
+ #error "AC_DISCONNECT_CAUSE definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Disconnect-Cause", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_INTEGER32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_0 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_2 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Origin-Host */
+ {
+ /*
+ The Origin-Host AVP (AVP Code 264) is of type DiameterIdentity, and
+ MUST be present in all Diameter messages. This AVP identifies the
+ endpoint that originated the Diameter message. Relay agents MUST NOT
+ modify this AVP.
+
+ The value of the Origin-Host AVP is guaranteed to be unique within a
+ single host.
+
+ Note that the Origin-Host AVP may resolve to more than one address as
+ the Diameter peer may support more than one address.
+
+ This AVP SHOULD be placed as close to the Diameter header as
+ possible.
+ */
+ struct dict_avp_data data = {
+ 264, /* Code */
+ #if AC_ORIGIN_HOST != 264
+ #error "AC_ORIGIN_HOST definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Origin-Host", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , DiameterIdentity_type, NULL);
+ }
+
+ /* Origin-Realm */
+ {
+ /*
+ The Origin-Realm AVP (AVP Code 296) is of type DiameterIdentity.
+ This AVP contains the Realm of the originator of any Diameter message
+ and MUST be present in all messages.
+
+ This AVP SHOULD be placed as close to the Diameter header as
+ possible.
+ */
+ struct dict_avp_data data = {
+ 296, /* Code */
+ #if AC_ORIGIN_REALM != 296
+ #error "AC_ORIGIN_REALM definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Origin-Realm", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , DiameterIdentity_type, NULL);
+ }
+
+ /* Destination-Host */
+ {
+ /*
+ The Destination-Host AVP (AVP Code 293) is of type DiameterIdentity.
+ This AVP MUST be present in all unsolicited agent initiated messages,
+ MAY be present in request messages, and MUST NOT be present in Answer
+ messages.
+
+ The absence of the Destination-Host AVP will cause a message to be
+ sent to any Diameter server supporting the application within the
+ realm specified in Destination-Realm AVP.
+
+ This AVP SHOULD be placed as close to the Diameter header as
+ possible.
+ */
+ struct dict_avp_data data = {
+ 293, /* Code */
+ #if AC_DESTINATION_HOST != 293
+ #error "AC_DESTINATION_HOST definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Destination-Host", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , DiameterIdentity_type, NULL);
+ }
+
+ /* Destination-Realm */
+ {
+ /*
+ The Destination-Realm AVP (AVP Code 283) is of type DiameterIdentity,
+ and contains the realm the message is to be routed to. The
+ Destination-Realm AVP MUST NOT be present in Answer messages.
+ Diameter Clients insert the realm portion of the User-Name AVP.
+ Diameter servers initiating a request message use the value of the
+ Origin-Realm AVP from a previous message received from the intended
+ target host (unless it is known a priori). When present, the
+ Destination-Realm AVP is used to perform message routing decisions.
+
+ Request messages whose ABNF does not list the Destination-Realm AVP
+ as a mandatory AVP are inherently non-routable messages.
+
+ This AVP SHOULD be placed as close to the Diameter header as
+ possible.
+ */
+ struct dict_avp_data data = {
+ 283, /* Code */
+ #if AC_DESTINATION_REALM != 283
+ #error "AC_DESTINATION_REALM definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Destination-Realm", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , DiameterIdentity_type, NULL);
+ }
+
+ /* Route-Record */
+ {
+ /*
+ The Route-Record AVP (AVP Code 282) is of type DiameterIdentity. The
+ identity added in this AVP MUST be the same as the one received in
+ the Origin-Host of the Capabilities Exchange message.
+ */
+ struct dict_avp_data data = {
+ 282, /* Code */
+ #if AC_ROUTE_RECORD != 282
+ #error "AC_ROUTE_RECORD definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Route-Record", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , DiameterIdentity_type, NULL);
+ }
+
+ /* Proxy-Host */
+ {
+ /*
+ The Proxy-Host AVP (AVP Code 280) is of type DiameterIdentity. This
+ AVP contains the identity of the host that added the Proxy-Info AVP.
+ */
+ struct dict_avp_data adata = {
+ 280, /* Code */
+ #if AC_PROXY_HOST != 280
+ #error "AC_PROXY_HOST definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Proxy-Host", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &adata , DiameterIdentity_type, NULL);
+ }
+
+ /* Proxy-State */
+ {
+ /*
+ The Proxy-State AVP (AVP Code 33) is of type OctetString, and
+ contains state local information, and MUST be treated as opaque data.
+ */
+ struct dict_avp_data adata = {
+ 33, /* Code */
+ #if AC_PROXY_STATE != 33
+ #error "AC_PROXY_STATE definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Proxy-State", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &adata , NULL, NULL);
+ }
+
+ /* Proxy-Info */
+ {
+ /*
+ The Proxy-Info AVP (AVP Code 284) is of type Grouped. The Grouped
+ Data field has the following ABNF grammar:
+
+ Proxy-Info ::= < AVP Header: 284 >
+ { Proxy-Host }
+ { Proxy-State }
+ * [ AVP ]
+ */
+ struct dict_object * avp;
+ struct dict_avp_data data = {
+ 284, /* Code */
+ #if AC_PROXY_INFO != 284
+ #error "AC_PROXY_INFO definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Proxy-Info", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_GROUPED /* base type of data */
+ };
+ struct local_rules_definition rules[] =
+ { { "Proxy-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Proxy-State", RULE_REQUIRED, -1, 1 }
+ };
+
+ CHECK_dict_new( DICT_AVP, &data , NULL, &avp);
+ PARSE_loc_rules( rules, avp );
+ }
+
+ /* Auth-Application-Id */
+ {
+ /*
+ The Auth-Application-Id AVP (AVP Code 258) is of type Unsigned32 and
+ is used in order to advertise support of the Authentication and
+ Authorization portion of an application (see Section 2.4). If
+ present in a message other than CER and CEA, the value of the Auth-
+ Application-Id AVP MUST match the Application Id present in the
+ Diameter message header.
+ */
+ struct dict_avp_data data = {
+ 258, /* Code */
+ #if AC_AUTH_APPLICATION_ID != 258
+ #error "AC_AUTH_APPLICATION_ID definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Auth-Application-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Acct-Application-Id */
+ {
+ /*
+ The Acct-Application-Id AVP (AVP Code 259) is of type Unsigned32 and
+ is used in order to advertise support of the Accounting portion of an
+ application (see Section 2.4). If present in a message other than
+ CER and CEA, the value of the Acct-Application-Id AVP MUST match the
+ Application Id present in the Diameter message header.
+ */
+ struct dict_avp_data data = {
+ 259, /* Code */
+ #if AC_ACCT_APPLICATION_ID != 259
+ #error "AC_ACCT_APPLICATION_ID definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Acct-Application-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Inband-Security-Id */
+ {
+ /*
+ The Inband-Security-Id AVP (AVP Code 299) is of type Unsigned32 and
+ is used in order to advertise support of the Security portion of the
+ application.
+
+ Currently, the following values are supported, but there is ample
+ room to add new security Ids.
+
+
+ NO_INBAND_SECURITY 0
+
+ This peer does not support TLS. This is the default value, if the
+ AVP is omitted.
+
+ TLS 1
+
+ This node supports TLS security, as defined by [RFC4346].
+ */
+
+ /* Although the RFC does not specify an "Enumerated" type here, we go forward and create one.
+ * This is the reason for the "*" in the type name
+ */
+
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_UNSIGNED32, "Enumerated(Inband-Security-Id)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_0 = { "NO_INBAND_SECURITY", { .u32 = ACV_ISI_NO_INBAND_SECURITY }};
+ struct dict_enumval_data t_1 = { "TLS", { .u32 = ACV_ISI_TLS }};
+ struct dict_avp_data data = {
+ 299, /* Code */
+ #if AC_INBAND_SECURITY_ID != 299
+ #error "AC_INBAND_SECURITY_ID definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Inband-Security-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_0 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Vendor-Specific-Application-Id */
+ {
+ /*
+ The Vendor-Specific-Application-Id AVP (AVP Code 260) is of type
+ Grouped and is used to advertise support of a vendor-specific
+ Diameter Application. Exactly one instance of either Auth-
+ Application-Id or Acct-Application-Id AVP MUST be present. The
+ Application Id carried by either Auth-Application-Id or Acct-
+ Application-Id AVP MUST comply with vendor specific Application Id
+ assignment described in Sec 11.3. It MUST also match the Application
+ Id present in the diameter header except when used in a CER or CEA
+ messages.
+
+ The Vendor-Id AVP is an informational AVP pertaining to the vendor
+ who may have authorship of the vendor-specific Diameter application.
+ It MUST NOT be used as a means of defining a completely separate
+ vendor-specific Application Id space.
+
+ This AVP MUST also be present as the first AVP in all experimental
+ commands defined in the vendor-specific application.
+
+ This AVP SHOULD be placed as close to the Diameter header as
+ possible.
+
+ AVP Format
+
+ <Vendor-Specific-Application-Id> ::= < AVP Header: 260 >
+ { Vendor-Id }
+ [ Auth-Application-Id ]
+ [ Acct-Application-Id ]
+
+ A Vendor-Specific-Application-Id AVP MUST contain exactly one of
+ either Auth-Application-Id or Acct-Application-Id. If a Vendor-
+ Specific-Application-Id is received without any of these two AVPs,
+ then the recipient SHOULD issue an answer with a Result-Code set to
+ DIAMETER_MISSING_AVP. The answer SHOULD also include a Failed-AVP
+ which MUST contain an example of an Auth-Application-Id AVP and an
+ Acct-Application-Id AVP.
+
+ If a Vendor-Specific-Application-Id is received that contains both
+ Auth-Application-Id and Acct-Application-Id, then the recipient
+ SHOULD issue an answer with Result-Code set to
+ DIAMETER_AVP_OCCURS_TOO_MANY_TIMES. The answer SHOULD also include a
+ Failed-AVP which MUST contain the received Auth-Application-Id AVP
+ and Acct-Application-Id AVP.
+ */
+ struct dict_object * avp;
+ struct dict_avp_data data = {
+ 260, /* Code */
+ #if AC_VENDOR_SPECIFIC_APPLICATION_ID != 260
+ #error "AC_VENDOR_SPECIFIC_APPLICATION_ID definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Vendor-Specific-Application-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_GROUPED /* base type of data */
+ };
+
+ struct local_rules_definition rules[] =
+ {
+#ifndef WORKAROUND_ACCEPT_INVALID_VSAI
+ /* ABNF from RFC6733 */
+ { "Vendor-Id", RULE_REQUIRED, -1, 1 }
+#else /* WORKAROUND_ACCEPT_INVALID_VSAI */
+ /* ABNF from RFC3588 (including erratum, because original text is nonsense) */
+ { "Vendor-Id", RULE_REQUIRED, -1, -1}
+#endif /* WORKAROUND_ACCEPT_INVALID_VSAI */
+ ,{ "Auth-Application-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Acct-Application-Id", RULE_OPTIONAL, -1, 1 }
+ };
+
+ /* Create the grouped AVP */
+ CHECK_dict_new( DICT_AVP, &data , NULL, &avp);
+ PARSE_loc_rules( rules, avp );
+
+ }
+
+ /* Redirect-Host */
+ {
+ /*
+ One or more of instances of this AVP MUST be present if the answer
+ message's 'E' bit is set and the Result-Code AVP is set to
+ DIAMETER_REDIRECT_INDICATION.
+
+ Upon receiving the above, the receiving Diameter node SHOULD forward
+ the request directly to one of the hosts identified in these AVPs.
+ The server contained in the selected Redirect-Host AVP SHOULD be used
+ for all messages pertaining to this session.
+ */
+ struct dict_avp_data data = {
+ 292, /* Code */
+ #if AC_REDIRECT_HOST != 292
+ #error "AC_REDIRECT_HOST definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Redirect-Host", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , DiameterURI_type, NULL);
+ }
+
+ /* Redirect-Host-Usage */
+ {
+ /*
+ The Redirect-Host-Usage AVP (AVP Code 261) is of type Enumerated.
+ This AVP MAY be present in answer messages whose 'E' bit is set and
+ the Result-Code AVP is set to DIAMETER_REDIRECT_INDICATION.
+
+ When present, this AVP dictates how the routing entry resulting from
+ the Redirect-Host is to be used. The following values are supported:
+
+
+ DONT_CACHE 0
+
+ The host specified in the Redirect-Host AVP should not be cached.
+ This is the default value.
+
+
+ ALL_SESSION 1
+
+ All messages within the same session, as defined by the same value
+ of the Session-ID AVP MAY be sent to the host specified in the
+ Redirect-Host AVP.
+
+
+ ALL_REALM 2
+
+ All messages destined for the realm requested MAY be sent to the
+ host specified in the Redirect-Host AVP.
+
+
+ REALM_AND_APPLICATION 3
+
+ All messages for the application requested to the realm specified
+ MAY be sent to the host specified in the Redirect-Host AVP.
+
+ ALL_APPLICATION 4
+
+ All messages for the application requested MAY be sent to the host
+ specified in the Redirect-Host AVP.
+
+
+ ALL_HOST 5
+
+ All messages that would be sent to the host that generated the
+ Redirect-Host MAY be sent to the host specified in the Redirect-
+ Host AVP.
+
+
+ ALL_USER 6
+
+ All messages for the user requested MAY be sent to the host
+ specified in the Redirect-Host AVP.
+
+
+ When multiple cached routes are created by redirect indications and
+ they differ only in redirect usage and peers to forward requests to
+ (see Section 6.1.8), a precedence rule MUST be applied to the
+ redirect usage values of the cached routes during normal routing to
+ resolve contentions that may occur. The precedence rule is the order
+ that dictate which redirect usage should be considered before any
+ other as they appear. The order is as follows:
+
+
+ 1. ALL_SESSION
+
+ 2. ALL_USER
+
+ 3. REALM_AND_APPLICATION
+
+ 4. ALL_REALM
+
+ 5. ALL_APPLICATION
+
+ 6. ALL_HOST
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_INTEGER32, "Enumerated(Redirect-Host-Usage)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_0 = { "DONT_CACHE", { .i32 = 0 }};
+ struct dict_enumval_data t_1 = { "ALL_SESSION", { .i32 = 1 }};
+ struct dict_enumval_data t_2 = { "ALL_REALM", { .i32 = 2 }};
+ struct dict_enumval_data t_3 = { "REALM_AND_APPLICATION", { .i32 = 3 }};
+ struct dict_enumval_data t_4 = { "ALL_APPLICATION", { .i32 = 4 }};
+ struct dict_enumval_data t_5 = { "ALL_HOST", { .i32 = 5 }};
+ struct dict_enumval_data t_6 = { "ALL_USER", { .i32 = 6 }};
+ struct dict_avp_data data = {
+ 261, /* Code */
+ #if AC_REDIRECT_HOST_USAGE != 261
+ #error "AC_REDIRECT_HOST_USAGE definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Redirect-Host-Usage", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_INTEGER32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_0 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_2 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_3 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_4 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_5 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_6 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Redirect-Max-Cache-Time */
+ {
+ /*
+ The Redirect-Max-Cache-Time AVP (AVP Code 262) is of type Unsigned32.
+ This AVP MUST be present in answer messages whose 'E' bit is set, the
+ Result-Code AVP is set to DIAMETER_REDIRECT_INDICATION and the
+ Redirect-Host-Usage AVP set to a non-zero value.
+
+ This AVP contains the maximum number of seconds the peer and route
+ table entries, created as a result of the Redirect-Host, will be
+ cached. Note that once a host created due to a redirect indication
+ is no longer reachable, any associated peer and routing table entries
+ MUST be deleted.
+ */
+ struct dict_avp_data data = {
+ 262, /* Code */
+ #if AC_REDIRECT_MAX_CACHE_TIME != 262
+ #error "AC_REDIRECT_MAX_CACHE_TIME definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Redirect-Max-Cache-Time", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Result-Code */
+ {
+ /*
+ The Result-Code AVP (AVP Code 268) is of type Unsigned32 and
+ indicates whether a particular request was completed successfully or
+ whether an error occurred. All Diameter answer messages defined in
+ IETF applications MUST include one Result-Code AVP. A non-successful
+ Result-Code AVP (one containing a non 2xxx value other than
+ DIAMETER_REDIRECT_INDICATION) MUST include the Error-Reporting-Host
+ AVP if the host setting the Result-Code AVP is different from the
+ identity encoded in the Origin-Host AVP.
+
+ The Result-Code data field contains an IANA-managed 32-bit address
+ space representing errors (see Section 11.4). Diameter provides the
+ following classes of errors, all identified by the thousands digit in
+ the decimal notation:
+
+ o 1xxx (Informational)
+
+ o 2xxx (Success)
+
+ o 3xxx (Protocol Errors)
+
+ o 4xxx (Transient Failures)
+
+ o 5xxx (Permanent Failure)
+
+ A non-recognized class (one whose first digit is not defined in this
+ section) MUST be handled as a permanent failure.
+ */
+
+ /* Although the RFC does not specify an "Enumerated" type here, we go forward and create one.
+ * This is the reason for the "*" in the type name
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_UNSIGNED32, "Enumerated(Result-Code)" , NULL, NULL, NULL };
+ struct dict_avp_data data = {
+ 268, /* Code */
+ #if AC_RESULT_CODE != 268
+ #error "AC_RESULT_CODE definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Result-Code", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+
+ /* Informational */
+ {
+ /* 1001 */
+ {
+ /*
+ This informational error is returned by a Diameter server to
+ inform the access device that the authentication mechanism being
+ used requires multiple round trips, and a subsequent request needs
+ to be issued in order for access to be granted.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_MULTI_ROUND_AUTH", { .u32 = ER_DIAMETER_MULTI_ROUND_AUTH }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ }
+ /* Success */
+ {
+ /* 2001 */
+ {
+ /*
+ The Request was successfully completed.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_SUCCESS", { .u32 = ER_DIAMETER_SUCCESS }};
+ #if ER_DIAMETER_SUCCESS != 2001
+ #error "ER_DIAMETER_SUCCESS definition mismatch"
+ #endif
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 2002 */
+ {
+ /*
+ When returned, the request was successfully completed, but
+ additional processing is required by the application in order to
+ provide service to the user.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_LIMITED_SUCCESS", { .u32 = ER_DIAMETER_LIMITED_SUCCESS }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ }
+ /* Protocol Errors */
+ {
+ /* 3001 -- might be changed to 5xxx soon */
+ {
+ /*
+ The Request contained a Command-Code that the receiver did not
+ recognize or support. This MUST be used when a Diameter node
+ receives an experimental command that it does not understand.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_COMMAND_UNSUPPORTED", { .u32 = ER_DIAMETER_COMMAND_UNSUPPORTED }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 3002 */
+ {
+ /*
+ This error is given when Diameter can not deliver the message to
+ the destination, either because no host within the realm
+ supporting the required application was available to process the
+ request, or because Destination-Host AVP was given without the
+ associated Destination-Realm AVP.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_UNABLE_TO_DELIVER", { .u32 = ER_DIAMETER_UNABLE_TO_DELIVER }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 3003 */
+ {
+ /*
+ The intended realm of the request is not recognized.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_REALM_NOT_SERVED", { .u32 = ER_DIAMETER_REALM_NOT_SERVED }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 3004 */
+ {
+ /*
+ When returned, a Diameter node SHOULD attempt to send the message
+ to an alternate peer. This error MUST only be used when a
+ specific server is requested, and it cannot provide the requested
+ service.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_TOO_BUSY", { .u32 = ER_DIAMETER_TOO_BUSY }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 3005 */
+ {
+ /*
+ An agent detected a loop while trying to get the message to the
+ intended recipient. The message MAY be sent to an alternate peer,
+ if one is available, but the peer reporting the error has
+ identified a configuration problem.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_LOOP_DETECTED", { .u32 = ER_DIAMETER_LOOP_DETECTED }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 3006 */
+ {
+ /*
+ A redirect agent has determined that the request could not be
+ satisfied locally and the initiator of the request should direct
+ the request directly to the server, whose contact information has
+ been added to the response. When set, the Redirect-Host AVP MUST
+ be present.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_REDIRECT_INDICATION", { .u32 = ER_DIAMETER_REDIRECT_INDICATION }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 3007 */
+ {
+ /*
+ A request was sent for an application that is not supported.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_APPLICATION_UNSUPPORTED", { .u32 = ER_DIAMETER_APPLICATION_UNSUPPORTED }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 3008 -- will change to 5xxx soon */
+ {
+ /*
+ A request was received whose bits in the Diameter header were
+ either set to an invalid combination, or to a value that is
+ inconsistent with the command code's definition.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_INVALID_HDR_BITS", { .u32 = ER_DIAMETER_INVALID_HDR_BITS }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 3009 -- will change to 5xxx soon */
+ {
+ /*
+ A request was received that included an AVP whose flag bits are
+ set to an unrecognized value, or that is inconsistent with the
+ AVP's definition.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_INVALID_AVP_BITS", { .u32 = ER_DIAMETER_INVALID_AVP_BITS }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 3010 -- will change to 5xxx soon */
+ {
+ /*
+ A CER was received from an unknown peer.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_UNKNOWN_PEER", { .u32 = ER_DIAMETER_UNKNOWN_PEER }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ }
+ /* Transient Failures */
+ {
+ /* 4001 */
+ {
+ /*
+ The authentication process for the user failed, most likely due to
+ an invalid password used by the user. Further attempts MUST only
+ be tried after prompting the user for a new password.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_AUTHENTICATION_REJECTED", { .u32 = ER_DIAMETER_AUTHENTICATION_REJECTED }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 4002 */
+ {
+ /*
+ A Diameter node received the accounting request but was unable to
+ commit it to stable storage due to a temporary lack of space.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_OUT_OF_SPACE", { .u32 = ER_DIAMETER_OUT_OF_SPACE }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 4003 */
+ {
+ /*
+ The peer has determined that it has lost the election process and
+ has therefore disconnected the transport connection.
+ */
+ struct dict_enumval_data error_code = { "ELECTION_LOST", { .u32 = ER_ELECTION_LOST }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ }
+ /* Permanent Failures */
+ {
+ /* 5001 */
+ {
+ /*
+ The peer received a message that contained an AVP that is not
+ recognized or supported and was marked with the Mandatory bit. A
+ Diameter message with this error MUST contain one or more Failed-
+ AVP AVP containing the AVPs that caused the failure.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_AVP_UNSUPPORTED", { .u32 = ER_DIAMETER_AVP_UNSUPPORTED }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5002 */
+ {
+ /*
+ The request contained an unknown Session-Id.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_UNKNOWN_SESSION_ID", { .u32 = ER_DIAMETER_UNKNOWN_SESSION_ID }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5003 */
+ {
+ /*
+ A request was received for which the user could not be authorized.
+ This error could occur if the service requested is not permitted
+ to the user.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_AUTHORIZATION_REJECTED",{ .u32 = ER_DIAMETER_AUTHORIZATION_REJECTED }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5004 */
+ {
+ /*
+ The request contained an AVP with an invalid value in its data
+ portion. A Diameter message indicating this error MUST include
+ the offending AVPs within a Failed-AVP AVP.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_INVALID_AVP_VALUE", { .u32 = ER_DIAMETER_INVALID_AVP_VALUE }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5005 */
+ {
+ /*
+ The request did not contain an AVP that is required by the Command
+ Code definition. If this value is sent in the Result-Code AVP, a
+ Failed-AVP AVP SHOULD be included in the message. The Failed-AVP
+ AVP MUST contain an example of the missing AVP complete with the
+ Vendor-Id if applicable. The value field of the missing AVP
+ should be of correct minimum length and contain zeroes.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_MISSING_AVP", { .u32 = ER_DIAMETER_MISSING_AVP }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5006 */
+ {
+ /*
+ A request was received that cannot be authorized because the user
+ has already expended allowed resources. An example of this error
+ condition is a user that is restricted to one dial-up PPP port,
+ attempts to establish a second PPP connection.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_RESOURCES_EXCEEDED", { .u32 = ER_DIAMETER_RESOURCES_EXCEEDED }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5007 */
+ {
+ /*
+ The Home Diameter server has detected AVPs in the request that
+ contradicted each other, and is not willing to provide service to
+ the user. The Failed-AVP AVPs MUST be present which contains the
+ AVPs that contradicted each other.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_CONTRADICTING_AVPS", { .u32 = ER_DIAMETER_CONTRADICTING_AVPS }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5008 */
+ {
+ /*
+ A message was received with an AVP that MUST NOT be present. The
+ Failed-AVP AVP MUST be included and contain a copy of the
+ offending AVP.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_AVP_NOT_ALLOWED", { .u32 = ER_DIAMETER_AVP_NOT_ALLOWED }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5009 */
+ {
+ /*
+ A message was received that included an AVP that appeared more
+ often than permitted in the message definition. The Failed-AVP
+ AVP MUST be included and contain a copy of the first instance of
+ the offending AVP that exceeded the maximum number of occurrences
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_AVP_OCCURS_TOO_MANY_TIMES",{ .u32 = ER_DIAMETER_AVP_OCCURS_TOO_MANY_TIMES }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5010 */
+ {
+ /*
+ This error is returned by a Diameter node that is not acting as a
+ relay when it receives a CER which advertises a set of
+ applications that it does not support.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_NO_COMMON_APPLICATION",{ .u32 = ER_DIAMETER_NO_COMMON_APPLICATION }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5011 */
+ {
+ /*
+ This error is returned when a request was received, whose version
+ number is unsupported.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_UNSUPPORTED_VERSION", { .u32 = ER_DIAMETER_UNSUPPORTED_VERSION }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5012 */
+ {
+ /*
+ This error is returned when a request is rejected for unspecified
+ reasons.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_UNABLE_TO_COMPLY", { .u32 = ER_DIAMETER_UNABLE_TO_COMPLY }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5013 -- will change to 3xxx */
+ {
+ /*
+ This error is returned when an unrecognized bit in the Diameter
+ header is set to one (1).
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_INVALID_BIT_IN_HEADER", { .u32 = ER_DIAMETER_INVALID_BIT_IN_HEADER }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5014 */
+ {
+ /*
+ The request contained an AVP with an invalid length. A Diameter
+ message indicating this error MUST include the offending AVPs
+ within a Failed-AVP AVP. In cases where the erroneous avp length
+ value exceeds the message length or is less than the minimum AVP
+ header length, it is sufficient to include the offending AVP
+ header and a zero filled payload of the minimum required length
+ for the payloads data type. If the AVP is a grouped AVP, the
+ grouped AVP header with an empty payload would be sufficient to
+ indicate the offending AVP. In the case where the offending AVP
+ header cannot be fully decoded when avp length is less than the
+ minimum AVP header length, it is sufficient to include an
+ offending AVP header that is formulated by padding the incomplete
+ AVP header with zero up to the minimum AVP header length.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_INVALID_AVP_LENGTH", { .u32 = ER_DIAMETER_INVALID_AVP_LENGTH }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5015 -- will change to 3xxx */
+ {
+ /*
+ This error is returned when a request is received with an invalid
+ message length.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_INVALID_MESSAGE_LENGTH", { .u32 = ER_DIAMETER_INVALID_MESSAGE_LENGTH }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5016 */
+ {
+ /*
+ The request contained an AVP with which is not allowed to have the
+ given value in the AVP Flags field. A Diameter message indicating
+ this error MUST include the offending AVPs within a Failed-AVP
+ AVP.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_INVALID_AVP_BIT_COMBO", { .u32 = ER_DIAMETER_INVALID_AVP_BIT_COMBO }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ /* 5017 */
+ {
+ /*
+ This error is returned when a CER message is received, and there
+ are no common security mechanisms supported between the peers. A
+ Capabilities-Exchange-Answer (CEA) MUST be returned with the
+ Result-Code AVP set to DIAMETER_NO_COMMON_SECURITY.
+ */
+ struct dict_enumval_data error_code = { "DIAMETER_NO_COMMON_SECURITY", { .u32 = ER_DIAMETER_NO_COMMON_SECURITY }};
+ CHECK_dict_new( DICT_ENUMVAL, &error_code , type, NULL);
+ }
+ }
+ }
+
+ /* Error-Message */
+ {
+ /*
+ The Error-Message AVP (AVP Code 281) is of type UTF8String. It MAY
+ accompany a Result-Code AVP as a human readable error message. The
+ Error-Message AVP is not intended to be useful in real-time, and
+ SHOULD NOT be expected to be parsed by network entities.
+ */
+ struct dict_avp_data data = {
+ 281, /* Code */
+ #if AC_ERROR_MESSAGE != 281
+ #error "AC_ERROR_MESSAGE definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Error-Message", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ 0, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , UTF8String_type, NULL);
+ }
+
+ /* Error-Reporting-Host */
+ {
+ /*
+ The Error-Reporting-Host AVP (AVP Code 294) is of type
+ DiameterIdentity. This AVP contains the identity of the Diameter
+ host that sent the Result-Code AVP to a value other than 2001
+ (Success), only if the host setting the Result-Code is different from
+ the one encoded in the Origin-Host AVP. This AVP is intended to be
+ used for troubleshooting purposes, and MUST be set when the Result-
+ Code AVP indicates a failure.
+ */
+ struct dict_avp_data data = {
+ 294, /* Code */
+ #if AC_ERROR_REPORTING_HOST != 294
+ #error "AC_ERROR_REPORTING_HOST definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Error-Reporting-Host", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ 0, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , DiameterIdentity_type, NULL);
+ }
+
+ /* Failed-AVP */
+ {
+ /*
+ The Failed-AVP AVP (AVP Code 279) is of type Grouped and provides
+ debugging information in cases where a request is rejected or not
+ fully processed due to erroneous information in a specific AVP. The
+ value of the Result-Code AVP will provide information on the reason
+ for the Failed-AVP AVP. A Diameter message SHOULD contain only one
+ Failed-AVP that corresponds to the error indicated by the Result-Code
+ AVP. For practical purposes, this Failed-AVP would typically refer
+ to the first AVP processing error that a Diameter node encounters.
+
+ The possible reasons for this AVP are the presence of an improperly
+ constructed AVP, an unsupported or unrecognized AVP, an invalid AVP
+ value, the omission of a required AVP, the presence of an explicitly
+ excluded AVP (see tables in Section 10), or the presence of two or
+ more occurrences of an AVP which is restricted to 0, 1, or 0-1
+ occurrences.
+
+ A Diameter message SHOULD contain one Failed-AVP AVP, containing the
+ entire AVP that could not be processed successfully. If the failure
+ reason is omission of a required AVP, an AVP with the missing AVP
+ code, the missing vendor id, and a zero filled payload of the minimum
+ required length for the omitted AVP will be added. If the failure
+ reason is an invalid AVP length where the reported length is less
+ than the minimum AVP header length or greater than the reported
+ message length, a copy of the offending AVP header and a zero filled
+ payload of the minimum required length SHOULD be added.
+
+ In the case where the offending AVP is embedded within a grouped AVP,
+ the Failed-AVP MAY contain the grouped AVP which in turn contains the
+ single offending AVP. The same method MAY be employed if the grouped
+ AVP itself is embedded in yet another grouped AVP and so on. In this
+ case, the Failed-AVP MAY contain the grouped AVP heirarchy up to the
+ single offending AVP. This enables the recipient to detect the
+ location of the offending AVP when embedded in a group.
+
+ AVP Format
+
+ <Failed-AVP> ::= < AVP Header: 279 >
+ 1* {AVP}
+ */
+ struct dict_avp_data data = {
+ 279, /* Code */
+ #if AC_FAILED_AVP != 279
+ #error "AC_FAILED_AVP definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Failed-AVP", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_GROUPED /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Experimental-Result */
+ {
+ /*
+ The Experimental-Result AVP (AVP Code 297) is of type Grouped, and
+ indicates whether a particular vendor-specific request was completed
+ successfully or whether an error occurred. Its Data field has the
+ following ABNF grammar:
+
+ AVP Format
+
+ Experimental-Result ::= < AVP Header: 297 >
+ { Vendor-Id }
+ { Experimental-Result-Code }
+
+ The Vendor-Id AVP (see Section 5.3.3) in this grouped AVP identifies
+ the vendor responsible for the assignment of the result code which
+ follows. All Diameter answer messages defined in vendor-specific
+ applications MUST include either one Result-Code AVP or one
+ Experimental-Result AVP.
+ */
+ struct dict_avp_data data = {
+ 297, /* Code */
+ 0, /* Vendor */
+ "Experimental-Result", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_GROUPED /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Experimental-Result-Code */
+ {
+ /*
+ The Experimental-Result-Code AVP (AVP Code 298) is of type Unsigned32
+ and contains a vendor-assigned value representing the result of
+ processing the request.
+
+ It is recommended that vendor-specific result codes follow the same
+ conventions given for the Result-Code AVP regarding the different
+ types of result codes and the handling of errors (for non 2xxx
+ values).
+ */
+ /* Although the RFC does not specify an "Enumerated" type here, we go forward and create one.
+ * This is the reason for the "*" in the type name. Vendors will have to define their values.
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_UNSIGNED32, "Enumerated(Experimental-Result-Code)" , NULL, NULL, NULL };
+ struct dict_avp_data data = {
+ 298, /* Code */
+ 0, /* Vendor */
+ "Experimental-Result-Code", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Auth-Request-Type */
+ {
+ /*
+ The Auth-Request-Type AVP (AVP Code 274) is of type Enumerated and is
+ included in application-specific auth requests to inform the peers
+ whether a user is to be authenticated only, authorized only or both.
+ Note any value other than both MAY cause RADIUS interoperability
+ issues. The following values are defined:
+
+
+ AUTHENTICATE_ONLY 1
+
+ The request being sent is for authentication only, and MUST
+ contain the relevant application specific authentication AVPs that
+ are needed by the Diameter server to authenticate the user.
+
+
+ AUTHORIZE_ONLY 2
+
+ The request being sent is for authorization only, and MUST contain
+ the application specific authorization AVPs that are necessary to
+ identify the service being requested/offered.
+
+
+ AUTHORIZE_AUTHENTICATE 3
+
+ The request contains a request for both authentication and
+ authorization. The request MUST include both the relevant
+ application specific authentication information, and authorization
+ information necessary to identify the service being requested/
+ offered.
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_INTEGER32, "Enumerated(Auth-Request-Type)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_1 = { "AUTHENTICATE_ONLY", { .i32 = 1 }};
+ struct dict_enumval_data t_2 = { "AUTHORIZE_ONLY", { .i32 = 2 }};
+ struct dict_enumval_data t_3 = { "AUTHORIZE_AUTHENTICATE", { .i32 = 3 }};
+ struct dict_avp_data data = {
+ 274, /* Code */
+ 0, /* Vendor */
+ "Auth-Request-Type", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_INTEGER32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_2 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_3 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Session-Id */
+ {
+ /*
+ The Session-Id AVP (AVP Code 263) is of type UTF8String and is used
+ to identify a specific session (see Section 8). All messages
+ pertaining to a specific session MUST include only one Session-Id AVP
+ and the same value MUST be used throughout the life of a session.
+ When present, the Session-Id SHOULD appear immediately following the
+ Diameter Header (see Section 3).
+
+ The Session-Id MUST be globally and eternally unique, as it is meant
+ to uniquely identify a user session without reference to any other
+ information, and may be needed to correlate historical authentication
+ information with accounting information. The Session-Id includes a
+ mandatory portion and an implementation-defined portion; a
+ recommended format for the implementation-defined portion is outlined
+ below.
+
+ (skipped, see RFC for detail)
+ */
+ struct dict_avp_data data = {
+ 263, /* Code */
+ #if AC_SESSION_ID != 263
+ #error "AC_SESSION_ID definition mismatch"
+ #endif
+ 0, /* Vendor */
+ "Session-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , UTF8String_type, NULL);
+ }
+
+ /* Authorization-Lifetime */
+ {
+ /*
+ The Authorization-Lifetime AVP (AVP Code 291) is of type Unsigned32
+ and contains the maximum number of seconds of service to be provided
+ to the user before the user is to be re-authenticated and/or re-
+ authorized. Great care should be taken when the Authorization-
+ Lifetime value is determined, since a low, non-zero, value could
+ create significant Diameter traffic, which could congest both the
+ network and the agents.
+
+ A value of zero (0) means that immediate re-auth is necessary by the
+ access device. This is typically used in cases where multiple
+ authentication methods are used, and a successful auth response with
+ this AVP set to zero is used to signal that the next authentication
+ method is to be immediately initiated. The absence of this AVP, or a
+ value of all ones (meaning all bits in the 32 bit field are set to
+ one) means no re-auth is expected.
+
+ If both this AVP and the Session-Timeout AVP are present in a
+ message, the value of the latter MUST NOT be smaller than the
+ Authorization-Lifetime AVP.
+
+ An Authorization-Lifetime AVP MAY be present in re-authorization
+ messages, and contains the number of seconds the user is authorized
+ to receive service from the time the re-auth answer message is
+ received by the access device.
+
+ This AVP MAY be provided by the client as a hint of the maximum
+ lifetime that it is willing to accept. However, the server MAY
+ return a value that is equal to, or smaller, than the one provided by
+ the client.
+ */
+ struct dict_avp_data data = {
+ 291, /* Code */
+ 0, /* Vendor */
+ "Authorization-Lifetime", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Auth-Grace-Period */
+ {
+ /*
+ The Auth-Grace-Period AVP (AVP Code 276) is of type Unsigned32 and
+ contains the number of seconds the Diameter server will wait
+ following the expiration of the Authorization-Lifetime AVP before
+ cleaning up resources for the session.
+ */
+ struct dict_avp_data data = {
+ 276, /* Code */
+ 0, /* Vendor */
+ "Auth-Grace-Period", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Auth-Session-State */
+ {
+ /*
+ The Auth-Session-State AVP (AVP Code 277) is of type Enumerated and
+ specifies whether state is maintained for a particular session. The
+ client MAY include this AVP in requests as a hint to the server, but
+ the value in the server's answer message is binding. The following
+ values are supported:
+
+
+ STATE_MAINTAINED 0
+
+ This value is used to specify that session state is being
+ maintained, and the access device MUST issue a session termination
+ message when service to the user is terminated. This is the
+ default value.
+
+
+ NO_STATE_MAINTAINED 1
+
+ This value is used to specify that no session termination messages
+ will be sent by the access device upon expiration of the
+ Authorization-Lifetime.
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_INTEGER32, "Enumerated(Auth-Session-State)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_0 = { "STATE_MAINTAINED", { .i32 = 0 }};
+ struct dict_enumval_data t_1 = { "NO_STATE_MAINTAINED", { .i32 = 1 }};
+ struct dict_avp_data data = {
+ 277, /* Code */
+ 0, /* Vendor */
+ "Auth-Session-State", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_INTEGER32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_0 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Re-Auth-Request-Type */
+ {
+ /*
+ The Re-Auth-Request-Type AVP (AVP Code 285) is of type Enumerated and
+ is included in application-specific auth answers to inform the client
+ of the action expected upon expiration of the Authorization-Lifetime.
+ If the answer message contains an Authorization-Lifetime AVP with a
+ positive value, the Re-Auth-Request-Type AVP MUST be present in an
+ answer message. The following values are defined:
+
+
+ AUTHORIZE_ONLY 0
+
+ An authorization only re-auth is expected upon expiration of the
+ Authorization-Lifetime. This is the default value if the AVP is
+ not present in answer messages that include the Authorization-
+ Lifetime.
+
+
+ AUTHORIZE_AUTHENTICATE 1
+
+ An authentication and authorization re-auth is expected upon
+ expiration of the Authorization-Lifetime.
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_INTEGER32, "Enumerated(Re-Auth-Request-Type)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_0 = { "AUTHORIZE_ONLY", { .i32 = 0 }};
+ struct dict_enumval_data t_1 = { "AUTHORIZE_AUTHENTICATE", { .i32 = 1 }};
+ struct dict_avp_data data = {
+ 285, /* Code */
+ 0, /* Vendor */
+ "Re-Auth-Request-Type", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_INTEGER32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_0 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Session-Timeout */
+ {
+ /*
+ The Session-Timeout AVP (AVP Code 27) [RFC2865] is of type Unsigned32
+ and contains the maximum number of seconds of service to be provided
+ to the user before termination of the session. When both the
+ Session-Timeout and the Authorization-Lifetime AVPs are present in an
+ answer message, the former MUST be equal to or greater than the value
+ of the latter.
+
+ A session that terminates on an access device due to the expiration
+ of the Session-Timeout MUST cause an STR to be issued, unless both
+ the access device and the home server had previously agreed that no
+ session termination messages would be sent (see Section 8.11).
+
+ A Session-Timeout AVP MAY be present in a re-authorization answer
+ message, and contains the remaining number of seconds from the
+ beginning of the re-auth.
+
+ A value of zero, or the absence of this AVP, means that this session
+ has an unlimited number of seconds before termination.
+
+ This AVP MAY be provided by the client as a hint of the maximum
+ timeout that it is willing to accept. However, the server MAY return
+ a value that is equal to, or smaller, than the one provided by the
+ client.
+ */
+ struct dict_avp_data data = {
+ 27, /* Code */
+ 0, /* Vendor */
+ "Session-Timeout", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* User-Name */
+ {
+ /*
+ The User-Name AVP (AVP Code 1) [RFC2865] is of type UTF8String, which
+ contains the User-Name, in a format consistent with the NAI
+ specification [RFC4282].
+ */
+ struct dict_avp_data data = {
+ 1, /* Code */
+ 0, /* Vendor */
+ "User-Name", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , UTF8String_type, NULL);
+ }
+
+ /* Termination-Cause */
+ {
+ /*
+ The Termination-Cause AVP (AVP Code 295) is of type Enumerated, and
+ is used to indicate the reason why a session was terminated on the
+ access device. The following values are defined:
+
+
+ DIAMETER_LOGOUT 1
+
+ The user initiated a disconnect
+
+
+ DIAMETER_SERVICE_NOT_PROVIDED 2
+
+ This value is used when the user disconnected prior to the receipt
+ of the authorization answer message.
+
+
+ DIAMETER_BAD_ANSWER 3
+
+ This value indicates that the authorization answer received by the
+ access device was not processed successfully.
+
+
+ DIAMETER_ADMINISTRATIVE 4
+
+ The user was not granted access, or was disconnected, due to
+ administrative reasons, such as the receipt of a Abort-Session-
+ Request message.
+
+
+ DIAMETER_LINK_BROKEN 5
+
+ The communication to the user was abruptly disconnected.
+
+
+ DIAMETER_AUTH_EXPIRED 6
+
+ The user's access was terminated since its authorized session time
+ has expired.
+
+
+ DIAMETER_USER_MOVED 7
+
+ The user is receiving services from another access device.
+
+
+ DIAMETER_SESSION_TIMEOUT 8
+
+ The user's session has timed out, and service has been terminated.
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_INTEGER32, "Enumerated(Termination-Cause)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_1 = { "DIAMETER_LOGOUT", { .i32 = 1 }};
+ struct dict_enumval_data t_2 = { "DIAMETER_SERVICE_NOT_PROVIDED", { .i32 = 2 }};
+ struct dict_enumval_data t_3 = { "DIAMETER_BAD_ANSWER", { .i32 = 3 }};
+ struct dict_enumval_data t_4 = { "DIAMETER_ADMINISTRATIVE", { .i32 = 4 }};
+ struct dict_enumval_data t_5 = { "DIAMETER_LINK_BROKEN", { .i32 = 5 }};
+ struct dict_enumval_data t_6 = { "DIAMETER_AUTH_EXPIRED", { .i32 = 6 }};
+ struct dict_enumval_data t_7 = { "DIAMETER_USER_MOVED", { .i32 = 7 }};
+ struct dict_enumval_data t_8 = { "DIAMETER_SESSION_TIMEOUT", { .i32 = 8 }};
+ struct dict_avp_data data = {
+ 295, /* Code */
+ 0, /* Vendor */
+ "Termination-Cause", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_INTEGER32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_2 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_3 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_4 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_5 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_6 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_7 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_8 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Origin-State-Id */
+ {
+ /*
+ The Origin-State-Id AVP (AVP Code 278), of type Unsigned32, is a
+ monotonically increasing value that is advanced whenever a Diameter
+ entity restarts with loss of previous state, for example upon reboot.
+ Origin-State-Id MAY be included in any Diameter message, including
+ CER.
+
+ A Diameter entity issuing this AVP MUST create a higher value for
+ this AVP each time its state is reset. A Diameter entity MAY set
+ Origin-State-Id to the time of startup, or it MAY use an incrementing
+ counter retained in non-volatile memory across restarts.
+
+ The Origin-State-Id, if present, MUST reflect the state of the entity
+ indicated by Origin-Host. If a proxy modifies Origin-Host, it MUST
+ either remove Origin-State-Id or modify it appropriately as well.
+ Typically, Origin-State-Id is used by an access device that always
+ starts up with no active sessions; that is, any session active prior
+ to restart will have been lost. By including Origin-State-Id in a
+ message, it allows other Diameter entities to infer that sessions
+ associated with a lower Origin-State-Id are no longer active. If an
+ access device does not intend for such inferences to be made, it MUST
+ either not include Origin-State-Id in any message, or set its value
+ to 0.
+ */
+ struct dict_avp_data data = {
+ 278, /* Code */
+ 0, /* Vendor */
+ "Origin-State-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Session-Binding */
+ {
+ /*
+ The Session-Binding AVP (AVP Code 270) is of type Unsigned32, and MAY
+ be present in application-specific authorization answer messages. If
+ present, this AVP MAY inform the Diameter client that all future
+ application-specific re-auth messages for this session MUST be sent
+ to the same authorization server. This AVP MAY also specify that a
+ Session-Termination-Request message for this session MUST be sent to
+ the same authorizing server.
+
+ This field is a bit mask, and the following bits have been defined:
+
+
+ RE_AUTH 1
+
+ When set, future re-auth messages for this session MUST NOT
+ include the Destination-Host AVP. When cleared, the default
+ value, the Destination-Host AVP MUST be present in all re-auth
+ messages for this session.
+
+
+ STR 2
+
+ When set, the STR message for this session MUST NOT include the
+ Destination-Host AVP. When cleared, the default value, the
+ Destination-Host AVP MUST be present in the STR message for this
+ session.
+
+
+ ACCOUNTING 4
+
+ When set, all accounting messages for this session MUST NOT
+ include the Destination-Host AVP. When cleared, the default
+ value, the Destination-Host AVP, if known, MUST be present in all
+ accounting messages for this session.
+ */
+
+ /* Although the RFC does not specify an "Enumerated" type here, we go forward and create one.
+ * This is the reason for the "*" in the type name
+ * The actual values of the AVP will not always be defined here, but at least it can be used in some cases.
+ * ... maybe the code will be changed later to support bitfields AVP ...
+ */
+
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_UNSIGNED32, "Enumerated(Session-Binding)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_1 = { "RE_AUTH", { .u32 = 1 }};
+ struct dict_enumval_data t_2 = { "STR", { .u32 = 2 }};
+ struct dict_enumval_data t_4 = { "ACCOUNTING", { .u32 = 4 }};
+ struct dict_avp_data data = {
+ 270, /* Code */
+ 0, /* Vendor */
+ "Session-Binding", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_2 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_4 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Session-Server-Failover */
+ {
+ /*
+ The Session-Server-Failover AVP (AVP Code 271) is of type Enumerated,
+ and MAY be present in application-specific authorization answer
+ messages that either do not include the Session-Binding AVP or
+ include the Session-Binding AVP with any of the bits set to a zero
+ value. If present, this AVP MAY inform the Diameter client that if a
+ re-auth or STR message fails due to a delivery problem, the Diameter
+ client SHOULD issue a subsequent message without the Destination-Host
+ AVP. When absent, the default value is REFUSE_SERVICE.
+
+ The following values are supported:
+
+
+ REFUSE_SERVICE 0
+
+ If either the re-auth or the STR message delivery fails, terminate
+ service with the user, and do not attempt any subsequent attempts.
+
+
+ TRY_AGAIN 1
+
+ If either the re-auth or the STR message delivery fails, resend
+ the failed message without the Destination-Host AVP present.
+
+
+ ALLOW_SERVICE 2
+
+ If re-auth message delivery fails, assume that re-authorization
+ succeeded. If STR message delivery fails, terminate the session.
+
+
+ TRY_AGAIN_ALLOW_SERVICE 3
+
+ If either the re-auth or the STR message delivery fails, resend
+ the failed message without the Destination-Host AVP present. If
+ the second delivery fails for re-auth, assume re-authorization
+ succeeded. If the second delivery fails for STR, terminate the
+ session.
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_INTEGER32, "Enumerated(Session-Server-Failover)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_0 = { "REFUSE_SERVICE", { .i32 = 0 }};
+ struct dict_enumval_data t_1 = { "TRY_AGAIN", { .i32 = 1 }};
+ struct dict_enumval_data t_2 = { "ALLOW_SERVICE", { .i32 = 2 }};
+ struct dict_enumval_data t_3 = { "TRY_AGAIN_ALLOW_SERVICE", { .i32 = 3 }};
+ struct dict_avp_data data = {
+ 271, /* Code */
+ 0, /* Vendor */
+ "Session-Server-Failover", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_INTEGER32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_0 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_2 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_3 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Multi-Round-Time-Out */
+ {
+ /*
+ The Multi-Round-Time-Out AVP (AVP Code 272) is of type Unsigned32,
+ and SHOULD be present in application-specific authorization answer
+ messages whose Result-Code AVP is set to DIAMETER_MULTI_ROUND_AUTH.
+ This AVP contains the maximum number of seconds that the access
+ device MUST provide the user in responding to an authentication
+ request.
+ */
+ struct dict_avp_data data = {
+ 272, /* Code */
+ 0, /* Vendor */
+ "Multi-Round-Time-Out", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Class */
+ {
+ /*
+ The Class AVP (AVP Code 25) is of type OctetString and is used to by
+ Diameter servers to return state information to the access device.
+ When one or more Class AVPs are present in application-specific
+ authorization answer messages, they MUST be present in subsequent re-
+ authorization, session termination and accounting messages. Class
+ AVPs found in a re-authorization answer message override the ones
+ found in any previous authorization answer message. Diameter server
+ implementations SHOULD NOT return Class AVPs that require more than
+ 4096 bytes of storage on the Diameter client. A Diameter client that
+ receives Class AVPs whose size exceeds local available storage MUST
+ terminate the session.
+ */
+ struct dict_avp_data data = {
+ 25, /* Code */
+ 0, /* Vendor */
+ "Class", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Event-Timestamp */
+ {
+ /*
+ The Event-Timestamp (AVP Code 55) is of type Time, and MAY be
+ included in an Accounting-Request and Accounting-Answer messages to
+ record the time that the reported event occurred, in seconds since
+ January 1, 1900 00:00 UTC.
+ */
+ struct dict_avp_data data = {
+ 55, /* Code */
+ 0, /* Vendor */
+ "Event-Timestamp", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , Time_type, NULL);
+ }
+
+
+ /* Accounting-Record-Type */
+ {
+ /*
+ The Accounting-Record-Type AVP (AVP Code 480) is of type Enumerated
+ and contains the type of accounting record being sent. The following
+ values are currently defined for the Accounting-Record-Type AVP:
+
+
+ EVENT_RECORD 1
+
+ An Accounting Event Record is used to indicate that a one-time
+ event has occurred (meaning that the start and end of the event
+ are simultaneous). This record contains all information relevant
+ to the service, and is the only record of the service.
+
+
+ START_RECORD 2
+
+ An Accounting Start, Interim, and Stop Records are used to
+ indicate that a service of a measurable length has been given. An
+ Accounting Start Record is used to initiate an accounting session,
+ and contains accounting information that is relevant to the
+ initiation of the session.
+
+
+ INTERIM_RECORD 3
+
+ An Interim Accounting Record contains cumulative accounting
+ information for an existing accounting session. Interim
+ Accounting Records SHOULD be sent every time a re-authentication
+ or re-authorization occurs. Further, additional interim record
+ triggers MAY be defined by application-specific Diameter
+ applications. The selection of whether to use INTERIM_RECORD
+ records is done by the Acct-Interim-Interval AVP.
+
+
+ STOP_RECORD 4
+
+ An Accounting Stop Record is sent to terminate an accounting
+ session and contains cumulative accounting information relevant to
+ the existing session.
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_INTEGER32, "Enumerated(Accounting-Record-Type)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_1 = { "EVENT_RECORD", { .i32 = 1 }};
+ struct dict_enumval_data t_2 = { "START_RECORD", { .i32 = 2 }};
+ struct dict_enumval_data t_3 = { "INTERIM_RECORD", { .i32 = 3 }};
+ struct dict_enumval_data t_4 = { "STOP_RECORD", { .i32 = 4 }};
+ struct dict_avp_data data = {
+ 480, /* Code */
+ 0, /* Vendor */
+ "Accounting-Record-Type", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_INTEGER32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_2 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_3 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_4 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ /* Acct-Interim-Interval */
+ {
+ /*
+ The Acct-Interim-Interval AVP (AVP Code 85) is of type Unsigned32 and
+ is sent from the Diameter home authorization server to the Diameter
+ client. The client uses information in this AVP to decide how and
+ when to produce accounting records. With different values in this
+ AVP, service sessions can result in one, two, or two+N accounting
+ records, based on the needs of the home-organization. The following
+ accounting record production behavior is directed by the inclusion of
+ this AVP:
+
+
+ 1. The omission of the Acct-Interim-Interval AVP or its inclusion
+ with Value field set to 0 means that EVENT_RECORD, START_RECORD,
+ and STOP_RECORD are produced, as appropriate for the service.
+
+
+ 2. The inclusion of the AVP with Value field set to a non-zero value
+ means that INTERIM_RECORD records MUST be produced between the
+ START_RECORD and STOP_RECORD records. The Value field of this
+ AVP is the nominal interval between these records in seconds.
+
+ The Diameter node that originates the accounting information,
+ known as the client, MUST produce the first INTERIM_RECORD record
+ roughly at the time when this nominal interval has elapsed from
+ the START_RECORD, the next one again as the interval has elapsed
+ once more, and so on until the session ends and a STOP_RECORD
+ record is produced.
+
+ The client MUST ensure that the interim record production times
+ are randomized so that large accounting message storms are not
+ created either among records or around a common service start
+ time.
+ */
+ struct dict_avp_data data = {
+ 85, /* Code */
+ 0, /* Vendor */
+ "Acct-Interim-Interval", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Accounting-Record-Number */
+ {
+ /*
+ The Accounting-Record-Number AVP (AVP Code 485) is of type Unsigned32
+ and identifies this record within one session. As Session-Id AVPs
+ are globally unique, the combination of Session-Id and Accounting-
+ Record-Number AVPs is also globally unique, and can be used in
+ matching accounting records with confirmations. An easy way to
+ produce unique numbers is to set the value to 0 for records of type
+ EVENT_RECORD and START_RECORD, and set the value to 1 for the first
+ INTERIM_RECORD, 2 for the second, and so on until the value for
+ STOP_RECORD is one more than for the last INTERIM_RECORD.
+ */
+ struct dict_avp_data data = {
+ 485, /* Code */
+ 0, /* Vendor */
+ "Accounting-Record-Number", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED32 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Acct-Session-Id */
+ {
+ /*
+ The Acct-Session-Id AVP (AVP Code 44) is of type OctetString is only
+ used when RADIUS/Diameter translation occurs. This AVP contains the
+ contents of the RADIUS Acct-Session-Id attribute.
+ */
+ struct dict_avp_data data = {
+ 44, /* Code */
+ 0, /* Vendor */
+ "Acct-Session-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Acct-Multi-Session-Id */
+ {
+ /*
+ The Acct-Multi-Session-Id AVP (AVP Code 50) is of type UTF8String,
+ following the format specified in Section 8.8. The Acct-Multi-
+ Session-Id AVP is used to link together multiple related accounting
+ sessions, where each session would have a unique Session-Id, but the
+ same Acct-Multi-Session-Id AVP. This AVP MAY be returned by the
+ Diameter server in an authorization answer, and MUST be used in all
+ accounting messages for the given session.
+ */
+ struct dict_avp_data data = {
+ 50, /* Code */
+ 0, /* Vendor */
+ "Acct-Multi-Session-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_OCTETSTRING /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , UTF8String_type, NULL);
+ }
+
+ /* Accounting-Sub-Session-Id */
+ {
+ /*
+ The Accounting-Sub-Session-Id AVP (AVP Code 287) is of type
+ Unsigned64 and contains the accounting sub-session identifier. The
+ combination of the Session-Id and this AVP MUST be unique per sub-
+ session, and the value of this AVP MUST be monotonically increased by
+ one for all new sub-sessions. The absence of this AVP implies no
+ sub-sessions are in use, with the exception of an Accounting-Request
+ whose Accounting-Record-Type is set to STOP_RECORD. A STOP_RECORD
+ message with no Accounting-Sub-Session-Id AVP present will signal the
+ termination of all sub-sessions for a given Session-Id.
+ */
+ struct dict_avp_data data = {
+ 287, /* Code */
+ 0, /* Vendor */
+ "Accounting-Sub-Session-Id", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_UNSIGNED64 /* base type of data */
+ };
+ CHECK_dict_new( DICT_AVP, &data , NULL, NULL);
+ }
+
+ /* Accounting-Realtime-Required */
+ {
+ /*
+ The Accounting-Realtime-Required AVP (AVP Code 483) is of type
+ Enumerated and is sent from the Diameter home authorization server to
+ the Diameter client or in the Accounting-Answer from the accounting
+ server. The client uses information in this AVP to decide what to do
+ if the sending of accounting records to the accounting server has
+ been temporarily prevented due to, for instance, a network problem.
+
+
+ DELIVER_AND_GRANT 1
+
+ The AVP with Value field set to DELIVER_AND_GRANT means that the
+ service MUST only be granted as long as there is a connection to
+ an accounting server. Note that the set of alternative accounting
+ servers are treated as one server in this sense. Having to move
+ the accounting record stream to a backup server is not a reason to
+ discontinue the service to the user.
+
+
+ GRANT_AND_STORE 2
+
+ The AVP with Value field set to GRANT_AND_STORE means that service
+ SHOULD be granted if there is a connection, or as long as records
+ can still be stored as described in Section 9.4.
+
+ This is the default behavior if the AVP isn't included in the
+ reply from the authorization server.
+
+
+ GRANT_AND_LOSE 3
+
+ The AVP with Value field set to GRANT_AND_LOSE means that service
+ SHOULD be granted even if the records can not be delivered or
+ stored.
+ */
+ struct dict_object * type;
+ struct dict_type_data tdata = { AVP_TYPE_INTEGER32, "Enumerated(Accounting-Realtime-Required)" , NULL, NULL, NULL };
+ struct dict_enumval_data t_1 = { "DELIVER_AND_GRANT", { .i32 = 1 }};
+ struct dict_enumval_data t_2 = { "GRANT_AND_STORE", { .i32 = 2 }};
+ struct dict_enumval_data t_3 = { "GRANT_AND_LOSE", { .i32 = 3 }};
+ struct dict_avp_data data = {
+ 483, /* Code */
+ 0, /* Vendor */
+ "Accounting-Realtime-Required", /* Name */
+ AVP_FLAG_VENDOR | AVP_FLAG_MANDATORY, /* Fixed flags */
+ AVP_FLAG_MANDATORY, /* Fixed flag values */
+ AVP_TYPE_INTEGER32 /* base type of data */
+ };
+ /* Create the Enumerated type, and then the AVP */
+ CHECK_dict_new( DICT_TYPE, &tdata , NULL, &type);
+ CHECK_dict_new( DICT_ENUMVAL, &t_1 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_2 , type, NULL);
+ CHECK_dict_new( DICT_ENUMVAL, &t_3 , type, NULL);
+ CHECK_dict_new( DICT_AVP, &data , type, NULL);
+ }
+
+ }
+
+ /* Commands section */
+ {
+ /* To avoid defining global variables for all the AVP that we use here, we do search the dictionary in each sub-block.
+ * This is far from optimal, but the code is clearer like this, and the time it requires at execution is not noticeable.
+ */
+
+ /* Generic message syntax when the 'E' bit is set */
+ {
+ /*
+ The 'E' (Error Bit) in the Diameter header is set when the request
+ caused a protocol-related error (see Section 7.1.3). A message with
+ the 'E' bit MUST NOT be sent as a response to an answer message.
+ Note that a message with the 'E' bit set is still subjected to the
+ processing rules defined in Section 6.2. When set, the answer
+ message will not conform to the ABNF specification for the command,
+ and will instead conform to the following ABNF:
+
+ Message Format
+
+ <answer-message> ::= < Diameter Header: code, ERR [PXY] >
+ 0*1< Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Result-Code }
+ [ Origin-State-Id ]
+ [ Error-Message ]
+ [ Error-Reporting-Host ]
+ [ Failed-AVP ]
+ * [ Proxy-Info ]
+ * [ AVP ]
+
+ Note that the code used in the header is the same than the one found
+ in the request message, but with the 'R' bit cleared and the 'E' bit
+ set. The 'P' bit in the header is set to the same value as the one
+ found in the request message.
+ */
+ struct dict_object * cmd_error;
+ struct local_rules_definition rules[] =
+ { { "Session-Id", RULE_FIXED_HEAD,0, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Result-Code", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Message", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Reporting-Host", RULE_OPTIONAL, -1, 1 }
+ ,{ "Failed-AVP", RULE_OPTIONAL, -1, 1 }
+ ,{ "Proxy-Info", RULE_OPTIONAL, -1,-1 }
+ };
+ CHECK_FCT( fd_dict_get_error_cmd(dict, &cmd_error) );
+ PARSE_loc_rules( rules, cmd_error );
+ }
+
+ /* Capabilities-Exchange-Request */
+ {
+ /*
+ The Capabilities-Exchange-Request (CER), indicated by the Command-
+ Code set to 257 and the Command Flags' 'R' bit set, is sent to
+ exchange local capabilities. Upon detection of a transport failure,
+ this message MUST NOT be sent to an alternate peer.
+
+ When Diameter is run over SCTP [RFC2960], which allows for
+ connections to span multiple interfaces and multiple IP addresses,
+ the Capabilities-Exchange-Request message MUST contain one Host-IP-
+ Address AVP for each potential IP address that MAY be locally used
+ when transmitting Diameter messages.
+
+ Message Format
+
+ <CER> ::= < Diameter Header: 257, REQ >
+ { Origin-Host }
+ { Origin-Realm }
+ 1* { Host-IP-Address }
+ { Vendor-Id }
+ { Product-Name }
+ [ Origin-State-Id ]
+ * [ Supported-Vendor-Id ]
+ * [ Auth-Application-Id ]
+ * [ Inband-Security-Id ]
+ * [ Acct-Application-Id ]
+ * [ Vendor-Specific-Application-Id ]
+ [ Firmware-Revision ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 257, /* Code */
+ #if CC_CAPABILITIES_EXCHANGE != 257
+ #error "CC_CAPABILITIES_EXCHANGE definition mismatch"
+ #endif
+ "Capabilities-Exchange-Request", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_RETRANSMIT | CMD_FLAG_ERROR, /* Fixed flags */
+ CMD_FLAG_REQUEST /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Host-IP-Address", RULE_REQUIRED, -1,-1 }
+ ,{ "Vendor-Id", RULE_REQUIRED, -1, 1 }
+ ,{ "Product-Name", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Supported-Vendor-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Auth-Application-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Inband-Security-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Acct-Application-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Vendor-Specific-Application-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Firmware-Revision", RULE_OPTIONAL, -1, 1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Capabilities-Exchange-Answer */
+ {
+ /*
+ The Capabilities-Exchange-Answer (CEA), indicated by the Command-Code
+ set to 257 and the Command Flags' 'R' bit cleared, is sent in
+ response to a CER message.
+
+ When Diameter is run over SCTP [RFC2960], which allows connections to
+ span multiple interfaces, hence, multiple IP addresses, the
+ Capabilities-Exchange-Answer message MUST contain one Host-IP-Address
+ AVP for each potential IP address that MAY be locally used when
+ transmitting Diameter messages.
+
+ Message Format
+
+ <CEA> ::= < Diameter Header: 257 >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ 1* { Host-IP-Address }
+ { Vendor-Id }
+ { Product-Name }
+ [ Origin-State-Id ]
+ [ Error-Message ]
+ [ Failed-AVP ]
+ * [ Supported-Vendor-Id ]
+ * [ Auth-Application-Id ]
+ * [ Inband-Security-Id ]
+ * [ Acct-Application-Id ]
+ * [ Vendor-Specific-Application-Id ]
+ [ Firmware-Revision ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 257, /* Code */
+ #if CC_CAPABILITIES_EXCHANGE != 257
+ #error "CC_CAPABILITIES_EXCHANGE definition mismatch"
+ #endif
+ "Capabilities-Exchange-Answer", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_RETRANSMIT, /* Fixed flags */
+ 0 /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Result-Code", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Host-IP-Address", RULE_REQUIRED, -1,-1 }
+ ,{ "Vendor-Id", RULE_REQUIRED, -1, 1 }
+ ,{ "Product-Name", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Message", RULE_OPTIONAL, -1, 1 }
+ ,{ "Failed-AVP", RULE_OPTIONAL, -1, 1 }
+ ,{ "Supported-Vendor-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Auth-Application-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Inband-Security-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Acct-Application-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Vendor-Specific-Application-Id", RULE_OPTIONAL, -1,-1 }
+ ,{ "Firmware-Revision", RULE_OPTIONAL, -1, 1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Disconnect-Peer-Request */
+ {
+ /*
+ The Disconnect-Peer-Request (DPR), indicated by the Command-Code set
+ to 282 and the Command Flags' 'R' bit set, is sent to a peer to
+ inform its intentions to shutdown the transport connection. Upon
+ detection of a transport failure, this message MUST NOT be sent to an
+ alternate peer.
+
+ Message Format
+
+ <DPR> ::= < Diameter Header: 282, REQ >
+ { Origin-Host }
+ { Origin-Realm }
+ { Disconnect-Cause }
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 282, /* Code */
+ #if CC_DISCONNECT_PEER != 282
+ #error "CC_DISCONNECT_PEER definition mismatch"
+ #endif
+ "Disconnect-Peer-Request", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_RETRANSMIT | CMD_FLAG_ERROR, /* Fixed flags */
+ CMD_FLAG_REQUEST /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Disconnect-Cause", RULE_REQUIRED, -1, 1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Disconnect-Peer-Answer */
+ {
+ /*
+ The Disconnect-Peer-Answer (DPA), indicated by the Command-Code set
+ to 282 and the Command Flags' 'R' bit cleared, is sent as a response
+ to the Disconnect-Peer-Request message. Upon receipt of this
+ message, the transport connection is shutdown.
+
+ Message Format
+
+ <DPA> ::= < Diameter Header: 282 >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ Error-Message ]
+ [ Failed-AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 282, /* Code */
+ #if CC_DISCONNECT_PEER != 282
+ #error "CC_DISCONNECT_PEER definition mismatch"
+ #endif
+ "Disconnect-Peer-Answer", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_RETRANSMIT, /* Fixed flags */
+ 0 /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Result-Code", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Error-Message", RULE_OPTIONAL, -1, 1 }
+ ,{ "Failed-AVP", RULE_OPTIONAL, -1, 1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Device-Watchdog-Request */
+ {
+ /*
+ The Device-Watchdog-Request (DWR), indicated by the Command-Code set
+ to 280 and the Command Flags' 'R' bit set, is sent to a peer when no
+ traffic has been exchanged between two peers (see Section 5.5.3).
+ Upon detection of a transport failure, this message MUST NOT be sent
+ to an alternate peer.
+
+ Message Format
+
+ <DWR> ::= < Diameter Header: 280, REQ >
+ { Origin-Host }
+ { Origin-Realm }
+ [ Origin-State-Id ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 280, /* Code */
+ #if CC_DEVICE_WATCHDOG != 280
+ #error "CC_DEVICE_WATCHDOG definition mismatch"
+ #endif
+ "Device-Watchdog-Request", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_RETRANSMIT | CMD_FLAG_ERROR, /* Fixed flags */
+ CMD_FLAG_REQUEST /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Device-Watchdog-Answer */
+ {
+ /*
+ The Device-Watchdog-Answer (DWA), indicated by the Command-Code set
+ to 280 and the Command Flags' 'R' bit cleared, is sent as a response
+ to the Device-Watchdog-Request message.
+
+ Message Format
+
+ <DWA> ::= < Diameter Header: 280 >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ Error-Message ]
+ [ Failed-AVP ]
+ [ Origin-State-Id ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 280, /* Code */
+ #if CC_DEVICE_WATCHDOG != 280
+ #error "CC_DEVICE_WATCHDOG definition mismatch"
+ #endif
+ "Device-Watchdog-Answer", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_RETRANSMIT, /* Fixed flags */
+ 0 /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Result-Code", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Error-Message", RULE_OPTIONAL, -1, 1 }
+ ,{ "Failed-AVP", RULE_OPTIONAL, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Re-Auth-Request */
+ {
+ /*
+ The Re-Auth-Request (RAR), indicated by the Command-Code set to 258
+ and the message flags' 'R' bit set, may be sent by any server to the
+ access device that is providing session service, to request that the
+ user be re-authenticated and/or re-authorized.
+
+
+ Message Format
+
+ <RAR> ::= < Diameter Header: 258, REQ, PXY >
+ < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Destination-Realm }
+ { Destination-Host }
+ { Auth-Application-Id }
+ { Re-Auth-Request-Type }
+ [ User-Name ]
+ [ Origin-State-Id ]
+ * [ Proxy-Info ]
+ * [ Route-Record ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 258, /* Code */
+ #if CC_RE_AUTH != 258
+ #error "CC_RE_AUTH definition mismatch"
+ #endif
+ "Re-Auth-Request", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_ERROR, /* Fixed flags */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Session-Id", RULE_FIXED_HEAD, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Destination-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Destination-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Auth-Application-Id", RULE_REQUIRED, -1, 1 }
+ ,{ "Re-Auth-Request-Type", RULE_REQUIRED, -1, 1 }
+ ,{ "User-Name", RULE_OPTIONAL, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Proxy-Info", RULE_OPTIONAL, -1,-1 }
+ ,{ "Route-Record", RULE_OPTIONAL, -1,-1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Re-Auth-Answer */
+ {
+ /*
+ The Re-Auth-Answer (RAA), indicated by the Command-Code set to 258
+ and the message flags' 'R' bit clear, is sent in response to the RAR.
+ The Result-Code AVP MUST be present, and indicates the disposition of
+ the request.
+
+ A successful RAA message MUST be followed by an application-specific
+ authentication and/or authorization message.
+
+
+ Message Format
+
+ <RAA> ::= < Diameter Header: 258, PXY >
+ < Session-Id >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ User-Name ]
+ [ Origin-State-Id ]
+ [ Error-Message ]
+ [ Error-Reporting-Host ]
+ [ Failed-AVP ]
+ * [ Redirect-Host ]
+ [ Redirect-Host-Usage ]
+ [ Redirect-Max-Cache-Time ]
+ * [ Proxy-Info ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 258, /* Code */
+ #if CC_RE_AUTH != 258
+ #error "CC_RE_AUTH definition mismatch"
+ #endif
+ "Re-Auth-Answer", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE, /* Fixed flags */
+ CMD_FLAG_PROXIABLE /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Session-Id", RULE_FIXED_HEAD, -1, 1 }
+ ,{ "Result-Code", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "User-Name", RULE_OPTIONAL, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Message", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Reporting-Host", RULE_OPTIONAL, -1, 1 }
+ ,{ "Failed-AVP", RULE_OPTIONAL, -1, 1 }
+ ,{ "Redirect-Host", RULE_OPTIONAL, -1,-1 }
+ ,{ "Redirect-Host-Usage", RULE_OPTIONAL, -1, 1 }
+ ,{ "Redirect-Max-Cache-Time", RULE_OPTIONAL, -1, 1 }
+ ,{ "Proxy-Info", RULE_OPTIONAL, -1,-1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Session-Termination-Request */
+ {
+ /*
+ The Session-Termination-Request (STR), indicated by the Command-Code
+ set to 275 and the Command Flags' 'R' bit set, is sent by the access
+ device to inform the Diameter Server that an authenticated and/or
+ authorized session is being terminated.
+
+
+ Message Format
+
+ <STR> ::= < Diameter Header: 275, REQ, PXY >
+ < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Destination-Realm }
+ { Auth-Application-Id }
+ { Termination-Cause }
+ [ User-Name ]
+ [ Destination-Host ]
+ * [ Class ]
+ [ Origin-State-Id ]
+ * [ Proxy-Info ]
+ * [ Route-Record ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 275, /* Code */
+ #if CC_SESSION_TERMINATION != 275
+ #error "CC_SESSION_TERMINATION definition mismatch"
+ #endif
+ "Session-Termination-Request", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_ERROR, /* Fixed flags */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Session-Id", RULE_FIXED_HEAD, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Destination-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Auth-Application-Id", RULE_REQUIRED, -1, 1 }
+ ,{ "Termination-Cause", RULE_REQUIRED, -1, 1 }
+ ,{ "User-Name", RULE_OPTIONAL, -1, 1 }
+ ,{ "Destination-Host", RULE_OPTIONAL, -1, 1 }
+ ,{ "Class", RULE_OPTIONAL, -1,-1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Proxy-Info", RULE_OPTIONAL, -1,-1 }
+ ,{ "Route-Record", RULE_OPTIONAL, -1,-1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Session-Termination-Answer */
+ {
+ /*
+ The Session-Termination-Answer (STA), indicated by the Command-Code
+ set to 275 and the message flags' 'R' bit clear, is sent by the
+ Diameter Server to acknowledge the notification that the session has
+ been terminated. The Result-Code AVP MUST be present, and MAY
+ contain an indication that an error occurred while servicing the STR.
+
+ Upon sending or receipt of the STA, the Diameter Server MUST release
+ all resources for the session indicated by the Session-Id AVP. Any
+ intermediate server in the Proxy-Chain MAY also release any
+ resources, if necessary.
+
+ Message Format
+
+ <STA> ::= < Diameter Header: 275, PXY >
+ < Session-Id >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ User-Name ]
+ * [ Class ]
+ [ Error-Message ]
+ [ Error-Reporting-Host ]
+ [ Failed-AVP ]
+ [ Origin-State-Id ]
+ * [ Redirect-Host ]
+ [ Redirect-Host-Usage ]
+ [ Redirect-Max-Cache-Time ]
+ * [ Proxy-Info ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 275, /* Code */
+ #if CC_SESSION_TERMINATION != 275
+ #error "CC_SESSION_TERMINATION definition mismatch"
+ #endif
+ "Session-Termination-Answer", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE, /* Fixed flags */
+ CMD_FLAG_PROXIABLE /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Session-Id", RULE_FIXED_HEAD, -1, 1 }
+ ,{ "Result-Code", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "User-Name", RULE_OPTIONAL, -1, 1 }
+ ,{ "Class", RULE_OPTIONAL, -1,-1 }
+ ,{ "Error-Message", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Reporting-Host", RULE_OPTIONAL, -1, 1 }
+ ,{ "Failed-AVP", RULE_OPTIONAL, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Redirect-Host", RULE_OPTIONAL, -1,-1 }
+ ,{ "Redirect-Host-Usage", RULE_OPTIONAL, -1, 1 }
+ ,{ "Redirect-Max-Cache-Time", RULE_OPTIONAL, -1, 1 }
+ ,{ "Proxy-Info", RULE_OPTIONAL, -1,-1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Abort-Session-Request */
+ {
+ /*
+ The Abort-Session-Request (ASR), indicated by the Command-Code set to
+ 274 and the message flags' 'R' bit set, may be sent by any server to
+ the access device that is providing session service, to request that
+ the session identified by the Session-Id be stopped.
+
+
+ Message Format
+
+ <ASR> ::= < Diameter Header: 274, REQ, PXY >
+ < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Destination-Realm }
+ { Destination-Host }
+ { Auth-Application-Id }
+ [ User-Name ]
+ [ Origin-State-Id ]
+ * [ Proxy-Info ]
+ * [ Route-Record ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 274, /* Code */
+ #if CC_ABORT_SESSION != 274
+ #error "CC_ABORT_SESSION definition mismatch"
+ #endif
+ "Abort-Session-Request", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_ERROR, /* Fixed flags */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Session-Id", RULE_FIXED_HEAD, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Destination-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Destination-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Auth-Application-Id", RULE_REQUIRED, -1, 1 }
+ ,{ "User-Name", RULE_OPTIONAL, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Proxy-Info", RULE_OPTIONAL, -1,-1 }
+ ,{ "Route-Record", RULE_OPTIONAL, -1,-1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Abort-Session-Answer */
+ {
+ /*
+ The Abort-Session-Answer (ASA), indicated by the Command-Code set to
+ 274 and the message flags' 'R' bit clear, is sent in response to the
+ ASR. The Result-Code AVP MUST be present, and indicates the
+ disposition of the request.
+
+ If the session identified by Session-Id in the ASR was successfully
+ terminated, Result-Code is set to DIAMETER_SUCCESS. If the session
+ is not currently active, Result-Code is set to
+ DIAMETER_UNKNOWN_SESSION_ID. If the access device does not stop the
+ session for any other reason, Result-Code is set to
+ DIAMETER_UNABLE_TO_COMPLY.
+
+ Message Format
+
+ <ASA> ::= < Diameter Header: 274, PXY >
+ < Session-Id >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ User-Name ]
+ [ Origin-State-Id ]
+ [ Error-Message ]
+ [ Error-Reporting-Host ]
+ [ Failed-AVP ]
+ * [ Redirect-Host ]
+ [ Redirect-Host-Usage ]
+ [ Redirect-Max-Cache-Time ]
+ * [ Proxy-Info ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 274, /* Code */
+ #if CC_ABORT_SESSION != 274
+ #error "CC_ABORT_SESSION definition mismatch"
+ #endif
+ "Abort-Session-Answer", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE, /* Fixed flags */
+ CMD_FLAG_PROXIABLE /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Session-Id", RULE_FIXED_HEAD, -1, 1 }
+ ,{ "Result-Code", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "User-Name", RULE_OPTIONAL, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Message", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Reporting-Host", RULE_OPTIONAL, -1, 1 }
+ ,{ "Failed-AVP", RULE_OPTIONAL, -1, 1 }
+ ,{ "Redirect-Host", RULE_OPTIONAL, -1,-1 }
+ ,{ "Redirect-Host-Usage", RULE_OPTIONAL, -1, 1 }
+ ,{ "Redirect-Max-Cache-Time", RULE_OPTIONAL, -1, 1 }
+ ,{ "Proxy-Info", RULE_OPTIONAL, -1,-1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Accounting-Request */
+ {
+ /*
+ The Accounting-Request (ACR) command, indicated by the Command-Code
+ field set to 271 and the Command Flags' 'R' bit set, is sent by a
+ Diameter node, acting as a client, in order to exchange accounting
+ information with a peer.
+
+ One of Acct-Application-Id and Vendor-Specific-Application-Id AVPs
+ MUST be present. If the Vendor-Specific-Application-Id grouped AVP
+ is present, it MUST include an Acct-Application-Id AVP.
+
+ The AVP listed below SHOULD include service specific accounting AVPs,
+ as described in Section 9.3.
+
+
+ Message Format
+
+ <ACR> ::= < Diameter Header: 271, REQ, PXY >
+ < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Destination-Realm }
+ { Accounting-Record-Type }
+ { Accounting-Record-Number }
+ [ Acct-Application-Id ]
+ [ Vendor-Specific-Application-Id ]
+ [ User-Name ]
+ [ Destination-Host ]
+ [ Accounting-Sub-Session-Id ]
+ [ Acct-Session-Id ]
+ [ Acct-Multi-Session-Id ]
+ [ Acct-Interim-Interval ]
+ [ Accounting-Realtime-Required ]
+ [ Origin-State-Id ]
+ [ Event-Timestamp ]
+ * [ Proxy-Info ]
+ * [ Route-Record ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 271, /* Code */
+ #if CC_ACCOUNTING != 271
+ #error "CC_ACCOUNTING definition mismatch"
+ #endif
+ "Accounting-Request", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE | CMD_FLAG_ERROR, /* Fixed flags */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Session-Id", RULE_FIXED_HEAD, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Destination-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Accounting-Record-Type", RULE_REQUIRED, -1, 1 }
+ ,{ "Accounting-Record-Number", RULE_REQUIRED, -1, 1 }
+ ,{ "Acct-Application-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Vendor-Specific-Application-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "User-Name", RULE_OPTIONAL, -1, 1 }
+ ,{ "Destination-Host", RULE_OPTIONAL, -1, 1 }
+ ,{ "Accounting-Sub-Session-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Acct-Session-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Acct-Multi-Session-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Acct-Interim-Interval", RULE_OPTIONAL, -1, 1 }
+ ,{ "Accounting-Realtime-Required", RULE_OPTIONAL, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Event-Timestamp", RULE_OPTIONAL, -1, 1 }
+ ,{ "Proxy-Info", RULE_OPTIONAL, -1,-1 }
+ ,{ "Route-Record", RULE_OPTIONAL, -1,-1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+
+ /* Accounting-Answer */
+ {
+ /*
+ The Accounting-Answer (ACA) command, indicated by the Command-Code
+ field set to 271 and the Command Flags' 'R' bit cleared, is used to
+ acknowledge an Accounting-Request command. The Accounting-Answer
+ command contains the same Session-Id as the corresponding request.
+
+ Only the target Diameter Server, known as the home Diameter Server,
+ SHOULD respond with the Accounting-Answer command.
+
+ One of Acct-Application-Id and Vendor-Specific-Application-Id AVPs
+ MUST be present. If the Vendor-Specific-Application-Id grouped AVP
+ is present, it MUST contain an Acct-Application-Id AVP.
+
+ The AVP listed below SHOULD include service specific accounting AVPs,
+ as described in Section 9.3.
+
+
+ Message Format
+
+ <ACA> ::= < Diameter Header: 271, PXY >
+ < Session-Id >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ { Accounting-Record-Type }
+ { Accounting-Record-Number }
+ [ Acct-Application-Id ]
+ [ Vendor-Specific-Application-Id ]
+ [ User-Name ]
+ [ Accounting-Sub-Session-Id ]
+ [ Acct-Session-Id ]
+ [ Acct-Multi-Session-Id ]
+ [ Error-Message ]
+ [ Error-Reporting-Host ]
+ [ Failed-AVP ]
+ [ Acct-Interim-Interval ]
+ [ Accounting-Realtime-Required ]
+ [ Origin-State-Id ]
+ [ Event-Timestamp ]
+ * [ Proxy-Info ]
+ * [ AVP ]
+ */
+ struct dict_object * cmd;
+ struct dict_cmd_data data = {
+ 271, /* Code */
+ #if CC_ACCOUNTING != 271
+ #error "CC_ACCOUNTING definition mismatch"
+ #endif
+ "Accounting-Answer", /* Name */
+ CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE, /* Fixed flags */
+ CMD_FLAG_PROXIABLE /* Fixed flag values */
+ };
+ struct local_rules_definition rules[] =
+ { { "Session-Id", RULE_FIXED_HEAD, -1, 1 }
+ ,{ "Result-Code", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Host", RULE_REQUIRED, -1, 1 }
+ ,{ "Origin-Realm", RULE_REQUIRED, -1, 1 }
+ ,{ "Accounting-Record-Type", RULE_REQUIRED, -1, 1 }
+ ,{ "Accounting-Record-Number", RULE_REQUIRED, -1, 1 }
+ ,{ "Acct-Application-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Vendor-Specific-Application-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "User-Name", RULE_OPTIONAL, -1, 1 }
+ ,{ "Accounting-Sub-Session-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Acct-Session-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Acct-Multi-Session-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Message", RULE_OPTIONAL, -1, 1 }
+ ,{ "Error-Reporting-Host", RULE_OPTIONAL, -1, 1 }
+ ,{ "Failed-AVP", RULE_OPTIONAL, -1, 1 }
+ ,{ "Acct-Interim-Interval", RULE_OPTIONAL, -1, 1 }
+ ,{ "Accounting-Realtime-Required", RULE_OPTIONAL, -1, 1 }
+ ,{ "Origin-State-Id", RULE_OPTIONAL, -1, 1 }
+ ,{ "Event-Timestamp", RULE_OPTIONAL, -1, 1 }
+ ,{ "Proxy-Info", RULE_OPTIONAL, -1,-1 }
+ };
+
+ CHECK_dict_new( DICT_COMMAND, &data , NULL, &cmd);
+ PARSE_loc_rules( rules, cmd );
+ }
+ }
+
+ return 0;
+}
diff --git a/libfdcore/endpoints.c b/libfdcore/endpoints.c
new file mode 100644
index 0000000..e80c2a3
--- /dev/null
+++ b/libfdcore/endpoints.c
@@ -0,0 +1,271 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+
+/* Add an endpoint information in a list */
+int fd_ep_add_merge( struct fd_list * list, sSA * sa, socklen_t sl, uint32_t flags )
+{
+ struct fd_endpoint * ep;
+ struct fd_list * li;
+ union {
+ sSA * sa;
+ sSA4 *sin;
+ sSA6 *sin6;
+ } ptr;
+ in_port_t * port;
+ int cmp = -1;
+
+ TRACE_ENTRY("%p %p %u %x", list, sa, sl, flags);
+ CHECK_PARAMS( list && sa && (sl <= sizeof(sSS)) );
+
+ if (list->next == NULL) {
+ /* the list is not initialized yet, do it */
+ fd_list_init(list, NULL);
+ }
+
+ ptr.sa = sa;
+
+ /* Filter out a bunch of invalid addresses */
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (! (flags & EP_ACCEPTALL)) {
+ if (IN_IS_ADDR_UNSPECIFIED(&ptr.sin->sin_addr)
+ || IN_IS_ADDR_LOOPBACK(&ptr.sin->sin_addr)
+ /* the next one filters both EXPERIMENTAL, BADCLASS and MULTICAST. */
+ || ((ntohl(ptr.sin->sin_addr.s_addr) & 0xe0000000) == 0xe0000000)
+ || (ptr.sin->sin_addr.s_addr == INADDR_BROADCAST)) {
+ LOG_A(" DEBUG:fd_ep_add_merge Address was ignored, not added.");
+ return 0;
+ }
+ }
+ port = &ptr.sin->sin_port;
+ break;
+
+ case AF_INET6:
+ if (! (flags & EP_ACCEPTALL)) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&ptr.sin6->sin6_addr)
+ || IN6_IS_ADDR_LOOPBACK(&ptr.sin6->sin6_addr)
+ || IN6_IS_ADDR_MULTICAST(&ptr.sin6->sin6_addr)
+ || IN6_IS_ADDR_LINKLOCAL(&ptr.sin6->sin6_addr)
+ || IN6_IS_ADDR_SITELOCAL(&ptr.sin6->sin6_addr)) {
+ LOG_A(" DEBUG:fd_ep_add_merge Address was ignored, not added.");
+ return 0;
+ }
+ }
+ port = &ptr.sin6->sin6_port;
+ break;
+
+ default:
+ LOG_A(" DEBUG:fd_ep_add_merge Address family was unknown, not added.");
+ return 0;
+ }
+
+ /* remove the ACCEPTALL flag */
+ flags &= ~EP_ACCEPTALL;
+
+ /* Search place in the list */
+ for (li = list->next; li != list; li = li->next) {
+ ep = (struct fd_endpoint *)li;
+ in_port_t * ep_port;
+
+ /* First, compare the address family */
+ if (ep->sa.sa_family < sa->sa_family)
+ continue;
+ if (ep->sa.sa_family > sa->sa_family)
+ break;
+
+ /* Then compare the address field */
+ switch (sa->sa_family) {
+ case AF_INET:
+ cmp = memcmp(&ep->sin.sin_addr, &ptr.sin->sin_addr, sizeof(struct in_addr));
+ ep_port = &ep->sin.sin_port;
+ break;
+ case AF_INET6:
+ cmp = memcmp(&ep->sin6.sin6_addr, &ptr.sin6->sin6_addr, sizeof(struct in6_addr));
+ ep_port = &ep->sin6.sin6_port;
+ break;
+ default:
+ ASSERT( 0 ); /* we got a different value previously in this same function */
+ }
+ if (cmp < 0)
+ continue;
+ if (cmp > 0)
+ break;
+
+ /* Finally compare the port, only if not 0 */
+ if (*port == 0)
+ break;
+ if (*ep_port == 0) {
+ /* save the port information in the list, and break */
+ *ep_port = *port;
+ break;
+ }
+ if (*ep_port < *port) {
+ cmp = -1;
+ continue;
+ }
+ if (*ep_port > *port)
+ cmp = 1;
+ break;
+ }
+
+ if (cmp) {
+ /* new item to be added */
+ CHECK_MALLOC( ep = malloc(sizeof(struct fd_endpoint)) );
+ memset(ep, 0, sizeof(struct fd_endpoint));
+ fd_list_init(&ep->chain, NULL);
+ memcpy(&ep->ss, sa, sl);
+
+ /* Insert in the list */
+ fd_list_insert_before(li, &ep->chain);
+ }
+
+ /* Merge the flags */
+ ep->flags |= flags;
+
+ return 0;
+}
+
+/* Delete endpoints that do not have a matching flag from a list (0: delete all endpoints) */
+int fd_ep_filter( struct fd_list * list, uint32_t flags )
+{
+ struct fd_list * li;
+
+ TRACE_ENTRY("%p %x", list, flags);
+ CHECK_PARAMS(list);
+
+ for (li = list->next; li != list; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *)li;
+
+ if (! (ep->flags & flags)) {
+ li = li->prev;
+ fd_list_unlink(&ep->chain);
+ free(ep);
+ }
+ }
+
+ return 0;
+}
+
+/* Keep only endpoints of the same family as af */
+int fd_ep_filter_family( struct fd_list * list, int af )
+{
+ struct fd_list * li;
+
+ TRACE_ENTRY("%p %d", list, af);
+ CHECK_PARAMS(list);
+
+ for (li = list->next; li != list; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *)li;
+
+ if (ep->sa.sa_family != af) {
+ li = li->prev;
+ fd_list_unlink(&ep->chain);
+ free(ep);
+ }
+ }
+
+ return 0;
+}
+
+/* Reset the given flag(s) from all items in the list */
+int fd_ep_clearflags( struct fd_list * list, uint32_t flags )
+{
+ struct fd_list * li;
+
+ TRACE_ENTRY("%p %x", list, flags);
+ CHECK_PARAMS(list);
+
+ for (li = list->next; li != list; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *)li;
+ ep->flags &= ~flags;
+ if (ep->flags == 0) {
+ li = li->prev;
+ fd_list_unlink(&ep->chain);
+ free(ep);
+ }
+ }
+
+ return 0;
+}
+
+DECLARE_FD_DUMP_PROTOTYPE(fd_ep_dump_one, int preamble, struct fd_endpoint * ep )
+{
+ FD_DUMP_HANDLE_OFFSET();
+
+ if (preamble) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{ep}(@%p): ", ep), return NULL);
+ }
+
+ if (!ep) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "INVALID/NULL"), return NULL);
+ return *buf;
+ }
+
+ CHECK_MALLOC_DO( fd_sa_dump( FD_DUMP_STD_PARAMS, &ep->sa, NI_NUMERICHOST | NI_NUMERICSERV ), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{%s%s%s%s%s}",
+ (ep->flags & EP_FL_CONF) ? "C" : "-",
+ (ep->flags & EP_FL_DISC) ? "D" : "-",
+ (ep->flags & EP_FL_ADV) ? "A" : "-",
+ (ep->flags & EP_FL_LL) ? "L" : "-",
+ (ep->flags & EP_FL_PRIMARY) ? "P" : "-"), return NULL);
+ return *buf;
+}
+
+DECLARE_FD_DUMP_PROTOTYPE(fd_ep_dump, int preamble, int indent, struct fd_list * eps )
+{
+ struct fd_list * li;
+
+ FD_DUMP_HANDLE_OFFSET();
+
+ if (preamble) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "%*s{eps}(@%p):", indent, "", eps), return NULL);
+ }
+ if (eps) {
+ for (li = eps->next; li != eps; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *)li;
+ if (preamble) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "\n%*s", indent+1, ""), return NULL);
+ } else if (li->prev != eps) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "\t"), return NULL);
+ }
+ CHECK_MALLOC_DO( fd_ep_dump_one( FD_DUMP_STD_PARAMS, preamble, ep ), return NULL);
+ }
+ }
+ return *buf;
+}
+
diff --git a/libfdcore/events.c b/libfdcore/events.c
new file mode 100644
index 0000000..bea5885
--- /dev/null
+++ b/libfdcore/events.c
@@ -0,0 +1,231 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/* Events are a subset of fifo queues, with a known type */
+
+int fd_event_send(struct fifo *queue, int code, size_t datasz, void * data)
+{
+ struct fd_event * ev;
+ CHECK_MALLOC( ev = malloc(sizeof(struct fd_event)) );
+ ev->code = code;
+ ev->size = datasz;
+ ev->data = data;
+ CHECK_FCT( fd_fifo_post(queue, &ev) );
+ return 0;
+}
+
+int fd_event_get(struct fifo *queue, int *code, size_t *datasz, void ** data)
+{
+ struct fd_event * ev;
+ CHECK_FCT( fd_fifo_get(queue, &ev) );
+ if (code)
+ *code = ev->code;
+ if (datasz)
+ *datasz = ev->size;
+ if (data)
+ *data = ev->data;
+ free(ev);
+ return 0;
+}
+
+int fd_event_timedget(struct fifo *queue, struct timespec * timeout, int timeoutcode, int *code, size_t *datasz, void ** data)
+{
+ struct fd_event * ev;
+ int ret = 0;
+ ret = fd_fifo_timedget(queue, &ev, timeout);
+ if (ret == ETIMEDOUT) {
+ if (code)
+ *code = timeoutcode;
+ if (datasz)
+ *datasz = 0;
+ if (data)
+ *data = NULL;
+ } else {
+ CHECK_FCT( ret );
+ if (code)
+ *code = ev->code;
+ if (datasz)
+ *datasz = ev->size;
+ if (data)
+ *data = ev->data;
+ free(ev);
+ }
+ return 0;
+}
+
+void fd_event_destroy(struct fifo **queue, void (*free_cb)(void * data))
+{
+ struct fd_event * ev;
+ /* Purge all events, and free the associated data if any */
+ while (fd_fifo_tryget( *queue, &ev ) == 0) {
+ (*free_cb)(ev->data);
+ free(ev);
+ }
+ CHECK_FCT_DO( fd_fifo_del(queue), /* continue */ );
+ return ;
+}
+
+const char * fd_ev_str(int event)
+{
+ switch (event) {
+ #define case_str( _val )\
+ case _val : return #_val
+ case_str(FDEV_TERMINATE_INT);
+ case_str(FDEV_TRIGGER);
+
+ default:
+ TRACE_DEBUG(FULL, "Unknown event : %d", event);
+ return "Unknown event";
+ }
+}
+
+/**********************************************************************/
+/* Trigged events */
+/* This allows extensions to register for / send triggers to other extensions */
+/* It is used for example for users interactions (through signals or ...) */
+
+/* Because the number of triggers is not expected to grow, we use a simple ordered chained list */
+static pthread_rwlock_t trig_rwl = PTHREAD_RWLOCK_INITIALIZER;
+static struct fd_list trig_list = FD_LIST_INITIALIZER(trig_list); /* The list of triggers ordered by trigger value */
+struct trig_item {
+ struct fd_list chain;
+ int trig_value;
+ const char * trig_module;
+ void (*cb)(void);
+};
+
+/* Add a new entry in the trigger list */
+int fd_event_trig_regcb(int trigger_val, const char * module, void (*cb)(void))
+{
+ struct trig_item * ti;
+ struct fd_list * li;
+
+ TRACE_ENTRY("%d %p %p", trigger_val, module, cb);
+ CHECK_PARAMS( trigger_val && cb );
+
+ /* Create a new trig_item */
+ CHECK_MALLOC( ti = malloc(sizeof(struct trig_item)) );
+ memset(ti, 0, sizeof(struct trig_item));
+ fd_list_init(&ti->chain, ti);
+ ti->trig_value = trigger_val;
+ ti->trig_module = module;
+ ti->cb = cb;
+
+ /* Now insert in the list */
+ CHECK_POSIX( pthread_rwlock_wrlock(&trig_rwl) );
+
+ for (li = trig_list.next; li != &trig_list; li = li->next) {
+ struct trig_item *t = li->o;
+ if (t->trig_value >= trigger_val)
+ break;
+ }
+
+ fd_list_insert_before(li, &ti->chain);
+
+ CHECK_POSIX( pthread_rwlock_unlock(&trig_rwl) );
+
+ return 0;
+}
+
+DECLARE_FD_DUMP_PROTOTYPE(fd_event_trig_dump)
+{
+ struct fd_list * li;
+ FD_DUMP_HANDLE_OFFSET();
+
+ CHECK_POSIX_DO( pthread_rwlock_rdlock(&trig_rwl), );
+
+ for (li = trig_list.next; li != &trig_list; li = li->next) {
+ struct trig_item *t = li->o;
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{signal:%d}'%s'->%p ", t->trig_value, t->trig_module, t->cb), break);
+ }
+
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&trig_rwl), );
+ return *buf;
+}
+
+static void *call_cb_detached(void * arg)
+{
+ void (*cb)(void) = arg;
+ fd_log_threadname("Trig'd callback thread");
+ TRACE_ENTRY("%p", arg);
+ (*cb)();
+ TRACE_DEBUG(ANNOYING, "Callback %p completed", cb);
+ return NULL;
+}
+
+int fd_event_trig_call_cb(int trigger_val)
+{
+ struct fd_list * li;
+ pthread_attr_t detached;
+ pthread_t th;
+
+ CHECK_POSIX( pthread_attr_init(&detached) );
+ CHECK_POSIX( pthread_attr_setdetachstate(&detached, PTHREAD_CREATE_DETACHED) );
+
+ CHECK_POSIX( pthread_rwlock_rdlock(&trig_rwl) );
+
+ for (li = trig_list.next; li != &trig_list; li = li->next) {
+ struct trig_item *t = li->o;
+ if (t->trig_value == trigger_val) {
+ TRACE_DEBUG(FULL, "Trigger %d: Calling %p in %s", t->trig_value, t->cb, t->trig_module);
+ CHECK_POSIX_DO( pthread_create( &th, &detached, call_cb_detached, t->cb ), break );
+ }
+ if (t->trig_value > trigger_val)
+ break;
+ }
+
+ CHECK_POSIX( pthread_rwlock_unlock(&trig_rwl) );
+
+ return 0;
+}
+
+int fd_event_trig_fini(void) {
+
+ TRACE_ENTRY("");
+
+ CHECK_POSIX( pthread_rwlock_wrlock(&trig_rwl) );
+
+ while (!FD_IS_LIST_EMPTY(&trig_list)) {
+ struct fd_list * li = trig_list.next;
+ fd_list_unlink(li);
+ free(li);
+ }
+
+ CHECK_POSIX( pthread_rwlock_unlock(&trig_rwl) );
+
+ return 0;
+}
diff --git a/libfdcore/extensions.c b/libfdcore/extensions.c
new file mode 100644
index 0000000..0d4fcca
--- /dev/null
+++ b/libfdcore/extensions.c
@@ -0,0 +1,299 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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 <dlfcn.h> /* We may use libtool's <ltdl.h> later for better portability.... */
+#include <libgen.h> /* for "basename" */
+
+/* plugins management */
+
+/* List of extensions to load, from the configuration parsing */
+struct fd_ext_info {
+ struct fd_list chain; /* link in the list */
+ char *filename; /* extension filename. must be a dynamic library with fd_ext_init symbol. */
+ char *conffile; /* optional configuration file name for the extension */
+ void *handler; /* object returned by dlopen() */
+ const char **depends; /* names of the other extensions this one depends on (if provided) */
+ char *ext_name; /* points to the extension name, either inside depends, or basename(filename) */
+ int free_ext_name; /* must be freed if it was malloc'd */
+ void (*fini)(void); /* optional address of the fd_ext_fini callback */
+ char *proto_ver;
+ double gen_date;
+};
+
+/* list of extensions */
+static struct fd_list ext_list = FD_LIST_INITIALIZER(ext_list);
+
+/* Add new extension */
+int fd_ext_add( char * filename, char * conffile )
+{
+ struct fd_ext_info * new;
+
+ TRACE_ENTRY("%p %p", filename, conffile);
+
+ /* Check the filename is valid */
+ CHECK_PARAMS( filename );
+
+ /* Create a new object in the list */
+ CHECK_MALLOC( new = malloc( sizeof(struct fd_ext_info) ) );
+ memset(new, 0, sizeof(struct fd_ext_info));
+ fd_list_init(&new->chain, NULL);
+ new->filename = filename;
+ new->conffile = conffile;
+ fd_list_insert_before( &ext_list, &new->chain );
+ TRACE_DEBUG (FULL, "Extension %s added to the list.", filename);
+ return 0;
+}
+
+/* Dump the list */
+DECLARE_FD_DUMP_PROTOTYPE(fd_ext_dump)
+{
+ struct fd_list * li;
+ FD_DUMP_HANDLE_OFFSET();
+
+ if (FD_IS_LIST_EMPTY(&ext_list)) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "-none-"), return NULL);
+ } else {
+ for (li = ext_list.next; li != &ext_list; li = li->next)
+ {
+ struct fd_ext_info * ext = (struct fd_ext_info *)li;
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "'%s'[%s], %sloaded%s",
+ ext->filename,
+ ext->conffile?:"(no config file)",
+ ext->handler ? "" : "not ", (li->next == &ext_list) ? "":"\n"), return NULL);
+ }
+ }
+ return *buf;
+}
+
+/* Load the dependencies */
+static int load_dependencies(struct fd_ext_info *ext)
+{
+ TRACE_ENTRY( "%p", ext);
+ /* Attempt to resolve the dependency array */
+ ext->depends = dlsym( ext->handler, "fd_ext_depends");
+ if (!ext->depends) {
+ /* Duplicate the filename */
+ char * tmp = strdup(ext->filename);
+ ext->ext_name = strdup(basename(tmp));
+ free(tmp);
+ ext->free_ext_name = 1;
+ TRACE_DEBUG(FULL, "Old extension's [%s] API: missing dependencies (ignored)", ext->ext_name);
+ return 0;
+ }
+
+ ext->ext_name = (char *)ext->depends[0];
+ return 0;
+
+}
+
+/* Check the dependencies. The object must have been dlopened already. */
+static int check_dependencies(struct fd_ext_info * ext)
+{
+ int i = 1;
+ TRACE_DEBUG(FULL, "Checking dependencies for '%s'...", ext->ext_name);
+
+ while (ext->depends[i]) {
+ struct fd_list * li;
+ for (li = ext_list.next; li != &ext_list; li = li->next)
+ {
+ struct fd_ext_info * e = (struct fd_ext_info *)li;
+ if (!strcasecmp(e->ext_name, ext->depends[i])) {
+ /* the dependency was already loaded */
+ break;
+ }
+ }
+
+ if (li == &ext_list) {
+ /* the dependency was not found */
+ LOG_F("Error: extension [%s] depends on [%s] which was not loaded first. Please fix your configuration file.",
+ ext->ext_name, ext->depends[i]);
+ return ESRCH;
+ }
+
+ i++;
+ }
+
+ /* All dependencies resolved successfully */
+ return 0;
+}
+
+/* Load all extensions in the list */
+int fd_ext_load()
+{
+ int ret;
+ int (*fd_ext_init)(int, int, char *) = NULL;
+ int (*fd_ext_init2)(int, int, char *) = NULL;
+ struct fd_list * li;
+
+ TRACE_ENTRY();
+
+ /* Loop on all extensions */
+ for (li = ext_list.next; li != &ext_list; li = li->next)
+ {
+ struct fd_ext_info * ext = (struct fd_ext_info *)li;
+ LOG_D( "Loading : %s", ext->filename);
+
+ /* Load the extension */
+#ifndef DEBUG
+ ext->handler = dlopen(ext->filename, RTLD_LAZY | RTLD_GLOBAL);
+#else /* DEBUG */
+ /* We resolve symbols immediatly so it's easier to find problems in ABI */
+ ext->handler = dlopen(ext->filename, RTLD_NOW | RTLD_GLOBAL);
+#endif /* DEBUG */
+ if (ext->handler == NULL) {
+ /* An error occured */
+ LOG_F("Loading of extension %s failed: %s", ext->filename, dlerror());
+ ext->handler = dlopen(ext->filename, RTLD_LAZY | RTLD_GLOBAL);
+ if (ext->handler) {
+ if (!check_dependencies(ext)) {
+ LOG_F("In addition, not all declared dependencies are satisfied (Internal Error!)");
+ }
+ }
+ return EINVAL;
+ }
+
+ /* Resolve the entry point of the extension */
+ fd_ext_init = ( int (*) (int, int, char *) )dlsym( ext->handler, "fd_ext_init" );
+
+ if (fd_ext_init == NULL) {
+ /* An error occured */
+ TRACE_ERROR("Unable to resolve symbol 'fd_ext_init' for extension %s: %s", ext->filename, dlerror());
+ return EINVAL;
+ }
+
+
+ /* Resolve the exit point of the extension, which is optional for extensions */
+ ext->fini = ( void (*) (void) )dlsym( ext->handler, "fd_ext_fini" );
+
+ if (ext->fini == NULL) {
+ /* Not provided */
+ TRACE_DEBUG (FULL, "Extension [%s] has no fd_ext_fini function.", ext->filename);
+ } else {
+ /* Provided */
+ TRACE_DEBUG (FULL, "Extension [%s] fd_ext_fini has been resolved successfully.", ext->filename);
+ }
+
+ /* Now call the entry point to initialize the extension */
+ ret = (*fd_ext_init)( FD_PROJECT_VERSION_MAJOR, FD_PROJECT_VERSION_MINOR, ext->conffile );
+ if (ret != 0) {
+ /* The extension was unable to load cleanly */
+ TRACE_ERROR("Extension %s returned an error during initialization: %s", ext->filename, strerror(ret));
+ return ret;
+ }
+
+ /* Proceed to the next extension */
+ }
+
+ for( li = ext_list.next; li != &ext_list; li = li->next )
+ {
+ struct fd_ext_info * ext = (struct fd_ext_info *)li;
+ CHECK_FCT( load_dependencies(ext));
+ }
+
+
+ for( li = ext_list.next; li != &ext_list; li = li->next )
+ {
+ struct fd_ext_info * ext = (struct fd_ext_info *)li;
+
+ /* Check if declared dependencies are satisfied. */
+ CHECK_FCT( check_dependencies(ext) );
+
+ /* Loading methods init2 & parserules */
+ fd_ext_init2 = ( int (*) (int, int, char *) )dlsym( ext->handler, "fd_ext_init2" );
+
+ if (fd_ext_init2 == NULL) {
+ /* Old extensions do not define fd_ext_init2 */
+ LOG_N("Unable to resolve symbol 'fd_ext_init2' for extension %s: ", ext->filename);
+ }
+ else {
+ ret = (*fd_ext_init2)( FD_PROJECT_VERSION_MAJOR, FD_PROJECT_VERSION_MINOR, ext->conffile );
+ if (ret != 0) {
+ /* The extension was unable to load cleanly */
+ TRACE_ERROR("Extension %s returned an error during parse local rules: %s", ext->filename, strerror(ret));
+ return ret;
+ }
+ }
+ }
+
+ LOG_N("All extensions loaded.");
+
+ /* We have finished. */
+ return 0;
+}
+
+/* Now unload the extensions and free the memory */
+int fd_ext_term( void )
+{
+ TRACE_ENTRY();
+
+ /* Loop on all extensions, in FIFO order */
+ while (!FD_IS_LIST_EMPTY(&ext_list))
+ {
+ struct fd_list * li = ext_list.next;
+ struct fd_ext_info * ext = (struct fd_ext_info *)li;
+
+ /* Unlink this element from the list */
+ fd_list_unlink(li);
+
+ /* Call the exit point of the extension, if it was resolved */
+ if (ext->fini != NULL) {
+ TRACE_DEBUG (FULL, "Calling [%s]->fd_ext_fini function.", ext->ext_name ?: ext->filename);
+ (*ext->fini)();
+ }
+
+#ifndef SKIP_DLCLOSE
+ /* Now unload the extension */
+ if (ext->handler) {
+ TRACE_DEBUG (FULL, "Unloading %s", ext->ext_name ?: ext->filename);
+ if ( dlclose(ext->handler) != 0 ) {
+ TRACE_DEBUG (INFO, "Unloading [%s] failed : %s", ext->ext_name ?: ext->filename, dlerror());
+ }
+ }
+#endif /* SKIP_DLCLOSE */
+
+ /* Free the object and continue */
+ if (ext->free_ext_name)
+ free(ext->ext_name);
+ free(ext->filename);
+ free(ext->conffile);
+ free(ext);
+ }
+
+ /* We always return 0 since we would not handle an error anyway... */
+ return 0;
+}
+
diff --git a/libfdcore/fdcore-internal.h b/libfdcore/fdcore-internal.h
new file mode 100644
index 0000000..7fac852
--- /dev/null
+++ b/libfdcore/fdcore-internal.h
@@ -0,0 +1,375 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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. *
+*********************************************************************************************************/
+
+/* This file contains the definitions for internal use in the freeDiameter core library */
+
+#ifndef _FDCORE_INTERNAL_H
+#define _FDCORE_INTERNAL_H
+
+#include <freeDiameter/freeDiameter-host.h>
+#include <freeDiameter/libfdcore.h>
+
+#ifdef DISABLE_SCTP
+#undef IPPROTO_SCTP
+#define IPPROTO_SCTP (2 = 4) /* some compilation error to spot the references */
+#endif /* DISABLE_SCTP */
+
+#ifndef HAVE_AI_ADDRCONFIG
+#define AI_ADDRCONFIG 0 /* ignore this flag at the moment */
+#endif /* HAVE_AI_ADDRCONFIG */
+
+/* Timeout for establishing a connection */
+#ifndef CNX_TIMEOUT
+#define CNX_TIMEOUT 10 /* in seconds */
+#endif /* CNX_TIMEOUT */
+
+/* Timeout for receiving a CER after incoming connection is established */
+#ifndef INCNX_TIMEOUT
+#define INCNX_TIMEOUT 20 /* in seconds */
+#endif /* INCNX_TIMEOUT */
+
+/* Timeout for receiving a CEA after CER is sent */
+#ifndef CEA_TIMEOUT
+#define CEA_TIMEOUT 10 /* in seconds */
+#endif /* CEA_TIMEOUT */
+
+/* The timeout value to wait for answer to a DPR */
+#ifndef DPR_TIMEOUT
+#define DPR_TIMEOUT 15 /* in seconds */
+#endif /* DPR_TIMEOUT */
+
+/* Delay where the connection is maintained opened to allow exchanging remaining pending answers after DPR/DPA */
+#ifndef GRACE_TIMEOUT
+#define GRACE_TIMEOUT 1 /* in seconds */
+#endif /* GRACE_TIMEOUT */
+
+/* The Vendor-Id to advertise in CER/CEA */
+#ifndef MY_VENDOR_ID
+#define MY_VENDOR_ID 0 /* Reserved value to tell it must be ignored */
+#endif /* MY_VENDOR_ID */
+
+
+
+/* Configuration */
+int fd_conf_init();
+int fd_conf_deinit();
+int fd_conf_parse();
+int fddparse(struct fd_config * conf); /* yacc generated */
+int fd_conf_stream_to_gnutls_datum(FILE * pemfile, gnutls_datum_t *out);
+
+
+/* Extensions */
+int fd_ext_add( char * filename, char * conffile );
+int fd_ext_load();
+int fd_ext_term(void);
+
+/* Messages */
+int fd_msg_init(void);
+extern struct dict_object * fd_dict_avp_OSI; /* Origin-State-Id */
+extern struct dict_object * fd_dict_cmd_CER; /* Capabilities-Exchange-Request */
+extern struct dict_object * fd_dict_cmd_DWR; /* Device-Watchdog-Request */
+extern struct dict_object * fd_dict_avp_DC; /* Disconnect-Cause */
+extern struct dict_object * fd_dict_cmd_DPR; /* Disconnect-Peer-Request */
+
+/* Global message queues */
+extern struct fifo * fd_g_incoming; /* all messages received from other peers, except local messages (CER, ...) */
+extern struct fifo * fd_g_outgoing; /* messages to be sent to other peers on the network following routing procedure */
+extern struct fifo * fd_g_local; /* messages to be handled to local extensions */
+/* Message queues */
+int fd_queues_init(void);
+int fd_queues_fini(struct fifo ** queue);
+
+/* Trigged events */
+int fd_event_trig_call_cb(int trigger_val);
+int fd_event_trig_fini(void);
+
+/* Create all the dictionary objects defined in the Diameter base RFC. */
+int fd_dict_base_protocol(struct dictionary * dict);
+
+/* Routing */
+int fd_rtdisp_init(void);
+int fd_rtdisp_cleanstop(void);
+int fd_rtdisp_fini(void);
+int fd_rtdisp_cleanup(void);
+
+/* Sentinel for the sent requests list */
+struct sr_list {
+ struct fd_list srs; /* requests ordered by hop-by-hop id */
+ struct fd_list exp; /* requests that have a timeout set, ordered by timeout */
+ long cnt; /* number of requests in the srs list */
+ long cnt_lost; /* number of requests that have not been answered in time.
+ It is decremented when an unexpected answer is received, so this may not be accurate. */
+ pthread_mutex_t mtx; /* mutex to protect these lists */
+ pthread_cond_t cnd; /* cond var used by the thread that handles timeouts */
+ pthread_t thr; /* the thread that handles timeouts (expirecb called in separate forked threads) */
+};
+
+/* Peers */
+struct fd_peer { /* The "real" definition of the peer structure */
+
+ /* The public data */
+ struct peer_hdr p_hdr;
+
+ /* Eye catcher, EYEC_PEER */
+ int p_eyec;
+ #define EYEC_PEER 0x373C9336
+
+ /* Origin of this peer object, for debug */
+ char *p_dbgorig;
+
+ /* State of the peer, and its lock */
+ enum peer_state p_state;
+ pthread_mutex_t p_state_mtx;
+
+ /* Chaining in peers sublists */
+ struct fd_list p_actives; /* list of peers in the STATE_OPEN state -- used by routing */
+ struct fd_list p_expiry; /* list of expiring peers, ordered by their timeout value */
+ struct timespec p_exp_timer; /* Timestamp where the peer will expire; updated each time activity is seen on the peer (except DW) */
+
+ /* Some flags influencing the peer state machine */
+ struct {
+ unsigned pf_responder : 1; /* The peer has been created to handle incoming connection */
+ unsigned pf_delete : 1; /* Destroy the peer when the connection is terminated */
+ unsigned pf_localterm : 1; /* If the latest DPR/DPA was initiated from this side */
+
+ unsigned pf_dw_pending : 1; /* A DWR message was sent and not answered yet */
+
+ unsigned pf_cnx_pb : 1; /* The peer was disconnected because of watchdogs; must exchange 3 watchdogs before putting back to normal */
+ unsigned pf_reopen_cnt : 2; /* remaining DW to be exchanged after re-established connection */
+
+ } p_flags;
+
+ /* The events queue, peer state machine thread, timer for states timeouts */
+ struct fifo *p_events; /* The mutex of this FIFO list protects also the state and timer information */
+ pthread_t p_psm;
+ struct timespec p_psm_timer;
+
+ /* Outgoing message queue, and thread managing sending the messages */
+ struct fifo *p_tosend;
+ pthread_t p_outthr;
+
+ /* The next hop-by-hop id value for the link, only read & modified by p_outthr */
+ uint32_t p_hbh;
+
+ /* Sent requests (for fallback), list of struct sentreq ordered by hbh */
+ struct sr_list p_sr;
+ struct fifo *p_tofailover;
+
+ /* Pending received requests not yet answered (count only) */
+ long p_reqin_count; /* We use p_state_mtx to protect this value */
+
+ /* Data for transitional states before the peer is in OPEN state */
+ struct {
+ struct cnxctx * p_receiver; /* Only used in case of election */
+ struct msg * p_cer; /* Only used in case of election */
+
+ pthread_t p_ini_thr; /* Initiator thread for establishing a connection */
+ struct fd_list p_connparams; /* The list of connection attempts, see p_cnx.c */
+ };
+
+ /* connection context: socket and related information */
+ struct cnxctx *p_cnxctx;
+
+ /* Callback for peer validation after the handshake */
+ int (*p_cb2)(struct peer_info *);
+
+ /* Callback on initial connection success / failure after the peer was added */
+ void (*p_cb)(struct peer_info *, void *);
+ void *p_cb_data;
+
+};
+#define CHECK_PEER( _p ) \
+ (((_p) != NULL) && (((struct fd_peer *)(_p))->p_eyec == EYEC_PEER))
+
+#define fd_peer_getstate(peer) fd_peer_get_state((struct peer_hdr *)(peer))
+
+
+/* Events codespace for struct fd_peer->p_events */
+enum {
+ /* request to terminate this peer : disconnect, requeue all messages */
+ FDEVP_TERMINATE = 1500
+
+ /* A connection object has received a message. (data contains the buffer + padding + struct fd_msg_pmdl) */
+ ,FDEVP_CNX_MSG_RECV
+
+ /* A connection object has encountered an error (disconnected). */
+ ,FDEVP_CNX_ERROR
+
+ /* Endpoints of a connection have been changed (multihomed SCTP). */
+ ,FDEVP_CNX_EP_CHANGE
+
+ /* The connection is being shutdown (SCTP notification). */
+ ,FDEVP_CNX_SHUTDOWN
+
+ /* A new connection (with a CER) has been received */
+ ,FDEVP_CNX_INCOMING
+
+ /* A new connection has been established to the remote peer (event data is the cnxctx object) */
+ ,FDEVP_CNX_ESTABLISHED
+
+ /* A connection attempt (initiator side) has failed */
+ ,FDEVP_CNX_FAILED
+
+ /* The PSM state is expired */
+ ,FDEVP_PSM_TIMEOUT
+
+};
+#define CHECK_PEVENT( _e ) \
+ (((int)(_e) >= FDEVP_TERMINATE) && ((int)(_e) <= FDEVP_PSM_TIMEOUT))
+/* The following macro is actually called in p_psm.c -- another solution would be to declare it static inline */
+#define DECLARE_PEV_STR() \
+const char * fd_pev_str(int event) \
+{ \
+ switch (event) { \
+ case_str(FDEVP_TERMINATE); \
+ case_str(FDEVP_CNX_MSG_RECV); \
+ case_str(FDEVP_CNX_ERROR); \
+ case_str(FDEVP_CNX_EP_CHANGE); \
+ case_str(FDEVP_CNX_INCOMING); \
+ case_str(FDEVP_CNX_ESTABLISHED); \
+ case_str(FDEVP_CNX_FAILED); \
+ case_str(FDEVP_PSM_TIMEOUT); \
+ } \
+ TRACE_DEBUG(FULL, "Unknown event : %d", event); \
+ return "Unknown event"; \
+}
+const char * fd_pev_str(int event);
+
+/* The data structure for FDEVP_CNX_INCOMING event */
+struct cnx_incoming {
+ struct msg * cer; /* the CER message received on this connection */
+ struct cnxctx * cnx; /* The connection context */
+ int validate; /* The peer is new, it must be validated (by an extension) or error CEA to be sent */
+};
+
+/* Functions */
+int fd_peer_fini();
+int fd_peer_alloc(struct fd_peer ** ptr);
+int fd_peer_free(struct fd_peer ** ptr);
+int fd_peer_handle_newCER( struct msg ** cer, struct cnxctx ** cnx );
+/* fd_peer_add declared in freeDiameter.h */
+int fd_peer_validate( struct fd_peer * peer );
+void fd_peer_failover_msg(struct fd_peer * peer);
+
+/* Peer expiry */
+int fd_p_expi_init(void);
+int fd_p_expi_fini(void);
+int fd_p_expi_update(struct fd_peer * peer );
+
+/* Peer state machine */
+int fd_psm_start();
+int fd_psm_begin(struct fd_peer * peer );
+int fd_psm_terminate(struct fd_peer * peer, char * reason );
+void fd_psm_abord(struct fd_peer * peer );
+void fd_psm_next_timeout(struct fd_peer * peer, int add_random, int delay);
+int fd_psm_change_state(struct fd_peer * peer, int new_state);
+void fd_psm_cleanup(struct fd_peer * peer, int terminate);
+
+/* Peer out */
+int fd_out_send(struct msg ** msg, struct cnxctx * cnx, struct fd_peer * peer, int update_reqin_cnt);
+int fd_out_start(struct fd_peer * peer);
+int fd_out_stop(struct fd_peer * peer);
+
+/* Initiating connections */
+int fd_p_cnx_init(struct fd_peer * peer);
+void fd_p_cnx_abort(struct fd_peer * peer, int cleanup_all);
+
+/* Peer sent requests cache */
+int fd_p_sr_store(struct sr_list * srlist, struct msg **req, uint32_t *hbhloc, uint32_t hbh_restore);
+int fd_p_sr_fetch(struct sr_list * srlist, uint32_t hbh, struct msg **req);
+int fd_p_sr_start(struct sr_list * srlist);
+int fd_p_sr_stop(struct sr_list * srlist);
+void fd_p_sr_failover(struct sr_list * srlist);
+
+/* Local Link messages (CER/CEA, DWR/DWA, DPR/DPA) */
+int fd_p_ce_msgrcv(struct msg ** msg, int req, struct fd_peer * peer);
+int fd_p_ce_handle_newCER(struct msg ** msg, struct fd_peer * peer, struct cnxctx ** cnx, int valid);
+int fd_p_ce_handle_newcnx(struct fd_peer * peer, struct cnxctx * initiator);
+int fd_p_ce_process_receiver(struct fd_peer * peer);
+void fd_p_ce_clear_cnx(struct fd_peer * peer, struct cnxctx ** cnx_kept);
+int fd_p_dw_handle(struct msg ** msg, int req, struct fd_peer * peer);
+int fd_p_dw_timeout(struct fd_peer * peer);
+int fd_p_dw_reopen(struct fd_peer * peer);
+int fd_p_dp_handle(struct msg ** msg, int req, struct fd_peer * peer);
+int fd_p_dp_initiate(struct fd_peer * peer, char * reason);
+int fd_p_dp_newdelay(struct fd_peer * peer);
+
+/* Active peers -- routing process should only ever take the read lock, the write lock is managed by PSMs */
+extern struct fd_list fd_g_activ_peers;
+extern pthread_rwlock_t fd_g_activ_peers_rw; /* protect the list */
+
+
+/* Server sockets */
+int fd_servers_start();
+int fd_servers_stop();
+
+/* Connection contexts -- there are also definitions in cnxctx.h for the relevant files */
+struct cnxctx * fd_cnx_serv_tcp(uint16_t port, int family, struct fd_endpoint * ep);
+struct cnxctx * fd_cnx_serv_sctp(uint16_t port, struct fd_list * ep_list);
+int fd_cnx_serv_listen(struct cnxctx * conn);
+struct cnxctx * fd_cnx_serv_accept(struct cnxctx * serv);
+struct cnxctx * fd_cnx_cli_connect_tcp(sSA * sa, socklen_t addrlen);
+struct cnxctx * fd_cnx_cli_connect_sctp(int no_ip6, uint16_t port, struct fd_list * list);
+int fd_cnx_start_clear(struct cnxctx * conn, int loop);
+void fd_cnx_sethostname(struct cnxctx * conn, DiamId_t hn);
+int fd_cnx_proto_info(struct cnxctx * conn, char * buf, size_t len);
+#define ALGO_HANDSHAKE_DEFAULT 0 /* TLS for TCP, DTLS for SCTP */
+#define ALGO_HANDSHAKE_3436 1 /* For TLS for SCTP also */
+int fd_cnx_handshake(struct cnxctx * conn, int mode, int algo, char * priority, void * alt_creds);
+char * fd_cnx_getid(struct cnxctx * conn);
+int fd_cnx_getproto(struct cnxctx * conn);
+int fd_cnx_getTLS(struct cnxctx * conn);
+int fd_cnx_is_unordered_delivery_supported(struct cnxctx * conn);
+int fd_cnx_unordered_delivery(struct cnxctx * conn, int is_allowed);
+int fd_cnx_getcred(struct cnxctx * conn, const gnutls_datum_t **cert_list, unsigned int *cert_list_size);
+int fd_cnx_get_local_eps(struct fd_list * list);
+int fd_cnx_getremoteeps(struct cnxctx * conn, struct fd_list * eps);
+char * fd_cnx_getremoteid(struct cnxctx * conn);
+int fd_cnx_receive(struct cnxctx * conn, struct timespec * timeout, unsigned char **buf, size_t * len);
+int fd_cnx_recv_setaltfifo(struct cnxctx * conn, struct fifo * alt_fifo); /* send FDEVP_CNX_MSG_RECV event to the fifo list */
+int fd_cnx_send(struct cnxctx * conn, unsigned char * buf, size_t len);
+void fd_cnx_destroy(struct cnxctx * conn);
+#ifdef GNUTLS_VERSION_300
+int fd_tls_verify_credentials_2(gnutls_session_t session);
+#endif /* GNUTLS_VERSION_300 */
+
+/* Internal calls of the hook mechanism */
+void fd_hook_call(enum fd_hook_type type, struct msg * msg, struct fd_peer * peer, void * other, struct fd_msg_pmdl * pmdl);
+void fd_hook_associate(struct msg * msg, struct fd_msg_pmdl * pmdl);
+int fd_hooks_init(void);
+size_t fd_msg_pmdl_sizewithoverhead(size_t datalen);
+struct fd_msg_pmdl * fd_msg_pmdl_get_inbuf(uint8_t * buf, size_t datalen);
+
+#endif /* _FDCORE_INTERNAL_H */
diff --git a/libfdcore/fdd.l b/libfdcore/fdd.l
new file mode 100644
index 0000000..8835def
--- /dev/null
+++ b/libfdcore/fdd.l
@@ -0,0 +1,285 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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. *
+*********************************************************************************************************/
+
+/* Lex configuration parser.
+ *
+ * This file defines the token for parsing the daemon's configuration file
+ * Note that each extension has a separate independant configuration file.
+ *
+ * Note : This module is NOT thread-safe. All processing must be done from one thread only.
+ */
+%{
+/* Include the daemon's header files */
+#include "fdcore-internal.h"
+/* Include yacc tokens definitions */
+#include "fdd.tab.h"
+
+/* Update the column information */
+#ifdef DEBUG_LEX
+#define YY_USER_ACTION { \
+ yylloc->first_column = yylloc->last_column + 1; \
+ yylloc->last_column = yylloc->first_column + yyleng - 1; \
+ fd_log_debug( \
+ "(%d:%d-%d:%d) matched rule %d, length=%d, txt='%s'", \
+ yylloc->first_line, yylloc->first_column, \
+ yylloc->last_line, yylloc->last_column, \
+ yy_act, yyleng, yytext); \
+}
+#else /* DEBUG_LEX */
+#define YY_USER_ACTION { \
+ yylloc->first_column = yylloc->last_column + 1; \
+ yylloc->last_column = yylloc->first_column + yyleng - 1; \
+}
+#endif
+
+/* %option noinput ? */
+#define YY_NO_INPUT
+
+/* Additional for files inclusion */
+#include <glob.h>
+#include <string.h>
+
+#define MAX_NESTED_CONF_FILES 5
+
+struct nested_conffiles_t {
+ YY_BUFFER_STATE parent_level_state;
+ glob_t filelist;
+ int current_file;
+} nested_conffiles[MAX_NESTED_CONF_FILES];
+
+int current_nested_level = 0;
+
+int globerrfct(const char *epath, int eerrno)
+{
+ TRACE_ERROR("Failed to scan %s: %s", epath, strerror(eerrno));
+ return 1;
+}
+
+%}
+
+%option bison-bridge bison-locations
+%option noyywrap
+%option nounput
+
+%x in_include
+
+/* Quoted string. Multilines do not match. */
+qstring \"[^\"\n]*\"
+
+%%
+<*>\n {
+ /* Update the line count */
+ yylloc->first_line++;
+ yylloc->last_line++;
+ yylloc->last_column=0;
+ }
+
+<*>([[:space:]]{-}[\n])+ ; /* Eat all spaces, not new lines */
+<*>#.*$ ; /* Eat all comments */
+
+
+include BEGIN(in_include);
+ /* Following an "include" keyword */
+<in_include>{
+{qstring} { /* Name of the file to include. This is directly sent to glob. */
+ int globerror=0;
+ char * buf = strdup(yytext+1);
+ if (buf[yyleng-2] != '"')
+ {
+ TRACE_ERROR("Unterminated string: %s", yytext);
+ return LEX_ERROR;
+ }
+ buf[yyleng-2] = '\0';
+
+ if (current_nested_level >= MAX_NESTED_CONF_FILES)
+ {
+ TRACE_ERROR("Too many recursion levels in configuration files includes");
+ return LEX_ERROR;
+ }
+
+ /* glob the include */
+ globerror = glob(buf, GLOB_ERR, globerrfct, &nested_conffiles[current_nested_level].filelist);
+
+ if (globerror == GLOB_NOSPACE)
+ {
+ TRACE_ERROR("Not enough memory to parse include directive.");
+ return LEX_ERROR;
+ }
+ if (globerror == GLOB_ABORTED)
+ {
+ TRACE_ERROR("An error was encountered in include directive.");
+ return LEX_ERROR;
+ }
+ if (globerror == GLOB_NOMATCH)
+ {
+ globfree(&nested_conffiles[current_nested_level].filelist);
+ goto nomatch;
+ }
+ if (globerror)
+ {
+ TRACE_ERROR("Unexpected error in glob (%d).", globerror);
+ return LEX_ERROR;
+ }
+
+ /* We have a list of files to include. */
+
+ /* save the current buffer for returning when this include has been parsed */
+ nested_conffiles[current_nested_level].parent_level_state = YY_CURRENT_BUFFER;
+
+ /* Start with the first match */
+ nested_conffiles[current_nested_level].current_file = 0;
+
+ yyin = fopen( nested_conffiles[current_nested_level].filelist.gl_pathv[0], "r" );
+
+ if ( ! yyin )
+ {
+ TRACE_ERROR("Error in %s: %s", nested_conffiles[current_nested_level].filelist.gl_pathv[0], strerror(errno));
+ return LEX_ERROR;
+ }
+
+ yy_switch_to_buffer(yy_create_buffer( yyin, YY_BUF_SIZE ));
+
+ /* In case of recursive includes */
+ current_nested_level++;
+
+nomatch:
+ BEGIN(INITIAL);
+ }
+}
+
+<<EOF>> {
+ if (current_nested_level == 0)
+ {
+ /* We are at the end of parsing */
+ yyterminate();
+ }
+
+ /* Otherwise we are doing an include statement */
+ --current_nested_level;
+ yy_delete_buffer(YY_CURRENT_BUFFER);
+
+ /* Go to next file, if any */
+ nested_conffiles[current_nested_level].current_file++;
+ if ( nested_conffiles[current_nested_level].filelist.gl_pathv[nested_conffiles[current_nested_level].current_file] == NULL )
+ {
+ /* We have finished with this list of includes */
+ globfree(&nested_conffiles[current_nested_level].filelist);
+ yy_switch_to_buffer(nested_conffiles[current_nested_level].parent_level_state);
+ }
+ else
+ {
+ /* Proceed to next included file */
+ yyin = fopen( nested_conffiles[current_nested_level].filelist.gl_pathv[nested_conffiles[current_nested_level].current_file], "r" );
+
+ if ( ! yyin )
+ {
+ TRACE_ERROR("Error in %s: %s", nested_conffiles[current_nested_level].filelist.gl_pathv[nested_conffiles[current_nested_level].current_file], strerror(errno));
+ return LEX_ERROR;
+ }
+
+ yy_switch_to_buffer(yy_create_buffer( yyin, YY_BUF_SIZE ));
+
+ /* In case of recursive includes */
+ current_nested_level++;
+ }
+
+}
+
+{qstring} {
+ /* First copy the string without the quotes for use in the yacc parser */
+ CHECK_MALLOC_DO( yylval->string = strdup(yytext+1), /* This allocates one useless tail char but... it's easier :D */
+ return LEX_ERROR );/* on error, trig an error in yacc parser */
+
+ yylval->string[yyleng-2] = '\0';
+
+ /* the yacc parser will check the string is valid */
+ return QSTRING;
+ }
+
+[[:digit:]]+ {
+ /* Convert this to an integer value */
+ int ret = sscanf(yytext, "%i", &yylval->integer);
+ if (ret != 1) {
+ /* No matching: an error occurred */
+ TRACE_ERROR("Unable to convert the value '%s' to a valid number: %s", yytext, strerror(errno));
+ return LEX_ERROR; /* trig an error in yacc parser */
+ /* Maybe we could REJECT instead of failing here? */
+ }
+ return INTEGER;
+ }
+
+ /* Full words tokens (keywords) */
+(?i:"Identity") { return IDENTITY; }
+(?i:"Realm") { return REALM; }
+(?i:"Port") { return PORT; }
+(?i:"SecPort") { return SECPORT; }
+ /* (?i:"SctpSec3436") { return SEC3436; } */
+(?i:"No_IPv6") { return NOIP6; }
+(?i:"No_IP") { return NOIP; }
+(?i:"No_TCP") { return NOTCP; }
+(?i:"No_SCTP") { return NOSCTP; }
+(?i:"Prefer_TCP") { return PREFERTCP; }
+(?i:"TLS_old_method") { return OLDTLS; }
+(?i:"SCTP_streams") { return SCTPSTREAMS; }
+(?i:"AppServThreads") { return APPSERVTHREADS;}
+(?i:"ListenOn") { return LISTENON; }
+(?i:"ThreadsPerServer") { return THRPERSRV; }
+(?i:"TcTimer") { return TCTIMER; }
+(?i:"TwTimer") { return TWTIMER; }
+(?i:"NoRelay") { return NORELAY; }
+(?i:"LoadExtension") { return LOADEXT; }
+(?i:"ConnectPeer") { return CONNPEER; }
+(?i:"ConnectTo") { return CONNTO; }
+(?i:"No_TLS") { return NOTLS; }
+(?i:"TLS_Cred") { return TLS_CRED; }
+(?i:"TLS_CA") { return TLS_CA; }
+(?i:"TLS_CRL") { return TLS_CRL; }
+(?i:"TLS_Prio") { return TLS_PRIO; }
+(?i:"TLS_DH_bits") { return TLS_DH_BITS; }
+(?i:"TLS_DH_file") { return TLS_DH_FILE; }
+
+
+ /* Valid single characters for yyparse */
+<*>[=,:;{}] { return yytext[0]; }
+
+ /* Unrecognized token */
+<*>[[:alnum:]]+ | /* This rule is only useful to print a complete token in error messages */
+ /* Unrecognized character */
+<*>. {
+ TRACE_ERROR("Unrecognized text on line %d col %d: '%s'.", yylloc->first_line, yylloc->first_column, yytext);
+ return LEX_ERROR;
+ }
+
+%%
diff --git a/libfdcore/fdd.y b/libfdcore/fdd.y
new file mode 100644
index 0000000..07707c9
--- /dev/null
+++ b/libfdcore/fdd.y
@@ -0,0 +1,667 @@
+/*********************************************************************************************************
+* 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. *
+*********************************************************************************************************/
+
+/* Yacc configuration parser.
+ *
+ * This file defines the grammar of the configuration file.
+ * Note that each extension has a separate independant configuration file.
+ *
+ * Note : This module is NOT thread-safe. All processing must be done from one thread only.
+ */
+
+/* For development only : */
+%debug
+%error-verbose
+
+%parse-param {struct fd_config * conf}
+
+/* Keep track of location */
+%locations
+%pure-parser
+
+%{
+#include "fdcore-internal.h"
+#include "fdd.tab.h" /* bug : bison does not define the YYLTYPE before including this bloc, so... */
+
+/* The Lex parser prototype */
+int fddlex(YYSTYPE *lvalp, YYLTYPE *llocp);
+
+/* Function to report error */
+void yyerror (YYLTYPE *ploc, struct fd_config * conf, char const *s)
+{
+ if (ploc->first_line != ploc->last_line) {
+ TRACE_ERROR("%s:%d.%d-%d.%d : %s", conf->cnf_file, ploc->first_line, ploc->first_column, ploc->last_line, ploc->last_column, s);
+ } else if (ploc->first_column != ploc->last_column) {
+ TRACE_ERROR("%s:%d.%d-%d : %s", conf->cnf_file, ploc->first_line, ploc->first_column, ploc->last_column, s);
+ } else {
+ TRACE_ERROR("%s:%d.%d : %s", conf->cnf_file, ploc->first_line, ploc->first_column, s);
+ }
+}
+
+int got_peer_noip = 0;
+int got_peer_noipv6 = 0;
+int got_peer_notcp = 0;
+int got_peer_nosctp = 0;
+
+struct peer_info fddpi;
+
+%}
+
+/* Values returned by lex for token */
+%union {
+ char *string; /* The string is allocated by strdup in lex.*/
+ int integer; /* Store integer values */
+}
+
+/* In case of error in the lexical analysis */
+%token LEX_ERROR
+
+%token <string> QSTRING
+%token <integer> INTEGER
+
+%type <string> extconf
+
+%token IDENTITY
+%token REALM
+%token PORT
+%token SECPORT
+%token SEC3436
+%token NOIP
+%token NOIP6
+%token NOTCP
+%token NOSCTP
+%token PREFERTCP
+%token OLDTLS
+%token NOTLS
+%token SCTPSTREAMS
+%token APPSERVTHREADS
+%token LISTENON
+%token THRPERSRV
+%token TCTIMER
+%token TWTIMER
+%token NORELAY
+%token LOADEXT
+%token CONNPEER
+%token CONNTO
+%token TLS_CRED
+%token TLS_CA
+%token TLS_CRL
+%token TLS_PRIO
+%token TLS_DH_BITS
+%token TLS_DH_FILE
+
+
+/* -------------------------------------- */
+%%
+
+ /* The grammar definition - Sections blocs. */
+conffile: /* Empty is OK -- for simplicity here, we reject in daemon later */
+ | conffile identity
+ | conffile realm
+ | conffile tctimer
+ | conffile twtimer
+ | conffile port
+ | conffile secport
+ | conffile sec3436
+ | conffile sctpstreams
+ | conffile listenon
+ | conffile thrpersrv
+ | conffile norelay
+ | conffile appservthreads
+ | conffile noip
+ | conffile noip6
+ | conffile notcp
+ | conffile nosctp
+ | conffile prefertcp
+ | conffile oldtls
+ | conffile loadext
+ | conffile connpeer
+ | conffile tls_cred
+ | conffile tls_ca
+ | conffile tls_crl
+ | conffile tls_prio
+ | conffile tls_dh
+ | conffile errors
+ {
+ yyerror(&yylloc, conf, "An error occurred while parsing the configuration file");
+ return EINVAL;
+ }
+ ;
+
+ /* Lexical or syntax error */
+errors: LEX_ERROR
+ | error
+ ;
+
+identity: IDENTITY '=' QSTRING ';'
+ {
+ conf->cnf_diamid = $3;
+ }
+ ;
+
+realm: REALM '=' QSTRING ';'
+ {
+ conf->cnf_diamrlm = $3;
+ }
+ ;
+
+tctimer: TCTIMER '=' INTEGER ';'
+ {
+ CHECK_PARAMS_DO( ($3 > 0),
+ { yyerror (&yylloc, conf, "Invalid value"); YYERROR; } );
+ conf->cnf_timer_tc = (unsigned int)$3;
+ }
+ ;
+
+twtimer: TWTIMER '=' INTEGER ';'
+ {
+ CHECK_PARAMS_DO( ($3 > 5),
+ { yyerror (&yylloc, conf, "Invalid value"); YYERROR; } );
+ conf->cnf_timer_tw = (unsigned int)$3;
+ }
+ ;
+
+port: PORT '=' INTEGER ';'
+ {
+ CHECK_PARAMS_DO( ($3 >= 0) && ($3 < 1<<16),
+ { yyerror (&yylloc, conf, "Invalid value"); YYERROR; } );
+ conf->cnf_port = (uint16_t)$3;
+ }
+ ;
+
+secport: SECPORT '=' INTEGER ';'
+ {
+ CHECK_PARAMS_DO( ($3 >= 0) && ($3 < 1<<16),
+ { yyerror (&yylloc, conf, "Invalid value"); YYERROR; } );
+ conf->cnf_port_tls = (uint16_t)$3;
+ }
+ ;
+
+sec3436: SEC3436 '=' INTEGER ';'
+ {
+ CHECK_PARAMS_DO( ($3 >= 0) && ($3 < 1<<16),
+ { yyerror (&yylloc, conf, "Invalid value"); YYERROR; } );
+ conf->cnf_port_3436 = (uint16_t)$3;
+ }
+ ;
+
+sctpstreams: SCTPSTREAMS '=' INTEGER ';'
+ {
+ CHECK_PARAMS_DO( ($3 > 0) && ($3 < 1<<16),
+ { yyerror (&yylloc, conf, "Invalid value"); YYERROR; } );
+ conf->cnf_sctp_str = (uint16_t)$3;
+ }
+ ;
+
+listenon: LISTENON '=' QSTRING ';'
+ {
+ struct addrinfo hints, *ai;
+ int ret;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ ret = getaddrinfo($3, NULL, &hints, &ai);
+ if (ret) { yyerror (&yylloc, conf, gai_strerror(ret)); YYERROR; }
+ CHECK_FCT_DO( fd_ep_add_merge( &conf->cnf_endpoints, ai->ai_addr, ai->ai_addrlen, EP_FL_CONF ), YYERROR );
+ freeaddrinfo(ai);
+ free($3);
+ }
+ ;
+
+thrpersrv: THRPERSRV '=' INTEGER ';'
+ {
+ CHECK_PARAMS_DO( ($3 > 0),
+ { yyerror (&yylloc, conf, "Invalid value"); YYERROR; } );
+ conf->cnf_thr_srv = $3;
+ }
+ ;
+
+norelay: NORELAY ';'
+ {
+ conf->cnf_flags.no_fwd = 1;
+ }
+ ;
+
+appservthreads: APPSERVTHREADS '=' INTEGER ';'
+ {
+ CHECK_PARAMS_DO( ($3 > 0) && ($3 < 256),
+ { yyerror (&yylloc, conf, "Invalid value"); YYERROR; } );
+ conf->cnf_dispthr = (uint16_t)$3;
+ }
+ ;
+
+noip: NOIP ';'
+ {
+ if (got_peer_noipv6) {
+ yyerror (&yylloc, conf, "No_IP conflicts with a ConnectPeer directive No_IPv6.");
+ YYERROR;
+ }
+ conf->cnf_flags.no_ip4 = 1;
+ }
+ ;
+
+noip6: NOIP6 ';'
+ {
+ if (got_peer_noip) {
+ yyerror (&yylloc, conf, "No_IP conflicts with a ConnectPeer directive No_IP.");
+ YYERROR;
+ }
+ conf->cnf_flags.no_ip6 = 1;
+ }
+ ;
+
+notcp: NOTCP ';'
+ {
+ #ifdef DISABLE_SCTP
+ yyerror (&yylloc, conf, "No_TCP cannot be specified for daemon compiled with DISABLE_SCTP option.");
+ YYERROR;
+ #endif
+ if (conf->cnf_flags.no_sctp)
+ {
+ yyerror (&yylloc, conf, "No_TCP conflicts with No_SCTP directive." );
+ YYERROR;
+ }
+ if (got_peer_nosctp) {
+ yyerror (&yylloc, conf, "No_TCP conflicts with a ConnectPeer directive No_SCTP.");
+ YYERROR;
+ }
+ conf->cnf_flags.no_tcp = 1;
+ }
+ ;
+
+nosctp: NOSCTP ';'
+ {
+ if (conf->cnf_flags.no_tcp)
+ {
+ yyerror (&yylloc, conf, "No_SCTP conflicts with No_TCP directive." );
+ YYERROR;
+ }
+ if (got_peer_notcp) {
+ yyerror (&yylloc, conf, "No_SCTP conflicts with a ConnectPeer directive No_TCP.");
+ YYERROR;
+ }
+ conf->cnf_flags.no_sctp = 1;
+ }
+ ;
+
+prefertcp: PREFERTCP ';'
+ {
+ conf->cnf_flags.pr_tcp = 1;
+ }
+ ;
+
+oldtls: OLDTLS ';'
+ {
+ conf->cnf_flags.tls_alg = 1;
+ }
+ ;
+
+loadext: LOADEXT '=' QSTRING extconf ';'
+ {
+ char * fname;
+ char * cfname;
+ FILE * fd;
+
+ /* Try and open the extension file */
+ fname = $3;
+ fd = fopen(fname, "r");
+ if ((fd == NULL) && (*fname != '/')) {
+ char * bkp = fname;
+ CHECK_MALLOC_DO( fname = malloc( strlen(bkp) + strlen(DEFAULT_EXTENSIONS_PATH) + 2 ),
+ { yyerror (&yylloc, conf, "Not enough memory"); YYERROR; } );
+ sprintf(fname, DEFAULT_EXTENSIONS_PATH "/%s", bkp);
+ fd = fopen(fname, "r");
+ if (fd == NULL) {
+ free(fname);
+ fname = bkp;
+ } else {
+ free(bkp);
+ }
+ }
+ if (fd != NULL) {
+ fclose(fd);
+ } /* otherwise, LD_LIBRARY_PATH will be tested by dl_open.
+ This should not give any security issue, otherwise we can add an "else fail" here. */
+
+ /* Try and open the configuration file (optional) */
+ cfname = $4;
+ if (cfname) {
+ fd = fopen(cfname, "r");
+ if ((fd == NULL) && (*cfname != '/')) {
+ char * test;
+ CHECK_MALLOC_DO( test = malloc( strlen(cfname) + strlen(DEFAULT_CONF_PATH) + 2 ),
+ { yyerror (&yylloc, conf, "Not enough memory"); YYERROR; } );
+ sprintf(test, DEFAULT_CONF_PATH "/%s", cfname);
+ fd = fopen(test, "r");
+ if (fd) {
+ free(cfname);
+ cfname=test;
+ } else {
+ /* This is not an error, we allow an extension to wait for something else than a real conf file. */
+ free(test);
+ }
+ }
+ if (fd)
+ fclose(fd);
+ }
+
+ CHECK_FCT_DO( fd_ext_add( fname, cfname ),
+ { yyerror (&yylloc, conf, "Error adding extension"); YYERROR; } );
+ }
+ ;
+
+extconf: /* empty */
+ {
+ $$ = NULL;
+ }
+ | ':' QSTRING
+ {
+ $$ = $2;
+ }
+ ;
+
+connpeer: {
+ memset(&fddpi, 0, sizeof(fddpi));
+ fddpi.config.pic_flags.persist = PI_PRST_ALWAYS;
+ fd_list_init( &fddpi.pi_endpoints, NULL );
+ }
+ CONNPEER '=' QSTRING peerinfo ';'
+ {
+ fddpi.pi_diamid = $4;
+ CHECK_FCT_DO( fd_peer_add ( &fddpi, conf->cnf_file, NULL, NULL ),
+ { yyerror (&yylloc, conf, "Error adding ConnectPeer information"); YYERROR; } );
+
+ /* Now destroy any content in the structure */
+ free(fddpi.pi_diamid);
+ free(fddpi.config.pic_realm);
+ free(fddpi.config.pic_priority);
+ while (!FD_IS_LIST_EMPTY(&fddpi.pi_endpoints)) {
+ struct fd_list * li = fddpi.pi_endpoints.next;
+ fd_list_unlink(li);
+ free(li);
+ }
+ }
+ ;
+
+peerinfo: /* empty */
+ | '{' peerparams '}'
+ ;
+
+peerparams: /* empty */
+ | peerparams NOIP ';'
+ {
+ if ((conf->cnf_flags.no_ip6) || (fddpi.config.pic_flags.pro3 == PI_P3_IP)) {
+ yyerror (&yylloc, conf, "No_IP conflicts with a No_IPv6 directive.");
+ YYERROR;
+ }
+ got_peer_noip++;
+ fddpi.config.pic_flags.pro3 = PI_P3_IPv6;
+ }
+ | peerparams NOIP6 ';'
+ {
+ if ((conf->cnf_flags.no_ip4) || (fddpi.config.pic_flags.pro3 == PI_P3_IPv6)) {
+ yyerror (&yylloc, conf, "No_IPv6 conflicts with a No_IP directive.");
+ YYERROR;
+ }
+ got_peer_noipv6++;
+ fddpi.config.pic_flags.pro3 = PI_P3_IP;
+ }
+ | peerparams NOTCP ';'
+ {
+ #ifdef DISABLE_SCTP
+ yyerror (&yylloc, conf, "No_TCP cannot be specified in daemon compiled with DISABLE_SCTP option.");
+ YYERROR;
+ #endif
+ if ((conf->cnf_flags.no_sctp) || (fddpi.config.pic_flags.pro4 == PI_P4_TCP)) {
+ yyerror (&yylloc, conf, "No_TCP conflicts with a No_SCTP directive.");
+ YYERROR;
+ }
+ got_peer_notcp++;
+ fddpi.config.pic_flags.pro4 = PI_P4_SCTP;
+ }
+ | peerparams NOSCTP ';'
+ {
+ if ((conf->cnf_flags.no_tcp) || (fddpi.config.pic_flags.pro4 == PI_P4_SCTP)) {
+ yyerror (&yylloc, conf, "No_SCTP conflicts with a No_TCP directive.");
+ YYERROR;
+ }
+ got_peer_nosctp++;
+ fddpi.config.pic_flags.pro4 = PI_P4_TCP;
+ }
+ | peerparams PREFERTCP ';'
+ {
+ fddpi.config.pic_flags.alg = PI_ALGPREF_TCP;
+ }
+ | peerparams OLDTLS ';'
+ {
+ fddpi.config.pic_flags.sec |= PI_SEC_TLS_OLD;
+ }
+ | peerparams NOTLS ';'
+ {
+ fddpi.config.pic_flags.sec |= PI_SEC_NONE;
+ }
+ | peerparams SEC3436 ';'
+ {
+ fddpi.config.pic_flags.sctpsec |= PI_SCTPSEC_3436;
+ }
+ | peerparams REALM '=' QSTRING ';'
+ {
+ fddpi.config.pic_realm = $4;
+ }
+ | peerparams PORT '=' INTEGER ';'
+ {
+ CHECK_PARAMS_DO( ($4 > 0) && ($4 < 1<<16),
+ { yyerror (&yylloc, conf, "Invalid port value"); YYERROR; } );
+ fddpi.config.pic_port = (uint16_t)$4;
+ }
+ | peerparams TCTIMER '=' INTEGER ';'
+ {
+ fddpi.config.pic_tctimer = $4;
+ }
+ | peerparams TWTIMER '=' INTEGER ';'
+ {
+ fddpi.config.pic_twtimer = $4;
+ }
+ | peerparams TLS_PRIO '=' QSTRING ';'
+ {
+ fddpi.config.pic_priority = $4;
+ }
+ | peerparams CONNTO '=' QSTRING ';'
+ {
+ struct addrinfo hints, *ai;
+ int ret;
+ int disc = 0;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICHOST;
+ ret = getaddrinfo($4, NULL, &hints, &ai);
+ if (ret == EAI_NONAME) {
+ /* The name was maybe not numeric, try again */
+ disc = EP_FL_DISC;
+ hints.ai_flags &= ~ AI_NUMERICHOST;
+ ret = getaddrinfo($4, NULL, &hints, &ai);
+ }
+ if (ret) { yyerror (&yylloc, conf, gai_strerror(ret)); YYERROR; }
+
+ CHECK_FCT_DO( fd_ep_add_merge( &fddpi.pi_endpoints, ai->ai_addr, ai->ai_addrlen, EP_FL_CONF | (disc ?: EP_ACCEPTALL) ), YYERROR );
+ free($4);
+ freeaddrinfo(ai);
+ }
+ ;
+
+tls_cred: TLS_CRED '=' QSTRING ',' QSTRING ';'
+ {
+ FILE * fd;
+ fd = fopen($3, "r");
+ if (fd == NULL) {
+ int ret = errno;
+ TRACE_ERROR("Unable to open certificate file %s for reading: %s", $3, strerror(ret));
+ yyerror (&yylloc, conf, "Error on file name");
+ YYERROR;
+ }
+ fclose(fd);
+ fd = fopen($5, "r");
+ if (fd == NULL) {
+ int ret = errno;
+ TRACE_ERROR("Unable to open private key file %s for reading: %s", $5, strerror(ret));
+ yyerror (&yylloc, conf, "Error on file name");
+ YYERROR;
+ }
+ fclose(fd);
+ conf->cnf_sec_data.cert_file = $3;
+ conf->cnf_sec_data.key_file = $5;
+
+ CHECK_GNUTLS_DO( gnutls_certificate_set_x509_key_file(
+ conf->cnf_sec_data.credentials,
+ conf->cnf_sec_data.cert_file,
+ conf->cnf_sec_data.key_file,
+ GNUTLS_X509_FMT_PEM),
+ { yyerror (&yylloc, conf, "Error opening certificate or private key file."); YYERROR; } );
+ }
+ ;
+
+tls_ca: TLS_CA '=' QSTRING ';'
+ {
+ FILE * fd;
+ fd = fopen($3, "rb");
+ if (fd == NULL) {
+ int ret = errno;
+ TRACE_ERROR("Unable to open CA file %s for reading: %s", $3, strerror(ret));
+ yyerror (&yylloc, conf, "Error on file name");
+ YYERROR;
+ }
+ #ifdef GNUTLS_VERSION_300
+ {
+ /* We import these CA in the trust list */
+ gnutls_x509_crt_t * calist;
+ unsigned int cacount;
+ gnutls_datum_t cafile;
+
+ CHECK_FCT_DO( fd_conf_stream_to_gnutls_datum(fd, &cafile),
+ { yyerror (&yylloc, conf, "Error reading CA file."); YYERROR; } );
+
+ CHECK_GNUTLS_DO( gnutls_x509_crt_list_import2(&calist, &cacount, &cafile, GNUTLS_X509_FMT_PEM,
+ GNUTLS_X509_CRT_LIST_FAIL_IF_UNSORTED),
+ { yyerror (&yylloc, conf, "Error importing CA file."); YYERROR; } );
+ free(cafile.data);
+
+ CHECK_GNUTLS_DO( gnutls_x509_trust_list_add_cas (fd_g_config->cnf_sec_data.trustlist, calist, cacount, 0),
+ { yyerror (&yylloc, conf, "Error saving CA in trust list."); YYERROR; } );
+ }
+ #endif /* GNUTLS_VERSION_300 */
+ fclose(fd);
+ conf->cnf_sec_data.ca_file = $3;
+ CHECK_GNUTLS_DO( conf->cnf_sec_data.ca_file_nr += gnutls_certificate_set_x509_trust_file(
+ conf->cnf_sec_data.credentials,
+ conf->cnf_sec_data.ca_file,
+ GNUTLS_X509_FMT_PEM),
+ { yyerror (&yylloc, conf, "Error setting CA parameters."); YYERROR; } );
+
+ }
+ ;
+
+tls_crl: TLS_CRL '=' QSTRING ';'
+ {
+ FILE * fd;
+ fd = fopen($3, "rb");
+ if (fd == NULL) {
+ int ret = errno;
+ TRACE_ERROR("Unable to open CRL file %s for reading: %s", $3, strerror(ret));
+ yyerror (&yylloc, conf, "Error on file name");
+ YYERROR;
+ }
+ #ifdef GNUTLS_VERSION_300
+ {
+ /* We import these CRL in the trust list */
+ gnutls_x509_crl_t * crllist;
+ unsigned int crlcount;
+ gnutls_datum_t crlfile;
+
+ CHECK_FCT_DO( fd_conf_stream_to_gnutls_datum(fd, &crlfile),
+ { yyerror (&yylloc, conf, "Error reading CRL file."); YYERROR; } );
+
+ CHECK_GNUTLS_DO( gnutls_x509_crl_list_import2(&crllist, &crlcount, &crlfile, GNUTLS_X509_FMT_PEM, 0),
+ { yyerror (&yylloc, conf, "Error importing CRL file."); YYERROR; } );
+ free(crlfile.data);
+
+ CHECK_GNUTLS_DO( gnutls_x509_trust_list_add_crls (fd_g_config->cnf_sec_data.trustlist, crllist, crlcount,
+ GNUTLS_TL_VERIFY_CRL,
+ 0),
+ { yyerror (&yylloc, conf, "Error importing CRL in trust list."); YYERROR; } );
+ }
+ #endif /* GNUTLS_VERSION_300 */
+ fclose(fd);
+ conf->cnf_sec_data.crl_file = $3;
+ CHECK_GNUTLS_DO( gnutls_certificate_set_x509_crl_file(
+ conf->cnf_sec_data.credentials,
+ conf->cnf_sec_data.crl_file,
+ GNUTLS_X509_FMT_PEM),
+ { yyerror (&yylloc, conf, "Error setting CRL parameters."); YYERROR; } );
+ }
+ ;
+
+tls_prio: TLS_PRIO '=' QSTRING ';'
+ {
+ const char * err_pos = NULL;
+ conf->cnf_sec_data.prio_string = $3;
+ CHECK_GNUTLS_DO( gnutls_priority_init(
+ &conf->cnf_sec_data.prio_cache,
+ conf->cnf_sec_data.prio_string,
+ &err_pos),
+ { yyerror (&yylloc, conf, "Error setting Priority parameter.");
+ TRACE_ERROR("Error at position : %s", err_pos);
+ YYERROR; } );
+ }
+ ;
+
+tls_dh: TLS_DH_BITS '=' INTEGER ';'
+ {
+ conf->cnf_sec_data.dh_bits = $3;
+ }
+ | TLS_DH_FILE '=' QSTRING ';'
+ {
+ FILE * fd;
+ free(conf->cnf_sec_data.dh_file);
+ conf->cnf_sec_data.dh_file = $3;
+ fd = fopen($3, "r");
+ if (fd == NULL) {
+ int ret = errno;
+ TRACE_ERROR("Unable to open DH file %s for reading: %s", $3, strerror(ret));
+ yyerror (&yylloc, conf, "Error on file name");
+ YYERROR;
+ }
+ fclose(fd);
+ }
+ ;
diff --git a/libfdcore/fifo_stats.c b/libfdcore/fifo_stats.c
new file mode 100644
index 0000000..0cffa04
--- /dev/null
+++ b/libfdcore/fifo_stats.c
@@ -0,0 +1,79 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/* See include/freeDiameter/libfdcore.h for more information */
+int fd_stat_getstats(enum fd_stat_type stat, struct peer_hdr * peer,
+ int * current_count, int * limit_count, int * highest_count, long long * total_count,
+ struct timespec * total, struct timespec * blocking, struct timespec * last)
+{
+ struct fd_peer * p = (struct fd_peer *)peer;
+ TRACE_ENTRY( "%d %p %p %p %p %p %p %p %p", stat, peer, current_count, limit_count, highest_count, total_count, total, blocking, last);
+
+ switch (stat) {
+ case STAT_G_LOCAL: {
+ CHECK_FCT( fd_fifo_getstats(fd_g_local, current_count, limit_count, highest_count, total_count, total, blocking, last) );
+ }
+ break;
+
+ case STAT_G_INCOMING: {
+ CHECK_FCT( fd_fifo_getstats(fd_g_incoming, current_count, limit_count, highest_count, total_count, total, blocking, last) );
+ }
+ break;
+
+ case STAT_G_OUTGOING: {
+ CHECK_FCT( fd_fifo_getstats(fd_g_outgoing, current_count, limit_count, highest_count, total_count, total, blocking, last) );
+ }
+ break;
+
+ case STAT_P_PSM: {
+ CHECK_PARAMS( CHECK_PEER( peer ) );
+ CHECK_FCT( fd_fifo_getstats(p->p_events, current_count, limit_count, highest_count, total_count, total, blocking, last) );
+ }
+ break;
+
+ case STAT_P_TOSEND: {
+ CHECK_PARAMS( CHECK_PEER( peer ) );
+ CHECK_FCT( fd_fifo_getstats(p->p_tosend, current_count, limit_count, highest_count, total_count, total, blocking, last) );
+ }
+ break;
+
+ default:
+ return EINVAL;
+ }
+
+ return 0;
+}
diff --git a/libfdcore/hooks.c b/libfdcore/hooks.c
new file mode 100644
index 0000000..f035614
--- /dev/null
+++ b/libfdcore/hooks.c
@@ -0,0 +1,454 @@
+/*********************************************************************************************************
+* 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"
+
+/* Structures for the fd_hook_data_hdl management */
+static struct fd_hook_data_hdl {
+ size_t pmd_size;
+ void (*pmd_init_cb)(struct fd_hook_permsgdata *);
+ void (*pmd_fini_cb)(struct fd_hook_permsgdata *);
+} HDH_array[FD_HOOK_HANDLE_LIMIT];
+static int max_index = 0;
+static pthread_mutex_t HDH_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* The structure linked from the msg structure list */
+struct pmd_list_item {
+ struct fd_list chain; /* this list is ordered by hdl */
+ struct fd_hook_data_hdl * hdl;
+ struct fd_hook_permsgdata { } pmd; /* this data belongs to the extension; we only know the size of it */
+};
+
+#define sizeof_pmd(hdl) (((size_t)&((struct pmd_list_item *)0)->pmd) + hdl->pmd_size)
+
+/* Now a hook registered by an extension */
+struct fd_hook_hdl {
+ struct fd_list chain[HOOK_LAST+1];
+ void (*fd_hook_cb)(enum fd_hook_type type, struct msg * msg, struct peer_hdr * peer, void * other, struct fd_hook_permsgdata *pmd, void * regdata);
+ void *regdata;
+ struct fd_hook_data_hdl *data_hdl;
+};
+
+/* Array of those hooks */
+struct {
+ struct fd_list sentinel;
+ pthread_rwlock_t rwlock;
+} HS_array[HOOK_LAST+1];
+
+/* Initialize the array of sentinels for the hooks */
+int fd_hooks_init(void)
+{
+ int i;
+ for (i=0; i <= HOOK_LAST; i++) {
+ fd_list_init(&HS_array[i].sentinel, NULL);
+ CHECK_POSIX( pthread_rwlock_init(&HS_array[i].rwlock, NULL) );
+ }
+ return 0;
+}
+
+/* Get a slot in the array */
+int fd_hook_data_register(
+ size_t permsgdata_size,
+ void (*permsgdata_init_cb) (struct fd_hook_permsgdata *),
+ void (*permsgdata_fini_cb) (struct fd_hook_permsgdata *),
+ struct fd_hook_data_hdl **new_handle)
+{
+ int ret = ENOSPC, idx;
+ TRACE_ENTRY("%zd %p %p %p", permsgdata_size, permsgdata_init_cb, permsgdata_fini_cb, new_handle);
+
+ CHECK_PARAMS( permsgdata_size && new_handle );
+
+ CHECK_POSIX( pthread_mutex_lock(&HDH_lock) );
+ if (max_index < FD_HOOK_HANDLE_LIMIT) {
+ idx = max_index++;
+ ret = 0;
+ }
+ CHECK_POSIX( pthread_mutex_unlock(&HDH_lock) );
+
+ if (ret == 0) {
+ HDH_array[idx].pmd_size = permsgdata_size;
+ HDH_array[idx].pmd_init_cb = permsgdata_init_cb;
+ HDH_array[idx].pmd_fini_cb = permsgdata_fini_cb;
+ *new_handle = &HDH_array[idx];
+ }
+
+ return ret;
+}
+
+/* Register a new hook callback */
+int fd_hook_register ( uint32_t type_mask,
+ void (*fd_hook_cb)(enum fd_hook_type type, struct msg * msg, struct peer_hdr * peer, void * other, struct fd_hook_permsgdata *pmd, void * regdata),
+ void *regdata,
+ struct fd_hook_data_hdl *data_hdl,
+ struct fd_hook_hdl ** handler )
+{
+ struct fd_hook_hdl * newhdl = NULL;
+ int i;
+
+ TRACE_ENTRY("%x %p %p %p %p", type_mask, fd_hook_cb, regdata, data_hdl, handler);
+
+ CHECK_PARAMS( fd_hook_cb && handler );
+
+ CHECK_MALLOC( newhdl = malloc(sizeof(struct fd_hook_hdl)) );
+ memset(newhdl, 0, sizeof(struct fd_hook_hdl));
+
+ newhdl->fd_hook_cb = fd_hook_cb;
+ newhdl->regdata = regdata;
+ newhdl->data_hdl = data_hdl;
+
+ for (i=0; i <= HOOK_LAST; i++) {
+ fd_list_init(&newhdl->chain[i], newhdl);
+ if (type_mask & (1<<i)) {
+ CHECK_POSIX( pthread_rwlock_wrlock(&HS_array[i].rwlock) );
+ fd_list_insert_before( &HS_array[i].sentinel, &newhdl->chain[i]);
+ CHECK_POSIX( pthread_rwlock_unlock(&HS_array[i].rwlock) );
+ }
+ }
+
+ *handler = newhdl;
+ return 0;
+}
+
+/* free this hook callback */
+int fd_hook_unregister( struct fd_hook_hdl * handler )
+{
+ int i;
+ TRACE_ENTRY("%p", handler);
+ CHECK_PARAMS( handler );
+
+ for (i=0; i <= HOOK_LAST; i++) {
+ if ( ! FD_IS_LIST_EMPTY(&handler->chain[i])) {
+ CHECK_POSIX( pthread_rwlock_wrlock(&HS_array[i].rwlock) );
+ fd_list_unlink(&handler->chain[i]);
+ CHECK_POSIX( pthread_rwlock_unlock(&HS_array[i].rwlock) );
+ }
+ }
+
+ free(handler);
+
+ return 0;
+}
+
+/* callback for the libfdproto to free the data associated with a message */
+static void pmdl_free(struct fd_msg_pmdl *pmdl)
+{
+ /* destroy all the items in the list */
+ while (!FD_IS_LIST_EMPTY(&pmdl->sentinel)) {
+ struct pmd_list_item * li = (struct pmd_list_item *)(pmdl->sentinel.next);
+ if (li->hdl->pmd_fini_cb) {
+ (*li->hdl->pmd_fini_cb)(&li->pmd);
+ }
+ fd_list_unlink(&li->chain);
+ free(li);
+ }
+ CHECK_POSIX_DO( pthread_mutex_destroy(&pmdl->lock), );
+ pmdl->sentinel.o = NULL;
+}
+
+/* Save the list of pmd into the message structure, as well as the callback to free this list */
+void fd_hook_associate(struct msg * msg, struct fd_msg_pmdl * pmdl)
+{
+ struct fd_msg_pmdl * in_msg;
+
+ CHECK_PARAMS_DO( msg && pmdl, return );
+ in_msg = fd_msg_pmdl_get(msg);
+ ASSERT(in_msg && (in_msg->sentinel.o == NULL)); /* error / already initialized ??? */
+ in_msg->sentinel.o = pmdl_free;
+ /* Now move all items from the pmdl pointer into the initialized list */
+ CHECK_POSIX_DO( pthread_mutex_lock(&pmdl->lock), );
+ fd_list_move_end(&in_msg->sentinel, &pmdl->sentinel);
+ CHECK_POSIX_DO( pthread_mutex_unlock(&pmdl->lock), );
+ pmdl_free(pmdl);
+ /* We're done */
+}
+
+/* Return the location of the permsgdata area corresponding to this handle, after eventually having created it. Return NULL in case of failure */
+static struct fd_hook_permsgdata * get_or_create_pmd(struct fd_msg_pmdl *pmdl, struct fd_hook_hdl * h)
+{
+ struct fd_hook_permsgdata * ret = NULL;
+ struct fd_list * li;
+
+ CHECK_POSIX_DO( pthread_mutex_lock(&pmdl->lock), );
+
+ if (pmdl->sentinel.o == NULL) {
+ pmdl->sentinel.o = pmdl_free;
+ }
+
+ /* Search in the list for an item with the same handle. The list is ordered by this handle */
+ for (li=pmdl->sentinel.next; li != &pmdl->sentinel; li = li->next) {
+ struct pmd_list_item * pli = (struct pmd_list_item *) li;
+ if (pli->hdl == h->data_hdl)
+ ret = &pli->pmd;
+ if (pli->hdl >= h->data_hdl)
+ break;
+ }
+ if (!ret) {
+ /* we need to create a new one and insert before li */
+ struct pmd_list_item * pli;
+ CHECK_MALLOC_DO( pli = malloc(sizeof_pmd(h->data_hdl)), );
+ if (pli) {
+ memset(pli, 0, sizeof_pmd(h->data_hdl));
+ fd_list_init(&pli->chain, pli);
+ pli->hdl = h->data_hdl;
+ ret = &pli->pmd;
+ if (h->data_hdl->pmd_init_cb) {
+ (*h->data_hdl->pmd_init_cb)(ret);
+ }
+ fd_list_insert_before(li, &pli->chain);
+ }
+ }
+
+ CHECK_POSIX_DO( pthread_mutex_unlock(&pmdl->lock), );
+ return ret;
+}
+
+struct fd_hook_permsgdata * fd_hook_get_request_pmd(struct fd_hook_data_hdl *data_hdl, struct msg * answer)
+{
+ struct msg * qry;
+ struct fd_msg_pmdl *pmdl;
+ struct fd_hook_permsgdata * ret = NULL;
+ struct fd_list * li;
+
+ CHECK_FCT_DO( fd_msg_answ_getq(answer, &qry), return NULL );
+ if (!qry)
+ return NULL;
+
+ pmdl = fd_msg_pmdl_get(qry);
+ if (!pmdl)
+ return NULL;
+
+ CHECK_POSIX_DO( pthread_mutex_lock(&pmdl->lock), );
+ /* Search in the list for an item with the same handle. The list is ordered by this handle */
+ for (li=pmdl->sentinel.next; li != &pmdl->sentinel; li = li->next) {
+ struct pmd_list_item * pli = (struct pmd_list_item *) li;
+ if (pli->hdl == data_hdl)
+ ret = &pli->pmd;
+ if (pli->hdl >= data_hdl)
+ break;
+ }
+ CHECK_POSIX_DO( pthread_mutex_unlock(&pmdl->lock), );
+ return ret;
+}
+
+/* Create a mask */
+uint32_t fd_hook_mask_helper(int dummy, ...)
+{
+ va_list ap;
+ uint32_t ret = 0;
+ int next;
+
+ va_start(ap, dummy);
+ while ((next = va_arg(ap, int)) >= 0) {
+ if (next > HOOK_LAST)
+ break; /* invalid parameter */
+ ret |= (1<<next);
+ }
+ va_end(ap);
+
+ return ret;
+}
+
+static pthread_mutex_t hook_default_mtx = PTHREAD_MUTEX_INITIALIZER;
+static char * hook_default_buf = NULL;
+static size_t hook_default_len = 0;
+
+/* The function that does the work of calling the extension's callbacks and also managing the permessagedata structures */
+void fd_hook_call(enum fd_hook_type type, struct msg * msg, struct fd_peer * peer, void * other, struct fd_msg_pmdl * pmdl)
+{
+ struct fd_list * li;
+ ASSERT(type <= HOOK_LAST);
+ int call_default = 0;
+
+ /* lock the list of hooks for this type */
+ CHECK_POSIX_DO( pthread_rwlock_rdlock(&HS_array[type].rwlock), );
+
+ pthread_cleanup_push( fd_cleanup_rwlock, &HS_array[type].rwlock );
+
+ if (FD_IS_LIST_EMPTY(&HS_array[type].sentinel)) {
+ call_default = 1;
+ } else {
+ /* for each registered hook */
+ for (li = HS_array[type].sentinel.next; li != &HS_array[type].sentinel; li = li->next) {
+ struct fd_hook_hdl * h = (struct fd_hook_hdl *)li->o;
+ struct fd_hook_permsgdata * pmd = NULL;
+
+ /* do we need to handle pmd ? */
+ if (h->data_hdl && pmdl) {
+ pmd = get_or_create_pmd(pmdl, h);
+ }
+
+ /* Now, call this callback */
+ (*h->fd_hook_cb)(type, msg, &peer->p_hdr, other, pmd, h->regdata);
+ }
+ }
+
+ pthread_cleanup_pop(0);
+
+ /* done */
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&HS_array[type].rwlock), );
+
+ if (call_default) {
+ CHECK_POSIX_DO( pthread_mutex_lock(&hook_default_mtx), );
+
+ pthread_cleanup_push( fd_cleanup_mutex, &hook_default_mtx );
+
+ /* There was no registered handler, default behavior for this hook */
+ switch (type) {
+ case HOOK_DATA_RECEIVED: {
+ struct fd_cnx_rcvdata *rcv_data = other;
+ LOG_A("RCV: %zd bytes", rcv_data->length);
+ break;
+ }
+
+ case HOOK_MESSAGE_RECEIVED: {
+ CHECK_MALLOC_DO(fd_msg_dump_summary(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ LOG_D("RCV from '%s': %s", peer ? peer->p_hdr.info.pi_diamid : "<unknown>", hook_default_buf);
+ break;
+ }
+
+ case HOOK_MESSAGE_LOCAL: {
+ CHECK_MALLOC_DO(fd_msg_dump_full(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ LOG_A("Handled to framework for sending: %s", hook_default_buf);
+ break;
+ }
+
+ case HOOK_MESSAGE_SENDING: {
+ LOG_A("SENDING message to '%s'", peer ? peer->p_hdr.info.pi_diamid : "<unknown>");
+ break;
+ }
+
+ case HOOK_MESSAGE_SENT: {
+ CHECK_MALLOC_DO(fd_msg_dump_summary(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ LOG_D("SENT to '%s': %s", peer ? peer->p_hdr.info.pi_diamid : "<unknown>", hook_default_buf);
+ break;
+ }
+
+ case HOOK_MESSAGE_FAILOVER: {
+ CHECK_MALLOC_DO(fd_msg_dump_summary(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ LOG_D("Failing over message sent to '%s': %s", peer ? peer->p_hdr.info.pi_diamid : "<unknown>", hook_default_buf);
+ break;
+ }
+
+ case HOOK_MESSAGE_PARSING_ERROR: {
+ if (msg) {
+ DiamId_t id = NULL;
+ if (fd_msg_source_get( msg, &id, NULL ))
+ id = (DiamId_t)"<error getting source>";
+
+ if (!id)
+ id = (DiamId_t)"<local>";
+
+ CHECK_MALLOC_DO(fd_msg_dump_treeview(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+
+ LOG_E("Parsing error: '%s' for the following message received from '%s':", (char *)other, (char *)id);
+ LOG_SPLIT(FD_LOG_ERROR, " ", hook_default_buf, NULL);
+ } else {
+ struct fd_cnx_rcvdata *rcv_data = other;
+ CHECK_MALLOC_DO(fd_dump_extend_hexdump(&hook_default_buf, &hook_default_len, NULL, rcv_data->buffer, rcv_data->length, 0, 0), break);
+ LOG_E("Parsing error: cannot parse %zdB buffer from '%s': %s", rcv_data->length, peer ? peer->p_hdr.info.pi_diamid : "<unknown>", hook_default_buf);
+ }
+ break;
+ }
+
+ case HOOK_MESSAGE_PARSING_ERROR2: {
+ CHECK_MALLOC_DO(fd_msg_dump_treeview(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+
+ LOG_E("Returning following message after parsing error:");
+ LOG_SPLIT(FD_LOG_ERROR, " ", hook_default_buf, NULL);
+ break;
+ }
+
+ case HOOK_MESSAGE_ROUTING_ERROR: {
+ CHECK_MALLOC_DO(fd_msg_dump_treeview(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ LOG_E("Routing error: '%s' for the following message:", (char *)other);
+ LOG_SPLIT(FD_LOG_ERROR, " ", hook_default_buf, NULL);
+ break;
+ }
+
+ case HOOK_MESSAGE_ROUTING_FORWARD: {
+ CHECK_MALLOC_DO(fd_msg_dump_summary(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ LOG_D("FORWARDING: %s", hook_default_buf);
+ break;
+ }
+
+ case HOOK_MESSAGE_ROUTING_LOCAL: {
+ CHECK_MALLOC_DO(fd_msg_dump_summary(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ LOG_D("DISPATCHING: %s", hook_default_buf);
+ break;
+ }
+
+ case HOOK_MESSAGE_DROPPED: {
+ CHECK_MALLOC_DO(fd_msg_dump_treeview(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ LOG_E("Message discarded ('%s'):", (char *)other);
+ LOG_SPLIT(FD_LOG_ERROR, " ", hook_default_buf, NULL);
+ break;
+ }
+
+ case HOOK_PEER_CONNECT_FAILED: {
+ if (msg) {
+ CHECK_MALLOC_DO(fd_msg_dump_full(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ LOG_N("Connection to '%s' failed: '%s'; CER/CEA dump:", peer ? peer->p_hdr.info.pi_diamid : "<unknown>", (char *)other);
+ LOG_SPLIT(FD_LOG_NOTICE, " ", hook_default_buf, NULL);
+ } else {
+ LOG_D("Connection to '%s' failed: %s", peer ? peer->p_hdr.info.pi_diamid : "<unknown>", (char *)other);
+ }
+ break;
+ }
+
+ case HOOK_PEER_CONNECT_SUCCESS: {
+ DiamId_t id = NULL;
+ if ((!fd_msg_source_get( msg, &id, NULL )) && (id == NULL)) { /* The CEA is locally issued */
+ fd_msg_answ_getq(msg, &msg); /* We dump the CER in that case */
+ }
+ CHECK_MALLOC_DO(fd_msg_dump_full(&hook_default_buf, &hook_default_len, NULL, msg, NULL, 0, 1), break);
+ char protobuf[40];
+ if (peer) {
+ CHECK_FCT_DO(fd_peer_cnx_proto_info(&peer->p_hdr, protobuf, sizeof(protobuf)), break );
+ } else {
+ protobuf[0] = '-';
+ protobuf[1] = '\0';
+ }
+ LOG_N("Connected to '%s' (%s), remote capabilities: ", peer ? peer->p_hdr.info.pi_diamid : "<unknown>", protobuf);
+ LOG_SPLIT(FD_LOG_NOTICE, " ", hook_default_buf, NULL);
+ break;
+ }
+
+ }
+
+ pthread_cleanup_pop(0);
+
+ CHECK_POSIX_DO( pthread_mutex_unlock(&hook_default_mtx), );
+ }
+}
diff --git a/libfdcore/messages.c b/libfdcore/messages.c
new file mode 100644
index 0000000..2242827
--- /dev/null
+++ b/libfdcore/messages.c
@@ -0,0 +1,452 @@
+/*********************************************************************************************************
+* 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"
+
+static struct dict_object * dict_avp_SI = NULL; /* Session-Id */
+static struct dict_object * dict_avp_OH = NULL; /* Origin-Host */
+static struct dict_object * dict_avp_OR = NULL; /* Origin-Realm */
+static struct dict_object * dict_avp_EM = NULL; /* Error-Message */
+static struct dict_object * dict_avp_ERH = NULL; /* Error-Reporting-Host */
+static struct dict_object * dict_avp_FAVP= NULL; /* Failed-AVP */
+static struct dict_object * dict_avp_RC = NULL; /* Result-Code */
+struct dict_object * fd_dict_avp_OSI = NULL; /* Origin-State-Id */
+struct dict_object * fd_dict_cmd_CER = NULL; /* Capabilities-Exchange-Request */
+struct dict_object * fd_dict_cmd_DWR = NULL; /* Device-Watchdog-Request */
+struct dict_object * fd_dict_avp_DC = NULL; /* Disconnect-Cause */
+struct dict_object * fd_dict_cmd_DPR = NULL; /* Disconnect-Peer-Request */
+
+/* Resolve the dictionary objects */
+int fd_msg_init(void)
+{
+ TRACE_ENTRY("");
+
+ /* Initialize the dictionary objects that we may use frequently */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Id", &dict_avp_SI , ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &dict_avp_OH , ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &dict_avp_OR , ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-State-Id", &fd_dict_avp_OSI , ENOENT) );
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Result-Code", &dict_avp_RC , ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Error-Message", &dict_avp_EM , ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Error-Reporting-Host", &dict_avp_ERH , ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Failed-AVP", &dict_avp_FAVP, ENOENT) );
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Disconnect-Cause", &fd_dict_avp_DC , ENOENT) );
+
+ CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_NAME, "Capabilities-Exchange-Request", &fd_dict_cmd_CER, ENOENT ) );
+ CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_NAME, "Device-Watchdog-Request", &fd_dict_cmd_DWR, ENOENT ) );
+ CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_NAME, "Disconnect-Peer-Request", &fd_dict_cmd_DPR, ENOENT ) );
+
+
+ return 0;
+}
+
+/* Add Origin-Host, Origin-Realm, Origin-State-Id AVPS at the end of the message */
+int fd_msg_add_origin ( struct msg * msg, int osi )
+{
+ union avp_value val;
+ struct avp * avp_OH = NULL;
+ struct avp * avp_OR = NULL;
+ struct avp * avp_OSI = NULL;
+
+ TRACE_ENTRY("%p", msg);
+ CHECK_PARAMS( msg );
+
+ /* Create the Origin-Host AVP */
+ CHECK_FCT( fd_msg_avp_new( dict_avp_OH, 0, &avp_OH ) );
+
+ /* Set its value */
+ memset(&val, 0, sizeof(val));
+ val.os.data = (os0_t)fd_g_config->cnf_diamid;
+ val.os.len = fd_g_config->cnf_diamid_len;
+ CHECK_FCT( fd_msg_avp_setvalue( avp_OH, &val ) );
+
+ /* Add it to the message */
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp_OH ) );
+
+
+ /* Create the Origin-Realm AVP */
+ CHECK_FCT( fd_msg_avp_new( dict_avp_OR, 0, &avp_OR ) );
+
+ /* Set its value */
+ memset(&val, 0, sizeof(val));
+ val.os.data = (os0_t)fd_g_config->cnf_diamrlm;
+ val.os.len = fd_g_config->cnf_diamrlm_len;
+ CHECK_FCT( fd_msg_avp_setvalue( avp_OR, &val ) );
+
+ /* Add it to the message */
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp_OR ) );
+
+ if (osi) {
+ /* Create the Origin-State-Id AVP */
+ CHECK_FCT( fd_msg_avp_new( fd_dict_avp_OSI, 0, &avp_OSI ) );
+
+ /* Set its value */
+ memset(&val, 0, sizeof(val));
+ val.u32 = fd_g_config->cnf_orstateid;
+ CHECK_FCT( fd_msg_avp_setvalue( avp_OSI, &val ) );
+
+ /* Add it to the message */
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp_OSI ) );
+ }
+
+ return 0;
+}
+
+/* Create a new Session-Id and add at the beginning of the message. */
+int fd_msg_new_session( struct msg * msg, os0_t opt, size_t optlen )
+{
+ union avp_value val;
+ struct avp * avp = NULL;
+ struct session * sess = NULL;
+ os0_t sid;
+ size_t sidlen;
+
+ TRACE_ENTRY("%p %p %zd", msg, opt, optlen);
+ CHECK_PARAMS( msg );
+
+ /* Check there is not already a session in the message */
+ CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, msg, &sess, NULL) );
+ CHECK_PARAMS( sess == NULL );
+
+ /* Ok, now create the session */
+ CHECK_FCT( fd_sess_new ( &sess, fd_g_config->cnf_diamid, fd_g_config->cnf_diamid_len, opt, optlen ) );
+ CHECK_FCT( fd_sess_getsid( sess, &sid, &sidlen) );
+
+ /* Create an AVP to hold it */
+ CHECK_FCT( fd_msg_avp_new( dict_avp_SI, 0, &avp ) );
+
+ /* Set its value */
+ memset(&val, 0, sizeof(val));
+ val.os.data = sid;
+ val.os.len = sidlen;
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+
+ /* Add it to the message */
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_FIRST_CHILD, avp ) );
+
+ /* Save the session associated with the message */
+ CHECK_FCT( fd_msg_sess_set( msg, sess) );
+
+ /* Done! */
+ return 0;
+}
+
+
+/* Add Result-Code and eventually Failed-AVP, Error-Message and Error-Reporting-Host AVPs */
+int fd_msg_rescode_set( struct msg * msg, char * rescode, char * errormsg, struct avp * optavp, int type_id )
+{
+ union avp_value val;
+ struct avp * avp_RC = NULL;
+ struct avp * avp_EM = NULL;
+ struct avp * avp_ERH = NULL;
+ struct avp * avp_FAVP= NULL;
+ uint32_t rc_val = 0;
+ int set_e_bit=0;
+ int std_err_msg=0;
+
+ TRACE_ENTRY("%p %s %p %p %d", msg, rescode, errormsg, optavp, type_id);
+
+ CHECK_PARAMS( msg && rescode );
+
+ /* Find the enum value corresponding to the rescode string, this will give the class of error */
+ {
+ struct dict_object * enum_obj = NULL;
+ struct dict_enumval_request req;
+ memset(&req, 0, sizeof(struct dict_enumval_request));
+
+ /* First, get the enumerated type of the Result-Code AVP (this is fast, no need to cache the object) */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_TYPE, TYPE_OF_AVP, dict_avp_RC, &(req.type_obj), ENOENT ) );
+
+ /* Now search for the value given as parameter */
+ req.search.enum_name = rescode;
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_ENUMVAL, ENUMVAL_BY_STRUCT, &req, &enum_obj, ENOTSUP) );
+
+ /* finally retrieve its data */
+ CHECK_FCT_DO( fd_dict_getval( enum_obj, &(req.search) ), return EINVAL );
+
+ /* copy the found value, we're done */
+ rc_val = req.search.enum_value.u32;
+ }
+
+ if (type_id == 1) {
+ /* Add the Origin-Host and Origin-Realm AVP */
+ CHECK_FCT( fd_msg_add_origin ( msg, 0 ) );
+ }
+
+ /* Create the Result-Code AVP */
+ CHECK_FCT( fd_msg_avp_new( dict_avp_RC, 0, &avp_RC ) );
+
+ /* Set its value */
+ memset(&val, 0, sizeof(val));
+ val.u32 = rc_val;
+ CHECK_FCT( fd_msg_avp_setvalue( avp_RC, &val ) );
+
+ /* Add it to the message */
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp_RC ) );
+
+ if (type_id == 2) {
+ /* Add the Error-Reporting-Host AVP */
+
+ CHECK_FCT( fd_msg_avp_new( dict_avp_ERH, 0, &avp_ERH ) );
+
+ /* Set its value */
+ memset(&val, 0, sizeof(val));
+ val.os.data = (uint8_t *)fd_g_config->cnf_diamid;
+ val.os.len = fd_g_config->cnf_diamid_len;
+ CHECK_FCT( fd_msg_avp_setvalue( avp_ERH, &val ) );
+
+ /* Add it to the message */
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp_ERH ) );
+
+ }
+
+ /* Now add the optavp in a FailedAVP if provided */
+ if (optavp) {
+ struct avp * optavp_cpy = NULL;
+ struct avp_hdr *opt_hdr, *optcpy_hdr;
+ struct dict_object * opt_model = NULL;
+ int is_grouped = 0;
+
+ /* Create the Failed-AVP AVP */
+ CHECK_FCT( fd_msg_avp_new( dict_avp_FAVP, 0, &avp_FAVP ) );
+
+ /* Was this AVP a grouped one? Best effort only here */
+ if (!fd_msg_model ( optavp, &opt_model ) && (opt_model != NULL)) {
+ struct dict_avp_data dictdata;
+ CHECK_FCT( fd_dict_getval(opt_model, &dictdata) );
+ if (dictdata.avp_basetype == AVP_TYPE_GROUPED)
+ is_grouped = 1;
+ }
+
+ /* Create a new AVP with a copy of the data of the invalid or missing AVP */
+ optavp_cpy = optavp;
+
+ if (is_grouped) {
+ CHECK_FCT( fd_msg_avp_new( opt_model, 0, &optavp_cpy) );
+ } else {
+ CHECK_FCT( fd_msg_avp_new( NULL, AVPFL_SET_BLANK_VALUE | AVPFL_SET_RAWDATA_FROM_AVP, &optavp_cpy) );
+
+ CHECK_FCT( fd_msg_avp_hdr(optavp, &opt_hdr) );
+ CHECK_FCT( fd_msg_avp_hdr(optavp_cpy, &optcpy_hdr) );
+ memcpy(optcpy_hdr, opt_hdr, sizeof(struct avp_hdr));
+ }
+
+ /* Add the passed AVP inside it */
+ CHECK_FCT( fd_msg_avp_add( avp_FAVP, MSG_BRW_LAST_CHILD, optavp_cpy ) );
+
+ /* And add to the message */
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp_FAVP ) );
+ }
+
+
+ /* Deal with the 'E' bit and the error message */
+ switch (rc_val / 1000) {
+ case 1: /* Informational */
+ case 2: /* Success */
+ /* Nothing special here: no E bit, no error message unless one is specified */
+ break;
+
+ case 3: /* Protocol Errors */
+ set_e_bit = 1;
+ std_err_msg = 1;
+ break;
+
+ case 4: /* Transcient Failure */
+ case 5: /* Permanent Failure */
+ default:
+ std_err_msg = 1;
+ break;
+
+ }
+
+ {
+ struct msg_hdr * hdr = NULL;
+
+ CHECK_FCT( fd_msg_hdr( msg, &hdr ) );
+
+ if (set_e_bit)
+ hdr->msg_flags |= CMD_FLAG_ERROR;
+ else
+ hdr->msg_flags &= ~ CMD_FLAG_ERROR;
+ }
+
+ if (std_err_msg || errormsg) {
+ /* Add the Error-Message AVP */
+
+ CHECK_FCT( fd_msg_avp_new( dict_avp_EM, 0, &avp_EM ) );
+
+ /* Set its value */
+ memset(&val, 0, sizeof(val));
+
+ if (errormsg) {
+ val.os.data = (uint8_t *)errormsg;
+ val.os.len = strlen(errormsg);
+ } else {
+ val.os.data = (uint8_t *)rescode;
+ val.os.len = strlen(rescode);
+ }
+ CHECK_FCT( fd_msg_avp_setvalue( avp_EM, &val ) );
+
+ /* Add it to the message */
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp_EM ) );
+ }
+
+ return 0;
+}
+
+static int fd_msg_send_int( struct msg ** pmsg, void (*anscb)(void *, struct msg **), void * data, void (*expirecb)(void *, DiamId_t, size_t, struct msg **), const struct timespec *timeout )
+{
+ struct msg_hdr *hdr;
+ DiamId_t diamid;
+
+ /* Save the callback in the message, with the timeout */
+ CHECK_FCT( fd_msg_anscb_associate( *pmsg, anscb, data, expirecb, timeout ) );
+
+ /* If this is a new request, call the HOOK_MESSAGE_LOCAL hook */
+ if ( (fd_msg_hdr(*pmsg, &hdr) == 0)
+ && (hdr->msg_flags & CMD_FLAG_REQUEST)
+ && (fd_msg_source_get(*pmsg, &diamid, NULL) == 0)
+ && (diamid == NULL)) {
+ fd_hook_call(HOOK_MESSAGE_LOCAL, *pmsg, NULL, NULL, fd_msg_pmdl_get(*pmsg));
+ }
+
+ /* Post the message in the outgoing queue */
+ CHECK_FCT( fd_fifo_post(fd_g_outgoing, pmsg) );
+
+ return 0;
+}
+
+/* Send a message and optionally register a callback for an answer */
+int fd_msg_send ( struct msg ** pmsg, void (*anscb)(void *, struct msg **), void * data )
+{
+ TRACE_ENTRY("%p %p %p", pmsg, anscb, data);
+ CHECK_PARAMS( pmsg );
+
+ return fd_msg_send_int(pmsg, anscb, data, NULL, NULL);
+}
+
+/* The variation of the same function with a timeout callback */
+int fd_msg_send_timeout ( struct msg ** pmsg, void (*anscb)(void *, struct msg **), void * data, void (*expirecb)(void *, DiamId_t, size_t, struct msg **), const struct timespec *timeout )
+{
+ TRACE_ENTRY("%p %p %p %p %p", pmsg, anscb, data, expirecb, timeout);
+ CHECK_PARAMS( pmsg && expirecb && timeout );
+
+ return fd_msg_send_int(pmsg, anscb, data, expirecb, timeout);
+}
+
+
+/* Parse a message against our dictionary, and in case of error log and eventually build the error reply -- returns the parsing status */
+int fd_msg_parse_or_error( struct msg ** msg, struct msg **error)
+{
+ int ret = 0;
+ struct msg * m;
+ struct msg_hdr * hdr = NULL;
+ struct fd_pei pei;
+
+ TRACE_ENTRY("%p", msg);
+
+ CHECK_PARAMS(msg && *msg && error);
+ m = *msg;
+ *error = NULL;
+
+ /* Parse the message against our dictionary */
+ ret = fd_msg_parse_rules ( m, fd_g_config->cnf_dict, &pei);
+ if ((ret != EBADMSG) /* Parsing grouped AVP failed / Conflicting rule found */
+ && (ret != ENOTSUP)) /* Command is not supported / Mandatory AVP is not supported */
+ return ret; /* 0 or another error */
+
+ /* Log */
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, m, NULL, pei.pei_message ?: pei.pei_errcode, fd_msg_pmdl_get(m));
+
+ CHECK_FCT( fd_msg_hdr(m, &hdr) );
+
+ /* Now create an answer error if the message is a query */
+ if (hdr->msg_flags & CMD_FLAG_REQUEST) {
+
+ /* Create the error message */
+ CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, &m, pei.pei_protoerr ? MSGFL_ANSW_ERROR : 0 ) );
+
+ /* Set the error code */
+ CHECK_FCT( fd_msg_rescode_set(m, pei.pei_errcode, pei.pei_message, pei.pei_avp, 1 ) );
+
+ /* free the pei AVP to avoid memory leak */
+ if (pei.pei_avp_free) {
+ fd_msg_free(pei.pei_avp);
+ }
+
+ *msg = NULL;
+ *error = m;
+
+ } else {
+ do { /* Rescue error messages */
+ struct avp * avp;
+ union avp_value * rc = NULL;
+
+ /* Search the Result-Code AVP */
+ CHECK_FCT_DO( fd_msg_browse(*msg, MSG_BRW_FIRST_CHILD, &avp, NULL), break );
+ while (avp) {
+ struct avp_hdr * ahdr;
+ CHECK_FCT_DO( fd_msg_avp_hdr( avp, &ahdr ), break );
+
+ if ((ahdr->avp_code == AC_RESULT_CODE) && (! (ahdr->avp_flags & AVP_FLAG_VENDOR)) ) {
+ /* Parse this AVP */
+ ASSERT( ahdr->avp_value );
+ rc = ahdr->avp_value;
+ break;
+ }
+
+ /* Go to next AVP */
+ CHECK_FCT_DO( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL), break );
+ }
+
+ if (rc) {
+ switch (rc->u32 / 1000) {
+ case 1: /* 1xxx : Informational */
+ case 2: /* 2xxx : Sucess */
+ /* In these cases, we want the message to validate the ABNF, so we will discard the bad message */
+ break;
+
+ default: /* Other errors */
+ /* We let the application decide what to do with the message, we rescue it */
+ *error = m;
+ }
+ }
+ } while (0);
+ }
+
+ return EBADMSG; /* We convert ENOTSUP to EBADMSG as well */
+}
diff --git a/libfdcore/p_ce.c b/libfdcore/p_ce.c
new file mode 100644
index 0000000..1b4cd13
--- /dev/null
+++ b/libfdcore/p_ce.c
@@ -0,0 +1,1103 @@
+/*********************************************************************************************************
+* 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"
+
+/* This file contains code to handle Capabilities Exchange messages (CER and CEA) and election process */
+
+/* Save a connection as peer's principal */
+static int set_peer_cnx(struct fd_peer * peer, struct cnxctx **cnx)
+{
+ CHECK_PARAMS( peer->p_cnxctx == NULL );
+
+ /* Save the connection in peer */
+ peer->p_cnxctx = *cnx;
+ *cnx = NULL;
+
+ /* Set the events to be sent to the PSM */
+ CHECK_FCT( fd_cnx_recv_setaltfifo(peer->p_cnxctx, peer->p_events) );
+
+ /* Read the credentials if possible */
+ if (fd_cnx_getTLS(peer->p_cnxctx)) {
+ CHECK_FCT( fd_cnx_getcred(peer->p_cnxctx, &peer->p_hdr.info.runtime.pir_cert_list, &peer->p_hdr.info.runtime.pir_cert_list_size) );
+ }
+
+ /* Read the endpoints, maybe used to reconnect to the peer later */
+ CHECK_FCT( fd_cnx_getremoteeps(peer->p_cnxctx, &peer->p_hdr.info.pi_endpoints) );
+
+ /* Read the protocol */
+ peer->p_hdr.info.runtime.pir_proto = fd_cnx_getproto(peer->p_cnxctx);
+
+ return 0;
+}
+
+/* Delete the peer connection, and cleanup associated information */
+void fd_p_ce_clear_cnx(struct fd_peer * peer, struct cnxctx ** cnx_kept)
+{
+ peer->p_hdr.info.runtime.pir_cert_list = NULL;
+ peer->p_hdr.info.runtime.pir_cert_list_size = 0;
+ peer->p_hdr.info.runtime.pir_proto = 0;
+
+ if (peer->p_cnxctx) {
+ if (cnx_kept != NULL) {
+ *cnx_kept = peer->p_cnxctx;
+ } else {
+ fd_cnx_destroy(peer->p_cnxctx);
+ }
+ peer->p_cnxctx = NULL;
+ }
+}
+
+/* Election: compare the Diameter Ids by lexical order, return true if the election is won */
+static __inline__ int election_result(struct fd_peer * peer)
+{
+ int ret = (strcasecmp(peer->p_hdr.info.pi_diamid, fd_g_config->cnf_diamid) < 0);
+ if (ret) {
+ TRACE_DEBUG(INFO, "Election WON against peer '%s'", peer->p_hdr.info.pi_diamid);
+ } else {
+ TRACE_DEBUG(INFO, "Election LOST against peer '%s'", peer->p_hdr.info.pi_diamid);
+ }
+ return ret;
+}
+
+/* Add AVPs about local information in a CER or CEA */
+static int add_CE_info(struct msg *msg, struct cnxctx * cnx, int isi_tls, int isi_none)
+{
+ struct dict_object * dictobj = NULL;
+ struct avp * avp = NULL;
+ union avp_value val;
+ struct fd_list *li;
+
+ /* Add the Origin-* AVPs */
+ CHECK_FCT( fd_msg_add_origin ( msg, 1 ) );
+
+ /* Find the model for Host-IP-Address AVP */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Host-IP-Address", &dictobj, ENOENT ) );
+
+ /* Add the AVP(s) -- not sure what is the purpose... We could probably only add the primary one ? */
+ for (li = fd_g_config->cnf_endpoints.next; li != &fd_g_config->cnf_endpoints; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *)li;
+ CHECK_FCT( fd_msg_avp_new ( dictobj, 0, &avp ) );
+ CHECK_FCT( fd_msg_avp_value_encode ( &ep->ss, avp ) );
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+ }
+
+ /* Vendor-Id, Product-Name, and Firmware-Revision AVPs */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Vendor-Id", &dictobj, ENOENT ) );
+ CHECK_FCT( fd_msg_avp_new ( dictobj, 0, &avp ) );
+ val.u32 = MY_VENDOR_ID;
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Product-Name", &dictobj, ENOENT ) );
+ CHECK_FCT( fd_msg_avp_new ( dictobj, 0, &avp ) );
+ val.os.data = (unsigned char *)FD_PROJECT_NAME;
+ val.os.len = strlen(FD_PROJECT_NAME);
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Firmware-Revision", &dictobj, ENOENT ) );
+ CHECK_FCT( fd_msg_avp_new ( dictobj, 0, &avp ) );
+ val.u32 = (uint32_t)(FD_PROJECT_VERSION_MAJOR * 10000 + FD_PROJECT_VERSION_MINOR * 100 + FD_PROJECT_VERSION_REV);
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+
+
+ /* Add the Inband-Security-Id AVP if needed */
+ if (isi_tls || isi_none) {
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Inband-Security-Id", &dictobj, ENOENT ) );
+
+ if (isi_none) {
+ CHECK_FCT( fd_msg_avp_new ( dictobj, 0, &avp ) );
+ val.u32 = ACV_ISI_NO_INBAND_SECURITY;
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+ }
+
+ if (isi_tls) {
+ CHECK_FCT( fd_msg_avp_new ( dictobj, 0, &avp ) );
+ val.u32 = ACV_ISI_TLS;
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+ }
+ }
+
+ /* List of local applications */
+ {
+ struct dict_object * dictobj_auth = NULL;
+ struct dict_object * dictobj_acct = NULL;
+ struct dict_object * dictobj_vid = NULL;
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Vendor-Specific-Application-Id", &dictobj, ENOENT ) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Vendor-Id", &dictobj_vid, ENOENT ) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Auth-Application-Id", &dictobj_auth, ENOENT ) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Acct-Application-Id", &dictobj_acct, ENOENT ) );
+
+ for (li = fd_g_config->cnf_apps.next; li != &fd_g_config->cnf_apps; li = li->next) {
+ struct fd_app * a = (struct fd_app *)(li);
+
+ if (a->flags.auth) {
+ CHECK_FCT( fd_msg_avp_new ( dictobj_auth, 0, &avp ) );
+ val.u32 = a->appid;
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ if (a->vndid != 0) {
+ struct avp * avp2 = NULL;
+ CHECK_FCT( fd_msg_avp_new ( dictobj, 0, &avp2 ) );
+ CHECK_FCT( fd_msg_avp_add( avp2, MSG_BRW_LAST_CHILD, avp ) );
+ avp = avp2;
+ CHECK_FCT( fd_msg_avp_new ( dictobj_vid, 0, &avp2 ) );
+ val.u32 = a->vndid;
+ CHECK_FCT( fd_msg_avp_setvalue( avp2, &val ) );
+ CHECK_FCT( fd_msg_avp_add( avp, MSG_BRW_LAST_CHILD, avp2 ) );
+ }
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+ }
+ if (a->flags.acct) {
+ CHECK_FCT( fd_msg_avp_new ( dictobj_acct, 0, &avp ) );
+ val.u32 = a->appid;
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ if (a->vndid != 0) {
+ struct avp * avp2 = NULL;
+ CHECK_FCT( fd_msg_avp_new ( dictobj, 0, &avp2 ) );
+ CHECK_FCT( fd_msg_avp_add( avp2, MSG_BRW_LAST_CHILD, avp ) );
+ avp = avp2;
+ CHECK_FCT( fd_msg_avp_new ( dictobj_vid, 0, &avp2 ) );
+ val.u32 = a->vndid;
+ CHECK_FCT( fd_msg_avp_setvalue( avp2, &val ) );
+ CHECK_FCT( fd_msg_avp_add( avp, MSG_BRW_LAST_CHILD, avp2 ) );
+ }
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+ }
+ }
+
+ /* do not forget the relay application */
+ if (! fd_g_config->cnf_flags.no_fwd) {
+ CHECK_FCT( fd_msg_avp_new ( dictobj_auth, 0, &avp ) );
+ val.u32 = AI_RELAY;
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+ }
+ }
+
+ /* Add the list of supported vendors */
+ {
+ uint32_t * array = fd_dict_get_vendorid_list(fd_g_config->cnf_dict);
+ if (array) {
+ int i = 0;
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Supported-Vendor-Id", &dictobj, ENOENT ) );
+
+ while (array[i] != 0) {
+ CHECK_FCT( fd_msg_avp_new ( dictobj, 0, &avp ) );
+ val.u32 = array[i];
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+ i++;
+ }
+
+ free(array);
+ }
+ }
+
+ return 0;
+}
+
+/* Remove any information saved from a previous CER/CEA exchange */
+static void cleanup_remote_CE_info(struct fd_peer * peer)
+{
+ /* free linked information */
+ free(peer->p_hdr.info.runtime.pir_realm);
+ free(peer->p_hdr.info.runtime.pir_prodname);
+ while (!FD_IS_LIST_EMPTY(&peer->p_hdr.info.runtime.pir_apps)) {
+ struct fd_list * li = peer->p_hdr.info.runtime.pir_apps.next;
+ fd_list_unlink(li);
+ free(li);
+ }
+ /* note: pir_cert_list needs not be freed (belongs to gnutls) */
+
+ /* cleanup the area */
+ memset(&peer->p_hdr.info.runtime, 0, sizeof(peer->p_hdr.info.runtime));
+
+ /* reinit the list */
+ fd_list_init(&peer->p_hdr.info.runtime.pir_apps, peer);
+
+ /* Remove previously advertised endpoints */
+ fd_ep_clearflags( &peer->p_hdr.info.pi_endpoints, EP_FL_ADV );
+}
+
+/* Extract information sent by the remote peer and save it in our peer structure */
+static int save_remote_CE_info(struct msg * msg, struct fd_peer * peer, struct fd_pei * error, uint32_t *rc)
+{
+ struct avp * avp = NULL;
+
+ cleanup_remote_CE_info(peer);
+
+ CHECK_FCT( fd_msg_browse( msg, MSG_BRW_FIRST_CHILD, &avp, NULL) );
+
+ /* Loop on all AVPs and save what we are interrested into */
+ while (avp) {
+ struct avp_hdr * hdr;
+
+ CHECK_FCT( fd_msg_avp_hdr( avp, &hdr ) );
+
+ if (hdr->avp_flags & AVP_FLAG_VENDOR) {
+ /* Ignore all vendor-specific AVPs in CER/CEA because we don't support any currently */
+ LOG_A("Ignored a vendor-specific AVP in CER / CEA");
+ goto next;
+ }
+
+ switch (hdr->avp_code) {
+ case AC_RESULT_CODE: /* Result-Code */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ if (rc)
+ *rc = hdr->avp_value->u32;
+ break;
+
+ case AC_ORIGIN_HOST: /* Origin-Host */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ /* We check that the value matches what we know, otherwise disconnect the peer */
+ if (fd_os_almostcasesrch(hdr->avp_value->os.data, hdr->avp_value->os.len,
+ peer->p_hdr.info.pi_diamid, peer->p_hdr.info.pi_diamidlen, NULL)) {
+ TRACE_DEBUG(INFO, "Received a message with Origin-Host set to '%.*s' while expecting '%s'",
+ (int)hdr->avp_value->os.len, hdr->avp_value->os.data, peer->p_hdr.info.pi_diamid);
+ error->pei_errcode = "DIAMETER_AVP_NOT_ALLOWED";
+ error->pei_message = "Your Origin-Host value does not match my configuration.";
+ error->pei_avp = avp;
+ return EINVAL;
+ }
+
+ break;
+
+ case AC_ORIGIN_REALM: /* Origin-Realm */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ /* In case of multiple AVPs */
+ if (peer->p_hdr.info.runtime.pir_realm) {
+ TRACE_DEBUG(INFO, "Multiple instances of the Origin-Realm AVP");
+ error->pei_errcode = "DIAMETER_AVP_OCCURS_TOO_MANY_TIMES";
+ error->pei_message = "I found several Origin-Realm AVPs";
+ error->pei_avp = avp;
+ return EINVAL;
+ }
+
+ /* If the octet string contains a \0 */
+ if (!fd_os_is_valid_DiameterIdentity(hdr->avp_value->os.data, hdr->avp_value->os.len)) {
+ error->pei_errcode = "DIAMETER_INVALID_AVP_VALUE";
+ error->pei_message = "Your Origin-Realm contains invalid characters.";
+ error->pei_avp = avp;
+ return EINVAL;
+ }
+
+ /* Save the value */
+ CHECK_MALLOC( peer->p_hdr.info.runtime.pir_realm = os0dup( hdr->avp_value->os.data, hdr->avp_value->os.len ) );
+ peer->p_hdr.info.runtime.pir_realmlen = hdr->avp_value->os.len;
+ break;
+
+ case AC_HOST_IP_ADDRESS: /* Host-IP-Address */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+ {
+ sSS ss;
+
+ /* Get the sockaddr value */
+ memset(&ss, 0, sizeof(ss));
+ CHECK_FCT_DO( fd_msg_avp_value_interpret( avp, &ss),
+ {
+ /* in case of error, assume the AVP value was wrong */
+ error->pei_errcode = "DIAMETER_INVALID_AVP_VALUE";
+ error->pei_avp = avp;
+ return EINVAL;
+ } );
+
+ /* Save this endpoint in the list as advertized */
+ CHECK_FCT( fd_ep_add_merge( &peer->p_hdr.info.pi_endpoints, (sSA *)&ss, sizeof(sSS), EP_FL_ADV ) );
+ }
+ break;
+
+ case AC_VENDOR_ID: /* Vendor-Id */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ /* In case of multiple AVPs */
+ if (peer->p_hdr.info.runtime.pir_vendorid) {
+ TRACE_DEBUG(INFO, "Multiple instances of the Vendor-Id AVP");
+ error->pei_errcode = "DIAMETER_AVP_OCCURS_TOO_MANY_TIMES";
+ error->pei_message = "I found several Vendor-Id AVPs";
+ error->pei_avp = avp;
+ return EINVAL;
+ }
+
+ peer->p_hdr.info.runtime.pir_vendorid = hdr->avp_value->u32;
+ break;
+
+ case AC_PRODUCT_NAME: /* Product-Name */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ /* In case of multiple AVPs */
+ if (peer->p_hdr.info.runtime.pir_prodname) {
+ TRACE_DEBUG(INFO, "Multiple instances of the Product-Name AVP");
+ error->pei_errcode = "DIAMETER_AVP_OCCURS_TOO_MANY_TIMES";
+ error->pei_message = "I found several Product-Name AVPs";
+ error->pei_avp = avp;
+ return EINVAL;
+ }
+
+ CHECK_MALLOC( peer->p_hdr.info.runtime.pir_prodname = calloc( hdr->avp_value->os.len + 1, 1 ) );
+ memcpy(peer->p_hdr.info.runtime.pir_prodname, hdr->avp_value->os.data, hdr->avp_value->os.len);
+ break;
+
+ case AC_ORIGIN_STATE_ID: /* Origin-State-Id */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ /* In case of multiple AVPs */
+ if (peer->p_hdr.info.runtime.pir_orstate) {
+ TRACE_DEBUG(INFO, "Multiple instances of the Origin-State-Id AVP");
+ error->pei_errcode = "DIAMETER_AVP_OCCURS_TOO_MANY_TIMES";
+ error->pei_message = "I found several Origin-State-Id AVPs";
+ error->pei_avp = avp;
+ return EINVAL;
+ }
+
+ peer->p_hdr.info.runtime.pir_orstate = hdr->avp_value->u32;
+ break;
+
+ case AC_SUPPORTED_VENDOR_ID: /* Supported-Vendor-Id */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ TRACE_DEBUG(FULL, "'%s' claims support for a subset of vendor %d features.", peer->p_hdr.info.pi_diamid, hdr->avp_value->u32);
+ /* not that it makes a difference for us...
+ -- if an application actually needs this info, we could save it somewhere.
+ */
+ break;
+
+ case AC_VENDOR_SPECIFIC_APPLICATION_ID: /* Vendor-Specific-Application-Id (grouped)*/
+ {
+ struct avp * inavp = NULL;
+ vendor_id_t vid = 0;
+ application_id_t auth_aid = 0;
+ application_id_t acct_aid = 0;
+ int invalid=0;
+
+ /* get the first child AVP */
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_FIRST_CHILD, &inavp, NULL) );
+
+ while (inavp) {
+ struct avp_hdr * inhdr;
+ CHECK_FCT( fd_msg_avp_hdr( inavp, &inhdr ) );
+
+ if (inhdr->avp_flags & AVP_FLAG_VENDOR) {
+ LOG_A("Ignored a vendor AVP inside Vendor-Specific-Application-Id AVP");
+ goto innext;
+ }
+
+ if (inhdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto innext;
+ }
+ switch (inhdr->avp_code) {
+ case AC_VENDOR_ID: /* Vendor-Id */
+#ifndef WORKAROUND_ACCEPT_INVALID_VSAI
+ if (vid != 0)
+ invalid++; /* We already had one such AVP. This is invalid according to RFC6733 but not RFC3588 (but there is an erratum) */
+#endif /* WORKAROUND_ACCEPT_INVALID_VSAI */
+ vid = inhdr->avp_value->u32;
+ break;
+ case AC_AUTH_APPLICATION_ID: /* Auth-Application-Id */
+ if (auth_aid != 0)
+ invalid++; /* We already had one such AVP */
+#ifndef WORKAROUND_ACCEPT_INVALID_VSAI
+ if (acct_aid != 0)
+ invalid++; /* Only 1 *-Application-Id AVP is allowed */
+#endif /* WORKAROUND_ACCEPT_INVALID_VSAI */
+ auth_aid = inhdr->avp_value->u32;
+ break;
+ case AC_ACCT_APPLICATION_ID: /* Acct-Application-Id */
+ if (acct_aid != 0)
+ invalid++; /* We already had one such AVP */
+#ifndef WORKAROUND_ACCEPT_INVALID_VSAI
+ if (auth_aid != 0)
+ invalid++; /* Only 1 *-Application-Id AVP is allowed */
+#endif /* WORKAROUND_ACCEPT_INVALID_VSAI */
+ acct_aid = inhdr->avp_value->u32;
+ break;
+ /* ignore other AVPs */
+ }
+
+ if (invalid) {
+ TRACE_DEBUG(FULL, "Invalid Vendor-Specific-Application-Id AVP received");
+ error->pei_errcode = "DIAMETER_INVALID_AVP_VALUE";
+ error->pei_avp = avp;
+ return EINVAL;
+ }
+
+ innext:
+ /* Go to next in AVP */
+ CHECK_FCT( fd_msg_browse(inavp, MSG_BRW_NEXT, &inavp, NULL) );
+ }
+
+ /* Add entry in the list */
+ if (auth_aid) {
+ CHECK_FCT( fd_app_merge(&peer->p_hdr.info.runtime.pir_apps, auth_aid, vid, 1, 0) );
+ }
+ if (acct_aid) {
+ CHECK_FCT( fd_app_merge(&peer->p_hdr.info.runtime.pir_apps, acct_aid, vid, 0, 1) );
+ }
+ }
+ break;
+
+ case AC_AUTH_APPLICATION_ID: /* Auth-Application-Id */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ if (hdr->avp_value->u32 == AI_RELAY) {
+ peer->p_hdr.info.runtime.pir_relay = 1;
+ } else {
+ CHECK_FCT( fd_app_merge(&peer->p_hdr.info.runtime.pir_apps, hdr->avp_value->u32, 0, 1, 0) );
+ }
+ break;
+
+ case AC_ACCT_APPLICATION_ID: /* Acct-Application-Id */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ if (hdr->avp_value->u32 == AI_RELAY) {
+ /* Not clear if the relay application can be inside this AVP... */
+ peer->p_hdr.info.runtime.pir_relay = 1;
+ } else {
+ CHECK_FCT( fd_app_merge(&peer->p_hdr.info.runtime.pir_apps, hdr->avp_value->u32, 0, 0, 1) );
+ }
+ break;
+
+ case AC_FIRMWARE_REVISION: /* Firmware-Revision */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+
+ peer->p_hdr.info.runtime.pir_firmrev = hdr->avp_value->u32;
+ break;
+
+ case AC_INBAND_SECURITY_ID: /* Inband-Security-Id */
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an AVP (code %x) with unset value in CER/CEA", hdr->avp_code);
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ goto next;
+ }
+ if (hdr->avp_value->u32 >= 32 ) {
+ error->pei_errcode = "DIAMETER_INVALID_AVP_VALUE";
+ error->pei_message = "I don't support this Inband-Security-Id value (yet).";
+ error->pei_avp = avp;
+ return EINVAL;
+ }
+ peer->p_hdr.info.runtime.pir_isi |= (1 << hdr->avp_value->u32);
+ break;
+ }
+
+next:
+ /* Go to next AVP */
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL) );
+ }
+
+ return 0;
+}
+
+/* Create a CER message for sending */
+static int create_CER(struct fd_peer * peer, struct cnxctx * cnx, struct msg ** cer)
+{
+ int isi_tls = 0;
+ int isi_none = 0;
+
+ /* Find CER dictionary object and create an instance */
+ CHECK_FCT( fd_msg_new ( fd_dict_cmd_CER, MSGFL_ALLOC_ETEID, cer ) );
+
+ /* Do we need Inband-Security-Id AVPs ? If we're already using TLS, we don't... */
+ if (!fd_cnx_getTLS(cnx)) {
+ isi_none = peer->p_hdr.info.config.pic_flags.sec & PI_SEC_NONE; /* we add it even if the peer does not use the old mechanism, it is impossible to distinguish */
+
+ if (peer->p_hdr.info.config.pic_flags.sec & PI_SEC_TLS_OLD) {
+ if (fd_g_config->cnf_sec_data.tls_disabled) {
+ LOG_N("TLS disabled locally, so Inband-Security-Id (TLS) not included for peer %s", peer->p_hdr.info.pi_diamid);
+ } else {
+ isi_tls = 1;
+ }
+ }
+ }
+
+ /* Add the information about the local peer */
+ CHECK_FCT( add_CE_info(*cer, cnx, isi_tls, isi_none) );
+
+ /* Done! */
+ return 0;
+}
+
+
+/* Continue with the initiator side */
+static int to_waitcea(struct fd_peer * peer, struct cnxctx * cnx)
+{
+ /* We sent a CER on the connection, set the event queue so that we receive the CEA */
+ CHECK_FCT( set_peer_cnx(peer, &cnx) );
+
+ /* Change state and reset the timer */
+ CHECK_FCT( fd_psm_change_state(peer, STATE_WAITCEA) );
+ fd_psm_next_timeout(peer, 0, CEA_TIMEOUT);
+
+ return 0;
+}
+
+/* Reject an incoming connection attempt */
+static void receiver_reject(struct cnxctx ** recv_cnx, struct msg ** cer, struct fd_pei * error)
+{
+ struct msg_hdr * hdr = NULL;
+
+ /* Create and send the CEA with appropriate error code */
+ CHECK_FCT_DO( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, cer, MSGFL_ANSW_ERROR ), goto destroy );
+ CHECK_FCT_DO( fd_msg_rescode_set(*cer, error->pei_errcode, error->pei_message, error->pei_avp, 0 ), goto destroy );
+ CHECK_FCT_DO( fd_msg_hdr( *cer, &hdr ), goto destroy );
+ if (hdr->msg_flags & CMD_FLAG_ERROR) {
+ /* Generic error format, just add the origin AVPs */
+ CHECK_FCT_DO( fd_msg_add_origin ( *cer, 1 ), goto destroy );
+ } else {
+ /* Add other AVPs to be compliant with the ABNF */
+ CHECK_FCT_DO( add_CE_info(*cer, *recv_cnx, 0, 0), goto destroy );
+ }
+ CHECK_FCT_DO( fd_out_send(cer, *recv_cnx, NULL, 0), goto destroy );
+
+ if (error->pei_avp_free) {
+ fd_msg_free(error->pei_avp);
+ }
+
+ /* And now destroy this connection */
+destroy:
+ fd_cnx_destroy(*recv_cnx);
+ *recv_cnx = NULL;
+ if (*cer) {
+ fd_hook_call(HOOK_MESSAGE_DROPPED, *cer, NULL, "An error occurred while rejecting this CER.", fd_msg_pmdl_get(*cer));
+ fd_msg_free(*cer);
+ *cer = NULL;
+ }
+}
+
+/* We have established a new connection to the remote peer, send CER and eventually process the election */
+int fd_p_ce_handle_newcnx(struct fd_peer * peer, struct cnxctx * initiator)
+{
+ struct msg * cer = NULL;
+
+ /* Send CER on the new connection */
+ CHECK_FCT( create_CER(peer, initiator, &cer) );
+ CHECK_FCT( fd_out_send(&cer, initiator, peer, 0) );
+
+ /* Are we doing an election ? */
+ if (fd_peer_getstate(peer) == STATE_WAITCNXACK_ELEC) {
+ if (election_result(peer)) {
+ /* Close initiator connection */
+ fd_cnx_destroy(initiator);
+
+ LOG_D("%s: Election lost on outgoing connection, closing and answering CEA on incoming connection.", peer->p_hdr.info.pi_diamid);
+
+ /* Process with the receiver side */
+ CHECK_FCT( fd_p_ce_process_receiver(peer) );
+
+ } else {
+ struct fd_pei pei;
+ memset(&pei, 0, sizeof(pei));
+ pei.pei_errcode = "ELECTION_LOST";
+ pei.pei_message = "Please answer my CER instead, you won the election.";
+ LOG_D("%s: Election lost on incoming connection, closing and waiting for CEA on outgoing connection.", peer->p_hdr.info.pi_diamid);
+
+ /* Answer an ELECTION LOST to the receiver side */
+ receiver_reject(&peer->p_receiver, &peer->p_cer, &pei);
+ CHECK_FCT( to_waitcea(peer, initiator) );
+ }
+ } else {
+ /* No election (yet) */
+ CHECK_FCT( to_waitcea(peer, initiator) );
+ }
+
+ return 0;
+}
+
+/* We have received a Capabilities Exchange message on the peer connection */
+int fd_p_ce_msgrcv(struct msg ** msg, int req, struct fd_peer * peer)
+{
+ uint32_t rc = 0;
+ int st;
+ struct fd_pei pei;
+
+ TRACE_ENTRY("%p %p", msg, peer);
+ CHECK_PARAMS( msg && *msg && CHECK_PEER(peer) );
+
+ /* The only valid situation where we are called is in WAITCEA and we receive a CEA (we may have won an election) */
+
+ /* Note : to implement Capabilities Update, we would need to change here */
+
+ /* If it is a CER, just reply an error */
+ if (req) {
+ /* Create the error message */
+ CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, msg, MSGFL_ANSW_ERROR ) );
+
+ /* Set the error code */
+ CHECK_FCT( fd_msg_rescode_set(*msg, "DIAMETER_UNABLE_TO_COMPLY", "No CER allowed in current state", NULL, 1 ) );
+
+ /* msg now contains an answer message to send back */
+ CHECK_FCT_DO( fd_out_send(msg, NULL, peer, 0), /* In case of error the message has already been dumped */ );
+ }
+
+ /* If the state is not WAITCEA, just discard the message */
+ if (req || ((st = fd_peer_getstate(peer)) != STATE_WAITCEA)) {
+ if (*msg) {
+ /* In such case, just discard the message */
+ char buf[128];
+ snprintf(buf, sizeof(buf), "Received while peer state machine was in state %s.", STATE_STR(st));
+ fd_hook_call(HOOK_MESSAGE_DROPPED, *msg, peer, buf, fd_msg_pmdl_get(*msg));
+
+ CHECK_FCT_DO( fd_msg_free(*msg), /* continue */);
+ *msg = NULL;
+ }
+
+ return 0;
+ }
+
+ memset(&pei, 0, sizeof(pei));
+
+ /* Save info from the CEA into the peer */
+ CHECK_FCT_DO( save_remote_CE_info(*msg, peer, &pei, &rc),
+ {
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, *msg, peer, "An error occurred while processing incoming CEA.", NULL);
+ goto cleanup;
+ } );
+
+ /* Check the Result-Code */
+ switch (rc) {
+ case ER_DIAMETER_SUCCESS:
+ /* Log success */
+ fd_hook_call(HOOK_PEER_CONNECT_SUCCESS, *msg, peer, NULL, NULL);
+
+ /* Dispose of the message, we don't need it anymore */
+ CHECK_FCT_DO( fd_msg_free(*msg), /* continue */ );
+ *msg = NULL;
+
+ /* No problem, we can continue */
+ break;
+
+ case ER_DIAMETER_TOO_BUSY:
+ /* Retry later */
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, *msg, peer, "Remote peer is too busy", NULL);
+ fd_psm_cleanup(peer, 0);
+ fd_psm_next_timeout(peer, 0, 300);
+ return 0;
+
+ case ER_ELECTION_LOST:
+ /* Ok, just wait for a little while for the CER to be processed on the other connection. */
+ TRACE_DEBUG(FULL, "Peer %s replied a CEA with Result-Code AVP ELECTION_LOST, waiting for events.", peer->p_hdr.info.pi_diamid);
+ return 0;
+
+ default:
+ /* In any other case, we abort all attempts to connect to this peer */
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, *msg, peer, "CEA with unexpected error code", NULL);
+ return EINVAL;
+ }
+
+
+ /* Handshake if needed, start clear otherwise */
+ if ( ! fd_cnx_getTLS(peer->p_cnxctx) ) {
+ int todo = peer->p_hdr.info.config.pic_flags.sec & peer->p_hdr.info.runtime.pir_isi ;
+ /* Special case: if the peer did not send a ISI AVP */
+ if (peer->p_hdr.info.runtime.pir_isi == 0)
+ todo = peer->p_hdr.info.config.pic_flags.sec;
+
+ if (todo == PI_SEC_NONE) {
+ /* Ok for clear connection */
+ TRACE_DEBUG(INFO, "No TLS protection negotiated with peer '%s'.", peer->p_hdr.info.pi_diamid);
+ CHECK_FCT( fd_cnx_start_clear(peer->p_cnxctx, 1) );
+
+ } else if (fd_g_config->cnf_sec_data.tls_disabled) {
+ LOG_E("Clear connection with remote peer '%s' is not (explicitly) allowed, and TLS is disabled. Giving up...", peer->p_hdr.info.pi_diamid);
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "TLS is disabled and peer is not configured for IPsec", NULL);
+ goto cleanup;
+
+ } else {
+ fd_psm_change_state(peer, STATE_OPEN_HANDSHAKE);
+ CHECK_FCT_DO( fd_cnx_handshake(peer->p_cnxctx, GNUTLS_CLIENT, ALGO_HANDSHAKE_3436, peer->p_hdr.info.config.pic_priority, NULL),
+ {
+ /* Handshake failed ... */
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "TLS handshake failed after CER/CEA exchange", NULL);
+ goto cleanup;
+ } );
+
+ /* Retrieve the credentials */
+ CHECK_FCT( fd_cnx_getcred(peer->p_cnxctx, &peer->p_hdr.info.runtime.pir_cert_list, &peer->p_hdr.info.runtime.pir_cert_list_size) );
+ }
+ }
+
+ /* Move to next state */
+ if (peer->p_flags.pf_cnx_pb) {
+ fd_psm_change_state(peer, STATE_REOPEN );
+ CHECK_FCT( fd_p_dw_reopen(peer) );
+ } else {
+ fd_psm_change_state(peer, STATE_OPEN );
+ fd_psm_next_timeout(peer, 1, peer->p_hdr.info.config.pic_twtimer ?: fd_g_config->cnf_timer_tw);
+ }
+
+ return 0;
+
+cleanup:
+ fd_p_ce_clear_cnx(peer, NULL);
+
+ /* Send the error to the peer */
+ CHECK_FCT( fd_event_send(peer->p_events, FDEVP_CNX_ERROR, 0, NULL) );
+
+ return 0;
+}
+
+/* Handle the receiver side to go to OPEN or OPEN_NEW state (any election is resolved) */
+int fd_p_ce_process_receiver(struct fd_peer * peer)
+{
+ struct fd_pei pei;
+ struct msg * msg = NULL;
+ int isi = 0;
+ int fatal = 0;
+ int tls_sync=0;
+
+ TRACE_ENTRY("%p", peer);
+
+ CHECK_FCT_DO( set_peer_cnx(peer, &peer->p_receiver),
+ {
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "Error saving the incoming connection in the peer structure", NULL);
+ return __ret__;
+ } );
+ msg = peer->p_cer;
+ peer->p_cer = NULL;
+
+ memset(&pei, 0, sizeof(pei));
+
+ /* Parse the content of the received CER */
+ CHECK_FCT_DO( save_remote_CE_info(msg, peer, &pei, NULL), goto error_abort );
+
+ /* Validate the realm if needed */
+ if (peer->p_hdr.info.config.pic_realm) {
+ size_t len = strlen(peer->p_hdr.info.config.pic_realm);
+ if (fd_os_almostcasesrch(peer->p_hdr.info.config.pic_realm, len, peer->p_hdr.info.runtime.pir_realm, peer->p_hdr.info.runtime.pir_realmlen, NULL)) {
+ TRACE_DEBUG(INFO, "Rejected CER from peer '%s', realm mismatch with configured value (returning DIAMETER_UNKNOWN_PEER).", peer->p_hdr.info.pi_diamid);
+ pei.pei_errcode = "DIAMETER_UNKNOWN_PEER"; /* maybe AVP_NOT_ALLOWED would be better fit? */
+ goto error_abort;
+ }
+ }
+
+ /* Save the credentials if handshake already occurred */
+ if ( fd_cnx_getTLS(peer->p_cnxctx) ) {
+ CHECK_FCT( fd_cnx_getcred(peer->p_cnxctx, &peer->p_hdr.info.runtime.pir_cert_list, &peer->p_hdr.info.runtime.pir_cert_list_size) );
+ }
+
+ /* Validate the peer if needed */
+ if (peer->p_flags.pf_responder) {
+ int res = fd_peer_validate( peer );
+ if (res < 0) {
+ TRACE_DEBUG(INFO, "Rejected CER from peer '%s', validation failed (returning DIAMETER_UNKNOWN_PEER).", peer->p_hdr.info.pi_diamid);
+ pei.pei_errcode = "DIAMETER_UNKNOWN_PEER";
+ goto error_abort;
+ }
+ CHECK_FCT( res );
+ }
+
+ /* Check if we have common applications */
+ if ( fd_g_config->cnf_flags.no_fwd && (! peer->p_hdr.info.runtime.pir_relay) ) {
+ int got_common;
+ CHECK_FCT( fd_app_check_common( &fd_g_config->cnf_apps, &peer->p_hdr.info.runtime.pir_apps, &got_common) );
+ if (!got_common) {
+ TRACE_DEBUG(INFO, "No common application with peer '%s', sending DIAMETER_NO_COMMON_APPLICATION", peer->p_hdr.info.pi_diamid);
+ pei.pei_errcode = "DIAMETER_NO_COMMON_APPLICATION";
+ fatal = 1;
+ goto error_abort;
+ }
+ }
+
+ /* Do we agree on ISI ? */
+ if ( ! fd_cnx_getTLS(peer->p_cnxctx) ) {
+
+ /* In case of responder, the validate callback must have set the config.pic_flags.sec value already */
+
+ /* First case: we are not using old mechanism: ISI are deprecated, we ignore it. */
+ if ( ! (peer->p_hdr.info.config.pic_flags.sec & PI_SEC_TLS_OLD)) {
+ /* Just check then that the peer configuration allows for IPsec protection */
+ if (peer->p_hdr.info.config.pic_flags.sec & PI_SEC_NONE) {
+ isi = PI_SEC_NONE;
+ } else {
+ /* otherwise, we should have already been protected. Reject */
+ TRACE_DEBUG(INFO, "Non TLS-protected CER/CEA exchanges are not allowed with this peer, rejecting.");
+ }
+ } else {
+ /* The old mechanism is allowed with this peer. Now, look into the ISI AVP values */
+
+ /* In case no ISI was present anyway: */
+ if (!peer->p_hdr.info.runtime.pir_isi) {
+ TRACE_DEBUG(INFO, "Inband-Security-Id AVP is missing in received CER.");
+ if (peer->p_hdr.info.config.pic_flags.sec & PI_SEC_NONE) {
+ isi = PI_SEC_NONE;
+ TRACE_DEBUG(INFO, "IPsec protection allowed by configuration, allowing this mechanism to be used.");
+ } else {
+ /* otherwise, we should have already been protected. Reject */
+ TRACE_DEBUG(INFO, "Rejecting the peer connection (please allow IPsec here or configure TLS in the remote peer).");
+ }
+ } else {
+ /* OK, the remote peer did send the ISI AVP. */
+ if ((peer->p_hdr.info.config.pic_flags.sec & PI_SEC_NONE) && (peer->p_hdr.info.runtime.pir_isi & PI_SEC_NONE)) {
+ /* We have allowed IPsec */
+ isi = PI_SEC_NONE;
+ } else if (fd_g_config->cnf_sec_data.tls_disabled) {
+ /* We can agree on TLS */
+ TRACE_DEBUG(INFO, "Remote peer is not allowed for IPsec and TLS is disabled.");;
+ } else if (peer->p_hdr.info.runtime.pir_isi & PI_SEC_TLS_OLD) {
+ /* We can agree on TLS */
+ isi = PI_SEC_TLS_OLD;
+ } else {
+ TRACE_DEBUG(INFO, "Remote peer requested IPsec protection, but local configuration forbids it.");
+ }
+ }
+ }
+
+ /* If we did not find an agreement */
+ if (!isi) {
+ TRACE_DEBUG(INFO, "No common security mechanism with '%s', sending DIAMETER_NO_COMMON_SECURITY", peer->p_hdr.info.pi_diamid);
+ pei.pei_errcode = "DIAMETER_NO_COMMON_SECURITY";
+ fatal = 1;
+ goto error_abort;
+ }
+
+ /* Do not send the ISI IPsec if we are using the new mechanism */
+ if ((isi == PI_SEC_NONE) && (! (peer->p_hdr.info.config.pic_flags.sec & PI_SEC_TLS_OLD)))
+ isi = 0;
+ } else if (peer->p_hdr.info.runtime.pir_isi & PI_SEC_TLS_OLD) {
+ /* Seem some weird peers are sending the Inband-Security-Id AVP on the secure port... No harm */
+ isi = PI_SEC_TLS_OLD;
+ }
+
+ /* Reply a CEA */
+ CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, &msg, 0 ) );
+ CHECK_FCT( fd_msg_rescode_set(msg, "DIAMETER_SUCCESS", NULL, NULL, 0 ) );
+ CHECK_FCT( add_CE_info(msg, peer->p_cnxctx, isi & PI_SEC_TLS_OLD, isi & PI_SEC_NONE) );
+
+ /* The connection is complete, but we may still need TLS handshake */
+ fd_hook_call(HOOK_PEER_CONNECT_SUCCESS, msg, peer, NULL, NULL);
+
+ CHECK_FCT( fd_out_send(&msg, peer->p_cnxctx, peer, 0 ) );
+
+ /* Handshake if needed */
+ if (isi & PI_SEC_TLS_OLD) {
+ fd_psm_change_state(peer, STATE_OPEN_HANDSHAKE);
+ CHECK_FCT_DO( fd_cnx_handshake(peer->p_cnxctx, GNUTLS_SERVER, ALGO_HANDSHAKE_3436, peer->p_hdr.info.config.pic_priority, NULL),
+ {
+ /* Handshake failed ... */
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "TLS handshake failed after CER/CEA exchange", NULL);
+ goto cleanup;
+ } );
+
+ /* Retrieve the credentials */
+ CHECK_FCT_DO( fd_cnx_getcred(peer->p_cnxctx, &peer->p_hdr.info.runtime.pir_cert_list, &peer->p_hdr.info.runtime.pir_cert_list_size),
+ {
+ /* Error ... */
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "Unable to retrieve remote credentials after TLS handshake", NULL);
+ goto cleanup;
+ } );
+
+
+ /* Call second validation callback if needed */
+ if (peer->p_cb2) {
+ TRACE_DEBUG(FULL, "Calling second validation callback for %s", peer->p_hdr.info.pi_diamid);
+ CHECK_FCT_DO( (*peer->p_cb2)( &peer->p_hdr.info ),
+ {
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "Validation callback rejected the peer after handshake", NULL);
+ CHECK_FCT( fd_psm_terminate( peer, "DO_NOT_WANT_TO_TALK_TO_YOU" ) );
+ return 0;
+ } );
+ }
+ tls_sync = 1;
+ } else {
+ if ( ! fd_cnx_getTLS(peer->p_cnxctx) ) {
+ TRACE_DEBUG(INFO, "No TLS protection negotiated with peer '%s'.", peer->p_hdr.info.pi_diamid);
+ CHECK_FCT( fd_cnx_start_clear(peer->p_cnxctx, 1) );
+ }
+ }
+
+ /* Move to OPEN or REOPEN state */
+ if (peer->p_flags.pf_cnx_pb) {
+ fd_psm_change_state(peer, STATE_REOPEN );
+ CHECK_FCT( fd_p_dw_reopen(peer) );
+ } else {
+ if ((!tls_sync) && (fd_cnx_is_unordered_delivery_supported(peer->p_cnxctx))) {
+ fd_psm_change_state(peer, STATE_OPEN_NEW );
+ /* send DWR */
+ CHECK_FCT( fd_p_dw_timeout(peer) );
+ } else {
+
+ fd_psm_change_state(peer, STATE_OPEN );
+ fd_psm_next_timeout(peer, 1, peer->p_hdr.info.config.pic_twtimer ?: fd_g_config->cnf_timer_tw);
+ }
+ }
+
+ return 0;
+
+error_abort:
+ if (pei.pei_errcode) {
+ /* Send the error */
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, msg, peer, pei.pei_message ?: pei.pei_errcode, NULL);
+ receiver_reject(&peer->p_cnxctx, &msg, &pei);
+ } else {
+ char buf[1024];
+ snprintf(buf, sizeof(buf), "Unexpected error occurred while processing incoming connection from '%s'.", peer->p_hdr.info.pi_diamid);
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, msg, peer, buf, NULL);
+ }
+
+cleanup:
+ if (msg) {
+ fd_msg_free(msg);
+ }
+ fd_p_ce_clear_cnx(peer, NULL);
+
+ /* Send the error to the peer */
+ CHECK_FCT( fd_event_send(peer->p_events, fatal ? FDEVP_TERMINATE : FDEVP_CNX_ERROR, 0, NULL) );
+
+ return 0;
+}
+
+/* We have received a CER on a new connection for this peer */
+int fd_p_ce_handle_newCER(struct msg ** msg, struct fd_peer * peer, struct cnxctx ** cnx, int valid)
+{
+ struct fd_pei pei;
+ int cur_state = fd_peer_getstate(peer);
+ memset(&pei, 0, sizeof(pei));
+
+ switch (cur_state) {
+ case STATE_CLOSED:
+ peer->p_receiver = *cnx;
+ *cnx = NULL;
+ peer->p_cer = *msg;
+ *msg = NULL;
+ CHECK_FCT( fd_p_ce_process_receiver(peer) );
+ break;
+
+ case STATE_WAITCNXACK:
+ /* Save the parameters in the peer, move to STATE_WAITCNXACK_ELEC */
+ peer->p_receiver = *cnx;
+ *cnx = NULL;
+ peer->p_cer = *msg;
+ *msg = NULL;
+ CHECK_FCT( fd_psm_change_state(peer, STATE_WAITCNXACK_ELEC) );
+ break;
+
+ case STATE_WAITCEA:
+ if (election_result(peer)) {
+
+ /* Close initiator connection (was already set as principal) */
+ LOG_D("%s: Election lost on outgoing connection, closing and answering CEA on incoming connection.", peer->p_hdr.info.pi_diamid);
+ fd_p_ce_clear_cnx(peer, NULL);
+
+ /* and go on with the receiver side */
+ peer->p_receiver = *cnx;
+ *cnx = NULL;
+ peer->p_cer = *msg;
+ *msg = NULL;
+ CHECK_FCT( fd_p_ce_process_receiver(peer) );
+
+ } else {
+
+ /* Answer an ELECTION LOST to the receiver side and continue */
+ pei.pei_errcode = "ELECTION_LOST";
+ pei.pei_message = "Please answer my CER instead, you won the election.";
+ LOG_D("%s: Election lost on incoming connection, closing and waiting for CEA on outgoing connection.", peer->p_hdr.info.pi_diamid);
+ receiver_reject(cnx, msg, &pei);
+ }
+ break;
+
+ default:
+ pei.pei_errcode = "DIAMETER_UNABLE_TO_COMPLY"; /* INVALID COMMAND? in case of Capabilities-Updates? */
+ pei.pei_message = "Invalid state to receive a new connection attempt.";
+ LOG_E("%s: Rejecting new connection attempt while our state machine is in state '%s'", peer->p_hdr.info.pi_diamid, STATE_STR(cur_state));
+ receiver_reject(cnx, msg, &pei);
+ }
+
+ return 0;
+}
diff --git a/libfdcore/p_cnx.c b/libfdcore/p_cnx.c
new file mode 100644
index 0000000..9dc2a03
--- /dev/null
+++ b/libfdcore/p_cnx.c
@@ -0,0 +1,357 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+
+/* TODO: change the behavior to handle properly forced ordering at beginning & end of OPEN state */
+
+/* This file contains code used by a peer state machine to initiate a connection to remote peer */
+
+struct next_conn {
+ struct fd_list chain;
+ int proto; /* Protocol of the next attempt */
+ union {
+ sSS ss; /* The address, only for TCP */
+ sSA4 sin;
+ sSA6 sin6;
+ };
+ uint16_t port; /* The port, for SCTP (included in ss for TCP) */
+ int dotls; /* Handshake TLS after connection ? */
+};
+
+static __inline__ void failed_connection_attempt(struct fd_peer * peer)
+{
+ /* Simply remove the first item in the list if not empty */
+ if (! FD_IS_LIST_EMPTY(&peer->p_connparams) ) {
+ struct fd_list * li = peer->p_connparams.next;
+ fd_list_unlink(li);
+ free(li);
+ }
+}
+
+static void empty_connection_list(struct fd_peer * peer)
+{
+ /* Remove all items */
+ while (!FD_IS_LIST_EMPTY(&peer->p_connparams)) {
+ failed_connection_attempt(peer);
+ }
+}
+
+static int prepare_connection_list(struct fd_peer * peer)
+{
+ struct fd_list * li, *last_prio;
+ struct next_conn * new;
+
+ uint16_t port_no; /* network order */
+ int dotls_immediate;
+ int count = 0;
+
+ TRACE_ENTRY("%p", peer);
+
+ /* Resolve peer address(es) if needed */
+ if (FD_IS_LIST_EMPTY(&peer->p_hdr.info.pi_endpoints)) {
+ struct addrinfo hints, *ai, *aip;
+ int ret;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG;
+ ret = getaddrinfo(peer->p_hdr.info.pi_diamid, NULL, &hints, &ai);
+ if (ret) {
+ TRACE_DEBUG(INFO, "Unable to resolve address for peer '%s' (%s), aborting", peer->p_hdr.info.pi_diamid, gai_strerror(ret));
+ if (ret != EAI_AGAIN)
+ fd_psm_terminate( peer, NULL );
+ return 0;
+ }
+
+ for (aip = ai; aip != NULL; aip = aip->ai_next) {
+ CHECK_FCT( fd_ep_add_merge( &peer->p_hdr.info.pi_endpoints, aip->ai_addr, aip->ai_addrlen, EP_FL_DISC ) );
+ }
+ freeaddrinfo(ai);
+ }
+
+ /* Remove addresses from unwanted family */
+ if (peer->p_hdr.info.config.pic_flags.pro3) {
+ CHECK_FCT( fd_ep_filter_family(
+ &peer->p_hdr.info.pi_endpoints,
+ (peer->p_hdr.info.config.pic_flags.pro3 == PI_P3_IP) ?
+ AF_INET
+ : AF_INET6));
+ }
+ if (fd_g_config->cnf_flags.no_ip4) {
+ CHECK_FCT( fd_ep_filter_family(
+ &peer->p_hdr.info.pi_endpoints,
+ AF_INET6));
+ }
+ if (fd_g_config->cnf_flags.no_ip6) {
+ CHECK_FCT( fd_ep_filter_family(
+ &peer->p_hdr.info.pi_endpoints,
+ AF_INET));
+ }
+
+ /* We don't use the alternate addresses that were sent by the remote peer */
+ CHECK_FCT( fd_ep_clearflags(&peer->p_hdr.info.pi_endpoints, EP_FL_ADV) );
+
+
+ /* Now check we have at least one address to attempt */
+ if (FD_IS_LIST_EMPTY(&peer->p_hdr.info.pi_endpoints)) {
+ TRACE_DEBUG(INFO, "No address %savailable to connect to peer '%s', aborting",
+ peer->p_hdr.info.config.pic_flags.pro3 ? "in the configured family " : "", peer->p_hdr.info.pi_diamid);
+ fd_psm_terminate( peer, NULL );
+ return 0;
+ }
+
+ /* Check if we are able to communicate with this peer */
+ if (fd_g_config->cnf_sec_data.tls_disabled && ( peer->p_hdr.info.config.pic_flags.sec != PI_SEC_NONE)) {
+ LOG_E("Peer '%s' not configured for No_TLS and TLS is locally disabled; giving up connection attempts",
+ peer->p_hdr.info.pi_diamid);
+ fd_psm_terminate( peer, NULL );
+ return 0;
+ }
+
+ /* Cleanup any previous list */
+ empty_connection_list(peer);
+
+ /* Prepare the parameters */
+ if ((peer->p_hdr.info.config.pic_flags.sec != PI_SEC_DEFAULT) || (fd_g_config->cnf_flags.tls_alg)) {
+ dotls_immediate = 0;
+ port_no = htons(peer->p_hdr.info.config.pic_port ?: DIAMETER_PORT);
+ } else {
+ dotls_immediate = 1;
+ port_no = htons(peer->p_hdr.info.config.pic_port ?: DIAMETER_SECURE_PORT);
+ }
+
+ last_prio = &peer->p_connparams;
+
+ /* Create TCP parameters unless specified otherwise */
+ if ((!fd_g_config->cnf_flags.no_tcp) && (peer->p_hdr.info.config.pic_flags.pro4 != PI_P4_SCTP)) {
+ for (li = peer->p_hdr.info.pi_endpoints.next; li != &peer->p_hdr.info.pi_endpoints; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *)li;
+
+ CHECK_MALLOC( new = malloc(sizeof(struct next_conn)) );
+ memset(new, 0, sizeof(struct next_conn));
+ fd_list_init(&new->chain, new);
+
+ new->proto = IPPROTO_TCP;
+
+ memcpy( &new->ss, &ep->ss, sizeof(sSS) );
+ switch (new->ss.ss_family) {
+ case AF_INET:
+ new->sin.sin_port = port_no;
+ break;
+ case AF_INET6:
+ new->sin6.sin6_port = port_no;
+ break;
+ default:
+ free(new);
+ continue; /* Move to the next endpoint */
+ }
+
+ new->dotls = dotls_immediate;
+
+ /* Add the new entry to the appropriate position (conf and disc go first) */
+ if (ep->flags & (EP_FL_CONF | EP_FL_DISC)) {
+ fd_list_insert_after(last_prio, &new->chain);
+ last_prio = &new->chain;
+ } else {
+ fd_list_insert_before(&peer->p_connparams, &new->chain);
+ }
+ count++;
+ }
+ }
+
+ /* Now, add the SCTP entry, if not disabled */
+#ifndef DISABLE_SCTP
+ if ((!fd_g_config->cnf_flags.no_sctp) && (peer->p_hdr.info.config.pic_flags.pro4 != PI_P4_TCP)) {
+ struct next_conn * new;
+
+ CHECK_MALLOC( new = malloc(sizeof(struct next_conn)) );
+ memset(new, 0, sizeof(struct next_conn));
+ fd_list_init(&new->chain, new);
+
+ new->proto = IPPROTO_SCTP;
+ new->port = ntohs(port_no); /* back to host byte order... */
+ new->dotls = dotls_immediate;
+
+ /* Add the new entry to the appropriate position (depending on preferences) */
+ if ((fd_g_config->cnf_flags.pr_tcp) || (peer->p_hdr.info.config.pic_flags.alg == PI_ALGPREF_TCP)) {
+ fd_list_insert_after(last_prio, &new->chain);
+ } else {
+ fd_list_insert_after(&peer->p_connparams, &new->chain); /* very first position */
+ }
+ count++;
+ }
+#endif /* DISABLE_SCTP */
+
+ LOG_D("Prepared %d sets of connection parameters to peer %s", count, peer->p_hdr.info.pi_diamid);
+
+ return 0;
+}
+
+
+/* The thread that attempts the connection */
+static void * connect_thr(void * arg)
+{
+ struct fd_peer * peer = arg;
+ struct cnxctx * cnx = NULL;
+ struct next_conn * nc = NULL;
+ int rebuilt = 0;
+ int fatal_error=0;
+
+ TRACE_ENTRY("%p", arg);
+ CHECK_PARAMS_DO( CHECK_PEER(peer), return NULL );
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "ConnTo:%s", peer->p_hdr.info.pi_diamid);
+ fd_log_threadname ( buf );
+ }
+
+ do {
+ /* Rebuild the list if needed, if it is empty -- but at most once */
+ if (FD_IS_LIST_EMPTY(&peer->p_connparams)) {
+ if (! rebuilt) {
+ CHECK_FCT_DO( fatal_error = prepare_connection_list(peer), goto out );
+ rebuilt ++;
+ }
+ if (FD_IS_LIST_EMPTY(&peer->p_connparams)) {
+ /* We encountered an error or we have looped over all the addresses of the peer. */
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "All connection attempts failed, will retry later", NULL);
+
+ CHECK_FCT_DO( fatal_error = fd_event_send(peer->p_events, FDEVP_CNX_FAILED, 0, NULL), goto out );
+ return NULL;
+ }
+ }
+
+ /* Attempt connection to the first entry */
+ nc = (struct next_conn *)(peer->p_connparams.next);
+
+ switch (nc->proto) {
+ case IPPROTO_TCP:
+ cnx = fd_cnx_cli_connect_tcp((sSA *)&nc->ss, sSAlen(&nc->ss));
+ break;
+#ifndef DISABLE_SCTP
+ case IPPROTO_SCTP:
+ cnx = fd_cnx_cli_connect_sctp((peer->p_hdr.info.config.pic_flags.pro3 == PI_P3_IP) ? 1 : fd_g_config->cnf_flags.no_ip6,
+ nc->port, &peer->p_hdr.info.pi_endpoints);
+ break;
+#endif /* DISABLE_SCTP */
+ }
+
+ if (cnx)
+ break;
+
+ /* Pop these parameters and continue */
+ failed_connection_attempt(peer);
+
+ pthread_testcancel();
+
+ } while (!cnx); /* and until cancellation or all addresses attempted without success */
+
+ /* Now, we have an established connection in cnx */
+
+ pthread_cleanup_push((void *)fd_cnx_destroy, cnx);
+
+ /* Set the hostname in the connection, so that handshake verifies the remote identity */
+ fd_cnx_sethostname(cnx,peer->p_hdr.info.pi_diamid);
+
+ /* Handshake if needed (secure port) */
+ if (nc->dotls) {
+ CHECK_FCT_DO( fd_cnx_handshake(cnx, GNUTLS_CLIENT,
+ ALGO_HANDSHAKE_3436,
+ peer->p_hdr.info.config.pic_priority, NULL),
+ {
+ /* Handshake failed ... */
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "TLS Handshake failed", NULL);
+ fd_cnx_destroy(cnx);
+ empty_connection_list(peer);
+ fd_ep_filter(&peer->p_hdr.info.pi_endpoints, EP_FL_CONF);
+ goto out_pop;
+ } );
+ LOG_A("%s: TLS handshake successful.", peer->p_hdr.info.pi_diamid);
+ } else {
+ /* Prepare to receive the next message */
+ CHECK_FCT_DO( fatal_error = fd_cnx_start_clear(cnx, 0), goto out_pop );
+ }
+
+ /* Upon success, generate FDEVP_CNX_ESTABLISHED */
+ CHECK_FCT_DO( fatal_error = fd_event_send(peer->p_events, FDEVP_CNX_ESTABLISHED, 0, cnx), );
+out_pop:
+ ;
+ pthread_cleanup_pop(0);
+
+out:
+
+ if (fatal_error) {
+
+ /* Cleanup the connection */
+ if (cnx)
+ fd_cnx_destroy(cnx);
+
+ /* Generate a termination event */
+ CHECK_FCT_DO(fd_core_shutdown(), );
+ }
+
+ return NULL;
+}
+
+
+/* Initiate a connection attempt to a remote peer */
+int fd_p_cnx_init(struct fd_peer * peer)
+{
+ TRACE_ENTRY("%p", peer);
+
+ /* Start the connect thread */
+ CHECK_FCT( pthread_create(&peer->p_ini_thr, NULL, connect_thr, peer) );
+ return 0;
+}
+
+/* Cancel a connection attempt */
+void fd_p_cnx_abort(struct fd_peer * peer, int cleanup_all)
+{
+ TRACE_ENTRY("%p %d", peer, cleanup_all);
+ CHECK_PARAMS_DO( CHECK_PEER(peer), return );
+
+ if (peer->p_ini_thr != (pthread_t)NULL) {
+ CHECK_FCT_DO( fd_thr_term(&peer->p_ini_thr), /* continue */);
+ failed_connection_attempt(peer);
+ }
+
+ if (cleanup_all) {
+ empty_connection_list(peer);
+ }
+}
+
diff --git a/libfdcore/p_dp.c b/libfdcore/p_dp.c
new file mode 100644
index 0000000..2cd39d5
--- /dev/null
+++ b/libfdcore/p_dp.c
@@ -0,0 +1,207 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/* This file contains code to handle Disconnect Peer messages (DPR and DPA) */
+
+/* Delay to use before next reconnect attempt */
+int fd_p_dp_newdelay(struct fd_peer * peer)
+{
+ int delay = peer->p_hdr.info.config.pic_tctimer ?: fd_g_config->cnf_timer_tc;
+
+ switch (peer->p_hdr.info.runtime.pir_lastDC) {
+ case ACV_DC_REBOOTING:
+ default:
+ /* We use TcTimer to attempt reconnection */
+ break;
+ case ACV_DC_BUSY:
+ /* No need to hammer the overloaded peer */
+ delay *= 10;
+ break;
+ case ACV_DC_NOT_FRIEND:
+ /* He does not want to speak to us... let's retry a *lot* later maybe */
+ delay *= 200;
+ break;
+ }
+ return delay;
+}
+
+/* Handle a received message */
+int fd_p_dp_handle(struct msg ** msg, int req, struct fd_peer * peer)
+{
+ long to_receive, to_send;
+ TRACE_ENTRY("%p %d %p", msg, req, peer);
+
+ if (req) {
+ /* We received a DPR, save the Disconnect-Cause and go to CLOSING_GRACE or terminate the connection */
+ struct avp * dc;
+
+ CHECK_FCT( fd_msg_search_avp ( *msg, fd_dict_avp_DC, &dc ));
+ if (dc) {
+ struct avp_hdr * hdr;
+ CHECK_FCT( fd_msg_avp_hdr( dc, &hdr ) );
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("BUG: Unset value in Disconnect-Cause in DPR");
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ }
+
+ /* save the cause */
+ peer->p_hdr.info.runtime.pir_lastDC = hdr->avp_value->u32;
+ }
+ if (TRACE_BOOL(INFO)) {
+ if (dc) {
+ struct dict_object * dictobj;
+ struct dict_enumval_request er;
+ memset(&er, 0, sizeof(er));
+
+ /* prepare the request */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_TYPE, TYPE_OF_AVP, fd_dict_avp_DC, &er.type_obj, ENOENT ) );
+ er.search.enum_value.u32 = peer->p_hdr.info.runtime.pir_lastDC;
+
+ /* Search the enum value */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_ENUMVAL, ENUMVAL_BY_STRUCT, &er, &dictobj, 0 ) );
+ if (dictobj) {
+ CHECK_FCT( fd_dict_getval( dictobj, &er.search ) );
+ TRACE_DEBUG(INFO, "Peer '%s' sent a DPR with cause: %s", peer->p_hdr.info.pi_diamid, er.search.enum_name);
+ } else {
+ TRACE_DEBUG(INFO, "Peer '%s' sent a DPR with unknown cause: %u", peer->p_hdr.info.pi_diamid, peer->p_hdr.info.runtime.pir_lastDC);
+ }
+ } else {
+ TRACE_DEBUG(INFO, "Peer '%s' sent a DPR without Disconnect-Cause AVP", peer->p_hdr.info.pi_diamid);
+ }
+ }
+
+ /* Now reply with a DPA */
+ CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, msg, 0 ) );
+ CHECK_FCT( fd_msg_rescode_set( *msg, "DIAMETER_SUCCESS", NULL, NULL, 1 ) );
+
+ /* Do we have pending exchanges with this peer? */
+ CHECK_FCT( fd_peer_get_load_pending(&peer->p_hdr, &to_receive, &to_send) );
+
+ if ((to_receive == 0) && (to_send == 0)) {
+ /* No pending exchange, move to CLOSING directly */
+ CHECK_FCT( fd_psm_change_state(peer, STATE_CLOSING) );
+
+ /* Now send the DPA */
+ CHECK_FCT( fd_out_send( msg, NULL, peer, 0) );
+
+ /* and move to CLOSED */
+ fd_psm_cleanup(peer, 0);
+
+ /* Reset the timer for next connection attempt -- we'll retry sooner or later depending on the disconnection cause */
+ fd_psm_next_timeout(peer, 1, fd_p_dp_newdelay(peer));
+ } else {
+ /* We have pending exchanges, we move to CLOSING_GRACE which allows exchanges of answers but
+ not new requests */
+ CHECK_FCT( fd_psm_change_state(peer, STATE_CLOSING_GRACE) );
+ fd_psm_next_timeout(peer, 0, GRACE_TIMEOUT);
+
+ /* Now send the DPA */
+ CHECK_FCT( fd_out_send( msg, NULL, peer, 0) );
+ }
+ } else {
+ /* We received a DPA */
+ int curstate = fd_peer_getstate(peer);
+ if (curstate != STATE_CLOSING_GRACE) {
+ TRACE_DEBUG(INFO, "Ignoring DPA received in state %s", STATE_STR(curstate));
+ }
+
+ /* In theory, we should control the Result-Code AVP. But since we will not go back to OPEN state here anyway, let's skip it */
+
+ /* TODO("Control Result-Code in the DPA") */
+ CHECK_FCT_DO( fd_msg_free( *msg ), /* continue */ );
+ *msg = NULL;
+
+ /* Do we still have pending exchanges with this peer? */
+ CHECK_FCT( fd_peer_get_load_pending(&peer->p_hdr, &to_receive, &to_send) );
+ if ((to_receive != 0) || (to_send != 0)) {
+ TRACE_DEBUG(INFO, "Received DPA but pending load: [%ld, %ld], giving grace delay before closing", to_receive, to_send);
+ fd_psm_next_timeout(peer, 0, GRACE_TIMEOUT);
+ peer->p_flags.pf_localterm = 1;
+ } else {
+ /* otherwise, go to CLOSING state, the psm will handle terminating the connection */
+ CHECK_FCT( fd_psm_change_state(peer, STATE_CLOSING) );
+ }
+ }
+
+ return 0;
+}
+
+/* Start disconnection of a peer: send DPR */
+int fd_p_dp_initiate(struct fd_peer * peer, char * reason)
+{
+ struct msg * msg = NULL;
+ struct dict_object * dictobj = NULL;
+ struct avp * avp = NULL;
+ struct dict_enumval_request er;
+ union avp_value val;
+
+ TRACE_ENTRY("%p %p", peer, reason);
+
+ /* Create a new DWR instance */
+ CHECK_FCT( fd_msg_new ( fd_dict_cmd_DPR, MSGFL_ALLOC_ETEID, &msg ) );
+
+ /* Add the Origin information */
+ CHECK_FCT( fd_msg_add_origin ( msg, 0 ) );
+
+ /* Add the Disconnect-Cause */
+ CHECK_FCT( fd_msg_avp_new ( fd_dict_avp_DC, 0, &avp ) );
+
+ /* Search the value in the dictionary */
+ memset(&er, 0, sizeof(er));
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_TYPE, TYPE_OF_AVP, fd_dict_avp_DC, &er.type_obj, ENOENT ) );
+ er.search.enum_name = reason ?: "REBOOTING";
+ CHECK_FCT_DO( fd_dict_search( fd_g_config->cnf_dict, DICT_ENUMVAL, ENUMVAL_BY_STRUCT, &er, &dictobj, ENOENT ), { ASSERT(0); /* internal error: unknown reason */ } );
+ CHECK_FCT( fd_dict_getval( dictobj, &er.search ) );
+
+ /* Set the value in the AVP */
+ val.u32 = er.search.enum_value.u32;
+ CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
+ CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
+
+ /* Save the value also in the peer */
+ peer->p_hdr.info.runtime.pir_lastDC = val.u32;
+
+ /* Update the peer state and timer */
+ CHECK_FCT( fd_psm_change_state(peer, STATE_CLOSING_GRACE) );
+ fd_psm_next_timeout(peer, 0, DPR_TIMEOUT);
+
+ /* Now send the DPR message */
+ CHECK_FCT_DO( fd_out_send(&msg, NULL, peer, 0), /* ignore since we are on timeout anyway */ );
+
+ return 0;
+}
diff --git a/libfdcore/p_dw.c b/libfdcore/p_dw.c
new file mode 100644
index 0000000..dc87f8d
--- /dev/null
+++ b/libfdcore/p_dw.c
@@ -0,0 +1,177 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/* This file contains code to handle Device Watchdog messages (DWR and DWA) */
+
+/* Check the value of Origin-State-Id is consistent in a DWR or DWA -- we return an error otherwise */
+static int check_state_id(struct msg * msg, struct fd_peer * peer)
+{
+ struct avp * osi;
+
+ /* Check if the request contains the Origin-State-Id */
+ CHECK_FCT( fd_msg_search_avp ( msg, fd_dict_avp_OSI, &osi ) );
+ if (osi) {
+ /* Check the value is consistent with the saved one */
+ struct avp_hdr * hdr;
+ CHECK_FCT( fd_msg_avp_hdr( osi, &hdr ) );
+ if (hdr->avp_value == NULL) {
+ /* This is a sanity check */
+ LOG_F("Ignored an Origin-State-Id AVP with unset value in DWR/DWA");
+ ASSERT(0); /* To check if this really happens, and understand why... */
+ }
+
+ if (! peer->p_hdr.info.runtime.pir_orstate) {
+ /* It was not already received in CER/CEA, save it now */
+ peer->p_hdr.info.runtime.pir_orstate = hdr->avp_value->u32;
+ }
+
+ if (peer->p_hdr.info.runtime.pir_orstate != hdr->avp_value->u32) {
+ TRACE_DEBUG(INFO, "Received a new Origin-State-Id from peer '%s'! (%x -> %x); resetting the connection.",
+ peer->p_hdr.info.pi_diamid,
+ peer->p_hdr.info.runtime.pir_orstate,
+ hdr->avp_value->u32 );
+ return EINVAL;
+ }
+ }
+ return 0;
+}
+
+/* Create and send a DWR */
+static int send_DWR(struct fd_peer * peer)
+{
+ struct msg * msg = NULL;
+
+ /* Create a new DWR instance */
+ CHECK_FCT( fd_msg_new ( fd_dict_cmd_DWR, MSGFL_ALLOC_ETEID, &msg ) );
+
+ /* Add the content of the message (only the origin) */
+ CHECK_FCT( fd_msg_add_origin ( msg, 1 ) );
+
+ /* Now send this message */
+ CHECK_FCT( fd_out_send(&msg, NULL, peer, 0) );
+
+ /* And mark the pending DW */
+ peer->p_flags.pf_dw_pending = 1;
+
+ return 0;
+}
+
+/* Handle an incoming message */
+int fd_p_dw_handle(struct msg ** msg, int req, struct fd_peer * peer)
+{
+ int reset_tmr = 0;
+
+ TRACE_ENTRY("%p %d %p", msg, req, peer);
+
+ /* Check the value of OSI for information */
+ CHECK_FCT( check_state_id(*msg, peer) );
+
+ if (req) {
+ /* If we receive a DWR, send back a DWA */
+ CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, msg, 0 ) );
+ CHECK_FCT( fd_msg_rescode_set( *msg, "DIAMETER_SUCCESS", NULL, NULL, 0 ) );
+ CHECK_FCT( fd_msg_add_origin ( *msg, 1 ) );
+ CHECK_FCT( fd_out_send( msg, peer->p_cnxctx, peer, 0) );
+
+ } else {
+ /* Discard the DWA */
+ CHECK_FCT_DO( fd_msg_free(*msg), /* continue */ );
+ *msg = NULL;
+
+ /* And clear the pending DW flag */
+ peer->p_flags.pf_dw_pending = 0;
+ }
+
+ /* Now update timeout */
+ if (req) {
+ /* Update timeout only if we did not already send a DWR ourselves */
+ reset_tmr = !peer->p_flags.pf_dw_pending;
+ } else {
+ /* Reset the timer */
+ reset_tmr = 1;
+ }
+ if (reset_tmr) {
+ fd_psm_next_timeout(peer, 1, peer->p_hdr.info.config.pic_twtimer ?: fd_g_config->cnf_timer_tw);
+ }
+
+ /* If we are in REOPEN state, increment the counter */
+ if (fd_peer_getstate(peer) == STATE_REOPEN) {
+ peer->p_flags.pf_reopen_cnt += 1;
+
+ if (peer->p_flags.pf_reopen_cnt) {
+ /* Send a new DWR */
+ CHECK_FCT( send_DWR(peer) );
+ } else {
+ /* Move to OPEN state */
+ CHECK_FCT( fd_psm_change_state(peer, STATE_OPEN) );
+ }
+ }
+
+ return 0;
+}
+
+/* Handle a timeout in the PSM (OPEN or REOPEN state only) */
+int fd_p_dw_timeout(struct fd_peer * peer)
+{
+ TRACE_ENTRY("%p", peer);
+
+ if (peer->p_flags.pf_dw_pending) {
+ /* We have sent a DWR and received no answer during TwTimer */
+ CHECK_FCT( fd_psm_change_state(peer, STATE_SUSPECT) );
+ fd_psm_next_timeout(peer, 0, 2 * (peer->p_hdr.info.config.pic_twtimer ?: fd_g_config->cnf_timer_tw) );
+ } else {
+ /* The timeout has expired, send a DWR */
+ CHECK_FCT( send_DWR(peer) );
+ fd_psm_next_timeout(peer, 0, peer->p_hdr.info.config.pic_twtimer ?: fd_g_config->cnf_timer_tw );
+ }
+
+ return 0;
+}
+
+/* Handle DW exchanges after the peer has come alive again */
+int fd_p_dw_reopen(struct fd_peer * peer)
+{
+ TRACE_ENTRY("%p", peer);
+
+ peer->p_flags.pf_reopen_cnt = 1;
+ peer->p_flags.pf_cnx_pb = 0;
+ CHECK_FCT( send_DWR(peer) );
+
+ return 0;
+}
+
+
diff --git a/libfdcore/p_expiry.c b/libfdcore/p_expiry.c
new file mode 100644
index 0000000..c51f9d8
--- /dev/null
+++ b/libfdcore/p_expiry.c
@@ -0,0 +1,205 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/* Delay for garbage collection of expired peers, in seconds */
+#define GC_TIME 120
+
+static pthread_t exp_thr = (pthread_t)NULL;
+static pthread_t gc_thr = (pthread_t)NULL;
+static struct fd_list exp_list = FD_LIST_INITIALIZER( exp_list );
+static pthread_cond_t exp_cnd = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t exp_mtx = PTHREAD_MUTEX_INITIALIZER;
+
+static void * gc_th_fct(void * arg)
+{
+ fd_log_threadname ( "Peers/garb. col." );
+ TRACE_ENTRY( "%p", arg );
+
+ do {
+ struct fd_list * li, purge = FD_LIST_INITIALIZER(purge);
+
+ sleep(GC_TIME); /* sleep is a cancellation point */
+
+ /* Now check in the peers list if any peer can be deleted */
+ CHECK_FCT_DO( pthread_rwlock_wrlock(&fd_g_peers_rw), goto error );
+
+ for (li = fd_g_peers.next; li != &fd_g_peers; li = li->next) {
+ struct fd_peer * peer = (struct fd_peer *)li->o;
+
+ if (fd_peer_getstate(peer) != STATE_ZOMBIE)
+ continue;
+
+ if (peer->p_hdr.info.config.pic_flags.persist == PI_PRST_ALWAYS)
+ continue; /* This peer was not supposed to terminate, keep it in the list for debug */
+
+ /* Ok, the peer was expired, let's remove it */
+ li = li->prev; /* to avoid breaking the loop */
+ fd_list_unlink(&peer->p_hdr.chain);
+ fd_list_insert_before(&purge, &peer->p_hdr.chain);
+ }
+
+ CHECK_FCT_DO( pthread_rwlock_unlock(&fd_g_peers_rw), goto error );
+
+ /* Now delete peers that are in the purge list */
+ while (!FD_IS_LIST_EMPTY(&purge)) {
+ struct fd_peer * peer = (struct fd_peer *)(purge.next->o);
+ fd_list_unlink(&peer->p_hdr.chain);
+ TRACE_DEBUG(INFO, "Garbage Collect: delete zombie peer '%s'", peer->p_hdr.info.pi_diamid);
+ CHECK_FCT_DO( fd_peer_free(&peer), /* Continue... what else to do ? */ );
+ }
+ } while (1);
+
+error:
+ TRACE_DEBUG(INFO, "An error occurred in peers module! GC thread is terminating...");
+ ASSERT(0);
+ CHECK_FCT_DO(fd_core_shutdown(), );
+ return NULL;
+}
+
+
+static void * exp_th_fct(void * arg)
+{
+ fd_log_threadname ( "Peers/expire" );
+ TRACE_ENTRY( "%p", arg );
+
+ CHECK_POSIX_DO( pthread_mutex_lock(&exp_mtx), { ASSERT(0); } );
+ pthread_cleanup_push( fd_cleanup_mutex, &exp_mtx );
+
+ do {
+ struct timespec now;
+ struct fd_peer * first;
+
+ /* Check if there are expiring peers available */
+ if (FD_IS_LIST_EMPTY(&exp_list)) {
+ /* Just wait for a change or cancelation */
+ CHECK_POSIX_DO( pthread_cond_wait( &exp_cnd, &exp_mtx ), { ASSERT(0); } );
+ /* Restart the loop on wakeup */
+ continue;
+ }
+
+ /* Get the pointer to the peer that expires first */
+ first = (struct fd_peer *)(exp_list.next->o);
+ ASSERT( CHECK_PEER(first) );
+
+ /* Get the current time */
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &now), { ASSERT(0); } );
+
+ /* If first peer is not expired, we just wait until it happens */
+ if ( TS_IS_INFERIOR( &now, &first->p_exp_timer ) ) {
+
+ CHECK_POSIX_DO2( pthread_cond_timedwait( &exp_cnd, &exp_mtx, &first->p_exp_timer ),
+ ETIMEDOUT, /* ETIMEDOUT is a normal return value, continue */,
+ /* on other error, */ { ASSERT(0); } );
+
+ /* on wakeup, loop */
+ continue;
+ }
+
+ /* Now, the first peer in the list is expired; signal it */
+ fd_list_unlink( &first->p_expiry );
+ CHECK_FCT_DO( fd_event_send(first->p_events, FDEVP_TERMINATE, 0, "DO_NOT_WANT_TO_TALK_TO_YOU"), break );
+
+ } while (1);
+
+ pthread_cleanup_pop( 1 );
+
+ TRACE_DEBUG(INFO, "An error occurred in peers module! Expiry thread is terminating...");
+ CHECK_FCT_DO(fd_core_shutdown(), );
+ return NULL;
+}
+
+/* Initialize peers expiry mechanism */
+int fd_p_expi_init(void)
+{
+ TRACE_ENTRY();
+ CHECK_FCT( pthread_create( &exp_thr, NULL, exp_th_fct, NULL ) );
+ CHECK_FCT( pthread_create( &gc_thr, NULL, gc_th_fct, NULL ) );
+ return 0;
+}
+
+/* Finish peers expiry mechanism */
+int fd_p_expi_fini(void)
+{
+ CHECK_FCT_DO( fd_thr_term(&exp_thr), );
+ CHECK_POSIX( pthread_mutex_lock(&exp_mtx) );
+ while (!FD_IS_LIST_EMPTY(&exp_list)) {
+ struct fd_peer * peer = (struct fd_peer *)(exp_list.next->o);
+ fd_list_unlink(&peer->p_expiry );
+ }
+ CHECK_POSIX( pthread_mutex_unlock(&exp_mtx) );
+
+ CHECK_FCT_DO( fd_thr_term(&gc_thr), );
+ return 0;
+}
+
+/* Add / requeue a peer in the expiry list */
+int fd_p_expi_update(struct fd_peer * peer )
+{
+ TRACE_ENTRY("%p", peer);
+ CHECK_PARAMS( CHECK_PEER(peer) );
+
+ CHECK_POSIX( pthread_mutex_lock(&exp_mtx) );
+
+ fd_list_unlink(&peer->p_expiry );
+
+ /* if peer expires */
+ if (peer->p_hdr.info.config.pic_flags.exp) {
+ struct fd_list * li;
+
+ /* update the p_exp_timer value */
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &peer->p_exp_timer), { ASSERT(0); } );
+ peer->p_exp_timer.tv_sec += peer->p_hdr.info.config.pic_lft;
+
+ /* add to the expiry list in appropriate position (probably around the end) */
+ for (li = exp_list.prev; li != &exp_list; li = li->prev) {
+ struct fd_peer * p = (struct fd_peer *)(li->o);
+ if (TS_IS_INFERIOR( &p->p_exp_timer, &peer->p_exp_timer ) )
+ break;
+ }
+
+ fd_list_insert_after(li, &peer->p_expiry);
+
+ /* signal the expiry thread if we added in first position */
+ if (li == &exp_list) {
+ CHECK_POSIX( pthread_cond_signal(&exp_cnd) );
+ }
+ }
+
+ CHECK_POSIX( pthread_mutex_unlock(&exp_mtx) );
+ return 0;
+}
+
diff --git a/libfdcore/p_out.c b/libfdcore/p_out.c
new file mode 100644
index 0000000..b410f11
--- /dev/null
+++ b/libfdcore/p_out.c
@@ -0,0 +1,237 @@
+/*********************************************************************************************************
+* 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"
+
+/* Alloc a new hbh for requests, bufferize the message and send on the connection, save in sentreq if provided */
+static int do_send(struct msg ** msg, struct cnxctx * cnx, uint32_t * hbh, struct fd_peer * peer)
+{
+ struct msg_hdr * hdr;
+ int msg_is_a_req;
+ uint8_t * buf;
+ size_t sz;
+ int ret;
+ uint32_t bkp_hbh = 0;
+ struct msg *cpy_for_logs_only;
+
+ TRACE_ENTRY("%p %p %p %p", msg, cnx, hbh, peer);
+
+ /* Retrieve the message header */
+ CHECK_FCT( fd_msg_hdr(*msg, &hdr) );
+
+ msg_is_a_req = (hdr->msg_flags & CMD_FLAG_REQUEST);
+ if (msg_is_a_req) {
+ CHECK_PARAMS(hbh && peer);
+ /* Alloc the hop-by-hop id and increment the value for next message */
+ bkp_hbh = hdr->msg_hbhid;
+ hdr->msg_hbhid = *hbh;
+ *hbh = hdr->msg_hbhid + 1;
+ }
+
+ /* Create the message buffer */
+ CHECK_FCT(fd_msg_bufferize( *msg, &buf, &sz ));
+ pthread_cleanup_push( free, buf );
+
+ cpy_for_logs_only = *msg;
+
+ /* Save a request before sending so that there is no race condition with the answer */
+ if (msg_is_a_req) {
+ CHECK_FCT_DO( ret = fd_p_sr_store(&peer->p_sr, msg, &hdr->msg_hbhid, bkp_hbh), goto out );
+ }
+
+ /* Log the message */
+ fd_hook_call(HOOK_MESSAGE_SENT, cpy_for_logs_only, peer, NULL, fd_msg_pmdl_get(cpy_for_logs_only));
+
+ pthread_cleanup_push((void *)fd_msg_free, *msg /* might be NULL, no problem */);
+
+ /* Send the message */
+ CHECK_FCT_DO( ret = fd_cnx_send(cnx, buf, sz), );
+
+ pthread_cleanup_pop(0);
+
+out:
+ ;
+ pthread_cleanup_pop(1);
+
+ if (ret)
+ return ret;
+
+ /* Free remaining messages (i.e. answers) */
+ if (*msg) {
+ CHECK_FCT( fd_msg_free(*msg) );
+ *msg = NULL;
+ }
+
+ return 0;
+}
+
+/* The code of the "out" thread */
+static void * out_thr(void * arg)
+{
+ struct fd_peer * peer = arg;
+ int stop = 0;
+ struct msg * msg;
+ ASSERT( CHECK_PEER(peer) );
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "OUT/%s", peer->p_hdr.info.pi_diamid);
+ fd_log_threadname ( buf );
+ }
+
+ /* Loop until cancelation */
+ while (!stop) {
+ int ret;
+
+ /* Retrieve next message to send */
+ CHECK_FCT_DO( fd_fifo_get(peer->p_tosend, &msg), goto error );
+
+ /* Send the message, log any error */
+ CHECK_FCT_DO( ret = do_send(&msg, peer->p_cnxctx, &peer->p_hbh, peer),
+ {
+ if (msg) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Error while sending this message: %s", strerror(ret));
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, NULL, buf, fd_msg_pmdl_get(msg));
+ fd_msg_free(msg);
+ }
+ stop = 1;
+ } );
+
+ }
+
+ /* If we're here it means there was an error on the socket. We need to continue to purge the fifo & until we are canceled */
+ CHECK_FCT_DO( fd_event_send(peer->p_events, FDEVP_CNX_ERROR, 0, NULL), /* What do we do if it fails? */ );
+
+ /* Requeue all routable messages in the global "out" queue, until we are canceled once the PSM deals with the CNX_ERROR sent above */
+ while ( fd_fifo_get(peer->p_tosend, &msg) == 0 ) {
+ if (fd_msg_is_routable(msg)) {
+ CHECK_FCT_DO(fd_fifo_post_noblock(peer->p_tofailover, (void *)&msg),
+ {
+ /* fallback: destroy the message */
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, NULL, "Internal error: unable to requeue this message during failover process", fd_msg_pmdl_get(msg));
+ CHECK_FCT_DO(fd_msg_free(msg), /* What can we do more? */)
+ } );
+ } else {
+ /* Just free it */
+ /* fd_hook_call(HOOK_MESSAGE_DROPPED, m, NULL, "Non-routable message freed during handover", fd_msg_pmdl_get(m)); */
+ CHECK_FCT_DO(fd_msg_free(msg), /* What can we do more? */)
+ }
+ }
+
+error:
+ /* It is not really a connection error, but the effect is the same, we are not able to send anymore message */
+ CHECK_FCT_DO( fd_event_send(peer->p_events, FDEVP_CNX_ERROR, 0, NULL), /* What do we do if it fails? */ );
+ return NULL;
+}
+
+/* Wrapper to sending a message either by out thread (peer in OPEN state) or directly; cnx or peer must be provided. Flags are valid only for direct sending, not through thread (unused) */
+int fd_out_send(struct msg ** msg, struct cnxctx * cnx, struct fd_peer * peer, int update_reqin_cnt)
+{
+ struct msg_hdr * hdr;
+
+ TRACE_ENTRY("%p %p %p", msg, cnx, peer);
+ CHECK_PARAMS( msg && *msg && (cnx || (peer && peer->p_cnxctx)));
+
+ fd_hook_call(HOOK_MESSAGE_SENDING, *msg, peer, NULL, fd_msg_pmdl_get(*msg));
+
+ if (update_reqin_cnt && peer) {
+ CHECK_FCT( fd_msg_hdr(*msg, &hdr) );
+ if (!(hdr->msg_flags & CMD_FLAG_REQUEST)) {
+ /* Update the count of pending answers to send */
+ CHECK_POSIX( pthread_mutex_lock(&peer->p_state_mtx) );
+ peer->p_reqin_count--;
+ CHECK_POSIX( pthread_mutex_unlock(&peer->p_state_mtx) );
+ }
+ }
+
+ if (fd_peer_getstate(peer) == STATE_OPEN) {
+ /* Normal case: just queue for the out thread to pick it up */
+ CHECK_FCT( fd_fifo_post(peer->p_tosend, msg) );
+
+ } else {
+ int ret;
+ uint32_t *hbh = NULL;
+
+ /* In other cases, the thread is not running, so we handle the sending directly */
+ if (peer)
+ hbh = &peer->p_hbh;
+
+ if (!cnx)
+ cnx = peer->p_cnxctx;
+
+ /* Do send the message */
+ CHECK_FCT_DO( ret = do_send(msg, cnx, hbh, peer),
+ {
+ if (msg) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Error while sending this message: %s", strerror(ret));
+ fd_hook_call(HOOK_MESSAGE_DROPPED, *msg, NULL, buf, fd_msg_pmdl_get(*msg));
+ fd_msg_free(*msg);
+ *msg = NULL;
+ }
+ } );
+ }
+
+ return 0;
+}
+
+/* Start the "out" thread that picks messages in p_tosend and send them on p_cnxctx */
+int fd_out_start(struct fd_peer * peer)
+{
+ TRACE_ENTRY("%p", peer);
+ CHECK_PARAMS( CHECK_PEER(peer) && (peer->p_outthr == (pthread_t)NULL) );
+
+ CHECK_POSIX( pthread_create(&peer->p_outthr, NULL, out_thr, peer) );
+
+ CHECK_FCT( fd_cnx_unordered_delivery(peer->p_cnxctx, 1) );
+
+ return 0;
+}
+
+/* Stop that thread */
+int fd_out_stop(struct fd_peer * peer)
+{
+ TRACE_ENTRY("%p", peer);
+ CHECK_PARAMS( CHECK_PEER(peer) );
+
+ CHECK_FCT( fd_cnx_unordered_delivery(peer->p_cnxctx, 0) );
+
+ CHECK_FCT( fd_thr_term(&peer->p_outthr) );
+
+ return 0;
+}
+
diff --git a/libfdcore/p_psm.c b/libfdcore/p_psm.c
new file mode 100644
index 0000000..2a30c5c
--- /dev/null
+++ b/libfdcore/p_psm.c
@@ -0,0 +1,952 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/*
+This file implement a Peer State Machine which is a mix of:
+ - the state machine described in rfc3588bis
+ - the state machine described in rfc3539#section-3.4
+ - the following observations.
+
+The delivery of Diameter messages must not always be unordered: order is important at
+begining and end of a connection lifetime. It means we need agility to
+switch between "ordering enforced" and "ordering not enforced to counter
+Head of the Line Blocking" modes of operation.
+
+The connection state machine represented in RFC3588 (and RFC6733) is
+incomplete, because it lacks the SUSPECT state and the 3 DWR/DWA
+exchanges (section 5.1) when the peer recovers from this state.
+Personnally I don't see the rationale for exchanging 3 messages (why 3?)
+but, if we require at least 1 DWR/DWA exchange to be always performed
+after the CER/CEA exchange (and initiated by the peer that sent the
+CEA), we have a simple way to deal with our ordering problem, as resumed
+below. Peers are: [i]nitiator, [r]esponder.
+ (1) [i] SCTP connection attempt.
+ (2) [r] accept the connection.
+ (3) [i,r] (if secure port) DTLS handshake, close on failure.
+ (4) [i] Send CER
+ (5) [r] Receive CER, send CEA using stream 0, flag "unordered" cleared.
+ [r] Immediately send a DWR after the CEA, also using stream 0,
+flag "unordered" cleared.
+ [r] Move to STATE_OPEN_NEW state -- equivalent to OPEN except
+that all messages are sent ordered at the moment.
+ (6) [i] receive CEA, move to OPEN state. All messages can be sent
+unordered in OPEN state.
+ [i] As per normal operation, reply with DWA to the DWR.
+ (7) [r] Upon reception of the DWA, move to OPEN state, messages can be
+sent unordered from this point.
+
+Note about (5) and (6): if the Diameter Identity received in CER or CEA
+does not match the credentials from the certificate presented during
+TLS handshake, we may need to specify a path of clean disconnection
+(not blocking the remote peer waiting for something).
+
+This proposed mechanism removes the problem of application messages
+received before the CEA by the initiator. Note that if the "old" inband
+TLS handshake is used, this handshake plays the same synchronization
+role than the new DWR/DWA, which becomes useless.
+
+
+The other time where ordering is important is by the end of connection
+lifetime, when one peer is shutting down the link for some reason
+(reboot, overload, no activity, etc...). In case of unordered delivery,
+we may have:
+- peer A sends an application message followed by a DPR. Peer B receives
+the DPR first and tears down the connection. Application message is lost.
+- Peer B sends an application message, then receives a DPR and answers a
+DPA. Peer A receives the DPA before the application message. The
+application message is lost.
+
+This situation is actually happening easily because DPR/DPA messages are
+very short, while application messages can be quite large. Therefore,
+they require much more time to deliver.
+
+I really cannot see a way to counter this effect by using the ordering
+of the messages, except by applying a timer (state STATE_CLOSING_GRACE).
+This timer can be also useful when we detect that some messages has not
+yet received an answer on this link, to give time to the application to
+complete the exchange ongoing.
+
+However, this problem must be balanced with the fact that the message
+that is lost will be in many cases sent again as the failover mechanism
+specifies.
+*/
+
+/* The actual declaration of peer_state_str */
+DECLARE_STATE_STR();
+
+/* Helper for next macro */
+#define case_str( _val ) \
+ case _val : return #_val
+
+DECLARE_PEV_STR();
+
+/************************************************************************/
+/* Delayed startup */
+/************************************************************************/
+static int started = 0;
+static pthread_mutex_t started_mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t started_cnd = PTHREAD_COND_INITIALIZER;
+
+/* Wait for start signal */
+static int fd_psm_waitstart()
+{
+ int ret = 0;
+ TRACE_ENTRY("");
+ CHECK_POSIX( pthread_mutex_lock(&started_mtx) );
+awake:
+ if (!ret && !started) {
+ pthread_cleanup_push( fd_cleanup_mutex, &started_mtx );
+ CHECK_POSIX_DO( ret = pthread_cond_wait(&started_cnd, &started_mtx), );
+ pthread_cleanup_pop( 0 );
+ goto awake;
+ }
+ CHECK_POSIX( pthread_mutex_unlock(&started_mtx) );
+ return ret;
+}
+
+/* Allow the state machines to start */
+int fd_psm_start()
+{
+ TRACE_ENTRY("");
+ CHECK_POSIX( pthread_mutex_lock(&started_mtx) );
+ started = 1;
+ CHECK_POSIX( pthread_cond_broadcast(&started_cnd) );
+ CHECK_POSIX( pthread_mutex_unlock(&started_mtx) );
+ return 0;
+}
+
+
+/************************************************************************/
+/* Manage the list of active peers */
+/************************************************************************/
+
+/* Enter/leave OPEN state */
+static int enter_open_state(struct fd_peer * peer)
+{
+ struct fd_list * li;
+ CHECK_PARAMS( FD_IS_LIST_EMPTY(&peer->p_actives) );
+
+ /* Callback registered by the credential validator (fd_peer_validate_register) */
+ if (peer->p_cb2) {
+ CHECK_FCT_DO( (*peer->p_cb2)(&peer->p_hdr.info),
+ {
+ TRACE_DEBUG(FULL, "Validation failed, terminating the connection");
+ fd_psm_terminate(peer, "DO_NOT_WANT_TO_TALK_TO_YOU" );
+ } );
+ peer->p_cb2 = NULL;
+ return 0;
+ }
+
+ /* Insert in the active peers list */
+ CHECK_POSIX( pthread_rwlock_wrlock(&fd_g_activ_peers_rw) );
+ for (li = fd_g_activ_peers.next; li != &fd_g_activ_peers; li = li->next) {
+ struct fd_peer * next_p = (struct fd_peer *)li->o;
+ int cmp = fd_os_cmp(peer->p_hdr.info.pi_diamid, peer->p_hdr.info.pi_diamidlen,
+ next_p->p_hdr.info.pi_diamid, next_p->p_hdr.info.pi_diamidlen);
+ if (cmp < 0)
+ break;
+ }
+ fd_list_insert_before(li, &peer->p_actives);
+ CHECK_POSIX( pthread_rwlock_unlock(&fd_g_activ_peers_rw) );
+
+ /* Callback registered when the peer was added, by fd_peer_add */
+ if (peer->p_cb) {
+ TRACE_DEBUG(FULL, "Calling add callback for peer %s", peer->p_hdr.info.pi_diamid);
+ (*peer->p_cb)(&peer->p_hdr.info, peer->p_cb_data); /* TODO: do this in a separate detached thread? */
+ peer->p_cb = NULL;
+ peer->p_cb_data = NULL;
+ }
+
+ /* Start the thread to handle outgoing messages */
+ CHECK_FCT( fd_out_start(peer) );
+
+ /* Update the expiry timer now */
+ CHECK_FCT( fd_p_expi_update(peer) );
+
+ return 0;
+}
+static int leave_open_state(struct fd_peer * peer, int skip_failover)
+{
+ /* Remove from active peers list */
+ CHECK_POSIX( pthread_rwlock_wrlock(&fd_g_activ_peers_rw) );
+ fd_list_unlink( &peer->p_actives );
+ CHECK_POSIX( pthread_rwlock_unlock(&fd_g_activ_peers_rw) );
+
+ /* Stop the "out" thread */
+ CHECK_FCT( fd_out_stop(peer) );
+
+ /* Failover the messages */
+ if (!skip_failover) {
+ fd_peer_failover_msg(peer);
+ }
+
+ return 0;
+}
+
+
+/************************************************************************/
+/* Helpers for state changes */
+/************************************************************************/
+
+/* Cleanup pending events in the peer */
+void fd_psm_events_free(struct fd_peer * peer)
+{
+ struct fd_event * ev;
+ /* Purge all events, and free the associated data if any */
+ while (fd_fifo_tryget( peer->p_events, &ev ) == 0) {
+ switch (ev->code) {
+ case FDEVP_CNX_ESTABLISHED: {
+ fd_cnx_destroy(ev->data);
+ }
+ break;
+
+ case FDEVP_TERMINATE:
+ /* Do not free the string since it is a constant */
+ break;
+
+ case FDEVP_CNX_INCOMING: {
+ struct cnx_incoming * evd = ev->data;
+ fd_hook_call(HOOK_MESSAGE_DROPPED, evd->cer, NULL, "Message discarded while cleaning peer state machine queue.", fd_msg_pmdl_get(evd->cer));
+ CHECK_FCT_DO( fd_msg_free(evd->cer), /* continue */);
+ fd_cnx_destroy(evd->cnx);
+ }
+ default:
+ free(ev->data);
+ }
+ free(ev);
+ }
+}
+
+/* Read state */
+int fd_peer_get_state(struct peer_hdr *peer)
+{
+ int ret;
+
+ struct fd_peer * p = (struct fd_peer *)peer;
+
+ if (!CHECK_PEER(p))
+ return -1;
+
+ CHECK_POSIX_DO( pthread_mutex_lock(&p->p_state_mtx), return -1 );
+ ret = p->p_state;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&p->p_state_mtx), return -1 );
+
+ return ret;
+}
+
+
+/* Change state */
+int fd_psm_change_state(struct fd_peer * peer, int new_state)
+{
+ int old;
+
+ TRACE_ENTRY("%p %d(%s)", peer, new_state, STATE_STR(new_state));
+ CHECK_PARAMS( CHECK_PEER(peer) );
+
+ old = fd_peer_getstate(peer);
+ if (old == new_state)
+ return 0;
+
+ LOG(((old == STATE_OPEN) || (new_state == STATE_OPEN)) ? FD_LOG_NOTICE : FD_LOG_DEBUG, "'%s'\t-> '%s'\t'%s'",
+ STATE_STR(old),
+ STATE_STR(new_state),
+ peer->p_hdr.info.pi_diamid);
+
+
+ CHECK_POSIX( pthread_mutex_lock(&peer->p_state_mtx) );
+ peer->p_state = new_state;
+ CHECK_POSIX( pthread_mutex_unlock(&peer->p_state_mtx) );
+
+ if (old == STATE_OPEN) {
+ CHECK_FCT( leave_open_state(peer, new_state == STATE_CLOSING_GRACE) );
+ }
+ if (old == STATE_CLOSING_GRACE) {
+ fd_peer_failover_msg(peer);
+ }
+
+ if (new_state == STATE_OPEN) {
+ CHECK_FCT( enter_open_state(peer) );
+ }
+
+ if (new_state == STATE_CLOSED) {
+ /* Purge event list */
+ fd_psm_events_free(peer);
+
+ /* Reset the counter of pending anwers to send */
+ peer->p_reqin_count = 0;
+
+ /* If the peer is not persistant, we destroy it */
+ if (peer->p_hdr.info.config.pic_flags.persist == PI_PRST_NONE) {
+ CHECK_FCT( fd_event_send(peer->p_events, FDEVP_TERMINATE, 0, NULL) );
+ }
+ }
+
+ return 0;
+}
+
+/* Set timeout timer of next event */
+void fd_psm_next_timeout(struct fd_peer * peer, int add_random, int delay)
+{
+ TRACE_DEBUG(FULL, "Peer timeout reset to %d seconds%s", delay, add_random ? " (+/- 2)" : "" );
+
+ /* Initialize the timer */
+ CHECK_POSIX_DO( clock_gettime( CLOCK_REALTIME, &peer->p_psm_timer ), ASSERT(0) );
+
+ if (add_random) {
+ if (delay > 2)
+ delay -= 2;
+ else
+ delay = 0;
+
+ /* Add a random value between 0 and 4sec */
+ peer->p_psm_timer.tv_sec += random() % 4;
+ peer->p_psm_timer.tv_nsec+= random() % 1000000000L;
+ if (peer->p_psm_timer.tv_nsec >= 1000000000L) {
+ peer->p_psm_timer.tv_nsec -= 1000000000L;
+ peer->p_psm_timer.tv_sec ++;
+ }
+ }
+
+ peer->p_psm_timer.tv_sec += delay;
+
+#ifdef SLOW_PSM
+ /* temporary for debug */
+ peer->p_psm_timer.tv_sec += 10;
+#endif
+}
+
+/* Cleanup the peer */
+void fd_psm_cleanup(struct fd_peer * peer, int terminate)
+{
+ /* Move to CLOSED state: failover messages, stop OUT thread, unlink peer from active list */
+ if (fd_peer_getstate(peer) != STATE_ZOMBIE) {
+ CHECK_FCT_DO( fd_psm_change_state(peer, STATE_CLOSED), /* continue */ );
+ }
+
+ fd_p_cnx_abort(peer, terminate);
+
+ fd_p_ce_clear_cnx(peer, NULL);
+
+ if (peer->p_receiver) {
+ fd_cnx_destroy(peer->p_receiver);
+ peer->p_receiver = NULL;
+ }
+
+ if (terminate) {
+ fd_psm_events_free(peer);
+ CHECK_FCT_DO( fd_fifo_del(&peer->p_events), /* continue */ );
+ }
+
+}
+
+
+/************************************************************************/
+/* The PSM thread */
+/************************************************************************/
+/* Cancelation cleanup : set ZOMBIE state in the peer */
+void cleanup_setstate(void * arg)
+{
+ struct fd_peer * peer = (struct fd_peer *)arg;
+ CHECK_PARAMS_DO( CHECK_PEER(peer), return );
+ CHECK_POSIX_DO( pthread_mutex_lock(&peer->p_state_mtx), );
+ peer->p_state = STATE_ZOMBIE;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&peer->p_state_mtx), );
+ return;
+}
+
+/* The state machine thread (controler) */
+static void * p_psm_th( void * arg )
+{
+ struct fd_peer * peer = (struct fd_peer *)arg;
+ int created_started = started ? 1 : 0;
+ int event;
+ size_t ev_sz;
+ void * ev_data;
+ int cur_state;
+
+ CHECK_PARAMS_DO( CHECK_PEER(peer), ASSERT(0) );
+
+ pthread_cleanup_push( cleanup_setstate, arg );
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "PSM/%s", peer->p_hdr.info.pi_diamid);
+ fd_log_threadname ( buf );
+ }
+
+ /* The state machine starts in CLOSED state */
+ CHECK_POSIX_DO( pthread_mutex_lock(&peer->p_state_mtx), goto psm_end );
+ peer->p_state = STATE_CLOSED;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&peer->p_state_mtx), goto psm_end );
+
+ /* Wait that the PSM are authorized to start in the daemon */
+ CHECK_FCT_DO( fd_psm_waitstart(), goto psm_end );
+
+ /* Initialize the timer */
+ if (peer->p_flags.pf_responder) {
+ fd_psm_next_timeout(peer, 0, INCNX_TIMEOUT);
+ } else {
+ fd_psm_next_timeout(peer, created_started, 0);
+ }
+
+psm_loop:
+ /* Get next event */
+ TRACE_DEBUG(FULL, "'%s' in state '%s' waiting for next event.",
+ peer->p_hdr.info.pi_diamid, STATE_STR(fd_peer_getstate(peer)));
+ CHECK_FCT_DO( fd_event_timedget(peer->p_events, &peer->p_psm_timer, FDEVP_PSM_TIMEOUT, &event, &ev_sz, &ev_data), goto psm_end );
+
+ cur_state = fd_peer_getstate(peer);
+ if (cur_state == -1)
+ goto psm_end;
+
+ TRACE_DEBUG(FULL, "'%s'\t<-- '%s'\t(%p,%zd)\t'%s'",
+ STATE_STR(cur_state),
+ fd_pev_str(event), ev_data, ev_sz,
+ peer->p_hdr.info.pi_diamid);
+
+ /* Now, the action depends on the current state and the incoming event */
+
+ /* The following states are impossible */
+ ASSERT( cur_state != STATE_NEW );
+ ASSERT( cur_state != STATE_ZOMBIE );
+ ASSERT( cur_state != STATE_OPEN_HANDSHAKE ); /* because it should exist only between two loops */
+
+ /* Purge invalid events */
+ if (!CHECK_PEVENT(event)) {
+ TRACE_DEBUG(INFO, "Invalid event received in PSM '%s' : %d", peer->p_hdr.info.pi_diamid, event);
+ ASSERT(0); /* we should investigate this situation */
+ goto psm_loop;
+ }
+
+ /* Requests to terminate the peer object */
+ if (event == FDEVP_TERMINATE) {
+ switch (cur_state) {
+ case STATE_OPEN:
+ case STATE_OPEN_NEW:
+ case STATE_REOPEN:
+ /* We cannot just close the connection, we have to send a DPR first */
+ CHECK_FCT_DO( fd_p_dp_initiate(peer, ev_data), goto psm_end );
+ goto psm_loop;
+
+ /*
+ case STATE_CLOSING:
+ case STATE_CLOSING_GRACE:
+ case STATE_WAITCNXACK:
+ case STATE_WAITCNXACK_ELEC:
+ case STATE_WAITCEA:
+ case STATE_SUSPECT:
+ case STATE_CLOSED:
+ */
+ default:
+ /* In these cases, we just cleanup the peer object (if needed) and terminate */
+ goto psm_end;
+ }
+ }
+
+ /* A message was received */
+ if (event == FDEVP_CNX_MSG_RECV) {
+ struct msg * msg = NULL;
+ struct msg_hdr * hdr;
+ struct fd_cnx_rcvdata rcv_data;
+ struct fd_msg_pmdl * pmdl = NULL;
+
+ rcv_data.buffer = ev_data;
+ rcv_data.length = ev_sz;
+ pmdl = fd_msg_pmdl_get_inbuf(rcv_data.buffer, rcv_data.length);
+
+ /* Parse the received buffer */
+ CHECK_FCT_DO( fd_msg_parse_buffer( (void *)&ev_data, ev_sz, &msg),
+ {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, NULL, peer, &rcv_data, pmdl );
+ free(ev_data);
+ CHECK_FCT_DO( fd_event_send(peer->p_events, FDEVP_CNX_ERROR, 0, NULL), goto psm_reset );
+ goto psm_loop;
+ } );
+
+ fd_hook_associate(msg, pmdl);
+ CHECK_FCT_DO( fd_msg_source_set( msg, peer->p_hdr.info.pi_diamid, peer->p_hdr.info.pi_diamidlen), goto psm_end);
+
+ /* If the current state does not allow receiving messages, just drop it */
+ if (cur_state == STATE_CLOSED) {
+ /* In such case, just discard the message */
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, peer, "Message purged from queue, peer in CLOSED state", fd_msg_pmdl_get(msg));
+ fd_msg_free(msg);
+ goto psm_loop;
+ }
+
+ /* Extract the header */
+ CHECK_FCT_DO( fd_msg_hdr(msg, &hdr), goto psm_end );
+
+ /* If it is an answer, associate with the request or drop */
+ if (!(hdr->msg_flags & CMD_FLAG_REQUEST)) {
+ struct msg * req;
+ /* Search matching request (same hbhid) */
+ CHECK_FCT_DO( fd_p_sr_fetch(&peer->p_sr, hdr->msg_hbhid, &req), goto psm_end );
+ if (req == NULL) {
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, peer, "Answer received with no corresponding sent request.", fd_msg_pmdl_get(msg));
+ fd_msg_free(msg);
+ goto psm_loop;
+ }
+
+ /* Associate */
+ CHECK_FCT_DO( fd_msg_answ_associate( msg, req ), goto psm_end );
+
+ }
+
+ /* Log incoming message */
+ fd_hook_call(HOOK_MESSAGE_RECEIVED, msg, peer, NULL, fd_msg_pmdl_get(msg));
+
+ if (cur_state == STATE_OPEN_NEW) {
+ /* OK, we have received something, so the connection is supposedly now in OPEN state at the remote site */
+ fd_psm_change_state(peer, STATE_OPEN );
+ }
+
+ /* Now handle non-link-local messages */
+ if (fd_msg_is_routable(msg)) {
+ switch (cur_state) {
+ /* To maximize compatibility -- should not be a security issue here */
+ case STATE_REOPEN:
+ case STATE_SUSPECT:
+ case STATE_CLOSING:
+ case STATE_CLOSING_GRACE:
+ TRACE_DEBUG(FULL, "Accepted a message while not in OPEN state... ");
+ /* The standard situation : */
+ case STATE_OPEN_NEW:
+ case STATE_OPEN:
+ /* We received a valid routable message, update the expiry timer */
+ CHECK_FCT_DO( fd_p_expi_update(peer), goto psm_end );
+
+ /* Set the message source and add the Route-Record */
+ CHECK_FCT_DO( fd_msg_source_setrr( msg, peer->p_hdr.info.pi_diamid, peer->p_hdr.info.pi_diamidlen, fd_g_config->cnf_dict ), goto psm_end);
+
+ if ((hdr->msg_flags & CMD_FLAG_REQUEST)) {
+ /* Mark the incoming request so that we know we have pending answers for this peer */
+ CHECK_POSIX_DO( pthread_mutex_lock(&peer->p_state_mtx), goto psm_end );
+ peer->p_reqin_count++;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&peer->p_state_mtx), goto psm_end );
+ }
+
+ /* Requeue to the global incoming queue */
+ CHECK_FCT_DO(fd_fifo_post(fd_g_incoming, &msg), goto psm_end );
+
+ /* Update the peer timer (only in OPEN state) */
+ if ((cur_state == STATE_OPEN) && (!peer->p_flags.pf_dw_pending)) {
+ fd_psm_next_timeout(peer, 1, peer->p_hdr.info.config.pic_twtimer ?: fd_g_config->cnf_timer_tw);
+ }
+ break;
+
+ /* In other states, we discard the message, it is either old or invalid to send it for the remote peer */
+ case STATE_WAITCNXACK:
+ case STATE_WAITCNXACK_ELEC:
+ case STATE_WAITCEA:
+ case STATE_CLOSED:
+ default: {
+ /* In such case, just discard the message */
+ char buf[128];
+ snprintf(buf, sizeof(buf), "Received while peer state machine was in state %s.", STATE_STR(cur_state));
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, peer, buf, fd_msg_pmdl_get(msg));
+ fd_msg_free(msg);
+ }
+ }
+ goto psm_loop;
+ }
+
+ /* Link-local message: They must be understood by our dictionary, otherwise we return an error */
+ {
+ struct msg * error = NULL;
+ int ret = fd_msg_parse_or_error( &msg, &error );
+ if (ret != EBADMSG) {
+ CHECK_FCT_DO( ret,
+ {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "%s: An unexpected error occurred while parsing a link-local message", peer->p_hdr.info.pi_diamid);
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, peer, buf, fd_msg_pmdl_get(msg));
+ fd_msg_free(msg);
+ goto psm_end;
+ } );
+ } else {
+ if (msg == NULL) {
+ /* Send the error back to the peer */
+ CHECK_FCT_DO( ret = fd_out_send(&error, NULL, peer, 0), );
+ if (error) {
+ char buf[256];
+ /* Only if an error occurred & the message was not saved / dumped */
+ snprintf(buf, sizeof(buf), "%s: error sending a message", peer->p_hdr.info.pi_diamid);
+ fd_hook_call(HOOK_MESSAGE_DROPPED, error, peer, buf, fd_msg_pmdl_get(error));
+ CHECK_FCT_DO( fd_msg_free(error), goto psm_end);
+ }
+ } else {
+ char buf[256];
+ /* We received an invalid answer, let's disconnect */
+ snprintf(buf, sizeof(buf), "%s: Received invalid answer to Base protocol message, disconnecting...", peer->p_hdr.info.pi_diamid);
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, peer, buf, fd_msg_pmdl_get(msg));
+ CHECK_FCT_DO( fd_msg_free(msg), goto psm_end);
+ CHECK_FCT_DO( fd_event_send(peer->p_events, FDEVP_CNX_ERROR, 0, NULL), goto psm_reset );
+ }
+ goto psm_loop;
+ }
+ }
+
+ /* Handle the LL message and update the expiry timer appropriately */
+ switch (hdr->msg_code) {
+ case CC_CAPABILITIES_EXCHANGE:
+ CHECK_FCT_DO( fd_p_ce_msgrcv(&msg, (hdr->msg_flags & CMD_FLAG_REQUEST), peer),
+ {
+ if (msg)
+ CHECK_FCT_DO( fd_msg_free(msg), );
+ goto psm_reset;
+ } );
+ break;
+
+ case CC_DISCONNECT_PEER:
+ CHECK_FCT_DO( fd_p_dp_handle(&msg, (hdr->msg_flags & CMD_FLAG_REQUEST), peer), goto psm_reset );
+ if (fd_peer_getstate(peer) == STATE_CLOSING)
+ goto psm_end;
+
+ break;
+
+ case CC_DEVICE_WATCHDOG:
+ CHECK_FCT_DO( fd_p_dw_handle(&msg, (hdr->msg_flags & CMD_FLAG_REQUEST), peer), goto psm_reset );
+ break;
+
+ default:
+ /* Unknown / unexpected / invalid message -- but validated by our dictionary */
+ TRACE_DEBUG(INFO, "Invalid non-routable command received: %u.", hdr->msg_code);
+ if (hdr->msg_flags & CMD_FLAG_REQUEST) {
+ do {
+ /* Reply with an error code */
+ CHECK_FCT_DO( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, &msg, MSGFL_ANSW_ERROR ), break );
+
+ /* Set the error code */
+ CHECK_FCT_DO( fd_msg_rescode_set(msg, "DIAMETER_COMMAND_UNSUPPORTED", "Or maybe the P-bit or application Id are erroneous.", NULL, 1 ), break );
+
+ /* Send the answer */
+ CHECK_FCT_DO( fd_out_send(&msg, peer->p_cnxctx, peer, 0), break );
+ } while (0);
+ } else {
+ /* We did ASK for it ??? */
+ TRACE_DEBUG(INFO, "Received answer with erroneous 'is_routable' result...");
+ }
+
+ /* Cleanup the message if not done */
+ if (msg) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Received un-handled non-routable command from peer '%s'.", peer->p_hdr.info.pi_diamid);
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, NULL, buf, fd_msg_pmdl_get(msg));
+ CHECK_FCT_DO( fd_msg_free(msg), /* continue */);
+ msg = NULL;
+ }
+ };
+
+ /* At this point the message must have been fully handled already */
+ if (msg) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Internal error ('%s'): unhandled message.", peer->p_hdr.info.pi_diamid);
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, NULL, buf, fd_msg_pmdl_get(msg));
+ fd_msg_free(msg);
+ }
+
+ goto psm_loop;
+ }
+
+ /* The connection object is broken */
+ if (event == FDEVP_CNX_ERROR) {
+ switch (cur_state) {
+ case STATE_WAITCNXACK_ELEC:
+ /* Abort the initiating side */
+ fd_p_cnx_abort(peer, 0);
+ /* Process the receiver side */
+ CHECK_FCT_DO( fd_p_ce_process_receiver(peer), goto psm_end );
+ break;
+
+ case STATE_WAITCEA:
+ case STATE_OPEN:
+ case STATE_OPEN_NEW:
+ case STATE_REOPEN:
+ case STATE_WAITCNXACK:
+ case STATE_SUSPECT:
+ default:
+ /* Mark the connection problem */
+ peer->p_flags.pf_cnx_pb = 1;
+
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "The connection was broken", NULL);
+
+ /* Destroy the connection, restart the timer to a new connection attempt */
+ fd_psm_next_timeout(peer, 1, peer->p_hdr.info.config.pic_tctimer ?: fd_g_config->cnf_timer_tc);
+
+ case STATE_CLOSED:
+ goto psm_reset;
+
+ case STATE_CLOSING:
+ /* We sent a DPR so we are terminating, do not wait for DPA */
+ goto psm_end;
+
+ case STATE_CLOSING_GRACE:
+ if (peer->p_flags.pf_localterm) /* initiated here */
+ goto psm_end;
+
+ fd_psm_cleanup(peer, 0);
+
+ /* Reset the timer for next connection attempt */
+ fd_psm_next_timeout(peer, 1, fd_p_dp_newdelay(peer));
+ goto psm_loop;
+ }
+ goto psm_loop;
+ }
+
+ /* The connection notified a change in endpoints */
+ if (event == FDEVP_CNX_EP_CHANGE) {
+ /* We actually don't care if we are in OPEN state here... */
+
+ /* Cleanup the remote LL and primary addresses */
+ CHECK_FCT_DO( fd_ep_filter( &peer->p_hdr.info.pi_endpoints, EP_FL_CONF | EP_FL_DISC | EP_FL_ADV ), /* ignore the error */);
+ CHECK_FCT_DO( fd_ep_clearflags( &peer->p_hdr.info.pi_endpoints, EP_FL_PRIMARY ), /* ignore the error */);
+
+ /* Get the new ones */
+ CHECK_FCT_DO( fd_cnx_getremoteeps(peer->p_cnxctx, &peer->p_hdr.info.pi_endpoints), /* ignore the error */);
+
+ /* We do not support local endpoints change currently, but it could be added here if needed (refresh fd_g_config->cnf_endpoints) */
+ {
+ char * buf = NULL;
+ size_t len = 0;
+ LOG_D("Got low layer notification (IGNORED): remote endpoint(s) changed: %s", fd_ep_dump(&buf, &len, NULL, 0, 0, &peer->p_hdr.info.pi_endpoints) ?: "error");
+ free(buf);
+ }
+
+ /* Done */
+ goto psm_loop;
+ }
+
+ /* A new connection was established and CER containing this peer id was received */
+ if (event == FDEVP_CNX_INCOMING) {
+ struct cnx_incoming * params = ev_data;
+ ASSERT(params);
+
+ /* Handle the message */
+ CHECK_FCT_DO( fd_p_ce_handle_newCER(¶ms->cer, peer, ¶ms->cnx, params->validate), goto psm_end );
+
+ /* Cleanup if needed */
+ if (params->cnx) {
+ fd_cnx_destroy(params->cnx);
+ params->cnx = NULL;
+ }
+ if (params->cer) {
+ CHECK_FCT_DO( fd_msg_free(params->cer), );
+ params->cer = NULL;
+ }
+
+ /* Loop */
+ free(ev_data);
+ goto psm_loop;
+ }
+
+ /* A new connection has been established with the remote peer */
+ if (event == FDEVP_CNX_ESTABLISHED) {
+ struct cnxctx * cnx = ev_data;
+
+ /* Release the resources of the connecting thread */
+ CHECK_POSIX_DO( pthread_join( peer->p_ini_thr, NULL), /* ignore, it is not a big deal */);
+ peer->p_ini_thr = (pthread_t)NULL;
+
+ switch (cur_state) {
+ case STATE_WAITCNXACK_ELEC:
+ case STATE_WAITCNXACK:
+ LOG_D("%s: Connection established, %s", peer->p_hdr.info.pi_diamid, fd_cnx_getid(cnx));
+ fd_p_ce_handle_newcnx(peer, cnx);
+ break;
+
+ default:
+ /* Just abort the attempt and continue */
+ TRACE_DEBUG(FULL, "Connection attempt successful but current state is %s, closing... (too slow?)", STATE_STR(cur_state));
+ fd_cnx_destroy(cnx);
+ }
+
+ goto psm_loop;
+ }
+
+ /* A new connection has not been established with the remote peer */
+ if (event == FDEVP_CNX_FAILED) {
+
+ /* Release the resources of the connecting thread */
+ CHECK_POSIX_DO( pthread_join( peer->p_ini_thr, NULL), /* ignore, it is not a big deal */);
+ peer->p_ini_thr = (pthread_t)NULL;
+
+ switch (cur_state) {
+ case STATE_WAITCNXACK_ELEC:
+ /* Abort the initiating side */
+ fd_p_cnx_abort(peer, 0);
+ /* Process the receiver side */
+ CHECK_FCT_DO( fd_p_ce_process_receiver(peer), goto psm_end );
+ break;
+
+ case STATE_WAITCNXACK:
+ /* Go back to CLOSE */
+ fd_psm_next_timeout(peer, 1, peer->p_hdr.info.config.pic_tctimer ?: fd_g_config->cnf_timer_tc);
+ goto psm_reset;
+
+ default:
+ /* Just ignore */
+ TRACE_DEBUG(FULL, "Connection attempt failed but current state is %s, ignoring...", STATE_STR(cur_state));
+ }
+
+ goto psm_loop;
+ }
+
+ /* The timeout for the current state has been reached */
+ if (event == FDEVP_PSM_TIMEOUT) {
+ switch (cur_state) {
+ case STATE_OPEN:
+ case STATE_REOPEN:
+ case STATE_OPEN_NEW:
+ CHECK_FCT_DO( fd_p_dw_timeout(peer), goto psm_end );
+ goto psm_loop;
+
+ case STATE_CLOSED:
+ LOG_D("%s: Connecting...", peer->p_hdr.info.pi_diamid);
+ CHECK_FCT_DO( fd_psm_change_state(peer, STATE_WAITCNXACK), goto psm_end );
+ fd_psm_next_timeout(peer, 0, CNX_TIMEOUT);
+ CHECK_FCT_DO( fd_p_cnx_init(peer), goto psm_end );
+ goto psm_loop;
+
+ case STATE_SUSPECT:
+ /* Mark the connection problem */
+ peer->p_flags.pf_cnx_pb = 1;
+ case STATE_WAITCNXACK:
+ case STATE_WAITCEA:
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, peer, "Timeout while waiting for remote peer", NULL);
+ case STATE_CLOSING:
+ /* Destroy the connection, restart the timer to a new connection attempt */
+ fd_psm_next_timeout(peer, 1, peer->p_hdr.info.config.pic_tctimer ?: fd_g_config->cnf_timer_tc);
+ goto psm_reset;
+
+ case STATE_CLOSING_GRACE:
+ /* The grace period is completed, now close */
+ if (peer->p_flags.pf_localterm)
+ goto psm_end;
+
+ fd_psm_cleanup(peer, 0);
+ /* Reset the timer for next connection attempt */
+ fd_psm_next_timeout(peer, 1, fd_p_dp_newdelay(peer));
+ goto psm_loop;
+
+ case STATE_WAITCNXACK_ELEC:
+ /* Abort the initiating side */
+ fd_p_cnx_abort(peer, 0);
+ /* Process the receiver side */
+ CHECK_FCT_DO( fd_p_ce_process_receiver(peer), goto psm_end );
+ goto psm_loop;
+
+ default:
+ ASSERT(0); /* implementation problem, we did not foresee this case? */
+ }
+ }
+
+ /* Default action : the handling has not yet been implemented. [for debug only] */
+ TRACE_DEBUG(INFO, "Missing handler in PSM for '%s'\t<-- '%s'", STATE_STR(cur_state), fd_pev_str(event));
+psm_reset:
+ if (peer->p_flags.pf_delete)
+ goto psm_end;
+ fd_psm_cleanup(peer, 0);
+ goto psm_loop;
+
+psm_end:
+ LOG_E("%s: Going to ZOMBIE state (no more activity)", peer->p_hdr.info.pi_diamid);
+ fd_psm_cleanup(peer, 1);
+ TRACE_DEBUG(INFO, "'%s'\t-> 'STATE_ZOMBIE' (terminated)\t'%s'",
+ STATE_STR(fd_peer_getstate(peer)),
+ peer->p_hdr.info.pi_diamid);
+ pthread_cleanup_pop(1); /* set STATE_ZOMBIE */
+ peer->p_psm = (pthread_t)NULL;
+ pthread_detach(pthread_self());
+ return NULL;
+}
+
+
+/************************************************************************/
+/* Functions to control the PSM */
+/************************************************************************/
+/* Create the PSM thread of one peer structure */
+int fd_psm_begin(struct fd_peer * peer )
+{
+ TRACE_ENTRY("%p", peer);
+
+ /* Check the peer and state are OK */
+ CHECK_PARAMS( fd_peer_getstate(peer) == STATE_NEW );
+
+ /* Create the FIFO for events */
+ CHECK_FCT( fd_fifo_new(&peer->p_events, 0) );
+
+ /* Create the PSM controler thread */
+ CHECK_POSIX( pthread_create( &peer->p_psm, NULL, p_psm_th, peer ) );
+
+ /* We're done */
+ return 0;
+}
+
+/* End the PSM (clean ending) */
+int fd_psm_terminate(struct fd_peer * peer, char * reason )
+{
+ TRACE_ENTRY("%p", peer);
+ CHECK_PARAMS( CHECK_PEER(peer) );
+
+ if (fd_peer_getstate(peer) != STATE_ZOMBIE) {
+ CHECK_FCT( fd_event_send(peer->p_events, FDEVP_TERMINATE, 0, reason) );
+ } else {
+ TRACE_DEBUG(FULL, "Peer '%s' was already terminated", peer->p_hdr.info.pi_diamid);
+ }
+ return 0;
+}
+
+/* End the PSM & cleanup the peer structure */
+void fd_psm_abord(struct fd_peer * peer )
+{
+ TRACE_ENTRY("%p", peer);
+
+ /* Cancel PSM thread */
+ CHECK_FCT_DO( fd_thr_term(&peer->p_psm), /* continue */ );
+
+ /* Cleanup the data */
+ fd_psm_cleanup(peer, 1);
+
+ /* Destroy the event list */
+ CHECK_FCT_DO( fd_fifo_del(&peer->p_events), /* continue */ );
+
+ /* Remaining cleanups are performed in fd_peer_free */
+ return;
+}
+
diff --git a/libfdcore/p_sr.c b/libfdcore/p_sr.c
new file mode 100644
index 0000000..36b5a55
--- /dev/null
+++ b/libfdcore/p_sr.c
@@ -0,0 +1,350 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/* Structure to store a sent request */
+struct sentreq {
+ struct fd_list chain; /* the "o" field points directly to the (new) hop-by-hop of the request (uint32_t *) */
+ struct msg *req; /* A request that was sent and not yet answered. */
+ uint32_t prevhbh;/* The value to set back in the hbh header when the message is retrieved */
+ struct fd_list expire; /* the list of expiring requests */
+ struct timespec timeout; /* Cache the expire date of the request so that the timeout thread does not need to get it each time. */
+ struct timespec added_on; /* the time the request was added */
+};
+
+/* Find an element in the hbh list, or the following one */
+static struct fd_list * find_or_next(struct fd_list * srlist, uint32_t hbh, int * match)
+{
+ struct fd_list * li;
+ *match = 0;
+ for (li = srlist->next; li != srlist; li = li->next) {
+ uint32_t * nexthbh = li->o;
+ if (*nexthbh < hbh)
+ continue;
+ if (*nexthbh == hbh)
+ *match = 1;
+ break;
+ }
+ return li;
+}
+
+/* Similar but start from the end, since we add requests in growing hbh order usually */
+static struct fd_list * find_or_prev(struct fd_list * srlist, uint32_t hbh, int * match)
+{
+ struct fd_list * li;
+ *match = 0;
+ for (li = srlist->prev; li != srlist; li = li->prev) {
+ uint32_t * prevhbh = li->o;
+ if (*prevhbh > hbh)
+ continue;
+ if (*prevhbh == hbh)
+ *match = 1;
+ break;
+ }
+ return li;
+}
+
+static void srl_dump(const char * text, struct fd_list * srlist)
+{
+ struct fd_list * li;
+ struct timespec now;
+
+ LOG_D("%sSentReq list @%p:", text, srlist);
+
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &now), );
+
+ for (li = srlist->next; li != srlist; li = li->next) {
+ struct sentreq * sr = (struct sentreq *)li;
+ uint32_t * nexthbh = li->o;
+
+ LOG_D(" - Next req (hbh:0x%x, prev:0x%x): [since %ld.%06ld sec]", *nexthbh, sr->prevhbh,
+ (long)((now.tv_nsec >= sr->added_on.tv_nsec) ? (now.tv_sec - sr->added_on.tv_sec) : (now.tv_sec - sr->added_on.tv_sec - 1)),
+ (long)((now.tv_nsec >= sr->added_on.tv_nsec) ? ((now.tv_nsec - sr->added_on.tv_nsec) / 1000) : ((now.tv_nsec - sr->added_on.tv_nsec + 1000000000) / 1000)));
+ }
+}
+
+/* thread that handles messages expiring. The thread is started only when needed */
+static void * sr_expiry_th(void * arg) {
+ struct sr_list * srlist = arg;
+
+ TRACE_ENTRY("%p", arg);
+ CHECK_PARAMS_DO( arg, return NULL );
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "ReqExp/%s", ((struct fd_peer *)(srlist->exp.o))->p_hdr.info.pi_diamid);
+ fd_log_threadname ( buf );
+ }
+
+ do {
+ struct timespec now;
+ struct sentreq * first;
+ struct msg * request;
+ struct fd_peer * sentto;
+ void (*expirecb)(void *, DiamId_t, size_t, struct msg **);
+ void * data;
+ int no_error;
+
+ CHECK_POSIX_DO( pthread_mutex_lock(&srlist->mtx), return NULL );
+ pthread_cleanup_push( fd_cleanup_mutex, &srlist->mtx );
+
+loop:
+ no_error = 0;
+
+ /* Check if there are expiring requests available */
+ if (FD_IS_LIST_EMPTY(&srlist->exp)) {
+ /* Just wait for a change or cancelation */
+ CHECK_POSIX_DO( pthread_cond_wait( &srlist->cnd, &srlist->mtx ), goto unlock );
+ /* Restart the loop on wakeup */
+ goto loop;
+ }
+
+ /* Get the pointer to the request that expires first */
+ first = (struct sentreq *)(srlist->exp.next->o);
+
+ /* Get the current time */
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &now), goto unlock );
+
+ /* If first request is not expired, we just wait until it happens */
+ if ( TS_IS_INFERIOR( &now, &first->timeout ) ) {
+
+ CHECK_POSIX_DO2( pthread_cond_timedwait( &srlist->cnd, &srlist->mtx, &first->timeout ),
+ ETIMEDOUT, /* ETIMEDOUT is a normal return value, continue */,
+ /* on other error, */ goto unlock );
+
+ /* on wakeup, loop */
+ goto loop;
+ }
+
+ /* Now, the first request in the list is expired; remove it and call the expirecb for it */
+ request = first->req;
+ sentto = first->chain.head->o;
+
+ TRACE_DEBUG(FULL, "Request %x was not answered by %s within the timer delay", *((uint32_t *)first->chain.o), sentto->p_hdr.info.pi_diamid);
+
+ /* Restore the hbhid */
+ *((uint32_t *)first->chain.o) = first->prevhbh;
+
+ /* Free the sentreq information */
+ fd_list_unlink(&first->chain);
+ srlist->cnt--;
+ srlist->cnt_lost++; /* We are not waiting for this answer anymore, but the remote peer may still be processing it. */
+ fd_list_unlink(&first->expire);
+ free(first);
+
+ no_error = 1;
+unlock:
+ ; /* pthread_cleanup_pop sometimes expands as "} ..." and the label before this cause some compilers to complain... */
+ pthread_cleanup_pop( 1 ); /* unlock the mutex */
+ if (!no_error)
+ break;
+
+
+ /* Retrieve callback in the message */
+ CHECK_FCT_DO( fd_msg_anscb_get( request, NULL, &expirecb, &data ), break);
+ ASSERT(expirecb);
+
+ /* Clean up this expirecb from the message */
+ CHECK_FCT_DO( fd_msg_anscb_reset( request, 0, 1 ), break);
+
+ /* Call it */
+ (*expirecb)(data, sentto->p_hdr.info.pi_diamid, sentto->p_hdr.info.pi_diamidlen, &request);
+
+ /* If the callback did not dispose of the message, do it now */
+ if (request) {
+ fd_hook_call(HOOK_MESSAGE_DROPPED, request, NULL, "Expiration period completed without an answer, and the expiry callback did not dispose of the message.", fd_msg_pmdl_get(request));
+ CHECK_FCT_DO( fd_msg_free(request), /* ignore */ );
+ }
+
+ } while (1);
+
+ ASSERT(0); /* we have encountered a problem, maybe time to signal the framework to terminate? */
+ return NULL;
+}
+
+
+/* Store a new sent request */
+int fd_p_sr_store(struct sr_list * srlist, struct msg **req, uint32_t *hbhloc, uint32_t hbh_restore)
+{
+ struct sentreq * sr;
+ struct fd_list * prev;
+ int match;
+ struct timespec * ts;
+
+ TRACE_ENTRY("%p %p %p %x", srlist, req, hbhloc, hbh_restore);
+ CHECK_PARAMS(srlist && req && *req && hbhloc);
+
+ CHECK_MALLOC( sr = malloc(sizeof(struct sentreq)) );
+ memset(sr, 0, sizeof(struct sentreq));
+ fd_list_init(&sr->chain, hbhloc);
+ sr->req = *req;
+ sr->prevhbh = hbh_restore;
+ fd_list_init(&sr->expire, sr);
+ CHECK_SYS( clock_gettime(CLOCK_REALTIME, &sr->added_on) );
+
+ /* Search the place in the list */
+ CHECK_POSIX( pthread_mutex_lock(&srlist->mtx) );
+ prev = find_or_prev(&srlist->srs, *hbhloc, &match);
+ if (match) {
+ TRACE_DEBUG(INFO, "A request with the same hop-by-hop Id (0x%x) was already sent: error", *hbhloc);
+ free(sr);
+ srl_dump("Current list of SR: ", &srlist->srs);
+ CHECK_POSIX_DO( pthread_mutex_unlock(&srlist->mtx), /* ignore */ );
+ return EINVAL;
+ }
+
+ /* Save in the list */
+ *req = NULL;
+ fd_list_insert_after(prev, &sr->chain);
+ srlist->cnt++;
+
+ /* In case of request with a timeout, also store in the timeout list */
+ ts = fd_msg_anscb_gettimeout( sr->req );
+ if (ts) {
+ struct fd_list * li;
+
+ memcpy(&sr->timeout, ts, sizeof(struct timespec));
+
+ /* browse srlist->exp from the end */
+ for (li = srlist->exp.prev; li != &srlist->exp; li = li->prev) {
+ struct sentreq * s = (struct sentreq *)(li->o);
+ if (TS_IS_INFERIOR(&s->timeout, ts))
+ break;
+ }
+
+ fd_list_insert_after(li, &sr->expire);
+
+ /* if the thread does not exist yet, create it */
+ if (srlist->thr == (pthread_t)NULL) {
+ CHECK_POSIX_DO( pthread_create(&srlist->thr, NULL, sr_expiry_th, srlist), /* continue anyway */);
+ } else {
+ /* or, if added in first position, signal the condvar to update the sleep time of the thread */
+ if (li == &srlist->exp) {
+ CHECK_POSIX_DO( pthread_cond_signal(&srlist->cnd), /* continue anyway */);
+ }
+ }
+ }
+
+ CHECK_POSIX( pthread_mutex_unlock(&srlist->mtx) );
+ return 0;
+}
+
+/* Fetch a request by hbh */
+int fd_p_sr_fetch(struct sr_list * srlist, uint32_t hbh, struct msg **req)
+{
+ struct sentreq * sr;
+ int match;
+
+ TRACE_ENTRY("%p %x %p", srlist, hbh, req);
+ CHECK_PARAMS(srlist && req);
+
+ /* Search the request in the list */
+ CHECK_POSIX( pthread_mutex_lock(&srlist->mtx) );
+ sr = (struct sentreq *)find_or_next(&srlist->srs, hbh, &match);
+ if (!match) {
+ TRACE_DEBUG(INFO, "There is no saved request with this hop-by-hop id (%x)", hbh);
+ srl_dump("Current list of SR: ", &srlist->srs);
+ *req = NULL;
+ if (srlist->cnt_lost > 0) {
+ srlist->cnt_lost--; /* This is probably an answer for a request we already timedout. */
+ } /* else, probably a bug in the remote peer */
+ } else {
+ /* Restore hop-by-hop id */
+ *((uint32_t *)sr->chain.o) = sr->prevhbh;
+ /* Unlink */
+ fd_list_unlink(&sr->chain);
+ srlist->cnt--;
+ fd_list_unlink(&sr->expire);
+ *req = sr->req;
+ free(sr);
+ }
+ CHECK_POSIX( pthread_mutex_unlock(&srlist->mtx) );
+
+ /* do not stop the expire thread here, it might cause creating/destroying it very often otherwise */
+
+ /* Done */
+ return 0;
+}
+
+/* Failover requests (free or requeue routables) */
+void fd_p_sr_failover(struct sr_list * srlist)
+{
+ CHECK_POSIX_DO( pthread_mutex_lock(&srlist->mtx), /* continue anyway */ );
+ while (!FD_IS_LIST_EMPTY(&srlist->srs)) {
+ struct sentreq * sr = (struct sentreq *)(srlist->srs.next);
+ fd_list_unlink(&sr->chain);
+ srlist->cnt--;
+ fd_list_unlink(&sr->expire);
+ if (fd_msg_is_routable(sr->req)) {
+ struct msg_hdr * hdr = NULL;
+ int ret;
+
+ /* Set the 'T' flag */
+ CHECK_FCT_DO(fd_msg_hdr(sr->req, &hdr), /* continue */);
+ if (hdr)
+ hdr->msg_flags |= CMD_FLAG_RETRANSMIT;
+
+ /* Restore the original hop-by-hop id of the request */
+ *((uint32_t *)sr->chain.o) = sr->prevhbh;
+
+ fd_hook_call(HOOK_MESSAGE_FAILOVER, sr->req, (struct fd_peer *)srlist->srs.o, NULL, fd_msg_pmdl_get(sr->req));
+
+ /* Requeue for sending to another peer */
+ CHECK_FCT_DO( ret = fd_fifo_post_noblock(fd_g_outgoing, (void *)&sr->req),
+ {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Internal error: error while requeuing during failover: %s", strerror(ret));
+ fd_hook_call(HOOK_MESSAGE_DROPPED, sr->req, NULL, buf, fd_msg_pmdl_get(sr->req));
+ CHECK_FCT_DO(fd_msg_free(sr->req), /* What can we do more? */)
+ });
+ } else {
+ /* Just free the request. */
+ /* fd_hook_call(HOOK_MESSAGE_DROPPED, sr->req, NULL, "Sent & unanswered local message discarded during failover.", fd_msg_pmdl_get(sr->req)); */
+ CHECK_FCT_DO(fd_msg_free(sr->req), /* Ignore */);
+ }
+ free(sr);
+ }
+ /* The list of expiring requests must be empty now */
+ ASSERT( FD_IS_LIST_EMPTY(&srlist->exp) );
+ ASSERT( srlist->cnt == 0 ); /* debug the counter management if needed */
+
+ CHECK_POSIX_DO( pthread_mutex_unlock(&srlist->mtx), /* continue anyway */ );
+
+ /* Terminate the expiry thread (must be done when the lock can be taken) */
+ CHECK_FCT_DO( fd_thr_term(&srlist->thr), /* ignore error */ );
+}
+
diff --git a/libfdcore/peers.c b/libfdcore/peers.c
new file mode 100644
index 0000000..98f1921
--- /dev/null
+++ b/libfdcore/peers.c
@@ -0,0 +1,693 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/* Global list of peers */
+struct fd_list fd_g_peers = FD_LIST_INITIALIZER(fd_g_peers);
+pthread_rwlock_t fd_g_peers_rw = PTHREAD_RWLOCK_INITIALIZER;
+
+/* List of active peers */
+struct fd_list fd_g_activ_peers = FD_LIST_INITIALIZER(fd_g_activ_peers); /* peers linked by their p_actives oredered by p_diamid */
+pthread_rwlock_t fd_g_activ_peers_rw = PTHREAD_RWLOCK_INITIALIZER;
+
+/* List of validation callbacks (registered with fd_peer_validate_register) */
+static struct fd_list validators = FD_LIST_INITIALIZER(validators); /* list items are simple fd_list with "o" pointing to the callback */
+static pthread_rwlock_t validators_rw = PTHREAD_RWLOCK_INITIALIZER;
+
+
+/* Alloc / reinit a peer structure. if *ptr is not NULL, it must already point to a valid struct fd_peer. */
+int fd_peer_alloc(struct fd_peer ** ptr)
+{
+ struct fd_peer *p;
+
+ TRACE_ENTRY("%p", ptr);
+ CHECK_PARAMS(ptr);
+
+ if (*ptr) {
+ p = *ptr;
+ } else {
+ CHECK_MALLOC( p = malloc(sizeof(struct fd_peer)) );
+ *ptr = p;
+ }
+
+ /* Now initialize the content */
+ memset(p, 0, sizeof(struct fd_peer));
+
+ fd_list_init(&p->p_hdr.chain, p);
+
+ fd_list_init(&p->p_hdr.info.pi_endpoints, p);
+ fd_list_init(&p->p_hdr.info.runtime.pir_apps, p);
+
+ p->p_eyec = EYEC_PEER;
+ CHECK_POSIX( pthread_mutex_init(&p->p_state_mtx, NULL) );
+
+ fd_list_init(&p->p_actives, p);
+ fd_list_init(&p->p_expiry, p);
+ CHECK_FCT( fd_fifo_new(&p->p_tosend, 5) );
+ CHECK_FCT( fd_fifo_new(&p->p_tofailover, 0) );
+ p->p_hbh = lrand48();
+
+ fd_list_init(&p->p_sr.srs, p);
+ fd_list_init(&p->p_sr.exp, p);
+ CHECK_POSIX( pthread_mutex_init(&p->p_sr.mtx, NULL) );
+ CHECK_POSIX( pthread_cond_init(&p->p_sr.cnd, NULL) );
+
+ fd_list_init(&p->p_connparams, p);
+
+ return 0;
+}
+
+/* Add a new peer entry */
+int fd_peer_add ( struct peer_info * info, const char * orig_dbg, void (*cb)(struct peer_info *, void *), void * cb_data )
+{
+ struct fd_peer *p = NULL;
+ struct fd_list * li, *li_inf;
+ int ret = 0;
+
+ TRACE_ENTRY("%p %p %p %p", info, orig_dbg, cb, cb_data);
+ CHECK_PARAMS(info && info->pi_diamid);
+
+ if (info->config.pic_realm) {
+ if (!fd_os_is_valid_DiameterIdentity((os0_t)info->config.pic_realm, strlen(info->config.pic_realm))) {
+ TRACE_DEBUG(INFO, "'%s' is not a valid DiameterIdentity.", info->config.pic_realm);
+ return EINVAL;
+ }
+ }
+
+ /* Create a structure to contain the new peer information */
+ CHECK_FCT( fd_peer_alloc(&p) );
+
+ /* Copy the informations from the parameters received */
+ p->p_hdr.info.pi_diamid = info->pi_diamid;
+ CHECK_FCT( fd_os_validate_DiameterIdentity(&p->p_hdr.info.pi_diamid, &p->p_hdr.info.pi_diamidlen, 1) );
+
+ memcpy( &p->p_hdr.info.config, &info->config, sizeof(p->p_hdr.info.config) );
+
+ /* Duplicate the strings if provided */
+ if (info->config.pic_realm) {
+ CHECK_MALLOC( p->p_hdr.info.config.pic_realm = strdup(info->config.pic_realm) );
+ }
+ if (info->config.pic_priority) {
+ CHECK_MALLOC( p->p_hdr.info.config.pic_priority = strdup(info->config.pic_priority) );
+ }
+
+ /* Move the list of endpoints into the peer */
+ if (info->pi_endpoints.next)
+ while (!FD_IS_LIST_EMPTY( &info->pi_endpoints ) ) {
+ li = info->pi_endpoints.next;
+ fd_list_unlink(li);
+ fd_list_insert_before(&p->p_hdr.info.pi_endpoints, li);
+ }
+
+ /* The internal data */
+ if (orig_dbg) {
+ CHECK_MALLOC( p->p_dbgorig = strdup(orig_dbg) );
+ } else {
+ CHECK_MALLOC( p->p_dbgorig = strdup("unspecified") );
+ }
+ p->p_cb = cb;
+ p->p_cb_data = cb_data;
+
+ /* Ok, now check if we don't already have an entry with the same Diameter Id, and insert this one */
+ CHECK_POSIX( pthread_rwlock_wrlock(&fd_g_peers_rw) );
+ li_inf = &fd_g_peers;
+ for (li = fd_g_peers.next; li != &fd_g_peers; li = li->next) {
+ struct fd_peer * next = (struct fd_peer *)li;
+ int cont;
+ int cmp = fd_os_almostcasesrch( p->p_hdr.info.pi_diamid, p->p_hdr.info.pi_diamidlen,
+ next->p_hdr.info.pi_diamid, next->p_hdr.info.pi_diamidlen,
+ &cont );
+ if (cmp > 0)
+ li_inf = li; /* it will come after this element, for sure */
+
+ if (cmp == 0) {
+ ret = EEXIST; /* we have a duplicate */
+ break;
+ }
+ if (!cont)
+ break;
+ }
+
+ /* We can insert the new peer object */
+ if (! ret)
+ do {
+ /* Update expiry list */
+ CHECK_FCT_DO( ret = fd_p_expi_update( p ), break );
+
+ /* Insert the new element in the list */
+ fd_list_insert_after( li_inf, &p->p_hdr.chain );
+ } while (0);
+
+ CHECK_POSIX( pthread_rwlock_unlock(&fd_g_peers_rw) );
+ if (ret) {
+ CHECK_FCT( fd_peer_free(&p) );
+ } else {
+ CHECK_FCT( fd_psm_begin(p) );
+ }
+ return ret;
+}
+
+/* Search for a peer */
+int fd_peer_getbyid( DiamId_t diamid, size_t diamidlen, int igncase, struct peer_hdr ** peer )
+{
+ struct fd_list * li;
+ TRACE_ENTRY("%p %zd %d %p", diamid, diamidlen, igncase, peer);
+ CHECK_PARAMS( diamid && diamidlen && peer );
+
+ *peer = NULL;
+
+ /* Search in the list */
+ CHECK_POSIX( pthread_rwlock_rdlock(&fd_g_peers_rw) );
+ if (igncase) {
+ for (li = fd_g_peers.next; li != &fd_g_peers; li = li->next) {
+ struct fd_peer * next = (struct fd_peer *)li;
+ int cmp, cont;
+ cmp = fd_os_almostcasesrch( diamid, diamidlen, next->p_hdr.info.pi_diamid, next->p_hdr.info.pi_diamidlen, &cont );
+ if (cmp == 0) {
+ *peer = &next->p_hdr;
+ break;
+ }
+ if (!cont)
+ break;
+ }
+ } else {
+ for (li = fd_g_peers.next; li != &fd_g_peers; li = li->next) {
+ struct fd_peer * next = (struct fd_peer *)li;
+ int cmp = fd_os_cmp( diamid, diamidlen, next->p_hdr.info.pi_diamid, next->p_hdr.info.pi_diamidlen );
+ if (cmp > 0)
+ continue;
+ if (cmp == 0)
+ *peer = &next->p_hdr;
+ break;
+ }
+ }
+ CHECK_POSIX( pthread_rwlock_unlock(&fd_g_peers_rw) );
+
+ return 0;
+}
+
+
+#define free_null( _v ) \
+ if (_v) { \
+ free(_v); \
+ (_v) = NULL; \
+ }
+
+#define free_list( _l ) \
+ while (!FD_IS_LIST_EMPTY(_l)) { \
+ struct fd_list * __li = ((struct fd_list *)(_l))->next; \
+ fd_list_unlink(__li); \
+ free(__li); \
+ }
+
+/* Empty the lists of p_tosend, p_failover, and p_sentreq messages */
+void fd_peer_failover_msg(struct fd_peer * peer)
+{
+ struct msg *m;
+ TRACE_ENTRY("%p", peer);
+ CHECK_PARAMS_DO(CHECK_PEER(peer), return);
+
+ /* Requeue all messages in the "out" queue */
+ while ( fd_fifo_tryget(peer->p_tosend, &m) == 0 ) {
+ /* but only if they are routable */
+ if (fd_msg_is_routable(m)) {
+ fd_hook_call(HOOK_MESSAGE_FAILOVER, m, peer, NULL, fd_msg_pmdl_get(m));
+ CHECK_FCT_DO(fd_fifo_post_noblock(fd_g_outgoing, (void *)&m),
+ {
+ /* fallback: destroy the message */
+ fd_hook_call(HOOK_MESSAGE_DROPPED, m, NULL, "Internal error: unable to requeue this message during failover process", fd_msg_pmdl_get(m));
+ CHECK_FCT_DO(fd_msg_free(m), /* What can we do more? */)
+ } );
+ } else {
+ /* Just free it */
+ /* fd_hook_call(HOOK_MESSAGE_DROPPED, m, NULL, "Non-routable message freed during handover", fd_msg_pmdl_get(m)); */
+ CHECK_FCT_DO(fd_msg_free(m), /* What can we do more? */)
+ }
+ }
+
+ /* Requeue all messages in the "failover" queue */
+ while ( fd_fifo_tryget(peer->p_tofailover, &m) == 0 ) {
+ fd_hook_call(HOOK_MESSAGE_FAILOVER, m, peer, NULL, fd_msg_pmdl_get(m));
+ CHECK_FCT_DO(fd_fifo_post_noblock(fd_g_outgoing, (void *)&m),
+ {
+ /* fallback: destroy the message */
+ fd_hook_call(HOOK_MESSAGE_DROPPED, m, NULL, "Internal error: unable to requeue this message during failover process", fd_msg_pmdl_get(m));
+ CHECK_FCT_DO(fd_msg_free(m), /* What can we do more? */)
+ } );
+ }
+
+ /* Requeue all routable sent requests */
+ fd_p_sr_failover(&peer->p_sr);
+
+ /* Done */
+ return;
+}
+
+/* Describe the current connection */
+int fd_peer_cnx_proto_info(struct peer_hdr *peer, char * buf, size_t len)
+{
+ struct fd_peer * p = (struct fd_peer *)peer;
+ TRACE_ENTRY("%p %p %zd", peer, buf, len);
+ CHECK_PARAMS(CHECK_PEER(peer) && buf && len);
+
+ if (p->p_cnxctx) {
+ CHECK_FCT(fd_cnx_proto_info(p->p_cnxctx, buf, len));
+ } else if (p->p_receiver) {
+ CHECK_FCT(fd_cnx_proto_info(p->p_receiver, buf, len));
+ } else {
+ snprintf(buf, len, "Not Connected");
+ }
+
+ return 0;
+}
+
+/* Return the value of srlist->cnt */
+int fd_peer_get_load_pending(struct peer_hdr *peer, long * to_receive, long * to_send)
+{
+ struct fd_peer * p = (struct fd_peer *)peer;
+ TRACE_ENTRY("%p %p %p", peer, to_receive, to_send);
+ CHECK_PARAMS(CHECK_PEER(peer));
+
+ if (to_receive) {
+ CHECK_POSIX( pthread_mutex_lock(&p->p_sr.mtx) );
+ *to_receive = p->p_sr.cnt;
+ CHECK_POSIX( pthread_mutex_unlock(&p->p_sr.mtx) );
+ }
+ if (to_send) {
+ CHECK_POSIX( pthread_mutex_lock(&p->p_state_mtx) );
+ *to_send = p->p_reqin_count;
+ CHECK_POSIX( pthread_mutex_unlock(&p->p_state_mtx) );
+ }
+
+ return 0;
+}
+
+
+/* Destroy a structure once cleanups have been performed (fd_psm_abord, ...) */
+int fd_peer_free(struct fd_peer ** ptr)
+{
+ struct fd_peer *p;
+
+ TRACE_ENTRY("%p", ptr);
+ CHECK_PARAMS(ptr);
+ p = *ptr;
+ *ptr = NULL;
+ CHECK_PARAMS(p);
+
+ CHECK_PARAMS( FD_IS_LIST_EMPTY(&p->p_hdr.chain) );
+
+ free_null(p->p_hdr.info.pi_diamid);
+
+ free_null(p->p_hdr.info.config.pic_realm);
+ free_null(p->p_hdr.info.config.pic_priority);
+
+ free_null(p->p_hdr.info.runtime.pir_realm);
+ free_null(p->p_hdr.info.runtime.pir_prodname);
+ free_list( &p->p_hdr.info.runtime.pir_apps );
+
+ free_list( &p->p_hdr.info.pi_endpoints );
+
+ free_null(p->p_dbgorig);
+
+ fd_list_unlink(&p->p_expiry);
+ fd_list_unlink(&p->p_actives);
+
+ CHECK_FCT_DO( fd_fifo_del(&p->p_tosend), /* continue */ );
+ CHECK_FCT_DO( fd_fifo_del(&p->p_tofailover), /* continue */ );
+ CHECK_POSIX_DO( pthread_mutex_destroy(&p->p_state_mtx), /* continue */);
+ CHECK_POSIX_DO( pthread_mutex_destroy(&p->p_sr.mtx), /* continue */);
+ CHECK_POSIX_DO( pthread_cond_destroy(&p->p_sr.cnd), /* continue */);
+
+ /* If the callback is still around... */
+ if (p->p_cb)
+ (*p->p_cb)(NULL, p->p_cb_data);
+
+ /* Free the structure */
+ free(p);
+ return 0;
+}
+
+/* Terminate peer module (destroy all peers, first gently, then violently) */
+int fd_peer_fini()
+{
+ struct fd_list * li;
+ struct fd_list purge = FD_LIST_INITIALIZER(purge); /* Store zombie peers here */
+ int list_empty;
+ struct timespec wait_until, now;
+
+ TRACE_ENTRY();
+
+ CHECK_FCT_DO(fd_p_expi_fini(), /* continue */);
+
+ TRACE_DEBUG(INFO, "Sending terminate signal to all peer connections");
+
+ CHECK_FCT_DO( pthread_rwlock_wrlock(&fd_g_peers_rw), /* continue */ );
+ for (li = fd_g_peers.next; li != &fd_g_peers; li = li->next) {
+ struct fd_peer * peer = (struct fd_peer *)li->o;
+
+ if (fd_peer_getstate(peer) != STATE_ZOMBIE) {
+ CHECK_FCT_DO( fd_psm_terminate(peer, "REBOOTING"), /* continue */ );
+ } else {
+ li = li->prev; /* to avoid breaking the loop */
+ fd_list_unlink(&peer->p_hdr.chain);
+ fd_list_insert_before(&purge, &peer->p_hdr.chain);
+ }
+ }
+ list_empty = FD_IS_LIST_EMPTY(&fd_g_peers);
+ CHECK_FCT_DO( pthread_rwlock_unlock(&fd_g_peers_rw), /* continue */ );
+
+ if (!list_empty) {
+ CHECK_SYS( clock_gettime(CLOCK_REALTIME, &now) );
+ fd_psm_start(); /* just in case */
+ TRACE_DEBUG(INFO, "Waiting for connections shutdown... (%d sec max)", DPR_TIMEOUT + 1);
+ wait_until.tv_sec = now.tv_sec + DPR_TIMEOUT + 1;
+ wait_until.tv_nsec = now.tv_nsec;
+ }
+
+ while ((!list_empty) && (TS_IS_INFERIOR(&now, &wait_until))) {
+
+ /* Allow the PSM(s) to execute */
+ usleep(100000);
+
+ /* Remove zombie peers */
+ CHECK_FCT_DO( pthread_rwlock_wrlock(&fd_g_peers_rw), /* continue */ );
+ for (li = fd_g_peers.next; li != &fd_g_peers; li = li->next) {
+ struct fd_peer * peer = (struct fd_peer *)li->o;
+ if (fd_peer_getstate(peer) == STATE_ZOMBIE) {
+ li = li->prev; /* to avoid breaking the loop */
+ fd_list_unlink(&peer->p_hdr.chain);
+ fd_list_insert_before(&purge, &peer->p_hdr.chain);
+ }
+ }
+ list_empty = FD_IS_LIST_EMPTY(&fd_g_peers);
+ CHECK_FCT_DO( pthread_rwlock_unlock(&fd_g_peers_rw), /* continue */ );
+ CHECK_SYS( clock_gettime(CLOCK_REALTIME, &now) );
+ }
+
+ if (!list_empty) {
+ TRACE_DEBUG(INFO, "Forcing connections shutdown");
+ CHECK_FCT_DO( pthread_rwlock_wrlock(&fd_g_peers_rw), /* continue */ );
+ while (!FD_IS_LIST_EMPTY(&fd_g_peers)) {
+ struct fd_peer * peer = (struct fd_peer *)(fd_g_peers.next->o);
+ fd_psm_abord(peer);
+ fd_list_unlink(&peer->p_hdr.chain);
+ fd_list_insert_before(&purge, &peer->p_hdr.chain);
+ }
+ CHECK_FCT_DO( pthread_rwlock_unlock(&fd_g_peers_rw), /* continue */ );
+ }
+
+ /* Free memory objects of all peers */
+ while (!FD_IS_LIST_EMPTY(&purge)) {
+ struct fd_peer * peer = (struct fd_peer *)(purge.next->o);
+ fd_list_unlink(&peer->p_hdr.chain);
+ fd_peer_free(&peer);
+ }
+
+ /* Now empty the validators list */
+ CHECK_FCT_DO( pthread_rwlock_wrlock(&validators_rw), /* continue */ );
+ while (!FD_IS_LIST_EMPTY( &validators )) {
+ struct fd_list * v = validators.next;
+ fd_list_unlink(v);
+ free(v);
+ }
+ CHECK_FCT_DO( pthread_rwlock_unlock(&validators_rw), /* continue */ );
+
+ return 0;
+}
+
+/* Dump info of one peer */
+DECLARE_FD_DUMP_PROTOTYPE(fd_peer_dump, struct peer_hdr * p, int details)
+{
+ FD_DUMP_HANDLE_OFFSET();
+
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{peer}(@%p): ", p), return NULL);
+
+ if (!CHECK_PEER(p)) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "INVALID/NULL"), return NULL);
+ } else {
+ struct fd_peer * peer = (struct fd_peer *)p;
+
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "%s [%s, cnt:%ldsr,%ldpa]", peer->p_hdr.info.pi_diamid, STATE_STR(fd_peer_getstate(peer)), peer->p_sr.cnt, peer->p_reqin_count), return NULL);
+ if (details > 0) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " rlm:%s", peer->p_hdr.info.runtime.pir_realm ?: "<unknown>"), return NULL);
+ if (peer->p_hdr.info.runtime.pir_prodname) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " ['%s' %u]", peer->p_hdr.info.runtime.pir_prodname, peer->p_hdr.info.runtime.pir_firmrev), return NULL);
+ }
+ }
+ if (details > 1) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " [from:%s] flags:%s%s%s%s%s%s%s%s lft:%ds",
+ peer->p_dbgorig ?: "unset",
+ peer->p_hdr.info.config.pic_flags.pro3 == PI_P3_DEFAULT ? "-" :
+ (peer->p_hdr.info.config.pic_flags.pro3 == PI_P3_IP ? "4" : "6"),
+ peer->p_hdr.info.config.pic_flags.pro4 == PI_P4_DEFAULT ? "-" :
+ (peer->p_hdr.info.config.pic_flags.pro4 == PI_P4_TCP ? "T" : "S"),
+ peer->p_hdr.info.config.pic_flags.alg ? "P" : "-",
+ peer->p_hdr.info.config.pic_flags.sec & PI_SEC_NONE ? "N" :"-",
+ peer->p_hdr.info.config.pic_flags.sec & PI_SEC_TLS_OLD ? "O" :"-",
+ peer->p_hdr.info.config.pic_flags.sctpsec & PI_SCTPSEC_3436 ? "3" :"-",
+ peer->p_hdr.info.config.pic_flags.exp ? "E" : "-",
+ peer->p_hdr.info.config.pic_flags.persist ? "P" : "-",
+ peer->p_hdr.info.config.pic_lft), return NULL);
+ }
+
+ }
+
+ return *buf;
+}
+
+/* Dump the list of peers */
+DECLARE_FD_DUMP_PROTOTYPE(fd_peer_dump_list, int details)
+{
+ struct fd_list * li;
+ FD_DUMP_HANDLE_OFFSET();
+
+ CHECK_FCT_DO( pthread_rwlock_rdlock(&fd_g_peers_rw), /* continue */ );
+
+ for (li = fd_g_peers.next; li != &fd_g_peers; li = li->next) {
+ CHECK_MALLOC_DO( fd_peer_dump(FD_DUMP_STD_PARAMS, (struct peer_hdr *)li->o, details), break);
+ if (li->next != &fd_g_peers) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "\n"), break);
+ }
+ }
+
+ CHECK_FCT_DO( pthread_rwlock_unlock(&fd_g_peers_rw), /* continue */ );
+ return *buf;
+}
+
+static struct dict_object *avp_oh_model = NULL;
+static pthread_mutex_t cache_avp_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Handle an incoming CER request on a new connection */
+int fd_peer_handle_newCER( struct msg ** cer, struct cnxctx ** cnx )
+{
+ struct msg * msg;
+ struct avp *avp_oh;
+ struct avp_hdr * avp_hdr;
+ struct fd_list * li, *li_inf;
+ int found = 0;
+ int ret = 0;
+ struct fd_peer * peer;
+ struct cnx_incoming * ev_data;
+
+ TRACE_ENTRY("%p %p", cer, cnx);
+ CHECK_PARAMS(cer && *cer && cnx && *cnx);
+
+ msg = *cer;
+
+ /* If needed, resolve the dictionary model for Origin-Host */
+ CHECK_POSIX( pthread_mutex_lock(&cache_avp_lock) );
+ if (!avp_oh_model) {
+ avp_code_t code = AC_ORIGIN_HOST;
+ CHECK_FCT_DO( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_CODE, &code, &avp_oh_model, ENOENT),
+ { LOG_E("Cannot find Origin-Host AVP definition in the dictionary!"); (void) pthread_mutex_unlock(&cache_avp_lock); return __ret__; } );
+ }
+ CHECK_POSIX( pthread_mutex_unlock(&cache_avp_lock) );
+
+ /* Find the Diameter Identity of the remote peer in the message */
+ CHECK_FCT( fd_msg_search_avp ( msg, avp_oh_model, &avp_oh ) );
+ ASSERT(avp_oh); /* otherwise it should not have passed rules validation, right? */
+ CHECK_FCT( fd_msg_avp_hdr ( avp_oh, &avp_hdr ) );
+
+ /* First, check if the Origin-Host value is valid */
+ if (!fd_os_is_valid_DiameterIdentity(avp_hdr->avp_value->os.data, avp_hdr->avp_value->os.len)) {
+ CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, cer, MSGFL_ANSW_ERROR ) );
+ CHECK_FCT( fd_msg_rescode_set(*cer, "DIAMETER_INVALID_AVP_VALUE",
+ "Your Origin-Host contains invalid characters.", avp_oh, 1 ) );
+
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, *cer, NULL, "Received CER with invalid Origin-Host AVP", NULL);
+
+ CHECK_FCT( fd_out_send(cer, *cnx, NULL, 0) );
+ return EINVAL;
+ }
+
+ /* Search if we already have this peer id in our list. We take directly the write lock so that we don't need to upgrade if it is a new peer.
+ * There is space for a small optimization here if needed.
+ */
+ CHECK_POSIX( pthread_rwlock_wrlock(&fd_g_peers_rw) );
+
+ li_inf = &fd_g_peers;
+ for (li = fd_g_peers.next; li != &fd_g_peers; li = li->next) {
+ int cmp, cont;
+ peer = (struct fd_peer *)li;
+ cmp = fd_os_almostcasesrch( avp_hdr->avp_value->os.data, avp_hdr->avp_value->os.len, peer->p_hdr.info.pi_diamid, peer->p_hdr.info.pi_diamidlen, &cont );
+ if (cmp > 0) {
+ li_inf = li;
+ }
+ if (cmp == 0) {
+ found = 1;
+ break;
+ }
+ if (!cont)
+ break;
+ }
+
+ if (!found) {
+ /* Create a new peer entry for this new remote peer */
+ peer = NULL;
+ CHECK_FCT_DO( ret = fd_peer_alloc(&peer), goto out );
+
+ /* Set the peer Diameter Id and the responder flag parameters */
+ CHECK_MALLOC_DO( peer->p_hdr.info.pi_diamid = os0dup(avp_hdr->avp_value->os.data, avp_hdr->avp_value->os.len),
+ { ret = ENOMEM; goto out; } );
+ peer->p_hdr.info.pi_diamidlen = avp_hdr->avp_value->os.len;
+ CHECK_MALLOC_DO( peer->p_dbgorig = strdup(fd_cnx_getid(*cnx)), { ret = ENOMEM; goto out; } );
+ peer->p_flags.pf_responder = 1;
+ peer->p_flags.pf_delete = 1;
+
+ LOG_D("Created new peer object for incoming CER: %s", peer->p_hdr.info.pi_diamid);
+
+#ifndef DISABLE_PEER_EXPIRY
+ /* Set this peer to expire on inactivity */
+ peer->p_hdr.info.config.pic_flags.exp = PI_EXP_INACTIVE;
+ peer->p_hdr.info.config.pic_lft = 3600; /* 1 hour without any message
+ -- RFC3539 states that this must not be inferior to BRINGDOWN_INTERVAL = 5 minutes */
+
+ CHECK_FCT_DO( ret = fd_p_expi_update( peer ), goto out );
+#endif /* DISABLE_PEER_EXPIRY */
+
+ /* Insert the new peer in the list (the PSM will take care of setting the expiry after validation) */
+ fd_list_insert_after( li_inf, &peer->p_hdr.chain );
+
+ /* Start the PSM, which will receive the event below */
+ CHECK_FCT_DO( ret = fd_psm_begin(peer), goto out );
+ } else {
+ /* Check if the peer is in zombie state */
+ if (fd_peer_getstate(peer) == STATE_ZOMBIE) {
+ /* Re-activate the peer */
+ if (peer->p_hdr.info.config.pic_flags.exp)
+ peer->p_flags.pf_responder = 1;
+ CHECK_POSIX_DO( pthread_mutex_lock(&peer->p_state_mtx), );
+ peer->p_state = STATE_NEW;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&peer->p_state_mtx), );
+ peer->p_flags.pf_localterm = 0;
+ CHECK_FCT_DO( ret = fd_psm_begin(peer), goto out );
+ }
+ }
+
+ /* Send the new connection event to the PSM */
+ CHECK_MALLOC_DO( ev_data = malloc(sizeof(struct cnx_incoming)), { ret = ENOMEM; goto out; } );
+ memset(ev_data, 0, sizeof(*ev_data));
+
+ ev_data->cer = msg;
+ ev_data->cnx = *cnx;
+ ev_data->validate = !found;
+
+ CHECK_FCT_DO( ret = fd_event_send(peer->p_events, FDEVP_CNX_INCOMING, sizeof(*ev_data), ev_data), goto out );
+
+out:
+ CHECK_POSIX( pthread_rwlock_unlock(&fd_g_peers_rw) );
+
+ if (ret == 0) {
+ /* Reset the "out" parameters, so that they are not cleanup on function return. */
+ *cer = NULL;
+ *cnx = NULL;
+ } else {
+ char buf[1024];
+ snprintf(buf, sizeof(buf), "An error occurred while processing new incoming CER: %s", strerror(ret));
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, *cer, NULL, buf, NULL);
+ }
+
+ return ret;
+}
+
+/* Save a callback to accept / reject incoming unknown peers */
+int fd_peer_validate_register ( int (*peer_validate)(struct peer_info * /* info */, int * /* auth */, int (**cb2)(struct peer_info *)) )
+{
+ struct fd_list * v;
+
+ TRACE_ENTRY("%p", peer_validate);
+ CHECK_PARAMS(peer_validate);
+
+ /* Alloc a new entry */
+ CHECK_MALLOC( v = malloc(sizeof(struct fd_list)) );
+ fd_list_init( v, peer_validate );
+
+ /* Add at the beginning of the list */
+ CHECK_FCT( pthread_rwlock_wrlock(&validators_rw) );
+ fd_list_insert_after(&validators, v);
+ CHECK_FCT( pthread_rwlock_unlock(&validators_rw));
+
+ /* Done! */
+ return 0;
+}
+
+/* Validate a peer by calling the callbacks in turn -- return 0 if the peer is validated, ! 0 in case of error (>0) or if the peer is rejected (-1) */
+int fd_peer_validate( struct fd_peer * peer )
+{
+ int ret = 0;
+ struct fd_list * v;
+
+ CHECK_FCT( pthread_rwlock_rdlock(&validators_rw) );
+ for (v = validators.next; v != &validators; v = v->next) {
+ int auth = 0;
+ pthread_cleanup_push(fd_cleanup_rwlock, &validators_rw);
+ CHECK_FCT_DO( ret = ((int(*)(struct peer_info *, int *, int (**)(struct peer_info *)))(v->o)) (&peer->p_hdr.info, &auth, &peer->p_cb2), );
+ pthread_cleanup_pop(0);
+ if (ret)
+ goto out;
+ if (auth) {
+ ret = (auth > 0) ? 0 : -1;
+ goto out;
+ }
+ peer->p_cb2 = NULL;
+ }
+
+ /* No callback has given a firm result, the default is to reject */
+ ret = -1;
+out:
+ CHECK_FCT( pthread_rwlock_unlock(&validators_rw));
+ return ret;
+}
diff --git a/libfdcore/queues.c b/libfdcore/queues.c
new file mode 100644
index 0000000..b317d34
--- /dev/null
+++ b/libfdcore/queues.c
@@ -0,0 +1,84 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/* The global message queues */
+struct fifo * fd_g_incoming = NULL;
+struct fifo * fd_g_outgoing = NULL;
+struct fifo * fd_g_local = NULL;
+
+/* Initialize the message queues. */
+int fd_queues_init(void)
+{
+ TRACE_ENTRY();
+ CHECK_FCT( fd_fifo_new ( &fd_g_incoming, 20 ) );
+ CHECK_FCT( fd_fifo_new ( &fd_g_outgoing, 30 ) );
+ CHECK_FCT( fd_fifo_new ( &fd_g_local, 25 ) );
+ return 0;
+}
+
+/* Destroy a queue after emptying it (and dumping the content) */
+int fd_queues_fini(struct fifo ** queue)
+{
+ struct msg * msg;
+ int ret = 0;
+
+ TRACE_ENTRY("%p", queue);
+
+ /* Note : the threads that post into this queue should already been stopped before this !!! */
+
+ CHECK_PARAMS(queue);
+ if (*queue == NULL)
+ return 0; /* the queue was not already initialized */
+
+ /* Empty all contents */
+ while (1) {
+ /* Check if there is a message in the queue */
+ ret = fd_fifo_tryget(*queue, &msg);
+ if (ret == EWOULDBLOCK)
+ break;
+ CHECK_FCT(ret);
+
+ /* We got one! */
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msg, NULL, "Message lost because framework is terminating.", fd_msg_pmdl_get(msg));
+ fd_msg_free(msg);
+ }
+
+ /* Now, delete the empty queue */
+ CHECK_FCT( fd_fifo_del ( queue ) );
+
+ return 0;
+}
diff --git a/libfdcore/routing_dispatch.c b/libfdcore/routing_dispatch.c
new file mode 100644
index 0000000..93df065
--- /dev/null
+++ b/libfdcore/routing_dispatch.c
@@ -0,0 +1,1328 @@
+/*********************************************************************************************************
+* 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"
+
+/********************************************************************************/
+/* First part : handling the extensions callbacks */
+/********************************************************************************/
+
+/* Lists of the callbacks, and locks to protect them */
+static pthread_rwlock_t rt_fwd_lock = PTHREAD_RWLOCK_INITIALIZER;
+static struct fd_list rt_fwd_list = FD_LIST_INITIALIZER_O(rt_fwd_list, &rt_fwd_lock);
+
+static pthread_rwlock_t rt_out_lock = PTHREAD_RWLOCK_INITIALIZER;
+static struct fd_list rt_out_list = FD_LIST_INITIALIZER_O(rt_out_list, &rt_out_lock);
+
+/* Items in the lists are the same */
+struct rt_hdl {
+ struct fd_list chain; /* link in the rt_fwd_list or rt_out_list */
+ void * cbdata; /* the registered data */
+ union {
+ int order; /* This value is used to sort the list */
+ int dir; /* It is the direction for FWD handlers */
+ int prio; /* and the priority for OUT handlers */
+ };
+ union {
+ int (*rt_fwd_cb)(void * cbdata, struct msg ** msg);
+ int (*rt_out_cb)(void * cbdata, struct msg ** msg, struct fd_list * candidates);
+ };
+};
+
+/* Add a new entry in the list */
+static int add_ordered(struct rt_hdl * new, struct fd_list * list)
+{
+ /* The list is ordered by prio parameter */
+ struct fd_list * li;
+
+ CHECK_POSIX( pthread_rwlock_wrlock(list->o) );
+
+ for (li = list->next; li != list; li = li->next) {
+ struct rt_hdl * h = (struct rt_hdl *) li;
+ if (new->order <= h->order)
+ break;
+ }
+
+ fd_list_insert_before(li, &new->chain);
+
+ CHECK_POSIX( pthread_rwlock_unlock(list->o) );
+
+ return 0;
+}
+
+/* Register a new FWD callback */
+int fd_rt_fwd_register ( int (*rt_fwd_cb)(void * cbdata, struct msg ** msg), void * cbdata, enum fd_rt_fwd_dir dir, struct fd_rt_fwd_hdl ** handler )
+{
+ struct rt_hdl * new;
+
+ TRACE_ENTRY("%p %p %d %p", rt_fwd_cb, cbdata, dir, handler);
+ CHECK_PARAMS( rt_fwd_cb );
+ CHECK_PARAMS( (dir >= RT_FWD_REQ) && ( dir <= RT_FWD_ANS) );
+
+ /* Create a new container */
+ CHECK_MALLOC(new = malloc(sizeof(struct rt_hdl)));
+ memset(new, 0, sizeof(struct rt_hdl));
+
+ /* Write the content */
+ fd_list_init(&new->chain, NULL);
+ new->cbdata = cbdata;
+ new->dir = dir;
+ new->rt_fwd_cb = rt_fwd_cb;
+
+ /* Save this in the list */
+ CHECK_FCT( add_ordered(new, &rt_fwd_list) );
+
+ /* Give it back to the extension if needed */
+ if (handler)
+ *handler = (void *)new;
+
+ return 0;
+}
+
+/* Remove it */
+int fd_rt_fwd_unregister ( struct fd_rt_fwd_hdl * handler, void ** cbdata )
+{
+ struct rt_hdl * del;
+ TRACE_ENTRY( "%p %p", handler, cbdata);
+ CHECK_PARAMS( handler );
+
+ del = (struct rt_hdl *)handler;
+ CHECK_PARAMS( del->chain.head == &rt_fwd_list );
+
+ /* Unlink */
+ CHECK_POSIX( pthread_rwlock_wrlock(&rt_fwd_lock) );
+ fd_list_unlink(&del->chain);
+ CHECK_POSIX( pthread_rwlock_unlock(&rt_fwd_lock) );
+
+ if (cbdata)
+ *cbdata = del->cbdata;
+
+ free(del);
+ return 0;
+}
+
+/* Register a new OUT callback */
+int fd_rt_out_register ( int (*rt_out_cb)(void * cbdata, struct msg ** pmsg, struct fd_list * candidates), void * cbdata, int priority, struct fd_rt_out_hdl ** handler )
+{
+ struct rt_hdl * new;
+
+ TRACE_ENTRY("%p %p %d %p", rt_out_cb, cbdata, priority, handler);
+ CHECK_PARAMS( rt_out_cb );
+
+ /* Create a new container */
+ CHECK_MALLOC(new = malloc(sizeof(struct rt_hdl)));
+ memset(new, 0, sizeof(struct rt_hdl));
+
+ /* Write the content */
+ fd_list_init(&new->chain, NULL);
+ new->cbdata = cbdata;
+ new->prio = priority;
+ new->rt_out_cb = rt_out_cb;
+
+ /* Save this in the list */
+ CHECK_FCT( add_ordered(new, &rt_out_list) );
+
+ /* Give it back to the extension if needed */
+ if (handler)
+ *handler = (void *)new;
+
+ return 0;
+}
+
+/* Remove it */
+int fd_rt_out_unregister ( struct fd_rt_out_hdl * handler, void ** cbdata )
+{
+ struct rt_hdl * del;
+ TRACE_ENTRY( "%p %p", handler, cbdata);
+ CHECK_PARAMS( handler );
+
+ del = (struct rt_hdl *)handler;
+ CHECK_PARAMS( del->chain.head == &rt_out_list );
+
+ /* Unlink */
+ CHECK_POSIX( pthread_rwlock_wrlock(&rt_out_lock) );
+ fd_list_unlink(&del->chain);
+ CHECK_POSIX( pthread_rwlock_unlock(&rt_out_lock) );
+
+ if (cbdata)
+ *cbdata = del->cbdata;
+
+ free(del);
+ return 0;
+}
+
+/********************************************************************************/
+/* Some default OUT routing callbacks */
+/********************************************************************************/
+
+/* Prevent sending to peers that do not support the message application */
+static int dont_send_if_no_common_app(void * cbdata, struct msg ** pmsg, struct fd_list * candidates)
+{
+ struct msg * msg = *pmsg;
+ struct fd_list * li;
+ struct msg_hdr * hdr;
+
+ TRACE_ENTRY("%p %p %p", cbdata, msg, candidates);
+ CHECK_PARAMS(msg && candidates);
+
+ CHECK_FCT( fd_msg_hdr(msg, &hdr) );
+
+ /* For Base Diameter Protocol, every peer is supposed to support it, so skip */
+ if (hdr->msg_appl == 0)
+ return 0;
+
+ /* Otherwise, check that the peers support the application */
+ for (li = candidates->next; li != candidates; li = li->next) {
+ struct rtd_candidate *c = (struct rtd_candidate *) li;
+ struct fd_peer * peer;
+ struct fd_app *found;
+ CHECK_FCT( fd_peer_getbyid( c->diamid, c->diamidlen, 0, (void *)&peer ) );
+ if (peer && !peer->p_hdr.info.runtime.pir_relay) {
+ /* Check if the remote peer advertised the message's appli */
+ CHECK_FCT( fd_app_check(&peer->p_hdr.info.runtime.pir_apps, hdr->msg_appl, &found) );
+ if (!found)
+ c->score += FD_SCORE_NO_DELIVERY;
+ }
+ }
+
+ return 0;
+}
+
+/* Detect if the Destination-Host and Destination-Realm match the peer */
+static int score_destination_avp(void * cbdata, struct msg ** pmsg, struct fd_list * candidates)
+{
+ struct msg * msg = *pmsg;
+ struct fd_list * li;
+ struct avp * avp;
+ union avp_value *dh = NULL, *dr = NULL;
+
+ TRACE_ENTRY("%p %p %p", cbdata, msg, candidates);
+ CHECK_PARAMS(msg && candidates);
+
+ /* Search the Destination-Host and Destination-Realm AVPs -- we could also use fd_msg_search_avp here, but this one is slightly more efficient */
+ CHECK_FCT( fd_msg_browse(msg, MSG_BRW_FIRST_CHILD, &avp, NULL) );
+ while (avp) {
+ struct avp_hdr * ahdr;
+ CHECK_FCT( fd_msg_avp_hdr( avp, &ahdr ) );
+
+ if (! (ahdr->avp_flags & AVP_FLAG_VENDOR)) {
+ switch (ahdr->avp_code) {
+ case AC_DESTINATION_HOST:
+ /* Parse this AVP */
+ CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) );
+ ASSERT( ahdr->avp_value );
+ dh = ahdr->avp_value;
+ break;
+
+ case AC_DESTINATION_REALM:
+ /* Parse this AVP */
+ CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) );
+ ASSERT( ahdr->avp_value );
+ dr = ahdr->avp_value;
+ break;
+ }
+ }
+
+ if (dh && dr)
+ break;
+
+ /* Go to next AVP */
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL) );
+ }
+
+ /* Now, check each candidate against these AVP values */
+ for (li = candidates->next; li != candidates; li = li->next) {
+ struct rtd_candidate *c = (struct rtd_candidate *) li;
+
+ #if 0 /* this is actually useless since the sending process will also ensure that the peer is still available */
+ struct fd_peer * peer;
+ /* Since the candidates list comes from the peers list, we do not have any issue with upper/lower case to find the peer object */
+ CHECK_FCT( fd_peer_getbyid( c->diamid, c->diamidlen, 0, (void *)&peer ) );
+ if (!peer)
+ continue; /* it has been deleted since the candidate list was generated; avoid sending to this one in that case. */
+ #endif /* 0 */
+
+ /* In the AVPs, the value comes from the network, so let's be case permissive */
+ if (dh && !fd_os_almostcasesrch(dh->os.data, dh->os.len, c->diamid, c->diamidlen, NULL) ) {
+ /* The candidate is the Destination-Host */
+ c->score += FD_SCORE_FINALDEST;
+ } else {
+ if (dr && !fd_os_almostcasesrch(dr->os.data, dr->os.len, c->realm, c->realmlen, NULL) ) {
+ /* The candidate's realm matchs the Destination-Realm */
+ c->score += FD_SCORE_REALM;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/********************************************************************************/
+/* Helper functions */
+/********************************************************************************/
+
+/* Find (first) '!' and '@' positions in a UTF-8 encoded string (User-Name AVP value) */
+static void nai_get_indexes(union avp_value * un, int * excl_idx, int * at_idx)
+{
+ int i;
+
+ TRACE_ENTRY("%p %p %p", un, excl_idx, at_idx);
+ CHECK_PARAMS_DO( un && excl_idx && at_idx, return );
+
+ *excl_idx = 0;
+ *at_idx = 0;
+
+ /* Search if there is a '!' before any '@' -- do we need to check it contains a '.' ? */
+ for (i = 0; i < un->os.len; i++) {
+ /* The '!' marks the decorated NAI */
+ if ( un->os.data[i] == (unsigned char) '!' ) {
+ if (!*excl_idx)
+ *excl_idx = i;
+ continue;
+ }
+ /* If we reach the realm part, we can stop */
+ if ( un->os.data[i] == (unsigned char) '@' ) {
+ *at_idx = i;
+ break;
+ }
+ /* Stop if we find a \0 in the middle */
+ if ( un->os.data[i] == 0 ) {
+ return;
+ }
+ /* Skip escaped characters */
+ if ( un->os.data[i] == (unsigned char) '\\' ) {
+ i++;
+ continue;
+ }
+ }
+
+ return;
+}
+
+/* Test if a User-Name AVP contains a Decorated NAI -- RFC4282, RFC5729 */
+/* Create new User-Name and Destination-Realm values */
+static int process_decorated_NAI(int * was_nai, union avp_value * un, union avp_value * dr)
+{
+ int at_idx, sep_idx;
+ unsigned char * old_un;
+ TRACE_ENTRY("%p %p %p", was_nai, un, dr);
+ CHECK_PARAMS(was_nai && un && dr);
+
+ /* Save the decorated User-Name, for example 'homerealm.example.net!user@otherrealm.example.net' */
+ old_un = un->os.data;
+
+ /* Search the positions of the first '!' and the '@' in the string */
+ nai_get_indexes(un, &sep_idx, &at_idx);
+ if ((!sep_idx) || (sep_idx > at_idx) || !fd_os_is_valid_DiameterIdentity(old_un, sep_idx /* this is the new realm part */)) {
+ *was_nai = 0;
+ return 0;
+ }
+
+ *was_nai = 1;
+
+ /* Create the new User-Name value */
+ CHECK_MALLOC( un->os.data = malloc( at_idx ) );
+ memcpy( un->os.data, old_un + sep_idx + 1, at_idx - sep_idx ); /* user@ */
+ memcpy( un->os.data + at_idx - sep_idx, old_un, sep_idx ); /* homerealm.example.net */
+
+ /* Create the new Destination-Realm value */
+ CHECK_MALLOC( dr->os.data = realloc(dr->os.data, sep_idx) );
+ memcpy( dr->os.data, old_un, sep_idx );
+ dr->os.len = sep_idx;
+
+ TRACE_DEBUG(FULL, "Processed Decorated NAI : '%.*s' became '%.*s' (%.*s)",
+ (int)un->os.len, old_un,
+ (int)at_idx, un->os.data,
+ (int)dr->os.len, dr->os.data);
+
+ un->os.len = at_idx;
+ free(old_un);
+
+ return 0;
+}
+
+
+/* Function to return an error to an incoming request */
+static int return_error(struct msg ** pmsg, char * error_code, char * error_message, struct avp * failedavp)
+{
+ struct fd_peer * peer;
+ int is_loc = 0;
+
+ /* Get the source of the message */
+ {
+ DiamId_t id;
+ size_t idlen;
+ CHECK_FCT( fd_msg_source_get( *pmsg, &id, &idlen ) );
+
+ if (id == NULL) {
+ is_loc = 1; /* The message was issued locally */
+ } else {
+
+ /* Search the peer with this id */
+ CHECK_FCT( fd_peer_getbyid( id, idlen, 0, (void *)&peer ) );
+
+ if (!peer) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Unable to send error '%s' to deleted peer '%s' in reply to this message.", error_code, id);
+ fd_hook_call(HOOK_MESSAGE_DROPPED, *pmsg, NULL, buf, fd_msg_pmdl_get(*pmsg));
+ fd_msg_free(*pmsg);
+ *pmsg = NULL;
+ return 0;
+ }
+ }
+ }
+
+ /* Create the error message */
+ CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, pmsg, MSGFL_ANSW_ERROR ) );
+
+ /* Set the error code */
+ CHECK_FCT( fd_msg_rescode_set(*pmsg, error_code, error_message, failedavp, 1 ) );
+
+ /* Send the answer */
+ if (is_loc) {
+ CHECK_FCT( fd_fifo_post(fd_g_incoming, pmsg) );
+ } else {
+ CHECK_FCT( fd_out_send(pmsg, NULL, peer, 1) );
+ }
+
+ /* Done */
+ return 0;
+}
+
+
+/****************************************************************************/
+/* Second part : threads moving messages in the daemon */
+/****************************************************************************/
+
+/* The DISPATCH message processing */
+static int msg_dispatch(struct msg * msg)
+{
+ struct msg_hdr * hdr;
+ int is_req = 0;
+ struct session * sess;
+ enum disp_action action;
+ char * ec = NULL;
+ char * em = NULL;
+ struct msg *msgptr = msg, *error = NULL;
+
+ /* Read the message header */
+ CHECK_FCT( fd_msg_hdr(msg, &hdr) );
+ is_req = hdr->msg_flags & CMD_FLAG_REQUEST;
+
+ /* Note: if the message is for local delivery, we should test for duplicate
+ (draft-asveren-dime-dupcons-00). This may conflict with path validation decisions, no clear answer yet */
+
+ /* At this point, we need to understand the message content, so parse it */
+ CHECK_FCT_DO( fd_msg_parse_or_error( &msgptr, &error ),
+ {
+ int rescue = 0;
+ if (__ret__ != EBADMSG) {
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msgptr, NULL, "Error while parsing received answer", fd_msg_pmdl_get(msgptr));
+ fd_msg_free(msgptr);
+ } else {
+ if (!msgptr) {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR2, error, NULL, NULL, fd_msg_pmdl_get(error));
+ /* error now contains the answer message to send back */
+ CHECK_FCT( fd_fifo_post(fd_g_outgoing, &error) );
+ } else if (!error) {
+ /* We have received an invalid answer to our query */
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msgptr, NULL, "Received answer failed the dictionary / rules parsing", fd_msg_pmdl_get(msgptr));
+ fd_msg_free(msgptr);
+ } else {
+ /* We will pass the invalid received error to the application */
+ rescue = 1;
+ }
+ }
+ if (!rescue)
+ return 0; /* We are done with this message, go to the next */
+ } );
+
+ /* First, if the original request was registered with a callback and we receive the answer, call it. */
+ if ( ! is_req ) {
+ struct msg * qry;
+ void (*anscb)(void *, struct msg **) = NULL;
+ void * data = NULL;
+
+ /* Retrieve the corresponding query */
+ CHECK_FCT( fd_msg_answ_getq( msgptr, &qry ) );
+
+ /* Retrieve any registered handler */
+ CHECK_FCT( fd_msg_anscb_get( qry, &anscb, NULL, &data ) );
+
+ /* If a callback was registered, pass the message to it */
+ if (anscb != NULL) {
+
+ TRACE_DEBUG(FULL, "Calling callback registered when query was sent (%p, %p)", anscb, data);
+ (*anscb)(data, &msgptr);
+
+ /* If the message is processed, we're done */
+ if (msgptr == NULL) {
+ return 0;
+ }
+
+ /* otherwise continue the dispatching --hoping that the anscb callback did not mess with our message :) */
+ }
+ }
+
+ /* Retrieve the session of the message */
+ CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, msgptr, &sess, NULL) );
+
+ /* Now, call any callback registered for the message */
+ CHECK_FCT( fd_msg_dispatch ( &msgptr, sess, &action, &ec, &em, &error) );
+
+ /* Now, act depending on msg and action and ec */
+ if (msgptr) {
+ switch ( action ) {
+ case DISP_ACT_CONT:
+ /* No callback has handled the message, let's reply with a generic error or relay it */
+ if (!fd_g_config->cnf_flags.no_fwd) {
+ /* requeue to fd_g_outgoing */
+ fd_hook_call(HOOK_MESSAGE_ROUTING_FORWARD, msgptr, NULL, NULL, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( fd_fifo_post(fd_g_outgoing, &msgptr) );
+ break;
+ }
+ /* We don't relay => reply error */
+ em = "The message was not handled by any extension callback";
+ ec = "DIAMETER_COMMAND_UNSUPPORTED";
+ /* and continue as if an error occurred... */
+ case DISP_ACT_ERROR:
+ /* We have a problem with delivering the message */
+ if (ec == NULL) {
+ ec = "DIAMETER_UNABLE_TO_COMPLY";
+ }
+
+ if (!is_req) {
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msgptr, NULL, "Internal error: Answer received to locally issued request, but not handled by any handler.", fd_msg_pmdl_get(msgptr));
+ fd_msg_free(msgptr);
+ break;
+ }
+
+ /* Create an answer with the error code and message */
+ CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, &msgptr, 0 ) );
+ CHECK_FCT( fd_msg_rescode_set(msgptr, ec, em, NULL, 1 ) );
+
+ case DISP_ACT_SEND:
+ /* Now, send the message */
+ CHECK_FCT( fd_fifo_post(fd_g_outgoing, &msgptr) );
+ }
+ } else if (em) {
+ fd_hook_call(HOOK_MESSAGE_DROPPED, error, NULL, em, fd_msg_pmdl_get(error));
+ fd_msg_free(error);
+ }
+
+ /* We're done with dispatching this message */
+ return 0;
+}
+
+/* The ROUTING-IN message processing */
+static int msg_rt_in(struct msg * msg)
+{
+ struct msg_hdr * hdr;
+ int is_req = 0;
+ int is_err = 0;
+ DiamId_t qry_src = NULL;
+ struct msg *msgptr = msg;
+
+ /* Read the message header */
+ CHECK_FCT( fd_msg_hdr(msg, &hdr) );
+ is_req = hdr->msg_flags & CMD_FLAG_REQUEST;
+ is_err = hdr->msg_flags & CMD_FLAG_ERROR;
+
+ /* Handle incorrect bits */
+ if (is_req && is_err) {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, "R & E bits were set", fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, "DIAMETER_INVALID_HDR_BITS", "R & E bits were set", NULL) );
+ return 0;
+ }
+
+ /* If it is a request, we must analyze its content to decide what we do with it */
+ if (is_req) {
+ struct avp * avp, *un = NULL;
+ union avp_value * un_val = NULL, *dr_val = NULL;
+ enum status { UNKNOWN, YES, NO };
+ /* Are we Destination-Host? */
+ enum status is_dest_host = UNKNOWN;
+ /* Are we Destination-Realm? */
+ enum status is_dest_realm = UNKNOWN;
+ /* Do we support the application of the message? */
+ enum status is_local_app = UNKNOWN;
+
+ /* Check if we have local support for the message application */
+ if ( (hdr->msg_appl == 0) || (hdr->msg_appl == AI_RELAY) ) {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, "Received a routable message with application id 0 or " _stringize(AI_RELAY) " (relay)", fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, "DIAMETER_APPLICATION_UNSUPPORTED", "Routable message with application id 0 or relay", NULL) );
+ return 0;
+ } else {
+ struct fd_app * app;
+ CHECK_FCT( fd_app_check(&fd_g_config->cnf_apps, hdr->msg_appl, &app) );
+ is_local_app = (app ? YES : NO);
+ }
+
+ /* Parse the message for Dest-Host, Dest-Realm, and Route-Record */
+ CHECK_FCT( fd_msg_browse(msgptr, MSG_BRW_FIRST_CHILD, &avp, NULL) );
+ while (avp) {
+ struct avp_hdr * ahdr;
+ struct fd_pei error_info;
+ int ret;
+
+ memset(&error_info, 0, sizeof(struct fd_pei));
+
+ CHECK_FCT( fd_msg_avp_hdr( avp, &ahdr ) );
+
+ if (! (ahdr->avp_flags & AVP_FLAG_VENDOR)) {
+ switch (ahdr->avp_code) {
+ case AC_DESTINATION_HOST:
+ /* Parse this AVP */
+ CHECK_FCT_DO( ret = fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, &error_info ),
+ {
+ if (error_info.pei_errcode) {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, error_info.pei_message ?: error_info.pei_errcode, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, error_info.pei_errcode, error_info.pei_message, error_info.pei_avp) );
+ if (error_info.pei_avp_free) { fd_msg_free(error_info.pei_avp); }
+ return 0;
+ } else {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, "Unspecified error while parsing Destination-Host AVP", fd_msg_pmdl_get(msgptr));
+ return ret;
+ }
+ } );
+ ASSERT( ahdr->avp_value );
+ /* Compare the Destination-Host AVP of the message with our identity */
+ if (!fd_os_almostcasesrch(ahdr->avp_value->os.data, ahdr->avp_value->os.len, fd_g_config->cnf_diamid, fd_g_config->cnf_diamid_len, NULL)) {
+ is_dest_host = YES;
+ } else {
+ is_dest_host = NO;
+ }
+ break;
+
+ case AC_DESTINATION_REALM:
+ /* Parse this AVP */
+ CHECK_FCT_DO( ret = fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, &error_info ),
+ {
+ if (error_info.pei_errcode) {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, error_info.pei_message ?: error_info.pei_errcode, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, error_info.pei_errcode, error_info.pei_message, error_info.pei_avp) );
+ if (error_info.pei_avp_free) { fd_msg_free(error_info.pei_avp); }
+ return 0;
+ } else {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, "Unspecified error while parsing Destination-Realm AVP", fd_msg_pmdl_get(msgptr));
+ return ret;
+ }
+ } );
+ ASSERT( ahdr->avp_value );
+ dr_val = ahdr->avp_value;
+ /* Compare the Destination-Realm AVP of the message with our identity */
+ if (!fd_os_almostcasesrch(dr_val->os.data, dr_val->os.len, fd_g_config->cnf_diamrlm, fd_g_config->cnf_diamrlm_len, NULL)) {
+ is_dest_realm = YES;
+ } else {
+ is_dest_realm = NO;
+ }
+ break;
+
+ /* we also use User-Name for decorated NAI */
+ case AC_USER_NAME:
+ /* Parse this AVP */
+ CHECK_FCT_DO( ret = fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, &error_info ),
+ {
+ if (error_info.pei_errcode) {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, error_info.pei_message ?: error_info.pei_errcode, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, error_info.pei_errcode, error_info.pei_message, error_info.pei_avp) );
+ if (error_info.pei_avp_free) { fd_msg_free(error_info.pei_avp); }
+ return 0;
+ } else {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, "Unspecified error while parsing User-Name AVP", fd_msg_pmdl_get(msgptr));
+ return ret;
+ }
+ } );
+ ASSERT( ahdr->avp_value );
+ un = avp;
+ un_val = ahdr->avp_value;
+ break;
+
+ case AC_ROUTE_RECORD:
+ /* Parse this AVP */
+ CHECK_FCT_DO( ret = fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, &error_info ),
+ {
+ if (error_info.pei_errcode) {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, error_info.pei_message ?: error_info.pei_errcode, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, error_info.pei_errcode, error_info.pei_message, error_info.pei_avp) );
+ if (error_info.pei_avp_free) { fd_msg_free(error_info.pei_avp); }
+ return 0;
+ } else {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, "Unspecified error while parsing Route-Record AVP", fd_msg_pmdl_get(msgptr));
+ return ret;
+ }
+ } );
+ ASSERT( ahdr->avp_value );
+ /* Is this our own name ? */
+ if (!fd_os_almostcasesrch(ahdr->avp_value->os.data, ahdr->avp_value->os.len, fd_g_config->cnf_diamid, fd_g_config->cnf_diamid_len, NULL)) {
+ /* Yes: then we must return DIAMETER_LOOP_DETECTED according to Diameter RFC */
+ char * error = "DIAMETER_LOOP_DETECTED";
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, error, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, error, NULL, NULL) );
+ return 0;
+ }
+ break;
+
+
+ }
+ }
+
+ /* Stop when we found all 3 AVPs -- they are supposed to be at the beginning of the message, so this should be fast */
+ if ((is_dest_host != UNKNOWN) && (is_dest_realm != UNKNOWN) && un)
+ break;
+
+ /* Go to next AVP */
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL) );
+ }
+
+ /* OK, now decide what we do with the request */
+
+ /* Handle the missing routing AVPs first */
+ if ( is_dest_realm == UNKNOWN ) {
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, "Non-routable message not supported (invalid bit ? missing Destination-Realm ?)", fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, "DIAMETER_COMMAND_UNSUPPORTED", "Non-routable message not supported (invalid bit ? missing Destination-Realm ?)", NULL) );
+ return 0;
+ }
+
+ /* If we are listed as Destination-Host */
+ if (is_dest_host == YES) {
+ if (is_local_app == YES) {
+ /* Ok, give the message to the dispatch thread */
+ fd_hook_call(HOOK_MESSAGE_ROUTING_LOCAL, msgptr, NULL, NULL, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( fd_fifo_post(fd_g_local, &msgptr) );
+ } else {
+ /* We don't support the application, reply an error */
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, "Application unsupported", fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, "DIAMETER_APPLICATION_UNSUPPORTED", NULL, NULL) );
+ }
+ return 0;
+ }
+
+ /* If the message is explicitely for someone else */
+ if ((is_dest_host == NO) || (is_dest_realm == NO)) {
+ if (fd_g_config->cnf_flags.no_fwd) {
+ fd_hook_call(HOOK_MESSAGE_ROUTING_ERROR, msgptr, NULL, "Message for another realm/host", fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, "DIAMETER_UNABLE_TO_DELIVER", "I am not a Diameter agent", NULL) );
+ return 0;
+ }
+ } else {
+ /* Destination-Host was not set, and Destination-Realm is matching : we may handle or pass to a fellow peer */
+ int is_nai = 0;
+
+ /* test for decorated NAI (RFC5729 section 4.4) */
+ /* Handle the decorated NAI */
+ if (un_val) {
+ CHECK_FCT_DO( process_decorated_NAI(&is_nai, un_val, dr_val),
+ {
+ /* If the process failed, we assume it is because of the AVP format */
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msgptr, NULL, "Failed to process decorated NAI", fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, "DIAMETER_INVALID_AVP_VALUE", "Failed to process decorated NAI", un) );
+ return 0;
+ } );
+ }
+
+ if (is_nai) {
+ /* We have transformed the AVP, now submit it again in the queue */
+ CHECK_FCT(fd_fifo_post(fd_g_incoming, &msgptr) );
+ return 0;
+ }
+
+ if (is_local_app == YES) {
+ /* Handle localy since we are able to */
+ fd_hook_call(HOOK_MESSAGE_ROUTING_LOCAL, msgptr, NULL, NULL, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT(fd_fifo_post(fd_g_local, &msgptr) );
+ return 0;
+ }
+
+ if (fd_g_config->cnf_flags.no_fwd) {
+ /* We return an error */
+ fd_hook_call(HOOK_MESSAGE_ROUTING_ERROR, msgptr, NULL, "Application unsupported", fd_msg_pmdl_get(msgptr));
+ CHECK_FCT( return_error( &msgptr, "DIAMETER_APPLICATION_UNSUPPORTED", NULL, NULL) );
+ return 0;
+ }
+ }
+
+ /* From that point, for requests, we will call the registered callbacks, then forward to another peer */
+
+ } else {
+ /* The message is an answer */
+ struct msg * qry;
+
+ /* Retrieve the corresponding query and its origin */
+ CHECK_FCT( fd_msg_answ_getq( msgptr, &qry ) );
+ CHECK_FCT( fd_msg_source_get( qry, &qry_src, NULL ) );
+
+ if ((!qry_src) && (!is_err)) {
+ /* The message is a normal answer to a request issued localy, we do not call the callbacks chain on it. */
+ fd_hook_call(HOOK_MESSAGE_ROUTING_LOCAL, msgptr, NULL, NULL, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT(fd_fifo_post(fd_g_local, &msgptr) );
+ return 0;
+ }
+
+ /* From that point, for answers, we will call the registered callbacks, then pass it to the dispatch module or forward it */
+ }
+
+ /* Call all registered callbacks for this message */
+ {
+ struct fd_list * li;
+
+ CHECK_FCT( pthread_rwlock_rdlock( &rt_fwd_lock ) );
+ pthread_cleanup_push( fd_cleanup_rwlock, &rt_fwd_lock );
+
+ /* requests: dir = 1 & 2 => in order; answers = 3 & 2 => in reverse order */
+ for ( li = (is_req ? rt_fwd_list.next : rt_fwd_list.prev) ; msgptr && (li != &rt_fwd_list) ; li = (is_req ? li->next : li->prev) ) {
+ struct rt_hdl * rh = (struct rt_hdl *)li;
+ int ret;
+
+ if (is_req && (rh->dir > RT_FWD_ALL))
+ break;
+ if ((!is_req) && (rh->dir < RT_FWD_ALL))
+ break;
+
+ /* Ok, call this cb */
+ TRACE_DEBUG(ANNOYING, "Calling next FWD callback on %p : %p", msgptr, rh->rt_fwd_cb);
+ CHECK_FCT_DO( ret = (*rh->rt_fwd_cb)(rh->cbdata, &msgptr),
+ {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "A FWD routing callback returned an error: %s", strerror(ret));
+ fd_hook_call(HOOK_MESSAGE_ROUTING_ERROR, msgptr, NULL, buf, fd_msg_pmdl_get(msgptr));
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msgptr, NULL, buf, fd_msg_pmdl_get(msgptr));
+ fd_msg_free(msgptr);
+ msgptr = NULL;
+ break;
+ } );
+ }
+
+ pthread_cleanup_pop(0);
+ CHECK_FCT( pthread_rwlock_unlock( &rt_fwd_lock ) );
+
+ /* If a callback has handled the message, we stop now */
+ if (!msgptr)
+ return 0;
+ }
+
+ /* Now pass the message to the next step: either forward to another peer, or dispatch to local extensions */
+ if (is_req || qry_src) {
+ fd_hook_call(HOOK_MESSAGE_ROUTING_FORWARD, msgptr, NULL, NULL, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT(fd_fifo_post(fd_g_outgoing, &msgptr) );
+ } else {
+ fd_hook_call(HOOK_MESSAGE_ROUTING_LOCAL, msgptr, NULL, NULL, fd_msg_pmdl_get(msgptr));
+ CHECK_FCT(fd_fifo_post(fd_g_local, &msgptr) );
+ }
+
+ /* We're done with this message */
+ return 0;
+}
+
+
+/* The ROUTING-OUT message processing */
+static int msg_rt_out(struct msg * msg)
+{
+ struct rt_data * rtd = NULL;
+ struct msg_hdr * hdr;
+ int is_req = 0;
+ int ret;
+ struct fd_list * li, *candidates;
+ struct avp * avp;
+ struct rtd_candidate * c;
+ struct msg *msgptr = msg;
+ DiamId_t qry_src = NULL;
+ size_t qry_src_len = 0;
+
+ /* Read the message header */
+ CHECK_FCT( fd_msg_hdr(msgptr, &hdr) );
+ is_req = hdr->msg_flags & CMD_FLAG_REQUEST;
+
+ /* For answers, the routing is very easy */
+ if ( ! is_req ) {
+ struct msg * qry;
+ struct msg_hdr * qry_hdr;
+ struct fd_peer * peer = NULL;
+
+ /* Retrieve the corresponding query and its origin */
+ CHECK_FCT( fd_msg_answ_getq( msgptr, &qry ) );
+ CHECK_FCT( fd_msg_source_get( qry, &qry_src, &qry_src_len ) );
+
+ ASSERT( qry_src ); /* if it is NULL, the message should have been in the LOCAL queue! */
+
+ /* Find the peer corresponding to this name */
+ CHECK_FCT( fd_peer_getbyid( qry_src, qry_src_len, 0, (void *) &peer ) );
+ if (fd_peer_getstate(peer) != STATE_OPEN && fd_peer_getstate(peer) != STATE_CLOSING_GRACE) {
+ char buf[128];
+ snprintf(buf, sizeof(buf), "Unable to forward answer to deleted / closed peer '%s'.", qry_src);
+ fd_hook_call(HOOK_MESSAGE_ROUTING_ERROR, msgptr, NULL, buf, fd_msg_pmdl_get(msgptr));
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msgptr, NULL, buf, fd_msg_pmdl_get(msgptr));
+ fd_msg_free(msgptr);
+ return 0;
+ }
+
+ /* We must restore the hop-by-hop id */
+ CHECK_FCT( fd_msg_hdr(qry, &qry_hdr) );
+ hdr->msg_hbhid = qry_hdr->msg_hbhid;
+
+ /* Push the message into this peer */
+ CHECK_FCT( fd_out_send(&msgptr, NULL, peer, 1) );
+
+ /* We're done with this answer */
+ return 0;
+ }
+
+ /* From that point, the message is a request */
+ CHECK_FCT( fd_msg_source_get( msgptr, &qry_src, &qry_src_len ) );
+ /* if qry_src != NULL, this message is relayed, otherwise it is locally issued */
+
+ /* Get the routing data out of the message if any (in case of re-transmit) */
+ CHECK_FCT( fd_msg_rt_get ( msgptr, &rtd ) );
+
+ /* If there is no routing data already, let's create it */
+ if (rtd == NULL) {
+ CHECK_FCT( fd_rtd_init(&rtd) );
+
+ /* Add all peers currently in OPEN state */
+ CHECK_FCT( pthread_rwlock_rdlock(&fd_g_activ_peers_rw) );
+ for (li = fd_g_activ_peers.next; li != &fd_g_activ_peers; li = li->next) {
+ struct fd_peer * p = (struct fd_peer *)li->o;
+ CHECK_FCT_DO( ret = fd_rtd_candidate_add(rtd,
+ p->p_hdr.info.pi_diamid,
+ p->p_hdr.info.pi_diamidlen,
+ p->p_hdr.info.runtime.pir_realm,
+ p->p_hdr.info.runtime.pir_realmlen),
+ { CHECK_FCT_DO( pthread_rwlock_unlock(&fd_g_activ_peers_rw), ); return ret; } );
+ }
+ CHECK_FCT( pthread_rwlock_unlock(&fd_g_activ_peers_rw) );
+
+ /* Now let's remove all peers from the Route-Records */
+ CHECK_FCT( fd_msg_browse(msgptr, MSG_BRW_FIRST_CHILD, &avp, NULL) );
+ while (avp) {
+ struct avp_hdr * ahdr;
+ struct fd_pei error_info;
+ CHECK_FCT( fd_msg_avp_hdr( avp, &ahdr ) );
+
+ if ((ahdr->avp_code == AC_ROUTE_RECORD) && (! (ahdr->avp_flags & AVP_FLAG_VENDOR)) ) {
+ /* Parse this AVP */
+ CHECK_FCT_DO( ret = fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, &error_info ),
+ {
+ if (error_info.pei_errcode) {
+ CHECK_FCT( return_error( &msgptr, error_info.pei_errcode, error_info.pei_message, error_info.pei_avp) );
+ if (error_info.pei_avp_free) { fd_msg_free(error_info.pei_avp); }
+ return 0;
+ } else {
+ return ret;
+ }
+ } );
+ ASSERT( ahdr->avp_value );
+ /* Remove this value from the list. We don't need to pay special attention to the contents here. */
+ fd_rtd_candidate_del(rtd, ahdr->avp_value->os.data, ahdr->avp_value->os.len);
+ }
+
+ /* Go to next AVP */
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL) );
+ }
+
+ /* Save the routing information in the message */
+ CHECK_FCT( fd_msg_rt_associate ( msgptr, rtd ) );
+ }
+
+ /* Note: we reset the scores and pass the message to the callbacks, maybe we could re-use the saved scores when we have received an error ? -- TODO */
+
+ /* Ok, we have our list in rtd now, let's (re)initialize the scores */
+ fd_rtd_candidate_extract(rtd, &candidates, FD_SCORE_INI);
+
+ /* Pass the list to registered callbacks (even if it is empty list) */
+ {
+ CHECK_FCT( pthread_rwlock_rdlock( &rt_out_lock ) );
+ pthread_cleanup_push( fd_cleanup_rwlock, &rt_out_lock );
+
+ /* We call the cb by reverse priority order */
+ for ( li = rt_out_list.prev ; (msgptr != NULL) && (li != &rt_out_list) ; li = li->prev ) {
+ struct rt_hdl * rh = (struct rt_hdl *)li;
+
+ TRACE_DEBUG(ANNOYING, "Calling next OUT callback on %p : %p (prio %d)", msgptr, rh->rt_out_cb, rh->prio);
+ CHECK_FCT_DO( ret = (*rh->rt_out_cb)(rh->cbdata, &msgptr, candidates),
+ {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "An OUT routing callback returned an error: %s", strerror(ret));
+ fd_hook_call(HOOK_MESSAGE_ROUTING_ERROR, msgptr, NULL, buf, fd_msg_pmdl_get(msgptr));
+ fd_hook_call(HOOK_MESSAGE_DROPPED, msgptr, NULL, buf, fd_msg_pmdl_get(msgptr));
+ fd_msg_free(msgptr);
+ msgptr = NULL;
+ } );
+ }
+
+ pthread_cleanup_pop(0);
+ CHECK_FCT( pthread_rwlock_unlock( &rt_out_lock ) );
+
+ /* If an error occurred or the callback disposed of the message, go to next message */
+ if (! msgptr) {
+ return 0;
+ }
+ }
+
+ /* Order the candidate peers by score attributed by the callbacks */
+ CHECK_FCT( fd_rtd_candidate_reorder(candidates) );
+
+ /* Now try sending the message */
+ for (li = candidates->prev; li != candidates; li = li->prev) {
+ struct fd_peer * peer;
+
+ c = (struct rtd_candidate *) li;
+
+ /* Stop when we have reached the end of valid candidates */
+ if (c->score < 0)
+ break;
+
+ /* Search for the peer */
+ CHECK_FCT( fd_peer_getbyid( c->diamid, c->diamidlen, 0, (void *)&peer ) );
+
+ if (fd_peer_getstate(peer) == STATE_OPEN) {
+ /* Send to this one */
+ CHECK_FCT_DO( fd_out_send(&msgptr, NULL, peer, 1), continue );
+
+ /* If the sending was successful */
+ break;
+ }
+ }
+
+ /* If the message has not been sent, return an error */
+ if (msgptr) {
+ fd_hook_call(HOOK_MESSAGE_ROUTING_ERROR, msgptr, NULL, "No remaining suitable candidate to route the message to", fd_msg_pmdl_get(msgptr));
+ return_error( &msgptr, "DIAMETER_UNABLE_TO_DELIVER", "No suitable candidate to route the message to", NULL);
+ }
+
+ /* We're done with this message */
+
+ return 0;
+}
+
+
+/********************************************************************************/
+/* Management of the threads */
+/********************************************************************************/
+
+/* Note: in the first version, we only create one thread of each kind.
+ We could improve the scalability by using the threshold feature of the queues
+ to create additional threads if a queue is filling up, or at least giving a configurable
+ number of threads of each kind.
+ */
+
+/* Control of the threads */
+static enum { RUN = 0, STOP = 1 } order_val = RUN;
+static pthread_mutex_t order_state_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Threads report their status */
+enum thread_state { NOTRUNNING = 0, RUNNING = 1 };
+static void cleanup_state(void * state_loc)
+{
+ CHECK_POSIX_DO( pthread_mutex_lock(&order_state_lock), );
+ *(enum thread_state *)state_loc = NOTRUNNING;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&order_state_lock), );
+}
+
+/* This is the common thread code (same for routing and dispatching) */
+static void * process_thr(void * arg, int (*action_cb)(struct msg * msg), struct fifo * queue, char * action_name)
+{
+ TRACE_ENTRY("%p %p %p %p", arg, action_cb, queue, action_name);
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "%s (%p)", action_name, arg);
+ fd_log_threadname ( buf );
+ }
+
+ /* The thread reports its status when canceled */
+ CHECK_PARAMS_DO(arg, return NULL);
+ pthread_cleanup_push( cleanup_state, arg );
+
+ /* Mark the thread running */
+ CHECK_POSIX_DO( pthread_mutex_lock(&order_state_lock), );
+ *(enum thread_state *)arg = RUNNING;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&order_state_lock), );
+
+ do {
+ struct msg * msg;
+
+ /* Test the current order */
+ {
+ int must_stop;
+ CHECK_POSIX_DO( pthread_mutex_lock(&order_state_lock), { ASSERT(0); } ); /* we lock to flush the caches */
+ must_stop = (order_val == STOP);
+ CHECK_POSIX_DO( pthread_mutex_unlock(&order_state_lock), { ASSERT(0); } );
+ if (must_stop)
+ goto end;
+
+ pthread_testcancel();
+ }
+
+ /* Ok, we are allowed to run */
+
+ /* Get the next message from the queue */
+ {
+ int ret;
+ struct timespec ts;
+
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &ts), goto fatal_error );
+ ts.tv_sec += 1;
+
+ ret = fd_fifo_timedget ( queue, &msg, &ts );
+ if (ret == ETIMEDOUT)
+ /* loop, check if the thread must stop now */
+ continue;
+ if (ret == EPIPE)
+ /* The queue was destroyed, we are probably exiting */
+ goto end;
+
+ /* check if another error occurred */
+ CHECK_FCT_DO( ret, goto fatal_error );
+ }
+
+ LOG_A("%s: Picked next message", action_name);
+
+ /* Now process the message */
+ CHECK_FCT_DO( (*action_cb)(msg), goto fatal_error);
+
+ /* We're done with this message */
+
+ } while (1);
+
+fatal_error:
+ TRACE_DEBUG(INFO, "An unrecoverable error occurred, %s thread is terminating...", action_name);
+ CHECK_FCT_DO(fd_core_shutdown(), );
+
+end:
+ ; /* noop so that we get rid of "label at end of compund statement" warning */
+ /* Mark the thread as terminated */
+ pthread_cleanup_pop(1);
+ return NULL;
+}
+
+/* The dispatch thread */
+static void * dispatch_thr(void * arg)
+{
+ return process_thr(arg, msg_dispatch, fd_g_local, "Dispatch");
+}
+
+/* The (routing-in) thread -- see description in freeDiameter.h */
+static void * routing_in_thr(void * arg)
+{
+ return process_thr(arg, msg_rt_in, fd_g_incoming, "Routing-IN");
+}
+
+/* The (routing-out) thread -- see description in freeDiameter.h */
+static void * routing_out_thr(void * arg)
+{
+ return process_thr(arg, msg_rt_out, fd_g_outgoing, "Routing-OUT");
+}
+
+
+/********************************************************************************/
+/* The functions for the other files */
+/********************************************************************************/
+
+static pthread_t * dispatch = NULL;
+static enum thread_state * disp_state = NULL;
+
+/* Later: make this more dynamic */
+static pthread_t rt_out = (pthread_t)NULL;
+static enum thread_state out_state = NOTRUNNING;
+
+static pthread_t rt_in = (pthread_t)NULL;
+static enum thread_state in_state = NOTRUNNING;
+
+/* Initialize the routing and dispatch threads */
+int fd_rtdisp_init(void)
+{
+ int i;
+
+ /* Prepare the array for dispatch */
+ CHECK_MALLOC( disp_state = calloc(fd_g_config->cnf_dispthr, sizeof(enum thread_state)) );
+ CHECK_MALLOC( dispatch = calloc(fd_g_config->cnf_dispthr, sizeof(pthread_t)) );
+
+ /* Create the threads */
+ for (i=0; i < fd_g_config->cnf_dispthr; i++) {
+ CHECK_POSIX( pthread_create( &dispatch[i], NULL, dispatch_thr, &disp_state[i] ) );
+ }
+ CHECK_POSIX( pthread_create( &rt_out, NULL, routing_out_thr, &out_state) );
+ CHECK_POSIX( pthread_create( &rt_in, NULL, routing_in_thr, &in_state) );
+
+ /* Later: TODO("Set the thresholds for the queues to create more threads as needed"); */
+
+ /* Register the built-in callbacks */
+ CHECK_FCT( fd_rt_out_register( dont_send_if_no_common_app, NULL, 10, NULL ) );
+ CHECK_FCT( fd_rt_out_register( score_destination_avp, NULL, 10, NULL ) );
+
+ return 0;
+}
+
+/* Ask the thread to terminate after next iteration */
+int fd_rtdisp_cleanstop(void)
+{
+ CHECK_POSIX_DO( pthread_mutex_lock(&order_state_lock), );
+ order_val = STOP;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&order_state_lock), );
+
+ return 0;
+}
+
+static void stop_thread_delayed(enum thread_state *st, pthread_t * thr, char * th_name)
+{
+ TRACE_ENTRY("%p %p", st, thr);
+ CHECK_PARAMS_DO(st && thr, return);
+ int terminated;
+
+ CHECK_POSIX_DO( pthread_mutex_lock(&order_state_lock), );
+ terminated = (*st == NOTRUNNING);
+ CHECK_POSIX_DO( pthread_mutex_unlock(&order_state_lock), );
+
+
+ /* Wait for a second for the thread to complete, by monitoring my_state */
+ if (!terminated) {
+ TRACE_DEBUG(INFO, "Waiting for the %s thread to have a chance to terminate", th_name);
+ do {
+ struct timespec ts, ts_final;
+
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &ts), break );
+
+ ts_final.tv_sec = ts.tv_sec + 1;
+ ts_final.tv_nsec = ts.tv_nsec;
+
+ while (TS_IS_INFERIOR( &ts, &ts_final )) {
+
+ CHECK_POSIX_DO( pthread_mutex_lock(&order_state_lock), );
+ terminated = (*st == NOTRUNNING);
+ CHECK_POSIX_DO( pthread_mutex_unlock(&order_state_lock), );
+ if (terminated)
+ break;
+
+ usleep(100000);
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &ts), break );
+ }
+ } while (0);
+ }
+
+ /* Now stop the thread and reclaim its resources */
+ CHECK_FCT_DO( fd_thr_term(thr ), /* continue */);
+
+}
+
+/* Stop the thread after up to one second of wait */
+int fd_rtdisp_fini(void)
+{
+ int i;
+
+ /* Destroy the incoming queue */
+ CHECK_FCT_DO( fd_queues_fini(&fd_g_incoming), /* ignore */);
+
+ /* Stop the routing IN thread */
+ stop_thread_delayed(&in_state, &rt_in, "IN routing");
+
+ /* Destroy the outgoing queue */
+ CHECK_FCT_DO( fd_queues_fini(&fd_g_outgoing), /* ignore */);
+
+ /* Stop the routing OUT thread */
+ stop_thread_delayed(&out_state, &rt_out, "OUT routing");
+
+ /* Destroy the local queue */
+ CHECK_FCT_DO( fd_queues_fini(&fd_g_local), /* ignore */);
+
+ /* Stop the Dispatch threads */
+ if (dispatch != NULL) {
+ for (i=0; i < fd_g_config->cnf_dispthr; i++) {
+ stop_thread_delayed(&disp_state[i], &dispatch[i], "Dispatching");
+ }
+ free(dispatch);
+ dispatch = NULL;
+ }
+ if (disp_state != NULL) {
+ free(disp_state);
+ disp_state = NULL;
+ }
+
+ return 0;
+}
+
+/* Cleanup handlers */
+int fd_rtdisp_cleanup(void)
+{
+ /* Cleanup all remaining handlers */
+ while (!FD_IS_LIST_EMPTY(&rt_fwd_list)) {
+ CHECK_FCT_DO( fd_rt_fwd_unregister ( (void *)rt_fwd_list.next, NULL ), /* continue */ );
+ }
+ while (!FD_IS_LIST_EMPTY(&rt_out_list)) {
+ CHECK_FCT_DO( fd_rt_out_unregister ( (void *)rt_out_list.next, NULL ), /* continue */ );
+ }
+
+ fd_disp_unregister_all(); /* destroy remaining handlers */
+
+ return 0;
+}
+
+
+/********************************************************************************/
+/* For extensions to register a new appl */
+/********************************************************************************/
+
+/* Add an application into the peer's supported apps */
+int fd_disp_app_support ( struct dict_object * app, struct dict_object * vendor, int auth, int acct )
+{
+ application_id_t aid = 0;
+ vendor_id_t vid = 0;
+
+ TRACE_ENTRY("%p %p %d %d", app, vendor, auth, acct);
+ CHECK_PARAMS( app && (auth || acct) );
+
+ {
+ enum dict_object_type type = 0;
+ struct dict_application_data data;
+ CHECK_FCT( fd_dict_gettype(app, &type) );
+ CHECK_PARAMS( type == DICT_APPLICATION );
+ CHECK_FCT( fd_dict_getval(app, &data) );
+ aid = data.application_id;
+ }
+
+ if (vendor) {
+ enum dict_object_type type = 0;
+ struct dict_vendor_data data;
+ CHECK_FCT( fd_dict_gettype(vendor, &type) );
+ CHECK_PARAMS( type == DICT_VENDOR );
+ CHECK_FCT( fd_dict_getval(vendor, &data) );
+ vid = data.vendor_id;
+ }
+
+ return fd_app_merge(&fd_g_config->cnf_apps, aid, vid, auth, acct);
+}
+
+
+
diff --git a/libfdcore/sctp.c b/libfdcore/sctp.c
new file mode 100644
index 0000000..c80a497
--- /dev/null
+++ b/libfdcore/sctp.c
@@ -0,0 +1,1353 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2015, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+#include "fdcore-internal.h"
+#include "cnxctx.h"
+
+#include <netinet/sctp.h>
+#include <sys/uio.h>
+
+/* Size of buffer to receive ancilliary data. May need to be enlarged if more sockopt are set... */
+#ifndef CMSG_BUF_LEN
+#define CMSG_BUF_LEN 1024
+#endif /* CMSG_BUF_LEN */
+
+/* Use old draft-ietf-tsvwg-sctpsocket-17 API ? If not defined, RFC6458 API will be used */
+/* #define OLD_SCTP_SOCKET_API */
+
+/* Automatically fallback to old API if some of the new symbols are not defined */
+#if (!defined(SCTP_CONNECTX_4_ARGS) || (!defined(SCTP_RECVRCVINFO)) || (!defined(SCTP_SNDINFO)))
+# define OLD_SCTP_SOCKET_API
+#endif
+
+
+/* Temper with the retransmission timers to try and improve disconnection detection response? Undef this to keep the defaults of SCTP stack */
+#ifndef USE_DEFAULT_SCTP_RTX_PARAMS /* make this a configuration option if useful */
+#define ADJUST_RTX_PARAMS
+#endif /* USE_DEFAULT_SCTP_RTX_PARAMS */
+
+/* Pre-binding socket options -- # streams read in config */
+static int fd_setsockopt_prebind(int sk)
+{
+ socklen_t sz;
+
+ TRACE_ENTRY( "%d", sk);
+
+ CHECK_PARAMS( sk > 0 );
+
+ {
+ int reuse = 1;
+ CHECK_SYS( setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) );
+ }
+
+#ifdef ADJUST_RTX_PARAMS
+ /* Set the retransmit parameters */
+ #ifdef SCTP_RTOINFO
+ {
+ struct sctp_rtoinfo rtoinfo;
+ memset(&rtoinfo, 0, sizeof(rtoinfo));
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(rtoinfo);
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_RTOINFO, &rtoinfo, &sz) );
+ if (sz != sizeof(rtoinfo))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(rtoinfo));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_RTOINFO : srto_initial : %u", rtoinfo.srto_initial);
+ fd_log_debug( " srto_min : %u", rtoinfo.srto_min);
+ fd_log_debug( " srto_max : %u", rtoinfo.srto_max);
+ }
+
+ /* rtoinfo.srto_initial: Estimate of the RTT before it can be measured; keep the default value */
+ rtoinfo.srto_max = 5000; /* Maximum retransmit timer (in ms), we want fast retransmission time. */
+ rtoinfo.srto_min = 1000; /* Value under which the RTO does not descend, we set this value to not conflict with srto_max */
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_RTOINFO, &rtoinfo, sizeof(rtoinfo)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_RTOINFO, &rtoinfo, &sz) );
+ fd_log_debug( "New SCTP_RTOINFO : srto_initial : %u", rtoinfo.srto_initial);
+ fd_log_debug( " srto_max : %u", rtoinfo.srto_max);
+ fd_log_debug( " srto_min : %u", rtoinfo.srto_min);
+ }
+ }
+ #else /* SCTP_RTOINFO */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_RTOINFO");
+ #endif /* SCTP_RTOINFO */
+
+ /* Set the association parameters: max number of retransmits, ... */
+ #ifdef SCTP_ASSOCINFO
+ {
+ struct sctp_assocparams assoc;
+ memset(&assoc, 0, sizeof(assoc));
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(assoc);
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_ASSOCINFO, &assoc, &sz) );
+ if (sz != sizeof(assoc))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(assoc));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_ASSOCINFO : sasoc_asocmaxrxt : %hu", assoc.sasoc_asocmaxrxt);
+ fd_log_debug( " sasoc_number_peer_destinations : %hu", assoc.sasoc_number_peer_destinations);
+ fd_log_debug( " sasoc_peer_rwnd : %u" , assoc.sasoc_peer_rwnd);
+ fd_log_debug( " sasoc_local_rwnd : %u" , assoc.sasoc_local_rwnd);
+ fd_log_debug( " sasoc_cookie_life : %u" , assoc.sasoc_cookie_life);
+ }
+
+ assoc.sasoc_asocmaxrxt = 4; /* Maximum number of retransmission attempts: we want fast detection of errors */
+ /* Note that this must remain less than the sum of retransmission parameters of the different paths. */
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_ASSOCINFO, &assoc, sizeof(assoc)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_ASSOCINFO, &assoc, &sz) );
+ fd_log_debug( "New SCTP_ASSOCINFO : sasoc_asocmaxrxt : %hu", assoc.sasoc_asocmaxrxt);
+ fd_log_debug( " sasoc_number_peer_destinations : %hu", assoc.sasoc_number_peer_destinations);
+ fd_log_debug( " sasoc_peer_rwnd : %u" , assoc.sasoc_peer_rwnd);
+ fd_log_debug( " sasoc_local_rwnd : %u" , assoc.sasoc_local_rwnd);
+ fd_log_debug( " sasoc_cookie_life : %u" , assoc.sasoc_cookie_life);
+ }
+ }
+ #else /* SCTP_ASSOCINFO */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_ASSOCINFO");
+ #endif /* SCTP_ASSOCINFO */
+#endif /* ADJUST_RTX_PARAMS */
+
+ /* Set the INIT parameters, such as number of streams */
+ #ifdef SCTP_INITMSG
+ {
+ struct sctp_initmsg init;
+ memset(&init, 0, sizeof(init));
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(init);
+
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG, &init, &sz) );
+ if (sz != sizeof(init))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(init));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_INITMSG : sinit_num_ostreams : %hu", init.sinit_num_ostreams);
+ fd_log_debug( " sinit_max_instreams : %hu", init.sinit_max_instreams);
+ fd_log_debug( " sinit_max_attempts : %hu", init.sinit_max_attempts);
+ fd_log_debug( " sinit_max_init_timeo : %hu", init.sinit_max_init_timeo);
+ }
+
+ /* Set the init options -- need to receive SCTP_COMM_UP to confirm the requested parameters, but we don't care (best effort) */
+ init.sinit_num_ostreams = fd_g_config->cnf_sctp_str; /* desired number of outgoing streams */
+ init.sinit_max_init_timeo = CNX_TIMEOUT * 1000;
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG, &init, sizeof(init)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG, &init, &sz) );
+ fd_log_debug( "New SCTP_INITMSG : sinit_num_ostreams : %hu", init.sinit_num_ostreams);
+ fd_log_debug( " sinit_max_instreams : %hu", init.sinit_max_instreams);
+ fd_log_debug( " sinit_max_attempts : %hu", init.sinit_max_attempts);
+ fd_log_debug( " sinit_max_init_timeo : %hu", init.sinit_max_init_timeo);
+ }
+ }
+ #else /* SCTP_INITMSG */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_INITMSG");
+ #endif /* SCTP_INITMSG */
+
+ /* The SO_LINGER option will be reset if we want to perform SCTP ABORT */
+ #ifdef SO_LINGER
+ {
+ struct linger linger;
+ memset(&linger, 0, sizeof(linger));
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(linger);
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, SOL_SOCKET, SO_LINGER, &linger, &sz) );
+ if (sz != sizeof(linger))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(linger));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SO_LINGER : l_onoff : %d", linger.l_onoff);
+ fd_log_debug( " l_linger : %d", linger.l_linger);
+ }
+
+ linger.l_onoff = 0; /* Do not activate the linger */
+ linger.l_linger = 0; /* Ignored, but it would mean : Return immediately when closing (=> abort) (graceful shutdown in background) */
+
+ /* Set the option */
+ CHECK_SYS( setsockopt(sk, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, SOL_SOCKET, SO_LINGER, &linger, &sz) );
+ fd_log_debug( "New SO_LINGER : l_onoff : %d", linger.l_onoff);
+ fd_log_debug( " l_linger : %d", linger.l_linger);
+ }
+ }
+ #else /* SO_LINGER */
+ TRACE_DEBUG(ANNOYING, "Skipping SO_LINGER");
+ #endif /* SO_LINGER */
+
+ /* Set the NODELAY option (Nagle-like algorithm) */
+ #ifdef SCTP_NODELAY
+ {
+ int nodelay;
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(nodelay);
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, &sz) );
+ if (sz != sizeof(nodelay))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(nodelay));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_NODELAY value : %s", nodelay ? "true" : "false");
+ }
+
+ nodelay = 1; /* We turn ON to disable the Nagle algorithm, so that packets are sent ASAP. */
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, &sz) );
+ fd_log_debug( "New SCTP_NODELAY value : %s", nodelay ? "true" : "false");
+ }
+ }
+ #else /* SCTP_NODELAY */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_NODELAY");
+ #endif /* SCTP_NODELAY */
+
+ /*
+ SO_RCVBUF size of receiver window
+ SO_SNDBUF size of pending data to send
+ SCTP_AUTOCLOSE for one-to-many only
+ SCTP_PRIMARY_ADDR use this address as primary locally
+ SCTP_ADAPTATION_LAYER set adaptation layer indication, we don't use this
+ */
+
+ /* Set the SCTP_DISABLE_FRAGMENTS option, required for TLS */
+ #ifdef SCTP_DISABLE_FRAGMENTS
+ {
+ int nofrag;
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(nofrag);
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &nofrag, &sz) );
+ if (sz != sizeof(nofrag))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(nofrag));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_DISABLE_FRAGMENTS value : %s", nofrag ? "true" : "false");
+ }
+
+ nofrag = 0; /* We turn ON the fragmentation, since Diameter messages & TLS messages can be quite large. */
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &nofrag, sizeof(nofrag)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &nofrag, &sz) );
+ fd_log_debug( "New SCTP_DISABLE_FRAGMENTS value : %s", nofrag ? "true" : "false");
+ }
+ }
+ #else /* SCTP_DISABLE_FRAGMENTS */
+ # error "TLS requires support of SCTP_DISABLE_FRAGMENTS"
+ #endif /* SCTP_DISABLE_FRAGMENTS */
+
+ /* SCTP_PEER_ADDR_PARAMS control heartbeat per peer address. We set it as a default for all addresses in the association; not sure if it works ... */
+ #ifdef SCTP_PEER_ADDR_PARAMS
+ {
+ struct sctp_paddrparams parms;
+ memset(&parms, 0, sizeof(parms));
+
+ /* Some kernel versions need this to be set */
+ parms.spp_address.ss_family = AF_INET;
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(parms);
+
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &parms, &sz) );
+ if (sz != sizeof(parms))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(parms));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_PEER_ADDR_PARAMS : spp_hbinterval : %u", parms.spp_hbinterval);
+ fd_log_debug( " spp_pathmaxrxt : %hu", parms.spp_pathmaxrxt);
+ fd_log_debug( " spp_pathmtu : %u", parms.spp_pathmtu);
+ fd_log_debug( " spp_flags : %x", parms.spp_flags);
+ // fd_log_debug( " spp_ipv6_flowlabel: %u", parms.spp_ipv6_flowlabel);
+ // fd_log_debug( " spp_ipv4_tos : %hhu",parms.spp_ipv4_tos);
+ }
+
+ parms.spp_flags = SPP_HB_ENABLE; /* Enable heartbeat for the association */
+ #ifdef SPP_PMTUD_ENABLE
+ parms.spp_flags |= SPP_PMTUD_ENABLE; /* also enable path MTU discovery mechanism */
+ #endif /* SPP_PMTUD_ENABLE */
+
+#ifdef ADJUST_RTX_PARAMS
+ parms.spp_hbinterval = 6000; /* Send an heartbeat every 6 seconds to quickly start retransmissions */
+ /* parms.spp_pathmaxrxt : max nbr of restransmissions on this address. There is a relationship with sasoc_asocmaxrxt, so we leave the default here */
+#endif /* ADJUST_RTX_PARAMS */
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &parms, sizeof(parms)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &parms, &sz) );
+ fd_log_debug( "New SCTP_PEER_ADDR_PARAMS : spp_hbinterval : %u", parms.spp_hbinterval);
+ fd_log_debug( " spp_pathmaxrxt : %hu", parms.spp_pathmaxrxt);
+ fd_log_debug( " spp_pathmtu : %u", parms.spp_pathmtu);
+ fd_log_debug( " spp_flags : %x", parms.spp_flags);
+ // fd_log_debug( " spp_ipv6_flowlabel: %u", parms.spp_ipv6_flowlabel);
+ // fd_log_debug( " spp_ipv4_tos : %hhu",parms.spp_ipv4_tos);
+ }
+ }
+ #else /* SCTP_PEER_ADDR_PARAMS */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_PEER_ADDR_PARAMS");
+ #endif /* SCTP_PEER_ADDR_PARAMS */
+
+ /*
+ SCTP_DEFAULT_SEND_PARAM - DEPRECATED // parameters for the sendto() call, we don't use it.
+ */
+
+ /* Subscribe to some notifications */
+#ifdef OLD_SCTP_SOCKET_API
+ #ifdef SCTP_EVENTS /* DEPRECATED */
+ {
+ struct sctp_event_subscribe event;
+
+ memset(&event, 0, sizeof(event));
+ event.sctp_data_io_event = 1; /* to receive the stream ID in SCTP_SNDRCV ancilliary data on message reception */
+ event.sctp_association_event = 0; /* new or closed associations (mostly for one-to-many style sockets) */
+ event.sctp_address_event = 1; /* address changes */
+ event.sctp_send_failure_event = 1; /* delivery failures */
+ event.sctp_peer_error_event = 1; /* remote peer sends an error */
+ event.sctp_shutdown_event = 1; /* peer has sent a SHUTDOWN */
+ event.sctp_partial_delivery_event = 1; /* a partial delivery is aborted, probably indicating the connection is being shutdown */
+ // event.sctp_adaptation_layer_event = 0; /* adaptation layer notifications */
+ // event.sctp_authentication_event = 0; /* when new key is made active */
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(event)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(event);
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_EVENTS, &event, &sz) );
+ if (sz != sizeof(event))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(event));
+ return ENOTSUP;
+ }
+
+ fd_log_debug( "SCTP_EVENTS : sctp_data_io_event : %hhu", event.sctp_data_io_event);
+ fd_log_debug( " sctp_association_event : %hhu", event.sctp_association_event);
+ fd_log_debug( " sctp_address_event : %hhu", event.sctp_address_event);
+ fd_log_debug( " sctp_send_failure_event : %hhu", event.sctp_send_failure_event);
+ fd_log_debug( " sctp_peer_error_event : %hhu", event.sctp_peer_error_event);
+ fd_log_debug( " sctp_shutdown_event : %hhu", event.sctp_shutdown_event);
+ fd_log_debug( " sctp_partial_delivery_event : %hhu", event.sctp_partial_delivery_event);
+ fd_log_debug( " sctp_adaptation_layer_event : %hhu", event.sctp_adaptation_layer_event);
+ // fd_log_debug( " sctp_authentication_event : %hhu", event.sctp_authentication_event);
+ }
+ }
+ #else /* SCTP_EVENTS */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_EVENTS");
+ #endif /* SCTP_EVENTS */
+#endif /* OLD_SCTP_SOCKET_API */
+
+ /* Set the v4 mapped addresses option */
+ #ifdef SCTP_I_WANT_MAPPED_V4_ADDR
+ if (!fd_g_config->cnf_flags.no_ip6) {
+ int v4mapped;
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(v4mapped);
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, &v4mapped, &sz) );
+ if (sz != sizeof(v4mapped))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(v4mapped));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_I_WANT_MAPPED_V4_ADDR value : %s", v4mapped ? "true" : "false");
+ }
+
+ #ifndef SCTP_USE_MAPPED_ADDRESSES
+ v4mapped = 0; /* We don't want v4 mapped addresses */
+ #else /* SCTP_USE_MAPPED_ADDRESSES */
+ v4mapped = 1; /* but we may have to, otherwise the bind fails in some environments */
+ #endif /* SCTP_USE_MAPPED_ADDRESSES */
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, &v4mapped, sizeof(v4mapped)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, &v4mapped, &sz) );
+ fd_log_debug( "New SCTP_I_WANT_MAPPED_V4_ADDR value : %s", v4mapped ? "true" : "false");
+ }
+ } else {
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_I_WANT_MAPPED_V4_ADDR, since IPv6 disabled.");
+ }
+ #else /* SCTP_I_WANT_MAPPED_V4_ADDR */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_I_WANT_MAPPED_V4_ADDR");
+ #endif /* SCTP_I_WANT_MAPPED_V4_ADDR */
+
+ /*
+ SCTP_MAXSEG max size of fragmented segments -- bound to PMTU
+ SCTP_HMAC_IDENT authentication algorithms
+ SCTP_AUTH_ACTIVE_KEY set the active key
+ SCTP_DELAYED_SACK control delayed acks
+ */
+
+
+ /* Set the interleaving option */
+ #ifdef SCTP_FRAGMENT_INTERLEAVE
+ {
+ int interleave;
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(interleave);
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &interleave, &sz) );
+ if (sz != sizeof(interleave))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(interleave));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_FRAGMENT_INTERLEAVE value : %d", interleave);
+ }
+
+ #if 0
+ interleave = 2; /* Allow partial delivery on several streams at the same time, since we are stream-aware in our security modules */
+ #else /* 0 */
+ interleave = 1; /* hmmm actually, we are not yet capable of handling this, and we don t need it. */
+ #endif /* 0 */
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &interleave, sizeof(interleave)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &interleave, &sz) );
+ fd_log_debug( "New SCTP_FRAGMENT_INTERLEAVE value : %d", interleave);
+ }
+ }
+ #else /* SCTP_FRAGMENT_INTERLEAVE */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_FRAGMENT_INTERLEAVE");
+ #endif /* SCTP_FRAGMENT_INTERLEAVE */
+
+ /*
+ SCTP_PARTIAL_DELIVERY_POINT control partial delivery size
+ SCTP_USE_EXT_RCVINFO - DEPRECATED use extended receive info structure (information about the next message if available)
+ */
+ /* SCTP_AUTO_ASCONF is set by the postbind function */
+ /*
+ SCTP_MAX_BURST number of packets that can be burst emitted
+ SCTP_CONTEXT save a context information along with the association.
+ */
+
+ /* SCTP_EXPLICIT_EOR: we assume implicit EOR in freeDiameter, so let's ensure this is known by the stack */
+ #ifdef SCTP_EXPLICIT_EOR
+ {
+ int bool;
+
+ if (TRACE_BOOL(ANNOYING)) {
+ sz = sizeof(bool);
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_EXPLICIT_EOR, &bool, &sz) );
+ if (sz != sizeof(bool))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(bool));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_EXPLICIT_EOR value : %s", bool ? "true" : "false");
+ }
+
+ bool = 0;
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_EXPLICIT_EOR, &bool, sizeof(bool)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_EXPLICIT_EOR, &bool, &sz) );
+ fd_log_debug( "New SCTP_EXPLICIT_EOR value : %s", bool ? "true" : "false");
+ }
+ }
+ #else /* SCTP_EXPLICIT_EOR */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_EXPLICIT_EOR");
+ #endif /* SCTP_EXPLICIT_EOR */
+
+ /*
+ SCTP_REUSE_PORT share one listening port with several sockets
+ */
+
+#ifndef OLD_SCTP_SOCKET_API
+ #ifdef SCTP_EVENT
+ {
+ /* Subscribe to the following events */
+ int events_I_want[] = {
+ #ifdef SCTP_ASSOC_CHANGE
+ /* SCTP_ASSOC_CHANGE, */
+ #endif
+ #ifdef SCTP_PEER_ADDR_CHANGE
+ SCTP_PEER_ADDR_CHANGE,
+ #endif
+ #ifdef SCTP_REMOTE_ERROR
+ SCTP_REMOTE_ERROR,
+ #endif
+ #ifdef SCTP_SEND_FAILED_EVENT
+ SCTP_SEND_FAILED_EVENT,
+ #endif
+ #ifdef SCTP_SHUTDOWN_EVENT
+ SCTP_SHUTDOWN_EVENT,
+ #endif
+ #ifdef SCTP_ADAPTATION_INDICATION
+ /* SCTP_ADAPTATION_INDICATION, */
+ #endif
+ #ifdef SCTP_PARTIAL_DELIVERY_EVENT
+ /* SCTP_PARTIAL_DELIVERY_EVENT, */
+ #endif
+ #ifdef SCTP_AUTHENTICATION_EVENT
+ /* SCTP_AUTHENTICATION_EVENT, */
+ #endif
+ #ifdef SCTP_SENDER_DRY_EVENT
+ /* SCTP_SENDER_DRY_EVENT, */
+ #endif
+ 0
+ };
+ int i;
+
+ struct sctp_event event;
+
+ for (i = 0; i < (sizeof(events_I_want) / sizeof(events_I_want[0]) - 1); i++) {
+ memset(&event, 0, sizeof(event));
+ event.se_type = events_I_want[i];
+ event.se_on = 1;
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)) );
+ }
+ }
+ #else /* SCTP_EVENT */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_EVENT");
+ #endif /* SCTP_EVENT */
+
+
+ #ifdef SCTP_RECVRCVINFO /* Replaces SCTP_SNDRCV */
+ {
+ int bool = 1;
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_RECVRCVINFO, &bool, sizeof(bool)) );
+
+ }
+ #else /* SCTP_RECVRCVINFO */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_RECVRCVINFO");
+ #endif /* SCTP_RECVRCVINFO */
+
+
+#endif /* OLD_SCTP_SOCKET_API */
+
+ /*
+ SCTP_RECVNXTINFO
+
+ SCTP_DEFAULT_SNDINFO : send defaults
+ SCTP_DEFAULT_PRINFO : default PR-SCTP
+ */
+
+
+ /* In case of no_ip4, force the v6only option */
+ #ifdef IPV6_V6ONLY
+ if (fd_g_config->cnf_flags.no_ip4) {
+ int opt = 1;
+ CHECK_SYS(setsockopt(sk, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)));
+ }
+ #endif /* IPV6_V6ONLY */
+
+ return 0;
+}
+
+
+/* Post-binding socket options */
+static int fd_setsockopt_postbind(int sk, int bound_to_default)
+{
+ TRACE_ENTRY( "%d %d", sk, bound_to_default);
+
+ CHECK_PARAMS( (sk > 0) );
+
+ /* Set the ASCONF option */
+ #ifdef SCTP_AUTO_ASCONF
+ if (bound_to_default) {
+ int asconf;
+
+ if (TRACE_BOOL(ANNOYING)) {
+ socklen_t sz;
+
+ sz = sizeof(asconf);
+ /* Read socket defaults */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_AUTO_ASCONF, &asconf, &sz) );
+ if (sz != sizeof(asconf))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %d", sz, (socklen_t)sizeof(asconf));
+ return ENOTSUP;
+ }
+ fd_log_debug( "Def SCTP_AUTO_ASCONF value : %s", asconf ? "true" : "false");
+ }
+
+ asconf = 1; /* allow automatic use of added or removed addresses in the association (for bound-all sockets) */
+
+ /* Set the option to the socket */
+ CHECK_SYS( setsockopt(sk, IPPROTO_SCTP, SCTP_AUTO_ASCONF, &asconf, sizeof(asconf)) );
+
+ if (TRACE_BOOL(ANNOYING)) {
+ socklen_t sz = sizeof(asconf);
+ /* Check new values */
+ CHECK_SYS( getsockopt(sk, IPPROTO_SCTP, SCTP_AUTO_ASCONF, &asconf, &sz) );
+ fd_log_debug( "New SCTP_AUTO_ASCONF value : %s", asconf ? "true" : "false");
+ }
+ }
+ #else /* SCTP_AUTO_ASCONF */
+ TRACE_DEBUG(ANNOYING, "Skipping SCTP_AUTO_ASCONF");
+ #endif /* SCTP_AUTO_ASCONF */
+
+ return 0;
+}
+
+/* Add addresses from a list to an array, with filter on the flags */
+static int add_addresses_from_list_mask(uint8_t ** array, size_t * size, int * addr_count, int target_family, uint16_t port, struct fd_list * list, uint32_t mask, uint32_t val)
+{
+ struct fd_list * li;
+ int to_add4 = 0;
+ int to_add6 = 0;
+ union {
+ uint8_t *buf;
+ sSA4 *sin;
+ sSA6 *sin6;
+ } ptr;
+ size_t sz;
+
+ /* First, count the number of addresses to add */
+ for (li = list->next; li != list; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *) li;
+
+ /* Do the flag match ? */
+ if ((val & mask) != (ep->flags & mask))
+ continue;
+
+ if (ep->sa.sa_family == AF_INET) {
+ to_add4 ++;
+ } else {
+ to_add6 ++;
+ }
+ }
+
+ if ((to_add4 + to_add6) == 0)
+ return 0; /* nothing to do */
+
+ /* The size to add */
+ if (target_family == AF_INET) {
+ sz = to_add4 * sizeof(sSA4);
+ } else {
+ #ifndef SCTP_USE_MAPPED_ADDRESSES
+ sz = (to_add4 * sizeof(sSA4)) + (to_add6 * sizeof(sSA6));
+ #else /* SCTP_USE_MAPPED_ADDRESSES */
+ sz = (to_add4 + to_add6) * sizeof(sSA6);
+ #endif /* SCTP_USE_MAPPED_ADDRESSES */
+ }
+
+ /* Now, (re)alloc the array to store the new addresses */
+ CHECK_MALLOC( *array = realloc(*array, *size + sz) );
+
+ /* Finally, add the addresses */
+ for (li = list->next; li != list; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *) li;
+
+ /* Skip v6 addresses for v4 socket */
+ if ((target_family == AF_INET) && (ep->sa.sa_family == AF_INET6))
+ continue;
+
+ /* Are the flags matching ? */
+ if ((val & mask) != (ep->flags & mask))
+ continue;
+
+ /* Size of the new SA we are adding (array may contain a mix of sockaddr_in and sockaddr_in6) */
+ #ifndef SCTP_USE_MAPPED_ADDRESSES
+ if (ep->sa.sa_family == AF_INET6)
+ #else /* SCTP_USE_MAPPED_ADDRESSES */
+ if (target_family == AF_INET6)
+ #endif /* SCTP_USE_MAPPED_ADDRESSES */
+ sz = sizeof(sSA6);
+ else
+ sz = sizeof(sSA4);
+
+ /* Place where we add the new address */
+ ptr.buf = *array + *size; /* place of the new SA */
+
+ /* Update other information */
+ *size += sz;
+ *addr_count += 1;
+
+ /* And write the addr in the buffer */
+ if (sz == sizeof(sSA4)) {
+ memcpy(ptr.buf, &ep->sin, sz);
+ ptr.sin->sin_port = port;
+ } else {
+ if (ep->sa.sa_family == AF_INET) { /* We must map the address */
+ memset(ptr.buf, 0, sz);
+ ptr.sin6->sin6_family = AF_INET6;
+ IN6_ADDR_V4MAP( &ptr.sin6->sin6_addr.s6_addr, ep->sin.sin_addr.s_addr );
+ } else {
+ memcpy(ptr.sin6, &ep->sin6, sz);
+ }
+ ptr.sin6->sin6_port = port;
+ }
+ }
+
+ return 0;
+}
+
+/* Create a socket server and bind it according to daemon s configuration */
+int fd_sctp_create_bind_server( int * sock, int family, struct fd_list * list, uint16_t port )
+{
+ int bind_default;
+
+ TRACE_ENTRY("%p %i %p %hu", sock, family, list, port);
+ CHECK_PARAMS(sock);
+
+ /* Create the socket */
+ CHECK_SYS( *sock = socket(family, SOCK_STREAM, IPPROTO_SCTP) );
+
+ /* Set pre-binding socket options, including number of streams etc... */
+ CHECK_FCT( fd_setsockopt_prebind(*sock) );
+
+ bind_default = (! list) || (FD_IS_LIST_EMPTY(list)) ;
+redo:
+ if ( bind_default ) {
+ /* Implicit endpoints : bind to default addresses */
+ union {
+ sSS ss;
+ sSA sa;
+ sSA4 sin;
+ sSA6 sin6;
+ } s;
+
+ /* 0.0.0.0 and [::] are all zeros */
+ memset(&s, 0, sizeof(s));
+
+ s.sa.sa_family = family;
+
+ if (family == AF_INET)
+ s.sin.sin_port = htons(port);
+ else
+ s.sin6.sin6_port = htons(port);
+
+ CHECK_SYS( bind(*sock, &s.sa, sSAlen(&s)) );
+
+ } else {
+ /* Explicit endpoints to bind to from config */
+
+ sSA * sar = NULL; /* array of addresses */
+ size_t sz = 0; /* size of the array */
+ int count = 0; /* number of sock addr in the array */
+
+ /* Create the array of configured addresses */
+ CHECK_FCT( add_addresses_from_list_mask((void *)&sar, &sz, &count, family, htons(port), list, EP_FL_CONF, EP_FL_CONF) );
+
+ if (!count) {
+ /* None of the addresses in the list came from configuration, we bind to default */
+ bind_default = 1;
+ goto redo;
+ }
+
+ #if 0
+ union {
+ sSA *sa;
+ uint8_t *buf;
+ } ptr;
+ int i;
+ ptr.sa = sar;
+ fd_log_debug("Calling sctp_bindx with the following address array:");
+ for (i = 0; i < count; i++) {
+ TRACE_sSA(FD_LOG_DEBUG, FULL, " - ", ptr.sa, NI_NUMERICHOST | NI_NUMERICSERV, "" );
+ ptr.buf += (ptr.sa->sa_family == AF_INET) ? sizeof(sSA4) : sizeof(sSA6) ;
+ }
+ #endif
+
+ /* Bind to this array */
+ CHECK_SYS( sctp_bindx(*sock, sar, count, SCTP_BINDX_ADD_ADDR) );
+
+ /* We don't need sar anymore */
+ free(sar);
+ }
+
+ /* Now, the server is bound, set remaining sockopt */
+ CHECK_FCT( fd_setsockopt_postbind(*sock, bind_default) );
+
+ /* Debug: show all local listening addresses */
+ #if 0
+ sSA *sar;
+ union {
+ sSA *sa;
+ uint8_t *buf;
+ } ptr;
+ int sz;
+
+ CHECK_SYS( sz = sctp_getladdrs(*sock, 0, &sar) );
+
+ fd_log_debug("SCTP server bound on :");
+ for (ptr.sa = sar; sz-- > 0; ptr.buf += (ptr.sa->sa_family == AF_INET) ? sizeof(sSA4) : sizeof(sSA6)) {
+ TRACE_sSA(FD_LOG_DEBUG, FULL, " - ", ptr.sa, NI_NUMERICHOST | NI_NUMERICSERV, "" );
+ }
+ sctp_freeladdrs(sar);
+ #endif
+
+ return 0;
+}
+
+/* Allow clients connections on server sockets */
+int fd_sctp_listen( int sock )
+{
+ TRACE_ENTRY("%d", sock);
+ CHECK_SYS( listen(sock, 5) );
+ return 0;
+}
+
+/* Create a client socket and connect to remote server */
+int fd_sctp_client( int *sock, int no_ip6, uint16_t port, struct fd_list * list )
+{
+ int family;
+ union {
+ uint8_t *buf;
+ sSA *sa;
+ } sar;
+ size_t size = 0;
+ int count = 0;
+ int ret;
+
+ sar.buf = NULL;
+
+ TRACE_ENTRY("%p %i %hu %p", sock, no_ip6, port, list);
+ CHECK_PARAMS( sock && list && (!FD_IS_LIST_EMPTY(list)) );
+
+ if (no_ip6) {
+ family = AF_INET;
+ } else {
+ family = AF_INET6;
+ }
+
+ /* Create the socket */
+ CHECK_SYS( *sock = socket(family, SOCK_STREAM, IPPROTO_SCTP) );
+
+ /* Cleanup if we are cancelled */
+ pthread_cleanup_push(fd_cleanup_socket, sock);
+
+ /* Set the socket options */
+ CHECK_FCT_DO( ret = fd_setsockopt_prebind(*sock), goto out );
+
+ /* Create the array of addresses, add first the configured addresses, then the discovered, then the other ones */
+ CHECK_FCT_DO( ret = add_addresses_from_list_mask(&sar.buf, &size, &count, family, htons(port), list, EP_FL_CONF, EP_FL_CONF ), goto out );
+ CHECK_FCT_DO( ret = add_addresses_from_list_mask(&sar.buf, &size, &count, family, htons(port), list, EP_FL_CONF | EP_FL_DISC, EP_FL_DISC ), goto out );
+ CHECK_FCT_DO( ret = add_addresses_from_list_mask(&sar.buf, &size, &count, family, htons(port), list, EP_FL_CONF | EP_FL_DISC, 0 ), goto out );
+
+ /* Try connecting */
+ LOG_A("Attempting SCTP connection (%d addresses attempted) ", count);
+
+#if 0
+ /* Dump the SAs */
+ union {
+ uint8_t *buf;
+ sSA *sa;
+ sSA4 *sin;
+ sSA6 *sin6;
+ } ptr;
+ int i;
+ ptr.buf = sar.buf;
+ for (i=0; i< count; i++) {
+ TRACE_sSA(FD_LOG_DEBUG, FULL, " - ", ptr.sa, NI_NUMERICHOST | NI_NUMERICSERV, "" );
+ ptr.buf += (ptr.sa->sa_family == AF_INET) ? sizeof(sSA4) : sizeof(sSA6);
+ }
+#endif
+
+ /* Bug in some Linux kernel, the sctp_connectx is not a cancellation point. To avoid blocking freeDiameter, we allow async cancel here */
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+#ifdef SCTP_CONNECTX_4_ARGS
+ ret = sctp_connectx(*sock, sar.sa, count, NULL);
+#else /* SCTP_CONNECTX_4_ARGS */
+ ret = sctp_connectx(*sock, sar.sa, count);
+#endif /* SCTP_CONNECTX_4_ARGS */
+ /* back to normal cancelation behabior */
+ pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
+
+ if (ret < 0) {
+ ret = errno;
+ /* Some errors are expected, we log at different level */
+ LOG_A("sctp_connectx returned an error: %s", strerror(ret));
+ goto out;
+ }
+
+ free(sar.buf); sar.buf = NULL;
+
+ /* Set the remaining sockopts */
+ CHECK_FCT_DO( ret = fd_setsockopt_postbind(*sock, 1),
+ {
+ CHECK_SYS_DO( shutdown(*sock, SHUT_RDWR), /* continue */ );
+ } );
+
+out:
+ ;
+ pthread_cleanup_pop(0);
+
+ if (ret) {
+ if (*sock > 0) {
+ CHECK_SYS_DO( close(*sock), /* continue */ );
+ *sock = -1;
+ }
+ free(sar.buf);
+ }
+ return ret;
+}
+
+/* Retrieve streams information from a connected association -- optionally provide the primary address */
+int fd_sctp_get_str_info( int sock, uint16_t *in, uint16_t *out, sSS *primary )
+{
+ struct sctp_status status;
+ socklen_t sz = sizeof(status);
+
+ TRACE_ENTRY("%d %p %p %p", sock, in, out, primary);
+ CHECK_PARAMS( (sock > 0) && in && out );
+
+ /* Read the association parameters */
+ memset(&status, 0, sizeof(status));
+ CHECK_SYS( getsockopt(sock, IPPROTO_SCTP, SCTP_STATUS, &status, &sz) );
+ if (sz != sizeof(status))
+ {
+ TRACE_DEBUG(INFO, "Invalid size of socket option: %d / %zd", sz, sizeof(status));
+ return ENOTSUP;
+ }
+#if 0
+ char sa_buf[sSA_DUMP_STRLEN];
+ fd_sa_sdump_numeric(sa_buf, &status.sstat_primary.spinfo_address);
+ fd_log_debug( "SCTP_STATUS : sstat_state : %i" , status.sstat_state);
+ fd_log_debug( " sstat_rwnd : %u" , status.sstat_rwnd);
+ fd_log_debug( " sstat_unackdata : %hu", status.sstat_unackdata);
+ fd_log_debug( " sstat_penddata : %hu", status.sstat_penddata);
+ fd_log_debug( " sstat_instrms : %hu", status.sstat_instrms);
+ fd_log_debug( " sstat_outstrms : %hu", status.sstat_outstrms);
+ fd_log_debug( " sstat_fragmentation_point : %u" , status.sstat_fragmentation_point);
+ fd_log_debug( " sstat_primary.spinfo_address : %s" , sa_buf);
+ fd_log_debug( " sstat_primary.spinfo_state : %d" , status.sstat_primary.spinfo_state);
+ fd_log_debug( " sstat_primary.spinfo_cwnd : %u" , status.sstat_primary.spinfo_cwnd);
+ fd_log_debug( " sstat_primary.spinfo_srtt : %u" , status.sstat_primary.spinfo_srtt);
+ fd_log_debug( " sstat_primary.spinfo_rto : %u" , status.sstat_primary.spinfo_rto);
+ fd_log_debug( " sstat_primary.spinfo_mtu : %u" , status.sstat_primary.spinfo_mtu);
+#endif /* 0 */
+
+ *in = status.sstat_instrms;
+ *out = status.sstat_outstrms;
+
+ if (primary)
+ memcpy(primary, &status.sstat_primary.spinfo_address, sizeof(sSS));
+
+ return 0;
+}
+
+/* Get the list of remote endpoints of the socket */
+int fd_sctp_get_remote_ep(int sock, struct fd_list * list)
+{
+ union {
+ sSA *sa;
+ uint8_t *buf;
+ } ptr;
+
+ sSA * data = NULL;
+ int count;
+
+ TRACE_ENTRY("%d %p", sock, list);
+ CHECK_PARAMS(list);
+
+ /* Read the list on the socket */
+ CHECK_SYS( count = sctp_getpaddrs(sock, 0, &data) );
+ ptr.sa = data;
+
+ while (count) {
+ socklen_t sl;
+ switch (ptr.sa->sa_family) {
+ case AF_INET: sl = sizeof(sSA4); break;
+ case AF_INET6: sl = sizeof(sSA6); break;
+ default:
+ TRACE_DEBUG(INFO, "Unknown address family returned in sctp_getpaddrs: %d, skip", ptr.sa->sa_family);
+ /* There is a bug in current Linux kernel: http://www.spinics.net/lists/linux-sctp/msg00760.html */
+ goto stop;
+ }
+
+ CHECK_FCT( fd_ep_add_merge( list, ptr.sa, sl, EP_FL_LL ) );
+ ptr.buf += sl;
+ count --;
+ }
+stop:
+ /* Free the list */
+ sctp_freepaddrs(data);
+
+ /* Now get the primary address, the add function will take care of merging with existing entry */
+ {
+
+ struct sctp_status status;
+ socklen_t sz = sizeof(status);
+ int ret;
+
+ memset(&status, 0, sizeof(status));
+ /* Attempt to use SCTP_STATUS message to retrieve the primary address */
+ CHECK_SYS_DO( ret = getsockopt(sock, IPPROTO_SCTP, SCTP_STATUS, &status, &sz), /* continue */);
+ if (sz != sizeof(status))
+ ret = -1;
+ sz = sizeof(sSS);
+ if (ret < 0)
+ {
+ /* Fallback to getsockname -- not recommended by draft-ietf-tsvwg-sctpsocket-19#section-7.4 */
+ CHECK_SYS(getpeername(sock, (sSA *)&status.sstat_primary.spinfo_address, &sz));
+ }
+
+ CHECK_FCT( fd_ep_add_merge( list, (sSA *)&status.sstat_primary.spinfo_address, sz, EP_FL_PRIMARY ) );
+ }
+
+ /* Done! */
+ return 0;
+}
+
+/* Send a vector over a specified stream */
+ssize_t fd_sctp_sendstrv(struct cnxctx * conn, uint16_t strid, const struct iovec *iov, int iovcnt)
+{
+ struct msghdr mhdr;
+ struct cmsghdr *hdr;
+#ifdef OLD_SCTP_SOCKET_API
+ struct sctp_sndrcvinfo *sndrcv;
+ uint8_t anci[CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))];
+#else /* OLD_SCTP_SOCKET_API */
+ struct sctp_sndinfo *sndinf;
+ uint8_t anci[CMSG_SPACE(sizeof(struct sctp_sndinfo))];
+#endif /* OLD_SCTP_SOCKET_API */
+ ssize_t ret;
+ struct timespec ts, now;
+
+ TRACE_ENTRY("%p %hu %p %d", conn, strid, iov, iovcnt);
+ CHECK_PARAMS_DO(conn && iov && iovcnt, { errno = EINVAL; return -1; } );
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &ts), return -1 );
+
+ memset(&mhdr, 0, sizeof(mhdr));
+ memset(&anci, 0, sizeof(anci));
+
+ /* Anciliary data: specify SCTP stream */
+ hdr = (struct cmsghdr *)anci;
+ hdr->cmsg_len = sizeof(anci);
+ hdr->cmsg_level = IPPROTO_SCTP;
+#ifdef OLD_SCTP_SOCKET_API
+ hdr->cmsg_type = SCTP_SNDRCV;
+ sndrcv = (struct sctp_sndrcvinfo *)CMSG_DATA(hdr);
+ sndrcv->sinfo_stream = strid;
+#else /* OLD_SCTP_SOCKET_API */
+ hdr->cmsg_type = SCTP_SNDINFO;
+ sndinf = (struct sctp_sndinfo *)CMSG_DATA(hdr);
+ sndinf->snd_sid = strid;
+#endif /* OLD_SCTP_SOCKET_API */
+ /* note : we could store other data also, for example in .sinfo_ppid for remote peer or in .sinfo_context for errors. */
+
+ /* We don't use mhdr.msg_name here; it could be used to specify an address different from the primary */
+
+ mhdr.msg_iov = (struct iovec *)iov;
+ mhdr.msg_iovlen = iovcnt;
+
+ mhdr.msg_control = anci;
+ mhdr.msg_controllen = sizeof(anci);
+
+ TRACE_DEBUG(FULL, "Sending %d chunks of data (first:%zdb) on stream %hu of socket %d", iovcnt, iov[0].iov_len, strid, conn->cc_socket);
+again:
+ ret = sendmsg(conn->cc_socket, &mhdr, 0);
+ /* Handle special case of timeout */
+ if ((ret < 0) && ((errno == EAGAIN) || (errno == EINTR))) {
+ pthread_testcancel();
+ /* Check how much time we were blocked for this sending. */
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &now), return -1 );
+ if ( ((now.tv_sec - ts.tv_sec) * 1000 + ((now.tv_nsec - ts.tv_nsec) / 1000000L)) > MAX_HOTL_BLOCKING_TIME) {
+ LOG_D("Unable to send any data for %dms, closing the connection", MAX_HOTL_BLOCKING_TIME);
+ } else if (! fd_cnx_teststate(conn, CC_STATUS_CLOSING )) {
+ goto again; /* don't care, just ignore */
+ }
+
+ /* propagate the error */
+ errno = -ret;
+ ret = -1;
+ }
+
+ CHECK_SYS_DO( ret, ); /* for tracing error only */
+
+ return ret;
+}
+
+/* Receive the next data from the socket, or next notification */
+int fd_sctp_recvmeta(struct cnxctx * conn, uint16_t * strid, uint8_t ** buf, size_t * len, int *event)
+{
+ ssize_t ret = 0;
+ struct msghdr mhdr;
+ char ancidata[ CMSG_BUF_LEN ];
+ struct iovec iov;
+ uint8_t *data = NULL;
+ size_t bufsz = 0, datasize = 0;
+ size_t mempagesz = sysconf(_SC_PAGESIZE); /* We alloc buffer by memory pages for efficiency */
+ int timedout = 0;
+
+ TRACE_ENTRY("%p %p %p %p %p", conn, strid, buf, len, event);
+ CHECK_PARAMS( conn && buf && len && event );
+
+ /* Cleanup out parameters */
+ *buf = NULL;
+ *len = 0;
+ *event = 0;
+
+ /* Prepare header for receiving message */
+ memset(&mhdr, 0, sizeof(mhdr));
+ mhdr.msg_iov = &iov;
+ mhdr.msg_iovlen = 1;
+ mhdr.msg_control = &ancidata;
+ mhdr.msg_controllen = sizeof(ancidata);
+
+next_message:
+ datasize = 0;
+
+ /* We will loop while all data is not received. */
+incomplete:
+ while (datasize >= bufsz ) {
+ /* The buffer is full, enlarge it */
+ bufsz += mempagesz;
+ CHECK_MALLOC( data = realloc(data, bufsz ) );
+ }
+ /* the new data will be received following the preceding */
+ memset(&iov, 0, sizeof(iov));
+ iov.iov_base = data + datasize ;
+ iov.iov_len = bufsz - datasize;
+
+ /* Receive data from the socket */
+again:
+ pthread_cleanup_push(free, data);
+ ret = recvmsg(conn->cc_socket, &mhdr, 0);
+ pthread_testcancel();
+ pthread_cleanup_pop(0);
+
+ /* First, handle timeouts (same as fd_cnx_s_recv) */
+ if ((ret < 0) && ((errno == EAGAIN) || (errno == EINTR))) {
+ if (! fd_cnx_teststate(conn, CC_STATUS_CLOSING ))
+ goto again; /* don't care, just ignore */
+ if (!timedout) {
+ timedout ++; /* allow for one timeout while closing */
+ goto again;
+ }
+ /* fallback to normal handling */
+ }
+
+ /* Handle errors */
+ if (ret <= 0) { /* Socket timedout, closed, or an error occurred */
+ CHECK_SYS_DO(ret, /* to log in case of error */);
+ free(data);
+ *event = FDEVP_CNX_ERROR;
+ return 0;
+ }
+
+ /* Update the size of data we received */
+ datasize += ret;
+
+ /* SCTP provides an indication when we received a full record; loop if it is not the case */
+ if ( ! (mhdr.msg_flags & MSG_EOR) ) {
+ goto incomplete;
+ }
+
+ /* Handle the case where the data received is a notification */
+ if (mhdr.msg_flags & MSG_NOTIFICATION) {
+ union sctp_notification * notif = (union sctp_notification *) data;
+
+ TRACE_DEBUG(FULL, "Received %zdb data of notification on socket %d", datasize, conn->cc_socket);
+
+ switch (notif->sn_header.sn_type) {
+
+ case SCTP_ASSOC_CHANGE: /* We should not receive these as we did not subscribe for it */
+ TRACE_DEBUG(FULL, "Received SCTP_ASSOC_CHANGE notification");
+ TRACE_DEBUG(ANNOYING, " state : %hu", notif->sn_assoc_change.sac_state);
+ TRACE_DEBUG(ANNOYING, " error : %hu", notif->sn_assoc_change.sac_error);
+ TRACE_DEBUG(ANNOYING, " instr : %hu", notif->sn_assoc_change.sac_inbound_streams);
+ TRACE_DEBUG(ANNOYING, " outstr : %hu", notif->sn_assoc_change.sac_outbound_streams);
+
+ *event = FDEVP_CNX_EP_CHANGE;
+ break;
+
+ case SCTP_PEER_ADDR_CHANGE:
+ TRACE_DEBUG(FULL, "Received SCTP_PEER_ADDR_CHANGE notification");
+ /* TRACE_sSA(FD_LOG_DEBUG, ANNOYING, " intf_change : ", &(notif->sn_paddr_change.spc_aaddr), NI_NUMERICHOST | NI_NUMERICSERV, "" ); */
+ TRACE_DEBUG(ANNOYING, " state : %d", notif->sn_paddr_change.spc_state);
+ TRACE_DEBUG(ANNOYING, " error : %d", notif->sn_paddr_change.spc_error);
+
+ *event = FDEVP_CNX_EP_CHANGE;
+ break;
+
+ case SCTP_REMOTE_ERROR:
+ TRACE_DEBUG(FULL, "Received SCTP_REMOTE_ERROR notification");
+ TRACE_DEBUG(ANNOYING, " err : %hu", ntohs(notif->sn_remote_error.sre_error));
+ TRACE_DEBUG(ANNOYING, " len : %hu", ntohs(notif->sn_remote_error.sre_length));
+
+ *event = FDEVP_CNX_ERROR;
+ break;
+
+#ifdef OLD_SCTP_SOCKET_API
+ case SCTP_SEND_FAILED:
+ TRACE_DEBUG(FULL, "Received SCTP_SEND_FAILED notification");
+ TRACE_DEBUG(ANNOYING, " len : %hu", notif->sn_send_failed.ssf_length);
+ TRACE_DEBUG(ANNOYING, " err : %d", notif->sn_send_failed.ssf_error);
+
+ *event = FDEVP_CNX_ERROR;
+ break;
+#else /* OLD_SCTP_SOCKET_API */
+ case SCTP_SEND_FAILED_EVENT:
+ TRACE_DEBUG(FULL, "Received SCTP_SEND_FAILED_EVENT notification");
+ *event = FDEVP_CNX_ERROR;
+ break;
+#endif /* OLD_SCTP_SOCKET_API */
+ case SCTP_SHUTDOWN_EVENT:
+ TRACE_DEBUG(FULL, "Received SCTP_SHUTDOWN_EVENT notification");
+
+ *event = FDEVP_CNX_SHUTDOWN;
+ break;
+
+#ifndef OLD_SCTP_SOCKET_API
+ case SCTP_NOTIFICATIONS_STOPPED_EVENT:
+ TRACE_DEBUG(INFO, "Received SCTP_NOTIFICATIONS_STOPPED_EVENT notification, marking the association in error state");
+ *event = FDEVP_CNX_ERROR;
+ break;
+#endif /* OLD_SCTP_SOCKET_API */
+
+ default:
+ TRACE_DEBUG(FULL, "Received unknown notification %d, ignored", notif->sn_header.sn_type);
+ goto next_message;
+ }
+
+ free(data);
+ return 0;
+ }
+
+ /* From this point, we have received a message */
+ *event = FDEVP_CNX_MSG_RECV;
+ *buf = data;
+ *len = datasize;
+
+ if (strid) {
+ struct cmsghdr *hdr;
+#ifdef OLD_SCTP_SOCKET_API
+ struct sctp_sndrcvinfo *sndrcv;
+#else /* OLD_SCTP_SOCKET_API */
+ struct sctp_rcvinfo *rcvinf;
+#endif /* OLD_SCTP_SOCKET_API */
+
+ /* Handle the anciliary data */
+ for (hdr = CMSG_FIRSTHDR(&mhdr); hdr; hdr = CMSG_NXTHDR(&mhdr, hdr)) {
+
+ /* We deal only with anciliary data at SCTP level */
+ if (hdr->cmsg_level != IPPROTO_SCTP) {
+ TRACE_DEBUG(FULL, "Received some anciliary data at level %d, skipped", hdr->cmsg_level);
+ continue;
+ }
+
+#ifdef OLD_SCTP_SOCKET_API
+ /* Also only interested in SCTP_SNDRCV message for the moment */
+ if (hdr->cmsg_type != SCTP_SNDRCV) {
+ TRACE_DEBUG(FULL, "Anciliary block IPPROTO_SCTP / %d, skipped", hdr->cmsg_type);
+ continue;
+ }
+
+ sndrcv = (struct sctp_sndrcvinfo *) CMSG_DATA(hdr);
+ if (TRACE_BOOL(ANNOYING)) {
+ fd_log_debug( "Anciliary block IPPROTO_SCTP / SCTP_SNDRCV");
+ fd_log_debug( " sinfo_stream : %hu", sndrcv->sinfo_stream);
+ fd_log_debug( " sinfo_ssn : %hu", sndrcv->sinfo_ssn);
+ fd_log_debug( " sinfo_flags : %hu", sndrcv->sinfo_flags);
+ /* fd_log_debug( " sinfo_pr_policy : %hu", sndrcv->sinfo_pr_policy); */
+ fd_log_debug( " sinfo_ppid : %u" , sndrcv->sinfo_ppid);
+ fd_log_debug( " sinfo_context : %u" , sndrcv->sinfo_context);
+ /* fd_log_debug( " sinfo_pr_value : %u" , sndrcv->sinfo_pr_value); */
+ fd_log_debug( " sinfo_tsn : %u" , sndrcv->sinfo_tsn);
+ fd_log_debug( " sinfo_cumtsn : %u" , sndrcv->sinfo_cumtsn);
+ }
+
+ *strid = sndrcv->sinfo_stream;
+#else /* OLD_SCTP_SOCKET_API */
+ /* Also only interested in SCTP_RCVINFO message for the moment */
+ if (hdr->cmsg_type != SCTP_RCVINFO) {
+ TRACE_DEBUG(FULL, "Anciliary block IPPROTO_SCTP / %d, skipped", hdr->cmsg_type);
+ continue;
+ }
+
+ rcvinf = (struct sctp_rcvinfo *) CMSG_DATA(hdr);
+
+ *strid = rcvinf->rcv_sid;
+#endif /* OLD_SCTP_SOCKET_API */
+
+
+ }
+ TRACE_DEBUG(FULL, "Received %zdb data on socket %d, stream %hu", datasize, conn->cc_socket, *strid);
+ } else {
+ TRACE_DEBUG(FULL, "Received %zdb data on socket %d (stream ignored)", datasize, conn->cc_socket);
+ }
+
+ return 0;
+}
diff --git a/libfdcore/sctp3436.c b/libfdcore/sctp3436.c
new file mode 100644
index 0000000..f45a1ab
--- /dev/null
+++ b/libfdcore/sctp3436.c
@@ -0,0 +1,793 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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. *
+*********************************************************************************************************/
+
+/* This file contains code for TLS over multi-stream SCTP wrapper implementation (GnuTLS does not support this) */
+/* See http://aaa.koganei.wide.ad.jp/blogs/index.php/waaad/2008/08/18/tls-over-sctp for history */
+
+#include "fdcore-internal.h"
+#include "cnxctx.h"
+
+#include <netinet/sctp.h>
+#include <sys/uio.h>
+
+/*
+
+Architecture of this wrapper:
+ - we have several fifo queues (1 per stream pairs).
+ GnuTLS is configured to use custom push / pull functions:
+ - the pull function retrieves the data from the fifo queue corresponding to a stream #.
+ - the push function sends the data on a certain stream.
+ We also have a demux thread that reads the socket and store received data in the appropriate fifo
+
+ We have one gnutls_session per stream pair, and as many threads that read the gnutls records and save incoming data to the target queue.
+
+This complexity is required because we cannot read a socket for a given stream only; we can only get the next message and find its stream.
+*/
+
+/* Note that this mechanism is replaced by DTLS in RFC6733 */
+
+/*************************************************************/
+/* threads */
+/*************************************************************/
+
+/* Demux received data and store in the appropriate fifo */
+static void * demuxer(void * arg)
+{
+ struct cnxctx * conn = arg;
+ uint8_t * buf;
+ size_t bufsz;
+ int event;
+ uint16_t strid;
+
+ 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), "Demuxer (%d:%s)", conn->cc_socket, conn->cc_remid);
+ fd_log_threadname ( buf );
+ }
+
+ ASSERT( conn->cc_proto == IPPROTO_SCTP );
+ ASSERT( fd_cnx_target_queue(conn) );
+ ASSERT( conn->cc_sctp3436_data.array );
+
+ do {
+ CHECK_FCT_DO( fd_sctp_recvmeta(conn, &strid, &buf, &bufsz, &event), goto fatal );
+ switch (event) {
+ case FDEVP_CNX_MSG_RECV:
+ /* Demux this message to the appropriate fifo, another thread will pull, gnutls process, and send to target queue */
+ if (strid < conn->cc_sctp_para.pairs) {
+ CHECK_FCT_DO(fd_event_send(conn->cc_sctp3436_data.array[strid].raw_recv, event, bufsz, buf), goto fatal );
+ } else {
+ TRACE_DEBUG(INFO, "Received packet (%zd bytes) on out-of-range stream #%d from %s, discarded.", bufsz, strid, conn->cc_remid);
+ free(buf);
+ }
+ break;
+
+ case FDEVP_CNX_EP_CHANGE:
+ /* Send this event to the target queue */
+ CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), event, bufsz, buf), goto fatal );
+ break;
+
+ case FDEVP_CNX_ERROR:
+ goto out;
+
+ case FDEVP_CNX_SHUTDOWN:
+ /* Just ignore the notification for now, we will get another error later anyway */
+ continue;
+
+ default:
+ goto fatal;
+ }
+
+ } while (conn->cc_loop);
+
+out:
+ /* Signal termination of the connection to all decipher threads */
+ for (strid = 0; strid < conn->cc_sctp_para.pairs; strid++) {
+ if (conn->cc_sctp3436_data.array[strid].raw_recv) {
+ CHECK_FCT_DO(fd_event_send(conn->cc_sctp3436_data.array[strid].raw_recv, FDEVP_CNX_ERROR, 0, NULL), goto fatal );
+ }
+ }
+ fd_cnx_markerror(conn);
+ TRACE_DEBUG(FULL, "Thread terminated");
+ return NULL;
+
+fatal:
+ /* An unrecoverable error occurred, stop the daemon */
+ CHECK_FCT_DO(fd_core_shutdown(), );
+ goto out;
+}
+
+/* Decrypt the data received in this stream pair and store it in the target queue */
+static void * decipher(void * arg)
+{
+ struct sctp3436_ctx * ctx = arg;
+ struct cnxctx *cnx;
+
+ TRACE_ENTRY("%p", arg);
+ CHECK_PARAMS_DO(ctx && ctx->raw_recv && ctx->parent, goto error);
+ cnx = ctx->parent;
+ ASSERT( fd_cnx_target_queue(cnx) );
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "Decipher (%hu@%d:%s)", ctx->strid, cnx->cc_socket, cnx->cc_remid);
+ fd_log_threadname ( buf );
+ }
+
+ /* The next function loops while there is no error */
+ CHECK_FCT_DO(fd_tls_rcvthr_core(cnx, ctx->strid ? ctx->session : cnx->cc_tls_para.session), /* continue */);
+error:
+ fd_cnx_markerror(cnx);
+ TRACE_DEBUG(FULL, "Thread terminated");
+ return NULL;
+}
+
+/*************************************************************/
+/* push / pull */
+/*************************************************************/
+
+#ifdef GNUTLS_VERSION_300
+/* Check if data is available for gnutls on a given context */
+static int sctp3436_pull_timeout(gnutls_transport_ptr_t tr, unsigned int ms)
+{
+ struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) tr;
+ struct timespec tsstore, *ts = NULL;
+ int ret;
+
+ TRACE_ENTRY("%p %d", tr, ms);
+
+ if (ctx->partial.buf)
+ return 1; /* data is already available for pull */
+
+ if (ms) {
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &tsstore), return -1 );
+ tsstore.tv_nsec += (long)ms * 1000000;
+ tsstore.tv_sec += tsstore.tv_nsec / 1000000000L;
+ tsstore.tv_nsec %= 1000000000L;
+ ts = &tsstore;
+ }
+
+ ret = fd_fifo_select ( ctx->raw_recv, ts );
+ if (ret < 0) {
+ errno = -ret;
+ ret = -1;
+ }
+
+ return ret;
+}
+#endif /* GNUTLS_VERSION_300 */
+
+/* Send data over the connection, called by gnutls */
+#ifndef GNUTLS_VERSION_212
+static ssize_t sctp3436_push(gnutls_transport_ptr_t tr, const void * data, size_t len)
+{
+ struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) tr;
+ struct iovec iov;
+
+ TRACE_ENTRY("%p %p %zd", tr, data, len);
+ CHECK_PARAMS_DO( tr && data, { errno = EINVAL; return -1; } );
+
+ iov.iov_base = (void *)data;
+ iov.iov_len = len;
+
+ return fd_sctp_sendstrv(ctx->parent, ctx->strid, &iov, 1);
+}
+#else /* GNUTLS_VERSION_212 */
+static ssize_t sctp3436_pushv(gnutls_transport_ptr_t tr, const giovec_t * iov, int iovcnt)
+{
+ struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) tr;
+
+ TRACE_ENTRY("%p %p %d", tr, iov, iovcnt);
+ CHECK_PARAMS_DO( tr && iov, { errno = EINVAL; return -1; } );
+
+ return fd_sctp_sendstrv(ctx->parent, ctx->strid, (const struct iovec *)iov, iovcnt);
+}
+#endif /* GNUTLS_VERSION_212 */
+
+/* Retrieve data received on a stream and already demultiplexed */
+static ssize_t sctp3436_pull(gnutls_transport_ptr_t tr, void * buf, size_t len)
+{
+ struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) tr;
+ size_t pulled = 0;
+ int emptied;
+
+ TRACE_ENTRY("%p %p %zd", tr, buf, len);
+ CHECK_PARAMS_DO( tr && buf, { errno = EINVAL; goto error; } );
+
+ /* If we don't have data available now, pull new message from the fifo -- this is blocking (until the queue is destroyed) */
+ if (!ctx->partial.buf) {
+ int ev;
+ CHECK_FCT_DO( errno = fd_event_get(ctx->raw_recv, &ev, &ctx->partial.bufsz, (void *)&ctx->partial.buf), goto error );
+ if (ev == FDEVP_CNX_ERROR) {
+ /* Documentations says to return 0 on connection closed, but it does hang within gnutls_handshake */
+ return -1;
+ }
+ }
+
+ pulled = ctx->partial.bufsz - ctx->partial.offset;
+ if (pulled <= len) {
+ emptied = 1;
+ } else {
+ /* limit to the capacity of destination buffer */
+ emptied = 0;
+ pulled = len;
+ }
+
+ /* Store the data in the destination buffer */
+ memcpy(buf, ctx->partial.buf + ctx->partial.offset, pulled);
+
+ /* Free the buffer if we read all its content, and reset the partial structure */
+ if (emptied) {
+ free(ctx->partial.buf);
+ memset(&ctx->partial, 0, sizeof(ctx->partial));
+ } else {
+ ctx->partial.offset += pulled;
+ }
+
+ /* We are done */
+ return pulled;
+
+error:
+ gnutls_transport_set_errno (ctx->session, errno);
+ return -1;
+}
+
+/* Set the parameters of a session to use the appropriate fifo and stream information */
+#ifndef GNUTLS_VERSION_300
+GCC_DIAG_OFF("-Wdeprecated-declarations")
+#endif /* !GNUTLS_VERSION_300 */
+static void set_sess_transport(gnutls_session_t session, struct sctp3436_ctx *ctx)
+{
+ /* Set the transport pointer passed to push & pull callbacks */
+ GNUTLS_TRACE( gnutls_transport_set_ptr( session, (gnutls_transport_ptr_t) ctx ) );
+
+ /* Reset the low water value, since we don't use sockets */
+#ifndef GNUTLS_VERSION_300
+ /* starting version 2.12, this call is not needed */
+ GNUTLS_TRACE( gnutls_transport_set_lowat( session, 0 ) );
+#else /* GNUTLS_VERSION_300 */
+ /* but in 3.0 we have to provide the pull_timeout callback */
+ GNUTLS_TRACE( gnutls_transport_set_pull_timeout_function( session, sctp3436_pull_timeout ) );
+#endif /* GNUTLS_VERSION_300 */
+
+ /* Set the push and pull callbacks */
+ GNUTLS_TRACE( gnutls_transport_set_pull_function(session, sctp3436_pull) );
+#ifndef GNUTLS_VERSION_212
+ GNUTLS_TRACE( gnutls_transport_set_push_function(session, sctp3436_push) );
+#else /* GNUTLS_VERSION_212 */
+ GNUTLS_TRACE( gnutls_transport_set_vec_push_function(session, sctp3436_pushv) );
+#endif /* GNUTLS_VERSION_212 */
+
+ return;
+}
+#ifndef GNUTLS_VERSION_300
+GCC_DIAG_ON("-Wdeprecated-declarations")
+#endif /* !GNUTLS_VERSION_300 */
+
+/*************************************************************/
+/* Session resuming support */
+/*************************************************************/
+
+struct sr_store {
+ struct fd_list list; /* list of sr_data, ordered by key.size then key.data */
+ pthread_rwlock_t lock;
+ struct cnxctx *parent;
+ /* Add another list to chain in a global list to implement a garbage collector on sessions -- TODO if needed */
+};
+
+/* Saved master session data for resuming sessions */
+struct sr_data {
+ struct fd_list chain;
+ gnutls_datum_t key;
+ gnutls_datum_t data;
+};
+
+/* Initialize the store area for a connection */
+static int store_init(struct cnxctx * conn)
+{
+ TRACE_ENTRY("%p", conn);
+ CHECK_PARAMS( conn && !conn->cc_sctp3436_data.sess_store );
+
+ CHECK_MALLOC( conn->cc_sctp3436_data.sess_store = malloc(sizeof(struct sr_store)) );
+ memset(conn->cc_sctp3436_data.sess_store, 0, sizeof(struct sr_store));
+
+ fd_list_init(&conn->cc_sctp3436_data.sess_store->list, NULL);
+ CHECK_POSIX( pthread_rwlock_init(&conn->cc_sctp3436_data.sess_store->lock, NULL) );
+ conn->cc_sctp3436_data.sess_store->parent = conn;
+
+ return 0;
+}
+
+/* Destroy the store area for a connection, and all its content */
+static void store_destroy(struct cnxctx * conn)
+{
+ /* Del all list entries */
+ TRACE_ENTRY("%p", conn);
+ CHECK_PARAMS_DO( conn, return );
+
+ if (!conn->cc_sctp3436_data.sess_store)
+ return;
+
+ CHECK_POSIX_DO( pthread_rwlock_destroy(&conn->cc_sctp3436_data.sess_store->lock), /* continue */ );
+
+ while (!FD_IS_LIST_EMPTY(&conn->cc_sctp3436_data.sess_store->list)) {
+ struct sr_data * sr = (struct sr_data *) conn->cc_sctp3436_data.sess_store->list.next;
+ fd_list_unlink( &sr->chain );
+ free(sr->key.data);
+ free(sr->data.data);
+ free(sr);
+ }
+
+ free(conn->cc_sctp3436_data.sess_store);
+ conn->cc_sctp3436_data.sess_store = NULL;
+ return;
+}
+
+/* Search the position (or next if not found) of a key in a store */
+static struct fd_list * find_or_next(struct sr_store * sto, gnutls_datum_t key, int * match)
+{
+ struct fd_list * ret;
+ *match = 0;
+
+ for (ret = sto->list.next; ret != &sto->list; ret = ret->next) {
+ int cmp = 0;
+ struct sr_data * sr = (struct sr_data *)ret;
+
+ cmp = fd_os_cmp(key.data, key.size, sr->key.data, sr->key.size);
+ if (cmp > 0)
+ continue;
+
+ if (cmp == 0)
+ *match = 1;
+
+ break;
+ }
+
+ return ret;
+}
+
+/* Callbacks for the TLS server side of the connection, called during gnutls_handshake */
+static int sr_store (void *dbf, gnutls_datum_t key, gnutls_datum_t data)
+{
+ struct sr_store * sto = (struct sr_store *)dbf;
+ struct fd_list * li;
+ struct sr_data * sr;
+ int match = 0;
+ int ret = 0;
+
+ TRACE_DEBUG( GNUTLS_DBG_LEVEL, "GNUTLS Callback: %s", __PRETTY_FUNCTION__ );
+ CHECK_PARAMS_DO( sto && key.data && data.data, return -1 );
+
+ CHECK_POSIX_DO( pthread_rwlock_wrlock(&sto->lock), return -1 );
+ TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Session store [key ", key.data, key.size, "]");
+
+ li = find_or_next(sto, key, &match);
+ if (match) {
+ sr = (struct sr_data *)li;
+
+ /* Check the data is the same */
+ if ((data.size != sr->data.size) || memcmp(data.data, sr->data.data, data.size)) {
+ TRACE_DEBUG(INFO, "GnuTLS tried to store a session with same key and different data!");
+ TRACE_BUFFER(FD_LOG_DEBUG, INFO, "Session store [key ", key.data, key.size, "]");
+ TRACE_BUFFER(FD_LOG_DEBUG, INFO, " -- old data [", sr->data.data, sr->data.size, "]");
+ TRACE_BUFFER(FD_LOG_DEBUG, INFO, " -- new data [", data.data, data.size, "]");
+
+ ret = -1;
+ } else {
+ TRACE_DEBUG(GNUTLS_DBG_LEVEL, "GnuTLS tried to store a session with same key and same data, skipped.");
+ }
+ goto out;
+ }
+
+ /* Create a new entry */
+ CHECK_MALLOC_DO( sr = malloc(sizeof(struct sr_data)), { ret = -1; goto out; } );
+ memset(sr, 0, sizeof(struct sr_data));
+
+ fd_list_init(&sr->chain, sr);
+
+ CHECK_MALLOC_DO( sr->key.data = malloc(key.size), { ret = -1; goto out; } );
+ sr->key.size = key.size;
+ memcpy(sr->key.data, key.data, key.size);
+
+ CHECK_MALLOC_DO( sr->data.data = malloc(data.size), { ret = -1; goto out; } );
+ sr->data.size = data.size;
+ memcpy(sr->data.data, data.data, data.size);
+
+ /* Save this new entry in the list, we are done */
+ fd_list_insert_before(li, &sr->chain);
+
+out:
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&sto->lock), return -1 );
+ return ret;
+}
+
+static int sr_remove (void *dbf, gnutls_datum_t key)
+{
+ struct sr_store * sto = (struct sr_store *)dbf;
+ struct fd_list * li;
+ struct sr_data * sr;
+ int match = 0;
+ int ret = 0;
+
+ TRACE_DEBUG( GNUTLS_DBG_LEVEL, "GNUTLS Callback: %s", __PRETTY_FUNCTION__ );
+ CHECK_PARAMS_DO( sto && key.data, return -1 );
+
+ CHECK_POSIX_DO( pthread_rwlock_wrlock(&sto->lock), return -1 );
+ TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Session delete [key ", key.data, key.size, "]");
+
+ li = find_or_next(sto, key, &match);
+ if (match) {
+ sr = (struct sr_data *)li;
+
+ /* Destroy this data */
+ fd_list_unlink(li);
+ free(sr->key.data);
+ free(sr->data.data);
+ free(sr);
+ } else {
+ /* It was not found */
+ ret = -1;
+ }
+
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&sto->lock), return -1 );
+ return ret;
+}
+
+static gnutls_datum_t sr_fetch (void *dbf, gnutls_datum_t key)
+{
+ struct sr_store * sto = (struct sr_store *)dbf;
+ struct fd_list * li;
+ struct sr_data * sr;
+ int match = 0;
+ gnutls_datum_t res = { NULL, 0 };
+ gnutls_datum_t error = { NULL, 0 };
+
+ TRACE_DEBUG( GNUTLS_DBG_LEVEL, "GNUTLS Callback: %s", __PRETTY_FUNCTION__ );
+ CHECK_PARAMS_DO( sto && key.data, return error );
+
+ CHECK_POSIX_DO( pthread_rwlock_rdlock(&sto->lock), return error );
+ TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Session fetch [key ", key.data, key.size, "]");
+
+ li = find_or_next(sto, key, &match);
+ if (match) {
+ sr = (struct sr_data *)li;
+ GNUTLS_TRACE( CHECK_MALLOC_DO(res.data = gnutls_malloc(sr->data.size), goto out ) );
+ res.size = sr->data.size;
+ memcpy(res.data, sr->data.data, res.size);
+ }
+out:
+ TRACE_DEBUG(GNUTLS_DBG_LEVEL, "Fetched (%p, %d) from store %p", res.data, res.size, sto);
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&sto->lock), return error);
+ return res;
+}
+
+/* Set the session pointer in a session object */
+static void set_resume_callbacks(gnutls_session_t session, struct cnxctx * conn)
+{
+ TRACE_ENTRY("%p", conn);
+
+ GNUTLS_TRACE( gnutls_db_set_retrieve_function(session, sr_fetch));
+ GNUTLS_TRACE( gnutls_db_set_remove_function (session, sr_remove));
+ GNUTLS_TRACE( gnutls_db_set_store_function (session, sr_store));
+ GNUTLS_TRACE( gnutls_db_set_ptr (session, conn->cc_sctp3436_data.sess_store));
+
+ return;
+}
+
+/* The handshake is made in parallel in several threads to speed up */
+static void * handshake_resume_th(void * arg)
+{
+ struct sctp3436_ctx * ctx = (struct sctp3436_ctx *) arg;
+ int resumed;
+
+ TRACE_ENTRY("%p", arg);
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "Handshake resume (%hu@%d)", ctx->strid, ctx->parent->cc_socket);
+ fd_log_threadname ( buf );
+ }
+
+ TRACE_DEBUG(FULL, "Starting TLS resumed handshake on stream %hu", ctx->strid);
+
+ CHECK_GNUTLS_DO( gnutls_handshake( ctx->session ), return NULL);
+
+ GNUTLS_TRACE( resumed = gnutls_session_is_resumed(ctx->session) );
+ #ifndef GNUTLS_VERSION_300
+ if (!resumed) {
+ /* Check the credentials here also */
+ CHECK_FCT_DO( fd_tls_verify_credentials(ctx->session, ctx->parent, 0), return NULL );
+ }
+ #endif /* GNUTLS_VERSION_300 */
+ if (TRACE_BOOL(FULL)) {
+ if (resumed) {
+ fd_log_debug("Session was resumed successfully on stream %hu (conn: '%s')", ctx->strid, fd_cnx_getid(ctx->parent));
+ } else {
+ fd_log_debug("Session was NOT resumed on stream %hu (full handshake) (conn: '%s')", ctx->strid, fd_cnx_getid(ctx->parent));
+ }
+ }
+
+ /* Finished, OK */
+ return arg;
+}
+
+
+/*************************************************************/
+/* Exported functions */
+/*************************************************************/
+
+/* Initialize the wrapper for the connection */
+int fd_sctp3436_init(struct cnxctx * conn)
+{
+ uint16_t i;
+
+ TRACE_ENTRY("%p", conn);
+ CHECK_PARAMS( conn && (conn->cc_sctp_para.pairs > 1) && (!conn->cc_sctp3436_data.array) );
+
+ /* First, alloc the array and initialize the non-TLS data */
+ CHECK_MALLOC( conn->cc_sctp3436_data.array = calloc(conn->cc_sctp_para.pairs, sizeof(struct sctp3436_ctx)) );
+ for (i = 0; i < conn->cc_sctp_para.pairs; i++) {
+ conn->cc_sctp3436_data.array[i].parent = conn;
+ conn->cc_sctp3436_data.array[i].strid = i;
+ CHECK_FCT( fd_fifo_new(&conn->cc_sctp3436_data.array[i].raw_recv, 10) );
+ }
+
+ /* Set push/pull functions in the master session, using fifo in array[0] */
+ set_sess_transport(conn->cc_tls_para.session, &conn->cc_sctp3436_data.array[0]);
+
+ /* For server side, we also initialize the resuming capabilities */
+ if (conn->cc_tls_para.mode == GNUTLS_SERVER) {
+
+ /* Prepare the store for sessions data */
+ CHECK_FCT( store_init(conn) );
+
+ /* Set the callbacks for resuming in the master session */
+ set_resume_callbacks(conn->cc_tls_para.session, conn);
+ }
+
+ /* Start the demux thread */
+ CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, demuxer, conn ) );
+
+ return 0;
+}
+
+/* Handshake other streams, after full handshake on the master session */
+int fd_sctp3436_handshake_others(struct cnxctx * conn, char * priority, void * alt_creds)
+{
+ uint16_t i;
+ int errors = 0;
+ gnutls_datum_t master_data;
+
+ TRACE_ENTRY("%p %p", conn, priority);
+ CHECK_PARAMS( conn && (conn->cc_sctp_para.pairs > 1) && conn->cc_sctp3436_data.array );
+
+ /* Server side: we set all the parameters, the resume callback will take care of resuming the session */
+ /* Client side: we duplicate the parameters of the master session, then set the transport pointer */
+
+ /* For client side, retrieve the master session parameters */
+ if (conn->cc_tls_para.mode == GNUTLS_CLIENT) {
+ CHECK_GNUTLS_DO( gnutls_session_get_data2(conn->cc_tls_para.session, &master_data), return ENOMEM );
+ /* For debug: */
+ if (TRACE_BOOL(GNUTLS_DBG_LEVEL)) {
+ uint8_t id[256];
+ size_t ids = sizeof(id);
+ CHECK_GNUTLS_DO( gnutls_session_get_id(conn->cc_tls_para.session, id, &ids), /* continue */ );
+ TRACE_BUFFER(FD_LOG_DEBUG, GNUTLS_DBG_LEVEL, "Master session id: [", id, ids, "]");
+ }
+ }
+
+ /* Initialize the session objects and start the handshake in a separate thread */
+ for (i = 1; i < conn->cc_sctp_para.pairs; i++) {
+ /* Set credentials and priority */
+ CHECK_FCT( fd_tls_prepare(&conn->cc_sctp3436_data.array[i].session, conn->cc_tls_para.mode, 0, priority, alt_creds) );
+
+ /* 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_sctp3436_data.array[i].session, (void *) conn);
+
+ if ((conn->cc_tls_para.cn != NULL) && (conn->cc_tls_para.mode == GNUTLS_CLIENT)) {
+ /* this might allow virtual hosting on the remote peer */
+ CHECK_GNUTLS_DO( gnutls_server_name_set (conn->cc_sctp3436_data.array[i].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_sctp3436_data.array[i].session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT));
+ #endif /* GNUTLS_VERSION_310 */
+
+ /* For the client, copy data from master session; for the server, set session resuming pointers */
+ if (conn->cc_tls_para.mode == GNUTLS_CLIENT) {
+ CHECK_GNUTLS_DO( gnutls_session_set_data(conn->cc_sctp3436_data.array[i].session, master_data.data, master_data.size), return ENOMEM );
+ } else {
+ set_resume_callbacks(conn->cc_sctp3436_data.array[i].session, conn);
+ }
+
+ /* Set transport parameters */
+ set_sess_transport(conn->cc_sctp3436_data.array[i].session, &conn->cc_sctp3436_data.array[i]);
+
+ /* Start the handshake thread */
+ CHECK_POSIX( pthread_create( &conn->cc_sctp3436_data.array[i].thr, NULL, handshake_resume_th, &conn->cc_sctp3436_data.array[i] ) );
+ }
+
+ /* We can now release the memory of master session data if any */
+ if (conn->cc_tls_para.mode == GNUTLS_CLIENT) {
+ GNUTLS_TRACE( gnutls_free(master_data.data) );
+ }
+
+ /* Now wait for all handshakes to finish */
+ for (i = 1; i < conn->cc_sctp_para.pairs; i++) {
+ void * ret;
+ CHECK_POSIX( pthread_join(conn->cc_sctp3436_data.array[i].thr, &ret) );
+ conn->cc_sctp3436_data.array[i].thr = (pthread_t) NULL;
+ if (ret == NULL) {
+ errors++; /* Handshake failed on this stream */
+ }
+ }
+
+ if (errors) {
+ TRACE_DEBUG(INFO, "Handshake failed on %d/%hd stream pairs", errors, conn->cc_sctp_para.pairs);
+ fd_cnx_markerror(conn);
+ return ENOTCONN;
+ }
+
+ return 0;
+}
+
+/* Receive messages from others ? all other stream pairs : the master pair */
+int fd_sctp3436_startthreads(struct cnxctx * conn, int others)
+{
+ uint16_t i;
+
+ TRACE_ENTRY("%p", conn);
+ CHECK_PARAMS( conn && conn->cc_sctp3436_data.array );
+
+ if (others) {
+ for (i = 1; i < conn->cc_sctp_para.pairs; i++) {
+
+ /* Start the decipher thread */
+ CHECK_POSIX( pthread_create( &conn->cc_sctp3436_data.array[i].thr, NULL, decipher, &conn->cc_sctp3436_data.array[i] ) );
+ }
+ } else {
+ CHECK_POSIX( pthread_create( &conn->cc_sctp3436_data.array[0].thr, NULL, decipher, &conn->cc_sctp3436_data.array[0] ) );
+ }
+ return 0;
+}
+
+/* Initiate a "bye" on all stream pairs */
+void fd_sctp3436_bye(struct cnxctx * conn)
+{
+ uint16_t i;
+
+ CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return );
+
+ /* End all TLS sessions, in series (not as efficient as paralel, but simpler) */
+ for (i = 1; i < conn->cc_sctp_para.pairs; i++) {
+ if ( ! fd_cnx_teststate(conn, CC_STATUS_ERROR)) {
+ CHECK_GNUTLS_DO( gnutls_bye(conn->cc_sctp3436_data.array[i].session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) );
+ }
+ }
+}
+
+/* After "bye" was sent on all streams, read from sessions until an error is received */
+void fd_sctp3436_waitthreadsterm(struct cnxctx * conn)
+{
+ uint16_t i;
+
+ TRACE_ENTRY("%p", conn);
+ CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return );
+
+ for (i = 0; i < conn->cc_sctp_para.pairs; i++) {
+ if (conn->cc_sctp3436_data.array[i].thr != (pthread_t)NULL) {
+ CHECK_POSIX_DO( pthread_join(conn->cc_sctp3436_data.array[i].thr, NULL), /* continue */ );
+ conn->cc_sctp3436_data.array[i].thr = (pthread_t)NULL;
+ }
+ }
+ return;
+}
+
+/* Free gnutls resources of all sessions */
+void fd_sctp3436_gnutls_deinit_others(struct cnxctx * conn)
+{
+ uint16_t i;
+
+ TRACE_ENTRY("%p", conn);
+ CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return );
+
+ for (i = 1; i < conn->cc_sctp_para.pairs; i++) {
+ if (conn->cc_sctp3436_data.array[i].session) {
+ GNUTLS_TRACE( gnutls_deinit(conn->cc_sctp3436_data.array[i].session) );
+ conn->cc_sctp3436_data.array[i].session = NULL;
+ }
+ }
+}
+
+
+/* Stop all receiver threads */
+void fd_sctp3436_stopthreads(struct cnxctx * conn)
+{
+ uint16_t i;
+
+ TRACE_ENTRY("%p", conn);
+ CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return );
+
+ for (i = 0; i < conn->cc_sctp_para.pairs; i++) {
+ CHECK_FCT_DO( fd_thr_term(&conn->cc_sctp3436_data.array[i].thr), /* continue */ );
+ }
+ return;
+}
+
+/* Destroy a wrapper context */
+void fd_sctp3436_destroy(struct cnxctx * conn)
+{
+ uint16_t i;
+
+ CHECK_PARAMS_DO( conn && conn->cc_sctp3436_data.array, return );
+
+ /* Terminate all receiving threads in case we did not do it yet */
+ fd_sctp3436_stopthreads(conn);
+
+ /* Now, stop the demux thread */
+ CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ );
+
+ /* Free remaining data in the array */
+ for (i = 0; i < conn->cc_sctp_para.pairs; i++) {
+ if (conn->cc_sctp3436_data.array[i].raw_recv)
+ fd_event_destroy( &conn->cc_sctp3436_data.array[i].raw_recv, free );
+ free(conn->cc_sctp3436_data.array[i].partial.buf);
+ if (conn->cc_sctp3436_data.array[i].session) {
+ GNUTLS_TRACE( gnutls_deinit(conn->cc_sctp3436_data.array[i].session) );
+ conn->cc_sctp3436_data.array[i].session = NULL;
+ }
+ }
+
+ /* Free the array itself now */
+ free(conn->cc_sctp3436_data.array);
+ conn->cc_sctp3436_data.array = NULL;
+
+ /* Delete the store of sessions */
+ store_destroy(conn);
+
+ return ;
+}
diff --git a/libfdcore/server.c b/libfdcore/server.c
new file mode 100644
index 0000000..85a8568
--- /dev/null
+++ b/libfdcore/server.c
@@ -0,0 +1,509 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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"
+
+/* Server (listening) part of the framework */
+
+static struct fd_list FD_SERVERS = FD_LIST_INITIALIZER(FD_SERVERS); /* The list of all server objects */
+/* We don't need to protect this list, it is only accessed from the main framework thread. */
+
+enum s_state {
+ NOT_CREATED=0,
+ RUNNING,
+ TERMINATED,
+ ERROR /* an error occurred, this is not a valid status */
+};
+
+/* Servers information */
+struct server {
+ struct fd_list chain; /* link in the FD_SERVERS list */
+
+ struct cnxctx * conn; /* server connection context (listening socket) */
+ int proto; /* IPPROTO_TCP or IPPROTO_SCTP */
+ int secur; /* TLS is started immediatly after connection ? 0: no; 1: RFU; 2: yes (TLS/TCP or TLS/SCTP) */
+
+ pthread_t thr; /* The thread waiting for new connections (will store the data in the clients fifo) */
+ enum s_state state; /* state of the thread */
+
+ struct fifo *pending; /* FIFO of struct cnxctx */
+ struct pool_workers {
+ struct server * s; /* pointer to the parent server structure */
+ int id; /* The worker id for logs */
+ pthread_t worker; /* The thread */
+ } *workers; /* array of cnf_thr_srv items */
+};
+
+
+/* Micro functions to read/change the status thread-safely */
+static pthread_mutex_t s_lock = PTHREAD_MUTEX_INITIALIZER;
+static enum s_state get_status(struct server * s)
+{
+ enum s_state r;
+ CHECK_POSIX_DO( pthread_mutex_lock(&s_lock), return ERROR );
+ r = s->state;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&s_lock), return ERROR );
+ return r;
+}
+static void set_status(struct server * s, enum s_state st)
+{
+ CHECK_POSIX_DO( pthread_mutex_lock(&s_lock), return );
+ s->state = st;
+ CHECK_POSIX_DO( pthread_mutex_unlock(&s_lock), return );
+}
+
+
+/* dump one item of the server->pending fifo */
+static DECLARE_FD_DUMP_PROTOTYPE(dump_cnx, void * item) {
+ struct cnxctx * c = item;
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, " '%s'", fd_cnx_getid(c)), return NULL);
+ return *buf;
+}
+
+/* Dump all servers information */
+DECLARE_FD_DUMP_PROTOTYPE(fd_servers_dump, int details)
+{
+ struct fd_list * li;
+
+ FD_DUMP_HANDLE_OFFSET();
+
+ for (li = FD_SERVERS.next; li != &FD_SERVERS; li = li->next) {
+ struct server * s = (struct server *)li;
+ enum s_state st = get_status(s);
+
+ if (details) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{server}(@%p)'%s': %s, %s(%d), %s", s, fd_cnx_getid(s->conn),
+ IPPROTO_NAME( s->proto ),
+ s->secur ? "Secur" : "NotSecur", s->secur,
+ (st == NOT_CREATED) ? "Thread not created" :
+ ((st == RUNNING) ? "Thread running" :
+ ((st == TERMINATED) ? "Thread terminated" :
+ "Thread status unknown"))), return NULL);
+ /* Dump the client list of this server */
+ CHECK_MALLOC_DO( fd_fifo_dump(FD_DUMP_STD_PARAMS, "pending connections", s->pending, dump_cnx), return NULL );
+
+ if (li->next != &FD_SERVERS) {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "\n"), return NULL);
+ }
+ } else {
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "'%s'(%s,%s) ", fd_cnx_getid(s->conn),
+ IPPROTO_NAME( s->proto ), s->secur ? "Secur" : "NotSecur"), return NULL);
+ }
+ }
+
+ return *buf;
+}
+
+
+/* The thread in the pool for handling new clients connecting to a server */
+static void * client_worker(void * arg)
+{
+ struct pool_workers * pw = arg;
+ struct server * s = pw->s;
+ struct cnxctx * c = NULL;
+ int fatal = 0;
+ struct timespec ts;
+ struct fd_cnx_rcvdata rcv_data;
+ struct fd_msg_pmdl * pmdl = NULL;
+ struct msg * msg = NULL;
+ struct msg_hdr *hdr = NULL;
+ struct fd_pei pei;
+
+ TRACE_ENTRY("%p", arg);
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "Worker#%d[%s%s]", pw->id, IPPROTO_NAME(s->proto), s->secur?", Sec" : "");
+ fd_log_threadname ( buf );
+ }
+
+ /* Loop until canceled / error */
+next_client:
+ LOG_A("Ready to process next incoming connection");
+
+ memset(&rcv_data, 0, sizeof(rcv_data));
+
+ /* Get the next connection */
+ CHECK_FCT_DO( fd_fifo_get( s->pending, &c ), { fatal = 1; goto cleanup; } );
+
+ /* Handshake if we are a secure server port, or start clear otherwise */
+ if (s->secur) {
+ LOG_D("Starting handshake with %s", fd_cnx_getid(c));
+
+ int ret = fd_cnx_handshake(c, GNUTLS_SERVER, (s->secur == 1) ? ALGO_HANDSHAKE_DEFAULT : ALGO_HANDSHAKE_3436, NULL, NULL);
+ if (ret != 0) {
+ char buf[1024];
+ snprintf(buf, sizeof(buf), "TLS handshake failed for connection '%s', connection closed.", fd_cnx_getid(c));
+
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, NULL, buf, NULL);
+
+ goto cleanup;
+ }
+ } else {
+ CHECK_FCT_DO( fd_cnx_start_clear(c, 0), goto cleanup );
+ }
+
+ /* Set the timeout to receive the first message */
+ CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &ts), { fatal = 1; goto cleanup; } );
+ ts.tv_sec += INCNX_TIMEOUT;
+
+ /* Receive the first Diameter message on the connection -- cleanup in case of timeout */
+ CHECK_FCT_DO( fd_cnx_receive(c, &ts, &rcv_data.buffer, &rcv_data.length),
+ {
+ char buf[1024];
+
+ switch (__ret__) {
+ case ETIMEDOUT:
+ snprintf(buf, sizeof(buf), "Client '%s' did not send CER within %ds, connection aborted.", fd_cnx_getid(c), INCNX_TIMEOUT);
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, NULL, buf, NULL);
+ break;
+
+ case ENOTCONN:
+ snprintf(buf, sizeof(buf), "Connection from '%s' in error before CER was received.", fd_cnx_getid(c));
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, NULL, buf, NULL);
+ break;
+
+ default:
+ snprintf(buf, sizeof(buf), "Connection from '%s': unspecified error, connection aborted.", fd_cnx_getid(c));
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, NULL, buf, NULL);
+ }
+ goto cleanup;
+ } );
+
+ TRACE_DEBUG(FULL, "Received %zdb from new client '%s'", rcv_data.length, fd_cnx_getid(c));
+
+ pmdl = fd_msg_pmdl_get_inbuf(rcv_data.buffer, rcv_data.length);
+
+ /* Try parsing this message */
+ CHECK_FCT_DO( fd_msg_parse_buffer( &rcv_data.buffer, rcv_data.length, &msg ),
+ { /* Parsing failed */
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, NULL, NULL, &rcv_data, pmdl );
+ goto cleanup;
+ } );
+
+ /* Log incoming message */
+ fd_hook_associate(msg, pmdl);
+ fd_hook_call(HOOK_MESSAGE_RECEIVED, msg, NULL, fd_cnx_getid(c), fd_msg_pmdl_get(msg));
+
+ /* We expect a CER, it must parse with our dictionary and rules */
+ CHECK_FCT_DO( fd_msg_parse_rules( msg, fd_g_config->cnf_dict, &pei ),
+ { /* Parsing failed -- trace details */
+ char buf[1024];
+
+ fd_hook_call(HOOK_MESSAGE_PARSING_ERROR, msg, NULL, pei.pei_message ?: pei.pei_errcode, fd_msg_pmdl_get(msg));
+
+ snprintf(buf, sizeof(buf), "Error parsing CER from '%s', connection aborted.", fd_cnx_getid(c));
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, NULL, NULL, buf, NULL);
+
+ goto cleanup;
+ } );
+
+ /* Now check we received a CER */
+ CHECK_FCT_DO( fd_msg_hdr ( msg, &hdr ), { fatal = 1; goto cleanup; } );
+ CHECK_PARAMS_DO( (hdr->msg_appl == 0) && (hdr->msg_flags & CMD_FLAG_REQUEST) && (hdr->msg_code == CC_CAPABILITIES_EXCHANGE),
+ { /* Parsing failed -- trace details */
+ char buf[1024];
+ snprintf(buf, sizeof(buf), "Expected CER from '%s', received a different message, connection aborted.", fd_cnx_getid(c));
+ fd_hook_call(HOOK_PEER_CONNECT_FAILED, msg, NULL, buf, NULL);
+ goto cleanup;
+ } );
+
+ /* Finally, pass the information to the peers module which will handle it in a separate thread */
+ pthread_cleanup_push((void *)fd_cnx_destroy, c);
+ pthread_cleanup_push((void *)fd_msg_free, msg);
+ CHECK_FCT_DO( fd_peer_handle_newCER( &msg, &c ), );
+ pthread_cleanup_pop(0);
+ pthread_cleanup_pop(0);
+
+cleanup:
+ /* Cleanup the parsed message if any */
+ if (msg) {
+ CHECK_FCT_DO( fd_msg_free(msg), /* continue */);
+ msg = NULL;
+ }
+
+ /* Close the connection if needed */
+ if (c != NULL) {
+ fd_cnx_destroy(c);
+ c = NULL;
+ }
+
+ /* Cleanup the received buffer if any */
+ free(rcv_data.buffer);
+
+
+ if (!fatal)
+ goto next_client;
+
+ LOG_E("Worker thread exiting.");
+ return NULL;
+}
+
+/* The thread managing a server */
+static void * serv_th(void * arg)
+{
+ struct server *s = (struct server *)arg;
+
+ CHECK_PARAMS_DO(s, goto error);
+ fd_log_threadname ( fd_cnx_getid(s->conn) );
+
+ set_status(s, RUNNING);
+
+ /* Accept incoming connections */
+ CHECK_FCT_DO( fd_cnx_serv_listen(s->conn), goto error );
+
+ do {
+ struct cnxctx * conn = NULL;
+
+ /* Wait for a new client or cancel */
+ CHECK_MALLOC_DO( conn = fd_cnx_serv_accept(s->conn), break );
+
+ /* Store this connection in the fifo for processing by the worker pool. Will block when the fifo is full */
+ pthread_cleanup_push((void *)fd_cnx_destroy, conn);
+ CHECK_FCT_DO( fd_fifo_post( s->pending, &conn ), break );
+ pthread_cleanup_pop(0);
+
+ } while (1);
+error:
+ if (s)
+ set_status(s, TERMINATED);
+
+ /* Send error signal to the core */
+ LOG_F( "An error occurred in server module! Thread is terminating...");
+ CHECK_FCT_DO(fd_core_shutdown(), );
+
+ return NULL;
+}
+
+
+/* Create a new server structure */
+static struct server * new_serv( int proto, int secur )
+{
+ struct server * new;
+ int i;
+
+ /* New server structure */
+ CHECK_MALLOC_DO( new = malloc(sizeof(struct server)), return NULL );
+
+ memset(new, 0, sizeof(struct server));
+ fd_list_init(&new->chain, new);
+ new->proto = proto;
+ new->secur = secur;
+
+ CHECK_FCT_DO( fd_fifo_new(&new->pending, 5), return NULL);
+ CHECK_MALLOC_DO( new->workers = calloc( fd_g_config->cnf_thr_srv, sizeof(struct pool_workers) ), return NULL );
+
+ for (i = 0; i < fd_g_config->cnf_thr_srv; i++) {
+ /* Create the pool */
+ new->workers[i].s = new;
+ new->workers[i].id = i;
+ CHECK_POSIX_DO( pthread_create( &new->workers[i].worker, NULL, client_worker, &new->workers[i]), return NULL );
+ }
+
+ return new;
+}
+
+/* Start all the servers */
+int fd_servers_start()
+{
+ struct server * s;
+
+ int empty_conf_ep = FD_IS_LIST_EMPTY(&fd_g_config->cnf_endpoints);
+
+ /* SCTP */
+ if (!fd_g_config->cnf_flags.no_sctp) {
+#ifdef DISABLE_SCTP
+ ASSERT(0);
+#else /* DISABLE_SCTP */
+
+ /* Create the server on unsecure port */
+ if (fd_g_config->cnf_port) {
+ CHECK_MALLOC( s = new_serv(IPPROTO_SCTP, 0) );
+ CHECK_MALLOC( s->conn = fd_cnx_serv_sctp(fd_g_config->cnf_port, empty_conf_ep ? NULL : &fd_g_config->cnf_endpoints) );
+ fd_list_insert_before( &FD_SERVERS, &s->chain );
+ CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) );
+ }
+
+ /* Create the server on secure port */
+ if (fd_g_config->cnf_port_tls) {
+ CHECK_MALLOC( s = new_serv(IPPROTO_SCTP, 2 /* Change when DTLS is introduced */) );
+ CHECK_MALLOC( s->conn = fd_cnx_serv_sctp(fd_g_config->cnf_port_tls, empty_conf_ep ? NULL : &fd_g_config->cnf_endpoints) );
+ fd_list_insert_before( &FD_SERVERS, &s->chain );
+ CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) );
+ }
+
+ /* Create the other server on 3436 secure port */
+ /*if (fd_g_config->cnf_port_3436) {
+ CHECK_MALLOC( s = new_serv(IPPROTO_SCTP, 2) );
+ CHECK_MALLOC( s->conn = fd_cnx_serv_sctp(fd_g_config->cnf_port_3436, empty_conf_ep ? NULL : &fd_g_config->cnf_endpoints) );
+ fd_list_insert_before( &FD_SERVERS, &s->chain );
+ CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) );
+ }*/
+
+#endif /* DISABLE_SCTP */
+ }
+
+ /* TCP */
+ if (!fd_g_config->cnf_flags.no_tcp) {
+
+ if (empty_conf_ep) {
+ /* Bind TCP servers on [0.0.0.0] */
+ if (!fd_g_config->cnf_flags.no_ip4) {
+
+ if (fd_g_config->cnf_port) {
+ CHECK_MALLOC( s = new_serv(IPPROTO_TCP, 0) );
+ CHECK_MALLOC( s->conn = fd_cnx_serv_tcp(fd_g_config->cnf_port, AF_INET, NULL) );
+ fd_list_insert_before( &FD_SERVERS, &s->chain );
+ CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) );
+ }
+
+ if (fd_g_config->cnf_port_tls) {
+ CHECK_MALLOC( s = new_serv(IPPROTO_TCP, 1) );
+ CHECK_MALLOC( s->conn = fd_cnx_serv_tcp(fd_g_config->cnf_port_tls, AF_INET, NULL) );
+ fd_list_insert_before( &FD_SERVERS, &s->chain );
+ CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) );
+ }
+ }
+
+ /* Bind TCP servers on [::] */
+ if (!fd_g_config->cnf_flags.no_ip6) {
+
+ if (fd_g_config->cnf_port) {
+ CHECK_MALLOC( s = new_serv(IPPROTO_TCP, 0) );
+ CHECK_MALLOC( s->conn = fd_cnx_serv_tcp(fd_g_config->cnf_port, AF_INET6, NULL) );
+ fd_list_insert_before( &FD_SERVERS, &s->chain );
+ CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) );
+ }
+
+ if (fd_g_config->cnf_port_tls) {
+ CHECK_MALLOC( s = new_serv(IPPROTO_TCP, 1) );
+ CHECK_MALLOC( s->conn = fd_cnx_serv_tcp(fd_g_config->cnf_port_tls, AF_INET6, NULL) );
+ fd_list_insert_before( &FD_SERVERS, &s->chain );
+ CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) );
+ }
+ }
+ } else {
+ /* Create all endpoints -- check flags */
+ struct fd_list * li;
+ for (li = fd_g_config->cnf_endpoints.next; li != &fd_g_config->cnf_endpoints; li = li->next) {
+ struct fd_endpoint * ep = (struct fd_endpoint *)li;
+ sSA * sa = (sSA *) &ep->ss;
+ if (! (ep->flags & EP_FL_CONF))
+ continue;
+ if (fd_g_config->cnf_flags.no_ip4 && (sa->sa_family == AF_INET))
+ continue;
+ if (fd_g_config->cnf_flags.no_ip6 && (sa->sa_family == AF_INET6))
+ continue;
+
+ if (fd_g_config->cnf_port) {
+ CHECK_MALLOC( s = new_serv(IPPROTO_TCP, 0) );
+ CHECK_MALLOC( s->conn = fd_cnx_serv_tcp(fd_g_config->cnf_port, sa->sa_family, ep) );
+ fd_list_insert_before( &FD_SERVERS, &s->chain );
+ CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) );
+ }
+
+ if (fd_g_config->cnf_port_tls) {
+ CHECK_MALLOC( s = new_serv(IPPROTO_TCP, 1) );
+ CHECK_MALLOC( s->conn = fd_cnx_serv_tcp(fd_g_config->cnf_port_tls, sa->sa_family, ep) );
+ fd_list_insert_before( &FD_SERVERS, &s->chain );
+ CHECK_POSIX( pthread_create( &s->thr, NULL, serv_th, s ) );
+ }
+ }
+ }
+ }
+
+ /* Now, if we had an empty list of local adresses (no address configured), try to read the real addresses from the kernel */
+ if (empty_conf_ep) {
+ CHECK_FCT(fd_cnx_get_local_eps(&fd_g_config->cnf_endpoints));
+ if (FD_IS_LIST_EMPTY(&fd_g_config->cnf_endpoints)) {
+ TRACE_DEBUG(INFO, "Unable to find the address(es) of the local system. "
+ "Please use \"ListenOn\" parameter in the configuration. "
+ "This information is required to generate the CER/CEA messages.");
+ return EINVAL;
+ }
+ }
+
+ {
+ char * buf = NULL;
+ size_t len = 0, offset = 0;
+ CHECK_MALLOC_DO( fd_dump_extend( &buf, &len, &offset , "Local server address(es): "), );
+ CHECK_MALLOC_DO( fd_ep_dump( &buf, &len, &offset, 0, 0, &fd_g_config->cnf_endpoints ), );
+ LOG_N("%s", buf ?: "Error dumping addresses");
+ free(buf);
+ }
+ return 0;
+}
+
+/* Terminate all the servers */
+int fd_servers_stop()
+{
+ TRACE_ENTRY("");
+
+ TRACE_DEBUG(INFO, "Shutting down server sockets...");
+
+ /* Loop on all servers */
+ while (!FD_IS_LIST_EMPTY(&FD_SERVERS)) {
+ struct server * s = (struct server *)(FD_SERVERS.next);
+ int i;
+ struct cnxctx * c;
+
+ /* cancel thread */
+ CHECK_FCT_DO( fd_thr_term(&s->thr), /* continue */);
+
+ /* destroy server connection context */
+ fd_cnx_destroy(s->conn);
+
+ /* cancel and destroy all worker threads */
+ for (i = 0; i < fd_g_config->cnf_thr_srv; i++) {
+ /* Destroy worker thread */
+ CHECK_FCT_DO( fd_thr_term(&s->workers[i].worker), /* continue */);
+ }
+ free(s->workers);
+
+ /* Close any pending connection */
+ while ( fd_fifo_tryget( s->pending, &c ) == 0 ) {
+ fd_cnx_destroy(c);
+ }
+ CHECK_FCT_DO( fd_fifo_del(&s->pending), );
+
+ /* Now destroy the server object */
+ fd_list_unlink(&s->chain);
+ free(s);
+ }
+
+ /* We're done! */
+ return 0;
+}
diff --git a/libfdcore/tcp.c b/libfdcore/tcp.c
new file mode 100644
index 0000000..fb6cf1e
--- /dev/null
+++ b/libfdcore/tcp.c
@@ -0,0 +1,168 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+#include "fdcore-internal.h"
+#include "cnxctx.h"
+
+#include <netinet/tcp.h>
+#include <netinet/ip6.h>
+#include <sys/socket.h>
+
+/* Set the socket options for TCP sockets, before bind is called */
+static int fd_tcp_setsockopt(int family, int sk)
+{
+ int ret = 0;
+ int opt;
+
+ /* Clear the NODELAY option in case it was set, as requested by rfc3539#section-3.2 */
+ /* Note that this is supposed to be the default, so we could probably remove this call ... */
+ opt = 0;
+ ret = setsockopt(sk, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
+ if (ret != 0) {
+ ret = errno;
+ TRACE_DEBUG(INFO, "Unable to set the socket TCP_NODELAY option: %s", strerror(ret));
+ return ret;
+ }
+
+ /* Under Linux, we may also set the TCP_CONGESTION option to one of the following strings:
+ - reno (default)
+ - bic
+ - cubic
+ - highspeed
+ - htcp
+ - hybla
+ - illinois
+ - lp
+ - scalable
+ - vegas
+ - veno
+ - westwood
+ - yeah
+ */
+
+ /* In case of v6 address, force the v6only option, we use a different socket for v4 */
+ #ifdef IPV6_V6ONLY
+ if (family == AF_INET6) {
+ opt = 1;
+ CHECK_SYS(setsockopt(sk, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)));
+ }
+ #endif /* IPV6_V6ONLY */
+
+ {
+ opt = 1;
+ CHECK_SYS( setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) );
+ }
+
+ /* There are also others sockopt that can be set, but nothing useful for us AFAICT */
+
+ return 0;
+}
+
+/* Create a socket server and bind it */
+int fd_tcp_create_bind_server( int * sock, sSA * sa, socklen_t salen )
+{
+ TRACE_ENTRY("%p %p %d", sock, sa, salen);
+
+ CHECK_PARAMS( sock && sa );
+
+ /* Create the socket */
+ CHECK_SYS( *sock = socket(sa->sa_family, SOCK_STREAM, IPPROTO_TCP) );
+
+ /* Set the socket options */
+ CHECK_FCT( fd_tcp_setsockopt(sa->sa_family, *sock) );
+
+ /* Bind the socket */
+ CHECK_SYS( bind( *sock, sa, salen ) );
+
+ /* We're done */
+ return 0;
+}
+
+/* Allow clients connections on server sockets */
+int fd_tcp_listen( int sock )
+{
+ TRACE_ENTRY("%d", sock);
+ CHECK_SYS( listen(sock, 5) );
+ return 0;
+}
+
+/* Create a client socket and connect to remote server */
+int fd_tcp_client( int *sock, sSA * sa, socklen_t salen )
+{
+ int ret = 0;
+ int s;
+
+ TRACE_ENTRY("%p %p %d", sock, sa, salen);
+ CHECK_PARAMS( sock && (*sock <= 0) && sa && salen );
+
+ /* Create the socket */
+ CHECK_SYS( s = socket(sa->sa_family, SOCK_STREAM, IPPROTO_TCP) );
+
+ /* Set the socket options */
+ CHECK_FCT( fd_tcp_setsockopt(sa->sa_family, s) );
+
+ /* Cleanup if we are cancelled */
+ pthread_cleanup_push(fd_cleanup_socket, &s);
+
+ /* Try connecting to the remote address */
+ ret = connect(s, sa, salen);
+
+ pthread_cleanup_pop(0);
+
+ if (ret < 0) {
+ ret = errno;
+ LOG_A( "connect returned an error: %s", strerror(ret));
+ CHECK_SYS_DO( close(s), /* continue */ );
+ *sock = -1;
+ return ret;
+ }
+
+ /* Done! */
+ *sock = s;
+ return ret;
+}
+
+/* Get the remote name of a TCP socket */
+int fd_tcp_get_remote_ep(int sock, sSS * ss, socklen_t *sl)
+{
+ TRACE_ENTRY("%d %p %p", sock, ss, sl);
+ CHECK_PARAMS( ss && sl );
+
+ *sl = sizeof(sSS);
+ CHECK_SYS(getpeername(sock, (sSA *)ss, sl));
+
+ return 0;
+}
+
diff --git a/libfdcore/version.c b/libfdcore/version.c
new file mode 100644
index 0000000..fa3a732
--- /dev/null
+++ b/libfdcore/version.c
@@ -0,0 +1,47 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, 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 <freeDiameter/version.h>
+
+#ifdef FD_PROJECT_VERSION_HG
+# define FD_LIBFDCORE_VERSION \
+ _stringize(FD_PROJECT_VERSION_MAJOR) "." _stringize(FD_PROJECT_VERSION_MINOR) "." _stringize(FD_PROJECT_VERSION_REV) "-" FD_PROJECT_VERSION_HG_VAL
+#else
+# define FD_LIBFDCORE_VERSION \
+ _stringize(FD_PROJECT_VERSION_MAJOR) "." _stringize(FD_PROJECT_VERSION_MINOR) "." _stringize(FD_PROJECT_VERSION_REV)
+#endif
+
+const char fd_core_version[] = FD_LIBFDCORE_VERSION;