diff options
Diffstat (limited to '')
-rw-r--r-- | pceplib/pcep_session_logic.c | 689 |
1 files changed, 689 insertions, 0 deletions
diff --git a/pceplib/pcep_session_logic.c b/pceplib/pcep_session_logic.c new file mode 100644 index 0000000..78d1072 --- /dev/null +++ b/pceplib/pcep_session_logic.c @@ -0,0 +1,689 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + * + * Author : Brady Johnson <brady@voltanet.io> + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <errno.h> +#include <limits.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#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(void) +{ + 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_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; + } + + 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)); + + 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(void) +{ + 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( + /* U flag */ + session->pcc_config + .support_stateful_pce_lsp_update, + /* S flag */ + session->pcc_config.support_include_db_version, + /* I flag */ + session->pcc_config + .support_pce_lsp_instantiation, + /* T flag */ + session->pcc_config + .support_lsp_triggered_resync, + /* D flag */ + session->pcc_config.support_lsp_delta_sync, + /* F flag */ + session->pcc_config + .support_pce_triggered_initial_sync)); + } + + 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; +} |