Mercurial > hg > freeDiameter
diff libfdcore/p_cnx.c @ 658:f198d16fa7f4
Initial commit for 1.1.0:
* Restructuring:
* libfreeDiameter:
- renamed folder & binary into libfdproto
- renamed libfD.h into fdproto-internal.h
- removed signals management (replaced by triggers in libfdcore)
* freeDiameter split into:
- libfdcore (most contents)
- renamed fD.h into fdcore-internal.h
- added core.c for framework init/shutdown.
- new triggers mechanism in events.c.
- freeDiameterd (main, command line parsing, signals management)
* tests:
- now in top-level directory tests.
* other changes:
- fd_dict_new now returns 0 on duplicate identical entries.
- fixes in dict_legacy_xml
- fixes in some dictionaries
- moved FD_DEFAULT_CONF_FILENAME definition to freeDiameter-host.h
author | Sebastien Decugis <sdecugis@nict.go.jp> |
---|---|
date | Fri, 14 Jan 2011 15:15:23 +0900 |
parents | freeDiameter/p_cnx.c@7337305ee51e |
children | 2e94ef0515d7 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libfdcore/p_cnx.c Fri Jan 14 15:15:23 2011 +0900 @@ -0,0 +1,329 @@ +/********************************************************************************************************* +* Software License Agreement (BSD License) * +* Author: Sebastien Decugis <sdecugis@nict.go.jp> * +* * +* Copyright (c) 2010, 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 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; + + 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) { + fd_log_debug("Unable to resolve address for peer '%s' (%s), aborting\n", 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)); + } + + /* Remove any local address that would be here, it should not happen but it does sometimes... */ + CHECK_FCT( fd_ep_filter_list(&peer->p_hdr.info.pi_endpoints, &fd_g_config->cnf_endpoints) ); + + /* Now check we have at least one address to attempt */ + if (FD_IS_LIST_EMPTY(&peer->p_hdr.info.pi_endpoints)) { + fd_log_debug("No address %savailable to connect to peer '%s', aborting\n", 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; + } + + /* 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 ?: fd_g_config->cnf_port); + } else { + dotls_immediate = 1; + port_no = htons(peer->p_hdr.info.config.pic_port ?: fd_g_config->cnf_port_tls); + } + + 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); + } + } + } + + /* 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 */ + } + } +#endif /* DISABLE_SCTP */ + + 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; + + TRACE_ENTRY("%p", arg); + CHECK_PARAMS_DO( CHECK_PEER(peer), return NULL ); + + /* Set the thread name */ + { + char buf[48]; + sprintf(buf, "ConnTo:%.*s", (int)(sizeof(buf)) - 8, 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( prepare_connection_list(peer), goto fatal_error ); + rebuilt ++; + } + if (FD_IS_LIST_EMPTY(&peer->p_connparams)) { + /* We encountered an error or we have looped over all the addresses of the peer. */ + TRACE_DEBUG(INFO, "Unable to connect to the peer %s, aborting attempts for now.", peer->p_hdr.info.pi_diamid); + CHECK_FCT_DO( fd_event_send(peer->p_events, FDEVP_CNX_FAILED, 0, NULL), goto fatal_error ); + 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) ?: 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 */ + + /* 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, peer->p_hdr.info.config.pic_priority, NULL), + { + /* Handshake failed ... */ + fd_log_debug("TLS Handshake failed with peer '%s', resetting the connection\n", peer->p_hdr.info.pi_diamid); + fd_cnx_destroy(cnx); + empty_connection_list(peer); + fd_ep_filter(&peer->p_hdr.info.pi_endpoints, EP_FL_CONF); + return NULL; + } ); + } else { + /* Prepare to receive the next message */ + CHECK_FCT_DO( fd_cnx_start_clear(cnx, 0), goto fatal_error ); + } + + /* Upon success, generate FDEVP_CNX_ESTABLISHED */ + CHECK_FCT_DO( fd_event_send(peer->p_events, FDEVP_CNX_ESTABLISHED, 0, cnx), goto fatal_error ); + + pthread_cleanup_pop(0); + + return NULL; + +fatal_error: + /* Cleanup the connection */ + if (cnx) + fd_cnx_destroy(cnx); + + /* Generate a termination event */ + CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), ); + + 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); + } +}