/* * This file is part of the PCEPlib, a PCEP protocol library. * * Copyright (C) 2020 Volta Networks https://voltanet.io/ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Author : Brady Johnson * */ #include #include #include #include #include #include #include #include "pcep_msg_encoding.h" #include "pcep_session_logic.h" #include "pcep_session_logic_internals.h" #include "pcep_timers.h" #include "pcep_utils_counters.h" #include "pcep_utils_ordered_list.h" #include "pcep_utils_logging.h" #include "pcep_utils_memory.h" /* * public API function implementations for the session_logic */ pcep_session_logic_handle *session_logic_handle_ = NULL; pcep_event_queue *session_logic_event_queue_ = NULL; int session_id_ = 0; void send_pcep_open(pcep_session *session); /* forward decl */ static bool run_session_logic_common() { if (session_logic_handle_ != NULL) { pcep_log(LOG_WARNING, "%s: Session Logic is already initialized.", __func__); return false; } session_logic_handle_ = pceplib_malloc( PCEPLIB_INFRA, sizeof(pcep_session_logic_handle)); memset(session_logic_handle_, 0, sizeof(pcep_session_logic_handle)); session_logic_handle_->active = true; session_logic_handle_->session_logic_condition = false; session_logic_handle_->session_list = ordered_list_initialize(pointer_compare_function); session_logic_handle_->session_event_queue = queue_initialize(); /* Initialize the event queue */ session_logic_event_queue_ = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event_queue)); session_logic_event_queue_->event_queue = queue_initialize(); if (pthread_mutex_init(&(session_logic_event_queue_->event_queue_mutex), NULL) != 0) { pcep_log( LOG_ERR, "%s: Cannot initialize session_logic event queue mutex.", __func__); return false; } pthread_cond_init(&(session_logic_handle_->session_logic_cond_var), NULL); if (pthread_mutex_init(&(session_logic_handle_->session_logic_mutex), NULL) != 0) { pcep_log(LOG_ERR, "%s: Cannot initialize session_logic mutex.", __func__); return false; } if (pthread_mutex_init(&(session_logic_handle_->session_list_mutex), NULL) != 0) { pcep_log(LOG_ERR, "%s: Cannot initialize session_list mutex.", __func__); return false; } return true; } bool run_session_logic() { if (!run_session_logic_common()) { return false; } if (pthread_create(&(session_logic_handle_->session_logic_thread), NULL, session_logic_loop, session_logic_handle_)) { pcep_log(LOG_ERR, "%s: Cannot initialize session_logic thread.", __func__); return false; } if (!initialize_timers(session_logic_timer_expire_handler)) { pcep_log(LOG_ERR, "%s: Cannot initialize session_logic timers.", __func__); return false; } /* No need to call initialize_socket_comm_loop() since it will be * called internally when the first socket_comm_session is created. */ return true; } bool run_session_logic_with_infra(pceplib_infra_config *infra_config) { if (infra_config == NULL) { return run_session_logic(); } /* Initialize the memory infrastructure before anything gets allocated */ if (infra_config->pceplib_infra_mt != NULL && infra_config->pceplib_messages_mt != NULL) { pceplib_memory_initialize( infra_config->pceplib_infra_mt, infra_config->pceplib_messages_mt, infra_config->malloc_func, infra_config->calloc_func, infra_config->realloc_func, infra_config->strdup_func, infra_config->free_func); } if (!run_session_logic_common()) { return false; } /* Create the pcep_session_logic pthread so it can be managed externally */ if (infra_config->pthread_create_func != NULL) { if (infra_config->pthread_create_func( &(session_logic_handle_->session_logic_thread), NULL, session_logic_loop, session_logic_handle_, "pcep_session_logic")) { pcep_log( LOG_ERR, "%s: Cannot initialize external session_logic thread.", __func__); return false; } } else { if (pthread_create( &(session_logic_handle_->session_logic_thread), NULL, session_logic_loop, session_logic_handle_)) { pcep_log(LOG_ERR, "%s: Cannot initialize session_logic thread.", __func__); return false; } } session_logic_event_queue_->event_callback = infra_config->pcep_event_func; session_logic_event_queue_->event_callback_data = infra_config->external_infra_data; if (!initialize_timers_external_infra( session_logic_timer_expire_handler, infra_config->external_infra_data, infra_config->timer_create_func, infra_config->timer_cancel_func, infra_config->pthread_create_func)) { pcep_log( LOG_ERR, "%s: Cannot initialize session_logic timers with infra.", __func__); return false; } /* We found a problem with the FRR sockets, where not all the KeepAlive * messages were received, so if the pthread_create_func is set, the * internal PCEPlib socket infrastructure will be used. */ /* For the SocketComm, the socket_read/write_func and the * pthread_create_func are mutually exclusive. */ if (infra_config->pthread_create_func != NULL) { if (!initialize_socket_comm_external_infra( infra_config->external_infra_data, NULL, NULL, infra_config->pthread_create_func)) { pcep_log( LOG_ERR, "%s: Cannot initialize session_logic socket comm with infra.", __func__); return false; } } else if (infra_config->socket_read_func != NULL && infra_config->socket_write_func != NULL) { if (!initialize_socket_comm_external_infra( infra_config->external_infra_data, infra_config->socket_read_func, infra_config->socket_write_func, NULL)) { pcep_log( LOG_ERR, "%s: Cannot initialize session_logic socket comm with infra.", __func__); return false; } } return true; } bool run_session_logic_wait_for_completion() { if (!run_session_logic()) { return false; } /* Blocking call, waits for session logic thread to complete */ pthread_join(session_logic_handle_->session_logic_thread, NULL); return true; } bool stop_session_logic() { if (session_logic_handle_ == NULL) { pcep_log(LOG_WARNING, "%s: Session logic already stopped", __func__); return false; } session_logic_handle_->active = false; teardown_timers(); pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); session_logic_handle_->session_logic_condition = true; pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); pthread_join(session_logic_handle_->session_logic_thread, NULL); pthread_mutex_destroy(&(session_logic_handle_->session_logic_mutex)); pthread_mutex_destroy(&(session_logic_handle_->session_list_mutex)); ordered_list_destroy(session_logic_handle_->session_list); queue_destroy(session_logic_handle_->session_event_queue); /* destroy the event_queue */ pthread_mutex_destroy(&(session_logic_event_queue_->event_queue_mutex)); queue_destroy(session_logic_event_queue_->event_queue); pceplib_free(PCEPLIB_INFRA, session_logic_event_queue_); /* Explicitly stop the socket comm loop started by the pcep_sessions */ destroy_socket_comm_loop(); pceplib_free(PCEPLIB_INFRA, session_logic_handle_); session_logic_handle_ = NULL; return true; } void close_pcep_session(pcep_session *session) { close_pcep_session_with_reason(session, PCEP_CLOSE_REASON_NO); } void close_pcep_session_with_reason(pcep_session *session, enum pcep_close_reason reason) { struct pcep_message *close_msg = pcep_msg_create_close(reason); pcep_log( LOG_INFO, "%s: [%ld-%ld] pcep_session_logic send pcep_close message for session [%d]", __func__, time(NULL), pthread_self(), session->session_id); session_send_message(session, close_msg); socket_comm_session_close_tcp_after_write(session->socket_comm_session); session->session_state = SESSION_STATE_INITIALIZED; } void destroy_pcep_session(pcep_session *session) { if (session == NULL) { pcep_log(LOG_WARNING, "%s: Cannot destroy NULL session", __func__); return; } /* Remove the session from the session_list and synchronize session * destroy with the session_logic_loop, so that no in-flight events * will be handled now that the session is destroyed. */ pthread_mutex_lock(&(session_logic_handle_->session_list_mutex)); ordered_list_remove_first_node_equals( session_logic_handle_->session_list, session); pcep_log(LOG_DEBUG, "%s: destroy_pcep_session delete session_list sessionPtr %p", __func__, session); pcep_session_cancel_timers(session); delete_counters_group(session->pcep_session_counters); queue_destroy_with_data(session->num_unknown_messages_time_queue); socket_comm_session_teardown(session->socket_comm_session); if (session->pcc_config.pcep_msg_versioning != NULL) { pceplib_free(PCEPLIB_INFRA, session->pcc_config.pcep_msg_versioning); } if (session->pce_config.pcep_msg_versioning != NULL) { pceplib_free(PCEPLIB_INFRA, session->pce_config.pcep_msg_versioning); } int session_id = session->session_id; pceplib_free(PCEPLIB_INFRA, session); pcep_log(LOG_INFO, "%s: [%ld-%ld] session [%d] destroyed", __func__, time(NULL), pthread_self(), session_id); pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex)); } void pcep_session_cancel_timers(pcep_session *session) { if (session == NULL) { return; } if (session->timer_id_dead_timer != TIMER_ID_NOT_SET) { cancel_timer(session->timer_id_dead_timer); } if (session->timer_id_keep_alive != TIMER_ID_NOT_SET) { cancel_timer(session->timer_id_keep_alive); } if (session->timer_id_open_keep_wait != TIMER_ID_NOT_SET) { cancel_timer(session->timer_id_open_keep_wait); } if (session->timer_id_open_keep_alive != TIMER_ID_NOT_SET) { cancel_timer(session->timer_id_open_keep_alive); } } /* Internal util function */ static int get_next_session_id() { if (session_id_ == INT_MAX) { session_id_ = 0; } return session_id_++; } /* Internal util function */ static pcep_session *create_pcep_session_pre_setup(pcep_configuration *config) { if (config == NULL) { pcep_log(LOG_WARNING, "%s: Cannot create pcep session with NULL config", __func__); return NULL; } pcep_session *session = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_session)); memset(session, 0, sizeof(pcep_session)); session->session_id = get_next_session_id(); session->session_state = SESSION_STATE_INITIALIZED; session->timer_id_open_keep_wait = TIMER_ID_NOT_SET; session->timer_id_open_keep_alive = TIMER_ID_NOT_SET; session->timer_id_dead_timer = TIMER_ID_NOT_SET; session->timer_id_keep_alive = TIMER_ID_NOT_SET; session->stateful_pce = false; session->num_unknown_messages_time_queue = queue_initialize(); session->pce_open_received = false; session->pce_open_rejected = false; session->pce_open_keep_alive_sent = false; session->pcc_open_rejected = false; session->pce_open_accepted = false; session->pcc_open_accepted = false; session->destroy_session_after_write = false; session->lsp_db_version = config->lsp_db_version; memcpy(&(session->pcc_config), config, sizeof(pcep_configuration)); /* copy the pcc_config to the pce_config until we receive the open * keep_alive response */ memcpy(&(session->pce_config), config, sizeof(pcep_configuration)); if (config->pcep_msg_versioning != NULL) { session->pcc_config.pcep_msg_versioning = pceplib_malloc( PCEPLIB_INFRA, sizeof(struct pcep_versioning)); memcpy(session->pcc_config.pcep_msg_versioning, config->pcep_msg_versioning, sizeof(struct pcep_versioning)); session->pce_config.pcep_msg_versioning = pceplib_malloc( PCEPLIB_INFRA, sizeof(struct pcep_versioning)); memcpy(session->pce_config.pcep_msg_versioning, config->pcep_msg_versioning, sizeof(struct pcep_versioning)); } pthread_mutex_lock(&(session_logic_handle_->session_list_mutex)); ordered_list_add_node(session_logic_handle_->session_list, session); pcep_log( LOG_DEBUG, "%s: create_pcep_session_pre_setup add session_list sessionPtr %p", __func__, session); pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex)); return session; } /* Internal util function */ static bool create_pcep_session_post_setup(pcep_session *session) { if (!socket_comm_session_connect_tcp(session->socket_comm_session)) { pcep_log(LOG_WARNING, "%s: Cannot establish TCP socket.", __func__); destroy_pcep_session(session); return false; } session->time_connected = time(NULL); create_session_counters(session); send_pcep_open(session); session->session_state = SESSION_STATE_PCEP_CONNECTING; session->timer_id_open_keep_wait = create_timer(session->pcc_config.keep_alive_seconds, session); // session->session_state = SESSION_STATE_OPENED; return true; } pcep_session *create_pcep_session(pcep_configuration *config, struct in_addr *pce_ip) { if (pce_ip == NULL) { pcep_log(LOG_WARNING, "%s: Cannot create pcep session with NULL pce_ip", __func__); return NULL; } pcep_session *session = create_pcep_session_pre_setup(config); if (session == NULL) { return NULL; } session->socket_comm_session = socket_comm_session_initialize_with_src( NULL, session_logic_msg_ready_handler, session_logic_message_sent_handler, session_logic_conn_except_notifier, &(config->src_ip.src_ipv4), ((config->src_pcep_port == 0) ? PCEP_TCP_PORT : config->src_pcep_port), pce_ip, ((config->dst_pcep_port == 0) ? PCEP_TCP_PORT : config->dst_pcep_port), config->socket_connect_timeout_millis, config->tcp_authentication_str, config->is_tcp_auth_md5, session); if (session->socket_comm_session == NULL) { pcep_log(LOG_WARNING, "%s: Cannot establish socket_comm_session.", __func__); destroy_pcep_session(session); return NULL; } if (create_pcep_session_post_setup(session) == false) { return NULL; } return session; } pcep_session *create_pcep_session_ipv6(pcep_configuration *config, struct in6_addr *pce_ip) { if (pce_ip == NULL) { pcep_log(LOG_WARNING, "%s: Cannot create pcep session with NULL pce_ip", __func__); return NULL; } pcep_session *session = create_pcep_session_pre_setup(config); if (session == NULL) { return NULL; } session->socket_comm_session = socket_comm_session_initialize_with_src_ipv6( NULL, session_logic_msg_ready_handler, session_logic_message_sent_handler, session_logic_conn_except_notifier, &(config->src_ip.src_ipv6), ((config->src_pcep_port == 0) ? PCEP_TCP_PORT : config->src_pcep_port), pce_ip, ((config->dst_pcep_port == 0) ? PCEP_TCP_PORT : config->dst_pcep_port), config->socket_connect_timeout_millis, config->tcp_authentication_str, config->is_tcp_auth_md5, session); if (session->socket_comm_session == NULL) { pcep_log(LOG_WARNING, "%s: Cannot establish ipv6 socket_comm_session.", __func__); destroy_pcep_session(session); return NULL; } if (create_pcep_session_post_setup(session) == false) { return NULL; } return session; } void session_send_message(pcep_session *session, struct pcep_message *message) { pcep_encode_message(message, session->pcc_config.pcep_msg_versioning); socket_comm_session_send_message(session->socket_comm_session, (char *)message->encoded_message, message->encoded_message_length, true); increment_message_tx_counters(session, message); /* The message->encoded_message will be freed in * socket_comm_session_send_message() once sent. * Setting to NULL here so pcep_msg_free_message() does not free it */ message->encoded_message = NULL; pcep_msg_free_message(message); } /* This function is also used in pcep_session_logic_states.c */ struct pcep_message *create_pcep_open(pcep_session *session) { /* create and send PCEP open * with PCEP, the PCC sends the config the PCE should use in the open * message, * and the PCE will send an open with the config the PCC should use. */ double_linked_list *tlv_list = dll_initialize(); if (session->pcc_config.support_stateful_pce_lsp_update || session->pcc_config.support_pce_lsp_instantiation || session->pcc_config.support_include_db_version || session->pcc_config.support_lsp_triggered_resync || session->pcc_config.support_lsp_delta_sync || session->pcc_config.support_pce_triggered_initial_sync) { /* Prepend this TLV as the first in the list */ dll_append( tlv_list, pcep_tlv_create_stateful_pce_capability( session->pcc_config .support_stateful_pce_lsp_update, /* U flag */ session->pcc_config .support_include_db_version, /* S flag */ session->pcc_config .support_lsp_triggered_resync, /* T flag */ session->pcc_config .support_lsp_delta_sync, /* D flag */ session->pcc_config .support_pce_triggered_initial_sync, /* F flag */ session->pcc_config .support_pce_lsp_instantiation)); /* I flag */ } if (session->pcc_config.support_include_db_version) { if (session->pcc_config.lsp_db_version != 0) { dll_append(tlv_list, pcep_tlv_create_lsp_db_version( session->pcc_config.lsp_db_version)); } } if (session->pcc_config.support_sr_te_pst) { bool flag_n = false; bool flag_x = false; if (session->pcc_config.pcep_msg_versioning ->draft_ietf_pce_segment_routing_07 == false) { flag_n = session->pcc_config.pcc_can_resolve_nai_to_sid; flag_x = (session->pcc_config.max_sid_depth == 0); } struct pcep_object_tlv_sr_pce_capability *sr_pce_cap_tlv = pcep_tlv_create_sr_pce_capability( flag_n, flag_x, session->pcc_config.max_sid_depth); double_linked_list *sub_tlv_list = NULL; if (session->pcc_config.pcep_msg_versioning ->draft_ietf_pce_segment_routing_07 == true) { /* With draft07, send the sr_pce_cap_tlv as a normal TLV */ dll_append(tlv_list, sr_pce_cap_tlv); } else { /* With draft16, send the sr_pce_cap_tlv as a sub-TLV in * the path_setup_type_capability TLV */ sub_tlv_list = dll_initialize(); dll_append(sub_tlv_list, sr_pce_cap_tlv); } uint8_t *pst = pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint8_t)); *pst = SR_TE_PST; double_linked_list *pst_list = dll_initialize(); dll_append(pst_list, pst); dll_append(tlv_list, pcep_tlv_create_path_setup_type_capability( pst_list, sub_tlv_list)); } struct pcep_message *open_msg = pcep_msg_create_open_with_tlvs( session->pcc_config.keep_alive_seconds, session->pcc_config.dead_timer_seconds, session->session_id, tlv_list); pcep_log( LOG_INFO, "%s: [%ld-%ld] pcep_session_logic create open message: TLVs [%d] for session [%d]", __func__, time(NULL), pthread_self(), tlv_list->num_entries, session->session_id); return (open_msg); } void send_pcep_open(pcep_session *session) { session_send_message(session, create_pcep_open(session)); } /* This is a blocking call, since it is synchronized with destroy_pcep_session() * and session_logic_loop(). It may be possible that the session has been * deleted but API users havent been informed yet. */ bool session_exists(pcep_session *session) { if (session_logic_handle_ == NULL) { pcep_log(LOG_DEBUG, "%s: session_exists session_logic_handle_ is NULL", __func__); return false; } pthread_mutex_lock(&(session_logic_handle_->session_list_mutex)); bool retval = (ordered_list_find(session_logic_handle_->session_list, session) != NULL); pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex)); return retval; }