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