mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-04-28 17:42:20 +00:00

This new dynamic module makes pathd behave as a PCC for dynamic candidate path using the external library pcpelib https://github.com/volta-networks/pceplib . The candidate paths defined as dynamic will trigger computation requests to the configured PCE, and the PCE response will be used to update the policy. It supports multiple PCE. The one with smaller precedence will be elected as the master PCE, and only if the connection repeatedly fails, the PCC will switch to another PCE. Example of configuration: segment-routing traffic-eng pcep pce-config CONF source-address ip 10.10.10.10 sr-draft07 ! pce PCE1 config CONF address ip 1.1.1.1 ! pce PCE2 config CONF address ip 2.2.2.2 ! pcc peer PCE1 precedence 10 peer PCE2 precedence 20 ! ! ! ! Co-authored-by: Brady Johnson <brady@voltanet.io> Co-authored-by: Emanuele Di Pascale <emanuele@voltanet.io> Co-authored-by: GalaxyGorilla <sascha@netdef.org> Co-authored-by: Javier Garcia <javier.garcia@voltanet.io> Co-authored-by: Renato Westphal <renato@opensourcerouting.org> Co-authored-by: Sebastien Merle <sebastien@netdef.org> Signed-off-by: Sebastien Merle <sebastien@netdef.org>
1818 lines
52 KiB
C
1818 lines
52 KiB
C
/*
|
|
* Copyright (C) 2020 NetDEF, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program 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 General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; see the file COPYING; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/* TODOS AND KNOWN ISSUES:
|
|
- Delete mapping from NB keys to PLSPID when an LSP is deleted either
|
|
by the PCE or by NB.
|
|
- Revert the hacks to work around ODL requiring a report with
|
|
operational status DOWN when an LSP is activated.
|
|
- Enforce only the PCE a policy has been delegated to can update it.
|
|
- If the router-id is used because the PCC IP is not specified
|
|
(either IPv4 or IPv6), the connection to the PCE is not reset
|
|
when the router-id changes.
|
|
*/
|
|
|
|
#include <zebra.h>
|
|
|
|
#include "log.h"
|
|
#include "command.h"
|
|
#include "libfrr.h"
|
|
#include "printfrr.h"
|
|
#include "version.h"
|
|
#include "northbound.h"
|
|
#include "frr_pthread.h"
|
|
#include "jhash.h"
|
|
|
|
#include "pathd/pathd.h"
|
|
#include "pathd/path_zebra.h"
|
|
#include "pathd/path_errors.h"
|
|
#include "pathd/path_pcep_memory.h"
|
|
#include "pathd/path_pcep.h"
|
|
#include "pathd/path_pcep_controller.h"
|
|
#include "pathd/path_pcep_lib.h"
|
|
#include "pathd/path_pcep_config.h"
|
|
#include "pathd/path_pcep_debug.h"
|
|
|
|
|
|
/* The number of time we will skip connecting if we are missing the PCC
|
|
* address for an inet family different from the selected transport one*/
|
|
#define OTHER_FAMILY_MAX_RETRIES 4
|
|
#define MAX_ERROR_MSG_SIZE 256
|
|
#define MAX_COMPREQ_TRIES 3
|
|
|
|
|
|
/* PCEP Event Handler */
|
|
static void handle_pcep_open(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct pcep_message *msg);
|
|
static void handle_pcep_message(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct pcep_message *msg);
|
|
static void handle_pcep_lsp_update(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct pcep_message *msg);
|
|
static void handle_pcep_lsp_initiate(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct pcep_message *msg);
|
|
static void handle_pcep_comp_reply(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct pcep_message *msg);
|
|
|
|
/* Internal Functions */
|
|
static const char *ipaddr_type_name(struct ipaddr *addr);
|
|
static bool filter_path(struct pcc_state *pcc_state, struct path *path);
|
|
static void select_pcc_addresses(struct pcc_state *pcc_state);
|
|
static void select_transport_address(struct pcc_state *pcc_state);
|
|
static void update_tag(struct pcc_state *pcc_state);
|
|
static void update_originator(struct pcc_state *pcc_state);
|
|
static void schedule_reconnect(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state);
|
|
static void schedule_session_timeout(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state);
|
|
static void cancel_session_timeout(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state);
|
|
static void send_pcep_message(struct pcc_state *pcc_state,
|
|
struct pcep_message *msg);
|
|
static void send_pcep_error(struct pcc_state *pcc_state,
|
|
enum pcep_error_type error_type,
|
|
enum pcep_error_value error_value);
|
|
static void send_report(struct pcc_state *pcc_state, struct path *path);
|
|
static void send_comp_request(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct req_entry *req);
|
|
static void cancel_comp_requests(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state);
|
|
static void cancel_comp_request(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct req_entry *req);
|
|
static void specialize_outgoing_path(struct pcc_state *pcc_state,
|
|
struct path *path);
|
|
static void specialize_incoming_path(struct pcc_state *pcc_state,
|
|
struct path *path);
|
|
static bool validate_incoming_path(struct pcc_state *pcc_state,
|
|
struct path *path, char *errbuff,
|
|
size_t buffsize);
|
|
static void set_pcc_address(struct pcc_state *pcc_state,
|
|
struct lsp_nb_key *nbkey, struct ipaddr *addr);
|
|
static int compare_pcc_opts(struct pcc_opts *lhs, struct pcc_opts *rhs);
|
|
static int compare_pce_opts(struct pce_opts *lhs, struct pce_opts *rhs);
|
|
static int get_previous_best_pce(struct pcc_state **pcc);
|
|
static int get_best_pce(struct pcc_state **pcc);
|
|
static int get_pce_count_connected(struct pcc_state **pcc);
|
|
static bool update_best_pce(struct pcc_state **pcc, int best);
|
|
|
|
/* Data Structure Helper Functions */
|
|
static void lookup_plspid(struct pcc_state *pcc_state, struct path *path);
|
|
static void lookup_nbkey(struct pcc_state *pcc_state, struct path *path);
|
|
static void free_req_entry(struct req_entry *req);
|
|
static struct req_entry *push_new_req(struct pcc_state *pcc_state,
|
|
struct path *path);
|
|
static void repush_req(struct pcc_state *pcc_state, struct req_entry *req);
|
|
static struct req_entry *pop_req(struct pcc_state *pcc_state, uint32_t reqid);
|
|
static bool add_reqid_mapping(struct pcc_state *pcc_state, struct path *path);
|
|
static void remove_reqid_mapping(struct pcc_state *pcc_state,
|
|
struct path *path);
|
|
static uint32_t lookup_reqid(struct pcc_state *pcc_state, struct path *path);
|
|
static bool has_pending_req_for(struct pcc_state *pcc_state, struct path *path);
|
|
|
|
/* Data Structure Callbacks */
|
|
static int plspid_map_cmp(const struct plspid_map_data *a,
|
|
const struct plspid_map_data *b);
|
|
static uint32_t plspid_map_hash(const struct plspid_map_data *e);
|
|
static int nbkey_map_cmp(const struct nbkey_map_data *a,
|
|
const struct nbkey_map_data *b);
|
|
static uint32_t nbkey_map_hash(const struct nbkey_map_data *e);
|
|
static int req_map_cmp(const struct req_map_data *a,
|
|
const struct req_map_data *b);
|
|
static uint32_t req_map_hash(const struct req_map_data *e);
|
|
|
|
/* Data Structure Declarations */
|
|
DECLARE_HASH(plspid_map, struct plspid_map_data, mi, plspid_map_cmp,
|
|
plspid_map_hash)
|
|
DECLARE_HASH(nbkey_map, struct nbkey_map_data, mi, nbkey_map_cmp,
|
|
nbkey_map_hash)
|
|
DECLARE_HASH(req_map, struct req_map_data, mi, req_map_cmp, req_map_hash)
|
|
|
|
static inline int req_entry_compare(const struct req_entry *a,
|
|
const struct req_entry *b)
|
|
{
|
|
return a->path->req_id - b->path->req_id;
|
|
}
|
|
RB_GENERATE(req_entry_head, req_entry, entry, req_entry_compare)
|
|
|
|
|
|
/* ------------ API Functions ------------ */
|
|
|
|
struct pcc_state *pcep_pcc_initialize(struct ctrl_state *ctrl_state, int index)
|
|
{
|
|
struct pcc_state *pcc_state = XCALLOC(MTYPE_PCEP, sizeof(*pcc_state));
|
|
|
|
pcc_state->id = index;
|
|
pcc_state->status = PCEP_PCC_DISCONNECTED;
|
|
pcc_state->next_reqid = 1;
|
|
pcc_state->next_plspid = 1;
|
|
|
|
RB_INIT(req_entry_head, &pcc_state->requests);
|
|
|
|
update_tag(pcc_state);
|
|
update_originator(pcc_state);
|
|
|
|
PCEP_DEBUG("%s PCC initialized", pcc_state->tag);
|
|
|
|
return pcc_state;
|
|
}
|
|
|
|
void pcep_pcc_finalize(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state)
|
|
{
|
|
PCEP_DEBUG("%s PCC finalizing...", pcc_state->tag);
|
|
|
|
pcep_pcc_disable(ctrl_state, pcc_state);
|
|
|
|
if (pcc_state->pcc_opts != NULL) {
|
|
XFREE(MTYPE_PCEP, pcc_state->pcc_opts);
|
|
pcc_state->pcc_opts = NULL;
|
|
}
|
|
if (pcc_state->pce_opts != NULL) {
|
|
XFREE(MTYPE_PCEP, pcc_state->pce_opts);
|
|
pcc_state->pce_opts = NULL;
|
|
}
|
|
if (pcc_state->originator != NULL) {
|
|
XFREE(MTYPE_PCEP, pcc_state->originator);
|
|
pcc_state->originator = NULL;
|
|
}
|
|
|
|
if (pcc_state->t_reconnect != NULL) {
|
|
thread_cancel(&pcc_state->t_reconnect);
|
|
pcc_state->t_reconnect = NULL;
|
|
}
|
|
|
|
if (pcc_state->t_update_best != NULL) {
|
|
thread_cancel(&pcc_state->t_update_best);
|
|
pcc_state->t_update_best = NULL;
|
|
}
|
|
|
|
if (pcc_state->t_session_timeout != NULL) {
|
|
thread_cancel(&pcc_state->t_session_timeout);
|
|
pcc_state->t_session_timeout = NULL;
|
|
}
|
|
|
|
XFREE(MTYPE_PCEP, pcc_state);
|
|
}
|
|
|
|
int compare_pcc_opts(struct pcc_opts *lhs, struct pcc_opts *rhs)
|
|
{
|
|
int retval;
|
|
|
|
if (lhs == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
if (rhs == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
retval = lhs->port - rhs->port;
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
retval = lhs->msd - rhs->msd;
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
if (IS_IPADDR_V4(&lhs->addr)) {
|
|
retval = memcmp(&lhs->addr.ipaddr_v4, &rhs->addr.ipaddr_v4,
|
|
sizeof(lhs->addr.ipaddr_v4));
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
} else if (IS_IPADDR_V6(&lhs->addr)) {
|
|
retval = memcmp(&lhs->addr.ipaddr_v6, &rhs->addr.ipaddr_v6,
|
|
sizeof(lhs->addr.ipaddr_v6));
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int compare_pce_opts(struct pce_opts *lhs, struct pce_opts *rhs)
|
|
{
|
|
if (lhs == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
if (rhs == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
int retval = lhs->port - rhs->port;
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
retval = strcmp(lhs->pce_name, rhs->pce_name);
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
retval = lhs->precedence - rhs->precedence;
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
retval = memcmp(&lhs->addr, &rhs->addr, sizeof(lhs->addr));
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pcep_pcc_update(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state,
|
|
struct pcc_opts *pcc_opts, struct pce_opts *pce_opts)
|
|
{
|
|
int ret = 0;
|
|
|
|
// If the options did not change, then there is nothing to do
|
|
if ((compare_pce_opts(pce_opts, pcc_state->pce_opts) == 0)
|
|
&& (compare_pcc_opts(pcc_opts, pcc_state->pcc_opts) == 0)) {
|
|
return ret;
|
|
}
|
|
|
|
if ((ret = pcep_pcc_disable(ctrl_state, pcc_state))) {
|
|
XFREE(MTYPE_PCEP, pcc_opts);
|
|
XFREE(MTYPE_PCEP, pce_opts);
|
|
return ret;
|
|
}
|
|
|
|
if (pcc_state->pcc_opts != NULL) {
|
|
XFREE(MTYPE_PCEP, pcc_state->pcc_opts);
|
|
}
|
|
if (pcc_state->pce_opts != NULL) {
|
|
XFREE(MTYPE_PCEP, pcc_state->pce_opts);
|
|
}
|
|
|
|
pcc_state->pcc_opts = pcc_opts;
|
|
pcc_state->pce_opts = pce_opts;
|
|
|
|
if (IS_IPADDR_V4(&pcc_opts->addr)) {
|
|
pcc_state->pcc_addr_v4 = pcc_opts->addr.ipaddr_v4;
|
|
SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4);
|
|
} else {
|
|
UNSET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4);
|
|
}
|
|
|
|
if (IS_IPADDR_V6(&pcc_opts->addr)) {
|
|
memcpy(&pcc_state->pcc_addr_v6, &pcc_opts->addr.ipaddr_v6,
|
|
sizeof(struct in6_addr));
|
|
SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6);
|
|
} else {
|
|
UNSET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6);
|
|
}
|
|
|
|
update_tag(pcc_state);
|
|
update_originator(pcc_state);
|
|
|
|
return pcep_pcc_enable(ctrl_state, pcc_state);
|
|
}
|
|
|
|
void pcep_pcc_reconnect(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state)
|
|
{
|
|
if (pcc_state->status == PCEP_PCC_DISCONNECTED)
|
|
pcep_pcc_enable(ctrl_state, pcc_state);
|
|
}
|
|
|
|
int pcep_pcc_enable(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state)
|
|
{
|
|
char pcc_buff[40];
|
|
char pce_buff[40];
|
|
|
|
assert(pcc_state->status == PCEP_PCC_DISCONNECTED);
|
|
assert(pcc_state->sess == NULL);
|
|
|
|
if (pcc_state->t_reconnect != NULL) {
|
|
thread_cancel(&pcc_state->t_reconnect);
|
|
pcc_state->t_reconnect = NULL;
|
|
}
|
|
|
|
select_transport_address(pcc_state);
|
|
|
|
/* Even though we are connecting using IPv6. we want to have an IPv4
|
|
* address so we can handle candidate path with IPv4 endpoints */
|
|
if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) {
|
|
if (pcc_state->retry_count < OTHER_FAMILY_MAX_RETRIES) {
|
|
flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS,
|
|
"skipping connection to PCE %s:%d due to "
|
|
"missing PCC IPv4 address",
|
|
ipaddr2str(&pcc_state->pce_opts->addr,
|
|
pce_buff, sizeof(pce_buff)),
|
|
pcc_state->pce_opts->port);
|
|
schedule_reconnect(ctrl_state, pcc_state);
|
|
return 0;
|
|
} else {
|
|
flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS,
|
|
"missing IPv4 PCC address, IPv4 candidate "
|
|
"paths will be ignored");
|
|
}
|
|
}
|
|
|
|
/* Even though we are connecting using IPv4. we want to have an IPv6
|
|
* address so we can handle candidate path with IPv6 endpoints */
|
|
if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) {
|
|
if (pcc_state->retry_count < OTHER_FAMILY_MAX_RETRIES) {
|
|
flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS,
|
|
"skipping connection to PCE %s:%d due to "
|
|
"missing PCC IPv6 address",
|
|
ipaddr2str(&pcc_state->pce_opts->addr,
|
|
pce_buff, sizeof(pce_buff)),
|
|
pcc_state->pce_opts->port);
|
|
schedule_reconnect(ctrl_state, pcc_state);
|
|
return 0;
|
|
} else {
|
|
flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS,
|
|
"missing IPv6 PCC address, IPv6 candidate "
|
|
"paths will be ignored");
|
|
}
|
|
}
|
|
|
|
/* Even if the maximum retries to try to have all the familly addresses
|
|
* have been spent, we still need the one for the transport familly */
|
|
if (pcc_state->pcc_addr_tr.ipa_type == IPADDR_NONE) {
|
|
flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS,
|
|
"skipping connection to PCE %s:%d due to missing "
|
|
"PCC address",
|
|
ipaddr2str(&pcc_state->pce_opts->addr, pce_buff,
|
|
sizeof(pce_buff)),
|
|
pcc_state->pce_opts->port);
|
|
schedule_reconnect(ctrl_state, pcc_state);
|
|
return 0;
|
|
}
|
|
|
|
PCEP_DEBUG("%s PCC connecting", pcc_state->tag);
|
|
pcc_state->sess = pcep_lib_connect(
|
|
&pcc_state->pcc_addr_tr, pcc_state->pcc_opts->port,
|
|
&pcc_state->pce_opts->addr, pcc_state->pce_opts->port,
|
|
pcc_state->pcc_opts->msd, &pcc_state->pce_opts->config_opts);
|
|
|
|
if (pcc_state->sess == NULL) {
|
|
flog_warn(EC_PATH_PCEP_LIB_CONNECT,
|
|
"failed to connect to PCE %s:%d from %s:%d",
|
|
ipaddr2str(&pcc_state->pce_opts->addr, pce_buff,
|
|
sizeof(pce_buff)),
|
|
pcc_state->pce_opts->port,
|
|
ipaddr2str(&pcc_state->pcc_addr_tr, pcc_buff,
|
|
sizeof(pcc_buff)),
|
|
pcc_state->pcc_opts->port);
|
|
schedule_reconnect(ctrl_state, pcc_state);
|
|
return 0;
|
|
}
|
|
|
|
// In case some best pce alternative were waiting to activate
|
|
if (pcc_state->t_update_best != NULL) {
|
|
thread_cancel(&pcc_state->t_update_best);
|
|
pcc_state->t_update_best = NULL;
|
|
}
|
|
|
|
pcc_state->status = PCEP_PCC_CONNECTING;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pcep_pcc_disable(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state)
|
|
{
|
|
switch (pcc_state->status) {
|
|
case PCEP_PCC_DISCONNECTED:
|
|
return 0;
|
|
case PCEP_PCC_CONNECTING:
|
|
case PCEP_PCC_SYNCHRONIZING:
|
|
case PCEP_PCC_OPERATING:
|
|
PCEP_DEBUG("%s Disconnecting PCC...", pcc_state->tag);
|
|
cancel_comp_requests(ctrl_state, pcc_state);
|
|
pcep_lib_disconnect(pcc_state->sess);
|
|
/* No need to remove if any PCEs is connected */
|
|
if (get_pce_count_connected(ctrl_state->pcc) == 0) {
|
|
pcep_thread_remove_candidate_path_segments(ctrl_state,
|
|
pcc_state);
|
|
}
|
|
pcc_state->sess = NULL;
|
|
pcc_state->status = PCEP_PCC_DISCONNECTED;
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
void pcep_pcc_sync_path(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
if (pcc_state->status == PCEP_PCC_SYNCHRONIZING) {
|
|
path->is_synching = true;
|
|
} else if (pcc_state->status == PCEP_PCC_OPERATING)
|
|
path->is_synching = false;
|
|
else
|
|
return;
|
|
|
|
path->go_active = true;
|
|
|
|
/* Accumulate the dynamic paths without any LSP so computation
|
|
* requests can be performed after synchronization */
|
|
if ((path->type == SRTE_CANDIDATE_TYPE_DYNAMIC)
|
|
&& (path->first_hop == NULL)
|
|
&& !has_pending_req_for(pcc_state, path)) {
|
|
PCEP_DEBUG("%s Scheduling computation request for path %s",
|
|
pcc_state->tag, path->name);
|
|
push_new_req(pcc_state, path);
|
|
return;
|
|
}
|
|
|
|
/* Synchronize the path if the PCE supports LSP updates and the
|
|
* endpoint address familly is supported */
|
|
if (pcc_state->caps.is_stateful) {
|
|
if (filter_path(pcc_state, path)) {
|
|
PCEP_DEBUG("%s Synchronizing path %s", pcc_state->tag,
|
|
path->name);
|
|
send_report(pcc_state, path);
|
|
} else {
|
|
PCEP_DEBUG(
|
|
"%s Skipping %s candidate path %s "
|
|
"synchronization",
|
|
pcc_state->tag,
|
|
ipaddr_type_name(&path->nbkey.endpoint),
|
|
path->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void pcep_pcc_sync_done(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state)
|
|
{
|
|
struct req_entry *req;
|
|
|
|
if (pcc_state->status != PCEP_PCC_SYNCHRONIZING
|
|
&& pcc_state->status != PCEP_PCC_OPERATING)
|
|
return;
|
|
|
|
if (pcc_state->caps.is_stateful
|
|
&& pcc_state->status == PCEP_PCC_SYNCHRONIZING) {
|
|
struct path *path = pcep_new_path();
|
|
*path = (struct path){.name = NULL,
|
|
.srp_id = 0,
|
|
.plsp_id = 0,
|
|
.status = PCEP_LSP_OPERATIONAL_DOWN,
|
|
.do_remove = false,
|
|
.go_active = false,
|
|
.was_created = false,
|
|
.was_removed = false,
|
|
.is_synching = false,
|
|
.is_delegated = false,
|
|
.first_hop = NULL,
|
|
.first_metric = NULL};
|
|
send_report(pcc_state, path);
|
|
pcep_free_path(path);
|
|
}
|
|
|
|
pcc_state->synchronized = true;
|
|
pcc_state->status = PCEP_PCC_OPERATING;
|
|
|
|
PCEP_DEBUG("%s Synchronization done", pcc_state->tag);
|
|
|
|
/* Start the computation request accumulated during synchronization */
|
|
RB_FOREACH (req, req_entry_head, &pcc_state->requests) {
|
|
send_comp_request(ctrl_state, pcc_state, req);
|
|
}
|
|
}
|
|
|
|
void pcep_pcc_send_report(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
if (pcc_state->status != PCEP_PCC_OPERATING)
|
|
return;
|
|
|
|
if (pcc_state->caps.is_stateful) {
|
|
PCEP_DEBUG("%s Send report for candidate path %s",
|
|
pcc_state->tag, path->name);
|
|
send_report(pcc_state, path);
|
|
}
|
|
}
|
|
|
|
/* ------------ Timeout handler ------------ */
|
|
|
|
void pcep_pcc_timeout_handler(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
enum pcep_ctrl_timer_type type, void *param)
|
|
{
|
|
struct req_entry *req;
|
|
|
|
switch (type) {
|
|
case TO_COMPUTATION_REQUEST:
|
|
assert(param != NULL);
|
|
req = (struct req_entry *)param;
|
|
pop_req(pcc_state, req->path->req_id);
|
|
flog_warn(EC_PATH_PCEP_COMPUTATION_REQUEST_TIMEOUT,
|
|
"Computation request %d timeout", req->path->req_id);
|
|
cancel_comp_request(ctrl_state, pcc_state, req);
|
|
if (req->retry_count++ < MAX_COMPREQ_TRIES) {
|
|
repush_req(pcc_state, req);
|
|
send_comp_request(ctrl_state, pcc_state, req);
|
|
return;
|
|
}
|
|
if (pcc_state->caps.is_stateful) {
|
|
struct path *path;
|
|
PCEP_DEBUG(
|
|
"%s Delegating undefined dynamic path %s to PCE %s",
|
|
pcc_state->tag, req->path->name,
|
|
pcc_state->originator);
|
|
path = pcep_copy_path(req->path);
|
|
path->is_delegated = true;
|
|
send_report(pcc_state, path);
|
|
free_req_entry(req);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* ------------ Pathd event handler ------------ */
|
|
|
|
void pcep_pcc_pathd_event_handler(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
enum pcep_pathd_event_type type,
|
|
struct path *path)
|
|
{
|
|
struct req_entry *req;
|
|
|
|
if (pcc_state->status != PCEP_PCC_OPERATING)
|
|
return;
|
|
|
|
/* Skipping candidate path with endpoint that do not match the
|
|
* configured or deduced PCC IP version */
|
|
if (!filter_path(pcc_state, path)) {
|
|
PCEP_DEBUG("%s Skipping %s candidate path %s event",
|
|
pcc_state->tag,
|
|
ipaddr_type_name(&path->nbkey.endpoint), path->name);
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case PCEP_PATH_CREATED:
|
|
if (has_pending_req_for(pcc_state, path)) {
|
|
PCEP_DEBUG(
|
|
"%s Candidate path %s created, computation request already sent",
|
|
pcc_state->tag, path->name);
|
|
return;
|
|
}
|
|
PCEP_DEBUG("%s Candidate path %s created", pcc_state->tag,
|
|
path->name);
|
|
if ((path->first_hop == NULL)
|
|
&& (path->type == SRTE_CANDIDATE_TYPE_DYNAMIC)) {
|
|
req = push_new_req(pcc_state, path);
|
|
send_comp_request(ctrl_state, pcc_state, req);
|
|
} else if (pcc_state->caps.is_stateful)
|
|
send_report(pcc_state, path);
|
|
return;
|
|
case PCEP_PATH_UPDATED:
|
|
PCEP_DEBUG("%s Candidate path %s updated", pcc_state->tag,
|
|
path->name);
|
|
if (pcc_state->caps.is_stateful)
|
|
send_report(pcc_state, path);
|
|
return;
|
|
case PCEP_PATH_REMOVED:
|
|
PCEP_DEBUG("%s Candidate path %s removed", pcc_state->tag,
|
|
path->name);
|
|
path->was_removed = true;
|
|
if (pcc_state->caps.is_stateful)
|
|
send_report(pcc_state, path);
|
|
return;
|
|
default:
|
|
flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR,
|
|
"Unexpected pathd event received by pcc %s: %u",
|
|
pcc_state->tag, type);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/* ------------ PCEP event handler ------------ */
|
|
|
|
void pcep_pcc_pcep_event_handler(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state, pcep_event *event)
|
|
{
|
|
PCEP_DEBUG("%s Received PCEP event: %s", pcc_state->tag,
|
|
pcep_event_type_name(event->event_type));
|
|
switch (event->event_type) {
|
|
case PCC_CONNECTED_TO_PCE:
|
|
assert(PCEP_PCC_CONNECTING == pcc_state->status);
|
|
PCEP_DEBUG("%s Connection established", pcc_state->tag);
|
|
pcc_state->status = PCEP_PCC_SYNCHRONIZING;
|
|
pcc_state->retry_count = 0;
|
|
pcc_state->synchronized = false;
|
|
PCEP_DEBUG("%s Starting PCE synchronization", pcc_state->tag);
|
|
cancel_session_timeout(ctrl_state, pcc_state);
|
|
pcep_pcc_calculate_best_pce(ctrl_state->pcc);
|
|
pcep_thread_start_sync(ctrl_state, pcc_state->id);
|
|
break;
|
|
case PCC_SENT_INVALID_OPEN:
|
|
PCEP_DEBUG("%s Sent invalid OPEN message", pcc_state->tag);
|
|
PCEP_DEBUG(
|
|
"%s Reconciling values: keep alive (%d) dead timer (%d) seconds ",
|
|
pcc_state->tag,
|
|
pcc_state->sess->pcc_config
|
|
.keep_alive_pce_negotiated_timer_seconds,
|
|
pcc_state->sess->pcc_config
|
|
.dead_timer_pce_negotiated_seconds);
|
|
pcc_state->pce_opts->config_opts.keep_alive_seconds =
|
|
pcc_state->sess->pcc_config
|
|
.keep_alive_pce_negotiated_timer_seconds;
|
|
pcc_state->pce_opts->config_opts.dead_timer_seconds =
|
|
pcc_state->sess->pcc_config
|
|
.dead_timer_pce_negotiated_seconds;
|
|
break;
|
|
|
|
case PCC_RCVD_INVALID_OPEN:
|
|
PCEP_DEBUG("%s Received invalid OPEN message", pcc_state->tag);
|
|
PCEP_DEBUG_PCEP("%s PCEP message: %s", pcc_state->tag,
|
|
format_pcep_message(event->message));
|
|
break;
|
|
case PCE_DEAD_TIMER_EXPIRED:
|
|
case PCE_CLOSED_SOCKET:
|
|
case PCE_SENT_PCEP_CLOSE:
|
|
case PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED:
|
|
case PCC_PCEP_SESSION_CLOSED:
|
|
case PCC_RCVD_MAX_INVALID_MSGS:
|
|
case PCC_RCVD_MAX_UNKOWN_MSGS:
|
|
pcep_pcc_disable(ctrl_state, pcc_state);
|
|
schedule_reconnect(ctrl_state, pcc_state);
|
|
schedule_session_timeout(ctrl_state, pcc_state);
|
|
break;
|
|
case MESSAGE_RECEIVED:
|
|
PCEP_DEBUG_PCEP("%s Received PCEP message: %s", pcc_state->tag,
|
|
format_pcep_message(event->message));
|
|
if (pcc_state->status == PCEP_PCC_CONNECTING) {
|
|
if (event->message->msg_header->type == PCEP_TYPE_OPEN)
|
|
handle_pcep_open(ctrl_state, pcc_state,
|
|
event->message);
|
|
break;
|
|
}
|
|
assert(pcc_state->status == PCEP_PCC_SYNCHRONIZING
|
|
|| pcc_state->status == PCEP_PCC_OPERATING);
|
|
handle_pcep_message(ctrl_state, pcc_state, event->message);
|
|
break;
|
|
default:
|
|
flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEPLIB_EVENT,
|
|
"Unexpected event from pceplib: %s",
|
|
format_pcep_event(event));
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*------------------ Multi-PCE --------------------- */
|
|
|
|
/* Internal util function, returns true if sync is necessary, false otherwise */
|
|
bool update_best_pce(struct pcc_state **pcc, int best)
|
|
{
|
|
PCEP_DEBUG(" recalculating pce precedence ");
|
|
if (best) {
|
|
struct pcc_state *best_pcc_state =
|
|
pcep_pcc_get_pcc_by_id(pcc, best);
|
|
if (best_pcc_state->previous_best != best_pcc_state->is_best) {
|
|
PCEP_DEBUG(" %s Resynch best (%i) previous best (%i)",
|
|
best_pcc_state->tag, best_pcc_state->id,
|
|
best_pcc_state->previous_best);
|
|
return true;
|
|
} else {
|
|
PCEP_DEBUG(
|
|
" %s No Resynch best (%i) previous best (%i)",
|
|
best_pcc_state->tag, best_pcc_state->id,
|
|
best_pcc_state->previous_best);
|
|
}
|
|
} else {
|
|
PCEP_DEBUG(" No best pce available, all pce seem disconnected");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int get_best_pce(struct pcc_state **pcc)
|
|
{
|
|
for (int i = 0; i < MAX_PCC; i++) {
|
|
if (pcc[i] && pcc[i]->pce_opts) {
|
|
if (pcc[i]->is_best == true) {
|
|
return pcc[i]->id;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int get_pce_count_connected(struct pcc_state **pcc)
|
|
{
|
|
int count = 0;
|
|
for (int i = 0; i < MAX_PCC; i++) {
|
|
if (pcc[i] && pcc[i]->pce_opts
|
|
&& pcc[i]->status != PCEP_PCC_DISCONNECTED) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int get_previous_best_pce(struct pcc_state **pcc)
|
|
{
|
|
int previous_best_pce = -1;
|
|
|
|
for (int i = 0; i < MAX_PCC; i++) {
|
|
if (pcc[i] && pcc[i]->pce_opts && pcc[i]->previous_best == true
|
|
&& pcc[i]->status != PCEP_PCC_DISCONNECTED) {
|
|
previous_best_pce = i;
|
|
break;
|
|
}
|
|
}
|
|
return previous_best_pce != -1 ? pcc[previous_best_pce]->id : 0;
|
|
}
|
|
|
|
/* Called by path_pcep_controller EV_REMOVE_PCC
|
|
* Event handler when a PCC is removed. */
|
|
int pcep_pcc_multi_pce_remove_pcc(struct ctrl_state *ctrl_state,
|
|
struct pcc_state **pcc)
|
|
{
|
|
int new_best_pcc_id = -1;
|
|
new_best_pcc_id = pcep_pcc_calculate_best_pce(pcc);
|
|
if (new_best_pcc_id) {
|
|
if (update_best_pce(ctrl_state->pcc, new_best_pcc_id) == true) {
|
|
pcep_thread_start_sync(ctrl_state, new_best_pcc_id);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Called by path_pcep_controller EV_SYNC_PATH
|
|
* Event handler when a path is sync'd. */
|
|
int pcep_pcc_multi_pce_sync_path(struct ctrl_state *ctrl_state, int pcc_id,
|
|
struct pcc_state **pcc)
|
|
{
|
|
int previous_best_pcc_id = -1;
|
|
|
|
if (pcc_id == get_best_pce(pcc)) {
|
|
previous_best_pcc_id = get_previous_best_pce(pcc);
|
|
if (previous_best_pcc_id != 0) {
|
|
/* while adding new pce, path has to resync to the
|
|
* previous best. pcep_thread_start_sync() will be
|
|
* called by the calling function */
|
|
if (update_best_pce(ctrl_state->pcc,
|
|
previous_best_pcc_id)
|
|
== true) {
|
|
cancel_comp_requests(
|
|
ctrl_state,
|
|
pcep_pcc_get_pcc_by_id(
|
|
pcc, previous_best_pcc_id));
|
|
pcep_thread_start_sync(ctrl_state,
|
|
previous_best_pcc_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Called by path_pcep_controller when the TM_CALCULATE_BEST_PCE
|
|
* timer expires */
|
|
int pcep_pcc_timer_update_best_pce(struct ctrl_state *ctrl_state, int pcc_id)
|
|
{
|
|
int ret = 0;
|
|
/* resync whatever was the new best */
|
|
int prev_best = get_best_pce(ctrl_state->pcc);
|
|
int best_id = pcep_pcc_calculate_best_pce(ctrl_state->pcc);
|
|
if (best_id && prev_best != best_id) { // Avoid Multiple call
|
|
struct pcc_state *pcc_state =
|
|
pcep_pcc_get_pcc_by_id(ctrl_state->pcc, best_id);
|
|
if (update_best_pce(ctrl_state->pcc, pcc_state->id) == true) {
|
|
pcep_thread_start_sync(ctrl_state, pcc_state->id);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Called by path_pcep_controller::pcep_thread_event_update_pce_options()
|
|
* Returns the best PCE id */
|
|
int pcep_pcc_calculate_best_pce(struct pcc_state **pcc)
|
|
{
|
|
int best_precedence = 255; // DEFAULT_PCE_PRECEDENCE;
|
|
int best_pce = -1;
|
|
int one_connected_pce = -1;
|
|
int previous_best_pce = -1;
|
|
int step_0_best = -1;
|
|
int step_0_previous = -1;
|
|
int pcc_count = 0;
|
|
|
|
// Get state
|
|
for (int i = 0; i < MAX_PCC; i++) {
|
|
if (pcc[i] && pcc[i]->pce_opts) {
|
|
zlog_debug(
|
|
"multi-pce: calculate all : i (%i) is_best (%i) previous_best (%i) ",
|
|
i, pcc[i]->is_best, pcc[i]->previous_best);
|
|
pcc_count++;
|
|
|
|
if (pcc[i]->is_best == true) {
|
|
step_0_best = i;
|
|
}
|
|
if (pcc[i]->previous_best == true) {
|
|
step_0_previous = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!pcc_count) {
|
|
return 0;
|
|
}
|
|
|
|
// Calculate best
|
|
for (int i = 0; i < MAX_PCC; i++) {
|
|
if (pcc[i] && pcc[i]->pce_opts
|
|
&& pcc[i]->status != PCEP_PCC_DISCONNECTED) {
|
|
one_connected_pce = i; // In case none better
|
|
if (pcc[i]->pce_opts->precedence <= best_precedence) {
|
|
if (best_pce != -1
|
|
&& pcc[best_pce]->pce_opts->precedence
|
|
== pcc[i]->pce_opts
|
|
->precedence) {
|
|
if (ipaddr_cmp(
|
|
&pcc[i]->pce_opts->addr,
|
|
&pcc[best_pce]
|
|
->pce_opts->addr)
|
|
> 0)
|
|
// collide of precedences so
|
|
// compare ip
|
|
best_pce = i;
|
|
} else {
|
|
if (!pcc[i]->previous_best) {
|
|
best_precedence =
|
|
pcc[i]->pce_opts
|
|
->precedence;
|
|
best_pce = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
zlog_debug(
|
|
"multi-pce: calculate data : sb (%i) sp (%i) oc (%i) b (%i) ",
|
|
step_0_best, step_0_previous, one_connected_pce, best_pce);
|
|
|
|
// Changed of state so ...
|
|
if (step_0_best != best_pce) {
|
|
// Calculate previous
|
|
previous_best_pce = step_0_best;
|
|
// Clean state
|
|
if (step_0_best != -1) {
|
|
pcc[step_0_best]->is_best = false;
|
|
}
|
|
if (step_0_previous != -1) {
|
|
pcc[step_0_previous]->previous_best = false;
|
|
}
|
|
|
|
// Set previous
|
|
if (previous_best_pce != -1
|
|
&& pcc[previous_best_pce]->status
|
|
== PCEP_PCC_DISCONNECTED) {
|
|
pcc[previous_best_pce]->previous_best = true;
|
|
zlog_debug("multi-pce: previous best pce (%i) ",
|
|
previous_best_pce + 1);
|
|
}
|
|
|
|
|
|
// Set best
|
|
if (best_pce != -1) {
|
|
pcc[best_pce]->is_best = true;
|
|
zlog_debug("multi-pce: best pce (%i) ", best_pce + 1);
|
|
} else {
|
|
if (one_connected_pce != -1) {
|
|
best_pce = one_connected_pce;
|
|
pcc[one_connected_pce]->is_best = true;
|
|
zlog_debug(
|
|
"multi-pce: one connected best pce (default) (%i) ",
|
|
one_connected_pce + 1);
|
|
} else {
|
|
for (int i = 0; i < MAX_PCC; i++) {
|
|
if (pcc[i] && pcc[i]->pce_opts) {
|
|
best_pce = i;
|
|
pcc[i]->is_best = true;
|
|
zlog_debug(
|
|
"(disconnected) best pce (default) (%i) ",
|
|
i + 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ((best_pce == -1) ? 0 : pcc[best_pce]->id);
|
|
}
|
|
|
|
int pcep_pcc_get_pcc_id_by_ip_port(struct pcc_state **pcc,
|
|
struct pce_opts *pce_opts)
|
|
{
|
|
if (pcc == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
for (int idx = 0; idx < MAX_PCC; idx++) {
|
|
if (pcc[idx]) {
|
|
if ((ipaddr_cmp((const struct ipaddr *)&pcc[idx]
|
|
->pce_opts->addr,
|
|
(const struct ipaddr *)&pce_opts->addr)
|
|
== 0)
|
|
&& pcc[idx]->pce_opts->port == pce_opts->port) {
|
|
zlog_debug("found pcc_id (%d) idx (%d)",
|
|
pcc[idx]->id, idx);
|
|
return pcc[idx]->id;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int pcep_pcc_get_pcc_id_by_idx(struct pcc_state **pcc, int idx)
|
|
{
|
|
if (pcc == NULL || idx < 0) {
|
|
return 0;
|
|
}
|
|
|
|
return pcc[idx] ? pcc[idx]->id : 0;
|
|
}
|
|
|
|
struct pcc_state *pcep_pcc_get_pcc_by_id(struct pcc_state **pcc, int id)
|
|
{
|
|
if (pcc == NULL || id < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_PCC; i++) {
|
|
if (pcc[i]) {
|
|
if (pcc[i]->id == id) {
|
|
zlog_debug("found id (%d) pcc_idx (%d)",
|
|
pcc[i]->id, i);
|
|
return pcc[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct pcc_state *pcep_pcc_get_pcc_by_name(struct pcc_state **pcc,
|
|
const char *pce_name)
|
|
{
|
|
if (pcc == NULL || pce_name == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_PCC; i++) {
|
|
if (pcc[i] == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(pcc[i]->pce_opts->pce_name, pce_name) == 0) {
|
|
return pcc[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int pcep_pcc_get_pcc_idx_by_id(struct pcc_state **pcc, int id)
|
|
{
|
|
if (pcc == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
for (int idx = 0; idx < MAX_PCC; idx++) {
|
|
if (pcc[idx]) {
|
|
if (pcc[idx]->id == id) {
|
|
zlog_debug("found pcc_id (%d) array_idx (%d)",
|
|
pcc[idx]->id, idx);
|
|
return idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int pcep_pcc_get_free_pcc_idx(struct pcc_state **pcc)
|
|
{
|
|
assert(pcc != NULL);
|
|
|
|
for (int idx = 0; idx < MAX_PCC; idx++) {
|
|
if (pcc[idx] == NULL) {
|
|
zlog_debug("new pcc_idx (%d)", idx);
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int pcep_pcc_get_pcc_id(struct pcc_state *pcc)
|
|
{
|
|
return ((pcc == NULL) ? 0 : pcc->id);
|
|
}
|
|
|
|
void pcep_pcc_copy_pcc_info(struct pcc_state **pcc,
|
|
struct pcep_pcc_info *pcc_info)
|
|
{
|
|
struct pcc_state *pcc_state =
|
|
pcep_pcc_get_pcc_by_name(pcc, pcc_info->pce_name);
|
|
if (!pcc_state) {
|
|
return;
|
|
}
|
|
|
|
pcc_info->ctrl_state = NULL;
|
|
pcc_info->msd = pcc_state->pcc_opts->msd;
|
|
pcc_info->pcc_port = pcc_state->pcc_opts->port;
|
|
pcc_info->next_plspid = pcc_state->next_plspid;
|
|
pcc_info->next_reqid = pcc_state->next_reqid;
|
|
pcc_info->status = pcc_state->status;
|
|
pcc_info->pcc_id = pcc_state->id;
|
|
pcc_info->is_best_multi_pce = pcc_state->is_best;
|
|
pcc_info->precedence =
|
|
pcc_state->pce_opts ? pcc_state->pce_opts->precedence : 0;
|
|
memcpy(&pcc_info->pcc_addr, &pcc_state->pcc_addr_tr,
|
|
sizeof(struct ipaddr));
|
|
}
|
|
|
|
|
|
/*------------------ PCEP Message handlers --------------------- */
|
|
|
|
void handle_pcep_open(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state, struct pcep_message *msg)
|
|
{
|
|
assert(msg->msg_header->type == PCEP_TYPE_OPEN);
|
|
pcep_lib_parse_capabilities(msg, &pcc_state->caps);
|
|
PCEP_DEBUG("PCE capabilities: %s, %s%s",
|
|
pcc_state->caps.is_stateful ? "stateful" : "stateless",
|
|
pcc_state->caps.supported_ofs_are_known
|
|
? (pcc_state->caps.supported_ofs == 0
|
|
? "no objective functions supported"
|
|
: "supported objective functions are ")
|
|
: "supported objective functions are unknown",
|
|
format_objfun_set(pcc_state->caps.supported_ofs));
|
|
}
|
|
|
|
void handle_pcep_message(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state, struct pcep_message *msg)
|
|
{
|
|
if (pcc_state->status != PCEP_PCC_OPERATING)
|
|
return;
|
|
|
|
switch (msg->msg_header->type) {
|
|
case PCEP_TYPE_INITIATE:
|
|
handle_pcep_lsp_initiate(ctrl_state, pcc_state, msg);
|
|
break;
|
|
case PCEP_TYPE_UPDATE:
|
|
handle_pcep_lsp_update(ctrl_state, pcc_state, msg);
|
|
break;
|
|
case PCEP_TYPE_PCREP:
|
|
handle_pcep_comp_reply(ctrl_state, pcc_state, msg);
|
|
break;
|
|
default:
|
|
flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_MESSAGE,
|
|
"Unexpected pcep message from pceplib: %s",
|
|
format_pcep_message(msg));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void handle_pcep_lsp_update(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct pcep_message *msg)
|
|
{
|
|
char err[MAX_ERROR_MSG_SIZE] = "";
|
|
struct path *path;
|
|
path = pcep_lib_parse_path(msg);
|
|
lookup_nbkey(pcc_state, path);
|
|
/* TODO: Investigate if this is safe to do in the controller thread */
|
|
path_pcep_config_lookup(path);
|
|
specialize_incoming_path(pcc_state, path);
|
|
PCEP_DEBUG("%s Received LSP update", pcc_state->tag);
|
|
PCEP_DEBUG_PATH("%s", format_path(path));
|
|
|
|
if (validate_incoming_path(pcc_state, path, err, sizeof(err)))
|
|
pcep_thread_update_path(ctrl_state, pcc_state->id, path);
|
|
else {
|
|
/* FIXME: Monitor the amount of errors from the PCE and
|
|
* possibly disconnect and blacklist */
|
|
flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE,
|
|
"Unsupported PCEP protocol feature: %s", err);
|
|
pcep_free_path(path);
|
|
}
|
|
}
|
|
|
|
void handle_pcep_lsp_initiate(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct pcep_message *msg)
|
|
{
|
|
PCEP_DEBUG("%s Received LSP initiate, not supported yet",
|
|
pcc_state->tag);
|
|
|
|
/* TODO when we support both PCC and PCE initiated sessions,
|
|
* we should first check the session type before
|
|
* rejecting this message. */
|
|
send_pcep_error(pcc_state, PCEP_ERRT_INVALID_OPERATION,
|
|
PCEP_ERRV_LSP_NOT_PCE_INITIATED);
|
|
}
|
|
|
|
void handle_pcep_comp_reply(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state,
|
|
struct pcep_message *msg)
|
|
{
|
|
char err[MAX_ERROR_MSG_SIZE] = "";
|
|
struct req_entry *req;
|
|
struct path *path;
|
|
|
|
path = pcep_lib_parse_path(msg);
|
|
req = pop_req(pcc_state, path->req_id);
|
|
if (req == NULL) {
|
|
/* TODO: check the rate of bad computation reply and close
|
|
* the connection if more that a given rate.
|
|
*/
|
|
PCEP_DEBUG(
|
|
"%s Received computation reply for unknown request "
|
|
"%d",
|
|
pcc_state->tag, path->req_id);
|
|
PCEP_DEBUG_PATH("%s", format_path(path));
|
|
send_pcep_error(pcc_state, PCEP_ERRT_UNKNOWN_REQ_REF,
|
|
PCEP_ERRV_UNASSIGNED);
|
|
return;
|
|
}
|
|
|
|
/* Cancel the computation request timeout */
|
|
pcep_thread_cancel_timer(&req->t_retry);
|
|
|
|
/* Transfer relevent metadata from the request to the response */
|
|
path->nbkey = req->path->nbkey;
|
|
path->plsp_id = req->path->plsp_id;
|
|
path->type = req->path->type;
|
|
path->name = XSTRDUP(MTYPE_PCEP, req->path->name);
|
|
specialize_incoming_path(pcc_state, path);
|
|
|
|
PCEP_DEBUG("%s Received computation reply %d (no-path: %s)",
|
|
pcc_state->tag, path->req_id,
|
|
path->no_path ? "true" : "false");
|
|
PCEP_DEBUG_PATH("%s", format_path(path));
|
|
|
|
if (path->no_path) {
|
|
PCEP_DEBUG("%s Computation for path %s did not find any result",
|
|
pcc_state->tag, path->name);
|
|
} else if (validate_incoming_path(pcc_state, path, err, sizeof(err))) {
|
|
/* Updating a dynamic path will automatically delegate it */
|
|
pcep_thread_update_path(ctrl_state, pcc_state->id, path);
|
|
free_req_entry(req);
|
|
return;
|
|
} else {
|
|
/* FIXME: Monitor the amount of errors from the PCE and
|
|
* possibly disconnect and blacklist */
|
|
flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE,
|
|
"Unsupported PCEP protocol feature: %s", err);
|
|
}
|
|
|
|
pcep_free_path(path);
|
|
|
|
/* Delegate the path regardless of the outcome */
|
|
/* TODO: For now we are using the path from the request, when
|
|
* pathd API is thread safe, we could get a new path */
|
|
if (pcc_state->caps.is_stateful) {
|
|
PCEP_DEBUG("%s Delegating undefined dynamic path %s to PCE %s",
|
|
pcc_state->tag, path->name, pcc_state->originator);
|
|
path = pcep_copy_path(req->path);
|
|
path->is_delegated = true;
|
|
send_report(pcc_state, path);
|
|
pcep_free_path(path);
|
|
}
|
|
|
|
free_req_entry(req);
|
|
}
|
|
|
|
|
|
/* ------------ Internal Functions ------------ */
|
|
|
|
const char *ipaddr_type_name(struct ipaddr *addr)
|
|
{
|
|
if (IS_IPADDR_V4(addr))
|
|
return "IPv4";
|
|
if (IS_IPADDR_V6(addr))
|
|
return "IPv6";
|
|
return "undefined";
|
|
}
|
|
|
|
bool filter_path(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
return (IS_IPADDR_V4(&path->nbkey.endpoint)
|
|
&& CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4))
|
|
|| (IS_IPADDR_V6(&path->nbkey.endpoint)
|
|
&& CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6));
|
|
}
|
|
|
|
void select_pcc_addresses(struct pcc_state *pcc_state)
|
|
{
|
|
/* If no IPv4 address was specified, try to get one from zebra */
|
|
if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) {
|
|
if (get_ipv4_router_id(&pcc_state->pcc_addr_v4)) {
|
|
SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4);
|
|
}
|
|
}
|
|
|
|
/* If no IPv6 address was specified, try to get one from zebra */
|
|
if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) {
|
|
if (get_ipv6_router_id(&pcc_state->pcc_addr_v6)) {
|
|
SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6);
|
|
}
|
|
}
|
|
}
|
|
|
|
void select_transport_address(struct pcc_state *pcc_state)
|
|
{
|
|
struct ipaddr *taddr = &pcc_state->pcc_addr_tr;
|
|
|
|
select_pcc_addresses(pcc_state);
|
|
|
|
taddr->ipa_type = IPADDR_NONE;
|
|
|
|
/* Select a transport source address in function of the configured PCE
|
|
* address */
|
|
if (IS_IPADDR_V4(&pcc_state->pce_opts->addr)) {
|
|
if (CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) {
|
|
taddr->ipa_type = IPADDR_V4;
|
|
taddr->ipaddr_v4 = pcc_state->pcc_addr_v4;
|
|
}
|
|
} else {
|
|
if (CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) {
|
|
taddr->ipa_type = IPADDR_V6;
|
|
taddr->ipaddr_v6 = pcc_state->pcc_addr_v6;
|
|
}
|
|
}
|
|
}
|
|
|
|
void update_tag(struct pcc_state *pcc_state)
|
|
{
|
|
if (pcc_state->pce_opts != NULL) {
|
|
assert(!IS_IPADDR_NONE(&pcc_state->pce_opts->addr));
|
|
if (IS_IPADDR_V6(&pcc_state->pce_opts->addr)) {
|
|
snprintfrr(pcc_state->tag, sizeof(pcc_state->tag),
|
|
"%pI6:%i (%u)",
|
|
&pcc_state->pce_opts->addr.ipaddr_v6,
|
|
pcc_state->pce_opts->port, pcc_state->id);
|
|
} else {
|
|
snprintfrr(pcc_state->tag, sizeof(pcc_state->tag),
|
|
"%pI4:%i (%u)",
|
|
&pcc_state->pce_opts->addr.ipaddr_v4,
|
|
pcc_state->pce_opts->port, pcc_state->id);
|
|
}
|
|
} else {
|
|
snprintfrr(pcc_state->tag, sizeof(pcc_state->tag), "(%u)",
|
|
pcc_state->id);
|
|
}
|
|
}
|
|
|
|
void update_originator(struct pcc_state *pcc_state)
|
|
{
|
|
char *originator;
|
|
if (pcc_state->originator != NULL) {
|
|
XFREE(MTYPE_PCEP, pcc_state->originator);
|
|
pcc_state->originator = NULL;
|
|
}
|
|
if (pcc_state->pce_opts == NULL)
|
|
return;
|
|
originator = XCALLOC(MTYPE_PCEP, 52);
|
|
assert(!IS_IPADDR_NONE(&pcc_state->pce_opts->addr));
|
|
if (IS_IPADDR_V6(&pcc_state->pce_opts->addr)) {
|
|
snprintfrr(originator, 52, "%pI6:%i",
|
|
&pcc_state->pce_opts->addr.ipaddr_v6,
|
|
pcc_state->pce_opts->port);
|
|
} else {
|
|
snprintfrr(originator, 52, "%pI4:%i",
|
|
&pcc_state->pce_opts->addr.ipaddr_v4,
|
|
pcc_state->pce_opts->port);
|
|
}
|
|
pcc_state->originator = originator;
|
|
}
|
|
|
|
void schedule_reconnect(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state)
|
|
{
|
|
pcc_state->retry_count++;
|
|
pcep_thread_schedule_reconnect(ctrl_state, pcc_state->id,
|
|
pcc_state->retry_count,
|
|
&pcc_state->t_reconnect);
|
|
if (pcc_state->retry_count == 1) {
|
|
pcep_thread_schedule_sync_best_pce(
|
|
ctrl_state, pcc_state->id,
|
|
pcc_state->pce_opts->config_opts
|
|
.delegation_timeout_seconds,
|
|
&pcc_state->t_update_best);
|
|
}
|
|
}
|
|
|
|
void schedule_session_timeout(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state)
|
|
{
|
|
/* No need to schedule timeout if multiple PCEs are connected */
|
|
if (get_pce_count_connected(ctrl_state->pcc)) {
|
|
PCEP_DEBUG_PCEP(
|
|
"schedule_session_timeout not setting timer for multi-pce mode");
|
|
|
|
return;
|
|
}
|
|
|
|
pcep_thread_schedule_session_timeout(
|
|
ctrl_state, pcep_pcc_get_pcc_id(pcc_state),
|
|
pcc_state->pce_opts->config_opts
|
|
.session_timeout_inteval_seconds,
|
|
&pcc_state->t_session_timeout);
|
|
}
|
|
|
|
void cancel_session_timeout(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state)
|
|
{
|
|
/* No need to schedule timeout if multiple PCEs are connected */
|
|
if (pcc_state->t_session_timeout == NULL) {
|
|
PCEP_DEBUG_PCEP("cancel_session_timeout timer thread NULL");
|
|
return;
|
|
}
|
|
|
|
PCEP_DEBUG_PCEP("Cancel session_timeout timer");
|
|
pcep_thread_cancel_timer(&pcc_state->t_session_timeout);
|
|
pcc_state->t_session_timeout = NULL;
|
|
}
|
|
|
|
void send_pcep_message(struct pcc_state *pcc_state, struct pcep_message *msg)
|
|
{
|
|
if (pcc_state->sess != NULL) {
|
|
PCEP_DEBUG_PCEP("%s Sending PCEP message: %s", pcc_state->tag,
|
|
format_pcep_message(msg));
|
|
send_message(pcc_state->sess, msg, true);
|
|
}
|
|
}
|
|
|
|
void send_pcep_error(struct pcc_state *pcc_state,
|
|
enum pcep_error_type error_type,
|
|
enum pcep_error_value error_value)
|
|
{
|
|
struct pcep_message *msg;
|
|
PCEP_DEBUG("%s Sending PCEP error type %s (%d) value %s (%d)",
|
|
pcc_state->tag, pcep_error_type_name(error_type), error_type,
|
|
pcep_error_value_name(error_type, error_value), error_value);
|
|
msg = pcep_lib_format_error(error_type, error_value);
|
|
send_pcep_message(pcc_state, msg);
|
|
}
|
|
|
|
void send_report(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
struct pcep_message *report;
|
|
|
|
path->req_id = 0;
|
|
specialize_outgoing_path(pcc_state, path);
|
|
PCEP_DEBUG_PATH("%s Sending path %s: %s", pcc_state->tag, path->name,
|
|
format_path(path));
|
|
report = pcep_lib_format_report(&pcc_state->caps, path);
|
|
send_pcep_message(pcc_state, report);
|
|
}
|
|
|
|
/* Updates the path for the PCE, updating the delegation and creation flags */
|
|
void specialize_outgoing_path(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
bool is_delegated = false;
|
|
bool was_created = false;
|
|
|
|
lookup_plspid(pcc_state, path);
|
|
|
|
set_pcc_address(pcc_state, &path->nbkey, &path->pcc_addr);
|
|
path->sender = pcc_state->pcc_addr_tr;
|
|
|
|
/* TODO: When the pathd API have a way to mark a path as
|
|
* delegated, use it instead of considering all dynamic path
|
|
* delegated. We need to disable the originator check for now,
|
|
* because path could be delegated without having any originator yet */
|
|
// if ((path->originator == NULL)
|
|
// || (strcmp(path->originator, pcc_state->originator) == 0)) {
|
|
// is_delegated = (path->type == SRTE_CANDIDATE_TYPE_DYNAMIC)
|
|
// && (path->first_hop != NULL);
|
|
// /* it seems the PCE consider updating an LSP a creation ?!?
|
|
// at least Cisco does... */
|
|
// was_created = path->update_origin == SRTE_ORIGIN_PCEP;
|
|
// }
|
|
is_delegated = (path->type == SRTE_CANDIDATE_TYPE_DYNAMIC);
|
|
was_created = path->update_origin == SRTE_ORIGIN_PCEP;
|
|
|
|
path->pcc_id = pcc_state->id;
|
|
path->go_active = is_delegated && pcc_state->is_best;
|
|
path->is_delegated = is_delegated && pcc_state->is_best;
|
|
path->was_created = was_created;
|
|
}
|
|
|
|
/* Updates the path for the PCC */
|
|
void specialize_incoming_path(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
set_pcc_address(pcc_state, &path->nbkey, &path->pcc_addr);
|
|
path->sender = pcc_state->pce_opts->addr;
|
|
path->pcc_id = pcc_state->id;
|
|
path->update_origin = SRTE_ORIGIN_PCEP;
|
|
path->originator = XSTRDUP(MTYPE_PCEP, pcc_state->originator);
|
|
}
|
|
|
|
/* Ensure the path can be handled by the PCC and if not, sends an error */
|
|
bool validate_incoming_path(struct pcc_state *pcc_state, struct path *path,
|
|
char *errbuff, size_t buffsize)
|
|
{
|
|
struct path_hop *hop;
|
|
enum pcep_error_type err_type = 0;
|
|
enum pcep_error_value err_value = PCEP_ERRV_UNASSIGNED;
|
|
|
|
for (hop = path->first_hop; hop != NULL; hop = hop->next) {
|
|
/* Hops without SID are not supported */
|
|
if (!hop->has_sid) {
|
|
snprintfrr(errbuff, buffsize, "SR segment without SID");
|
|
err_type = PCEP_ERRT_RECEPTION_OF_INV_OBJECT;
|
|
err_value = PCEP_ERRV_DISJOINTED_CONF_TLV_MISSING;
|
|
break;
|
|
}
|
|
/* Hops with non-MPLS SID are not supported */
|
|
if (!hop->is_mpls) {
|
|
snprintfrr(errbuff, buffsize,
|
|
"SR segment with non-MPLS SID");
|
|
err_type = PCEP_ERRT_RECEPTION_OF_INV_OBJECT;
|
|
err_value = PCEP_ERRV_UNSUPPORTED_NAI;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (err_type != 0) {
|
|
send_pcep_error(pcc_state, err_type, err_value);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void send_comp_request(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state, struct req_entry *req)
|
|
{
|
|
assert(req != NULL);
|
|
|
|
if (req->t_retry)
|
|
return;
|
|
|
|
assert(req->path != NULL);
|
|
assert(req->path->req_id > 0);
|
|
assert(RB_FIND(req_entry_head, &pcc_state->requests, req) == req);
|
|
assert(lookup_reqid(pcc_state, req->path) == req->path->req_id);
|
|
|
|
int timeout;
|
|
char buff[40];
|
|
struct pcep_message *msg;
|
|
|
|
if (!pcc_state->is_best) {
|
|
return;
|
|
}
|
|
/* TODO: Add a timer to retry the computation request ? */
|
|
|
|
specialize_outgoing_path(pcc_state, req->path);
|
|
|
|
PCEP_DEBUG(
|
|
"%s Sending computation request %d for path %s to %s (retry %d)",
|
|
pcc_state->tag, req->path->req_id, req->path->name,
|
|
ipaddr2str(&req->path->nbkey.endpoint, buff, sizeof(buff)),
|
|
req->retry_count);
|
|
PCEP_DEBUG_PATH("%s Computation request path %s: %s", pcc_state->tag,
|
|
req->path->name, format_path(req->path));
|
|
|
|
msg = pcep_lib_format_request(&pcc_state->caps, req->path);
|
|
send_pcep_message(pcc_state, msg);
|
|
req->was_sent = true;
|
|
|
|
/* TODO: Enable this back when the pcep config changes are merged back
|
|
*/
|
|
// timeout = pcc_state->pce_opts->config_opts.pcep_request_time_seconds;
|
|
timeout = 30;
|
|
pcep_thread_schedule_timeout(ctrl_state, pcc_state->id,
|
|
TO_COMPUTATION_REQUEST, timeout,
|
|
(void *)req, &req->t_retry);
|
|
}
|
|
|
|
void cancel_comp_requests(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state)
|
|
{
|
|
struct req_entry *req, *safe_req;
|
|
|
|
RB_FOREACH_SAFE (req, req_entry_head, &pcc_state->requests, safe_req) {
|
|
cancel_comp_request(ctrl_state, pcc_state, req);
|
|
RB_REMOVE(req_entry_head, &pcc_state->requests, req);
|
|
remove_reqid_mapping(pcc_state, req->path);
|
|
free_req_entry(req);
|
|
}
|
|
}
|
|
|
|
void cancel_comp_request(struct ctrl_state *ctrl_state,
|
|
struct pcc_state *pcc_state, struct req_entry *req)
|
|
{
|
|
char buff[40];
|
|
struct pcep_message *msg;
|
|
|
|
if (req->was_sent) {
|
|
/* TODO: Send a computation request cancelation
|
|
* notification to the PCE */
|
|
pcep_thread_cancel_timer(&req->t_retry);
|
|
}
|
|
|
|
PCEP_DEBUG(
|
|
"%s Canceling computation request %d for path %s to %s (retry %d)",
|
|
pcc_state->tag, req->path->req_id, req->path->name,
|
|
ipaddr2str(&req->path->nbkey.endpoint, buff, sizeof(buff)),
|
|
req->retry_count);
|
|
PCEP_DEBUG_PATH("%s Canceled computation request path %s: %s",
|
|
pcc_state->tag, req->path->name,
|
|
format_path(req->path));
|
|
|
|
msg = pcep_lib_format_request_cancelled(req->path->req_id);
|
|
send_pcep_message(pcc_state, msg);
|
|
}
|
|
|
|
void set_pcc_address(struct pcc_state *pcc_state, struct lsp_nb_key *nbkey,
|
|
struct ipaddr *addr)
|
|
{
|
|
select_pcc_addresses(pcc_state);
|
|
if (IS_IPADDR_V6(&nbkey->endpoint)) {
|
|
assert(CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6));
|
|
addr->ipa_type = IPADDR_V6;
|
|
addr->ipaddr_v6 = pcc_state->pcc_addr_v6;
|
|
} else if (IS_IPADDR_V4(&nbkey->endpoint)) {
|
|
assert(CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4));
|
|
addr->ipa_type = IPADDR_V4;
|
|
addr->ipaddr_v4 = pcc_state->pcc_addr_v4;
|
|
} else {
|
|
addr->ipa_type = IPADDR_NONE;
|
|
}
|
|
}
|
|
|
|
|
|
/* ------------ Data Structure Helper Functions ------------ */
|
|
|
|
void lookup_plspid(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
struct plspid_map_data key, *plspid_mapping;
|
|
struct nbkey_map_data *nbkey_mapping;
|
|
|
|
if (path->nbkey.color != 0) {
|
|
key.nbkey = path->nbkey;
|
|
plspid_mapping = plspid_map_find(&pcc_state->plspid_map, &key);
|
|
if (plspid_mapping == NULL) {
|
|
plspid_mapping =
|
|
XCALLOC(MTYPE_PCEP, sizeof(*plspid_mapping));
|
|
plspid_mapping->nbkey = key.nbkey;
|
|
plspid_mapping->plspid = pcc_state->next_plspid;
|
|
plspid_map_add(&pcc_state->plspid_map, plspid_mapping);
|
|
nbkey_mapping =
|
|
XCALLOC(MTYPE_PCEP, sizeof(*nbkey_mapping));
|
|
nbkey_mapping->nbkey = key.nbkey;
|
|
nbkey_mapping->plspid = pcc_state->next_plspid;
|
|
nbkey_map_add(&pcc_state->nbkey_map, nbkey_mapping);
|
|
pcc_state->next_plspid++;
|
|
// FIXME: Send some error to the PCE isntead of crashing
|
|
assert(pcc_state->next_plspid <= 1048576);
|
|
}
|
|
path->plsp_id = plspid_mapping->plspid;
|
|
}
|
|
}
|
|
|
|
void lookup_nbkey(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
struct nbkey_map_data key, *mapping;
|
|
// TODO: Should give an error to the PCE instead of crashing
|
|
assert(path->plsp_id != 0);
|
|
key.plspid = path->plsp_id;
|
|
mapping = nbkey_map_find(&pcc_state->nbkey_map, &key);
|
|
assert(mapping != NULL);
|
|
path->nbkey = mapping->nbkey;
|
|
}
|
|
|
|
void free_req_entry(struct req_entry *req)
|
|
{
|
|
pcep_free_path(req->path);
|
|
XFREE(MTYPE_PCEP, req);
|
|
}
|
|
|
|
struct req_entry *push_new_req(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
struct req_entry *req;
|
|
|
|
req = XCALLOC(MTYPE_PCEP, sizeof(*req));
|
|
req->retry_count = 0;
|
|
req->path = pcep_copy_path(path);
|
|
repush_req(pcc_state, req);
|
|
|
|
return req;
|
|
}
|
|
|
|
void repush_req(struct pcc_state *pcc_state, struct req_entry *req)
|
|
{
|
|
uint32_t reqid = pcc_state->next_reqid;
|
|
void *res;
|
|
|
|
req->was_sent = false;
|
|
req->path->req_id = reqid;
|
|
res = RB_INSERT(req_entry_head, &pcc_state->requests, req);
|
|
assert(res == NULL);
|
|
assert(add_reqid_mapping(pcc_state, req->path) == true);
|
|
|
|
pcc_state->next_reqid += 1;
|
|
/* Wrapping is allowed, but 0 is not a valid id */
|
|
if (pcc_state->next_reqid == 0)
|
|
pcc_state->next_reqid = 1;
|
|
}
|
|
|
|
struct req_entry *pop_req(struct pcc_state *pcc_state, uint32_t reqid)
|
|
{
|
|
struct path path = {.req_id = reqid};
|
|
struct req_entry key = {.path = &path};
|
|
struct req_entry *req;
|
|
|
|
req = RB_FIND(req_entry_head, &pcc_state->requests, &key);
|
|
if (req == NULL)
|
|
return NULL;
|
|
RB_REMOVE(req_entry_head, &pcc_state->requests, req);
|
|
remove_reqid_mapping(pcc_state, req->path);
|
|
|
|
return req;
|
|
}
|
|
|
|
bool add_reqid_mapping(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
struct req_map_data *mapping;
|
|
mapping = XCALLOC(MTYPE_PCEP, sizeof(*mapping));
|
|
mapping->nbkey = path->nbkey;
|
|
mapping->reqid = path->req_id;
|
|
if (req_map_add(&pcc_state->req_map, mapping) != NULL) {
|
|
XFREE(MTYPE_PCEP, mapping);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void remove_reqid_mapping(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
struct req_map_data key, *mapping;
|
|
key.nbkey = path->nbkey;
|
|
mapping = req_map_find(&pcc_state->req_map, &key);
|
|
if (mapping != NULL) {
|
|
req_map_del(&pcc_state->req_map, mapping);
|
|
XFREE(MTYPE_PCEP, mapping);
|
|
}
|
|
}
|
|
|
|
uint32_t lookup_reqid(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
struct req_map_data key, *mapping;
|
|
key.nbkey = path->nbkey;
|
|
mapping = req_map_find(&pcc_state->req_map, &key);
|
|
if (mapping != NULL)
|
|
return mapping->reqid;
|
|
return 0;
|
|
}
|
|
|
|
bool has_pending_req_for(struct pcc_state *pcc_state, struct path *path)
|
|
{
|
|
return lookup_reqid(pcc_state, path) != 0;
|
|
}
|
|
|
|
|
|
/* ------------ Data Structure Callbacks ------------ */
|
|
|
|
#define CMP_RETURN(A, B) \
|
|
if (A != B) \
|
|
return (A < B) ? -1 : 1
|
|
|
|
static uint32_t hash_nbkey(const struct lsp_nb_key *nbkey)
|
|
{
|
|
uint32_t hash;
|
|
hash = jhash_2words(nbkey->color, nbkey->preference, 0x55aa5a5a);
|
|
switch (nbkey->endpoint.ipa_type) {
|
|
case IPADDR_V4:
|
|
return jhash(&nbkey->endpoint.ipaddr_v4,
|
|
sizeof(nbkey->endpoint.ipaddr_v4), hash);
|
|
case IPADDR_V6:
|
|
return jhash(&nbkey->endpoint.ipaddr_v6,
|
|
sizeof(nbkey->endpoint.ipaddr_v6), hash);
|
|
default:
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
static int cmp_nbkey(const struct lsp_nb_key *a, const struct lsp_nb_key *b)
|
|
{
|
|
CMP_RETURN(a->color, b->color);
|
|
int cmp = ipaddr_cmp(&a->endpoint, &b->endpoint);
|
|
if (cmp != 0)
|
|
return cmp;
|
|
CMP_RETURN(a->preference, b->preference);
|
|
return 0;
|
|
}
|
|
|
|
int plspid_map_cmp(const struct plspid_map_data *a,
|
|
const struct plspid_map_data *b)
|
|
{
|
|
return cmp_nbkey(&a->nbkey, &b->nbkey);
|
|
}
|
|
|
|
uint32_t plspid_map_hash(const struct plspid_map_data *e)
|
|
{
|
|
return hash_nbkey(&e->nbkey);
|
|
}
|
|
|
|
int nbkey_map_cmp(const struct nbkey_map_data *a,
|
|
const struct nbkey_map_data *b)
|
|
{
|
|
CMP_RETURN(a->plspid, b->plspid);
|
|
return 0;
|
|
}
|
|
|
|
uint32_t nbkey_map_hash(const struct nbkey_map_data *e)
|
|
{
|
|
return e->plspid;
|
|
}
|
|
|
|
int req_map_cmp(const struct req_map_data *a, const struct req_map_data *b)
|
|
{
|
|
return cmp_nbkey(&a->nbkey, &b->nbkey);
|
|
}
|
|
|
|
uint32_t req_map_hash(const struct req_map_data *e)
|
|
{
|
|
return hash_nbkey(&e->nbkey);
|
|
}
|