Initial commit
Change-Id: I6a4444e3c193dae437cd7929f4c39aba7b749efa
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;
+}