diff options
Diffstat (limited to 'pathd')
33 files changed, 16594 insertions, 0 deletions
diff --git a/pathd/.gitignore b/pathd/.gitignore new file mode 100644 index 0000000..95f4a99 --- /dev/null +++ b/pathd/.gitignore @@ -0,0 +1,2 @@ +libpath.a +pathd diff --git a/pathd/Makefile b/pathd/Makefile new file mode 100644 index 0000000..b681a9a --- /dev/null +++ b/pathd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. pathd/pathd +%: ALWAYS + @$(MAKE) -s -C .. pathd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/pathd/path_cli.c b/pathd/path_cli.c new file mode 100644 index 0000000..4775aa3 --- /dev/null +++ b/pathd/path_cli.c @@ -0,0 +1,1344 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include <float.h> +#include <math.h> +#include <zebra.h> + +#include "memory.h" +#include "log.h" +#include "command.h" +#include "mpls.h" +#include "northbound_cli.h" +#include "termtable.h" + +#include "pathd/pathd.h" +#include "pathd/path_nb.h" +#ifndef VTYSH_EXTRACT_PL +#include "pathd/path_cli_clippy.c" +#endif +#include "pathd/path_ted.h" + +#define XPATH_MAXATTRSIZE 64 +#define XPATH_MAXKEYSIZE 42 +#define XPATH_POLICY_BASELEN 100 +#define XPATH_POLICY_MAXLEN (XPATH_POLICY_BASELEN + XPATH_MAXATTRSIZE) +#define XPATH_CANDIDATE_BASELEN (XPATH_POLICY_BASELEN + XPATH_MAXKEYSIZE) +#define XPATH_CANDIDATE_MAXLEN (XPATH_CANDIDATE_BASELEN + XPATH_MAXATTRSIZE) + + +static int config_write_segment_routing(struct vty *vty); +static int segment_list_has_src_dst( + struct vty *vty, char *xpath, long index, const char *index_str, + struct in_addr adj_src_ipv4, struct in_addr adj_dst_ipv4, + struct in6_addr adj_src_ipv6, struct in6_addr adj_dst_ipv6, + const char *adj_src_ipv4_str, const char *adj_dst_ipv4_str, + const char *adj_src_ipv6_str, const char *adj_dst_ipv6_str); +static int segment_list_has_prefix( + struct vty *vty, char *xpath, long index, const char *index_str, + const struct prefix_ipv4 *prefix_ipv4, const char *prefix_ipv4_str, + const struct prefix_ipv6 *prefix_ipv6, const char *prefix_ipv6_str, + const char *has_algo, long algo, const char *algo_str, + const char *has_iface_id, long iface_id, const char *iface_id_str); + +DEFINE_MTYPE_STATIC(PATHD, PATH_CLI, "Client"); + +DEFINE_HOOK(pathd_srte_config_write, (struct vty *vty), (vty)); + +/* Vty node structures. */ +static struct cmd_node segment_routing_node = { + .name = "segment-routing", + .node = SEGMENT_ROUTING_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-sr)# ", + .config_write = config_write_segment_routing, +}; + +static struct cmd_node sr_traffic_eng_node = { + .name = "sr traffic-eng", + .node = SR_TRAFFIC_ENG_NODE, + .parent_node = SEGMENT_ROUTING_NODE, + .prompt = "%s(config-sr-te)# ", +}; + +static struct cmd_node srte_segment_list_node = { + .name = "srte segment-list", + .node = SR_SEGMENT_LIST_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-segment-list)# ", +}; + +static struct cmd_node srte_policy_node = { + .name = "srte policy", + .node = SR_POLICY_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-policy)# ", +}; + +static struct cmd_node srte_candidate_dyn_node = { + .name = "srte candidate-dyn", + .node = SR_CANDIDATE_DYN_NODE, + .parent_node = SR_POLICY_NODE, + .prompt = "%s(config-sr-te-candidate)# ", +}; + + +/* + * Show SR-TE info + */ +DEFPY(show_srte_policy, + show_srte_policy_cmd, + "show sr-te policy", + SHOW_STR + "SR-TE info\n" + "SR-TE Policy\n") +{ + struct ttable *tt; + struct srte_policy *policy; + char *table; + + if (RB_EMPTY(srte_policy_head, &srte_policies)) { + vty_out(vty, "No SR Policies to display.\n\n"); + return CMD_SUCCESS; + } + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Endpoint|Color|Name|BSID|Status"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + char endpoint[46]; + char binding_sid[16] = "-"; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + if (policy->binding_sid != MPLS_LABEL_NONE) + snprintf(binding_sid, sizeof(binding_sid), "%u", + policy->binding_sid); + + ttable_add_row(tt, "%s|%u|%s|%s|%s", endpoint, policy->color, + policy->name, binding_sid, + policy->status == SRTE_POLICY_STATUS_UP + ? "Active" + : "Inactive"); + } + + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + + ttable_del(tt); + + return CMD_SUCCESS; +} + + +/* + * Show detailed SR-TE info + */ +DEFPY(show_srte_policy_detail, + show_srte_policy_detail_cmd, + "show sr-te policy detail", + SHOW_STR + "SR-TE info\n" + "SR-TE Policy\n" + "Show a detailed summary\n") +{ + struct srte_policy *policy; + + if (RB_EMPTY(srte_policy_head, &srte_policies)) { + vty_out(vty, "No SR Policies to display.\n\n"); + return CMD_SUCCESS; + } + + vty_out(vty, "\n"); + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + struct srte_candidate *candidate; + char endpoint[46]; + char binding_sid[16] = "-"; + char *segment_list_info; + static char undefined_info[] = "(undefined)"; + static char created_by_pce_info[] = "(created by PCE)"; + + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + if (policy->binding_sid != MPLS_LABEL_NONE) + snprintf(binding_sid, sizeof(binding_sid), "%u", + policy->binding_sid); + vty_out(vty, + "Endpoint: %s Color: %u Name: %s BSID: %s Status: %s\n", + endpoint, policy->color, policy->name, binding_sid, + policy->status == SRTE_POLICY_STATUS_UP ? "Active" + : "Inactive"); + + RB_FOREACH (candidate, srte_candidate_head, + &policy->candidate_paths) { + struct srte_segment_list *segment_list; + + segment_list = candidate->lsp->segment_list; + if (segment_list == NULL) + segment_list_info = undefined_info; + else if (segment_list->protocol_origin + == SRTE_ORIGIN_PCEP) + segment_list_info = created_by_pce_info; + else + segment_list_info = + candidate->lsp->segment_list->name; + + vty_out(vty, + " %s Preference: %d Name: %s Type: %s Segment-List: %s Protocol-Origin: %s\n", + CHECK_FLAG(candidate->flags, F_CANDIDATE_BEST) + ? "*" + : " ", + candidate->preference, candidate->name, + candidate->type == SRTE_CANDIDATE_TYPE_EXPLICIT + ? "explicit" + : "dynamic", + segment_list_info, + srte_origin2str( + candidate->lsp->protocol_origin)); + } + + vty_out(vty, "\n"); + } + + return CMD_SUCCESS; +} + +DEFPY_NOSH( + segment_routing_list, + segment_routing_cmd, + "segment-routing", + "Configure segment routing\n") +{ + VTY_PUSH_CONTEXT_NULL(SEGMENT_ROUTING_NODE); + return CMD_SUCCESS; +} + +DEFPY_NOSH( + sr_traffic_eng_list, + sr_traffic_eng_cmd, + "traffic-eng", + "Configure SR traffic engineering\n") +{ + VTY_PUSH_CONTEXT_NULL(SR_TRAFFIC_ENG_NODE); + return CMD_SUCCESS; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list + */ +DEFPY_NOSH( + srte_segment_list, + srte_segment_list_cmd, + "segment-list WORD$name", + "Segment List\n" + "Segment List Name\n") +{ + char xpath[XPATH_MAXLEN]; + int ret; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']/protocol-origin", + name); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, "local"); + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']/originator", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, "config"); + + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) { + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']", name); + VTY_PUSH_XPATH(SR_SEGMENT_LIST_NODE, xpath); + } + + return ret; +} + +DEFPY(srte_no_segment_list, + srte_no_segment_list_cmd, + "no segment-list WORD$name", + NO_STR + "Segment List\n" + "Segment List Name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_segment_list(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " segment-list %s\n", + yang_dnode_get_string(dnode, "./name")); +} + +void cli_show_srte_segment_list_end(struct vty *vty, + const struct lyd_node *dnode) +{ + vty_out(vty, " exit\n"); +} + +static int segment_list_has_src_dst( + struct vty *vty, char *xpath, long index, const char *index_str, + struct in_addr adj_src_ipv4, struct in_addr adj_dst_ipv4, + struct in6_addr adj_src_ipv6, struct in6_addr adj_dst_ipv6, + const char *adj_src_ipv4_str, const char *adj_dst_ipv4_str, + const char *adj_src_ipv6_str, const char *adj_dst_ipv6_str) +{ + const char *node_src_id; + uint32_t ted_sid = MPLS_LABEL_NONE; + + struct ipaddr ip_src = {}; + struct ipaddr ip_dst = {}; + if (adj_src_ipv4_str != NULL) { + ip_src.ipa_type = IPADDR_V4; + ip_src.ip._v4_addr = adj_src_ipv4; + ip_dst.ipa_type = IPADDR_V4; + ip_dst.ip._v4_addr = adj_dst_ipv4; + } else if (adj_src_ipv6_str != NULL) { + ip_src.ipa_type = IPADDR_V6; + ip_src.ip._v6_addr = adj_src_ipv6; + ip_dst.ipa_type = IPADDR_V6; + ip_dst.ip._v6_addr = adj_dst_ipv6; + } else { + return CMD_ERR_NO_MATCH; + } + ted_sid = path_ted_query_type_f(&ip_src, &ip_dst); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_warn( + "%s: [rcv ted] CLI NOT FOUND Continue query_type_f SRC (%pIA) DST (%pIA)!", + __func__, &ip_src, &ip_dst); + } + /* type */ + snprintf(xpath, XPATH_MAXLEN, "./segment[index='%s']/nai/type", + index_str); + if (adj_src_ipv4_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv4_adjacency"); + node_src_id = adj_src_ipv4_str; + } else if (adj_src_ipv6_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv6_adjacency"); + node_src_id = adj_src_ipv6_str; + } else { + /* + * This is just to make the compiler happy about + * node_src_id not being initialized. This + * should never happen unless we change the cli + * function. + */ + assert(!"We must have a adj_src_ipv4_str or a adj_src_ipv6_str"); + } + + /* addresses */ + snprintf(xpath, XPATH_MAXLEN, "./segment[index='%s']/nai/local-address", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, node_src_id); + snprintf(xpath, XPATH_MAXLEN, + "./segment[index='%s']/nai/remote-address", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + adj_dst_ipv4_str ? adj_dst_ipv4_str + : adj_dst_ipv6_str); + return CMD_SUCCESS; +} +int segment_list_has_prefix( + struct vty *vty, char *xpath, long index, const char *index_str, + const struct prefix_ipv4 *prefix_ipv4, const char *prefix_ipv4_str, + const struct prefix_ipv6 *prefix_ipv6, const char *prefix_ipv6_str, + const char *has_algo, long algo, const char *algo_str, + const char *has_iface_id, long iface_id, const char *iface_id_str) +{ + char buf_prefix[INET6_ADDRSTRLEN]; + + uint32_t ted_sid = MPLS_LABEL_NONE; + struct prefix prefix_cli = {}; + struct ipaddr pre_ipaddr = {}; + /* prefix with algorithm or local interface id */ + /* Type */ + snprintf(xpath, XPATH_MAXLEN, "./segment[index='%s']/nai/type", + index_str); + if (has_iface_id != NULL) { + if (prefix_ipv4_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv4_local_iface"); + } else if (prefix_ipv6_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv6_local_iface"); + } else { + return CMD_ERR_NO_MATCH; + } + } else { + if (prefix_ipv4_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv4_algo"); + } else if (prefix_ipv6_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv6_algo"); + } else { + return CMD_ERR_NO_MATCH; + } + } + /* Prefix */ + if (prefix_ipv4_str != NULL) { + if (!str2prefix(prefix_ipv4_str, &prefix_cli)) { + vty_out(vty, "%% Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + inet_ntop(AF_INET, &prefix_cli.u.prefix4, buf_prefix, + sizeof(buf_prefix)); + pre_ipaddr.ipa_type = IPADDR_V4; + pre_ipaddr.ip._v4_addr = prefix_cli.u.prefix4; + } else if (prefix_ipv6_str != NULL) { + if (!str2prefix(prefix_ipv6_str, &prefix_cli)) { + vty_out(vty, "%% Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + inet_ntop(AF_INET6, &prefix_cli.u.prefix6, buf_prefix, + sizeof(buf_prefix)); + pre_ipaddr.ipa_type = IPADDR_V6; + pre_ipaddr.ip._v6_addr = prefix_cli.u.prefix6; + } + snprintf(xpath, XPATH_MAXLEN, "./segment[index='%s']/nai/local-address", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, buf_prefix); + snprintf(xpath, XPATH_MAXLEN, + "./segment[index='%s']/nai/local-prefix-len", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + prefix_ipv4_str + ? strchr(prefix_ipv4_str, '/') + 1 + : strchr(prefix_ipv6_str, '/') + 1); + /* Alg / Iface */ + if (has_algo != NULL) { + snprintf(xpath, XPATH_MAXLEN, + "./segment[index='%s']/nai/algorithm", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, algo_str); + } else { + if (has_iface_id != NULL) { + snprintf(xpath, XPATH_MAXLEN, + "./segment[index='%s']/nai/local-interface", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + iface_id_str); + } + } + if (has_algo != NULL) { + ted_sid = path_ted_query_type_c(&prefix_cli, algo); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_err( + "%s: [rcv ted] CLI NOT FOUND Continue query_type_c PREFIX (%pIA/%d) ALGO (%ld) sid:(%d)!", + __func__, &pre_ipaddr, prefix_cli.prefixlen, + algo, ted_sid); + } + } + if (has_iface_id != NULL) { + ted_sid = path_ted_query_type_e(&prefix_cli, iface_id); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_err( + "%s: [rcv ted] CLI NOT FOUND Continue query_type_e PREFIX (%pIA/%d) IFACE (%ld) sid:(%d)!", + __func__, &pre_ipaddr, prefix_cli.prefixlen, + iface_id, ted_sid); + } + } + return CMD_SUCCESS; +} +/* + * XPath: /frr-pathd:pathd/srte/segment-list/segment + */ +/* clang-format off */ +DEFPY(srte_segment_list_segment, srte_segment_list_segment_cmd, + "index (0-4294967295)$index <[mpls$has_mpls_label label (16-1048575)$label] " + "|" + "[nai$has_nai <" + "prefix <A.B.C.D/M$prefix_ipv4|X:X::X:X/M$prefix_ipv6>" + "<algorithm$has_algo (0-1)$algo| iface$has_iface_id (0-4294967295)$iface_id>" + "| adjacency$has_adj " + "<A.B.C.D$adj_src_ipv4 A.B.C.D$adj_dst_ipv4|X:X::X:X$adj_src_ipv6 X:X::X:X$adj_dst_ipv6>" + ">]" + ">", + "Index\n" + "Index Value\n" + "MPLS or IP Label\n" + "Label\n" + "Label Value\n" + "Segment NAI\n" + "NAI prefix identifier\n" + "NAI IPv4 prefix identifier\n" + "NAI IPv6 prefix identifier\n" + "IGP Algorithm\n" + "Algorithm Value SPF or Strict-SPF\n" + "Interface Id\n" + "Interface Value\n" + "ADJ identifier\n" + "ADJ IPv4 src identifier\n" + "ADJ IPv4 dst identifier\n" + "ADJ IPv6 src identifier\n" + "ADJ IPv6 dst identifier\n") +/* clang-format on */ +{ + char xpath[XPATH_MAXLEN]; + int status = CMD_SUCCESS; + + + snprintf(xpath, sizeof(xpath), "./segment[index='%s']", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + if (has_mpls_label != NULL) { + snprintf(xpath, sizeof(xpath), + "./segment[index='%s']/sid-value", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, label_str); + return nb_cli_apply_changes(vty, NULL); + } + + if (has_adj != NULL) { + status = segment_list_has_src_dst(vty, xpath, index, index_str, + adj_src_ipv4, adj_dst_ipv4, + adj_src_ipv6, adj_dst_ipv6, + adj_src_ipv4_str, adj_dst_ipv4_str, + adj_src_ipv6_str, adj_dst_ipv6_str); + if (status != CMD_SUCCESS) + return status; + } else { + status = segment_list_has_prefix( + vty, xpath, index, index_str, prefix_ipv4, + prefix_ipv4_str, prefix_ipv6, prefix_ipv6_str, has_algo, + algo, algo_str, has_iface_id, iface_id, iface_id_str); + if (status != CMD_SUCCESS) + return status; + } + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_segment_list_no_segment, + srte_segment_list_no_segment_cmd, + "no index (0-4294967295)$index", + NO_STR + "Index\n" + "Index Value\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./segment[index='%s']", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_segment_list_segment(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " index %s", yang_dnode_get_string(dnode, "./index")); + if (yang_dnode_exists(dnode, "./sid-value")) { + vty_out(vty, " mpls label %s", + yang_dnode_get_string(dnode, "./sid-value")); + } + if (yang_dnode_exists(dnode, "./nai")) { + struct ipaddr addr; + struct ipaddr addr_rmt; + + switch (yang_dnode_get_enum(dnode, "./nai/type")) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + yang_dnode_get_ip(&addr, dnode, "./nai/local-address"); + vty_out(vty, " nai prefix %pI4", &addr.ipaddr_v4); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + yang_dnode_get_ip(&addr, dnode, "./nai/local-address"); + vty_out(vty, " nai prefix %pI6", &addr.ipaddr_v6); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + yang_dnode_get_ip(&addr, dnode, "./nai/local-address"); + yang_dnode_get_ip(&addr_rmt, dnode, + "./nai/remote-address"); + vty_out(vty, " nai adjacency %pI4", &addr.ipaddr_v4); + vty_out(vty, " %pI4", &addr_rmt.ipaddr_v4); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + yang_dnode_get_ip(&addr, dnode, "./nai/local-address"); + yang_dnode_get_ip(&addr_rmt, dnode, + "./nai/remote-address"); + vty_out(vty, " nai adjacency %pI6", &addr.ipaddr_v6); + vty_out(vty, " %pI6", &addr_rmt.ipaddr_v6); + break; + default: + break; + } + if (yang_dnode_exists(dnode, "./nai/local-prefix-len")) { + vty_out(vty, "/%s", + yang_dnode_get_string( + dnode, "./nai/local-prefix-len")); + } + if (yang_dnode_exists(dnode, "./nai/local-interface")) { + vty_out(vty, " iface %s", + yang_dnode_get_string(dnode, + "./nai/local-interface")); + } + if (yang_dnode_exists(dnode, "./nai/algorithm")) { + vty_out(vty, " algorithm %s", + yang_dnode_get_string(dnode, + "./nai/algorithm")); + } + } + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-pathd:pathd/policy + */ +DEFPY_NOSH( + srte_policy, + srte_policy_cmd, + "policy color (0-4294967295)$num endpoint <A.B.C.D|X:X::X:X>$endpoint", + "Segment Routing Policy\n" + "SR Policy color\n" + "SR Policy color value\n" + "SR Policy endpoint\n" + "SR Policy endpoint IPv4 address\n" + "SR Policy endpoint IPv6 address\n") +{ + char xpath[XPATH_POLICY_BASELEN]; + int ret; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/policy[color='%s'][endpoint='%s']", + num_str, endpoint_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(SR_POLICY_NODE, xpath); + + return ret; +} + +DEFPY(srte_no_policy, + srte_no_policy_cmd, + "no policy color (0-4294967295)$num endpoint <A.B.C.D|X:X::X:X>$endpoint", + NO_STR + "Segment Routing Policy\n" + "SR Policy color\n" + "SR Policy color value\n" + "SR Policy endpoint\n" + "SR Policy endpoint IPv4 address\n" + "SR Policy endpoint IPv6 address\n") +{ + char xpath[XPATH_POLICY_BASELEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/policy[color='%s'][endpoint='%s']", + num_str, endpoint_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_policy(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " policy color %s endpoint %s\n", + yang_dnode_get_string(dnode, "./color"), + yang_dnode_get_string(dnode, "./endpoint")); +} + +void cli_show_srte_policy_end(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, " exit\n"); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/name + */ +DEFPY(srte_policy_name, + srte_policy_name_cmd, + "name WORD$name", + "Segment Routing Policy name\n" + "SR Policy name value\n") +{ + nb_cli_enqueue_change(vty, "./name", NB_OP_CREATE, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_policy_no_name, + srte_policy_no_name_cmd, + "no name [WORD]", + NO_STR + "Segment Routing Policy name\n" + "SR Policy name value\n") +{ + nb_cli_enqueue_change(vty, "./name", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + + +void cli_show_srte_policy_name(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " name %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/binding-sid + */ +DEFPY(srte_policy_binding_sid, + srte_policy_binding_sid_cmd, + "binding-sid (16-1048575)$label", + "Segment Routing Policy Binding-SID\n" + "SR Policy Binding-SID label\n") +{ + nb_cli_enqueue_change(vty, "./binding-sid", NB_OP_CREATE, label_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_policy_no_binding_sid, + srte_policy_no_binding_sid_cmd, + "no binding-sid [(16-1048575)]", + NO_STR + "Segment Routing Policy Binding-SID\n" + "SR Policy Binding-SID label\n") +{ + nb_cli_enqueue_change(vty, "./binding-sid", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_policy_binding_sid(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " binding-sid %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path + */ +DEFPY(srte_policy_candidate_exp, + srte_policy_candidate_exp_cmd, + "candidate-path preference (0-4294967295)$preference name WORD$name \ + explicit segment-list WORD$list_name", + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Explicit Path\n" + "List of SIDs\n" + "Name of the Segment List\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, preference_str); + nb_cli_enqueue_change(vty, "./name", NB_OP_MODIFY, name); + nb_cli_enqueue_change(vty, "./protocol-origin", NB_OP_MODIFY, "local"); + nb_cli_enqueue_change(vty, "./originator", NB_OP_MODIFY, "config"); + nb_cli_enqueue_change(vty, "./type", NB_OP_MODIFY, "explicit"); + nb_cli_enqueue_change(vty, "./segment-list-name", NB_OP_MODIFY, + list_name); + return nb_cli_apply_changes(vty, "./candidate-path[preference='%s']", + preference_str); +} + +DEFPY_NOSH( + srte_policy_candidate_dyn, + srte_policy_candidate_dyn_cmd, + "candidate-path preference (0-4294967295)$preference name WORD$name dynamic", + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Dynamic Path\n") +{ + char xpath[XPATH_MAXLEN + XPATH_CANDIDATE_BASELEN]; + int ret; + + snprintf(xpath, sizeof(xpath), "%s/candidate-path[preference='%s']", + VTY_CURR_XPATH, preference_str); + + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, preference_str); + nb_cli_enqueue_change(vty, "./name", NB_OP_MODIFY, name); + nb_cli_enqueue_change(vty, "./protocol-origin", NB_OP_MODIFY, "local"); + nb_cli_enqueue_change(vty, "./originator", NB_OP_MODIFY, "config"); + nb_cli_enqueue_change(vty, "./type", NB_OP_MODIFY, "dynamic"); + ret = nb_cli_apply_changes(vty, "./candidate-path[preference='%s']", + preference_str); + + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(SR_CANDIDATE_DYN_NODE, xpath); + + return ret; +} + +DEFPY(srte_candidate_bandwidth, + srte_candidate_bandwidth_cmd, + "bandwidth BANDWIDTH$value [required$required]", + "Define a bandwidth constraint\n" + "Bandwidth value\n" + "Required constraint\n") +{ + nb_cli_enqueue_change(vty, "./constraints/bandwidth/required", + NB_OP_MODIFY, required ? "true" : "false"); + nb_cli_enqueue_change(vty, "./constraints/bandwidth/value", + NB_OP_MODIFY, value); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_bandwidth, + srte_candidate_no_bandwidth_cmd, + "no bandwidth [BANDWIDTH$value] [required$required]", + NO_STR + "Remove a bandwidth constraint\n" + "Bandwidth value\n" + "Required constraint\n") +{ + nb_cli_enqueue_change(vty, "./constraints/bandwidth", NB_OP_DESTROY, + NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_affinity_filter, srte_candidate_affinity_filter_cmd, + "affinity <exclude-any|include-any|include-all>$type BITPATTERN$value", + "Affinity constraint\n" + "Exclude any matching link\n" + "Include any matching link\n" + "Include all matching links\n" + "Attribute filter bit pattern as an hexadecimal value from 0x00000000 to 0xFFFFFFFF\n") +{ + uint32_t filter; + char xpath[XPATH_CANDIDATE_MAXLEN]; + char decimal_value[11]; + + if (sscanf(value, "0x%x", &filter) != 1) { + vty_out(vty, "affinity type: fscanf: %s\n", + safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + snprintf(decimal_value, sizeof(decimal_value), "%u", filter); + snprintf(xpath, sizeof(xpath), "./constraints/affinity/%s", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, decimal_value); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_affinity_filter, srte_candidate_no_affinity_filter_cmd, + "no affinity <exclude-any|include-any|include-all>$type [BITPATTERN$value]", + NO_STR + "Affinity constraint\n" + "Exclude any matching link\n" + "Include any matching link\n" + "Include all matching links\n" + "Attribute filter bit pattern as an hexadecimal value from 0x00000000 to 0xFFFFFFFF\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./constraints/affinity/%s", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_metric, + srte_candidate_metric_cmd, + "metric [bound$bound] <igp|te|hc|abc|lmll|cigp|cte|pigp|pte|phc|msd|pd|pdv|pl|ppd|ppdv|ppl|nap|nlp|dc|bnc>$type METRIC$value [required$required]", + "Define a metric constraint\n" + "If the metric is bounded\n" + "IGP metric\n" + "TE metric\n" + "Hop Counts\n" + "Aggregate bandwidth consumption\n" + "Load of the most loaded link\n" + "Cumulative IGP cost\n" + "Cumulative TE cost\n" + "P2MP IGP metric\n" + "P2MP TE metric\n" + "P2MP hop count metric\n" + "Segment-ID (SID) Depth.\n" + "Path Delay metric\n" + "Path Delay Variation metric\n" + "Path Loss metric\n" + "P2MP Path Delay metric\n" + "P2MP Path Delay variation metric\n" + "P2MP Path Loss metric\n" + "Number of adaptations on a path\n" + "Number of layers on a path\n" + "Domain Count metric\n" + "Border Node Count metric\n" + "Metric value\n" + "Required constraint\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + snprintf(xpath, sizeof(xpath), "./constraints/metrics[type='%s']/value", + type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, value); + snprintf(xpath, sizeof(xpath), + "./constraints/metrics[type='%s']/is-bound", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + (bound != NULL) ? "true" : "false"); + snprintf(xpath, sizeof(xpath), + "./constraints/metrics[type='%s']/required", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + required ? "true" : "false"); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_metric, + srte_candidate_no_metric_cmd, + "no metric [bound] <igp|te|hc|abc|lmll|cigp|cte|pigp|pte|phc|msd|pd|pdv|pl|ppd|ppdv|ppl|nap|nlp|dc|bnc>$type [METRIC$value] [required$required]", + NO_STR + "Remove a metric constraint\n" + "If the metric is bounded\n" + "IGP metric\n" + "TE metric\n" + "Hop Counts\n" + "Aggregate bandwidth consumption\n" + "Load of the most loaded link\n" + "Cumulative IGP cost\n" + "Cumulative TE cost\n" + "P2MP IGP metric\n" + "P2MP TE metric\n" + "P2MP hop count metric\n" + "Segment-ID (SID) Depth.\n" + "Path Delay metric\n" + "Path Delay Variation metric\n" + "Path Loss metric\n" + "P2MP Path Delay metric\n" + "P2MP Path Delay variation metric\n" + "P2MP Path Loss metric\n" + "Number of adaptations on a path\n" + "Number of layers on a path\n" + "Domain Count metric\n" + "Border Node Count metric\n" + "Metric value\n" + "Required constraint\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + snprintf(xpath, sizeof(xpath), "./constraints/metrics[type='%s']", + type); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_policy_no_candidate, + srte_policy_no_candidate_cmd, + "no candidate-path\ + preference (0-4294967295)$preference\ + [name WORD\ + <\ + explicit segment-list WORD\ + |dynamic\ + >]", + NO_STR + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Explicit Path\n" + "List of SIDs\n" + "Name of the Segment List\n" + "Dynamic Path\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, "./candidate-path[preference='%s']", + preference_str); +} + +DEFPY(srte_candidate_objfun, + srte_candidate_objfun_cmd, + "objective-function <mcp|mlp|mbp|mbc|mll|mcc|spt|mct|mplp|mup|mrup|mtd|mbn|mctd|msl|mss|msn>$type [required$required]", + "Define an objective function constraint\n" + "Minimum Cost Path\n" + "Minimum Load Path\n" + "Maximum residual Bandwidth Path\n" + "Minimize aggregate Bandwidth Consumption\n" + "Minimize the Load of the most loaded Link\n" + "Minimize the Cumulative Cost of a set of paths\n" + "Shortest Path Tree\n" + "Minimum Cost Tree\n" + "Minimum Packet Loss Path\n" + "Maximum Under-Utilized Path\n" + "Maximum Reserved Under-Utilized Path\n" + "Minimize the number of Transit Domains\n" + "Minimize the number of Border Nodes\n" + "Minimize the number of Common Transit Domains\n" + "Minimize the number of Shared Links\n" + "Minimize the number of Shared SRLGs\n" + "Minimize the number of Shared Nodes\n" + "Required constraint\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + nb_cli_enqueue_change(vty, "./constraints/objective-function", + NB_OP_DESTROY, NULL); + snprintf(xpath, sizeof(xpath), + "./constraints/objective-function/required"); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + required ? "true" : "false"); + nb_cli_enqueue_change(vty, "./constraints/objective-function/type", + NB_OP_MODIFY, type); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_objfun, + srte_candidate_no_objfun_cmd, + "no objective-function [<mcp|mlp|mbp|mbc|mll|mcc|spt|mct|mplp|mup|mrup|mtd|mbn|mctd|msl|mss|msn>] [required$required]", + NO_STR + "Remove an objective function constraint\n" + "Minimum Cost Path\n" + "Minimum Load Path\n" + "Maximum residual Bandwidth Path\n" + "Minimize aggregate Bandwidth Consumption\n" + "Minimize the Load of the most loaded Link\n" + "Minimize the Cumulative Cost of a set of paths\n" + "Shortest Path Tree\n" + "Minimum Cost Tree\n" + "Minimum Packet Loss Path\n" + "Maximum Under-Utilized Path\n" + "Maximum Reserved Under-Utilized Path\n" + "Minimize the number of Transit Domains\n" + "Minimize the number of Border Nodes\n" + "Minimize the number of Common Transit Domains\n" + "Minimize the number of Shared Links\n" + "Minimize the number of Shared SRLGs\n" + "Minimize the number of Shared Nodes\n" + "Required constraint\n") +{ + nb_cli_enqueue_change(vty, "./constraints/objective-function", + NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +static const char *objfun_type_name(enum objfun_type type) +{ + switch (type) { + case OBJFUN_MCP: + return "mcp"; + case OBJFUN_MLP: + return "mlp"; + case OBJFUN_MBP: + return "mbp"; + case OBJFUN_MBC: + return "mbc"; + case OBJFUN_MLL: + return "mll"; + case OBJFUN_MCC: + return "mcc"; + case OBJFUN_SPT: + return "spt"; + case OBJFUN_MCT: + return "mct"; + case OBJFUN_MPLP: + return "mplp"; + case OBJFUN_MUP: + return "mup"; + case OBJFUN_MRUP: + return "mrup"; + case OBJFUN_MTD: + return "mtd"; + case OBJFUN_MBN: + return "mbn"; + case OBJFUN_MCTD: + return "mctd"; + case OBJFUN_MSL: + return "msl"; + case OBJFUN_MSS: + return "mss"; + case OBJFUN_MSN: + return "msn"; + default: + return NULL; + } +} + +DEFPY_NOSH(show_debugging_pathd, show_debugging_pathd_cmd, + "show debugging [pathd]", + SHOW_STR + "State of each debugging option\n" + "pathd module debugging\n") +{ + /* nothing to do here */ + return CMD_SUCCESS; +} + +static const char *metric_type_name(enum srte_candidate_metric_type type) +{ + switch (type) { + case SRTE_CANDIDATE_METRIC_TYPE_IGP: + return "igp"; + case SRTE_CANDIDATE_METRIC_TYPE_TE: + return "te"; + case SRTE_CANDIDATE_METRIC_TYPE_HC: + return "hc"; + case SRTE_CANDIDATE_METRIC_TYPE_ABC: + return "abc"; + case SRTE_CANDIDATE_METRIC_TYPE_LMLL: + return "lmll"; + case SRTE_CANDIDATE_METRIC_TYPE_CIGP: + return "cigp"; + case SRTE_CANDIDATE_METRIC_TYPE_CTE: + return "cte"; + case SRTE_CANDIDATE_METRIC_TYPE_PIGP: + return "pigp"; + case SRTE_CANDIDATE_METRIC_TYPE_PTE: + return "pte"; + case SRTE_CANDIDATE_METRIC_TYPE_PHC: + return "phc"; + case SRTE_CANDIDATE_METRIC_TYPE_MSD: + return "msd"; + case SRTE_CANDIDATE_METRIC_TYPE_PD: + return "pd"; + case SRTE_CANDIDATE_METRIC_TYPE_PDV: + return "pdv"; + case SRTE_CANDIDATE_METRIC_TYPE_PL: + return "pl"; + case SRTE_CANDIDATE_METRIC_TYPE_PPD: + return "ppd"; + case SRTE_CANDIDATE_METRIC_TYPE_PPDV: + return "ppdv"; + case SRTE_CANDIDATE_METRIC_TYPE_PPL: + return "ppl"; + case SRTE_CANDIDATE_METRIC_TYPE_NAP: + return "nap"; + case SRTE_CANDIDATE_METRIC_TYPE_NLP: + return "nlp"; + case SRTE_CANDIDATE_METRIC_TYPE_DC: + return "dc"; + case SRTE_CANDIDATE_METRIC_TYPE_BNC: + return "bnc"; + default: + return NULL; + } +} + +static void config_write_float(struct vty *vty, float value) +{ + if (fabs(truncf(value) - value) < FLT_EPSILON) { + vty_out(vty, " %d", (int)value); + return; + } else { + vty_out(vty, " %f", value); + } +} + +static void config_write_metric(struct vty *vty, + enum srte_candidate_metric_type type, + float value, bool required, bool is_bound) +{ + const char *name = metric_type_name(type); + if (name == NULL) + return; + vty_out(vty, " metric %s%s", is_bound ? "bound " : "", + metric_type_name(type)); + config_write_float(vty, value); + vty_out(vty, required ? " required" : ""); + vty_out(vty, "\n"); +} + +static int config_write_metric_cb(const struct lyd_node *dnode, void *arg) +{ + struct vty *vty = arg; + enum srte_candidate_metric_type type; + bool required, is_bound = false; + float value; + + type = yang_dnode_get_enum(dnode, "./type"); + value = (float)yang_dnode_get_dec64(dnode, "./value"); + required = yang_dnode_get_bool(dnode, "./required"); + if (yang_dnode_exists(dnode, "./is-bound")) + is_bound = yang_dnode_get_bool(dnode, "./is-bound"); + + config_write_metric(vty, type, value, required, is_bound); + return YANG_ITER_CONTINUE; +} + +void cli_show_srte_policy_candidate_path(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + float bandwidth; + uint32_t affinity; + bool required; + enum objfun_type objfun_type; + const char *type = yang_dnode_get_string(dnode, "./type"); + + vty_out(vty, " candidate-path preference %s name %s %s", + yang_dnode_get_string(dnode, "./preference"), + yang_dnode_get_string(dnode, "./name"), type); + if (strmatch(type, "explicit")) + vty_out(vty, " segment-list %s", + yang_dnode_get_string(dnode, "./segment-list-name")); + vty_out(vty, "\n"); + + if (strmatch(type, "dynamic")) { + if (yang_dnode_exists(dnode, "./constraints/bandwidth")) { + bandwidth = (float)yang_dnode_get_dec64( + dnode, "./constraints/bandwidth/value"); + required = yang_dnode_get_bool( + dnode, "./constraints/bandwidth/required"); + vty_out(vty, " bandwidth"); + config_write_float(vty, bandwidth); + if (required) + vty_out(vty, " required"); + vty_out(vty, "\n"); + } + if (yang_dnode_exists(dnode, + "./constraints/affinity/exclude-any")) { + affinity = yang_dnode_get_uint32( + dnode, "./constraints/affinity/exclude-any"); + vty_out(vty, " affinity exclude-any 0x%08x\n", + affinity); + } + if (yang_dnode_exists(dnode, + "./constraints/affinity/include-any")) { + affinity = yang_dnode_get_uint32( + dnode, "./constraints/affinity/include-any"); + vty_out(vty, " affinity include-any 0x%08x\n", + affinity); + } + if (yang_dnode_exists(dnode, + "./constraints/affinity/include-all")) { + affinity = yang_dnode_get_uint32( + dnode, "./constraints/affinity/include-all"); + vty_out(vty, " affinity include-all 0x%08x\n", + affinity); + } + yang_dnode_iterate(config_write_metric_cb, vty, dnode, + "./constraints/metrics"); + if (yang_dnode_exists(dnode, + "./constraints/objective-function")) { + objfun_type = yang_dnode_get_enum(dnode, + "./constraints/objective-function/type"); + required = yang_dnode_get_bool(dnode, + "./constraints/objective-function/required"); + vty_out(vty, " objective-function %s%s\n", + objfun_type_name(objfun_type), + required ? " required" : ""); + } + } +} + +void cli_show_srte_policy_candidate_path_end(struct vty *vty, + const struct lyd_node *dnode) +{ + const char *type = yang_dnode_get_string(dnode, "./type"); + + if (strmatch(type, "dynamic")) + vty_out(vty, " exit\n"); +} + +static int config_write_dnode(const struct lyd_node *dnode, void *arg) +{ + struct vty *vty = arg; + + nb_cli_show_dnode_cmds(vty, dnode, false); + + return YANG_ITER_CONTINUE; +} + +int config_write_segment_routing(struct vty *vty) +{ + vty_out(vty, "segment-routing\n"); + vty_out(vty, " traffic-eng\n"); + + path_ted_config_write(vty); + + yang_dnode_iterate(config_write_dnode, vty, running_config->dnode, + "/frr-pathd:pathd/srte/segment-list"); + yang_dnode_iterate(config_write_dnode, vty, running_config->dnode, + "/frr-pathd:pathd/srte/policy"); + + hook_call(pathd_srte_config_write, vty); + + vty_out(vty, " exit\n"); + vty_out(vty, "exit\n"); + + return 1; +} + +void path_cli_init(void) +{ + install_node(&segment_routing_node); + install_node(&sr_traffic_eng_node); + install_node(&srte_segment_list_node); + install_node(&srte_policy_node); + install_node(&srte_candidate_dyn_node); + install_default(SEGMENT_ROUTING_NODE); + install_default(SR_TRAFFIC_ENG_NODE); + install_default(SR_SEGMENT_LIST_NODE); + install_default(SR_POLICY_NODE); + install_default(SR_CANDIDATE_DYN_NODE); + + install_element(ENABLE_NODE, &show_debugging_pathd_cmd); + install_element(ENABLE_NODE, &show_srte_policy_cmd); + install_element(ENABLE_NODE, &show_srte_policy_detail_cmd); + + install_element(CONFIG_NODE, &segment_routing_cmd); + install_element(SEGMENT_ROUTING_NODE, &sr_traffic_eng_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_segment_list_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_no_segment_list_cmd); + install_element(SR_SEGMENT_LIST_NODE, + &srte_segment_list_segment_cmd); + install_element(SR_SEGMENT_LIST_NODE, + &srte_segment_list_no_segment_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_policy_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_no_policy_cmd); + install_element(SR_POLICY_NODE, &srte_policy_name_cmd); + install_element(SR_POLICY_NODE, &srte_policy_no_name_cmd); + install_element(SR_POLICY_NODE, &srte_policy_binding_sid_cmd); + install_element(SR_POLICY_NODE, &srte_policy_no_binding_sid_cmd); + install_element(SR_POLICY_NODE, &srte_policy_candidate_exp_cmd); + install_element(SR_POLICY_NODE, &srte_policy_candidate_dyn_cmd); + install_element(SR_POLICY_NODE, &srte_policy_no_candidate_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_bandwidth_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_bandwidth_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_affinity_filter_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_affinity_filter_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_metric_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_metric_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_objfun_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_objfun_cmd); +} diff --git a/pathd/path_debug.c b/pathd/path_debug.c new file mode 100644 index 0000000..eec5707 --- /dev/null +++ b/pathd/path_debug.c @@ -0,0 +1,123 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include <string.h> +#include <stdbool.h> +#include <time.h> +#include <libyang/libyang.h> + +#include "printfrr.h" +#include "ipaddr.h" + +#include "pathd/path_debug.h" + +THREAD_DATA char _debug_buff[DEBUG_BUFF_SIZE]; + +/** + * Gives the string representation of an srte_protocol_origin enum value. + * + * @param origin The enum value to convert to string + * @return a constant string representation of the enum value + */ +const char *srte_protocol_origin_name(enum srte_protocol_origin origin) +{ + switch (origin) { + case SRTE_ORIGIN_UNDEFINED: + return "UNDEFINED"; + case SRTE_ORIGIN_PCEP: + return "PCEP"; + case SRTE_ORIGIN_BGP: + return "BGP"; + case SRTE_ORIGIN_LOCAL: + return "LOCAL"; + default: + return "UNKNOWN"; + } +} + +/** + * Gives the string representation of an srte_candidate_type enum value. + * + * @param origin The enum value to convert to string + * @return a constant string representation of the enum value + */ +const char *srte_candidate_type_name(enum srte_candidate_type type) +{ + switch (type) { + case SRTE_CANDIDATE_TYPE_EXPLICIT: + return "EXPLICIT"; + case SRTE_CANDIDATE_TYPE_DYNAMIC: + return "DYNAMIC"; + case SRTE_CANDIDATE_TYPE_UNDEFINED: + return "UNDEFINED"; + default: + return "UNKNOWN"; + } +} + +/** + * Gives the string representation of an objfun_type enum value. + * + * @param origin The enum value to convert to string + * @return a constant string representation of the enum value + */ +const char *objfun_type_name(enum objfun_type type) +{ + switch (type) { + case OBJFUN_UNDEFINED: + return "UNDEFINED"; + case OBJFUN_MCP: + return "MCP"; + case OBJFUN_MLP: + return "MLP"; + case OBJFUN_MBP: + return "MBP"; + case OBJFUN_MBC: + return "MBC"; + case OBJFUN_MLL: + return "MLL"; + case OBJFUN_MCC: + return "MCC"; + case OBJFUN_SPT: + return "SPT"; + case OBJFUN_MCT: + return "MCT"; + case OBJFUN_MPLP: + return "MPLP"; + case OBJFUN_MUP: + return "MUP"; + case OBJFUN_MRUP: + return "MRUP"; + case OBJFUN_MTD: + return "MTD"; + case OBJFUN_MBN: + return "MBN"; + case OBJFUN_MCTD: + return "MCTD"; + case OBJFUN_MSL: + return "MSL"; + case OBJFUN_MSS: + return "MSS"; + case OBJFUN_MSN: + return "MSN"; + default: + return "UNKNOWN"; + } +} diff --git a/pathd/path_debug.h b/pathd/path_debug.h new file mode 100644 index 0000000..d9cfcb6 --- /dev/null +++ b/pathd/path_debug.h @@ -0,0 +1,44 @@ +/* + * 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 + */ + +#ifndef _PATH_DEBUG_H_ +#define _PATH_DEBUG_H_ + +#include "pathd/pathd.h" + +#ifdef __GNUC__ +#define THREAD_DATA __thread +#else +#define THREAD_DATA +#endif + +#define DEBUG_IDENT_SIZE 4 +#define DEBUG_BUFF_SIZE 4096 +#define TUP(A, B) ((((uint32_t)(A)) << 16) | ((uint32_t)(B))) +#define PATHD_FORMAT_INIT() _debug_buff[0] = 0 +#define PATHD_FORMAT(fmt, ...) \ + csnprintfrr(_debug_buff, DEBUG_BUFF_SIZE, fmt, ##__VA_ARGS__) +#define PATHD_FORMAT_FINI() _debug_buff + +extern THREAD_DATA char _debug_buff[DEBUG_BUFF_SIZE]; + +const char *srte_protocol_origin_name(enum srte_protocol_origin origin); +const char *srte_candidate_type_name(enum srte_candidate_type type); +const char *objfun_type_name(enum objfun_type type); + +#endif // _PATH_DEBUG_H_
\ No newline at end of file diff --git a/pathd/path_errors.c b/pathd/path_errors.c new file mode 100644 index 0000000..68372c2 --- /dev/null +++ b/pathd/path_errors.c @@ -0,0 +1,140 @@ +/* + * pathd-specific error messages. + * 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 + */ + +#include <zebra.h> + +#include "lib/ferr.h" +#include "path_errors.h" + +/* clang-format off */ +static struct log_ref ferr_path_err[] = { + { + .code = EC_PATH_SYSTEM_CALL, + .title = "Thread setup error", + .description = "A system call for creating, or setting up PCEP module's pthread failed", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_PATH_PCEP_PCC_INIT, + .title = "PCC initialization error", + .description = "pceplib PCC initialization call failed", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_PATH_PCEP_PCC_FINI, + .title = "PCC finalization error", + .description = "pceplib PCC finalization call failed", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_PATH_PCEP_PCC_CONF_UPDATE, + .title = "PCC configuration update error", + .description = "The update of the PCC configuration failed", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = END_FERR, + } +}; + +static struct log_ref ferr_path_warn[] = { + { + .code = EC_PATH_PCEP_LIB_CONNECT, + .title = "PCC connection error", + .description = "The PCEP module failed to connected to configured PCE", + .suggestion = "Check the connectivity between the PCC and the PCE" + }, + { + .code = EC_PATH_PCEP_PROTOCOL_ERROR, + .title = "PCEP protocol error", + .description = "The PCE did not respect the PCEP protocol", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + .title = "PCC connection error", + .description = "The PCEP module did not try to connect because it is missing a source address", + .suggestion = "Wait for the router ID to be defined or set the PCC source address in the configuration" + }, + { + .code = EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + .title = "Recoverable internal error", + .description = "Some recoverable internal error", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + .title = "Unsupported PCEP feature", + .description = "Received an unsupported PCEP message", + .suggestion = "The PCC and PCE are probably not compatible. Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEP_MESSAGE, + .title = "Unexpected PCEP message", + .description = "The PCEP module received an unexpected PCEP message", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEPLIB_EVENT, + .title = "Unexpected pceplib event", + .description = "The PCEP module received an unexpected event from pceplib", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEP_OBJECT, + .title = "Unexpected PCEP object", + .description = "The PCEP module received an unexpected PCEP object from a PCE", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + .title = "Unexpected PCEP TLV", + .description = "The PCEP module received an unexpected PCEP TLV from a PCE", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEP_ERO_SUBOBJ, + .title = "Unexpected PCEP ERO sub-object", + .description = "The PCEP module received an unexpected PCEP ERO sub-object from a PCE", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_SR_NAI, + .title = "Unexpected PCEP SR segment NAI", + .description = "The PCEP module received an SR segment with an unsupported NAI specification from the PCE", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_COMPUTATION_REQUEST_TIMEOUT, + .title = "Computation request timeout", + .description = "The PCE did not respond in time to the PCC computation request", + .suggestion = "The PCE is overloaded or incompatible with the PCC, try with a different PCE" + }, + { + .code = END_FERR, + } + +}; +/* clang-format on */ + +void path_error_init(void) +{ + log_ref_add(ferr_path_err); + log_ref_add(ferr_path_warn); +} diff --git a/pathd/path_errors.h b/pathd/path_errors.h new file mode 100644 index 0000000..5d05b66 --- /dev/null +++ b/pathd/path_errors.h @@ -0,0 +1,46 @@ +/* + * 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 + */ + +#ifndef __PATH_ERRORS_H__ +#define __PATH_ERRORS_H__ + +#include "lib/ferr.h" + +enum path_log_refs { + EC_PATH_PCEP_INIT = PATH_FERR_START, + EC_PATH_SYSTEM_CALL, + EC_PATH_PCEP_PCC_INIT, + EC_PATH_PCEP_PCC_FINI, + EC_PATH_PCEP_PCC_CONF_UPDATE, + EC_PATH_PCEP_LIB_CONNECT, + EC_PATH_PCEP_PROTOCOL_ERROR, + EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + EC_PATH_PCEP_UNEXPECTED_PCEP_MESSAGE, + EC_PATH_PCEP_UNEXPECTED_PCEPLIB_EVENT, + EC_PATH_PCEP_UNEXPECTED_PCEP_OBJECT, + EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + EC_PATH_PCEP_UNEXPECTED_PCEP_ERO_SUBOBJ, + EC_PATH_PCEP_UNEXPECTED_SR_NAI, + EC_PATH_PCEP_COMPUTATION_REQUEST_TIMEOUT +}; + +extern void path_error_init(void); + +#endif diff --git a/pathd/path_main.c b/pathd/path_main.c new file mode 100644 index 0000000..0a71c80 --- /dev/null +++ b/pathd/path_main.c @@ -0,0 +1,158 @@ +/* + * 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 + */ +#include <zebra.h> + +#include <lib/version.h> +#include "getopt.h" +#include "thread.h" +#include "command.h" +#include "log.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "libfrr.h" +#include "vrf.h" +#include "filter.h" + +#include "pathd.h" +#include "path_nb.h" +#include "path_zebra.h" +#include "path_errors.h" +#include "path_ted.h" + +char backup_config_file[256]; + +zebra_capabilities_t _caps_p[] = {}; + +struct zebra_privs_t pathd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +struct option longopts[] = {{0}}; + +/* Master of threads. */ +struct thread_master *master; + +static struct frr_daemon_info pathd_di; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); + + /* Reload config file. */ + vty_read_config(NULL, pathd_di.config_file, config_default); +} + +/* SIGINT / SIGTERM handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + zlog_notice("Unregisterfrom opaque,etc "); + pathd_shutdown(); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t path_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *pathd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_pathd_info, +}; + +#define PATH_VTY_PORT 2621 + +FRR_DAEMON_INFO(pathd, PATH, .vty_port = PATH_VTY_PORT, + + .proghelp = "Implementation of PATH.", + + .signals = path_signals, .n_signals = array_size(path_signals), + + .privs = &pathd_privs, .yang_modules = pathd_yang_modules, + .n_yang_modules = array_size(pathd_yang_modules), +); + +int main(int argc, char **argv, char **envp) +{ + frr_preinit(&pathd_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + master = frr_init(); + + access_list_init(); + + path_error_init(); + path_zebra_init(master); + path_cli_init(); + path_ted_init(master); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/pathd/path_nb.c b/pathd/path_nb.c new file mode 100644 index 0000000..1ab8b7f --- /dev/null +++ b/pathd/path_nb.c @@ -0,0 +1,363 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include "northbound.h" +#include "libfrr.h" + +#include "pathd/path_nb.h" + +static int iter_objfun_cb(const struct lyd_node *dnode, void *arg); +static int dummy_create(struct nb_cb_create_args *args); +static int dummy_modify(struct nb_cb_modify_args *args); +static int dummy_destroy(struct nb_cb_destroy_args *args); + +struct of_cb_pref { + uint32_t index; + enum objfun_type type; + struct of_cb_pref *next; +}; + +struct of_cb_args { + struct of_cb_pref *first; + uint32_t free_slot; + struct of_cb_pref prefs[MAX_OBJFUN_TYPE]; +}; + +/* clang-format off */ +const struct frr_yang_module_info frr_pathd_info = { + .name = "frr-pathd", + .nodes = { + { + .xpath = "/frr-pathd:pathd", + .cbs = { + .apply_finish = pathd_apply_finish, + }, + .priority = NB_DFLT_PRIORITY + 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list", + .cbs = { + .create = pathd_srte_segment_list_create, + .cli_show = cli_show_srte_segment_list, + .cli_show_end = cli_show_srte_segment_list_end, + .destroy = pathd_srte_segment_list_destroy, + .get_next = pathd_srte_segment_list_get_next, + .get_keys = pathd_srte_segment_list_get_keys, + .lookup_entry = pathd_srte_segment_list_lookup_entry, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/protocol-origin", + .cbs = { + .modify = pathd_srte_segment_list_protocol_origin_modify, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/originator", + .cbs = { + .modify = pathd_srte_segment_list_originator_modify, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment", + .cbs = { + .create = pathd_srte_segment_list_segment_create, + .cli_show = cli_show_srte_segment_list_segment, + .destroy = pathd_srte_segment_list_segment_destroy, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/sid-value", + .cbs = { + .modify = pathd_srte_segment_list_segment_sid_value_modify, + .destroy = pathd_srte_segment_list_segment_sid_value_destroy, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_segment_list_segment_nai_destroy, + .apply_finish = pathd_srte_segment_list_segment_nai_apply_finish + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/type", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/local-address", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/local-interface", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/local-prefix-len", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/remote-address", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/remote-interface", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/algorithm", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy", + .cbs = { + .create = pathd_srte_policy_create, + .cli_show = cli_show_srte_policy, + .cli_show_end = cli_show_srte_policy_end, + .destroy = pathd_srte_policy_destroy, + .get_next = pathd_srte_policy_get_next, + .get_keys = pathd_srte_policy_get_keys, + .lookup_entry = pathd_srte_policy_lookup_entry, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/name", + .cbs = { + .modify = pathd_srte_policy_name_modify, + .cli_show = cli_show_srte_policy_name, + .destroy = pathd_srte_policy_name_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/binding-sid", + .cbs = { + .modify = pathd_srte_policy_binding_sid_modify, + .cli_show = cli_show_srte_policy_binding_sid, + .destroy = pathd_srte_policy_binding_sid_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/is-operational", + .cbs = { + .get_elem = pathd_srte_policy_is_operational_get_elem + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path", + .cbs = { + .create = pathd_srte_policy_candidate_path_create, + .cli_show = cli_show_srte_policy_candidate_path, + .cli_show_end = cli_show_srte_policy_candidate_path_end, + .destroy = pathd_srte_policy_candidate_path_destroy, + .get_next = pathd_srte_policy_candidate_path_get_next, + .get_keys = pathd_srte_policy_candidate_path_get_keys, + .lookup_entry = pathd_srte_policy_candidate_path_lookup_entry, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/name", + .cbs = { + .modify = pathd_srte_policy_candidate_path_name_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/is-best-candidate-path", + .cbs = { + .get_elem = pathd_srte_policy_candidate_path_is_best_candidate_path_get_elem, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/protocol-origin", + .cbs = { + .modify = pathd_srte_policy_candidate_path_protocol_origin_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/originator", + .cbs = { + .modify = pathd_srte_policy_candidate_path_originator_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/discriminator", + .cbs = { + .get_elem = pathd_srte_policy_candidate_path_discriminator_get_elem, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/type", + .cbs = { + .modify = pathd_srte_policy_candidate_path_type_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/segment-list-name", + .cbs = { + .destroy = pathd_srte_policy_candidate_path_segment_list_name_destroy, + .modify = pathd_srte_policy_candidate_path_segment_list_name_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_policy_candidate_path_bandwidth_destroy, + .apply_finish = pathd_srte_policy_candidate_path_bandwidth_apply_finish + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth/required", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth/value", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/exclude-any", + .cbs = { + .modify = pathd_srte_policy_candidate_path_exclude_any_modify, + .destroy = pathd_srte_policy_candidate_path_exclude_any_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-any", + .cbs = { + .modify = pathd_srte_policy_candidate_path_include_any_modify, + .destroy = pathd_srte_policy_candidate_path_include_any_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-all", + .cbs = { + .modify = pathd_srte_policy_candidate_path_include_all_modify, + .destroy = pathd_srte_policy_candidate_path_include_all_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_policy_candidate_path_metrics_destroy, + .apply_finish = pathd_srte_policy_candidate_path_metrics_apply_finish + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/value", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/required", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/is-bound", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/is-computed", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_policy_candidate_path_objfun_destroy, + .apply_finish = pathd_srte_policy_candidate_path_objfun_apply_finish + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function/required", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function/type", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = NULL, + }, + } +}; + +void iter_objfun_prefs(const struct lyd_node *dnode, const char* path, + of_pref_cp_t fun, void *arg) +{ + struct of_cb_args args = {0}; + struct of_cb_pref *p; + + yang_dnode_iterate(iter_objfun_cb, &args, dnode, path); + for (p = args.first; p != NULL; p = p->next) + fun(p->type, arg); +} + +int iter_objfun_cb(const struct lyd_node *dnode, void *arg) +{ + struct of_cb_args *of_arg = arg; + struct of_cb_pref *pref; + struct of_cb_pref **p; + + if (of_arg->free_slot >= MAX_OBJFUN_TYPE) + return YANG_ITER_STOP; + + pref = &of_arg->prefs[of_arg->free_slot++]; + + pref->index = yang_dnode_get_uint32(dnode, "./index"); + pref->type = yang_dnode_get_enum(dnode, "./type"); + + /* Simplistic insertion sort */ + p = &of_arg->first; + while (true) { + if (*p == NULL) { + *p = pref; + break; + } + if ((*p)->index >= pref->index) { + pref->next = *p; + *p = pref; + break; + } + p = &(*p)->next; + } + + return YANG_ITER_CONTINUE; +} + +int dummy_create(struct nb_cb_create_args *args) +{ + return NB_OK; +} + +int dummy_modify(struct nb_cb_modify_args *args) +{ + return NB_OK; +} + +int dummy_destroy(struct nb_cb_destroy_args *args) +{ + return NB_OK; +} diff --git a/pathd/path_nb.h b/pathd/path_nb.h new file mode 100644 index 0000000..dacc5eb --- /dev/null +++ b/pathd/path_nb.h @@ -0,0 +1,139 @@ +/* + * 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 + */ + +#ifndef _FRR_PATH_NB_H_ +#define _FRR_PATH_NB_H_ + +#include "pathd/pathd.h" + +extern const struct frr_yang_module_info frr_pathd_info; + +/* Mandatory callbacks. */ +int pathd_srte_segment_list_create(struct nb_cb_create_args *args); +int pathd_srte_segment_list_destroy(struct nb_cb_destroy_args *args); + +const void *pathd_srte_segment_list_get_next(struct nb_cb_get_next_args *args); +int pathd_srte_segment_list_get_keys(struct nb_cb_get_keys_args *args); +const void * +pathd_srte_segment_list_lookup_entry(struct nb_cb_lookup_entry_args *args); + +int pathd_srte_segment_list_segment_create(struct nb_cb_create_args *args); +int pathd_srte_segment_list_segment_destroy(struct nb_cb_destroy_args *args); +int pathd_srte_segment_list_protocol_origin_modify( + struct nb_cb_modify_args *args); +int pathd_srte_segment_list_originator_modify(struct nb_cb_modify_args *args); +int pathd_srte_segment_list_segment_sid_value_modify( + struct nb_cb_modify_args *args); +int pathd_srte_segment_list_segment_nai_destroy( + struct nb_cb_destroy_args *args); +void pathd_srte_segment_list_segment_nai_apply_finish( + struct nb_cb_apply_finish_args *args); +int pathd_srte_segment_list_segment_sid_value_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_create(struct nb_cb_create_args *args); +int pathd_srte_policy_destroy(struct nb_cb_destroy_args *args); +const void *pathd_srte_policy_get_next(struct nb_cb_get_next_args *args); +int pathd_srte_policy_get_keys(struct nb_cb_get_keys_args *args); +const void * +pathd_srte_policy_lookup_entry(struct nb_cb_lookup_entry_args *args); +int pathd_srte_policy_name_modify(struct nb_cb_modify_args *args); +int pathd_srte_policy_name_destroy(struct nb_cb_destroy_args *args); +int pathd_srte_policy_binding_sid_modify(struct nb_cb_modify_args *args); +int pathd_srte_policy_binding_sid_destroy(struct nb_cb_destroy_args *args); +struct yang_data * +pathd_srte_policy_is_operational_get_elem(struct nb_cb_get_elem_args *args); +int pathd_srte_policy_candidate_path_create(struct nb_cb_create_args *args); +int pathd_srte_policy_candidate_path_destroy(struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_name_modify( + struct nb_cb_modify_args *args); +const void * +pathd_srte_policy_candidate_path_get_next(struct nb_cb_get_next_args *args); +int pathd_srte_policy_candidate_path_get_keys(struct nb_cb_get_keys_args *args); +const void *pathd_srte_policy_candidate_path_lookup_entry( + struct nb_cb_lookup_entry_args *args); +void pathd_srte_policy_candidate_path_bandwidth_apply_finish( + struct nb_cb_apply_finish_args *args); +int pathd_srte_policy_candidate_path_bandwidth_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_exclude_any_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_exclude_any_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_include_any_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_include_any_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_include_all_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_include_all_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_metrics_destroy( + struct nb_cb_destroy_args *args); +void pathd_srte_policy_candidate_path_metrics_apply_finish( + struct nb_cb_apply_finish_args *args); +int pathd_srte_policy_candidate_path_objfun_destroy( + struct nb_cb_destroy_args *args); +void pathd_srte_policy_candidate_path_objfun_apply_finish( + struct nb_cb_apply_finish_args *args); +struct yang_data * +pathd_srte_policy_candidate_path_is_best_candidate_path_get_elem( + struct nb_cb_get_elem_args *args); +int pathd_srte_policy_candidate_path_protocol_origin_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_originator_modify( + struct nb_cb_modify_args *args); +struct yang_data *pathd_srte_policy_candidate_path_discriminator_get_elem( + struct nb_cb_get_elem_args *args); +int pathd_srte_policy_candidate_path_type_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_segment_list_name_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_segment_list_name_destroy( + struct nb_cb_destroy_args *args); + +/* Optional 'apply_finish' callbacks. */ +void pathd_apply_finish(struct nb_cb_apply_finish_args *args); + +/* Optional 'cli_show' callbacks. */ +void cli_show_srte_segment_list(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_segment_list_end(struct vty *vty, + const struct lyd_node *dnode); +void cli_show_srte_segment_list_segment(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_end(struct vty *vty, const struct lyd_node *dnode); +void cli_show_srte_policy_name(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_binding_sid(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_candidate_path(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_candidate_path_end(struct vty *vty, + const struct lyd_node *dnode); + +/* Utility functions */ +typedef void (*of_pref_cp_t)(enum objfun_type type, void *arg); +void iter_objfun_prefs(const struct lyd_node *dnode, const char *path, + of_pref_cp_t fun, void *arg); + +#endif /* _FRR_PATH_NB_H_ */ diff --git a/pathd/path_nb_config.c b/pathd/path_nb_config.c new file mode 100644 index 0000000..e1fbb37 --- /dev/null +++ b/pathd/path_nb_config.c @@ -0,0 +1,769 @@ +/* + * 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 + */ + +#include <zebra.h> +#include <lib_errors.h> + +#include "northbound.h" +#include "libfrr.h" + +#include "pathd/path_zebra.h" +#include "pathd/path_nb.h" + +/* + * XPath: /frr-pathd:pathd + */ +void pathd_apply_finish(struct nb_cb_apply_finish_args *args) +{ + srte_apply_changes(); +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list + */ +int pathd_srte_segment_list_create(struct nb_cb_create_args *args) +{ + struct srte_segment_list *segment_list; + const char *name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "./name"); + segment_list = srte_segment_list_add(name); + nb_running_set_entry(args->dnode, segment_list); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_NEW); + + return NB_OK; +} + +int pathd_srte_segment_list_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_segment_list *segment_list; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_unset_entry(args->dnode); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_DELETED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/protocol-origin + */ +int pathd_srte_segment_list_protocol_origin_modify( + struct nb_cb_modify_args *args) +{ + struct srte_segment_list *segment_list; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_get_entry(args->dnode, NULL, true); + segment_list->protocol_origin = yang_dnode_get_enum(args->dnode, NULL); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/originator + */ +int pathd_srte_segment_list_originator_modify(struct nb_cb_modify_args *args) +{ + struct srte_segment_list *segment_list; + const char *originator; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_get_entry(args->dnode, NULL, true); + originator = yang_dnode_get_string(args->dnode, NULL); + strlcpy(segment_list->originator, originator, + sizeof(segment_list->originator)); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/segment + */ +int pathd_srte_segment_list_segment_create(struct nb_cb_create_args *args) +{ + struct srte_segment_list *segment_list; + struct srte_segment_entry *segment; + uint32_t index; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_get_entry(args->dnode, NULL, true); + index = yang_dnode_get_uint32(args->dnode, "./index"); + segment = srte_segment_entry_add(segment_list, index); + nb_running_set_entry(args->dnode, segment); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + +int pathd_srte_segment_list_segment_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_unset_entry(args->dnode); + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + srte_segment_entry_del(segment); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/segment/sid-value + */ +int pathd_srte_segment_list_segment_sid_value_modify( + struct nb_cb_modify_args *args) +{ + mpls_label_t sid_value; + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_get_entry(args->dnode, NULL, true); + sid_value = yang_dnode_get_uint32(args->dnode, NULL); + segment->sid_value = sid_value; + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + +int pathd_srte_segment_list_segment_sid_value_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_get_entry(args->dnode, NULL, true); + segment->sid_value = MPLS_LABEL_NONE; + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + + +int pathd_srte_segment_list_segment_nai_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_get_entry(args->dnode, NULL, true); + segment->nai_type = SRTE_SEGMENT_NAI_TYPE_NONE; + segment->nai_local_addr.ipa_type = IPADDR_NONE; + segment->nai_local_iface = 0; + segment->nai_remote_addr.ipa_type = IPADDR_NONE; + segment->nai_remote_iface = 0; + + return NB_OK; +} + +void pathd_srte_segment_list_segment_nai_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_segment_entry *segment; + enum srte_segment_nai_type type; + struct ipaddr local_addr, remote_addr; + uint32_t local_iface = 0, remote_iface = 0; + uint8_t algo = 0, local_prefix_len = 0; + const char *algo_buf, *local_prefix_len_buf; + + segment = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "./type"); + + yang_dnode_get_ip(&local_addr, args->dnode, "./local-address"); + + switch (type) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + yang_dnode_get_ip(&remote_addr, args->dnode, + "./remote-address"); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + yang_dnode_get_ip(&remote_addr, args->dnode, + "./remote-address"); + local_iface = + yang_dnode_get_uint32(args->dnode, "./local-interface"); + remote_iface = yang_dnode_get_uint32(args->dnode, + "./remote-interface"); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + algo_buf = yang_dnode_get_string(args->dnode, "./algorithm"); + algo = atoi(algo_buf); + local_prefix_len_buf = yang_dnode_get_string( + args->dnode, "./local-prefix-len"); + local_prefix_len = atoi(local_prefix_len_buf); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + local_iface = + yang_dnode_get_uint32(args->dnode, "./local-interface"); + local_prefix_len_buf = yang_dnode_get_string( + args->dnode, "./local-prefix-len"); + local_prefix_len = atoi(local_prefix_len_buf); + break; + default: + break; + } + + zlog_debug(" Segment list name (%d) index (%s) ", segment->index, + segment->segment_list->name); + if (srte_segment_entry_set_nai(segment, type, &local_addr, local_iface, + &remote_addr, remote_iface, algo, + local_prefix_len)) + SET_FLAG(segment->segment_list->flags, + F_SEGMENT_LIST_SID_CONFLICT); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy + */ +int pathd_srte_policy_create(struct nb_cb_create_args *args) +{ + struct srte_policy *policy; + uint32_t color; + struct ipaddr endpoint; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + color = yang_dnode_get_uint32(args->dnode, "./color"); + yang_dnode_get_ip(&endpoint, args->dnode, "./endpoint"); + policy = srte_policy_add(color, &endpoint, SRTE_ORIGIN_LOCAL, NULL); + + nb_running_set_entry(args->dnode, policy); + SET_FLAG(policy->flags, F_POLICY_NEW); + + return NB_OK; +} + +int pathd_srte_policy_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_policy *policy; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_unset_entry(args->dnode); + SET_FLAG(policy->flags, F_POLICY_DELETED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/name + */ +int pathd_srte_policy_name_modify(struct nb_cb_modify_args *args) +{ + struct srte_policy *policy; + const char *name; + + if (args->event != NB_EV_APPLY && args->event != NB_EV_VALIDATE) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + + if (args->event == NB_EV_VALIDATE) { + /* the policy name is fixed after setting it once */ + if (strlen(policy->name) > 0) { + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "The SR Policy name is fixed!"); + return NB_ERR_RESOURCE; + } else + return NB_OK; + } + + name = yang_dnode_get_string(args->dnode, NULL); + strlcpy(policy->name, name, sizeof(policy->name)); + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + + return NB_OK; +} + +int pathd_srte_policy_name_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_policy *policy; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + policy->name[0] = '\0'; + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/binding-sid + */ +int pathd_srte_policy_binding_sid_modify(struct nb_cb_modify_args *args) +{ + struct srte_policy *policy; + mpls_label_t binding_sid; + + binding_sid = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + break; + case NB_EV_PREPARE: + if (path_zebra_request_label(binding_sid) < 0) + return NB_ERR_RESOURCE; + break; + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + policy = nb_running_get_entry(args->dnode, NULL, true); + srte_policy_update_binding_sid(policy, binding_sid); + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + break; + } + + return NB_OK; +} + +int pathd_srte_policy_binding_sid_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_policy *policy; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + srte_policy_update_binding_sid(policy, MPLS_LABEL_NONE); + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path + */ +int pathd_srte_policy_candidate_path_create(struct nb_cb_create_args *args) +{ + struct srte_policy *policy; + struct srte_candidate *candidate; + uint32_t preference; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + preference = yang_dnode_get_uint32(args->dnode, "./preference"); + candidate = + srte_candidate_add(policy, preference, SRTE_ORIGIN_LOCAL, NULL); + nb_running_set_entry(args->dnode, candidate); + SET_FLAG(candidate->flags, F_CANDIDATE_NEW); + + return NB_OK; +} + +int pathd_srte_policy_candidate_path_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_unset_entry(args->dnode); + SET_FLAG(candidate->flags, F_CANDIDATE_DELETED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/name + */ +int pathd_srte_policy_candidate_path_name_modify(struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + const char *name; + char xpath[XPATH_MAXLEN]; + char xpath_buf[XPATH_MAXLEN - 3]; + + if (args->event != NB_EV_APPLY && args->event != NB_EV_VALIDATE) + return NB_OK; + + /* the candidate name is fixed after setting it once, this is checked + * here */ + if (args->event == NB_EV_VALIDATE) { + /* first get the precise path to the candidate path */ + yang_dnode_get_path(args->dnode, xpath_buf, sizeof(xpath_buf)); + snprintf(xpath, sizeof(xpath), "%s%s", xpath_buf, "/.."); + + candidate = nb_running_get_entry_non_rec(NULL, xpath, false); + + /* then check if it exists and if the name was provided */ + if (candidate && strlen(candidate->name) > 0) { + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "The candidate name is fixed!"); + return NB_ERR_RESOURCE; + } else + return NB_OK; + } + + candidate = nb_running_get_entry(args->dnode, NULL, true); + + name = yang_dnode_get_string(args->dnode, NULL); + strlcpy(candidate->name, name, sizeof(candidate->name)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + + +static int affinity_filter_modify(struct nb_cb_modify_args *args, + enum affinity_filter_type type) +{ + uint32_t filter; + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + filter = yang_dnode_get_uint32(args->dnode, NULL); + srte_candidate_set_affinity_filter(candidate, type, filter); + + return NB_OK; +} + +static int affinity_filter_destroy(struct nb_cb_destroy_args *args, + enum affinity_filter_type type) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + srte_candidate_unset_affinity_filter(candidate, type); + + return NB_OK; +} + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/exclude-any + */ + +int pathd_srte_policy_candidate_path_exclude_any_modify( + struct nb_cb_modify_args *args) +{ + return affinity_filter_modify(args, AFFINITY_FILTER_EXCLUDE_ANY); +} + +int pathd_srte_policy_candidate_path_exclude_any_destroy( + struct nb_cb_destroy_args *args) +{ + return affinity_filter_destroy(args, AFFINITY_FILTER_EXCLUDE_ANY); +} + + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-any + */ +int pathd_srte_policy_candidate_path_include_any_modify( + struct nb_cb_modify_args *args) +{ + return affinity_filter_modify(args, AFFINITY_FILTER_INCLUDE_ANY); +} + +int pathd_srte_policy_candidate_path_include_any_destroy( + struct nb_cb_destroy_args *args) +{ + return affinity_filter_destroy(args, AFFINITY_FILTER_INCLUDE_ANY); +} + + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-all + */ +int pathd_srte_policy_candidate_path_include_all_modify( + struct nb_cb_modify_args *args) +{ + return affinity_filter_modify(args, AFFINITY_FILTER_INCLUDE_ALL); +} + +int pathd_srte_policy_candidate_path_include_all_destroy( + struct nb_cb_destroy_args *args) +{ + return affinity_filter_destroy(args, AFFINITY_FILTER_INCLUDE_ALL); +} + + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics + */ +int pathd_srte_policy_candidate_path_metrics_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + enum srte_candidate_metric_type type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + + type = yang_dnode_get_enum(args->dnode, "./type"); + srte_candidate_unset_metric(candidate, type); + + return NB_OK; +} + +void pathd_srte_policy_candidate_path_metrics_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_candidate *candidate; + enum srte_candidate_metric_type type; + float value; + bool required, is_bound = false, is_computed = false; + + assert(args->context != NULL); + + candidate = nb_running_get_entry(args->dnode, NULL, true); + + type = yang_dnode_get_enum(args->dnode, "./type"); + value = (float)yang_dnode_get_dec64(args->dnode, "./value"); + required = yang_dnode_get_bool(args->dnode, "./required"); + if (yang_dnode_exists(args->dnode, "./is-bound")) + is_bound = yang_dnode_get_bool(args->dnode, "./is-bound"); + if (yang_dnode_exists(args->dnode, "./is-computed")) + is_computed = yang_dnode_get_bool(args->dnode, "./is-computed"); + + srte_candidate_set_metric(candidate, type, value, required, is_bound, + is_computed); +} + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function + */ +int pathd_srte_policy_candidate_path_objfun_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + + candidate = nb_running_get_entry(args->dnode, NULL, true); + srte_candidate_unset_objfun(candidate); + + return NB_OK; +} + +void pathd_srte_policy_candidate_path_objfun_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_candidate *candidate; + enum objfun_type type; + bool required; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + required = yang_dnode_get_bool(args->dnode, "./required"); + type = yang_dnode_get_enum(args->dnode, "./type"); + srte_candidate_set_objfun(candidate, required, type); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/protocol-origin + */ +int pathd_srte_policy_candidate_path_protocol_origin_modify( + struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + enum srte_protocol_origin protocol_origin; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + protocol_origin = yang_dnode_get_enum(args->dnode, NULL); + candidate->protocol_origin = protocol_origin; + candidate->lsp->protocol_origin = protocol_origin; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/originator + */ +int pathd_srte_policy_candidate_path_originator_modify( + struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + const char *originator; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + originator = yang_dnode_get_string(args->dnode, NULL); + strlcpy(candidate->originator, originator, + sizeof(candidate->originator)); + strlcpy(candidate->lsp->originator, originator, + sizeof(candidate->lsp->originator)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/type + */ +int pathd_srte_policy_candidate_path_type_modify(struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + enum srte_candidate_type type; + char xpath[XPATH_MAXLEN]; + char xpath_buf[XPATH_MAXLEN - 3]; + + if (args->event != NB_EV_APPLY && args->event != NB_EV_VALIDATE) + return NB_OK; + + /* the candidate type is fixed after setting it once, this is checked + * here */ + if (args->event == NB_EV_VALIDATE) { + /* first get the precise path to the candidate path */ + yang_dnode_get_path(args->dnode, xpath_buf, sizeof(xpath_buf)); + snprintf(xpath, sizeof(xpath), "%s%s", xpath_buf, "/.."); + + candidate = nb_running_get_entry_non_rec(NULL, xpath, false); + + /* then check if it exists and if the type was provided */ + if (candidate + && candidate->type != SRTE_CANDIDATE_TYPE_UNDEFINED) { + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "The candidate type is fixed!"); + return NB_ERR_RESOURCE; + } else + return NB_OK; + } + + candidate = nb_running_get_entry(args->dnode, NULL, true); + + type = yang_dnode_get_enum(args->dnode, NULL); + candidate->type = type; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/segment-list-name + */ +int pathd_srte_policy_candidate_path_segment_list_name_modify( + struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + const char *segment_list_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + segment_list_name = yang_dnode_get_string(args->dnode, NULL); + + candidate->segment_list = srte_segment_list_find(segment_list_name); + candidate->lsp->segment_list = candidate->segment_list; + assert(candidate->segment_list); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +int pathd_srte_policy_candidate_path_segment_list_name_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + candidate->segment_list = NULL; + candidate->lsp->segment_list = NULL; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth + */ +void pathd_srte_policy_candidate_path_bandwidth_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_candidate *candidate; + float value; + bool required; + + assert(args->context != NULL); + + candidate = nb_running_get_entry(args->dnode, NULL, true); + value = (float)yang_dnode_get_dec64(args->dnode, "./value"); + required = yang_dnode_get_bool(args->dnode, "./required"); + srte_candidate_set_bandwidth(candidate, value, required); +} + +int pathd_srte_policy_candidate_path_bandwidth_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + srte_candidate_unset_bandwidth(candidate); + return NB_OK; +} diff --git a/pathd/path_nb_state.c b/pathd/path_nb_state.c new file mode 100644 index 0000000..60f04f4 --- /dev/null +++ b/pathd/path_nb_state.c @@ -0,0 +1,189 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "northbound.h" +#include "libfrr.h" + +#include "pathd/pathd.h" +#include "pathd/path_nb.h" + +/* + * XPath: /frr-pathd:pathd/srte/segment-list + */ +const void *pathd_srte_segment_list_get_next(struct nb_cb_get_next_args *args) +{ + struct srte_segment_list *segment_list = + (struct srte_segment_list *)args->list_entry; + + if (args->list_entry == NULL) + segment_list = + RB_MIN(srte_segment_list_head, &srte_segment_lists); + else + segment_list = RB_NEXT(srte_segment_list_head, segment_list); + + return segment_list; +} + +int pathd_srte_segment_list_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct srte_segment_list *segment_list = + (struct srte_segment_list *)args->list_entry; + + args->keys->num = 1; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%s", + segment_list->name); + + return NB_OK; +} + +const void * +pathd_srte_segment_list_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + return srte_segment_list_find(args->keys->key[0]); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy + */ +const void *pathd_srte_policy_get_next(struct nb_cb_get_next_args *args) +{ + struct srte_policy *policy = (struct srte_policy *)args->list_entry; + + if (args->list_entry == NULL) + policy = RB_MIN(srte_policy_head, &srte_policies); + else + policy = RB_NEXT(srte_policy_head, policy); + + return policy; +} + +int pathd_srte_policy_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct srte_policy *policy = + (struct srte_policy *)args->list_entry; + + args->keys->num = 2; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%u", + policy->color); + ipaddr2str(&policy->endpoint, args->keys->key[1], + sizeof(args->keys->key[1])); + + return NB_OK; +} + +const void *pathd_srte_policy_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + uint32_t color; + struct ipaddr endpoint; + + color = yang_str2uint32(args->keys->key[0]); + yang_str2ip(args->keys->key[1], &endpoint); + + return srte_policy_find(color, &endpoint); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/is-operational + */ +struct yang_data * +pathd_srte_policy_is_operational_get_elem(struct nb_cb_get_elem_args *args) +{ + struct srte_policy *policy = (struct srte_policy *)args->list_entry; + bool is_operational = false; + + if (policy->status == SRTE_POLICY_STATUS_UP) + is_operational = true; + + return yang_data_new_bool(args->xpath, is_operational); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path + */ +const void * +pathd_srte_policy_candidate_path_get_next(struct nb_cb_get_next_args *args) +{ + struct srte_policy *policy = + (struct srte_policy *)args->parent_list_entry; + struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + if (args->list_entry == NULL) + candidate = + RB_MIN(srte_candidate_head, &policy->candidate_paths); + else + candidate = RB_NEXT(srte_candidate_head, candidate); + + return candidate; +} + +int pathd_srte_policy_candidate_path_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + args->keys->num = 1; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%u", + candidate->preference); + + return NB_OK; +} + +const void *pathd_srte_policy_candidate_path_lookup_entry( + struct nb_cb_lookup_entry_args *args) +{ + struct srte_policy *policy = + (struct srte_policy *)args->parent_list_entry; + uint32_t preference; + + preference = yang_str2uint32(args->keys->key[0]); + + return srte_candidate_find(policy, preference); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate_path/is-best-candidate-path + */ +struct yang_data * +pathd_srte_policy_candidate_path_is_best_candidate_path_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + return yang_data_new_bool( + args->xpath, CHECK_FLAG(candidate->flags, F_CANDIDATE_BEST)); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/discriminator + */ +struct yang_data *pathd_srte_policy_candidate_path_discriminator_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + return yang_data_new_uint32(args->xpath, candidate->discriminator); +} diff --git a/pathd/path_pcep.c b/pathd/path_pcep.c new file mode 100644 index 0000000..ce631eb --- /dev/null +++ b/pathd/path_pcep.c @@ -0,0 +1,387 @@ +/* + * 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 + */ + +#include <zebra.h> +#include "pceplib/pcep_utils_counters.h" + +#include "memory.h" +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "lib/version.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" +#include "termtable.h" + +#include "pathd/pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_cli.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" + +DEFINE_MTYPE(PATHD, PCEP, "PCEP module"); + +/* + * Globals. + */ +static struct pcep_glob pcep_glob_space = {.dbg = {0, "pathd module: pcep"}}; +struct pcep_glob *pcep_g = &pcep_glob_space; + +/* Main Thread Even Handler */ +static int pcep_main_event_handler(enum pcep_main_event_type type, int pcc_id, + void *payload); +static int pcep_main_event_start_sync(int pcc_id); +static int pcep_main_event_start_sync_cb(struct path *path, void *arg); +static int pcep_main_event_initiate_candidate(struct path *path); +static int pcep_main_event_update_candidate(struct path *path); +static int pcep_main_event_remove_candidate_segments(const char *originator, + bool force); + +/* Hook Handlers called from the Main Thread */ +static int pathd_candidate_created_handler(struct srte_candidate *candidate); +static int pathd_candidate_updated_handler(struct srte_candidate *candidate); +static int pathd_candidate_removed_handler(struct srte_candidate *candidate); + +/* Path manipulation functions */ +static struct path_metric *pcep_copy_metrics(struct path_metric *metric); +static struct path_hop *pcep_copy_hops(struct path_hop *hop); + +/* Other static functions */ +static void notify_status(struct path *path, bool not_changed); + +/* Module Functions */ +static int pcep_module_finish(void); +static int pcep_module_late_init(struct thread_master *tm); +static int pcep_module_init(void); + +/* ------------ Path Helper Functions ------------ */ + +struct path *pcep_new_path(void) +{ + struct path *path; + path = XCALLOC(MTYPE_PCEP, sizeof(*path)); + path->binding_sid = MPLS_LABEL_NONE; + path->enforce_bandwidth = true; + return path; +} + +struct path_hop *pcep_new_hop(void) +{ + struct path_hop *hop; + hop = XCALLOC(MTYPE_PCEP, sizeof(*hop)); + return hop; +} + +struct path_metric *pcep_new_metric(void) +{ + struct path_metric *metric; + metric = XCALLOC(MTYPE_PCEP, sizeof(*metric)); + return metric; +} + +struct path_metric *pcep_copy_metrics(struct path_metric *metric) +{ + if (metric == NULL) + return NULL; + struct path_metric *new_metric = pcep_new_metric(); + *new_metric = *metric; + new_metric->next = pcep_copy_metrics(metric->next); + return new_metric; +} + +struct path_hop *pcep_copy_hops(struct path_hop *hop) +{ + if (hop == NULL) + return NULL; + struct path_hop *new_hop = pcep_new_hop(); + *new_hop = *hop; + new_hop->next = pcep_copy_hops(hop->next); + return new_hop; +} + +struct path *pcep_copy_path(struct path *path) +{ + struct path *new_path = pcep_new_path(); + + *new_path = *path; + new_path->first_metric = pcep_copy_metrics(path->first_metric); + new_path->first_hop = pcep_copy_hops(path->first_hop); + if (path->name != NULL) + new_path->name = XSTRDUP(MTYPE_PCEP, path->name); + if (path->originator != NULL) + new_path->originator = XSTRDUP(MTYPE_PCEP, path->originator); + return new_path; +} + +void pcep_free_path(struct path *path) +{ + struct path_hop *hop; + struct path_metric *metric; + char *tmp; + + metric = path->first_metric; + while (metric != NULL) { + struct path_metric *next = metric->next; + XFREE(MTYPE_PCEP, metric); + metric = next; + } + hop = path->first_hop; + while (hop != NULL) { + struct path_hop *next = hop->next; + XFREE(MTYPE_PCEP, hop); + hop = next; + } + if (path->originator != NULL) { + /* The path own the memory, it is const so it is clear it + shouldn't be modified. XFREE macro do not support type casting + so we need a temporary variable */ + tmp = (char *)path->originator; + XFREE(MTYPE_PCEP, tmp); + path->originator = NULL; + } + if (path->name != NULL) { + /* The path own the memory, it is const so it is clear it + shouldn't be modified. XFREE macro do not support type casting + so we need a temporary variable */ + tmp = (char *)path->name; + XFREE(MTYPE_PCEP, tmp); + path->name = NULL; + } + XFREE(MTYPE_PCEP, path); +} + +/* ------------ Other Static Functions ------------ */ + +void notify_status(struct path *path, bool not_changed) +{ + struct path *resp = NULL; + + if ((resp = path_pcep_config_get_path(&path->nbkey))) { + resp->srp_id = path->srp_id; + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "(%s) Send report for candidate path %s", __func__, + path->name); + pcep_ctrl_send_report(pcep_g->fpt, path->pcc_id, resp, + not_changed); + } +} + +/* ------------ Main Thread Even Handler ------------ */ + +int pcep_main_event_handler(enum pcep_main_event_type type, int pcc_id, + void *payload) +{ + int ret = 0; + + switch (type) { + case PCEP_MAIN_EVENT_START_SYNC: + ret = pcep_main_event_start_sync(pcc_id); + break; + case PCEP_MAIN_EVENT_INITIATE_CANDIDATE: + assert(payload != NULL); + ret = pcep_main_event_initiate_candidate( + (struct path *)payload); + break; + case PCEP_MAIN_EVENT_UPDATE_CANDIDATE: + assert(payload != NULL); + ret = pcep_main_event_update_candidate((struct path *)payload); + break; + case PCEP_MAIN_EVENT_REMOVE_CANDIDATE_LSP: + ret = pcep_main_event_remove_candidate_segments( + (const char *)payload, true); + break; + default: + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Unexpected event received in the main thread: %u", + type); + break; + } + + return ret; +} + +int pcep_main_event_start_sync(int pcc_id) +{ + path_pcep_config_list_path(pcep_main_event_start_sync_cb, &pcc_id); + pcep_ctrl_sync_done(pcep_g->fpt, pcc_id); + return 0; +} + +int pcep_main_event_start_sync_cb(struct path *path, void *arg) +{ + int *pcc_id = (int *)arg; + pcep_ctrl_sync_path(pcep_g->fpt, *pcc_id, path); + return 1; +} + +int pcep_main_event_initiate_candidate(struct path *path) +{ + int ret = 0; + + ret = path_pcep_config_initiate_path(path); + if (path->do_remove) { + struct pcep_error *error; + error = XCALLOC(MTYPE_PCEP, sizeof(*error)); + error->path = path; + error->error_type = PCEP_ERRT_INVALID_OPERATION; + switch (ret) { + case ERROR_19_1: + error->error_value = + PCEP_ERRV_LSP_UPDATE_FOR_NON_DELEGATED_LSP; + break; + case ERROR_19_3: + error->error_value = + PCEP_ERRV_LSP_UPDATE_UNKNOWN_PLSP_ID; + break; + case ERROR_19_9: + error->error_value = PCEP_ERRV_LSP_NOT_PCE_INITIATED; + break; + default: + zlog_warn("(%s)PCE tried to REMOVE unknown error!", + __func__); + XFREE(MTYPE_PCEP, error); + pcep_free_path(path); + return ret; + break; + } + pcep_ctrl_send_error(pcep_g->fpt, path->pcc_id, error); + } else if (ret != PATH_NB_ERR && path->srp_id != 0) + notify_status(path, ret == PATH_NB_NO_CHANGE); + return ret; +} + +int pcep_main_event_update_candidate(struct path *path) +{ + int ret = 0; + + ret = path_pcep_config_update_path(path); + if (ret != PATH_NB_ERR && path->srp_id != 0) + notify_status(path, ret == PATH_NB_NO_CHANGE); + return ret; +} + +int pcep_main_event_remove_candidate_segments(const char *originator, + bool force) +{ + srte_candidate_unset_segment_list(originator, force); + /* Avoid compiler warnings about const char* */ + void *free_ptr = (void *)originator; + XFREE(MTYPE_PCEP, free_ptr); + + srte_apply_changes(); + + return 0; +} + +/* ------------ Hook Handlers Functions Called From Main Thread ------------ */ + +int pathd_candidate_created_handler(struct srte_candidate *candidate) +{ + struct path *path = candidate_to_path(candidate); + int ret = pcep_ctrl_pathd_event(pcep_g->fpt, PCEP_PATH_CREATED, path); + return ret; +} + +int pathd_candidate_updated_handler(struct srte_candidate *candidate) +{ + struct path *path = candidate_to_path(candidate); + int ret = pcep_ctrl_pathd_event(pcep_g->fpt, PCEP_PATH_UPDATED, path); + return ret; +} + +int pathd_candidate_removed_handler(struct srte_candidate *candidate) +{ + struct path *path = candidate_to_path(candidate); + int ret = pcep_ctrl_pathd_event(pcep_g->fpt, PCEP_PATH_REMOVED, path); + return ret; +} + + +/* ------------ Module Functions ------------ */ + +/* this creates threads, therefore must run after fork(). but it must also + * run before config load, so the CLI commands don't try to touch things that + * aren't set up yet... + */ +static int pcep_module_config_pre(struct thread_master *tm) +{ + assert(pcep_g->fpt == NULL); + assert(pcep_g->master == NULL); + + struct frr_pthread *fpt; + + if (pcep_ctrl_initialize(tm, &fpt, pcep_main_event_handler)) + return 1; + + if (pcep_lib_initialize(fpt)) + return 1; + + pcep_g->master = tm; + pcep_g->fpt = fpt; + + return 0; +} + +static int pcep_module_late_init(struct thread_master *tm) +{ + hook_register(pathd_candidate_created, pathd_candidate_created_handler); + hook_register(pathd_candidate_updated, pathd_candidate_updated_handler); + hook_register(pathd_candidate_removed, pathd_candidate_removed_handler); + + hook_register(frr_config_pre, pcep_module_config_pre); + hook_register(frr_fini, pcep_module_finish); + + pcep_cli_init(); + + return 0; +} + +int pcep_module_finish(void) +{ + pcep_ctrl_finalize(&pcep_g->fpt); + pcep_lib_finalize(); + + for (int i = 0; i < MAX_PCC; i++) + if (pcep_g->pce_opts_cli[i] != NULL) + XFREE(MTYPE_PCEP, pcep_g->pce_opts_cli[i]); + + return 0; +} + +int pcep_module_init(void) +{ + pcep_g->num_pce_opts_cli = 0; + for (int i = 0; i < MAX_PCE; i++) + pcep_g->pce_opts_cli[i] = NULL; + pcep_g->num_config_group_opts = 0; + for (int i = 0; i < MAX_PCE; i++) + pcep_g->config_group_opts[i] = NULL; + + hook_register(frr_late_init, pcep_module_late_init); + return 0; +} + +FRR_MODULE_SETUP(.name = "frr_pathd_pcep", .version = FRR_VERSION, + .description = "FRR pathd PCEP module", + .init = pcep_module_init, +); diff --git a/pathd/path_pcep.h b/pathd/path_pcep.h new file mode 100644 index 0000000..597a4b6 --- /dev/null +++ b/pathd/path_pcep.h @@ -0,0 +1,338 @@ +/* + * 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 + */ + +#ifndef _PATH_PCEP_H_ +#define _PATH_PCEP_H_ + +#include <stdbool.h> +#include <debug.h> +#include <netinet/tcp.h> +#include "memory.h" +#include "pceplib/pcep_utils_logging.h" +#include "pceplib/pcep_pcc_api.h" +#include "mpls.h" +#include "pathd/pathd.h" + +DECLARE_MTYPE(PCEP); + +#define PCEP_DEFAULT_PORT 4189 +#define MAX_PCC 32 +#define MAX_PCE 32 +#define MAX_TAG_SIZE 50 +#define PCEP_DEBUG_MODE_BASIC 0x01 +#define PCEP_DEBUG_MODE_PATH 0x02 +#define PCEP_DEBUG_MODE_PCEP 0x04 +#define PCEP_DEBUG_MODE_PCEPLIB 0x08 +#define PCEP_DEBUG(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_BASIC)) \ + DEBUGD(&pcep_g->dbg, "pcep: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PCEP_DEBUG_PATH(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PATH)) \ + DEBUGD(&pcep_g->dbg, "pcep: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PCEP_DEBUG_PCEP(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEP)) \ + DEBUGD(&pcep_g->dbg, "pcep: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PCEP_DEBUG_PCEPLIB(priority, fmt, ...) \ + do { \ + switch (priority) { \ + case LOG_DEBUG: \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, \ + PCEP_DEBUG_MODE_PCEPLIB)) \ + DEBUGD(&pcep_g->dbg, "pcep: " fmt, \ + ##__VA_ARGS__); \ + break; \ + case LOG_INFO: \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, \ + PCEP_DEBUG_MODE_PCEPLIB)) \ + DEBUGI(&pcep_g->dbg, "pcep: " fmt, \ + ##__VA_ARGS__); \ + break; \ + case LOG_NOTICE: \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, \ + PCEP_DEBUG_MODE_PCEPLIB)) \ + DEBUGN(&pcep_g->dbg, "pcep: " fmt, \ + ##__VA_ARGS__); \ + break; \ + case LOG_WARNING: \ + case LOG_ERR: \ + default: \ + zlog(priority, "pcep: " fmt, ##__VA_ARGS__); \ + break; \ + } \ + } while (0) + +struct pcep_config_group_opts { + char name[64]; + char tcp_md5_auth[PCEP_MD5SIG_MAXKEYLEN]; + struct ipaddr source_ip; + short source_port; + bool draft07; + bool pce_initiated; + int keep_alive_seconds; + int min_keep_alive_seconds; + int max_keep_alive_seconds; + int dead_timer_seconds; + int min_dead_timer_seconds; + int max_dead_timer_seconds; + int pcep_request_time_seconds; + int session_timeout_inteval_seconds; + int delegation_timeout_seconds; +}; + +struct pce_opts { + struct ipaddr addr; + short port; + char pce_name[64]; + struct pcep_config_group_opts config_opts; + uint8_t precedence; /* Multi-PCE precedence */ +}; + +struct pcc_opts { + struct ipaddr addr; + short port; + short msd; +}; + +/* Encapsulate the pce_opts with needed CLI information */ +struct pce_opts_cli { + struct pce_opts pce_opts; + char config_group_name[64]; + /* These are the values configured in the pcc-peer sub-commands. + * These need to be stored for later merging. Notice, it could + * be that not all of them are set. */ + struct pcep_config_group_opts pce_config_group_opts; + /* The pce_opts->config_opts will be a merge of the default values, + * optional config_group values (which overwrite default values), + * and any values configured in the pce sub-commands (which overwrite + * both default and config_group values). This flag indicates of the + * values need to be merged or not. */ + bool merged; +}; + +struct lsp_nb_key { + uint32_t color; + struct ipaddr endpoint; + uint32_t preference; +}; + +struct sid_mpls { + mpls_label_t label; + uint8_t traffic_class; + bool is_bottom; + uint8_t ttl; +}; + +struct pcep_caps { + bool is_stateful; + /* If we know the objective functions supported by the PCE. + * If we don't know, it doesn't mean the PCE doesn't support any */ + bool supported_ofs_are_known; + /* Defined if we know which objective funtions are supported by the PCE. + * One bit per objective function, the bit index being equal to + * enum pcep_objfun_type values: bit 0 is not used, bit 1 is + * PCEP_OBJFUN_MCP, up to bit 17 that is PCEP_OBJFUN_MSN */ + uint32_t supported_ofs; +}; + +union sid { + uint32_t value; + struct sid_mpls mpls; +}; + +struct nai { + /* NAI type */ + enum pcep_sr_subobj_nai type; + /* Local IP address*/ + struct ipaddr local_addr; + /* Local interface identifier if the NAI is an unnumbered adjacency */ + uint32_t local_iface; + /* Remote address if the NAI is an adjacency */ + struct ipaddr remote_addr; + /* Remote interface identifier if the NAI is an unnumbered adjacency */ + uint32_t remote_iface; +}; + +struct path_hop { + /* Pointer to the next hop in the path */ + struct path_hop *next; + /* Indicateif this ia a loose or strict hop */ + bool is_loose; + /* Indicate if there is an SID for the hop */ + bool has_sid; + /* Indicate if the hop as a MPLS label */ + bool is_mpls; + /* Indicate if the MPLS label has extra attributes (TTL, class..)*/ + bool has_attribs; + /* Hop's SID if available */ + union sid sid; + /* Indicate if there is a NAI for this hop */ + bool has_nai; + /* NAI if available */ + struct nai nai; +}; + +struct path_metric { + /* Pointer to the next metric */ + struct path_metric *next; + /* The metric type */ + enum pcep_metric_types type; + /* If the metric should be enforced */ + bool enforce; + /* If the metric value is bound (a maximum) */ + bool is_bound; + /* If the metric value is computed */ + bool is_computed; + /* The metric value */ + float value; +}; + +struct path { + /* Both the nbkey and the plspid are keys comming from the PCC, + but the PCE is only using the plspid. The missing key is looked up by + the PCC so we always have both */ + + /* The northbound key identifying this path */ + struct lsp_nb_key nbkey; + /* The generated unique PLSP identifier for this path. + See draft-ietf-pce-stateful-pce */ + uint32_t plsp_id; + + /* The transport address the path is comming from, PCE or PCC*/ + struct ipaddr sender; + /* The pcc protocol address, must be the same family as the endpoint */ + struct ipaddr pcc_addr; + + /* The identifier of the PCC the path is for/from. If 0 it is undefined, + meaning it hasn't be set yet or is for all the PCC */ + int pcc_id; + + /* The origin of the path creation */ + enum srte_protocol_origin create_origin; + /* The origin of the path modification */ + enum srte_protocol_origin update_origin; + /* The identifier of the entity that originated the path */ + const char *originator; + /* The type of the path, for PCE initiated or updated path it is always + SRTE_CANDIDATE_TYPE_DYNAMIC */ + enum srte_candidate_type type; + + /* The following data comes from either the PCC or the PCE if available + */ + + /* Path's binding SID */ + mpls_label_t binding_sid; + /* The name of the path */ + const char *name; + /* The request identifier from the PCE, when getting a path from the + PCE. See draft-ietf-pce-stateful-pce */ + uint32_t srp_id; + /* The request identifier from the PCC , when getting a path from the + PCE after a computation request. See rfc5440, section-7.4 */ + uint32_t req_id; + /* The operational status of the path */ + enum pcep_lsp_operational_status status; + /* If true, the receiver (PCC) must remove the path. + See draft-ietf-pce-pce-initiated-lsp */ + bool do_remove; + /* Indicate the given path was removed by the PCC. + See draft-ietf-pce-stateful-pce, section-7.3, flag R */ + bool was_removed; + /* Indicate the path is part of the synchronization process. + See draft-ietf-pce-stateful-pce, section-7.3, flag S */ + bool is_synching; + /* Indicate if the path bandwidth requirment is defined */ + bool has_bandwidth; + /* Indicate if the bandwidth requirment should be enforced */ + bool enforce_bandwidth; + /* Path required bandwidth if defined */ + float bandwidth; + /* Specify the list of hop defining the path */ + struct path_hop *first_hop; + /* Specify the list of metrics */ + struct path_metric *first_metric; + /* Indicate if the path has a PCC-defined objective function */ + bool has_pcc_objfun; + /* Indicate the PCC-defined objective function is required */ + bool enforce_pcc_objfun; + /* PCC-defined Objective Function */ + enum objfun_type pcc_objfun; + /* Indicate if the path has a PCE-defined objective function */ + bool has_pce_objfun; + /* PCE-defined Objective Function */ + enum objfun_type pce_objfun; + /* Indicate if some affinity filters are defined */ + bool has_affinity_filters; + /* Affinity attribute filters indexed by enum affinity_filter_type - 1 + */ + uint32_t affinity_filters[MAX_AFFINITY_FILTER_TYPE]; + + /* The following data need to be specialized for a given PCE */ + + /* Indicate the path is delegated to the PCE. + See draft-ietf-pce-stateful-pce, section-7.3, flag D */ + bool is_delegated; + /* Indicate if the PCE wants the path to get active. + See draft-ietf-pce-stateful-pce, section-7.3, flag A */ + bool go_active; + /* Indicate the given path was created by the PCE, + See draft-ietf-pce-pce-initiated-lsp, section-5.3.1, flag C */ + bool was_created; + + /* The following data is defined for comnputation replies */ + + /* Indicate that no path could be computed */ + bool no_path; +}; + +struct pcep_glob { + struct debug dbg; + struct thread_master *master; + struct frr_pthread *fpt; + uint8_t num_pce_opts_cli; + struct pce_opts_cli *pce_opts_cli[MAX_PCE]; + uint8_t num_config_group_opts; + struct pcep_config_group_opts *config_group_opts[MAX_PCE]; +}; + +extern struct pcep_glob *pcep_g; + +struct pcep_error { + struct path *path; + int error_type; + int error_value; + /* Rfc 8281 PcInitiated error on bad values */ +#define ERROR_19_1 1 +#define ERROR_19_3 2 +#define ERROR_19_9 3 +}; + +/* Path Helper Functions */ +struct path *pcep_new_path(void); +struct path_hop *pcep_new_hop(void); +struct path_metric *pcep_new_metric(void); +struct path *pcep_copy_path(struct path *path); +void pcep_free_path(struct path *path); + + +#endif // _PATH_PCEP_H_ diff --git a/pathd/path_pcep_cli.c b/pathd/path_pcep_cli.c new file mode 100644 index 0000000..d2b49a7 --- /dev/null +++ b/pathd/path_pcep_cli.c @@ -0,0 +1,2064 @@ +/* + * Copyright (C) 2020 Volta Networks, Inc + * Brady Johnson + * + * 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 + */ + +#include <zebra.h> +#include "pceplib/pcep_utils_counters.h" +#include "pceplib/pcep_session_logic.h" + +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "lib/version.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" +#include "termtable.h" + +#include "pathd/pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_cli.h" +#include "pathd/path_pcep_controller.h" +#include "pathd/path_pcep_debug.h" +#include "pathd/path_pcep_lib.h" +#include "pathd/path_pcep_pcc.h" + +#ifndef VTYSH_EXTRACT_PL +#include "pathd/path_pcep_cli_clippy.c" +#endif + +#define DEFAULT_PCE_PRECEDENCE 255 +#define DEFAULT_PCC_MSD 4 +#define DEFAULT_SR_DRAFT07 false +#define DEFAULT_PCE_INITIATED false +#define DEFAULT_TIMER_KEEP_ALIVE 30 +#define DEFAULT_TIMER_KEEP_ALIVE_MIN 1 +#define DEFAULT_TIMER_KEEP_ALIVE_MAX 255 +#define DEFAULT_TIMER_DEADTIMER 120 +#define DEFAULT_TIMER_DEADTIMER_MIN 4 +#define DEFAULT_TIMER_DEADTIMER_MAX 255 +#define DEFAULT_TIMER_PCEP_REQUEST 30 +#define DEFAULT_TIMER_SESSION_TIMEOUT_INTERVAL 30 +#define DEFAULT_DELEGATION_TIMEOUT_INTERVAL 10 + +/* CLI Function declarations */ +static int pcep_cli_debug_config_write(struct vty *vty); +static int pcep_cli_debug_set_all(uint32_t flags, bool set); +static int pcep_cli_pcep_config_write(struct vty *vty); +static int pcep_cli_pcc_config_write(struct vty *vty); +static int pcep_cli_pce_config_write(struct vty *vty); +static int pcep_cli_pcep_pce_config_write(struct vty *vty); + +/* Internal Util Function declarations */ +static struct pce_opts_cli *pcep_cli_find_pce(const char *pce_name); +static bool pcep_cli_add_pce(struct pce_opts_cli *pce_opts_cli); +static struct pce_opts_cli *pcep_cli_create_pce_opts(const char *name); +static void pcep_cli_delete_pce(const char *pce_name); +static void +pcep_cli_merge_pcep_pce_config_options(struct pce_opts_cli *pce_opts_cli); +static struct pcep_config_group_opts * +pcep_cli_find_pcep_pce_config(const char *group_name); +static bool +pcep_cli_add_pcep_pce_config(struct pcep_config_group_opts *config_group_opts); +static struct pcep_config_group_opts * +pcep_cli_create_pcep_pce_config(const char *group_name); +static bool pcep_cli_is_pcep_pce_config_used(const char *group_name); +static void pcep_cli_delete_pcep_pce_config(const char *group_name); +static int pcep_cli_print_pce_config(struct pcep_config_group_opts *group_opts, + char *buf, size_t buf_len); +static void print_pcep_capabilities(char *buf, size_t buf_len, + pcep_configuration *config); +static void print_pcep_session(struct vty *vty, struct pce_opts *pce_opts, + struct pcep_pcc_info *pcc_info); +static bool pcep_cli_pcc_has_pce(const char *pce_name); +static void pcep_cli_add_pce_connection(struct pce_opts *pce_opts); +static void pcep_cli_remove_pce_connection(struct pce_opts *pce_opts); +static int path_pcep_cli_pcc_pcc_peer_delete(struct vty *vty, + const char *peer_name, + const char *precedence_str, + long precedence); + +/* + * Globals. + */ + +static const char PCEP_VTYSH_ARG_ADDRESS[] = "address"; +static const char PCEP_VTYSH_ARG_SOURCE_ADDRESS[] = "source-address"; +static const char PCEP_VTYSH_ARG_IP[] = "ip"; +static const char PCEP_VTYSH_ARG_IPV6[] = "ipv6"; +static const char PCEP_VTYSH_ARG_PORT[] = "port"; +static const char PCEP_VTYSH_ARG_PRECEDENCE[] = "precedence"; +static const char PCEP_VTYSH_ARG_MSD[] = "msd"; +static const char PCEP_VTYSH_ARG_KEEP_ALIVE[] = "keep-alive"; +static const char PCEP_VTYSH_ARG_TIMER[] = "timer"; +static const char PCEP_VTYSH_ARG_KEEP_ALIVE_MIN[] = "min-peer-keep-alive"; +static const char PCEP_VTYSH_ARG_KEEP_ALIVE_MAX[] = "max-peer-keep-alive"; +static const char PCEP_VTYSH_ARG_DEAD_TIMER[] = "dead-timer"; +static const char PCEP_VTYSH_ARG_DEAD_TIMER_MIN[] = "min-peer-dead-timer"; +static const char PCEP_VTYSH_ARG_DEAD_TIMER_MAX[] = "max-peer-dead-timer"; +static const char PCEP_VTYSH_ARG_PCEP_REQUEST[] = "pcep-request"; +static const char PCEP_VTYSH_ARG_SESSION_TIMEOUT[] = "session-timeout-interval"; +static const char PCEP_VTYSH_ARG_DELEGATION_TIMEOUT[] = "delegation-timeout"; +static const char PCEP_VTYSH_ARG_SR_DRAFT07[] = "sr-draft07"; +static const char PCEP_VTYSH_ARG_PCE_INIT[] = "pce-initiated"; +static const char PCEP_VTYSH_ARG_TCP_MD5[] = "tcp-md5-auth"; +static const char PCEP_VTYSH_ARG_BASIC[] = "basic"; +static const char PCEP_VTYSH_ARG_PATH[] = "path"; +static const char PCEP_VTYSH_ARG_MESSAGE[] = "message"; +static const char PCEP_VTYSH_ARG_PCEPLIB[] = "pceplib"; +static const char PCEP_CLI_CAP_STATEFUL[] = " [Stateful PCE]"; +static const char PCEP_CLI_CAP_INCL_DB_VER[] = " [Include DB version]"; +static const char PCEP_CLI_CAP_LSP_TRIGGERED[] = " [LSP Triggered Resync]"; +static const char PCEP_CLI_CAP_LSP_DELTA[] = " [LSP Delta Sync]"; +static const char PCEP_CLI_CAP_PCE_TRIGGERED[] = + " [PCE triggered Initial Sync]"; +static const char PCEP_CLI_CAP_SR_TE_PST[] = " [SR TE PST]"; +static const char PCEP_CLI_CAP_PCC_RESOLVE_NAI[] = + " [PCC can resolve NAI to SID]"; +static const char PCEP_CLI_CAP_PCC_INITIATED[] = " [PCC Initiated LSPs]"; +static const char PCEP_CLI_CAP_PCC_PCE_INITIATED[] = + " [PCC and PCE Initiated LSPs]"; + +struct pce_connections { + int num_connections; + struct pce_opts *connections[MAX_PCC]; +}; + +struct pce_connections pce_connections_g = {.num_connections = 0}; + +/* Default PCE group that all PCE-Groups and PCEs will inherit from */ +struct pcep_config_group_opts default_pcep_config_group_opts_g = { + .name = "default", + .tcp_md5_auth = "\0", + .draft07 = DEFAULT_SR_DRAFT07, + .pce_initiated = DEFAULT_PCE_INITIATED, + .keep_alive_seconds = DEFAULT_TIMER_KEEP_ALIVE, + .min_keep_alive_seconds = DEFAULT_TIMER_KEEP_ALIVE_MIN, + .max_keep_alive_seconds = DEFAULT_TIMER_KEEP_ALIVE_MAX, + .dead_timer_seconds = DEFAULT_TIMER_DEADTIMER, + .min_dead_timer_seconds = DEFAULT_TIMER_DEADTIMER_MIN, + .max_dead_timer_seconds = DEFAULT_TIMER_DEADTIMER_MAX, + .pcep_request_time_seconds = DEFAULT_TIMER_PCEP_REQUEST, + .session_timeout_inteval_seconds = + DEFAULT_TIMER_SESSION_TIMEOUT_INTERVAL, + .delegation_timeout_seconds = DEFAULT_DELEGATION_TIMEOUT_INTERVAL, + .source_port = DEFAULT_PCEP_TCP_PORT, + .source_ip.ipa_type = IPADDR_NONE, +}; + +/* Used by PCEP_PCE_CONFIG_NODE sub-commands to operate on the current pce group + */ +struct pcep_config_group_opts *current_pcep_config_group_opts_g = NULL; +/* Used by PCEP_PCE_NODE sub-commands to operate on the current pce opts */ +struct pce_opts_cli *current_pce_opts_g = NULL; +short pcc_msd_g = DEFAULT_PCC_MSD; +bool pcc_msd_configured_g = false; + +static struct cmd_node pcep_node = { + .name = "srte pcep", + .node = PCEP_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-pcep)# " +}; + +static struct cmd_node pcep_pcc_node = { + .name = "srte pcep pcc", + .node = PCEP_PCC_NODE, + .parent_node = PCEP_NODE, + .prompt = "%s(config-sr-te-pcep-pcc)# " +}; + +static struct cmd_node pcep_pce_node = { + .name = "srte pcep pce", + .node = PCEP_PCE_NODE, + .parent_node = PCEP_NODE, + .prompt = "%s(config-sr-te-pcep-pce)# " +}; + +static struct cmd_node pcep_pce_config_node = { + .name = "srte pcep pce-config", + .node = PCEP_PCE_CONFIG_NODE, + .parent_node = PCEP_NODE, + .prompt = "%s(pce-sr-te-pcep-pce-config)# " +}; + +/* Common code used in VTYSH processing for int values */ +#define PCEP_VTYSH_INT_ARG_CHECK(arg_str, arg_val, arg_store, min_value, \ + max_value) \ + if (arg_str != NULL) { \ + if (arg_val <= min_value || arg_val >= max_value) { \ + vty_out(vty, \ + "%% Invalid value %ld in range [%d - %d]", \ + arg_val, min_value, max_value); \ + return CMD_WARNING; \ + } \ + arg_store = arg_val; \ + } + +#define MERGE_COMPARE_CONFIG_GROUP_VALUE(config_param, not_set_value) \ + pce_opts_cli->pce_opts.config_opts.config_param = \ + pce_opts_cli->pce_config_group_opts.config_param; \ + if (pce_opts_cli->pce_config_group_opts.config_param \ + == not_set_value) { \ + pce_opts_cli->pce_opts.config_opts.config_param = \ + ((pce_config != NULL \ + && pce_config->config_param != not_set_value) \ + ? pce_config->config_param \ + : default_pcep_config_group_opts_g \ + .config_param); \ + } + +/* + * Internal Util functions + */ + +/* Check if a pce_opts_cli already exists based on its name and return it, + * return NULL otherwise */ +static struct pce_opts_cli *pcep_cli_find_pce(const char *pce_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + struct pce_opts_cli *pce_rhs_cli = pcep_g->pce_opts_cli[i]; + if (pce_rhs_cli != NULL) { + if (strcmp(pce_name, pce_rhs_cli->pce_opts.pce_name) + == 0) { + return pce_rhs_cli; + } + } + } + + return NULL; +} + +/* Add a new pce_opts_cli to pcep_g, return false if MAX_PCES, true otherwise */ +static bool pcep_cli_add_pce(struct pce_opts_cli *pce_opts_cli) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->pce_opts_cli[i] == NULL) { + pcep_g->pce_opts_cli[i] = pce_opts_cli; + pcep_g->num_pce_opts_cli++; + return true; + } + } + + return false; +} + +/* Create a new pce opts_cli */ +static struct pce_opts_cli *pcep_cli_create_pce_opts(const char *name) +{ + struct pce_opts_cli *pce_opts_cli = + XCALLOC(MTYPE_PCEP, sizeof(struct pce_opts_cli)); + strlcpy(pce_opts_cli->pce_opts.pce_name, name, + sizeof(pce_opts_cli->pce_opts.pce_name)); + pce_opts_cli->pce_opts.port = PCEP_DEFAULT_PORT; + + return pce_opts_cli; +} + +static void pcep_cli_delete_pce(const char *pce_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->pce_opts_cli[i] != NULL) { + if (strcmp(pcep_g->pce_opts_cli[i]->pce_opts.pce_name, + pce_name) + == 0) { + XFREE(MTYPE_PCEP, pcep_g->pce_opts_cli[i]); + pcep_g->pce_opts_cli[i] = NULL; + pcep_g->num_pce_opts_cli--; + return; + } + } + } +} + +static void +pcep_cli_merge_pcep_pce_config_options(struct pce_opts_cli *pce_opts_cli) +{ + if (pce_opts_cli->merged == true) { + return; + } + + struct pcep_config_group_opts *pce_config = + pcep_cli_find_pcep_pce_config(pce_opts_cli->config_group_name); + + /* Configuration priorities: + * 1) pce_opts->config_opts, if present, overwrite pce_config + * config_opts 2) pce_config config_opts, if present, overwrite + * default config_opts 3) If neither pce_opts->config_opts nor + * pce_config config_opts are set, then the default config_opts value + * will be used. + */ + + const char *tcp_md5_auth_str = + pce_opts_cli->pce_config_group_opts.tcp_md5_auth; + if (pce_opts_cli->pce_config_group_opts.tcp_md5_auth[0] == '\0') { + if (pce_config != NULL && pce_config->tcp_md5_auth[0] != '\0') { + tcp_md5_auth_str = pce_config->tcp_md5_auth; + } else { + tcp_md5_auth_str = + default_pcep_config_group_opts_g.tcp_md5_auth; + } + } + strlcpy(pce_opts_cli->pce_opts.config_opts.tcp_md5_auth, + tcp_md5_auth_str, + sizeof(pce_opts_cli->pce_opts.config_opts.tcp_md5_auth)); + + struct ipaddr *source_ip = + &pce_opts_cli->pce_config_group_opts.source_ip; + if (pce_opts_cli->pce_config_group_opts.source_ip.ipa_type + == IPADDR_NONE) { + if (pce_config != NULL + && pce_config->source_ip.ipa_type != IPADDR_NONE) { + source_ip = &pce_config->source_ip; + } else { + source_ip = &default_pcep_config_group_opts_g.source_ip; + } + } + memcpy(&pce_opts_cli->pce_opts.config_opts.source_ip, source_ip, + sizeof(struct ipaddr)); + + MERGE_COMPARE_CONFIG_GROUP_VALUE(draft07, false); + MERGE_COMPARE_CONFIG_GROUP_VALUE(pce_initiated, false); + MERGE_COMPARE_CONFIG_GROUP_VALUE(keep_alive_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(min_keep_alive_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(max_keep_alive_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(dead_timer_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(min_dead_timer_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(max_dead_timer_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(pcep_request_time_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(session_timeout_inteval_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(delegation_timeout_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(source_port, 0); + + pce_opts_cli->merged = true; +} + +/* Check if a pcep_config_group_opts already exists based on its name and return + * it, return NULL otherwise */ +static struct pcep_config_group_opts * +pcep_cli_find_pcep_pce_config(const char *group_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + struct pcep_config_group_opts *pcep_pce_config_rhs = + pcep_g->config_group_opts[i]; + if (pcep_pce_config_rhs != NULL) { + if (strcmp(group_name, pcep_pce_config_rhs->name) + == 0) { + return pcep_pce_config_rhs; + } + } + } + + return NULL; +} + +/* Add a new pcep_config_group_opts to pcep_g, return false if MAX_PCE, + * true otherwise */ +static bool pcep_cli_add_pcep_pce_config( + struct pcep_config_group_opts *pcep_config_group_opts) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->config_group_opts[i] == NULL) { + pcep_g->config_group_opts[i] = pcep_config_group_opts; + pcep_g->num_config_group_opts++; + return true; + } + } + + return false; +} + +/* Create a new pce group, inheriting its values from the default pce group */ +static struct pcep_config_group_opts * +pcep_cli_create_pcep_pce_config(const char *group_name) +{ + struct pcep_config_group_opts *pcep_config_group_opts = + XCALLOC(MTYPE_PCEP, sizeof(struct pcep_config_group_opts)); + strlcpy(pcep_config_group_opts->name, group_name, + sizeof(pcep_config_group_opts->name)); + + return pcep_config_group_opts; +} + +/* Iterate the pce_opts and return true if the pce-group-name is referenced, + * false otherwise. */ +static bool pcep_cli_is_pcep_pce_config_used(const char *group_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->pce_opts_cli[i] != NULL) { + if (strcmp(pcep_g->pce_opts_cli[i]->config_group_name, + group_name) + == 0) { + return true; + } + } + } + + return false; +} + +static void pcep_cli_delete_pcep_pce_config(const char *group_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->config_group_opts[i] != NULL) { + if (strcmp(pcep_g->config_group_opts[i]->name, + group_name) + == 0) { + XFREE(MTYPE_PCEP, pcep_g->config_group_opts[i]); + pcep_g->config_group_opts[i] = NULL; + pcep_g->num_config_group_opts--; + return; + } + } + } +} + +static bool pcep_cli_pcc_has_pce(const char *pce_name) +{ + for (int i = 0; i < MAX_PCC; i++) { + struct pce_opts *pce_opts = pce_connections_g.connections[i]; + if (pce_opts == NULL) { + continue; + } + + if (strcmp(pce_opts->pce_name, pce_name) == 0) { + return true; + } + } + + return false; +} + +static void pcep_cli_add_pce_connection(struct pce_opts *pce_opts) +{ + for (int i = 0; i < MAX_PCC; i++) { + if (pce_connections_g.connections[i] == NULL) { + pce_connections_g.num_connections++; + pce_connections_g.connections[i] = pce_opts; + return; + } + } +} + +static void pcep_cli_remove_pce_connection(struct pce_opts *pce_opts) +{ + for (int i = 0; i < MAX_PCC; i++) { + if (pce_connections_g.connections[i] == pce_opts) { + pce_connections_g.num_connections--; + pce_connections_g.connections[i] = NULL; + return; + } + } +} + +/* + * VTY command implementations + */ + +static int path_pcep_cli_debug(struct vty *vty, const char *no_str, + const char *basic_str, const char *path_str, + const char *message_str, const char *pceplib_str) +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + bool no = (no_str != NULL); + + DEBUG_MODE_SET(&pcep_g->dbg, mode, !no); + + if (basic_str != NULL) { + DEBUG_FLAGS_SET(&pcep_g->dbg, PCEP_DEBUG_MODE_BASIC, !no); + } + if (path_str != NULL) { + DEBUG_FLAGS_SET(&pcep_g->dbg, PCEP_DEBUG_MODE_PATH, !no); + } + if (message_str != NULL) { + DEBUG_FLAGS_SET(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEP, !no); + } + if (pceplib_str != NULL) { + DEBUG_FLAGS_SET(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEPLIB, !no); + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_show_srte_pcep_counters(struct vty *vty) +{ + int i, j, row; + time_t diff_time; + struct tm tm_info; + char tm_buffer[26]; + struct counters_group *group; + struct counters_subgroup *subgroup; + struct counter *counter; + const char *group_name, *empty_string = ""; + struct ttable *tt; + char *table; + + group = pcep_ctrl_get_counters(pcep_g->fpt, 1); + + if (group == NULL) { + vty_out(vty, "No counters to display.\n\n"); + return CMD_SUCCESS; + } + + diff_time = time(NULL) - group->start_time; + localtime_r(&group->start_time, &tm_info); + strftime(tm_buffer, sizeof(tm_buffer), "%Y-%m-%d %H:%M:%S", &tm_info); + + vty_out(vty, "PCEP counters since %s (%uh %um %us):\n", tm_buffer, + (uint32_t)(diff_time / 3600), (uint32_t)((diff_time / 60) % 60), + (uint32_t)(diff_time % 60)); + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Group|Name|Value"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + for (row = 0, i = 0; i <= group->num_subgroups; i++) { + subgroup = group->subgroups[i]; + if (subgroup != NULL) { + group_name = subgroup->counters_subgroup_name; + for (j = 0; j <= subgroup->num_counters; j++) { + counter = subgroup->counters[j]; + if (counter != NULL) { + ttable_add_row(tt, "%s|%s|%u", + group_name, + counter->counter_name, + counter->counter_value); + row++; + group_name = empty_string; + } + } + ttable_rowseps(tt, row, BOTTOM, true, '-'); + } + } + + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + + ttable_del(tt); + + pcep_lib_free_counters(group); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcep_pce_config(struct vty *vty, + const char *pcep_pce_config) +{ + struct pcep_config_group_opts *pce_config = + pcep_cli_find_pcep_pce_config(pcep_pce_config); + if (pce_config == NULL) { + pce_config = pcep_cli_create_pcep_pce_config(pcep_pce_config); + if (pcep_cli_add_pcep_pce_config(pce_config) == false) { + vty_out(vty, + "%% Cannot create pce-config, as the Maximum limit of %d pce-config has been reached.\n", + MAX_PCE); + XFREE(MTYPE_PCEP, pce_config); + return CMD_WARNING; + } + } else { + vty_out(vty, + "Notice: changes to this pce-config will not affect PCEs already configured with this group\n"); + } + + current_pcep_config_group_opts_g = pce_config; + vty->node = PCEP_PCE_CONFIG_NODE; + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcep_pce_config_delete(struct vty *vty, + const char *pcep_pce_config) +{ + struct pcep_config_group_opts *pce_config = + pcep_cli_find_pcep_pce_config(pcep_pce_config); + if (pce_config == NULL) { + vty_out(vty, + "%% Cannot delete pce-config, since it does not exist.\n"); + return CMD_WARNING; + } + + if (pcep_cli_is_pcep_pce_config_used(pce_config->name)) { + vty_out(vty, + "%% Cannot delete pce-config, since it is in use by a peer.\n"); + return CMD_WARNING; + } + + pcep_cli_delete_pcep_pce_config(pce_config->name); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_show_srte_pcep_pce_config(struct vty *vty, + const char *pcep_pce_config) +{ + char buf[1024] = ""; + + /* Only show 1 Peer config group */ + struct pcep_config_group_opts *group_opts; + if (pcep_pce_config != NULL) { + if (strcmp(pcep_pce_config, "default") == 0) { + group_opts = &default_pcep_config_group_opts_g; + } else { + group_opts = + pcep_cli_find_pcep_pce_config(pcep_pce_config); + } + if (group_opts == NULL) { + vty_out(vty, "%% pce-config [%s] does not exist.\n", + pcep_pce_config); + return CMD_WARNING; + } + + vty_out(vty, "pce-config: %s\n", group_opts->name); + pcep_cli_print_pce_config(group_opts, buf, sizeof(buf)); + vty_out(vty, "%s", buf); + return CMD_SUCCESS; + } + + /* Show all Peer config groups */ + for (int i = 0; i < MAX_PCE; i++) { + group_opts = pcep_g->config_group_opts[i]; + if (group_opts == NULL) { + continue; + } + + vty_out(vty, "pce-config: %s\n", group_opts->name); + pcep_cli_print_pce_config(group_opts, buf, sizeof(buf)); + vty_out(vty, "%s", buf); + buf[0] = 0; + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pce(struct vty *vty, const char *pce_peer_name) +{ + /* If it already exists, it will be updated in the sub-commands */ + struct pce_opts_cli *pce_opts_cli = pcep_cli_find_pce(pce_peer_name); + if (pce_opts_cli == NULL) { + pce_opts_cli = pcep_cli_create_pce_opts(pce_peer_name); + + if (!pcep_cli_add_pce(pce_opts_cli)) { + vty_out(vty, + "%% Cannot create PCE, as the Maximum limit of %d PCEs has been reached.\n", + MAX_PCE); + XFREE(MTYPE_PCEP, pce_opts_cli); + return CMD_WARNING; + } + } + + current_pce_opts_g = pce_opts_cli; + vty->node = PCEP_PCE_NODE; + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pce_delete(struct vty *vty, const char *pce_peer_name) +{ + struct pce_opts_cli *pce_opts_cli = pcep_cli_find_pce(pce_peer_name); + if (pce_opts_cli == NULL) { + vty_out(vty, "%% PCC peer does not exist.\n"); + return CMD_WARNING; + } + + /* To better work with frr-reload, go ahead and delete it if its in use + */ + if (pcep_cli_pcc_has_pce(pce_peer_name)) { + vty_out(vty, + "%% Notice: the pce is in use by a PCC, also disconnecting.\n"); + path_pcep_cli_pcc_pcc_peer_delete(vty, pce_peer_name, NULL, 0); + } + + pcep_cli_delete_pce(pce_peer_name); + + return CMD_SUCCESS; +} + +/* Internal Util func to show an individual PCE, + * only used by path_pcep_cli_show_srte_pcep_pce() */ +static void show_pce_peer(struct vty *vty, struct pce_opts_cli *pce_opts_cli) +{ + struct pce_opts *pce_opts = &pce_opts_cli->pce_opts; + vty_out(vty, "PCE: %s\n", pce_opts->pce_name); + + /* Remote PCE IP address */ + if (IS_IPADDR_V6(&pce_opts->addr)) { + vty_out(vty, " %s %s %pI6 %s %d\n", PCEP_VTYSH_ARG_ADDRESS, + PCEP_VTYSH_ARG_IPV6, &pce_opts->addr.ipaddr_v6, + PCEP_VTYSH_ARG_PORT, pce_opts->port); + } else { + vty_out(vty, " %s %s %pI4 %s %d\n", PCEP_VTYSH_ARG_ADDRESS, + PCEP_VTYSH_ARG_IP, &pce_opts->addr.ipaddr_v4, + PCEP_VTYSH_ARG_PORT, pce_opts->port); + } + + if (pce_opts_cli->config_group_name[0] != '\0') { + vty_out(vty, " pce-config: %s\n", + pce_opts_cli->config_group_name); + } + + char buf[1024] = ""; + pcep_cli_print_pce_config(&pce_opts->config_opts, buf, sizeof(buf)); + vty_out(vty, "%s", buf); +} + +static int path_pcep_cli_show_srte_pcep_pce(struct vty *vty, + const char *pce_peer) +{ + /* Only show 1 PCE */ + struct pce_opts_cli *pce_opts_cli; + if (pce_peer != NULL) { + pce_opts_cli = pcep_cli_find_pce(pce_peer); + if (pce_opts_cli == NULL) { + vty_out(vty, "%% PCE [%s] does not exist.\n", pce_peer); + return CMD_WARNING; + } + + pcep_cli_merge_pcep_pce_config_options(pce_opts_cli); + show_pce_peer(vty, pce_opts_cli); + + return CMD_SUCCESS; + } + + /* Show all PCEs */ + for (int i = 0; i < MAX_PCE; i++) { + pce_opts_cli = pcep_g->pce_opts_cli[i]; + if (pce_opts_cli == NULL) { + continue; + } + + pcep_cli_merge_pcep_pce_config_options(pce_opts_cli); + show_pce_peer(vty, pce_opts_cli); + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_sr_draft07(struct vty *vty) +{ + struct pcep_config_group_opts *pce_config = NULL; + + if (vty->node == PCEP_PCE_NODE) { + /* TODO need to see if the pce is in use, and reset the + * connection */ + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + pce_config->draft07 = true; + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_pce_initiated(struct vty *vty) +{ + struct pcep_config_group_opts *pce_config = NULL; + + if (vty->node == PCEP_PCE_NODE) { + /* TODO need to see if the pce is in use, and reset the + * connection */ + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + pce_config->pce_initiated = true; + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_tcp_md5_auth(struct vty *vty, + const char *tcp_md5_auth) +{ + struct pcep_config_group_opts *pce_config = NULL; + + if (vty->node == PCEP_PCE_NODE) { + /* TODO need to see if the pce is in use, and reset the + * connection */ + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + strlcpy(pce_config->tcp_md5_auth, tcp_md5_auth, + sizeof(pce_config->tcp_md5_auth)); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_address(struct vty *vty, const char *ip_str, + struct in_addr *ip, const char *ipv6_str, + struct in6_addr *ipv6, + const char *port_str, long port) +{ + struct pce_opts *pce_opts = NULL; + if (vty->node == PCEP_PCE_NODE) { + /* TODO need to see if the pce is in use, and reset the + * connection */ + pce_opts = ¤t_pce_opts_g->pce_opts; + current_pce_opts_g->merged = false; + } else { + return CMD_ERR_NO_MATCH; + } + + if (ipv6_str != NULL) { + pce_opts->addr.ipa_type = IPADDR_V6; + memcpy(&pce_opts->addr.ipaddr_v6, ipv6, + sizeof(struct in6_addr)); + } else if (ip_str != NULL) { + pce_opts->addr.ipa_type = IPADDR_V4; + memcpy(&pce_opts->addr.ipaddr_v4, ip, sizeof(struct in_addr)); + } else { + return CMD_ERR_NO_MATCH; + } + + /* Handle the optional port */ + pce_opts->port = PCEP_DEFAULT_PORT; + PCEP_VTYSH_INT_ARG_CHECK(port_str, port, pce_opts->port, 0, 65535); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_source_address(struct vty *vty, + const char *ip_str, + struct in_addr *ip, + const char *ipv6_str, + struct in6_addr *ipv6, + const char *port_str, long port) +{ + struct pcep_config_group_opts *pce_config = NULL; + if (vty->node == PCEP_PCE_NODE) { + /* TODO need to see if the pce is in use, and reset the + * connection */ + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + /* Handle the optional source IP */ + if (ipv6_str != NULL) { + pce_config->source_ip.ipa_type = IPADDR_V6; + memcpy(&pce_config->source_ip.ipaddr_v6, ipv6, + sizeof(struct in6_addr)); + } else if (ip_str != NULL) { + pce_config->source_ip.ipa_type = IPADDR_V4; + memcpy(&pce_config->source_ip.ipaddr_v4, ip, + sizeof(struct in_addr)); + } + + /* Handle the optional port */ + PCEP_VTYSH_INT_ARG_CHECK(port_str, port, pce_config->source_port, 0, + 65535); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_pcep_pce_config_ref(struct vty *vty, + const char *config_group_name) +{ + if (vty->node == PCEP_PCE_NODE) { + /* TODO need to see if the pce is in use, and reset the + * connection */ + current_pce_opts_g->merged = false; + } else { + return CMD_ERR_NO_MATCH; + } + + struct pcep_config_group_opts *pce_config = + pcep_cli_find_pcep_pce_config(config_group_name); + if (pce_config == NULL) { + vty_out(vty, "%% pce-config [%s] does not exist.\n", + config_group_name); + return CMD_WARNING; + } + + strlcpy(current_pce_opts_g->config_group_name, config_group_name, + sizeof(current_pce_opts_g->config_group_name)); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_timers( + struct vty *vty, const char *keep_alive_str, long keep_alive, + const char *min_peer_keep_alive_str, long min_peer_keep_alive, + const char *max_peer_keep_alive_str, long max_peer_keep_alive, + const char *dead_timer_str, long dead_timer, + const char *min_peer_dead_timer_str, long min_peer_dead_timer, + const char *max_peer_dead_timer_str, long max_peer_dead_timer, + const char *pcep_request_str, long pcep_request, + const char *session_timeout_interval_str, long session_timeout_interval, + const char *delegation_timeout_str, long delegation_timeout) +{ + struct pcep_config_group_opts *pce_config = NULL; + if (vty->node == PCEP_PCE_NODE) { + /* TODO need to see if the pce is in use, and reset the + * connection */ + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + if (min_peer_keep_alive && max_peer_keep_alive) + if (min_peer_keep_alive >= max_peer_keep_alive) { + return CMD_ERR_NO_MATCH; + } + + if (min_peer_dead_timer && max_peer_dead_timer) + if (min_peer_dead_timer >= max_peer_dead_timer) { + return CMD_ERR_NO_MATCH; + } + + /* Handle the arguments */ + PCEP_VTYSH_INT_ARG_CHECK(keep_alive_str, keep_alive, + pce_config->keep_alive_seconds, 0, 64); + PCEP_VTYSH_INT_ARG_CHECK(min_peer_keep_alive_str, min_peer_keep_alive, + pce_config->min_keep_alive_seconds, 0, 256); + PCEP_VTYSH_INT_ARG_CHECK(max_peer_keep_alive_str, max_peer_keep_alive, + pce_config->max_keep_alive_seconds, 0, 256); + PCEP_VTYSH_INT_ARG_CHECK(dead_timer_str, dead_timer, + pce_config->dead_timer_seconds, 3, 256); + PCEP_VTYSH_INT_ARG_CHECK(min_peer_dead_timer_str, min_peer_dead_timer, + pce_config->min_dead_timer_seconds, 3, 256); + PCEP_VTYSH_INT_ARG_CHECK(max_peer_dead_timer_str, max_peer_dead_timer, + pce_config->max_dead_timer_seconds, 3, 256); + PCEP_VTYSH_INT_ARG_CHECK(pcep_request_str, pcep_request, + pce_config->pcep_request_time_seconds, 0, 121); + PCEP_VTYSH_INT_ARG_CHECK( + session_timeout_interval_str, session_timeout_interval, + pce_config->session_timeout_inteval_seconds, 0, 121); + PCEP_VTYSH_INT_ARG_CHECK(delegation_timeout_str, delegation_timeout, + pce_config->delegation_timeout_seconds, 0, 61); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcc(struct vty *vty) +{ + VTY_PUSH_CONTEXT_NULL(PCEP_PCC_NODE); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcc_delete(struct vty *vty) +{ + /* Clear the pce_connections */ + memset(&pce_connections_g, 0, sizeof(pce_connections_g)); + pcc_msd_configured_g = false; + + pcep_ctrl_remove_pcc(pcep_g->fpt, NULL); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcc_pcc_msd(struct vty *vty, const char *msd_str, + long msd) +{ + pcc_msd_configured_g = true; + PCEP_VTYSH_INT_ARG_CHECK(msd_str, msd, pcc_msd_g, 0, 33); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcc_pcc_peer(struct vty *vty, const char *peer_name, + const char *precedence_str, + long precedence) +{ + /* Check if the pcc-peer exists */ + struct pce_opts_cli *pce_opts_cli = pcep_cli_find_pce(peer_name); + if (pce_opts_cli == NULL) { + vty_out(vty, "%% PCE [%s] does not exist.\n", peer_name); + return CMD_WARNING; + } + struct pce_opts *pce_opts = &pce_opts_cli->pce_opts; + + /* Check if the pcc-peer is duplicated */ + if (pcep_cli_pcc_has_pce(peer_name)) { + vty_out(vty, "%% The peer [%s] has already been configured.\n", + peer_name); + return CMD_WARNING; + } + + /* Get the optional precedence argument */ + pce_opts->precedence = DEFAULT_PCE_PRECEDENCE; + PCEP_VTYSH_INT_ARG_CHECK(precedence_str, precedence, + pce_opts->precedence, 0, 256); + + /* Finalize the pce_opts config values */ + pcep_cli_merge_pcep_pce_config_options(pce_opts_cli); + pcep_cli_add_pce_connection(&pce_opts_cli->pce_opts); + + /* Verify the PCE has the IP set */ + struct in6_addr zero_v6_addr; + memset(&zero_v6_addr, 0, sizeof(zero_v6_addr)); + if (memcmp(&pce_opts->addr.ip, &zero_v6_addr, IPADDRSZ(&pce_opts->addr)) + == 0) { + vty_out(vty, + "%% The peer [%s] does not have an IP set and cannot be used until it does.\n", + peer_name); + return CMD_WARNING; + } + + /* Update the pcc_opts with the source ip, port, and msd */ + struct pcc_opts *pcc_opts_copy = + XMALLOC(MTYPE_PCEP, sizeof(struct pcc_opts)); + memcpy(&pcc_opts_copy->addr, + &pce_opts_cli->pce_opts.config_opts.source_ip, + sizeof(pcc_opts_copy->addr)); + pcc_opts_copy->msd = pcc_msd_g; + pcc_opts_copy->port = pce_opts_cli->pce_opts.config_opts.source_port; + if (pcep_ctrl_update_pcc_options(pcep_g->fpt, pcc_opts_copy)) { + return CMD_WARNING; + } + + /* Send a copy of the pce_opts, this one is only used for the CLI */ + struct pce_opts *pce_opts_copy = + XMALLOC(MTYPE_PCEP, sizeof(struct pce_opts)); + memcpy(pce_opts_copy, pce_opts, sizeof(struct pce_opts)); + if (pcep_ctrl_update_pce_options(pcep_g->fpt, pce_opts_copy)) { + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcc_pcc_peer_delete(struct vty *vty, + const char *peer_name, + const char *precedence_str, + long precedence) +{ + /* Check if the pcc-peer is connected to the PCC */ + if (!pcep_cli_pcc_has_pce(peer_name)) { + vty_out(vty, + "%% WARN: The peer [%s] is not connected to the PCC.\n", + peer_name); + return CMD_WARNING; + } + + struct pce_opts_cli *pce_opts_cli = pcep_cli_find_pce(peer_name); + pcep_cli_remove_pce_connection(&pce_opts_cli->pce_opts); + + /* Send a copy of the pce_opts, this one is used for CLI only */ + struct pce_opts *pce_opts_copy = + XMALLOC(MTYPE_PCEP, sizeof(struct pce_opts)); + memcpy(pce_opts_copy, &pce_opts_cli->pce_opts, sizeof(struct pce_opts)); + pcep_ctrl_remove_pcc(pcep_g->fpt, pce_opts_copy); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_show_srte_pcep_pcc(struct vty *vty) +{ + vty_out(vty, "pcc msd %d\n", pcc_msd_g); + + return CMD_SUCCESS; +} + +/* Internal util function to print pcep capabilities to a buffer */ +static void print_pcep_capabilities(char *buf, size_t buf_len, + pcep_configuration *config) +{ + if (config->support_stateful_pce_lsp_update) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_STATEFUL); + } + if (config->support_include_db_version) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_INCL_DB_VER); + } + if (config->support_lsp_triggered_resync) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_LSP_TRIGGERED); + } + if (config->support_lsp_delta_sync) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_LSP_DELTA); + } + if (config->support_pce_triggered_initial_sync) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_PCE_TRIGGERED); + } + if (config->support_sr_te_pst) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_SR_TE_PST); + } + if (config->pcc_can_resolve_nai_to_sid) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_PCC_RESOLVE_NAI); + } +} + +/* Internal util function to print a pcep session */ +static void print_pcep_session(struct vty *vty, struct pce_opts *pce_opts, + struct pcep_pcc_info *pcc_info) +{ + char buf[1024]; + buf[0] = '\0'; + + vty_out(vty, "\nPCE %s\n", pce_opts->pce_name); + + /* PCE IP */ + if (IS_IPADDR_V4(&pce_opts->addr)) { + vty_out(vty, " PCE IP %pI4 port %d\n", + &pce_opts->addr.ipaddr_v4, pce_opts->port); + } else if (IS_IPADDR_V6(&pce_opts->addr)) { + vty_out(vty, " PCE IPv6 %pI6 port %d\n", + &pce_opts->addr.ipaddr_v6, pce_opts->port); + } + + /* PCC IP */ + if (IS_IPADDR_V4(&pcc_info->pcc_addr)) { + vty_out(vty, " PCC IP %pI4 port %d\n", + &pcc_info->pcc_addr.ipaddr_v4, pcc_info->pcc_port); + } else if (IS_IPADDR_V6(&pcc_info->pcc_addr)) { + vty_out(vty, " PCC IPv6 %pI6 port %d\n", + &pcc_info->pcc_addr.ipaddr_v6, pcc_info->pcc_port); + } + vty_out(vty, " PCC MSD %d\n", pcc_info->msd); + + if (pcc_info->status == PCEP_PCC_OPERATING) { + vty_out(vty, " Session Status UP\n"); + } else { + vty_out(vty, " Session Status %s\n", + pcc_status_name(pcc_info->status)); + } + + if (pcc_info->is_best_multi_pce) { + vty_out(vty, " Precedence %d, best candidate\n", + ((pcc_info->precedence > 0) ? pcc_info->precedence + : DEFAULT_PCE_PRECEDENCE)); + } else { + vty_out(vty, " Precedence %d\n", + ((pcc_info->precedence > 0) ? pcc_info->precedence + : DEFAULT_PCE_PRECEDENCE)); + } + vty_out(vty, " Confidence %s\n", + ((pcc_info->previous_best) ? "low" + : "normal")); + + /* PCEPlib pcep session values, get a thread safe copy of the counters + */ + pcep_session *session = + pcep_ctrl_get_pcep_session(pcep_g->fpt, pcc_info->pcc_id); + + /* Config Options values */ + struct pcep_config_group_opts *config_opts = &pce_opts->config_opts; + if (session != NULL) { + vty_out(vty, " Timer: KeepAlive config %d, pce-negotiated %d\n", + config_opts->keep_alive_seconds, + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds); + vty_out(vty, " Timer: DeadTimer config %d, pce-negotiated %d\n", + config_opts->dead_timer_seconds, + session->pcc_config.dead_timer_pce_negotiated_seconds); + } else { + vty_out(vty, " Timer: KeepAlive %d\n", + config_opts->keep_alive_seconds); + vty_out(vty, " Timer: DeadTimer %d\n", + config_opts->dead_timer_seconds); + } + vty_out(vty, " Timer: PcRequest %d\n", + config_opts->pcep_request_time_seconds); + vty_out(vty, " Timer: SessionTimeout Interval %d\n", + config_opts->session_timeout_inteval_seconds); + vty_out(vty, " Timer: Delegation Timeout %d\n", + config_opts->delegation_timeout_seconds); + if (strlen(config_opts->tcp_md5_auth) > 0) { + vty_out(vty, " TCP MD5 Auth Str: %s\n", + config_opts->tcp_md5_auth); + } else { + vty_out(vty, " No TCP MD5 Auth\n"); + } + + if (config_opts->draft07) { + vty_out(vty, " PCE SR Version draft07\n"); + } else { + vty_out(vty, " PCE SR Version draft16 and RFC8408\n"); + } + + vty_out(vty, " Next PcReq ID %d\n", pcc_info->next_reqid); + vty_out(vty, " Next PLSP ID %d\n", pcc_info->next_plspid); + + if (session != NULL) { + if (pcc_info->status == PCEP_PCC_SYNCHRONIZING + || pcc_info->status == PCEP_PCC_OPERATING) { + time_t current_time = time(NULL); + struct tm lt = {0}; + /* Just for the timezone */ + localtime_r(¤t_time, <); + gmtime_r(&session->time_connected, <); + vty_out(vty, + " Connected for %u seconds, since %d-%02d-%02d %02d:%02d:%02d UTC\n", + (uint32_t)(current_time + - session->time_connected), + lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, + lt.tm_hour, lt.tm_min, lt.tm_sec); + } + + /* PCC capabilities */ + buf[0] = '\0'; + int index = 0; + if (config_opts->pce_initiated) { + index += csnprintfrr(buf, sizeof(buf), "%s", + PCEP_CLI_CAP_PCC_PCE_INITIATED); + } else { + index += csnprintfrr(buf, sizeof(buf), "%s", + PCEP_CLI_CAP_PCC_INITIATED); + } + print_pcep_capabilities(buf, sizeof(buf) - index, + &session->pcc_config); + vty_out(vty, " PCC Capabilities:%s\n", buf); + + /* PCE capabilities */ + buf[0] = '\0'; + print_pcep_capabilities(buf, sizeof(buf), &session->pce_config); + if (buf[0] != '\0') { + vty_out(vty, " PCE Capabilities:%s\n", buf); + } + XFREE(MTYPE_PCEP, session); + } else { + vty_out(vty, " Detailed session information not available\n"); + } + + /* Message Counters, get a thread safe copy of the counters */ + struct counters_group *group = + pcep_ctrl_get_counters(pcep_g->fpt, pcc_info->pcc_id); + + if (group != NULL) { + struct counters_subgroup *rx_msgs = + find_subgroup(group, COUNTER_SUBGROUP_ID_RX_MSG); + struct counters_subgroup *tx_msgs = + find_subgroup(group, COUNTER_SUBGROUP_ID_TX_MSG); + + if (rx_msgs != NULL && tx_msgs != NULL) { + vty_out(vty, " PCEP Message Statistics\n"); + vty_out(vty, " %27s %6s\n", "Sent", "Rcvd"); + for (int i = 0; i < rx_msgs->max_counters; i++) { + struct counter *rx_counter = + rx_msgs->counters[i]; + struct counter *tx_counter = + tx_msgs->counters[i]; + if (rx_counter != NULL && tx_counter != NULL) { + vty_out(vty, " %20s: %5d %5d\n", + tx_counter->counter_name, + tx_counter->counter_value, + rx_counter->counter_value); + } + } + vty_out(vty, " %20s: %5d %5d\n", "Total", + subgroup_counters_total(tx_msgs), + subgroup_counters_total(rx_msgs)); + } + pcep_lib_free_counters(group); + } else { + vty_out(vty, " Counters not available\n"); + } + + XFREE(MTYPE_PCEP, pcc_info); +} + +static int path_pcep_cli_show_srte_pcep_session(struct vty *vty, + const char *pcc_peer) +{ + struct pce_opts_cli *pce_opts_cli; + struct pcep_pcc_info *pcc_info; + + /* Only show 1 PCEP session */ + if (pcc_peer != NULL) { + pce_opts_cli = pcep_cli_find_pce(pcc_peer); + if (pce_opts_cli == NULL) { + vty_out(vty, "%% PCE [%s] does not exist.\n", pcc_peer); + return CMD_WARNING; + } + + if (!pcep_cli_pcc_has_pce(pcc_peer)) { + vty_out(vty, "%% PCC is not connected to PCE [%s].\n", + pcc_peer); + return CMD_WARNING; + } + + pcc_info = pcep_ctrl_get_pcc_info(pcep_g->fpt, pcc_peer); + if (pcc_info == NULL) { + vty_out(vty, + "%% Cannot retrieve PCEP session info for PCE [%s]\n", + pcc_peer); + return CMD_WARNING; + } + + print_pcep_session(vty, &pce_opts_cli->pce_opts, pcc_info); + + return CMD_SUCCESS; + } + + /* Show all PCEP sessions */ + struct pce_opts *pce_opts; + int num_pcep_sessions_conf = 0; + int num_pcep_sessions_conn = 0; + for (int i = 0; i < MAX_PCC; i++) { + pce_opts = pce_connections_g.connections[i]; + if (pce_opts == NULL) { + continue; + } + + pcc_info = + pcep_ctrl_get_pcc_info(pcep_g->fpt, pce_opts->pce_name); + if (pcc_info == NULL) { + vty_out(vty, + "%% Cannot retrieve PCEP session info for PCE [%s]\n", + pce_opts->pce_name); + continue; + } + + num_pcep_sessions_conn += + pcc_info->status == PCEP_PCC_OPERATING ? 1 : 0; + num_pcep_sessions_conf++; + print_pcep_session(vty, pce_opts, pcc_info); + } + + vty_out(vty, "PCEP Sessions => Configured %d ; Connected %d\n", + num_pcep_sessions_conf, num_pcep_sessions_conn); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_clear_srte_pcep_session(struct vty *vty, + const char *pcc_peer) +{ + struct pce_opts_cli *pce_opts_cli; + + /* Only clear 1 PCEP session */ + if (pcc_peer != NULL) { + pce_opts_cli = pcep_cli_find_pce(pcc_peer); + if (pce_opts_cli == NULL) { + vty_out(vty, "%% PCE [%s] does not exist.\n", pcc_peer); + return CMD_WARNING; + } + + if (!pcep_cli_pcc_has_pce(pcc_peer)) { + vty_out(vty, "%% PCC is not connected to PCE [%s].\n", + pcc_peer); + return CMD_WARNING; + } + + pcep_ctrl_reset_pcc_session(pcep_g->fpt, + pce_opts_cli->pce_opts.pce_name); + vty_out(vty, "PCEP session cleared for peer %s\n", pcc_peer); + + return CMD_SUCCESS; + } + + /* Clear all PCEP sessions */ + struct pce_opts *pce_opts; + int num_pcep_sessions = 0; + for (int i = 0; i < MAX_PCC; i++) { + pce_opts = pce_connections_g.connections[i]; + if (pce_opts == NULL) { + continue; + } + + num_pcep_sessions++; + pcep_ctrl_reset_pcc_session(pcep_g->fpt, pce_opts->pce_name); + vty_out(vty, "PCEP session cleared for peer %s\n", + pce_opts->pce_name); + } + + vty_out(vty, "Cleared [%d] PCEP sessions\n", num_pcep_sessions); + + return CMD_SUCCESS; +} + +/* + * Config Write functions + */ + +int pcep_cli_debug_config_write(struct vty *vty) +{ + char buff[128] = ""; + + if (DEBUG_MODE_CHECK(&pcep_g->dbg, DEBUG_MODE_CONF)) { + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_BASIC)) + csnprintfrr(buff, sizeof(buff), " %s", + PCEP_VTYSH_ARG_BASIC); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PATH)) + csnprintfrr(buff, sizeof(buff), " %s", + PCEP_VTYSH_ARG_PATH); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEP)) + csnprintfrr(buff, sizeof(buff), " %s", + PCEP_VTYSH_ARG_MESSAGE); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEPLIB)) + csnprintfrr(buff, sizeof(buff), " %s", + PCEP_VTYSH_ARG_PCEPLIB); + vty_out(vty, "debug pathd pcep%s\n", buff); + buff[0] = 0; + return 1; + } + + return 0; +} + +int pcep_cli_debug_set_all(uint32_t flags, bool set) +{ + DEBUG_FLAGS_SET(&pcep_g->dbg, flags, set); + + /* If all modes have been turned off, don't preserve options. */ + if (!DEBUG_MODE_CHECK(&pcep_g->dbg, DEBUG_MODE_ALL)) + DEBUG_CLEAR(&pcep_g->dbg); + + return 0; +} + +int pcep_cli_pcep_config_write(struct vty *vty) +{ + vty_out(vty, " pcep\n"); + pcep_cli_pcep_pce_config_write(vty); + pcep_cli_pce_config_write(vty); + pcep_cli_pcc_config_write(vty); + vty_out(vty, " exit\n"); + return 1; +} + +int pcep_cli_pcc_config_write(struct vty *vty) +{ + struct pce_opts *pce_opts; + char buf[128] = ""; + int lines = 0; + + /* The MSD, nor any PCE peers have been configured on the PCC */ + if (!pcc_msd_configured_g && pce_connections_g.num_connections == 0) { + return lines; + } + + vty_out(vty, " pcc\n"); + lines++; + + /* Prepare the MSD, if present */ + if (pcc_msd_configured_g) { + vty_out(vty, " %s %d\n", PCEP_VTYSH_ARG_MSD, pcc_msd_g); + lines++; + } + + if (pce_connections_g.num_connections == 0) { + goto exit; + } + + buf[0] = 0; + for (int i = 0; i < MAX_PCC; i++) { + pce_opts = pce_connections_g.connections[i]; + if (pce_opts == NULL) { + continue; + } + + /* Only show the PCEs configured in the pcc sub-command */ + if (!pcep_cli_pcc_has_pce(pce_opts->pce_name)) { + continue; + } + + csnprintfrr(buf, sizeof(buf), " peer %s", + pce_opts->pce_name); + if (pce_opts->precedence > 0 + && pce_opts->precedence != DEFAULT_PCE_PRECEDENCE) { + csnprintfrr(buf, sizeof(buf), " %s %d", + PCEP_VTYSH_ARG_PRECEDENCE, + pce_opts->precedence); + } + vty_out(vty, "%s\n", buf); + lines++; + buf[0] = 0; + } +exit: + vty_out(vty, " exit\n"); + + return lines; +} + +/* Internal function used by pcep_cli_pce_config_write() + * and pcep_cli_pcep_pce_config_write() */ +static int pcep_cli_print_pce_config(struct pcep_config_group_opts *group_opts, + char *buf, size_t buf_len) +{ + int lines = 0; + + if (group_opts->source_ip.ipa_type != IPADDR_NONE + || group_opts->source_port != 0) { + csnprintfrr(buf, buf_len, " "); + if (IS_IPADDR_V4(&group_opts->source_ip)) { + csnprintfrr(buf, buf_len, " %s %s %pI4", + PCEP_VTYSH_ARG_SOURCE_ADDRESS, + PCEP_VTYSH_ARG_IP, + &group_opts->source_ip.ipaddr_v4); + } else if (IS_IPADDR_V6(&group_opts->source_ip)) { + csnprintfrr(buf, buf_len, " %s %s %pI6", + PCEP_VTYSH_ARG_SOURCE_ADDRESS, + PCEP_VTYSH_ARG_IPV6, + &group_opts->source_ip.ipaddr_v6); + } + if (group_opts->source_port > 0) { + csnprintfrr(buf, buf_len, " %s %d", PCEP_VTYSH_ARG_PORT, + group_opts->source_port); + } + csnprintfrr(buf, buf_len, "\n"); + lines++; + } + /* Group the keep-alive together for devman */ + if ((group_opts->keep_alive_seconds > 0) + || (group_opts->min_keep_alive_seconds > 0) + || (group_opts->max_keep_alive_seconds > 0)) { + csnprintfrr(buf, buf_len, " %s", PCEP_VTYSH_ARG_TIMER); + + if (group_opts->keep_alive_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_KEEP_ALIVE, + group_opts->keep_alive_seconds); + } + if (group_opts->min_keep_alive_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_KEEP_ALIVE_MIN, + group_opts->min_keep_alive_seconds); + } + if (group_opts->max_keep_alive_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_KEEP_ALIVE_MAX, + group_opts->max_keep_alive_seconds); + } + csnprintfrr(buf, buf_len, "\n"); + lines++; + } + + /* Group the dead-timer together for devman */ + if ((group_opts->dead_timer_seconds > 0) + || (group_opts->min_dead_timer_seconds > 0) + || (group_opts->max_dead_timer_seconds > 0)) { + csnprintfrr(buf, buf_len, " %s", PCEP_VTYSH_ARG_TIMER); + + if (group_opts->dead_timer_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_DEAD_TIMER, + group_opts->dead_timer_seconds); + } + if (group_opts->min_dead_timer_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_DEAD_TIMER_MIN, + group_opts->min_dead_timer_seconds); + } + if (group_opts->max_dead_timer_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_DEAD_TIMER_MAX, + group_opts->max_dead_timer_seconds); + } + csnprintfrr(buf, buf_len, "\n"); + lines++; + } + + if (group_opts->pcep_request_time_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %s %d\n", + PCEP_VTYSH_ARG_TIMER, PCEP_VTYSH_ARG_PCEP_REQUEST, + group_opts->pcep_request_time_seconds); + lines++; + } + if (group_opts->delegation_timeout_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %s %d\n", + PCEP_VTYSH_ARG_TIMER, + PCEP_VTYSH_ARG_DELEGATION_TIMEOUT, + group_opts->delegation_timeout_seconds); + lines++; + } + if (group_opts->session_timeout_inteval_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %s %d\n", + PCEP_VTYSH_ARG_TIMER, + PCEP_VTYSH_ARG_SESSION_TIMEOUT, + group_opts->session_timeout_inteval_seconds); + lines++; + } + if (group_opts->tcp_md5_auth[0] != '\0') { + csnprintfrr(buf, buf_len, " %s %s\n", PCEP_VTYSH_ARG_TCP_MD5, + group_opts->tcp_md5_auth); + lines++; + } + if (group_opts->draft07) { + csnprintfrr(buf, buf_len, " %s\n", + PCEP_VTYSH_ARG_SR_DRAFT07); + lines++; + } + if (group_opts->pce_initiated) { + csnprintfrr(buf, buf_len, " %s\n", PCEP_VTYSH_ARG_PCE_INIT); + lines++; + } + + return lines; +} + +int pcep_cli_pce_config_write(struct vty *vty) +{ + int lines = 0; + char buf[1024] = ""; + + for (int i = 0; i < MAX_PCE; i++) { + struct pce_opts_cli *pce_opts_cli = pcep_g->pce_opts_cli[i]; + if (pce_opts_cli == NULL) { + continue; + } + struct pce_opts *pce_opts = &pce_opts_cli->pce_opts; + + vty_out(vty, " pce %s\n", pce_opts->pce_name); + if (IS_IPADDR_V6(&pce_opts->addr)) { + vty_out(vty, " %s %s %pI6", PCEP_VTYSH_ARG_ADDRESS, + PCEP_VTYSH_ARG_IPV6, &pce_opts->addr.ipaddr_v6); + } else if (IS_IPADDR_V4(&pce_opts->addr)) { + vty_out(vty, " address %s %pI4", PCEP_VTYSH_ARG_IP, + &pce_opts->addr.ipaddr_v4); + } + if (pce_opts->port != PCEP_DEFAULT_PORT) { + vty_out(vty, " %s %d", PCEP_VTYSH_ARG_PORT, + pce_opts->port); + } + vty_out(vty, "%s\n", buf); + lines += 2; + + if (pce_opts_cli->config_group_name[0] != '\0') { + vty_out(vty, " config %s\n", + pce_opts_cli->config_group_name); + lines++; + } + + /* Only display the values configured on the PCE, not the values + * from its optional pce-config-group, nor the default values */ + lines += pcep_cli_print_pce_config( + &pce_opts_cli->pce_config_group_opts, buf, sizeof(buf)); + + vty_out(vty, "%s", buf); + buf[0] = '\0'; + + vty_out(vty, " exit\n"); + } + + return lines; +} + +int pcep_cli_pcep_pce_config_write(struct vty *vty) +{ + int lines = 0; + char buf[1024] = ""; + + for (int i = 0; i < MAX_PCE; i++) { + struct pcep_config_group_opts *group_opts = + pcep_g->config_group_opts[i]; + if (group_opts == NULL) { + continue; + } + + vty_out(vty, " pce-config %s\n", group_opts->name); + lines += 1; + + lines += + pcep_cli_print_pce_config(group_opts, buf, sizeof(buf)); + vty_out(vty, "%s", buf); + buf[0] = 0; + + vty_out(vty, " exit\n"); + } + + return lines; +} + +/* + * VTYSH command syntax definitions + * The param names are taken from the path_pcep_cli_clippy.c generated file. + */ + +DEFPY(show_debugging_pathd_pcep, + show_debugging_pathd_pcep_cmd, + "show debugging pathd-pcep", + SHOW_STR + "State of each debugging option\n" + "pathd pcep module debugging\n") +{ + vty_out(vty, "Pathd pcep debugging status:\n"); + + if (DEBUG_MODE_CHECK(&pcep_g->dbg, DEBUG_MODE_CONF)) { + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_BASIC)) + vty_out(vty, " Pathd pcep %s debugging is on\n", + PCEP_VTYSH_ARG_BASIC); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PATH)) + vty_out(vty, " Pathd pcep %s debugging is on\n", + PCEP_VTYSH_ARG_PATH); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEP)) + vty_out(vty, " Pathd pcep %s debugging is on\n", + PCEP_VTYSH_ARG_MESSAGE); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEPLIB)) + vty_out(vty, " Pathd pcep %s debugging is on\n", + PCEP_VTYSH_ARG_PCEPLIB); + } + + return CMD_SUCCESS; +} + +DEFPY(pcep_cli_debug, + pcep_cli_debug_cmd, + "[no] debug pathd pcep [basic]$basic_str [path]$path_str [message]$message_str [pceplib]$pceplib_str", + NO_STR DEBUG_STR + "pathd debugging\n" + "pcep module debugging\n" + "module basic debugging\n" + "path structures debugging\n" + "pcep message debugging\n" + "pceplib debugging\n") +{ + return path_pcep_cli_debug(vty, no, basic_str, path_str, message_str, + pceplib_str); +} + +DEFPY(pcep_cli_show_srte_pcep_counters, + pcep_cli_show_srte_pcep_counters_cmd, + "show sr-te pcep counters", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "PCEP counters\n") +{ + return path_pcep_cli_show_srte_pcep_counters(vty); +} + +DEFPY_NOSH( + pcep_cli_pcep, + pcep_cli_pcep_cmd, + "pcep", + "PCEP configuration\n") +{ + vty->node = PCEP_NODE; + return CMD_SUCCESS; +} + +DEFPY_NOSH( + pcep_cli_pcep_pce_config, + pcep_cli_pcep_pce_config_cmd, + "pce-config WORD$name", + "Shared configuration\n" + "Shared configuration name\n") +{ + return path_pcep_cli_pcep_pce_config(vty, name); +} + +DEFPY(pcep_cli_pcep_no_pce_config, + pcep_cli_pcep_no_pce_config_cmd, + "no pce-config WORD$name", + NO_STR + "Shared configuration\n" + "Shared configuration name\n") +{ + return path_pcep_cli_pcep_pce_config_delete(vty, name); +} + +DEFPY(pcep_cli_show_srte_pcep_pce_config, + pcep_cli_show_srte_pcep_pce_config_cmd, + "show sr-te pcep pce-config [<default|WORD>$name]", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "Show shared PCE configuration\n" + "Show default hard-coded values\n" + "Shared configuration name\n") +{ + return path_pcep_cli_show_srte_pcep_pce_config(vty, name); +} + +DEFPY_NOSH( + pcep_cli_pce, + pcep_cli_pce_cmd, + "pce WORD$name", + "PCE configuration, address sub-config is mandatory\n" + "PCE name\n") +{ + return path_pcep_cli_pce(vty, name); +} + +DEFPY(pcep_cli_no_pce, + pcep_cli_no_pce_cmd, + "no pce WORD$name", + NO_STR + "PCE configuration, address sub-config is mandatory\n" + "PCE name\n") +{ + return path_pcep_cli_pce_delete(vty, name); +} + +DEFPY(pcep_cli_show_srte_pcep_pce, + pcep_cli_show_srte_pcep_pce_cmd, + "show sr-te pcep pce [WORD$name]", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "Show detailed pce values\n" + "pce name\n") +{ + return path_pcep_cli_show_srte_pcep_pce(vty, name); +} + +DEFPY(pcep_cli_peer_sr_draft07, + pcep_cli_peer_sr_draft07_cmd, + "sr-draft07", + "Configure PCC to send PCEP Open with SR draft07\n") +{ + return path_pcep_cli_peer_sr_draft07(vty); +} + +DEFPY(pcep_cli_peer_pce_initiated, + pcep_cli_peer_pce_initiated_cmd, + "pce-initiated", + "Configure PCC to accept PCE initiated LSPs\n") +{ + return path_pcep_cli_peer_pce_initiated(vty); +} + +DEFPY(pcep_cli_peer_tcp_md5_auth, + pcep_cli_peer_tcp_md5_auth_cmd, + "tcp-md5-auth WORD", + "Configure PCC TCP-MD5 RFC2385 Authentication\n" + "TCP-MD5 Authentication string\n") +{ + return path_pcep_cli_peer_tcp_md5_auth(vty, tcp_md5_auth); +} + +DEFPY(pcep_cli_peer_address, + pcep_cli_peer_address_cmd, + "address <ip A.B.C.D | ipv6 X:X::X:X> [port (1024-65535)]", + "PCE IP Address configuration, mandatory configuration\n" + "PCE IPv4 address\n" + "Remote PCE server IPv4 address\n" + "PCE IPv6 address\n" + "Remote PCE server IPv6 address\n" + "Remote PCE server port\n" + "Remote PCE server port value\n") +{ + return path_pcep_cli_peer_address(vty, ip_str, &ip, ipv6_str, &ipv6, + port_str, port); +} + +DEFPY(pcep_cli_peer_source_address, + pcep_cli_peer_source_address_cmd, + "source-address [ip A.B.C.D | ipv6 X:X::X:X] [port (1024-65535)]", + "PCE source IP Address configuration\n" + "PCE source IPv4 address\n" + "PCE source IPv4 address value\n" + "PCE source IPv6 address\n" + "PCE source IPv6 address value\n" + "Source PCE server port\n" + "Source PCE server port value\n") +{ + return path_pcep_cli_peer_source_address(vty, ip_str, &ip, ipv6_str, + &ipv6, port_str, port); +} + +DEFPY(pcep_cli_peer_pcep_pce_config_ref, + pcep_cli_peer_pcep_pce_config_ref_cmd, + "config WORD$name", + "PCE shared configuration to use\n" + "Shared configuration name\n") +{ + return path_pcep_cli_peer_pcep_pce_config_ref(vty, name); +} + +DEFPY(pcep_cli_peer_timers, + pcep_cli_peer_timers_cmd, + "timer [keep-alive (1-63)] [min-peer-keep-alive (1-255)] [max-peer-keep-alive (1-255)] " + "[dead-timer (4-255)] [min-peer-dead-timer (4-255)] [max-peer-dead-timer (4-255)] " + "[pcep-request (1-120)] [session-timeout-interval (1-120)] [delegation-timeout (1-60)]", + "PCE PCEP Session Timers configuration\n" + "PCC Keep Alive Timer\n" + "PCC Keep Alive Timer value in seconds\n" + "Min Acceptable PCE Keep Alive Timer\n" + "Min Acceptable PCE Keep Alive Timer value in seconds\n" + "Max Acceptable PCE Keep Alive Timer\n" + "Max Acceptable PCE Keep Alive Timer value in seconds\n" + "PCC Dead Timer\n" + "PCC Dead Timer value in seconds\n" + "Min Acceptable PCE Dead Timer\n" + "Min Acceptable PCE Dead Timer value in seconds\n" + "Max Acceptable PCE Dead Timer\n" + "Max Acceptable PCE Dead Timer value in seconds\n" + "PCC PCEP Request Timer\n" + "PCC PCEP Request Timer value in seconds\n" + "PCC Session Timeout Interval\n" + "PCC Session Timeout Interval value in seconds\n" + "Multi-PCE delegation timeout\n" + "Multi-PCE delegation timeout value in seconds\n") +{ + return path_pcep_cli_peer_timers( + vty, keep_alive_str, keep_alive, min_peer_keep_alive_str, + min_peer_keep_alive, max_peer_keep_alive_str, + max_peer_keep_alive, dead_timer_str, dead_timer, + min_peer_dead_timer_str, min_peer_dead_timer, + max_peer_dead_timer_str, max_peer_dead_timer, pcep_request_str, + pcep_request, session_timeout_interval_str, + session_timeout_interval, delegation_timeout_str, + delegation_timeout); +} + +DEFPY_NOSH( + pcep_cli_pcc, + pcep_cli_pcc_cmd, + "pcc", + "PCC configuration\n") +{ + return path_pcep_cli_pcc(vty); +} + +DEFPY(pcep_cli_no_pcc, + pcep_cli_no_pcc_cmd, + "no pcc", + NO_STR + "PCC configuration\n") +{ + return path_pcep_cli_pcc_delete(vty); +} + +DEFPY(pcep_cli_pcc_pcc_msd, + pcep_cli_pcc_pcc_msd_cmd, + "msd (1-32)", + "PCC maximum SID depth \n" + "PCC maximum SID depth value\n") +{ + return path_pcep_cli_pcc_pcc_msd(vty, msd_str, msd); +} + +DEFPY(pcep_cli_pcc_pcc_peer, + pcep_cli_pcc_pcc_peer_cmd, + "[no] peer WORD [precedence (1-255)]", + NO_STR + "PCC PCE peer\n" + "PCC PCE name\n" + "PCC Multi-PCE precedence\n" + "PCE precedence\n") +{ + if (no != NULL) { + return path_pcep_cli_pcc_pcc_peer_delete( + vty, peer, precedence_str, precedence); + } else { + return path_pcep_cli_pcc_pcc_peer(vty, peer, precedence_str, + precedence); + } +} + +DEFPY(pcep_cli_show_srte_pcc, + pcep_cli_show_srte_pcc_cmd, + "show sr-te pcep pcc", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "Show current PCC configuration\n") +{ + return path_pcep_cli_show_srte_pcep_pcc(vty); +} + +DEFPY(pcep_cli_show_srte_pcep_session, + pcep_cli_show_srte_pcep_session_cmd, + "show sr-te pcep session [WORD]$pce", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "Show PCEP Session information\n" + "PCE name\n") +{ + return path_pcep_cli_show_srte_pcep_session(vty, pce); +} + +DEFPY(pcep_cli_clear_srte_pcep_session, + pcep_cli_clear_srte_pcep_session_cmd, + "clear sr-te pcep session [WORD]$pce", + CLEAR_STR + "SR-TE\n" + "PCEP\n" + "Reset PCEP connection\n" + "PCE name\n") +{ + return path_pcep_cli_clear_srte_pcep_session(vty, pce); +} + +void pcep_cli_init(void) +{ + hook_register(pathd_srte_config_write, pcep_cli_pcep_config_write); + hook_register(nb_client_debug_config_write, + pcep_cli_debug_config_write); + hook_register(nb_client_debug_set_all, pcep_cli_debug_set_all); + + memset(&pce_connections_g, 0, sizeof(pce_connections_g)); + + install_node(&pcep_node); + install_node(&pcep_pcc_node); + install_node(&pcep_pce_node); + install_node(&pcep_pce_config_node); + + install_default(PCEP_PCE_CONFIG_NODE); + install_default(PCEP_PCE_NODE); + install_default(PCEP_PCC_NODE); + install_default(PCEP_NODE); + + install_element(SR_TRAFFIC_ENG_NODE, &pcep_cli_pcep_cmd); + + /* PCEP configuration group related configuration commands */ + install_element(PCEP_NODE, &pcep_cli_pcep_pce_config_cmd); + install_element(PCEP_NODE, &pcep_cli_pcep_no_pce_config_cmd); + install_element(PCEP_PCE_CONFIG_NODE, + &pcep_cli_peer_source_address_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &pcep_cli_peer_timers_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &pcep_cli_peer_sr_draft07_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &pcep_cli_peer_pce_initiated_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &pcep_cli_peer_tcp_md5_auth_cmd); + + /* PCE peer related configuration commands */ + install_element(PCEP_NODE, &pcep_cli_pce_cmd); + install_element(PCEP_NODE, &pcep_cli_no_pce_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_address_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_source_address_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_pcep_pce_config_ref_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_timers_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_sr_draft07_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_pce_initiated_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_tcp_md5_auth_cmd); + + /* PCC related configuration commands */ + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcc_cmd); + install_element(PCEP_NODE, &pcep_cli_pcc_cmd); + install_element(PCEP_NODE, &pcep_cli_no_pcc_cmd); + install_element(PCEP_PCC_NODE, &pcep_cli_pcc_pcc_peer_cmd); + install_element(PCEP_PCC_NODE, &pcep_cli_pcc_pcc_msd_cmd); + + /* Top commands */ + install_element(CONFIG_NODE, &pcep_cli_debug_cmd); + install_element(ENABLE_NODE, &pcep_cli_debug_cmd); + install_element(ENABLE_NODE, &show_debugging_pathd_pcep_cmd); + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcep_counters_cmd); + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcep_pce_config_cmd); + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcep_pce_cmd); + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcep_session_cmd); + install_element(ENABLE_NODE, &pcep_cli_clear_srte_pcep_session_cmd); +} diff --git a/pathd/path_pcep_cli.h b/pathd/path_pcep_cli.h new file mode 100644 index 0000000..0b101ab --- /dev/null +++ b/pathd/path_pcep_cli.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 Volta Networks, Inc + * Brady Johnson + * + * 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 + */ + +#ifndef _PATH_PCEP_CLI_H_ +#define _PATH_PCEP_CLI_H_ + + +/* PCEP CLI Functions */ +void pcep_cli_init(void); + +#endif // _PATH_PCEP_CLI_H_ diff --git a/pathd/path_pcep_config.c b/pathd/path_pcep_config.c new file mode 100644 index 0000000..99b1c34 --- /dev/null +++ b/pathd/path_pcep_config.c @@ -0,0 +1,546 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include <northbound.h> +#include <yang.h> +#include <printfrr.h> +#include "pceplib/pcep_msg_objects.h" +#include "pathd/pathd.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_config.h" +#include "pathd/path_pcep_debug.h" +#include "thread.h" + +#define MAX_XPATH 256 +#define MAX_FLOAT_LEN 22 +#define INETADDR4_MAXLEN 16 +#define INETADDR6_MAXLEN 40 +#define INITIATED_CANDIDATE_PREFERENCE 255 +#define INITIATED_POLICY_COLOR 1 + + +static void copy_candidate_objfun_info(struct srte_candidate *candidate, + struct path *path); +static void copy_candidate_affinity_filters(struct srte_candidate *candidate, + struct path *path); +static struct path_hop * +path_pcep_config_list_path_hops(struct srte_segment_list *segment_list); +static struct srte_candidate *lookup_candidate(struct lsp_nb_key *key); +static char *candidate_name(struct srte_candidate *candidate); +static enum pcep_lsp_operational_status +status_int_to_ext(enum srte_policy_status status); +static enum pcep_sr_subobj_nai pcep_nai_type(enum srte_segment_nai_type type); +static enum srte_segment_nai_type srte_nai_type(enum pcep_sr_subobj_nai type); + +void path_pcep_refine_path(struct path *path) +{ + struct srte_candidate *candidate = lookup_candidate(&path->nbkey); + struct srte_lsp *lsp; + + if (candidate == NULL) + return; + + lsp = candidate->lsp; + + if (path->name == NULL) + path->name = candidate_name(candidate); + if (path->type == SRTE_CANDIDATE_TYPE_UNDEFINED) + path->type = candidate->type; + if (path->create_origin == SRTE_ORIGIN_UNDEFINED) + path->create_origin = candidate->protocol_origin; + if ((path->update_origin == SRTE_ORIGIN_UNDEFINED) + && (lsp->segment_list != NULL)) + path->update_origin = lsp->segment_list->protocol_origin; +} + +struct path *path_pcep_config_get_path(struct lsp_nb_key *key) +{ + struct srte_candidate *candidate = lookup_candidate(key); + if (candidate == NULL) + return NULL; + return candidate_to_path(candidate); +} + +void path_pcep_config_list_path(path_list_cb_t cb, void *arg) +{ + struct path *path; + struct srte_policy *policy; + struct srte_candidate *candidate; + + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + RB_FOREACH (candidate, srte_candidate_head, + &policy->candidate_paths) { + path = candidate_to_path(candidate); + if (!cb(path, arg)) + return; + } + } +} + +struct path *candidate_to_path(struct srte_candidate *candidate) +{ + char *name; + struct path *path; + struct path_hop *hop = NULL; + struct path_metric *metric = NULL; + struct srte_policy *policy; + struct srte_lsp *lsp; + enum pcep_lsp_operational_status status; + enum srte_protocol_origin update_origin = 0; + char *originator = NULL; + + policy = candidate->policy; + lsp = candidate->lsp; + + if (lsp->segment_list != NULL) { + hop = path_pcep_config_list_path_hops(lsp->segment_list); + update_origin = lsp->segment_list->protocol_origin; + originator = XSTRDUP(MTYPE_PCEP, lsp->segment_list->originator); + } + path = pcep_new_path(); + name = candidate_name(candidate); + if (CHECK_FLAG(candidate->flags, F_CANDIDATE_BEST)) { + status = status_int_to_ext(policy->status); + } else { + status = PCEP_LSP_OPERATIONAL_DOWN; + } + for (uint32_t i = 0; i < MAX_METRIC_TYPE; i++) { + struct path_metric *path_metric; + struct srte_metric *srte_metric = &lsp->metrics[i]; + if (CHECK_FLAG(srte_metric->flags, F_METRIC_IS_DEFINED)) { + path_metric = pcep_new_metric(); + path_metric->next = metric; + metric = path_metric; + metric->type = i + 1; + metric->value = srte_metric->value; + metric->enforce = CHECK_FLAG(srte_metric->flags, + F_METRIC_IS_REQUIRED); + metric->is_bound = CHECK_FLAG(srte_metric->flags, + F_METRIC_IS_BOUND); + metric->is_computed = CHECK_FLAG(srte_metric->flags, + F_METRIC_IS_COMPUTED); + } + } + *path = (struct path){ + .nbkey = (struct lsp_nb_key){.color = policy->color, + .endpoint = policy->endpoint, + .preference = + candidate->preference}, + .create_origin = lsp->protocol_origin, + .update_origin = update_origin, + .originator = originator, + .plsp_id = 0, + .name = name, + .type = candidate->type, + .srp_id = policy->srp_id, + .req_id = 0, + .binding_sid = policy->binding_sid, + .status = status, + .do_remove = false, + .go_active = false, + .was_created = false, + .was_removed = false, + .is_synching = false, + .is_delegated = false, + .first_hop = hop, + .first_metric = metric}; + + path->has_bandwidth = CHECK_FLAG(lsp->flags, F_CANDIDATE_HAS_BANDWIDTH); + if (path->has_bandwidth) { + path->enforce_bandwidth = + CHECK_FLAG(lsp->flags, F_CANDIDATE_REQUIRED_BANDWIDTH); + path->bandwidth = lsp->bandwidth; + } else { + path->enforce_bandwidth = true; + path->bandwidth = 0; + } + + copy_candidate_objfun_info(candidate, path); + copy_candidate_affinity_filters(candidate, path); + + return path; +} + +void copy_candidate_objfun_info(struct srte_candidate *candidate, + struct path *path) +{ + struct srte_lsp *lsp = candidate->lsp; + + if (lsp != NULL) { + if (CHECK_FLAG(lsp->flags, F_CANDIDATE_HAS_OBJFUN)) { + path->has_pce_objfun = true; + path->pce_objfun = lsp->objfun; + } else { + path->has_pce_objfun = false; + path->pce_objfun = OBJFUN_UNDEFINED; + } + } + if (CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_OBJFUN)) { + path->has_pcc_objfun = true; + path->pcc_objfun = candidate->objfun; + path->enforce_pcc_objfun = CHECK_FLAG( + candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN); + + } else { + path->has_pcc_objfun = false; + path->pcc_objfun = OBJFUN_UNDEFINED; + UNSET_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN); + } +} + +void copy_candidate_affinity_filters(struct srte_candidate *candidate, + struct path *path) +{ + bool eany = CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_EXCLUDE_ANY); + bool iany = CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_INCLUDE_ANY); + bool iall = CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_INCLUDE_ALL); + path->has_affinity_filters = eany || iany || iall; + path->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY - 1] = + eany ? candidate->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY + - 1] + : 0; + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY - 1] = + iany ? candidate->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY + - 1] + : 0; + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL - 1] = + iall ? candidate->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL + - 1] + : 0; +} + +struct path_hop * +path_pcep_config_list_path_hops(struct srte_segment_list *segment_list) +{ + struct srte_segment_entry *segment; + struct path_hop *hop = NULL, *last_hop = NULL; + + RB_FOREACH_REVERSE (segment, srte_segment_entry_head, + &segment_list->segments) { + hop = pcep_new_hop(); + *hop = (struct path_hop){ + .next = last_hop, + .is_loose = false, + .has_sid = true, + .is_mpls = true, + .has_attribs = false, + .sid = {.mpls = {.label = segment->sid_value}}, + .has_nai = + segment->nai_type != SRTE_SEGMENT_NAI_TYPE_NONE, + .nai = {.type = pcep_nai_type(segment->nai_type)}}; + switch (segment->nai_type) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + memcpy(&hop->nai.local_addr, &segment->nai_local_addr, + sizeof(struct ipaddr)); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + memcpy(&hop->nai.local_addr, &segment->nai_local_addr, + sizeof(struct ipaddr)); + memcpy(&hop->nai.remote_addr, &segment->nai_remote_addr, + sizeof(struct ipaddr)); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + memcpy(&hop->nai.local_addr, &segment->nai_local_addr, + sizeof(struct ipaddr)); + hop->nai.local_iface = segment->nai_local_iface; + memcpy(&hop->nai.remote_addr, &segment->nai_remote_addr, + sizeof(struct ipaddr)); + hop->nai.remote_iface = segment->nai_remote_iface; + break; + default: + break; + } + last_hop = hop; + } + return hop; +} + +int path_pcep_config_initiate_path(struct path *path) +{ + struct srte_policy *policy; + struct srte_candidate *candidate; + + if (path->do_remove) { + zlog_warn("PCE %s tried to REMOVE pce-initiate a path ", + path->originator); + candidate = lookup_candidate(&path->nbkey); + if (candidate) { + if (!path->is_delegated) { + zlog_warn( + "(%s)PCE tried to REMOVE but it's not Delegated!", + __func__); + return ERROR_19_1; + } + if (candidate->type != SRTE_CANDIDATE_TYPE_DYNAMIC) { + zlog_warn( + "(%s)PCE tried to REMOVE but it's not PCE origin!", + __func__); + return ERROR_19_9; + } + zlog_warn( + "(%s)PCE tried to REMOVE found candidate!, let's remove", + __func__); + candidate->policy->srp_id = path->srp_id; + SET_FLAG(candidate->policy->flags, F_POLICY_DELETED); + SET_FLAG(candidate->flags, F_CANDIDATE_DELETED); + } else { + zlog_warn("(%s)PCE tried to REMOVE not existing LSP!", + __func__); + return ERROR_19_3; + } + srte_apply_changes(); + } else { + assert(!IS_IPADDR_NONE(&path->nbkey.endpoint)); + + if (path->nbkey.preference == 0) + path->nbkey.preference = INITIATED_CANDIDATE_PREFERENCE; + + if (path->nbkey.color == 0) + path->nbkey.color = INITIATED_POLICY_COLOR; + + candidate = lookup_candidate(&path->nbkey); + if (!candidate) { + policy = srte_policy_add( + path->nbkey.color, &path->nbkey.endpoint, + SRTE_ORIGIN_PCEP, path->originator); + strlcpy(policy->name, path->name, sizeof(policy->name)); + policy->binding_sid = path->binding_sid; + SET_FLAG(policy->flags, F_POLICY_NEW); + candidate = srte_candidate_add( + policy, path->nbkey.preference, + SRTE_ORIGIN_PCEP, path->originator); + candidate->policy->srp_id = path->srp_id; + strlcpy(candidate->name, path->name, + sizeof(candidate->name)); + SET_FLAG(candidate->flags, F_CANDIDATE_NEW); + } else { + policy = candidate->policy; + if ((path->originator != candidate->originator) + || (path->originator != policy->originator)) { + /* There is already an initiated path from + * another PCE, show a warning and regect the + * initiated path */ + zlog_warn( + "PCE %s tried to initiate a path already initiated by PCE %s", + path->originator, + candidate->originator); + return 1; + } + if ((policy->protocol_origin != SRTE_ORIGIN_PCEP) + || (candidate->protocol_origin + != SRTE_ORIGIN_PCEP)) { + /* There is already an initiated path from + * another PCE, show a warning and regect the + * initiated path */ + zlog_warn( + "PCE %s tried to initiate a path created localy", + path->originator); + return 1; + } + } + return path_pcep_config_update_path(path); + } + return 0; +} + +int path_pcep_config_update_path(struct path *path) +{ + assert(path != NULL); + assert(path->nbkey.preference != 0); + assert(path->nbkey.endpoint.ipa_type == IPADDR_V4); + + int number_of_sid_clashed = 0; + struct path_hop *hop; + struct path_metric *metric; + int index; + char segment_list_name_buff[64 + 1 + 64 + 1 + 11 + 1]; + char *segment_list_name = NULL; + struct srte_candidate *candidate; + struct srte_segment_list *segment_list = NULL; + struct srte_segment_entry *segment; + + candidate = lookup_candidate(&path->nbkey); + + // if there is no candidate to update we are done + if (!candidate) + return 0; + + candidate->policy->srp_id = path->srp_id; + // first clean up old segment list if present + if (candidate->lsp->segment_list) { + SET_FLAG(candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_DELETED); + srte_segment_list_del(candidate->lsp->segment_list); + candidate->lsp->segment_list = NULL; + } + + if (path->first_hop == NULL) + return PATH_NB_ERR; + + snprintf(segment_list_name_buff, sizeof(segment_list_name_buff), + "%s-%u", path->name, path->plsp_id); + segment_list_name = segment_list_name_buff; + + segment_list = srte_segment_list_add(segment_list_name); + segment_list->protocol_origin = path->update_origin; + strlcpy(segment_list->originator, path->originator, + sizeof(segment_list->originator)); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_NEW); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + for (hop = path->first_hop, index = 10; hop != NULL; + hop = hop->next, index += 10) { + assert(hop->has_sid); + assert(hop->is_mpls); + + segment = srte_segment_entry_add(segment_list, index); + + segment->sid_value = (mpls_label_t)hop->sid.mpls.label; + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + if (!hop->has_nai) + continue; + if (srte_segment_entry_set_nai( + segment, srte_nai_type(hop->nai.type), + &hop->nai.local_addr, hop->nai.local_iface, + &hop->nai.remote_addr, hop->nai.remote_iface, 0, 0) + == PATH_SID_ERROR) + /* TED queries don't match PCE */ + /* Don't apply srte,zebra changes */ + number_of_sid_clashed++; + } + + candidate->lsp->segment_list = segment_list; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + for (metric = path->first_metric; metric != NULL; metric = metric->next) + srte_lsp_set_metric( + candidate->lsp, + (enum srte_candidate_metric_type)metric->type, + metric->value, metric->enforce, metric->is_bound, + metric->is_computed); + + if (path->has_bandwidth) + srte_lsp_set_bandwidth(candidate->lsp, path->bandwidth, + path->enforce_bandwidth); + + if (path->has_pce_objfun) { + SET_FLAG(candidate->lsp->flags, F_CANDIDATE_HAS_OBJFUN); + candidate->lsp->objfun = path->pce_objfun; + } + + if (number_of_sid_clashed) + SET_FLAG(segment->segment_list->flags, + F_SEGMENT_LIST_SID_CONFLICT); + else + srte_apply_changes(); + + return 0; +} + +struct srte_candidate *lookup_candidate(struct lsp_nb_key *key) +{ + struct srte_policy *policy = NULL; + policy = srte_policy_find(key->color, &key->endpoint); + if (policy == NULL) + return NULL; + return srte_candidate_find(policy, key->preference); +} + +char *candidate_name(struct srte_candidate *candidate) +{ + if (candidate->protocol_origin == SRTE_ORIGIN_PCEP + || candidate->protocol_origin == SRTE_ORIGIN_BGP) + return asprintfrr(MTYPE_PCEP, "%s", candidate->policy->name); + else + return asprintfrr(MTYPE_PCEP, "%s-%s", candidate->policy->name, + candidate->name); +} + +enum pcep_lsp_operational_status +status_int_to_ext(enum srte_policy_status status) +{ + switch (status) { + case SRTE_POLICY_STATUS_UP: + return PCEP_LSP_OPERATIONAL_ACTIVE; + case SRTE_POLICY_STATUS_GOING_UP: + return PCEP_LSP_OPERATIONAL_GOING_UP; + case SRTE_POLICY_STATUS_GOING_DOWN: + return PCEP_LSP_OPERATIONAL_GOING_DOWN; + default: + return PCEP_LSP_OPERATIONAL_DOWN; + } +} + +enum pcep_sr_subobj_nai pcep_nai_type(enum srte_segment_nai_type type) +{ + switch (type) { + case SRTE_SEGMENT_NAI_TYPE_NONE: + return PCEP_SR_SUBOBJ_NAI_ABSENT; + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + return PCEP_SR_SUBOBJ_NAI_IPV4_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + return PCEP_SR_SUBOBJ_NAI_IPV6_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + return PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + return PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + return PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES: + return PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY; + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + return PCEP_SR_SUBOBJ_NAI_IPV4_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + return PCEP_SR_SUBOBJ_NAI_IPV6_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + return PCEP_SR_SUBOBJ_NAI_IPV4_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + return PCEP_SR_SUBOBJ_NAI_IPV6_NODE; + default: + return PCEP_SR_SUBOBJ_NAI_UNKNOWN; + } +} + +enum srte_segment_nai_type srte_nai_type(enum pcep_sr_subobj_nai type) +{ + switch (type) { + case PCEP_SR_SUBOBJ_NAI_ABSENT: + return SRTE_SEGMENT_NAI_TYPE_NONE; + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + return SRTE_SEGMENT_NAI_TYPE_IPV4_NODE; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + return SRTE_SEGMENT_NAI_TYPE_IPV6_NODE; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + return SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + return SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + return SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY; + default: + return SRTE_SEGMENT_NAI_TYPE_NONE; + } +} diff --git a/pathd/path_pcep_config.h b/pathd/path_pcep_config.h new file mode 100644 index 0000000..e56d497 --- /dev/null +++ b/pathd/path_pcep_config.h @@ -0,0 +1,46 @@ +/* + * 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 + */ + +#ifndef _PATH_PCEP_CONFIG_H_ +#define _PATH_PCEP_CONFIG_H_ + +#include <stdbool.h> +#include <debug.h> + +#include "pathd/path_pcep.h" + +#define PATH_NB_NO_CHANGE 0 +#define PATH_NB_OK 1 +#define PATH_NB_ERR -1 + +typedef int (*path_list_cb_t)(struct path *path, void *arg); + +/* Lookup the candidate path and fill up the missing path attributes like name + * and type. Used for path generated from PCEP message received from the PCE + * so they contains more information about the candidate path. If no matching + * policy or candidate path is found, nothing is changed. + * MUST BE CALLED FROM THE MAIN THREAD */ +void path_pcep_refine_path(struct path *path); +struct path *path_pcep_config_get_path(struct lsp_nb_key *key); +void path_pcep_config_list_path(path_list_cb_t cb, void *arg); +int path_pcep_config_initiate_path(struct path *path); +int path_pcep_config_update_path(struct path *path); +struct path *candidate_to_path(struct srte_candidate *candidate); + + +#endif // _PATH_PCEP_CONFIG_H_ diff --git a/pathd/path_pcep_controller.c b/pathd/path_pcep_controller.c new file mode 100644 index 0000000..b9f2ba3 --- /dev/null +++ b/pathd/path_pcep_controller.c @@ -0,0 +1,1092 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "lib/version.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" +#include "network.h" + +#include "pathd/pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_controller.h" +#include "pathd/path_pcep_pcc.h" +#include "pathd/path_pcep_config.h" +#include "pathd/path_pcep_debug.h" + +#define MAX_RECONNECT_DELAY 120 + +/* Event handling data structures */ +enum pcep_ctrl_event_type { + EV_UPDATE_PCC_OPTS = 1, + EV_UPDATE_PCE_OPTS, + EV_REMOVE_PCC, + EV_PATHD_EVENT, + EV_SYNC_PATH, + EV_SYNC_DONE, + EV_PCEPLIB_EVENT, + EV_RESET_PCC_SESSION, + EV_SEND_REPORT, + EV_SEND_ERROR, + EV_PATH_REFINED +}; + +struct pcep_ctrl_event_data { + struct ctrl_state *ctrl_state; + enum pcep_ctrl_event_type type; + uint32_t sub_type; + int pcc_id; + void *payload; +}; + +struct pcep_main_event_data { + pcep_main_event_handler_t handler; + int pcc_id; + enum pcep_main_event_type type; + void *payload; +}; + +struct pcep_refine_path_event_data { + struct ctrl_state *ctrl_state; + int pcc_id; + pcep_refine_callback_t continue_lsp_update_handler; + struct path *path; + void *payload; +}; + +/* Synchronous call arguments */ + +struct get_counters_args { + struct ctrl_state *ctrl_state; + int pcc_id; + struct counters_group *counters; +}; + +struct get_pcep_session_args { + struct ctrl_state *ctrl_state; + int pcc_id; + pcep_session *pcep_session; +}; + +/* Internal Functions Called From Main Thread */ +static int pcep_ctrl_halt_cb(struct frr_pthread *fpt, void **res); +static void pcep_refine_path_event_cb(struct thread *thread); + +/* Internal Functions Called From Controller Thread */ +static void pcep_thread_finish_event_handler(struct thread *thread); + +/* Controller Thread Timer Handler */ +static int schedule_thread_timer(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timer_type timer_type, + enum pcep_ctrl_timeout_type timeout_type, + uint32_t delay, void *payload, + struct thread **thread); +static int schedule_thread_timer_with_cb( + struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timer_type timer_type, + enum pcep_ctrl_timeout_type timeout_type, uint32_t delay, void *payload, + struct thread **thread, pcep_ctrl_thread_callback timer_cb); +static void pcep_thread_timer_handler(struct thread *thread); + +/* Controller Thread Socket read/write Handler */ +static int schedule_thread_socket(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_socket_type type, bool is_read, + void *payload, int fd, struct thread **thread, + pcep_ctrl_thread_callback cb); + +/* Controller Thread Event Handler */ +static int send_to_thread(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_event_type type, uint32_t sub_type, + void *payload); +static int send_to_thread_with_cb(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_event_type type, + uint32_t sub_type, void *payload, + pcep_ctrl_thread_callback event_cb); +static void pcep_thread_event_handler(struct thread *thread); +static int pcep_thread_event_update_pcc_options(struct ctrl_state *ctrl_state, + struct pcc_opts *opts); +static int pcep_thread_event_update_pce_options(struct ctrl_state *ctrl_state, + int pcc_id, + struct pce_opts *opts); +static int pcep_thread_event_remove_pcc_by_id(struct ctrl_state *ctrl_state, + int pcc_id); +static int pcep_thread_event_remove_pcc_all(struct ctrl_state *ctrl_state); +static int pcep_thread_event_remove_pcc(struct ctrl_state *ctrl_state, + struct pce_opts *pce_opts); +static int pcep_thread_event_sync_path(struct ctrl_state *ctrl_state, + int pcc_id, struct path *path); +static int pcep_thread_event_sync_done(struct ctrl_state *ctrl_state, + int pcc_id); +static int pcep_thread_event_pathd_event(struct ctrl_state *ctrl_state, + enum pcep_pathd_event_type type, + struct path *path); +static void +pcep_thread_path_refined_event(struct ctrl_state *ctrl_state, + struct pcep_refine_path_event_data *data); + +/* Main Thread Event Handler */ +static int send_to_main(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_main_event_type type, void *payload); +static void pcep_main_event_handler(struct thread *thread); + +/* Helper functions */ +static void set_ctrl_state(struct frr_pthread *fpt, + struct ctrl_state *ctrl_state); +static struct ctrl_state *get_ctrl_state(struct frr_pthread *fpt); +int get_next_id(struct ctrl_state *ctrl_state); +int set_pcc_state(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state); +void remove_pcc_state(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static uint32_t backoff_delay(uint32_t max, uint32_t base, uint32_t attempt); +static const char *timer_type_name(enum pcep_ctrl_timer_type type); +static const char *timeout_type_name(enum pcep_ctrl_timeout_type type); + + +/* ------------ API Functions Called from Main Thread ------------ */ + +int pcep_ctrl_initialize(struct thread_master *main_thread, + struct frr_pthread **fpt, + pcep_main_event_handler_t event_handler) +{ + assert(fpt != NULL); + + int ret = 0; + struct ctrl_state *ctrl_state; + struct frr_pthread_attr attr = { + .start = frr_pthread_attr_default.start, + .stop = pcep_ctrl_halt_cb, + }; + + PCEP_DEBUG("Initializing pcep module controller"); + + /* Create and start the FRR pthread */ + *fpt = frr_pthread_new(&attr, "PCEP thread", "pcep_controller"); + if (*fpt == NULL) { + flog_err(EC_PATH_SYSTEM_CALL, + "failed to initialize PCEP thread"); + return 1; + } + ret = frr_pthread_run(*fpt, NULL); + if (ret < 0) { + flog_err(EC_PATH_SYSTEM_CALL, "failed to create PCEP thread"); + return ret; + } + frr_pthread_wait_running(*fpt); + + /* Initialize the thread state */ + ctrl_state = XCALLOC(MTYPE_PCEP, sizeof(*ctrl_state)); + ctrl_state->main = main_thread; + ctrl_state->self = (*fpt)->master; + ctrl_state->main_event_handler = event_handler; + ctrl_state->pcc_count = 0; + ctrl_state->pcc_last_id = 0; + ctrl_state->pcc_opts = + XCALLOC(MTYPE_PCEP, sizeof(*ctrl_state->pcc_opts)); + /* Default to no PCC address defined */ + ctrl_state->pcc_opts->addr.ipa_type = IPADDR_NONE; + ctrl_state->pcc_opts->port = PCEP_DEFAULT_PORT; + + /* Keep the state reference for events */ + set_ctrl_state(*fpt, ctrl_state); + + return ret; +} + +int pcep_ctrl_finalize(struct frr_pthread **fpt) +{ + assert(fpt != NULL); + + int ret = 0; + + PCEP_DEBUG("Finalizing pcep module controller"); + + if (*fpt != NULL) { + frr_pthread_stop(*fpt, NULL); + *fpt = NULL; + } + + return ret; +} + +int pcep_ctrl_update_pcc_options(struct frr_pthread *fpt, struct pcc_opts *opts) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_UPDATE_PCC_OPTS, 0, opts); +} + +int pcep_ctrl_update_pce_options(struct frr_pthread *fpt, struct pce_opts *opts) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_UPDATE_PCE_OPTS, 0, opts); +} + +int pcep_ctrl_remove_pcc(struct frr_pthread *fpt, struct pce_opts *pce_opts) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_REMOVE_PCC, 0, pce_opts); +} + +int pcep_ctrl_reset_pcc_session(struct frr_pthread *fpt, char *pce_name) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_RESET_PCC_SESSION, 0, pce_name); +} + +int pcep_ctrl_pathd_event(struct frr_pthread *fpt, + enum pcep_pathd_event_type type, struct path *path) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_PATHD_EVENT, type, path); +} + +int pcep_ctrl_sync_path(struct frr_pthread *fpt, int pcc_id, struct path *path) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, pcc_id, EV_SYNC_PATH, 0, path); +} + +int pcep_ctrl_sync_done(struct frr_pthread *fpt, int pcc_id) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, pcc_id, EV_SYNC_DONE, 0, NULL); +} + +struct counters_group *pcep_ctrl_get_counters(struct frr_pthread *fpt, + int pcc_id) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + struct counters_group *counters = NULL; + struct pcc_state *pcc_state; + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (pcc_state) { + counters = pcep_lib_copy_counters(pcc_state->sess); + } + return counters; +} + +pcep_session *pcep_ctrl_get_pcep_session(struct frr_pthread *fpt, int pcc_id) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + struct pcc_state *pcc_state; + pcep_session *session = NULL; + + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (pcc_state) { + session = pcep_lib_copy_pcep_session(pcc_state->sess); + } + return session; +} + +struct pcep_pcc_info *pcep_ctrl_get_pcc_info(struct frr_pthread *fpt, + const char *pce_name) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + struct pcep_pcc_info *pcc_info = XCALLOC(MTYPE_PCEP, sizeof(*pcc_info)); + if( pcc_info && ctrl_state){ + strlcpy(pcc_info->pce_name, pce_name, sizeof(pcc_info->pce_name)); + pcep_pcc_copy_pcc_info(ctrl_state->pcc, pcc_info); + } + + return pcc_info; +} + +int pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, + struct path *path, bool is_stable) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, pcc_id, EV_SEND_REPORT, is_stable, + path); +} + + +int pcep_ctrl_send_error(struct frr_pthread *fpt, int pcc_id, + struct pcep_error *error) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, pcc_id, EV_SEND_ERROR, 0, error); +} + + +/* ------------ Internal Functions Called from Main Thread ------------ */ + +int pcep_ctrl_halt_cb(struct frr_pthread *fpt, void **res) +{ + thread_add_event(fpt->master, pcep_thread_finish_event_handler, + (void *)fpt, 0, NULL); + pthread_join(fpt->thread, res); + + return 0; +} + +void pcep_refine_path_event_cb(struct thread *thread) +{ + struct pcep_refine_path_event_data *data = THREAD_ARG(thread); + assert(data != NULL); + struct ctrl_state *ctrl_state = data->ctrl_state; + struct path *path = data->path; + assert(path != NULL); + int pcc_id = data->pcc_id; + + + path_pcep_refine_path(path); + send_to_thread(ctrl_state, pcc_id, EV_PATH_REFINED, 0, data); +} + + +/* ------------ API Functions Called From Controller Thread ------------ */ + +void pcep_thread_start_sync(struct ctrl_state *ctrl_state, int pcc_id) +{ + send_to_main(ctrl_state, pcc_id, PCEP_MAIN_EVENT_START_SYNC, NULL); +} + +void pcep_thread_update_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path) +{ + send_to_main(ctrl_state, pcc_id, PCEP_MAIN_EVENT_UPDATE_CANDIDATE, + path); +} + +void pcep_thread_initiate_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path) +{ + send_to_main(ctrl_state, pcc_id, PCEP_MAIN_EVENT_INITIATE_CANDIDATE, + path); +} + +void pcep_thread_remove_candidate_path_segments(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + if (!pcc_state) + return; + /* Will be deleted when the event is handled */ + char *originator = XSTRDUP(MTYPE_PCEP, pcc_state->originator); + PCEP_DEBUG("schedule candidate path segments removal for originator %s", + originator); + send_to_main(ctrl_state, pcep_pcc_get_pcc_id(pcc_state), + PCEP_MAIN_EVENT_REMOVE_CANDIDATE_LSP, originator); +} + +void pcep_thread_schedule_sync_best_pce(struct ctrl_state *ctrl_state, + int pcc_id, int delay, + struct thread **thread) +{ + + schedule_thread_timer(ctrl_state, pcc_id, TM_CALCULATE_BEST_PCE, + TO_UNDEFINED, delay, NULL, thread); +} + +void pcep_thread_cancel_timer(struct thread **thread) +{ + if (thread == NULL || *thread == NULL) { + return; + } + + struct pcep_ctrl_timer_data *data = THREAD_ARG(*thread); + PCEP_DEBUG("Timer %s / %s canceled", timer_type_name(data->timer_type), + timeout_type_name(data->timeout_type)); + if (data != NULL) { + XFREE(MTYPE_PCEP, data); + } + + if ((*thread)->master->owner == pthread_self()) { + thread_cancel(thread); + } else { + thread_cancel_async((*thread)->master, thread, NULL); + } +} + +void pcep_thread_schedule_reconnect(struct ctrl_state *ctrl_state, int pcc_id, + int retry_count, struct thread **thread) +{ + uint32_t delay = backoff_delay(MAX_RECONNECT_DELAY, 1, retry_count); + PCEP_DEBUG("Schedule RECONNECT_PCC for %us (retry %u)", delay, + retry_count); + schedule_thread_timer(ctrl_state, pcc_id, TM_RECONNECT_PCC, + TO_UNDEFINED, delay, NULL, thread); +} + +void pcep_thread_schedule_timeout(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timeout_type timeout_type, + uint32_t delay, void *param, + struct thread **thread) +{ + assert(timeout_type > TO_UNDEFINED); + assert(timeout_type < TO_MAX); + PCEP_DEBUG("Schedule timeout %s for %us", + timeout_type_name(timeout_type), delay); + schedule_thread_timer(ctrl_state, pcc_id, TM_TIMEOUT, timeout_type, + delay, param, thread); +} + +void pcep_thread_schedule_pceplib_timer(struct ctrl_state *ctrl_state, + int delay, void *payload, + struct thread **thread, + pcep_ctrl_thread_callback timer_cb) +{ + PCEP_DEBUG("Schedule PCEPLIB_TIMER for %us", delay); + schedule_thread_timer_with_cb(ctrl_state, 0, TM_PCEPLIB_TIMER, + TO_UNDEFINED, delay, payload, thread, + timer_cb); +} + +void pcep_thread_schedule_session_timeout(struct ctrl_state *ctrl_state, + int pcc_id, int delay, + struct thread **thread) +{ + PCEP_DEBUG("Schedule session_timeout interval for %us", delay); + schedule_thread_timer(ctrl_state, pcc_id, TM_SESSION_TIMEOUT_PCC, + TO_UNDEFINED, delay, NULL, thread); +} + +int pcep_thread_pcc_count(struct ctrl_state *ctrl_state) +{ + if (ctrl_state == NULL) { + return 0; + } + + return ctrl_state->pcc_count; +} + +int pcep_thread_refine_path(struct ctrl_state *ctrl_state, int pcc_id, + pcep_refine_callback_t cb, struct path *path, + void *payload) +{ + struct pcep_refine_path_event_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->ctrl_state = ctrl_state; + data->path = path; + data->pcc_id = pcc_id; + data->continue_lsp_update_handler = cb; + data->payload = payload; + + thread_add_event(ctrl_state->main, pcep_refine_path_event_cb, + (void *)data, 0, NULL); + return 0; +} + +void pcep_thread_path_refined_event(struct ctrl_state *ctrl_state, + struct pcep_refine_path_event_data *data) +{ + assert(data != NULL); + int pcc_id = data->pcc_id; + pcep_refine_callback_t continue_lsp_update_handler = data->continue_lsp_update_handler; + assert(continue_lsp_update_handler != NULL); + struct path *path = data->path; + void *payload = data->payload; + struct pcc_state *pcc_state = NULL; + XFREE(MTYPE_PCEP, data); + + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + continue_lsp_update_handler(ctrl_state, pcc_state, path, payload); +} + + +/* ------------ Internal Functions Called From Controller Thread ------------ */ + +void pcep_thread_finish_event_handler(struct thread *thread) +{ + int i; + struct frr_pthread *fpt = THREAD_ARG(thread); + struct ctrl_state *ctrl_state = fpt->data; + + assert(ctrl_state != NULL); + + for (i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + pcep_pcc_finalize(ctrl_state, ctrl_state->pcc[i]); + ctrl_state->pcc[i] = NULL; + } + } + + XFREE(MTYPE_PCEP, ctrl_state->pcc_opts); + XFREE(MTYPE_PCEP, ctrl_state); + fpt->data = NULL; + + atomic_store_explicit(&fpt->running, false, memory_order_relaxed); +} + +/* ------------ Controller Thread Timer Handler ------------ */ + +int schedule_thread_timer_with_cb(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timer_type timer_type, + enum pcep_ctrl_timeout_type timeout_type, + uint32_t delay, void *payload, + struct thread **thread, + pcep_ctrl_thread_callback timer_cb) +{ + assert(thread != NULL); + + struct pcep_ctrl_timer_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->ctrl_state = ctrl_state; + data->timer_type = timer_type; + data->timeout_type = timeout_type; + data->pcc_id = pcc_id; + data->payload = payload; + + thread_add_timer(ctrl_state->self, timer_cb, (void *)data, delay, + thread); + + return 0; +} + +int schedule_thread_timer(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timer_type timer_type, + enum pcep_ctrl_timeout_type timeout_type, + uint32_t delay, void *payload, struct thread **thread) +{ + return schedule_thread_timer_with_cb(ctrl_state, pcc_id, timer_type, + timeout_type, delay, payload, + thread, pcep_thread_timer_handler); +} + +void pcep_thread_timer_handler(struct thread *thread) +{ + /* data unpacking */ + struct pcep_ctrl_timer_data *data = THREAD_ARG(thread); + assert(data != NULL); + struct ctrl_state *ctrl_state = data->ctrl_state; + assert(ctrl_state != NULL); + enum pcep_ctrl_timer_type timer_type = data->timer_type; + enum pcep_ctrl_timeout_type timeout_type = data->timeout_type; + int pcc_id = data->pcc_id; + void *param = data->payload; + XFREE(MTYPE_PCEP, data); + + struct pcc_state *pcc_state = NULL; + + switch (timer_type) { + case TM_RECONNECT_PCC: + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (!pcc_state) + return; + pcep_pcc_reconnect(ctrl_state, pcc_state); + break; + case TM_TIMEOUT: + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (!pcc_state) + return; + pcep_pcc_timeout_handler(ctrl_state, pcc_state, timeout_type, + param); + break; + case TM_CALCULATE_BEST_PCE: + /* Previous best disconnect so new best should be synced */ + pcep_pcc_timer_update_best_pce(ctrl_state, pcc_id); + break; + case TM_SESSION_TIMEOUT_PCC: + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_thread_remove_candidate_path_segments(ctrl_state, + pcc_state); + break; + default: + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Unknown controller timer triggered: %u", timer_type); + break; + } +} + +void pcep_thread_pcep_event(struct thread *thread) +{ + struct pcep_ctrl_event_data *data = THREAD_ARG(thread); + assert(data != NULL); + struct ctrl_state *ctrl_state = data->ctrl_state; + pcep_event *event = data->payload; + XFREE(MTYPE_PCEP, data); + int i; + + for (i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + struct pcc_state *pcc_state = ctrl_state->pcc[i]; + if (pcc_state->sess != event->session) + continue; + pcep_pcc_pcep_event_handler(ctrl_state, pcc_state, + event); + break; + } + } + destroy_pcep_event(event); +} + +/* ------------ Controller Thread Socket Functions ------------ */ + +int schedule_thread_socket(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_socket_type type, bool is_read, + void *payload, int fd, struct thread **thread, + pcep_ctrl_thread_callback socket_cb) +{ + assert(thread != NULL); + + struct pcep_ctrl_socket_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->ctrl_state = ctrl_state; + data->type = type; + data->is_read = is_read; + data->fd = fd; + data->pcc_id = pcc_id; + data->payload = payload; + + if (is_read) { + thread_add_read(ctrl_state->self, socket_cb, (void *)data, fd, + thread); + } else { + thread_add_write(ctrl_state->self, socket_cb, (void *)data, fd, + thread); + } + + return 0; +} + +int pcep_thread_socket_write(void *fpt, void **thread, int fd, void *payload, + pcep_ctrl_thread_callback socket_cb) +{ + struct ctrl_state *ctrl_state = ((struct frr_pthread *)fpt)->data; + + return schedule_thread_socket(ctrl_state, 0, SOCK_PCEPLIB, false, + payload, fd, (struct thread **)thread, + socket_cb); +} + +int pcep_thread_socket_read(void *fpt, void **thread, int fd, void *payload, + pcep_ctrl_thread_callback socket_cb) +{ + struct ctrl_state *ctrl_state = ((struct frr_pthread *)fpt)->data; + + return schedule_thread_socket(ctrl_state, 0, SOCK_PCEPLIB, true, + payload, fd, (struct thread **)thread, + socket_cb); +} + +int pcep_thread_send_ctrl_event(void *fpt, void *payload, + pcep_ctrl_thread_callback cb) +{ + struct ctrl_state *ctrl_state = ((struct frr_pthread *)fpt)->data; + + return send_to_thread_with_cb(ctrl_state, 0, EV_PCEPLIB_EVENT, 0, + payload, cb); +} + +/* ------------ Controller Thread Event Handler ------------ */ + +int send_to_thread(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_event_type type, uint32_t sub_type, + void *payload) +{ + return send_to_thread_with_cb(ctrl_state, pcc_id, type, sub_type, + payload, pcep_thread_event_handler); +} + +int send_to_thread_with_cb(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_event_type type, uint32_t sub_type, + void *payload, pcep_ctrl_thread_callback event_cb) +{ + struct pcep_ctrl_event_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->ctrl_state = ctrl_state; + data->type = type; + data->sub_type = sub_type; + data->pcc_id = pcc_id; + data->payload = payload; + + thread_add_event(ctrl_state->self, event_cb, (void *)data, 0, NULL); + + return 0; +} + +void pcep_thread_event_handler(struct thread *thread) +{ + /* data unpacking */ + struct pcep_ctrl_event_data *data = THREAD_ARG(thread); + assert(data != NULL); + struct ctrl_state *ctrl_state = data->ctrl_state; + assert(ctrl_state != NULL); + enum pcep_ctrl_event_type type = data->type; + uint32_t sub_type = data->sub_type; + int pcc_id = data->pcc_id; + void *payload = data->payload; + XFREE(MTYPE_PCEP, data); + + /* Possible sub-type values */ + enum pcep_pathd_event_type path_event_type = PCEP_PATH_UNDEFINED; + + /* Possible payload values, maybe an union would be better... */ + struct path *path = NULL; + struct pcc_opts *pcc_opts = NULL; + struct pce_opts *pce_opts = NULL; + struct pcc_state *pcc_state = NULL; + struct pcep_refine_path_event_data *refine_data = NULL; + + struct path *path_copy = NULL; + struct pcep_error *error = NULL; + + switch (type) { + case EV_UPDATE_PCC_OPTS: + assert(payload != NULL); + pcc_opts = (struct pcc_opts *)payload; + pcep_thread_event_update_pcc_options(ctrl_state, pcc_opts); + break; + case EV_UPDATE_PCE_OPTS: + assert(payload != NULL); + pce_opts = (struct pce_opts *)payload; + pcep_thread_event_update_pce_options(ctrl_state, pcc_id, + pce_opts); + break; + case EV_REMOVE_PCC: + pce_opts = (struct pce_opts *)payload; + if (pcep_thread_event_remove_pcc(ctrl_state, pce_opts) == 0) + pcep_pcc_multi_pce_remove_pcc(ctrl_state, + ctrl_state->pcc); + break; + case EV_PATHD_EVENT: + assert(payload != NULL); + path_event_type = (enum pcep_pathd_event_type)sub_type; + path = (struct path *)payload; + pcep_thread_event_pathd_event(ctrl_state, path_event_type, + path); + break; + case EV_SYNC_PATH: + assert(payload != NULL); + path = (struct path *)payload; + pcep_pcc_multi_pce_sync_path(ctrl_state, pcc_id, + ctrl_state->pcc); + pcep_thread_event_sync_path(ctrl_state, pcc_id, path); + break; + case EV_SYNC_DONE: + pcep_thread_event_sync_done(ctrl_state, pcc_id); + break; + case EV_RESET_PCC_SESSION: + pcc_state = pcep_pcc_get_pcc_by_name(ctrl_state->pcc, + (const char *)payload); + if (pcc_state) { + pcep_pcc_disable(ctrl_state, pcc_state); + pcep_pcc_enable(ctrl_state, pcc_state); + } else { + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Cannot reset state for PCE: %s", + (const char *)payload); + } + break; + case EV_SEND_REPORT: + assert(payload != NULL); + path = (struct path *)payload; + if (pcc_id == 0) { + for (int i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + path_copy = pcep_copy_path(path); + pcep_pcc_send_report( + ctrl_state, ctrl_state->pcc[i], + path_copy, (bool)sub_type); + } + } + } else { + pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_pcc_send_report(ctrl_state, pcc_state, path, + (bool)sub_type); + } + break; + case EV_PATH_REFINED: + assert(payload != NULL); + refine_data = (struct pcep_refine_path_event_data *)payload; + pcep_thread_path_refined_event(ctrl_state, refine_data); + break; + case EV_SEND_ERROR: + assert(payload != NULL); + error = (struct pcep_error *)payload; + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_pcc_send_error(ctrl_state, pcc_state, error, + (bool)sub_type); + break; + default: + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Unexpected event received in controller thread: %u", + type); + break; + } +} + +int pcep_thread_event_update_pcc_options(struct ctrl_state *ctrl_state, + struct pcc_opts *opts) +{ + assert(opts != NULL); + if (ctrl_state->pcc_opts != NULL) { + XFREE(MTYPE_PCEP, ctrl_state->pcc_opts); + } + ctrl_state->pcc_opts = opts; + return 0; +} + +int pcep_thread_event_update_pce_options(struct ctrl_state *ctrl_state, + int pcc_id, struct pce_opts *pce_opts) +{ + if (!pce_opts || !ctrl_state) { + return 0; + } + struct pcc_state *pcc_state; + struct pcc_opts *pcc_opts; + + int current_pcc_id = + pcep_pcc_get_pcc_id_by_ip_port(ctrl_state->pcc, pce_opts); + if (current_pcc_id) { + pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, current_pcc_id); + } else { + pcc_state = pcep_pcc_initialize(ctrl_state, + get_next_id(ctrl_state)); + if (set_pcc_state(ctrl_state, pcc_state)) { + XFREE(MTYPE_PCEP, pcc_state); + return 0; + } + } + + /* Copy the pcc options to delegate it to the update function */ + pcc_opts = XCALLOC(MTYPE_PCEP, sizeof(*pcc_opts)); + memcpy(pcc_opts, ctrl_state->pcc_opts, sizeof(*pcc_opts)); + + if (pcep_pcc_update(ctrl_state, pcc_state, pcc_opts, pce_opts)) { + flog_err(EC_PATH_PCEP_PCC_CONF_UPDATE, + "failed to update PCC configuration"); + } + + + return 0; +} + +int pcep_thread_event_remove_pcc_by_id(struct ctrl_state *ctrl_state, + int pcc_id) +{ + if (pcc_id) { + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (pcc_state) { + remove_pcc_state(ctrl_state, pcc_state); + pcep_pcc_finalize(ctrl_state, pcc_state); + } + } + return 0; +} + +int pcep_thread_event_remove_pcc_all(struct ctrl_state *ctrl_state) +{ + assert(ctrl_state != NULL); + + for (int i = 0; i < MAX_PCC; i++) { + pcep_thread_event_remove_pcc_by_id( + ctrl_state, + pcep_pcc_get_pcc_id_by_idx(ctrl_state->pcc, i)); + } + return 0; +} + +int pcep_thread_event_remove_pcc(struct ctrl_state *ctrl_state, + struct pce_opts *pce_opts) +{ + assert(ctrl_state != NULL); + + if (pce_opts) { + int pcc_id = pcep_pcc_get_pcc_id_by_ip_port(ctrl_state->pcc, + pce_opts); + if (pcc_id) { + pcep_thread_event_remove_pcc_by_id(ctrl_state, pcc_id); + } else { + return -1; + } + XFREE(MTYPE_PCEP, pce_opts); + } else { + pcep_thread_event_remove_pcc_all(ctrl_state); + } + + return 0; +} + +int pcep_thread_event_sync_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path) +{ + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_pcc_sync_path(ctrl_state, pcc_state, path); + pcep_free_path(path); + return 0; +} + +int pcep_thread_event_sync_done(struct ctrl_state *ctrl_state, int pcc_id) +{ + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_pcc_sync_done(ctrl_state, pcc_state); + return 0; +} + +int pcep_thread_event_pathd_event(struct ctrl_state *ctrl_state, + enum pcep_pathd_event_type type, + struct path *path) +{ + int i; + + for (i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + struct pcc_state *pcc_state = ctrl_state->pcc[i]; + pcep_pcc_pathd_event_handler(ctrl_state, pcc_state, + type, path); + } + } + + pcep_free_path(path); + + return 0; +} + + +/* ------------ Main Thread Event Handler ------------ */ + +int send_to_main(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_main_event_type type, void *payload) +{ + struct pcep_main_event_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->handler = ctrl_state->main_event_handler; + data->type = type; + data->pcc_id = pcc_id; + data->payload = payload; + + thread_add_event(ctrl_state->main, pcep_main_event_handler, + (void *)data, 0, NULL); + return 0; +} + +void pcep_main_event_handler(struct thread *thread) +{ + /* data unpacking */ + struct pcep_main_event_data *data = THREAD_ARG(thread); + assert(data != NULL); + pcep_main_event_handler_t handler = data->handler; + enum pcep_main_event_type type = data->type; + int pcc_id = data->pcc_id; + void *payload = data->payload; + XFREE(MTYPE_PCEP, data); + + handler(type, pcc_id, payload); +} + + +/* ------------ Helper functions ------------ */ + +void set_ctrl_state(struct frr_pthread *fpt, struct ctrl_state *ctrl_state) +{ + assert(fpt != NULL); + fpt->data = ctrl_state; +} + +struct ctrl_state *get_ctrl_state(struct frr_pthread *fpt) +{ + assert(fpt != NULL); + assert(fpt->data != NULL); + + struct ctrl_state *ctrl_state; + ctrl_state = (struct ctrl_state *)fpt->data; + assert(ctrl_state != NULL); + return ctrl_state; +} + +int get_next_id(struct ctrl_state *ctrl_state) +{ + return ++ctrl_state->pcc_last_id; +} + +int set_pcc_state(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state) +{ + assert(ctrl_state != NULL); + assert(pcep_pcc_get_pcc_id(pcc_state) != 0); + + int current_pcc_idx = pcep_pcc_get_free_pcc_idx(ctrl_state->pcc); + if (current_pcc_idx >= 0) { + ctrl_state->pcc[current_pcc_idx] = pcc_state; + ctrl_state->pcc_count++; + PCEP_DEBUG("added pce pcc_id (%d) idx (%d)", + pcep_pcc_get_pcc_id(pcc_state), current_pcc_idx); + return 0; + } else { + PCEP_DEBUG("Max number of pce "); + return 1; + } +} + +void remove_pcc_state(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + assert(ctrl_state != NULL); + assert(pcep_pcc_get_pcc_id(pcc_state) != 0); + + int idx = 0; + idx = pcep_pcc_get_pcc_idx_by_id(ctrl_state->pcc, + pcep_pcc_get_pcc_id(pcc_state)); + if (idx != -1) { + ctrl_state->pcc[idx] = NULL; + ctrl_state->pcc_count--; + PCEP_DEBUG("removed pce pcc_id (%d)", + pcep_pcc_get_pcc_id(pcc_state)); + } +} + +uint32_t backoff_delay(uint32_t max, uint32_t base, uint32_t retry_count) +{ + uint32_t a = MIN(max, base * (1 << retry_count)); + uint64_t r = frr_weak_random(), m = RAND_MAX; + uint32_t b = (a / 2) + (r * (a / 2)) / m; + return b; +} + +const char *timer_type_name(enum pcep_ctrl_timer_type type) +{ + switch (type) { + case TM_UNDEFINED: + return "UNDEFINED"; + case TM_RECONNECT_PCC: + return "RECONNECT_PCC"; + case TM_PCEPLIB_TIMER: + return "PCEPLIB_TIMER"; + case TM_TIMEOUT: + return "TIMEOUT"; + default: + return "UNKNOWN"; + } +}; + +const char *timeout_type_name(enum pcep_ctrl_timeout_type type) +{ + switch (type) { + case TO_UNDEFINED: + return "UNDEFINED"; + case TO_COMPUTATION_REQUEST: + return "COMPUTATION_REQUEST"; + default: + return "UNKNOWN"; + } +} diff --git a/pathd/path_pcep_controller.h b/pathd/path_pcep_controller.h new file mode 100644 index 0000000..bc7ed49 --- /dev/null +++ b/pathd/path_pcep_controller.h @@ -0,0 +1,184 @@ +/* + * 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 + */ + +#ifndef _PATH_PCEP_CONTROLLER_H_ +#define _PATH_PCEP_CONTROLLER_H_ + +#include "pathd/path_pcep.h" + +struct ctrl_state; +struct pcc_state; + +enum pcep_main_event_type { + PCEP_MAIN_EVENT_UNDEFINED = 0, + PCEP_MAIN_EVENT_START_SYNC, + PCEP_MAIN_EVENT_INITIATE_CANDIDATE, + PCEP_MAIN_EVENT_UPDATE_CANDIDATE, + PCEP_MAIN_EVENT_REMOVE_CANDIDATE_LSP, +}; + +typedef int (*pcep_main_event_handler_t)(enum pcep_main_event_type type, + int pcc_id, void *payload); +typedef void (*pcep_refine_callback_t)(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct path *path, void *payload); + +enum pcep_pathd_event_type { + PCEP_PATH_UNDEFINED = 0, + PCEP_PATH_CREATED, + PCEP_PATH_UPDATED, + PCEP_PATH_REMOVED +}; + +struct ctrl_state { + struct thread_master *main; + struct thread_master *self; + pcep_main_event_handler_t main_event_handler; + struct pcc_opts *pcc_opts; + int pcc_count; + int pcc_last_id; + struct pcc_state *pcc[MAX_PCC]; +}; + +/* Timer handling data structures */ + +enum pcep_ctrl_timeout_type { TO_UNDEFINED, TO_COMPUTATION_REQUEST, TO_MAX }; + +enum pcep_ctrl_timer_type { + TM_UNDEFINED, + TM_RECONNECT_PCC, + TM_PCEPLIB_TIMER, + TM_TIMEOUT, + TM_CALCULATE_BEST_PCE, + TM_SESSION_TIMEOUT_PCC, + TM_MAX +}; + +struct pcep_ctrl_timer_data { + struct ctrl_state *ctrl_state; + enum pcep_ctrl_timer_type timer_type; + enum pcep_ctrl_timeout_type timeout_type; + int pcc_id; + void *payload; +}; + +/* Socket handling data structures */ + +enum pcep_ctrl_socket_type { SOCK_PCEPLIB = 1 }; + +struct pcep_ctrl_socket_data { + struct ctrl_state *ctrl_state; + enum pcep_ctrl_socket_type type; + bool is_read; + int fd; + int pcc_id; + void *payload; +}; + +typedef void (*pcep_ctrl_thread_callback)(struct thread *); + +/* PCC connection information, populated in a thread-safe + * manner with pcep_ctrl_get_pcc_info() */ +struct pcep_pcc_info { + struct ctrl_state *ctrl_state; /* will be NULL when returned */ + char pce_name[64]; + int pcc_id; + struct ipaddr pcc_addr; + uint16_t pcc_port; + int status; + short msd; + uint32_t next_reqid; + uint32_t next_plspid; + bool is_best_multi_pce; + bool previous_best; + uint8_t precedence; +}; + +/* Functions called from the main thread */ +int pcep_ctrl_initialize(struct thread_master *main_thread, + struct frr_pthread **fpt, + pcep_main_event_handler_t event_handler); +int pcep_ctrl_finalize(struct frr_pthread **fpt); +int pcep_ctrl_update_pcc_options(struct frr_pthread *fpt, + struct pcc_opts *opts); +int pcep_ctrl_update_pce_options(struct frr_pthread *fpt, + struct pce_opts *opts); +int pcep_ctrl_remove_pcc(struct frr_pthread *fpt, struct pce_opts *pce_opts); +int pcep_ctrl_reset_pcc_session(struct frr_pthread *fpt, char *pce_name); +int pcep_ctrl_pathd_event(struct frr_pthread *fpt, + enum pcep_pathd_event_type type, struct path *path); +int pcep_ctrl_sync_path(struct frr_pthread *fpt, int pcc_id, struct path *path); +int pcep_ctrl_sync_done(struct frr_pthread *fpt, int pcc_id); +struct counters_group *pcep_ctrl_get_counters(struct frr_pthread *fpt, + int pcc_id); +pcep_session *pcep_ctrl_get_pcep_session(struct frr_pthread *fpt, int pcc_id); +struct pcep_pcc_info *pcep_ctrl_get_pcc_info(struct frr_pthread *fpt, + const char *pce_name); + +/* Asynchronously send a report. The caller is giving away the path structure, + * it shouldn't be allocated on the stack. If `pcc_id` is `0` the report is + * sent by all PCCs. The parameter is_stable is used to hint whether the status + * will soon change, this is used to ensure all report updates are sent even + * when missing status update events */ +int pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, + struct path *path, bool is_stable); + +int pcep_ctrl_send_error(struct frr_pthread *fpt, int pcc_id, + struct pcep_error *error); + +/* Functions called from the controller thread */ +void pcep_thread_start_sync(struct ctrl_state *ctrl_state, int pcc_id); +void pcep_thread_update_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path); +void pcep_thread_initiate_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path); +void pcep_thread_cancel_timer(struct thread **thread); +void pcep_thread_schedule_reconnect(struct ctrl_state *ctrl_state, int pcc_id, + int retry_count, struct thread **thread); +void pcep_thread_schedule_timeout(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timeout_type type, + uint32_t delay, void *param, + struct thread **thread); +void pcep_thread_schedule_session_timeout(struct ctrl_state *ctrl_state, + int pcc_id, int delay, + struct thread **thread); +void pcep_thread_remove_candidate_path_segments(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); + +void pcep_thread_schedule_sync_best_pce(struct ctrl_state *ctrl_state, + int pcc_id, int delay, + struct thread **thread); +void pcep_thread_schedule_pceplib_timer(struct ctrl_state *ctrl_state, + int delay, void *payload, + struct thread **thread, + pcep_ctrl_thread_callback cb); +int pcep_thread_socket_read(void *fpt, void **thread, int fd, void *payload, + pcep_ctrl_thread_callback cb); +int pcep_thread_socket_write(void *fpt, void **thread, int fd, void *payload, + pcep_ctrl_thread_callback cb); + +int pcep_thread_send_ctrl_event(void *fpt, void *payload, + pcep_ctrl_thread_callback cb); +void pcep_thread_pcep_event(struct thread *thread); +int pcep_thread_pcc_count(struct ctrl_state *ctrl_state); +/* Called by the PCC to refine a path in the main thread */ +int pcep_thread_refine_path(struct ctrl_state *ctrl_state, int pcc_id, + pcep_refine_callback_t cb, struct path *path, + void *payload); + +#endif // _PATH_PCEP_CONTROLLER_H_ diff --git a/pathd/path_pcep_debug.c b/pathd/path_pcep_debug.c new file mode 100644 index 0000000..b0802ae --- /dev/null +++ b/pathd/path_pcep_debug.c @@ -0,0 +1,1777 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include <string.h> +#include <stdbool.h> +#include <time.h> +#include <libyang/libyang.h> + +#include "printfrr.h" +#include "ipaddr.h" + +#include "pathd/path_pcep_debug.h" + +static void _format_pcc_opts(int ps, struct pcc_opts *ops); +static void _format_pce_opts(int ps, struct pce_opts *ops); +static void _format_pcc_caps(int ps, struct pcep_caps *caps); +static void _format_pcc_state(int ps, struct pcc_state *state); +static void _format_ctrl_state(int ps, struct ctrl_state *state); +static void _format_path(int ps, struct path *path); +static void _format_path_hop(int ps, struct path_hop *hop); +static void _format_path_metric(int ps, struct path_metric *metric); +static void _format_pcep_event(int ps, pcep_event *event); +static void _format_pcep_message(int ps, struct pcep_message *msg); +static void _format_pcep_objects(int ps, double_linked_list *objs); +static void _format_pcep_object(int ps, struct pcep_object_header *obj); +static void _format_pcep_object_details(int ps, struct pcep_object_header *obj); +static void _format_pcep_object_error(int ps, struct pcep_object_error *obj); +static void _format_pcep_object_open(int ps, struct pcep_object_open *obj); +static void _format_pcep_object_rp(int ps, struct pcep_object_rp *obj); +static void _format_pcep_object_srp(int ps, struct pcep_object_srp *obj); +static void _format_pcep_object_lsp(int psps, struct pcep_object_lsp *obj); +static void _format_pcep_object_lspa(int psps, struct pcep_object_lspa *obj); +static void +_format_pcep_object_ipv4_endpoint(int ps, + struct pcep_object_endpoints_ipv4 *obj); +static void _format_pcep_object_metric(int ps, struct pcep_object_metric *obj); +static void _format_pcep_object_bandwidth(int ps, + struct pcep_object_bandwidth *obj); +static void _format_pcep_object_nopath(int ps, struct pcep_object_nopath *obj); +static void +_format_pcep_object_objfun(int ps, struct pcep_object_objective_function *obj); +static void _format_pcep_object_ro(int ps, struct pcep_object_ro *obj); +static void _format_pcep_object_ro_details(int ps, + struct pcep_object_ro_subobj *ro); +static void _format_pcep_object_ro_ipv4(int ps, + struct pcep_ro_subobj_ipv4 *obj); +static void _format_pcep_object_ro_sr(int ps, struct pcep_ro_subobj_sr *obj); +static void _format_pcep_object_tlvs(int ps, struct pcep_object_header *obj); +static void _format_pcep_object_tlv(int ps, + struct pcep_object_tlv_header *tlv_header); +static void +_format_pcep_object_tlv_details(int ps, + struct pcep_object_tlv_header *tlv_header); +static void _format_pcep_object_tlv_symbolic_path_name( + int ps, struct pcep_object_tlv_symbolic_path_name *tlv); +static void _format_pcep_object_tlv_stateful_pce_capability( + int ps, struct pcep_object_tlv_stateful_pce_capability *tlv); +static void _format_pcep_object_tlv_sr_pce_capability( + int ps, struct pcep_object_tlv_sr_pce_capability *tlv); +static void _format_pcep_object_tlv_path_setup_type( + int ps, struct pcep_object_tlv_path_setup_type *tlv); + +const char *pcc_status_name(enum pcc_status status) +{ + switch (status) { + case PCEP_PCC_INITIALIZED: + return "INITIALIZED"; + case PCEP_PCC_DISCONNECTED: + return "DISCONNECTED"; + case PCEP_PCC_CONNECTING: + return "CONNECTING"; + case PCEP_PCC_SYNCHRONIZING: + return "SYNCHRONIZING"; + case PCEP_PCC_OPERATING: + return "OPERATING"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_event_type_name(pcep_event_type event_type) +{ + switch (event_type) { + case MESSAGE_RECEIVED: + return "MESSAGE_RECEIVED"; + case PCE_CLOSED_SOCKET: + return "PCE_CLOSED_SOCKET"; + case PCE_SENT_PCEP_CLOSE: + return "PCE_SENT_PCEP_CLOSE"; + case PCE_DEAD_TIMER_EXPIRED: + return "PCE_DEAD_TIMER_EXPIRED"; + case PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED: + return "PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED"; + case PCC_CONNECTED_TO_PCE: + return "PCC_CONNECTED_TO_PCE"; + case PCC_PCEP_SESSION_CLOSED: + return "PCC_PCEP_SESSION_CLOSED"; + case PCC_RCVD_INVALID_OPEN: + return "PCC_RCVD_INVALID_OPEN"; + case PCC_RCVD_MAX_INVALID_MSGS: + return "PCC_RCVD_MAX_INVALID_MSGS"; + case PCC_RCVD_MAX_UNKOWN_MSGS: + return "PCC_RCVD_MAX_UNKOWN_MSGS"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_error_type_name(enum pcep_error_type error_type) +{ + switch (error_type) { + + case PCEP_ERRT_SESSION_FAILURE: + return "SESSION_FAILURE"; + case PCEP_ERRT_CAPABILITY_NOT_SUPPORTED: + return "CAPABILITY_NOT_SUPPORTED"; + case PCEP_ERRT_UNKNOW_OBJECT: + return "UNKNOW_OBJECT"; + case PCEP_ERRT_NOT_SUPPORTED_OBJECT: + return "NOT_SUPPORTED_OBJECT"; + case PCEP_ERRT_POLICY_VIOLATION: + return "POLICY_VIOLATION"; + case PCEP_ERRT_MANDATORY_OBJECT_MISSING: + return "MANDATORY_OBJECT_MISSING"; + case PCEP_ERRT_SYNC_PC_REQ_MISSING: + return "SYNC_PC_REQ_MISSING"; + case PCEP_ERRT_UNKNOWN_REQ_REF: + return "UNKNOWN_REQ_REF"; + case PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION: + return "ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION"; + case PCEP_ERRT_RECEPTION_OF_INV_OBJECT: + return "RECEPTION_OF_INV_OBJECT"; + case PCEP_ERRT_UNRECOGNIZED_EXRS_SUBOBJ: + return "UNRECOGNIZED_EXRS_SUBOBJ"; + case PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR: + return "DIFFSERV_AWARE_TE_ERROR"; + case PCEP_ERRT_BRPC_PROC_COMPLETION_ERROR: + return "BRPC_PROC_COMPLETION_ERROR"; + case PCEP_ERRT_UNASSIGNED14: + return "UNASSIGNED14"; + case PCEP_ERRT_GLOBAL_CONCURRENT_ERROR: + return "GLOBAL_CONCURRENT_ERROR"; + case PCEP_ERRT_P2PMP_CAP_ERROR: + return "P2PMP_CAP_ERROR"; + case PCEP_ERRT_P2P_ENDPOINTS_ERROR: + return "P2P_ENDPOINTS_ERROR"; + case PCEP_ERRT_P2P_FRAGMENTATION_ERROR: + return "P2P_FRAGMENTATION_ERROR"; + case PCEP_ERRT_INVALID_OPERATION: + return "INVALID_OPERATION"; + case PCEP_ERRT_LSP_STATE_SYNC_ERROR: + return "LSP_STATE_SYNC_ERROR"; + case PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE: + return "INVALID_TE_PATH_SETUP_TYPE"; + case PCEP_ERRT_UNASSIGNED22: + return "UNASSIGNED22"; + case PCEP_ERRT_BAD_PARAMETER_VALUE: + return "BAD_PARAMETER_VALUE"; + case PCEP_ERRT_LSP_INSTANTIATE_ERROR: + return "LSP_INSTANTIATE_ERROR"; + case PCEP_ERRT_START_TLS_FAILURE: + return "START_TLS_FAILURE"; + case PCEP_ERRT_ASSOCIATION_ERROR: + return "ASSOCIATION_ERROR"; + case PCEP_ERRT_WSON_RWA_ERROR: + return "WSON_RWA_ERROR"; + case PCEP_ERRT_H_PCE_ERROR: + return "H_PCE_ERROR"; + case PCEP_ERRT_PATH_COMP_FAILURE: + return "PATH_COMP_FAILURE"; + case PCEP_ERRT_UNASSIGNED30: + return "UNASSIGNED30"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_error_value_name(enum pcep_error_type error_type, + enum pcep_error_value error_value) +{ + switch (TUP(error_type, error_value)) { + + case TUP(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, PCEP_ERRV_UNASSIGNED): + case TUP(PCEP_ERRT_SYNC_PC_REQ_MISSING, PCEP_ERRV_UNASSIGNED): + case TUP(PCEP_ERRT_UNKNOWN_REQ_REF, PCEP_ERRV_UNASSIGNED): + case TUP(PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION, + PCEP_ERRV_UNASSIGNED): + case TUP(PCEP_ERRT_UNRECOGNIZED_EXRS_SUBOBJ, PCEP_ERRV_UNASSIGNED): + return "UNASSIGNED"; + + case TUP(PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_RECVD_INVALID_OPEN_MSG): + return "RECVD_INVALID_OPEN_MSG"; + case TUP(PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_OPENWAIT_TIMED_OUT): + return "OPENWAIT_TIMED_OUT"; + case TUP(PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NO_NEG): + return "UNACCEPTABLE_OPEN_MSG_NO_NEG"; + case TUP(PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG): + return "UNACCEPTABLE_OPEN_MSG_NEG"; + case TUP(PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_RECVD_SECOND_OPEN_MSG_UNACCEPTABLE): + return "RECVD_SECOND_OPEN_MSG_UNACCEPTABLE"; + case TUP(PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_RECVD_PCERR): + return "RECVD_PCERR"; + case TUP(PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_KEEPALIVEWAIT_TIMED_OUT): + return "KEEPALIVEWAIT_TIMED_OUT"; + case TUP(PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_PCEP_VERSION_NOT_SUPPORTED): + return "PCEP_VERSION_NOT_SUPPORTED"; + + case TUP(PCEP_ERRT_UNKNOW_OBJECT, PCEP_ERRV_UNREC_OBJECT_CLASS): + return "UNREC_OBJECT_CLASS"; + case TUP(PCEP_ERRT_UNKNOW_OBJECT, PCEP_ERRV_UNREC_OBJECT_TYPE): + return "UNREC_OBJECT_TYPE"; + + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_NOT_SUPPORTED_OBJECT_CLASS): + return "NOT_SUPPORTED_OBJECT_CLASS"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_NOT_SUPPORTED_OBJECT_TYPE): + return "NOT_SUPPORTED_OBJECT_TYPE"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, PCEP_ERRV_UNSUPPORTED_PARAM): + return "UNSUPPORTED_PARAM"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_UNSUPPORTED_NW_PERF_CONSTRAINT): + return "UNSUPPORTED_NW_PERF_CONSTRAINT"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_NOT_SUPPORTED_BW_OBJECT_3_4): + return "NOT_SUPPORTED_BW_OBJECT_3_4"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_UNSUPPORTED_ENDPOINT_TYPE): + return "UNSUPPORTED_ENDPOINT_TYPE"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_UNSUPPORTED_ENDPOINT_TLV): + return "UNSUPPORTED_ENDPOINT_TLV"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_UNSUPPORTED_RP_FLAG_GRANULARITY): + return "UNSUPPORTED_RP_FLAG_GRANULARITY"; + + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_C_BIT_SET_IN_METRIC_OBJECT): + return "C_BIT_SET_IN_METRIC_OBJECT"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_O_BIT_CLEARD_IN_RP_OBJECT): + return "O_BIT_CLEARD_IN_RP_OBJECT"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_OBJECTIVE_FUNC_NOT_ALLOWED): + return "OBJECTIVE_FUNC_NOT_ALLOWED"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, PCEP_ERRV_RP_OF_BIT_SET): + return "RP_OF_BIT_SET"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_GLOBAL_CONCURRENCY_NOT_ALLOWED): + return "GLOBAL_CONCURRENCY_NOT_ALLOWED"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, PCEP_ERRV_MONITORING_MSG_REJECTED): + return "MONITORING_MSG_REJECTED"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_P2MP_PATH_COMP_NOT_ALLOWED): + return "P2MP_PATH_COMP_NOT_ALLOWED"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_UNALLOWED_NW_PERF_CONSTRAINT): + return "UNALLOWED_NW_PERF_CONSTRAINT"; + + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_RP_OBJECT_MISSING): + return "RP_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_RRO_OBJECT_MISSING_FOR_REOP): + return "RRO_OBJECT_MISSING_FOR_REOP"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_EP_OBJECT_MISSING): + return "EP_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_MONITOR_OBJECT_MISSING): + return "MONITOR_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING): + return "LSP_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_ERO_OBJECT_MISSING): + return "ERO_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING): + return "SRP_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_ID_TLV_MISSING): + return "LSP_ID_TLV_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_DB_TLV_MISSING): + return "LSP_DB_TLV_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_S2LS_OBJECT_MISSING): + return "S2LS_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_P2MP_LSP_ID_TLV_MISSING): + return "P2MP_LSP_ID_TLV_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_DISJOINTED_CONF_TLV_MISSING): + return "DISJOINTED_CONF_TLV_MISSING"; + + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_P_FLAG_NOT_CORRECT_IN_OBJECT): + return "P_FLAG_NOT_CORRECT_IN_OBJECT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_BAD_LABEL_VALUE): + return "BAD_LABEL_VALUE"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_UNSUPPORTED_NUM_SR_ERO_SUBOBJECTS): + return "UNSUPPORTED_NUM_SR_ERO_SUBOBJECTS"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_BAD_LABEL_FORMAT): + return "BAD_LABEL_FORMAT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_ERO_SR_ERO_MIX): + return "ERO_SR_ERO_MIX"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_SR_ERO_SID_NAI_ABSENT): + return "SR_ERO_SID_NAI_ABSENT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_SR_RRO_SID_NAI_ABSENT): + return "SR_RRO_SID_NAI_ABSENT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_SYMBOLIC_PATH_NAME_TLV_MISSING): + return "SYMBOLIC_PATH_NAME_TLV_MISSING"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MSD_EXCEEDS_PCEP_SESSION_MAX): + return "MSD_EXCEEDS_PCEP_SESSION_MAX"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_RRO_SR_RRO_MIX): + return "RRO_SR_RRO_MIX"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_MALFORMED_OBJECT): + return "MALFORMED_OBJECT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MISSING_PCE_SR_CAP_TLV): + return "MISSING_PCE_SR_CAP_TLV"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_UNSUPPORTED_NAI): + return "UNSUPPORTED_NAI"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_UNKNOWN_SID): + return "UNKNOWN_SID"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_CANNOT_RESOLVE_NAI_TO_SID): + return "CANNOT_RESOLVE_NAI_TO_SID"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_COULD_NOT_FIND_SRGB): + return "COULD_NOT_FIND_SRGB"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_SID_EXCEEDS_SRGB): + return "SID_EXCEEDS_SRGB"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_COULD_NOT_FIND_SRLB): + return "COULD_NOT_FIND_SRLB"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_SID_EXCEEDS_SRLB): + return "SID_EXCEEDS_SRLB"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_INCONSISTENT_SID): + return "INCONSISTENT_SID"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MSD_MUST_BE_NONZERO): + return "MSD_MUST_BE_NONZERO"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MISMATCH_O_S2LS_LSP): + return "MISMATCH_O_S2LS_LSP"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_INCOMPATIBLE_H_PCE_OF): + return "INCOMPATIBLE_H_PCE_OF"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_BAD_BANDWIDTH_TYPE_3_4): + return "BAD_BANDWIDTH_TYPE_3_4"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_UNSUPPORTED_LSP_PROT_FLAGS): + return "UNSUPPORTED_LSP_PROT_FLAGS"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_UNSUPPORTED_2ND_LSP_PROT_FLAGS): + return "UNSUPPORTED_2ND_LSP_PROT_FLAGS"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_UNSUPPORTED_LINK_PROT_TYPE): + return "UNSUPPORTED_LINK_PROT_TYPE"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_LABEL_SET_TLV_NO_RP_R): + return "LABEL_SET_TLV_NO_RP_R"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_WRONG_LABEL_SET_TLV_O_L_SET): + return "WRONG_LABEL_SET_TLV_O_L_SET"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_WRONG_LABEL_SET_O_SET): + return "WRONG_LABEL_SET_O_SET"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MISSING_GMPLS_CAP_TLV): + return "MISSING_GMPLS_CAP_TLV"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_INCOMPATIBLE_OF_CODE): + return "INCOMPATIBLE_OF_CODE"; + + case TUP(PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR, + PCEP_ERRV_UNSUPPORTED_CLASS_TYPE): + return "UNSUPPORTED_CLASS_TYPE"; + case TUP(PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR, + PCEP_ERRV_INVALID_CLASS_TYPE): + return "INVALID_CLASS_TYPE"; + case TUP(PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR, + PCEP_ERRV_CLASS_SETUP_TYPE_NOT_TE_CLASS): + return "CLASS_SETUP_TYPE_NOT_TE_CLASS"; + + case TUP(PCEP_ERRT_BRPC_PROC_COMPLETION_ERROR, + PCEP_ERRV_BRPC_PROC_NOT_SUPPORTED): + return "BRPC_PROC_NOT_SUPPORTED"; + + case TUP(PCEP_ERRT_GLOBAL_CONCURRENT_ERROR, + PCEP_ERRV_INSUFFICIENT_MEMORY): + return "INSUFFICIENT_MEMORY"; + case TUP(PCEP_ERRT_GLOBAL_CONCURRENT_ERROR, + PCEP_ERRV_GLOBAL_CONCURRENT_OPT_NOT_SUPPORTED): + return "GLOBAL_CONCURRENT_OPT_NOT_SUPPORTED"; + + case TUP(PCEP_ERRT_P2PMP_CAP_ERROR, PCEP_ERRV_PCE_INSUFFICIENT_MEMORY): + return "PCE_INSUFFICIENT_MEMORY"; + case TUP(PCEP_ERRT_P2PMP_CAP_ERROR, + PCEP_ERRV_PCE_NOT_CAPABLE_P2MP_COMP): + return "PCE_NOT_CAPABLE_P2MP_COMP"; + + case TUP(PCEP_ERRT_P2P_ENDPOINTS_ERROR, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE2): + return "NO_EP_WITH_LEAF_TYPE2"; + case TUP(PCEP_ERRT_P2P_ENDPOINTS_ERROR, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE3): + return "NO_EP_WITH_LEAF_TYPE3"; + case TUP(PCEP_ERRT_P2P_ENDPOINTS_ERROR, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE4): + return "NO_EP_WITH_LEAF_TYPE4"; + case TUP(PCEP_ERRT_P2P_ENDPOINTS_ERROR, PCEP_ERRV_INCONSITENT_EP): + return "INCONSITENT_EP"; + + case TUP(PCEP_ERRT_P2P_FRAGMENTATION_ERROR, + PCEP_ERRV_FRAG_REQUEST_FAILURE): + return "FRAG_REQUEST_FAILURE"; + case TUP(PCEP_ERRT_P2P_FRAGMENTATION_ERROR, + PCEP_ERRV_FRAG_REPORT_FAILURE): + return "FRAG_REPORT_FAILURE"; + case TUP(PCEP_ERRT_P2P_FRAGMENTATION_ERROR, + PCEP_ERRV_FRAG_UPDATE_FAILURE): + return "FRAG_UPDATE_FAILURE"; + case TUP(PCEP_ERRT_P2P_FRAGMENTATION_ERROR, + PCEP_ERRV_FRAG_INSTANTIATION_FAILURE): + return "FRAG_INSTANTIATION_FAILURE"; + + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_UPDATE_FOR_NON_DELEGATED_LSP): + return "LSP_UPDATE_FOR_NON_DELEGATED_LS"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_UPDATE_NON_ADVERTISED_PCE): + return "LSP_UPDATE_NON_ADVERTISED_PC"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_UPDATE_UNKNOWN_PLSP_ID): + return "LSP_UPDATE_UNKNOWN_PLSP_I"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_REPORT_NON_ADVERTISED_PCE): + return "LSP_REPORT_NON_ADVERTISED_PC"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_PCE_INIT_LSP_LIMIT_REACHED): + return "PCE_INIT_LSP_LIMIT_REACHE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_PCE_INIT_LSP_DELEGATION_CANT_REVOKE): + return "PCE_INIT_LSP_DELEGATION_CANT_REVOK"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_INIT_NON_ZERO_PLSP_ID): + return "LSP_INIT_NON_ZERO_PLSP_I"; + case TUP(PCEP_ERRT_INVALID_OPERATION, PCEP_ERRV_LSP_NOT_PCE_INITIATED): + return "LSP_NOT_PCE_INITIATE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_PCE_INIT_OP_FREQ_LIMIT_REACHED): + return "PCE_INIT_OP_FREQ_LIMIT_REACHE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_REPORT_P2MP_NOT_ADVERTISED): + return "LSP_REPORT_P2MP_NOT_ADVERTISE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_UPDATE_P2MP_NOT_ADVERTISED): + return "LSP_UPDATE_P2MP_NOT_ADVERTISE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_INSTANTIATION_P2MP_NOT_ADVERTISED): + return "LSP_INSTANTIATION_P2MP_NOT_ADVERTISE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_AUTO_BW_CAP_NOT_ADVERTISED): + return "AUTO_BW_CAP_NOT_ADVERTISE"; + + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_PCE_CANT_PROCESS_LSP_REPORT): + return "PCE_CANT_PROCESS_LSP_REPORT"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_LSP_DB_VERSION_MISMATCH): + return "LSP_DB_VERSION_MISMATCH"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_TRIGGER_ATTEMPT_BEFORE_PCE_TRIGGER): + return "TRIGGER_ATTEMPT_BEFORE_PCE_TRIGGER"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_TRIGGER_ATTEMPT_NO_PCE_TRIGGER_CAP): + return "TRIGGER_ATTEMPT_NO_PCE_TRIGGER_CAP"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_PCC_CANT_COMPLETE_STATE_SYNC): + return "PCC_CANT_COMPLETE_STATE_SYNC"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_INVALID_LSP_DB_VERSION_NUMBER): + return "INVALID_LSP_DB_VERSION_NUMBER"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_INVALID_SPEAKER_ENTITY_ID): + return "INVALID_SPEAKER_ENTITY_ID"; + + case TUP(PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE, + PCEP_ERRV_UNSUPPORTED_PATH_SETUP_TYPE): + return "UNSUPPORTED_PATH_SETUP_TYPE"; + case TUP(PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE, + PCEP_ERRV_MISMATCHED_PATH_SETUP_TYPE): + return "MISMATCHED_PATH_SETUP_TYPE"; + + case TUP(PCEP_ERRT_BAD_PARAMETER_VALUE, + PCEP_ERRV_SYMBOLIC_PATH_NAME_IN_USE): + return "SYMBOLIC_PATH_NAME_IN_USE"; + case TUP(PCEP_ERRT_BAD_PARAMETER_VALUE, + PCEP_ERRV_LSP_SPEAKER_ID_NOT_PCE_INITIATED): + return "LSP_SPEAKER_ID_NOT_PCE_INITIATED"; + + case TUP(PCEP_ERRT_LSP_INSTANTIATE_ERROR, + PCEP_ERRV_UNACCEPTABLE_INSTANTIATE_ERROR): + return "UNACCEPTABLE_INSTANTIATE_ERROR"; + case TUP(PCEP_ERRT_LSP_INSTANTIATE_ERROR, PCEP_ERRV_INTERNAL_ERROR): + return "INTERNAL_ERROR"; + case TUP(PCEP_ERRT_LSP_INSTANTIATE_ERROR, PCEP_ERRV_SIGNALLING_ERROR): + return "SIGNALLING_ERROR"; + + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_START_TLS_AFTER_PCEP_EXCHANGE): + return "START_TLS_AFTER_PCEP_EXCHANGE"; + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_MSG_NOT_START_TLS_OPEN_ERROR): + return "MSG_NOT_START_TLS_OPEN_ERROR"; + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_CONNECTION_WO_TLS_NOT_POSSIBLE): + return "CONNECTION_WO_TLS_NOT_POSSIBLE"; + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_CONNECTION_WO_TLS_IS_POSSIBLE): + return "CONNECTION_WO_TLS_IS_POSSIBLE"; + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_NO_START_TLS_BEFORE_START_TLS_WAIT_TIMER): + return "NO_START_TLS_BEFORE_START_TLS_WAIT_TIMER"; + + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_ASSOC_TYPE_NOT_SUPPORTED): + return "ASSOC_TYPE_NOT_SUPPORTED"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_TOO_MANY_LSPS_IN_ASSOC_GRP): + return "TOO_MANY_LSPS_IN_ASSOC_GRP"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, PCEP_ERRV_TOO_MANY_ASSOC_GROUPS): + return "TOO_MANY_ASSOC_GROUPS"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, PCEP_ERRV_ASSOCIATION_UNKNOWN): + return "ASSOCIATION_UNKNOWN"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_OP_CONF_ASSOC_INFO_MISMATCH): + return "OP_CONF_ASSOC_INFO_MISMATCH"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, PCEP_ERRV_ASSOC_INFO_MISMATCH): + return "ASSOC_INFO_MISMATCH"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_CANNOT_JOIN_ASSOC_GROUP): + return "CANNOT_JOIN_ASSOC_GROUP"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, PCEP_ERRV_ASSOC_ID_NOT_IN_RANGE): + return "ASSOC_ID_NOT_IN_RANGE"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_TUNNEL_EP_MISMATCH_PATH_PROT_ASSOC): + return "TUNNEL_EP_MISMATCH_PATH_PROT_ASSOC"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_ATTEMPTED_ADD_LSP_PATH_PROT_ASSOC): + return "ATTEMPTED_ADD_LSP_PATH_PROT_ASSOC"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_PROTECTION_TYPE_NOT_SUPPORTED): + return "PROTECTION_TYPE_NOT_SUPPORTED"; + + case TUP(PCEP_ERRT_WSON_RWA_ERROR, PCEP_ERRV_RWA_INSUFFICIENT_MEMORY): + return "RWA_INSUFFICIENT_MEMORY"; + case TUP(PCEP_ERRT_WSON_RWA_ERROR, PCEP_ERRV_RWA_COMP_NOT_SUPPORTED): + return "RWA_COMP_NOT_SUPPORTED"; + case TUP(PCEP_ERRT_WSON_RWA_ERROR, PCEP_ERRV_SYNTAX_ENC_ERROR): + return "SYNTAX_ENC_ERROR"; + + case TUP(PCEP_ERRT_H_PCE_ERROR, PCEP_ERRV_H_PCE_CAP_NOT_ADVERTISED): + return "H_PCE_CAP_NOT_ADVERTISED"; + case TUP(PCEP_ERRT_H_PCE_ERROR, + PCEP_ERRV_PARENT_PCE_CAP_CANT_BE_PROVIDED): + return "PARENT_PCE_CAP_CANT_BE_PROVIDED"; + + case TUP(PCEP_ERRT_PATH_COMP_FAILURE, + PCEP_ERRV_UNACCEPTABLE_REQUEST_MSG): + return "UNACCEPTABLE_REQUEST_MSG"; + case TUP(PCEP_ERRT_PATH_COMP_FAILURE, + PCEP_ERRV_GENERALIZED_BW_VAL_NOT_SUPPORTED): + return "GENERALIZED_BW_VAL_NOT_SUPPORTED"; + case TUP(PCEP_ERRT_PATH_COMP_FAILURE, + PCEP_ERRV_LABEL_SET_CONSTRAINT_COULD_NOT_BE_MET): + return "LABEL_SET_CONSTRAINT_COULD_NOT_BE_MET"; + case TUP(PCEP_ERRT_PATH_COMP_FAILURE, + PCEP_ERRV_LABEL_CONSTRAINT_COULD_NOT_BE_MET): + return "LABEL_CONSTRAINT_COULD_NOT_BE_MET"; + + default: + return "UNKNOWN"; + } +} + +const char *pcep_message_type_name(enum pcep_message_types pcep_message_type) +{ + switch (pcep_message_type) { + + case PCEP_TYPE_OPEN: + return "OPEN"; + case PCEP_TYPE_KEEPALIVE: + return "KEEPALIVE"; + case PCEP_TYPE_PCREQ: + return "PCREQ"; + case PCEP_TYPE_PCREP: + return "PCREP"; + case PCEP_TYPE_PCNOTF: + return "PCNOTF"; + case PCEP_TYPE_ERROR: + return "ERROR"; + case PCEP_TYPE_CLOSE: + return "CLOSE"; + case PCEP_TYPE_REPORT: + return "REPORT"; + case PCEP_TYPE_UPDATE: + return "UPDATE"; + case PCEP_TYPE_INITIATE: + return "INITIATE"; + case PCEP_TYPE_START_TLS: + return "START_TLS"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_object_class_name(enum pcep_object_classes obj_class) +{ + switch (obj_class) { + case PCEP_OBJ_CLASS_OPEN: + return "OPEN"; + case PCEP_OBJ_CLASS_RP: + return "RP"; + case PCEP_OBJ_CLASS_NOPATH: + return "NOPATH"; + case PCEP_OBJ_CLASS_ENDPOINTS: + return "ENDPOINTS"; + case PCEP_OBJ_CLASS_BANDWIDTH: + return "BANDWIDTH"; + case PCEP_OBJ_CLASS_METRIC: + return "METRIC"; + case PCEP_OBJ_CLASS_ERO: + return "ERO"; + case PCEP_OBJ_CLASS_RRO: + return "RRO"; + case PCEP_OBJ_CLASS_LSPA: + return "LSPA"; + case PCEP_OBJ_CLASS_IRO: + return "IRO"; + case PCEP_OBJ_CLASS_SVEC: + return "SVEC"; + case PCEP_OBJ_CLASS_NOTF: + return "NOTF"; + case PCEP_OBJ_CLASS_ERROR: + return "ERROR"; + case PCEP_OBJ_CLASS_CLOSE: + return "CLOSE"; + case PCEP_OBJ_CLASS_OF: + return "OF"; + case PCEP_OBJ_CLASS_LSP: + return "LSP"; + case PCEP_OBJ_CLASS_SRP: + return "SRP"; + case PCEP_OBJ_CLASS_VENDOR_INFO: + return "VENDOR_INFO"; + case PCEP_OBJ_CLASS_INTER_LAYER: + return "INTER_LAYER"; + case PCEP_OBJ_CLASS_SWITCH_LAYER: + return "SWITCH_LAYER"; + case PCEP_OBJ_CLASS_REQ_ADAP_CAP: + return "REQ_ADAP_CAP"; + case PCEP_OBJ_CLASS_SERVER_IND: + return "SERVER_IND"; + case PCEP_OBJ_CLASS_ASSOCIATION: + return "ASSOCIATION"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_object_type_name(enum pcep_object_classes obj_class, + enum pcep_object_types obj_type) +{ + switch (TUP(obj_class, obj_type)) { + case TUP(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN): + return "OPEN"; + case TUP(PCEP_OBJ_CLASS_RP, PCEP_OBJ_TYPE_RP): + return "RP"; + case TUP(PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH): + return "NOPATH"; + case TUP(PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV4): + return "ENDPOINT_IPV4"; + case TUP(PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV6): + return "ENDPOINT_IPV6"; + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_REQ): + return "BANDWIDTH_REQ"; + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_TELSP): + return "BANDWIDTH_TELSP"; + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_CISCO): + return "BANDWIDTH_CISCO"; + case TUP(PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC): + return "METRIC"; + case TUP(PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO): + return "ERO"; + case TUP(PCEP_OBJ_CLASS_RRO, PCEP_OBJ_TYPE_RRO): + return "RRO"; + case TUP(PCEP_OBJ_CLASS_LSPA, PCEP_OBJ_TYPE_LSPA): + return "LSPA"; + case TUP(PCEP_OBJ_CLASS_IRO, PCEP_OBJ_TYPE_IRO): + return "IRO"; + case TUP(PCEP_OBJ_CLASS_SVEC, PCEP_OBJ_TYPE_SVEC): + return "SVEC"; + case TUP(PCEP_OBJ_CLASS_NOTF, PCEP_OBJ_TYPE_NOTF): + return "NOTF"; + case TUP(PCEP_OBJ_CLASS_ERROR, PCEP_OBJ_TYPE_ERROR): + return "ERROR"; + case TUP(PCEP_OBJ_CLASS_CLOSE, PCEP_OBJ_TYPE_CLOSE): + return "CLOSE"; + case TUP(PCEP_OBJ_CLASS_INTER_LAYER, PCEP_OBJ_TYPE_INTER_LAYER): + return "INTER_LAYER"; + case TUP(PCEP_OBJ_CLASS_SWITCH_LAYER, PCEP_OBJ_TYPE_SWITCH_LAYER): + return "SWITCH_LAYER"; + case TUP(PCEP_OBJ_CLASS_REQ_ADAP_CAP, PCEP_OBJ_TYPE_REQ_ADAP_CAP): + return "REQ_ADAP_CAP"; + case TUP(PCEP_OBJ_CLASS_SERVER_IND, PCEP_OBJ_TYPE_SERVER_IND): + return "SERVER_IND"; + case TUP(PCEP_OBJ_CLASS_ASSOCIATION, PCEP_OBJ_TYPE_ASSOCIATION_IPV4): + return "ASSOCIATION_IPV4"; + case TUP(PCEP_OBJ_CLASS_ASSOCIATION, PCEP_OBJ_TYPE_ASSOCIATION_IPV6): + return "ASSOCIATION_IPV6"; + case TUP(PCEP_OBJ_CLASS_OF, PCEP_OBJ_TYPE_OF): + return "OF"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_lsp_status_name(enum pcep_lsp_operational_status status) +{ + switch (status) { + case PCEP_LSP_OPERATIONAL_DOWN: + return "DOWN"; + case PCEP_LSP_OPERATIONAL_UP: + return "UP"; + case PCEP_LSP_OPERATIONAL_ACTIVE: + return "ACTIVE"; + case PCEP_LSP_OPERATIONAL_GOING_DOWN: + return "GOING_DOWN"; + case PCEP_LSP_OPERATIONAL_GOING_UP: + return "GOING_UP"; + default: + return "UNKNOWN"; + } +} + + +const char *pcep_tlv_type_name(enum pcep_object_tlv_types tlv_type) +{ + switch (tlv_type) { + case PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR: + return "NO_PATH_VECTOR"; + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + return "OBJECTIVE_FUNCTION_LIST"; + case PCEP_OBJ_TLV_TYPE_VENDOR_INFO: + return "VENDOR_INFO"; + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + return "STATEFUL_PCE_CAPABILITY"; + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + return "SYMBOLIC_PATH_NAME"; + case PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS: + return "IPV4_LSP_IDENTIFIERS"; + case PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS: + return "IPV6_LSP_IDENTIFIERS"; + case PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE: + return "LSP_ERROR_CODE"; + case PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC: + return "RSVP_ERROR_SPEC"; + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + return "LSP_DB_VERSION"; + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + return "SPEAKER_ENTITY_ID"; + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + return "SR_PCE_CAPABILITY"; + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + return "PATH_SETUP_TYPE"; + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + return "PATH_SETUP_TYPE_CAPABILITY"; + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID: + return "SRPOLICY_POL_ID"; + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME: + return "SRPOLICY_POL_NAME"; + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID: + return "SRPOLICY_CPATH_ID"; + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE: + return "SRPOLICY_CPATH_PREFERENCE"; + case PCEP_OBJ_TLV_TYPE_UNKNOWN: + return "UNKNOWN"; + case PCEP_OBJ_TLV_TYPE_ARBITRARY: + return "ARBITRARY"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_ro_type_name(enum pcep_ro_subobj_types ro_type) +{ + switch (ro_type) { + + case RO_SUBOBJ_TYPE_IPV4: + return "IPV4"; + case RO_SUBOBJ_TYPE_IPV6: + return "IPV6"; + case RO_SUBOBJ_TYPE_LABEL: + return "LABEL"; + case RO_SUBOBJ_TYPE_UNNUM: + return "UNNUM"; + case RO_SUBOBJ_TYPE_ASN: + return "ASN"; + case RO_SUBOBJ_TYPE_SR: + return "SR"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_nai_type_name(enum pcep_sr_subobj_nai nai_type) +{ + switch (nai_type) { + case PCEP_SR_SUBOBJ_NAI_ABSENT: + return "ABSENT"; + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + return "IPV4_NODE"; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + return "IPV6_NODE"; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + return "IPV4_ADJACENCY"; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + return "IPV6_ADJACENCY"; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + return "UNNUMBERED_IPV4_ADJACENCY"; + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: + return "LINK_LOCAL_IPV6_ADJACENCY"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_metric_type_name(enum pcep_metric_types type) +{ + switch (type) { + case PCEP_METRIC_IGP: + return "IGP"; + case PCEP_METRIC_TE: + return "TE"; + case PCEP_METRIC_HOP_COUNT: + return "HOP_COUNT"; + case PCEP_METRIC_AGGREGATE_BW: + return "AGGREGATE_BW"; + case PCEP_METRIC_MOST_LOADED_LINK: + return "MOST_LOADED_LINK"; + case PCEP_METRIC_CUMULATIVE_IGP: + return "CUMULATIVE_IGP"; + case PCEP_METRIC_CUMULATIVE_TE: + return "CUMULATIVE_TE"; + case PCEP_METRIC_P2MP_IGP: + return "P2MP_IGP"; + case PCEP_METRIC_P2MP_TE: + return "P2MP_TE"; + case PCEP_METRIC_P2MP_HOP_COUNT: + return "P2MP_HOP_COUNT"; + case PCEP_METRIC_SEGMENT_ID_DEPTH: + return "SEGMENT_ID_DEPTH"; + case PCEP_METRIC_PATH_DELAY: + return "PATH_DELAY"; + case PCEP_METRIC_PATH_DELAY_VARIATION: + return "PATH_DELAY_VARIATION"; + case PCEP_METRIC_PATH_LOSS: + return "PATH_LOSS"; + case PCEP_METRIC_P2MP_PATH_DELAY: + return "P2MP_PATH_DELAY"; + case PCEP_METRIC_P2MP_PATH_DELAY_VARIATION: + return "P2MP_PATH_DELAY_VARIATION"; + case PCEP_METRIC_P2MP_PATH_LOSS: + return "P2MP_PATH_LOSS"; + case PCEP_METRIC_NUM_PATH_ADAPTATIONS: + return "NUM_PATH_ADAPTATIONS"; + case PCEP_METRIC_NUM_PATH_LAYERS: + return "NUM_PATH_LAYERS"; + case PCEP_METRIC_DOMAIN_COUNT: + return "DOMAIN_COUNT"; + case PCEP_METRIC_BORDER_NODE_COUNT: + return "BORDER_NODE_COUNT"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_nopath_tlv_err_code_name(enum pcep_nopath_tlv_err_codes type) +{ + switch (type) { + case PCEP_NOPATH_TLV_ERR_NO_TLV: + return "NO_TLV"; + case PCEP_NOPATH_TLV_ERR_PCE_UNAVAILABLE: + return "PCE_UNAVAILABLE"; + case PCEP_NOPATH_TLV_ERR_UNKNOWN_DST: + return "UNKNOWN_DST"; + case PCEP_NOPATH_TLV_ERR_UNKNOWN_SRC: + return "UNKNOWN_SRC"; + default: + return "UNKNOWN"; + } +} + +const char *format_objfun_set(uint32_t flags) +{ + int i, c; + PATHD_FORMAT_INIT(); + for (i = 1, c = 0; i <= MAX_OBJFUN_TYPE; i++) { + if (CHECK_FLAG(flags, i)) { + if (c > 0) + PATHD_FORMAT(", %s", objfun_type_name(i)); + else + PATHD_FORMAT("%s", objfun_type_name(i)); + c++; + } + } + return PATHD_FORMAT_FINI(); +} + + +const char *format_pcc_opts(struct pcc_opts *opts) +{ + PATHD_FORMAT_INIT(); + _format_pcc_opts(0, opts); + return PATHD_FORMAT_FINI(); +} + +const char *format_pcc_state(struct pcc_state *state) +{ + PATHD_FORMAT_INIT(); + _format_pcc_state(0, state); + return PATHD_FORMAT_FINI(); +} + +const char *format_ctrl_state(struct ctrl_state *state) +{ + PATHD_FORMAT_INIT(); + _format_ctrl_state(0, state); + return PATHD_FORMAT_FINI(); +} + +const char *format_path(struct path *path) +{ + PATHD_FORMAT_INIT(); + _format_path(0, path); + return PATHD_FORMAT_FINI(); +} + +const char *format_pcep_event(pcep_event *event) +{ + PATHD_FORMAT_INIT(); + _format_pcep_event(0, event); + return PATHD_FORMAT_FINI(); +} + +const char *format_pcep_message(struct pcep_message *msg) +{ + PATHD_FORMAT_INIT(); + _format_pcep_message(0, msg); + return PATHD_FORMAT_FINI(); +} + +void _format_pcc_opts(int ps, struct pcc_opts *opts) +{ + if (opts == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + if (IS_IPADDR_V4(&opts->addr)) { + PATHD_FORMAT("%*saddr_v4: %pI4\n", ps2, "", + &opts->addr.ipaddr_v4); + } else { + PATHD_FORMAT("%*saddr_v4: undefined", ps2, ""); + } + if (IS_IPADDR_V6(&opts->addr)) { + PATHD_FORMAT("%*saddr_v6: %pI6\n", ps2, "", + &opts->addr.ipaddr_v6); + } else { + PATHD_FORMAT("%*saddr_v6: undefined", ps2, ""); + } + PATHD_FORMAT("%*sport: %i\n", ps2, "", opts->port); + PATHD_FORMAT("%*smsd: %i\n", ps2, "", opts->msd); + } +} + +void _format_pce_opts(int ps, struct pce_opts *opts) +{ + if (opts == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + if (IS_IPADDR_V6(&opts->addr)) { + PATHD_FORMAT("%*saddr: %pI6\n", ps2, "", + &opts->addr.ipaddr_v6); + } else { + PATHD_FORMAT("%*saddr: %pI4\n", ps2, "", + &opts->addr.ipaddr_v4); + } + PATHD_FORMAT("%*sport: %i\n", ps2, "", opts->port); + } +} + +void _format_pcc_caps(int ps, struct pcep_caps *caps) +{ + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*sis_stateful: %d\n", ps2, "", caps->is_stateful); +} + +void _format_pcc_state(int ps, struct pcc_state *state) +{ + if (state == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*sstatus: %s\n", ps2, "", + pcc_status_name(state->status)); + PATHD_FORMAT("%*spcc_opts: ", ps2, ""); + _format_pcc_opts(ps2, state->pcc_opts); + PATHD_FORMAT("%*spce_opts: ", ps2, ""); + _format_pce_opts(ps2, state->pce_opts); + if (state->sess == NULL) { + PATHD_FORMAT("%*ssess: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*ssess: <PCC SESSION %p>\n", ps2, "", + state->sess); + } + PATHD_FORMAT("%*scaps: ", ps2, ""); + _format_pcc_caps(ps2, &state->caps); + } +} + +void _format_ctrl_state(int ps, struct ctrl_state *state) +{ + if (state == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int i; + int ps2 = ps + DEBUG_IDENT_SIZE; + int ps3 = ps2 + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + if (state->main == NULL) { + PATHD_FORMAT("%*smain: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*smain: <THREAD MASTER %p>\n", ps2, "", + state->main); + } + if (state->self == NULL) { + PATHD_FORMAT("%*sself: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*sself: <THREAD MASTER %p>\n", ps2, "", + state->self); + } + PATHD_FORMAT("%*spcc_count: %d\n", ps2, "", state->pcc_count); + PATHD_FORMAT("%*spcc:\n", ps2, ""); + for (i = 0; i < MAX_PCC; i++) { + if (state->pcc[i]) { + PATHD_FORMAT("%*s- ", ps3 - 2, ""); + _format_pcc_state(ps3, state->pcc[i]); + } + } + } +} + +void _format_path(int ps, struct path *path) +{ + if (path == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + int ps3 = ps2 + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*snbkey: \n", ps2, ""); + PATHD_FORMAT("%*scolor: %u\n", ps3, "", path->nbkey.color); + switch (path->nbkey.endpoint.ipa_type) { + case IPADDR_V4: + PATHD_FORMAT("%*sendpoint: %pI4\n", ps3, "", + &path->nbkey.endpoint.ipaddr_v4); + break; + case IPADDR_V6: + PATHD_FORMAT("%*sendpoint: %pI6\n", ps3, "", + &path->nbkey.endpoint.ipaddr_v6); + break; + default: + PATHD_FORMAT("%*sendpoint: NONE\n", ps3, ""); + break; + } + PATHD_FORMAT("%*spreference: %u\n", ps3, "", + path->nbkey.preference); + + if (path->sender.ipa_type == IPADDR_V4) { + PATHD_FORMAT("%*ssender: %pI4\n", ps2, "", + &path->sender.ipaddr_v4); + } else if (path->sender.ipa_type == IPADDR_V6) { + PATHD_FORMAT("%*ssender: %pI6\n", ps2, "", + &path->sender.ipaddr_v6); + } else { + PATHD_FORMAT("%*ssender: UNDEFINED\n", ps2, ""); + } + if (path->pcc_addr.ipa_type == IPADDR_V4) { + PATHD_FORMAT("%*spcc_addr: %pI4\n", ps2, "", + &path->pcc_addr.ipaddr_v4); + } else if (path->pcc_addr.ipa_type == IPADDR_V6) { + PATHD_FORMAT("%*spcc_addr: %pI6\n", ps2, "", + &path->pcc_addr.ipaddr_v6); + } else { + PATHD_FORMAT("%*spcc_addr: UNDEFINED\n", ps2, ""); + } + PATHD_FORMAT("%*spcc_id: %u\n", ps2, "", path->pcc_id); + PATHD_FORMAT("%*screate_origin: %s (%u)\n", ps2, "", + srte_protocol_origin_name(path->create_origin), + path->create_origin); + PATHD_FORMAT("%*supdate_origin: %s (%u)\n", ps2, "", + srte_protocol_origin_name(path->update_origin), + path->update_origin); + if (path->originator != NULL) { + PATHD_FORMAT("%*soriginator: %s\n", ps2, "", + path->originator); + } else { + PATHD_FORMAT("%*soriginator: UNDEFINED\n", ps2, ""); + } + PATHD_FORMAT("%*stype: %s (%u)\n", ps2, "", + srte_candidate_type_name(path->type), path->type); + PATHD_FORMAT("%*splsp_id: %u\n", ps2, "", path->plsp_id); + if (path->name == NULL) { + PATHD_FORMAT("%*sname: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*sname: %s\n", ps2, "", path->name); + } + PATHD_FORMAT("%*ssrp_id: %u\n", ps2, "", path->srp_id); + PATHD_FORMAT("%*sreq_id: %u\n", ps2, "", path->req_id); + PATHD_FORMAT("%*sstatus: %s (%u)\n", ps2, "", + pcep_lsp_status_name(path->status), path->status); + PATHD_FORMAT("%*sdo_remove: %u\n", ps2, "", path->do_remove); + PATHD_FORMAT("%*sgo_active: %u\n", ps2, "", path->go_active); + PATHD_FORMAT("%*swas_created: %u\n", ps2, "", + path->was_created); + PATHD_FORMAT("%*swas_removed: %u\n", ps2, "", + path->was_removed); + PATHD_FORMAT("%*sis_synching: %u\n", ps2, "", + path->is_synching); + PATHD_FORMAT("%*sis_delegated: %u\n", ps2, "", + path->is_delegated); + PATHD_FORMAT("%*shas_bandwidth: %u\n", ps2, "", + path->has_bandwidth); + if (path->has_bandwidth) { + PATHD_FORMAT("%*senforce_bandwidth: %u\n", ps2, "", + path->enforce_bandwidth); + PATHD_FORMAT("%*sbandwidth: %f\n", ps2, "", + path->bandwidth); + } + PATHD_FORMAT("%*shas_pcc_objfun: %u\n", ps2, "", + path->has_pcc_objfun); + if (path->has_pcc_objfun) { + PATHD_FORMAT("%*senforce_pcc_objfun: %d\n", ps2, "", + path->enforce_pcc_objfun); + PATHD_FORMAT("%*spcc_objfun: %s (%u)\n", ps2, "", + objfun_type_name(path->pcc_objfun), + path->pcc_objfun); + } + PATHD_FORMAT("%*shas_pce_objfun: %u\n", ps2, "", + path->has_pce_objfun); + if (path->has_pce_objfun) + PATHD_FORMAT("%*spce_objfun: %s (%u)\n", ps2, "", + objfun_type_name(path->pce_objfun), + path->pce_objfun); + PATHD_FORMAT("%*shas_affinity_filters: %u\n", ps2, "", + path->has_affinity_filters); + if (path->has_affinity_filters) { + PATHD_FORMAT("%*sexclude_any: 0x%08x\n", ps2, "", + path->affinity_filters + [AFFINITY_FILTER_EXCLUDE_ANY - 1]); + PATHD_FORMAT("%*sinclude_any: 0x%08x\n", ps2, "", + path->affinity_filters + [AFFINITY_FILTER_INCLUDE_ANY - 1]); + PATHD_FORMAT("%*sinclude_all: 0x%08x\n", ps2, "", + path->affinity_filters + [AFFINITY_FILTER_INCLUDE_ALL - 1]); + } + + if (path->first_hop == NULL) { + PATHD_FORMAT("%*shops: []\n", ps2, ""); + } else { + PATHD_FORMAT("%*shops: \n", ps2, ""); + for (struct path_hop *hop = path->first_hop; + hop != NULL; hop = hop->next) { + PATHD_FORMAT("%*s- ", ps3 - 2, ""); + _format_path_hop(ps3, hop); + } + } + if (path->first_metric == NULL) { + PATHD_FORMAT("%*smetrics: []\n", ps2, ""); + } else { + PATHD_FORMAT("%*smetrics: \n", ps2, ""); + for (struct path_metric *metric = path->first_metric; + NULL != metric; metric = metric->next) { + PATHD_FORMAT("%*s- ", ps3 - 2, ""); + _format_path_metric(ps3, metric); + } + } + } +} + +void _format_path_metric(int ps, struct path_metric *metric) +{ + PATHD_FORMAT("type: %s (%u)\n", pcep_metric_type_name(metric->type), + metric->type); + PATHD_FORMAT("%*senforce: %u\n", ps, "", metric->enforce); + PATHD_FORMAT("%*sis_bound: %u\n", ps, "", metric->is_bound); + PATHD_FORMAT("%*sis_computed: %u\n", ps, "", metric->is_computed); + PATHD_FORMAT("%*svalue: %f\n", ps, "", metric->value); +} + +void _format_path_hop(int ps, struct path_hop *hop) +{ + PATHD_FORMAT("is_loose: %u\n", hop->is_loose); + PATHD_FORMAT("%*shas_sid: %u\n", ps, "", hop->has_sid); + + if (hop->has_sid) { + PATHD_FORMAT("%*sis_mpls: %u\n", ps, "", hop->is_mpls); + if (hop->is_mpls) { + PATHD_FORMAT("%*shas_attribs: %u\n", ps, "", + hop->has_attribs); + PATHD_FORMAT("%*slabel: %u\n", ps, "", + hop->sid.mpls.label); + if (hop->has_attribs) { + PATHD_FORMAT("%*straffic_class: %u\n", ps, "", + hop->sid.mpls.traffic_class); + PATHD_FORMAT("%*sis_bottom: %u\n", ps, "", + hop->sid.mpls.is_bottom); + PATHD_FORMAT("%*sttl: %u\n", ps, "", + hop->sid.mpls.ttl); + } + } else { + PATHD_FORMAT("%*sSID: %u\n", ps, "", hop->sid.value); + } + } + + PATHD_FORMAT("%*shas_nai: %u\n", ps, "", hop->has_nai); + if (hop->has_nai) { + PATHD_FORMAT("%*snai_type: %s (%u)\n", ps, "", + pcep_nai_type_name(hop->nai.type), hop->nai.type); + switch (hop->nai.type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + PATHD_FORMAT("%*sNAI: %pI4\n", ps, "", + &hop->nai.local_addr.ipaddr_v4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + PATHD_FORMAT("%*sNAI: %pI6\n", ps, "", + &hop->nai.local_addr.ipaddr_v6); + break; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + PATHD_FORMAT("%*sNAI: %pI4/%pI4\n", ps, "", + &hop->nai.local_addr.ipaddr_v4, + &hop->nai.remote_addr.ipaddr_v4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + PATHD_FORMAT("%*sNAI: %pI6/%pI6\n", ps, "", + &hop->nai.local_addr.ipaddr_v6, + &hop->nai.remote_addr.ipaddr_v6); + break; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + PATHD_FORMAT("%*sNAI: %pI6(%u)/%pI6(%u)\n", ps, "", + &hop->nai.local_addr.ipaddr_v6, + hop->nai.local_iface, + &hop->nai.remote_addr.ipaddr_v6, + hop->nai.remote_iface); + break; + default: + PATHD_FORMAT("%*sNAI: UNSUPPORTED\n", ps, ""); + break; + } + } +} + +void _format_pcep_event(int ps, pcep_event *event) +{ + if (event == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*sevent_type: %s\n", ps2, "", + pcep_event_type_name(event->event_type)); + PATHD_FORMAT("%*sevent_time: %s", ps2, "", + ctime(&event->event_time)); + if (event->session == NULL) { + PATHD_FORMAT("%*ssession: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*ssession: <PCC SESSION %p>\n", ps2, "", + event->session); + } + PATHD_FORMAT("%*smessage: ", ps2, ""); + _format_pcep_message(ps2, event->message); + } +} + +void _format_pcep_message(int ps, struct pcep_message *msg) +{ + if (msg == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*spcep_version: %u\n", ps2, "", + msg->msg_header->pcep_version); + PATHD_FORMAT("%*stype: %s (%u)\n", ps2, "", + pcep_message_type_name(msg->msg_header->type), + msg->msg_header->type); + PATHD_FORMAT("%*sobjects: ", ps2, ""); + _format_pcep_objects(ps2, msg->obj_list); + } +} + +void _format_pcep_objects(int ps, double_linked_list *objs) +{ + if (objs == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + double_linked_list_node *node; + int ps2 = ps + DEBUG_IDENT_SIZE; + int i; + + if (objs->num_entries == 0) { + PATHD_FORMAT("[]\n"); + return; + } + + PATHD_FORMAT("\n"); + for (node = objs->head, i = 0; node != NULL; + node = node->next_node, i++) { + struct pcep_object_header *obj = + (struct pcep_object_header *)node->data; + PATHD_FORMAT("%*s- ", ps2 - 2, ""); + _format_pcep_object(ps2, obj); + } + } +} + +void _format_pcep_object(int ps, struct pcep_object_header *obj) +{ + if (obj == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + PATHD_FORMAT("object_class: %s (%u)\n", + pcep_object_class_name(obj->object_class), + obj->object_class); + PATHD_FORMAT("%*sobject_type: %s (%u)\n", ps, "", + pcep_object_type_name(obj->object_class, + obj->object_type), + obj->object_type); + PATHD_FORMAT("%*sflag_p: %u\n", ps, "", obj->flag_p); + PATHD_FORMAT("%*sflag_i: %u\n", ps, "", obj->flag_i); + _format_pcep_object_details(ps, obj); + _format_pcep_object_tlvs(ps, obj); + } +} + +void _format_pcep_object_details(int ps, struct pcep_object_header *obj) +{ + switch (TUP(obj->object_class, obj->object_type)) { + case TUP(PCEP_OBJ_CLASS_ERROR, PCEP_OBJ_TYPE_ERROR): + _format_pcep_object_error(ps, (struct pcep_object_error *)obj); + break; + case TUP(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN): + _format_pcep_object_open(ps, (struct pcep_object_open *)obj); + break; + case TUP(PCEP_OBJ_CLASS_RP, PCEP_OBJ_TYPE_RP): + _format_pcep_object_rp(ps, (struct pcep_object_rp *)obj); + break; + case TUP(PCEP_OBJ_CLASS_SRP, PCEP_OBJ_TYPE_SRP): + _format_pcep_object_srp(ps, (struct pcep_object_srp *)obj); + break; + case TUP(PCEP_OBJ_CLASS_LSP, PCEP_OBJ_TYPE_LSP): + _format_pcep_object_lsp(ps, (struct pcep_object_lsp *)obj); + break; + case TUP(PCEP_OBJ_CLASS_LSPA, PCEP_OBJ_TYPE_LSPA): + _format_pcep_object_lspa(ps, (struct pcep_object_lspa *)obj); + break; + case TUP(PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV4): + _format_pcep_object_ipv4_endpoint( + ps, (struct pcep_object_endpoints_ipv4 *)obj); + break; + case TUP(PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO): + _format_pcep_object_ro(ps, (struct pcep_object_ro *)obj); + break; + case TUP(PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC): + _format_pcep_object_metric(ps, + (struct pcep_object_metric *)obj); + break; + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_REQ): + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_CISCO): + _format_pcep_object_bandwidth( + ps, (struct pcep_object_bandwidth *)obj); + break; + case TUP(PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH): + _format_pcep_object_nopath(ps, + (struct pcep_object_nopath *)obj); + break; + case TUP(PCEP_OBJ_CLASS_OF, PCEP_OBJ_TYPE_OF): + _format_pcep_object_objfun( + ps, (struct pcep_object_objective_function *)obj); + break; + default: + PATHD_FORMAT("%*s...\n", ps, ""); + break; + } +} + +void _format_pcep_object_error(int ps, struct pcep_object_error *obj) +{ + PATHD_FORMAT("%*serror_type: %s (%u)\n", ps, "", + pcep_error_type_name(obj->error_type), obj->error_type); + PATHD_FORMAT("%*serror_value: %s (%u)\n", ps, "", + pcep_error_value_name(obj->error_type, obj->error_value), + obj->error_value); +} + + +void _format_pcep_object_open(int ps, struct pcep_object_open *obj) +{ + PATHD_FORMAT("%*sopen_version: %u\n", ps, "", obj->open_version); + PATHD_FORMAT("%*sopen_keepalive: %u\n", ps, "", obj->open_keepalive); + PATHD_FORMAT("%*sopen_deadtimer: %u\n", ps, "", obj->open_deadtimer); + PATHD_FORMAT("%*sopen_sid: %u\n", ps, "", obj->open_sid); +} + +void _format_pcep_object_rp(int ps, struct pcep_object_rp *obj) +{ + PATHD_FORMAT("%*spriority: %u\n", ps, "", obj->priority); + PATHD_FORMAT("%*sflag_reoptimization: %u\n", ps, "", + obj->flag_reoptimization); + PATHD_FORMAT("%*sflag_bidirectional: %u\n", ps, "", + obj->flag_bidirectional); + PATHD_FORMAT("%*sflag_strict: %u\n", ps, "", obj->flag_strict); + PATHD_FORMAT("%*sflag_of: %u\n", ps, "", obj->flag_of); + PATHD_FORMAT("%*srequest_id: %u\n", ps, "", obj->request_id); +} + + +void _format_pcep_object_srp(int ps, struct pcep_object_srp *obj) +{ + PATHD_FORMAT("%*sflag_lsp_remove: %u\n", ps, "", obj->flag_lsp_remove); + PATHD_FORMAT("%*ssrp_id_number: %u\n", ps, "", obj->srp_id_number); +} + +void _format_pcep_object_lsp(int ps, struct pcep_object_lsp *obj) +{ + PATHD_FORMAT("%*splsp_id: %u\n", ps, "", obj->plsp_id); + PATHD_FORMAT("%*sstatus: %s\n", ps, "", + pcep_lsp_status_name(obj->operational_status)); + PATHD_FORMAT("%*sflag_d: %u\n", ps, "", obj->flag_d); + PATHD_FORMAT("%*sflag_s: %u\n", ps, "", obj->flag_s); + PATHD_FORMAT("%*sflag_r: %u\n", ps, "", obj->flag_r); + PATHD_FORMAT("%*sflag_a: %u\n", ps, "", obj->flag_a); + PATHD_FORMAT("%*sflag_c: %u\n", ps, "", obj->flag_c); +} + +void _format_pcep_object_lspa(int ps, struct pcep_object_lspa *obj) +{ + PATHD_FORMAT("%*slspa_exclude_any: 0x%08x\n", ps, "", + obj->lspa_exclude_any); + PATHD_FORMAT("%*slspa_include_any: 0x%08x\n", ps, "", + obj->lspa_include_any); + PATHD_FORMAT("%*slspa_include_all: 0x%08x\n", ps, "", + obj->lspa_include_all); + PATHD_FORMAT("%*ssetup_priority: %u\n", ps, "", obj->setup_priority); + PATHD_FORMAT("%*sholding_priority: %u\n", ps, "", + obj->holding_priority); + PATHD_FORMAT("%*sflag_local_protection: %u\n", ps, "", + obj->flag_local_protection); +} + +void _format_pcep_object_ipv4_endpoint(int ps, + struct pcep_object_endpoints_ipv4 *obj) +{ + PATHD_FORMAT("%*ssrc_ipv4: %pI4\n", ps, "", &obj->src_ipv4); + PATHD_FORMAT("%*sdst_ipv4: %pI4\n", ps, "", &obj->dst_ipv4); +} + +void _format_pcep_object_metric(int ps, struct pcep_object_metric *obj) +{ + PATHD_FORMAT("%*stype: %s (%u)\n", ps, "", + pcep_metric_type_name(obj->type), obj->type); + PATHD_FORMAT("%*sflag_b: %u\n", ps, "", obj->flag_b); + PATHD_FORMAT("%*sflag_c: %u\n", ps, "", obj->flag_c); + PATHD_FORMAT("%*svalue: %f\n", ps, "", obj->value); +} + +void _format_pcep_object_bandwidth(int ps, struct pcep_object_bandwidth *obj) +{ + PATHD_FORMAT("%*sbandwidth: %f\n", ps, "", obj->bandwidth); +} + +void _format_pcep_object_nopath(int ps, struct pcep_object_nopath *obj) +{ + PATHD_FORMAT("%*sni: %u\n", ps, "", obj->ni); + PATHD_FORMAT("%*sflag_c: %u\n", ps, "", obj->flag_c); + PATHD_FORMAT("%*serr_code: %s (%u)\n", ps, "", + pcep_nopath_tlv_err_code_name(obj->err_code), + obj->err_code); +} + +void _format_pcep_object_objfun(int ps, + struct pcep_object_objective_function *obj) +{ + PATHD_FORMAT("%*sof_code: %s (%u)\n", ps, "", + objfun_type_name(obj->of_code), obj->of_code); +} + +void _format_pcep_object_ro(int ps, struct pcep_object_ro *obj) +{ + double_linked_list *obj_list = obj->sub_objects; + double_linked_list_node *node; + struct pcep_object_ro_subobj *sub_obj; + + int ps2 = ps + DEBUG_IDENT_SIZE; + int i; + + if ((obj_list == NULL) || (obj_list->num_entries == 0)) { + PATHD_FORMAT("%*ssub_objects: []\n", ps, ""); + return; + } + + PATHD_FORMAT("%*ssub_objects:\n", ps, ""); + + for (node = obj_list->head, i = 0; node != NULL; + node = node->next_node, i++) { + sub_obj = (struct pcep_object_ro_subobj *)node->data; + PATHD_FORMAT("%*s- flag_subobj_loose_hop: %u\n", ps2 - 2, "", + sub_obj->flag_subobj_loose_hop); + PATHD_FORMAT("%*sro_subobj_type: %s (%u)\n", ps2, "", + pcep_ro_type_name(sub_obj->ro_subobj_type), + sub_obj->ro_subobj_type); + _format_pcep_object_ro_details(ps2, sub_obj); + } +} + +void _format_pcep_object_ro_details(int ps, struct pcep_object_ro_subobj *ro) +{ + switch (ro->ro_subobj_type) { + case RO_SUBOBJ_TYPE_IPV4: + _format_pcep_object_ro_ipv4(ps, + (struct pcep_ro_subobj_ipv4 *)ro); + break; + case RO_SUBOBJ_TYPE_SR: + _format_pcep_object_ro_sr(ps, (struct pcep_ro_subobj_sr *)ro); + break; + default: + PATHD_FORMAT("%*s...\n", ps, ""); + break; + } +} + +void _format_pcep_object_ro_ipv4(int ps, struct pcep_ro_subobj_ipv4 *obj) +{ + PATHD_FORMAT("%*sip_addr: %pI4\n", ps, "", &obj->ip_addr); + PATHD_FORMAT("%*sprefix_length: %u\n", ps, "", obj->prefix_length); + PATHD_FORMAT("%*sflag_local_protection: %u\n", ps, "", + obj->flag_local_protection); +} + +void _format_pcep_object_ro_sr(int ps, struct pcep_ro_subobj_sr *obj) +{ + PATHD_FORMAT("%*snai_type = %s (%u)\n", ps, "", + pcep_nai_type_name(obj->nai_type), obj->nai_type); + PATHD_FORMAT("%*sflag_f: %u\n", ps, "", obj->flag_f); + PATHD_FORMAT("%*sflag_s: %u\n", ps, "", obj->flag_s); + PATHD_FORMAT("%*sflag_c: %u\n", ps, "", obj->flag_c); + PATHD_FORMAT("%*sflag_m: %u\n", ps, "", obj->flag_m); + + if (!obj->flag_s) { + PATHD_FORMAT("%*sSID: %u\n", ps, "", obj->sid); + if (obj->flag_m) { + PATHD_FORMAT("%*slabel: %u\n", ps, "", + GET_SR_ERO_SID_LABEL(obj->sid)); + if (obj->flag_c) { + PATHD_FORMAT("%*sTC: %u\n", ps, "", + GET_SR_ERO_SID_TC(obj->sid)); + PATHD_FORMAT("%*sS: %u\n", ps, "", + GET_SR_ERO_SID_S(obj->sid)); + PATHD_FORMAT("%*sTTL: %u\n", ps, "", + GET_SR_ERO_SID_TTL(obj->sid)); + } + } + } + + if (!obj->flag_f) { + struct in_addr *laddr4, *raddr4; + struct in6_addr *laddr6, *raddr6; + uint32_t *liface, *riface; + assert(obj->nai_list != NULL); + double_linked_list_node *n = obj->nai_list->head; + assert(n != NULL); + assert(n->data != NULL); + switch (obj->nai_type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + laddr4 = (struct in_addr *)n->data; + PATHD_FORMAT("%*sNAI: %pI4\n", ps, "", laddr4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + laddr6 = (struct in6_addr *)n->data; + PATHD_FORMAT("%*sNAI: %pI6\n", ps, "", laddr6); + break; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + assert(n->next_node != NULL); + assert(n->next_node->data != NULL); + laddr4 = (struct in_addr *)n->data; + raddr4 = (struct in_addr *)n->next_node->data; + PATHD_FORMAT("%*sNAI: %pI4/%pI4\n", ps, "", laddr4, + raddr4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + assert(n->next_node != NULL); + assert(n->next_node->data != NULL); + laddr6 = (struct in6_addr *)n->data; + raddr6 = (struct in6_addr *)n->next_node->data; + PATHD_FORMAT("%*sNAI: %pI6/%pI6\n", ps, "", laddr6, + raddr6); + break; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + laddr4 = (struct in_addr *)n->data; + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + liface = (uint32_t *)n->data; + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + raddr4 = (struct in_addr *)n->data; + assert(n != NULL); + assert(n->data != NULL); + riface = (uint32_t *)n->data; + PATHD_FORMAT("%*sNAI: %pI4(%u)/%pI4(%u)\n", ps, "", + laddr4, *liface, raddr4, *riface); + break; + default: + PATHD_FORMAT("%*sNAI: UNSUPPORTED\n", ps, ""); + break; + } + } +} + +void _format_pcep_object_tlvs(int ps, struct pcep_object_header *obj) +{ + double_linked_list *tlv_list = obj->tlv_list; + struct pcep_object_tlv_header *tlv; + double_linked_list_node *node; + int ps2 = ps + DEBUG_IDENT_SIZE; + int i = 0; + + if (tlv_list == NULL) + return; + if (tlv_list->num_entries == 0) { + PATHD_FORMAT("%*stlvs: []\n", ps, ""); + return; + } + + PATHD_FORMAT("%*stlvs:\n", ps, ""); + + for (node = tlv_list->head, i = 0; node != NULL; + node = node->next_node, i++) { + tlv = (struct pcep_object_tlv_header *)node->data; + PATHD_FORMAT("%*s- ", ps2 - 2, ""); + _format_pcep_object_tlv(ps2, tlv); + } +} + +void _format_pcep_object_tlv(int ps, struct pcep_object_tlv_header *tlv_header) +{ + PATHD_FORMAT("type: %s (%u)\n", pcep_tlv_type_name(tlv_header->type), + tlv_header->type); + _format_pcep_object_tlv_details(ps, tlv_header); +} + +void _format_pcep_object_tlv_details(int ps, + struct pcep_object_tlv_header *tlv_header) +{ + switch (tlv_header->type) { + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + _format_pcep_object_tlv_symbolic_path_name( + ps, (struct pcep_object_tlv_symbolic_path_name *) + tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + _format_pcep_object_tlv_stateful_pce_capability( + ps, (struct pcep_object_tlv_stateful_pce_capability *) + tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + _format_pcep_object_tlv_sr_pce_capability( + ps, + (struct pcep_object_tlv_sr_pce_capability *)tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + _format_pcep_object_tlv_path_setup_type( + ps, + (struct pcep_object_tlv_path_setup_type *)tlv_header); + break; + default: + PATHD_FORMAT("%*s...\n", ps, ""); + break; + } +} + +void _format_pcep_object_tlv_symbolic_path_name( + int ps, struct pcep_object_tlv_symbolic_path_name *tlv) +{ + PATHD_FORMAT("%*ssymbolic_path_name: %.*s\n", ps, "", + tlv->symbolic_path_name_length, tlv->symbolic_path_name); +} + +void _format_pcep_object_tlv_stateful_pce_capability( + int ps, struct pcep_object_tlv_stateful_pce_capability *tlv) +{ + PATHD_FORMAT("%*sflag_u_lsp_update_capability: %u\n", ps, "", + tlv->flag_u_lsp_update_capability); + PATHD_FORMAT("%*sflag_s_include_db_version: %u\n", ps, "", + tlv->flag_s_include_db_version); + PATHD_FORMAT("%*sflag_i_lsp_instantiation_capability: %u\n", ps, "", + tlv->flag_i_lsp_instantiation_capability); + PATHD_FORMAT("%*sflag_t_triggered_resync: %u\n", ps, "", + tlv->flag_t_triggered_resync); + PATHD_FORMAT("%*sflag_d_delta_lsp_sync: %u\n", ps, "", + tlv->flag_d_delta_lsp_sync); + PATHD_FORMAT("%*sflag_f_triggered_initial_sync: %u\n", ps, "", + tlv->flag_f_triggered_initial_sync); +} + +void _format_pcep_object_tlv_sr_pce_capability( + int ps, struct pcep_object_tlv_sr_pce_capability *tlv) +{ + + PATHD_FORMAT("%*sflag_n: %u\n", ps, "", tlv->flag_n); + PATHD_FORMAT("%*sflag_x: %u\n", ps, "", tlv->flag_x); + PATHD_FORMAT("%*smax_sid_depth: %u\n", ps, "", tlv->max_sid_depth); +} + +void _format_pcep_object_tlv_path_setup_type( + int ps, struct pcep_object_tlv_path_setup_type *tlv) +{ + PATHD_FORMAT("%*spath_setup_type: %u\n", ps, "", tlv->path_setup_type); +} diff --git a/pathd/path_pcep_debug.h b/pathd/path_pcep_debug.h new file mode 100644 index 0000000..bc78c21 --- /dev/null +++ b/pathd/path_pcep_debug.h @@ -0,0 +1,55 @@ +/* + * 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 + */ + +#ifndef _PATH_PCEP_DEBUG_H_ +#define _PATH_PCEP_DEBUG_H_ + +#include "pathd/path_debug.h" +#include "pceplib/pcep_pcc_api.h" +#include "pceplib/pcep_msg_objects.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_controller.h" +#include "pathd/path_pcep_pcc.h" +#include "pathd/path_pcep_lib.h" + +const char *pcc_status_name(enum pcc_status status); + +const char *pcep_error_type_name(enum pcep_error_type error_type); +const char *pcep_error_value_name(enum pcep_error_type error_type, + enum pcep_error_value error_value); +const char *pcep_event_type_name(pcep_event_type event_type); +const char *pcep_message_type_name(enum pcep_message_types pcep_message_type); +const char *pcep_object_class_name(enum pcep_object_classes obj_class); +const char *pcep_object_type_name(enum pcep_object_classes obj_class, + enum pcep_object_types obj_type); +const char *pcep_lsp_status_name(enum pcep_lsp_operational_status status); +const char *pcep_tlv_type_name(enum pcep_object_tlv_types tlv_type); +const char *pcep_ro_type_name(enum pcep_ro_subobj_types ro_type); +const char *pcep_nai_type_name(enum pcep_sr_subobj_nai nai_type); +const char *pcep_metric_type_name(enum pcep_metric_types type); +const char *pcep_nopath_tlv_err_code_name(enum pcep_nopath_tlv_err_codes code); + +const char *format_objfun_set(uint32_t flags); +const char *format_pcc_opts(struct pcc_opts *ops); +const char *format_pcc_state(struct pcc_state *state); +const char *format_ctrl_state(struct ctrl_state *state); +const char *format_path(struct path *path); +const char *format_pcep_event(pcep_event *event); +const char *format_pcep_message(struct pcep_message *msg); + +#endif // _PATH_PCEP_DEBUG_H_ diff --git a/pathd/path_pcep_lib.c b/pathd/path_pcep_lib.c new file mode 100644 index 0000000..8e3565d --- /dev/null +++ b/pathd/path_pcep_lib.c @@ -0,0 +1,1266 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include "memory.h" + +#include <debug.h> +#include "pceplib/pcep_utils_counters.h" +#include "pceplib/pcep_timers.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_lib.h" +#include "pathd/path_pcep_debug.h" + +DEFINE_MTYPE_STATIC(PATHD, PCEPLIB_INFRA, "PCEPlib Infrastructure"); +DEFINE_MTYPE_STATIC(PATHD, PCEPLIB_MESSAGES, "PCEPlib PCEP Messages"); + +#define CLASS_TYPE(CLASS, TYPE) (((CLASS) << 16) | (TYPE)) +#define DEFAULT_LSAP_SETUP_PRIO 4 +#define DEFAULT_LSAP_HOLDING_PRIO 4 +#define DEFAULT_LSAP_LOCAL_PRETECTION false +#define MAX_PATH_NAME_SIZE 255 + +/* pceplib logging callback */ +static int pceplib_logging_cb(int level, const char *fmt, va_list args); + +/* Socket callbacks */ +static int pcep_lib_pceplib_socket_read_cb(void *fpt, void **thread, int fd, + void *payload); +static int pcep_lib_pceplib_socket_write_cb(void *fpt, void **thread, int fd, + void *payload); +static void pcep_lib_socket_read_ready(struct thread *thread); +static void pcep_lib_socket_write_ready(struct thread *thread); + +/* pceplib pcep_event callbacks */ +static void pcep_lib_pceplib_event_cb(void *fpt, pcep_event *event); + +/* pceplib pthread creation callback */ +static int pcep_lib_pthread_create_cb(pthread_t *pthread_id, + const pthread_attr_t *attr, + void *(*start_routine)(void *), + void *data, const char *thread_name); +void *pcep_lib_pthread_start_passthrough(void *data); +int pcep_lib_pthread_stop_cb(struct frr_pthread *, void **); + +/* Internal functions */ +static double_linked_list *pcep_lib_format_path(struct pcep_caps *caps, + struct path *path); +static void pcep_lib_format_constraints(struct path *path, + double_linked_list *objs); +static void pcep_lib_parse_open(struct pcep_caps *caps, + struct pcep_object_open *open); +static void +pcep_lib_parse_open_pce_capability(struct pcep_caps *caps, + struct pcep_object_tlv_header *tlv_header); +static void +pcep_lib_parse_open_objfun_list(struct pcep_caps *caps, + struct pcep_object_tlv_header *tlv_header); +static void pcep_lib_parse_rp(struct path *path, struct pcep_object_rp *rp); +static void pcep_lib_parse_srp(struct path *path, struct pcep_object_srp *srp); +static void pcep_lib_parse_lsp(struct path *path, struct pcep_object_lsp *lsp); +static void pcep_lib_parse_lspa(struct path *path, + struct pcep_object_lspa *lspa); +static void pcep_lib_parse_lsp_symbolic_name( + struct path *path, struct pcep_object_tlv_symbolic_path_name *tlv); +static void pcep_lib_parse_metric(struct path *path, + struct pcep_object_metric *obj); +static void +pcep_lib_parse_endpoints_ipv4(struct path *path, + struct pcep_object_endpoints_ipv4 *obj); +static void +pcep_lib_parse_endpoints_ipv6(struct path *path, + struct pcep_object_endpoints_ipv6 *obj); +static void pcep_lib_parse_vendor_info(struct path *path, + struct pcep_object_vendor_info *obj); +static void pcep_lib_parse_ero(struct path *path, struct pcep_object_ro *ero); +static struct path_hop *pcep_lib_parse_ero_sr(struct path_hop *next, + struct pcep_ro_subobj_sr *sr); +static struct counters_group *copy_counter_group(struct counters_group *from); +static struct counters_subgroup * +copy_counter_subgroup(struct counters_subgroup *from); +static struct counter *copy_counter(struct counter *from); +static void free_counter_group(struct counters_group *group); +static void free_counter_subgroup(struct counters_subgroup *subgroup); +static void free_counter(struct counter *counter); + +struct pcep_lib_pthread_passthrough_data { + void *(*start_routine)(void *data); + void *data; +}; + +/* ------------ API Functions ------------ */ + +int pcep_lib_initialize(struct frr_pthread *fpt) +{ + PCEP_DEBUG("Initializing pceplib"); + + /* Register pceplib logging callback */ + register_logger(pceplib_logging_cb); + + /* Its ok that this object goes out of scope, as it + * wont be stored, and its values will be copied */ + struct pceplib_infra_config infra = { + /* Memory infrastructure */ + .pceplib_infra_mt = MTYPE_PCEPLIB_INFRA, + .pceplib_messages_mt = MTYPE_PCEPLIB_MESSAGES, + .malloc_func = (pceplib_malloc_func)qmalloc, + .calloc_func = (pceplib_calloc_func)qcalloc, + .realloc_func = (pceplib_realloc_func)qrealloc, + .strdup_func = (pceplib_strdup_func)qstrdup, + .free_func = (pceplib_free_func)qfree, + /* Timers infrastructure */ + .external_infra_data = fpt, + .socket_read_func = pcep_lib_pceplib_socket_read_cb, + .socket_write_func = pcep_lib_pceplib_socket_write_cb, + /* PCEP events */ + .pcep_event_func = pcep_lib_pceplib_event_cb, + /* PCEPlib pthread creation callback */ + .pthread_create_func = pcep_lib_pthread_create_cb}; + if (!initialize_pcc_infra(&infra)) { + flog_err(EC_PATH_PCEP_PCC_INIT, "failed to initialize pceplib"); + return 1; + } + + return 0; +} + +void pcep_lib_finalize(void) +{ + PCEP_DEBUG("Finalizing pceplib"); + if (!destroy_pcc()) { + flog_err(EC_PATH_PCEP_PCC_FINI, "failed to finalize pceplib"); + } +} + + +pcep_session * +pcep_lib_connect(struct ipaddr *src_addr, int src_port, struct ipaddr *dst_addr, + int dst_port, short msd, + const struct pcep_config_group_opts *pcep_options) +{ + pcep_configuration *config; + pcep_session *sess; + + config = create_default_pcep_configuration(); + config->dst_pcep_port = dst_port; + config->src_pcep_port = src_port; + if (IS_IPADDR_V6(src_addr)) { + config->is_src_ipv6 = true; + memcpy(&config->src_ip.src_ipv6, &src_addr->ipaddr_v6, + sizeof(struct in6_addr)); + } else { + config->is_src_ipv6 = false; + config->src_ip.src_ipv4 = src_addr->ipaddr_v4; + } + + config->support_stateful_pce_lsp_update = true; + config->support_pce_lsp_instantiation = pcep_options->pce_initiated; + config->support_include_db_version = false; + config->support_lsp_triggered_resync = false; + config->support_lsp_delta_sync = false; + config->support_pce_triggered_initial_sync = false; + config->support_sr_te_pst = true; + config->pcc_can_resolve_nai_to_sid = false; + + config->max_sid_depth = msd; + config->pcep_msg_versioning->draft_ietf_pce_segment_routing_07 = + pcep_options->draft07; + config->keep_alive_seconds = pcep_options->keep_alive_seconds; + config->min_keep_alive_seconds = pcep_options->min_keep_alive_seconds; + config->max_keep_alive_seconds = pcep_options->max_keep_alive_seconds; + config->dead_timer_seconds = pcep_options->dead_timer_seconds; + config->min_dead_timer_seconds = pcep_options->min_dead_timer_seconds; + config->max_dead_timer_seconds = pcep_options->max_dead_timer_seconds; + config->request_time_seconds = pcep_options->pcep_request_time_seconds; + /* TODO when available in the pceplib, set it here + pcep_options->state_timeout_inteval_seconds;*/ + + if (pcep_options->tcp_md5_auth[0] != '\0') { + config->is_tcp_auth_md5 = true; + strlcpy(config->tcp_authentication_str, + pcep_options->tcp_md5_auth, + sizeof(config->tcp_authentication_str)); + } else { + config->is_tcp_auth_md5 = false; + } + + if (IS_IPADDR_V6(dst_addr)) { + sess = connect_pce_ipv6(config, &dst_addr->ipaddr_v6); + } else { + sess = connect_pce(config, &dst_addr->ipaddr_v4); + } + destroy_pcep_configuration(config); + return sess; +} + +void pcep_lib_disconnect(pcep_session *sess) +{ + assert(sess != NULL); + disconnect_pce(sess); +} + +/* Callback passed to pceplib to write to a socket. + * When the socket is ready to be written to, + * pcep_lib_socket_write_ready() will be called */ + +int pcep_lib_pceplib_socket_write_cb(void *fpt, void **thread, int fd, + void *payload) +{ + return pcep_thread_socket_write(fpt, thread, fd, payload, + pcep_lib_socket_write_ready); +} + +/* Callback passed to pceplib to read from a socket. + * When the socket is ready to be read from, + * pcep_lib_socket_read_ready() will be called */ + +int pcep_lib_pceplib_socket_read_cb(void *fpt, void **thread, int fd, + void *payload) +{ + return pcep_thread_socket_read(fpt, thread, fd, payload, + pcep_lib_socket_read_ready); +} + +/* Callbacks called by path_pcep_controller when a socket is ready to read/write + */ + +void pcep_lib_socket_write_ready(struct thread *thread) +{ + struct pcep_ctrl_socket_data *data = THREAD_ARG(thread); + assert(data != NULL); + + pceplib_external_socket_write(data->fd, data->payload); + XFREE(MTYPE_PCEP, data); +} + +void pcep_lib_socket_read_ready(struct thread *thread) +{ + struct pcep_ctrl_socket_data *data = THREAD_ARG(thread); + assert(data != NULL); + + pceplib_external_socket_read(data->fd, data->payload); + XFREE(MTYPE_PCEP, data); +} + +/* Callback passed to pceplib when a pcep_event is ready */ +void pcep_lib_pceplib_event_cb(void *fpt, pcep_event *event) +{ + pcep_thread_send_ctrl_event(fpt, event, pcep_thread_pcep_event); +} + +/* Wrapper function around the actual pceplib thread start function */ +void *pcep_lib_pthread_start_passthrough(void *data) +{ + struct frr_pthread *fpt = data; + struct pcep_lib_pthread_passthrough_data *passthrough_data = fpt->data; + void *start_routine_data = passthrough_data->data; + void *(*start_routine)(void *) = passthrough_data->start_routine; + XFREE(MTYPE_PCEP, passthrough_data); + + if (start_routine != NULL) { + return start_routine(start_routine_data); + } + + return NULL; +} + +int pcep_lib_pthread_create_cb(pthread_t *thread_id, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *data, + const char *thread_name) +{ + /* Since FRR calls the start_routine with a struct frr_pthread, + * we have to store the real data and callback in a passthrough + * and pass the actual data the start_routine needs */ + struct pcep_lib_pthread_passthrough_data *passthrough_data = XMALLOC( + MTYPE_PCEP, sizeof(struct pcep_lib_pthread_passthrough_data)); + passthrough_data->data = data; + passthrough_data->start_routine = start_routine; + + struct frr_pthread_attr fpt_attr = { + .start = pcep_lib_pthread_start_passthrough, + .stop = pcep_lib_pthread_stop_cb}; + struct frr_pthread *fpt = + frr_pthread_new(&fpt_attr, thread_name, "pcep_lib"); + if (fpt == NULL) { + return 1; + } + + fpt->data = passthrough_data; + int retval = frr_pthread_run(fpt, attr); + if (retval) { + return retval; + } + + *thread_id = fpt->thread; + + return 0; +} + +int pcep_lib_pthread_stop_cb(struct frr_pthread *fpt, void **res) +{ + pcep_lib_finalize(); + frr_pthread_destroy(fpt); + + return 0; +} + +struct pcep_message *pcep_lib_format_report(struct pcep_caps *caps, + struct path *path) +{ + double_linked_list *objs = pcep_lib_format_path(caps, path); + return pcep_msg_create_report(objs); +} + +static struct pcep_object_rp *create_rp(uint32_t reqid) +{ + double_linked_list *rp_tlvs; + struct pcep_object_tlv_path_setup_type *setup_type_tlv; + struct pcep_object_rp *rp; + + rp_tlvs = dll_initialize(); + setup_type_tlv = pcep_tlv_create_path_setup_type(SR_TE_PST); + dll_append(rp_tlvs, setup_type_tlv); + + rp = pcep_obj_create_rp(0, false, false, false, true, reqid, rp_tlvs); + + return rp; +} + +struct pcep_message *pcep_lib_format_request(struct pcep_caps *caps, + struct path *path) +{ + struct ipaddr *src = &path->pcc_addr; + struct ipaddr *dst = &path->nbkey.endpoint; + double_linked_list *objs; + struct pcep_object_rp *rp; + struct pcep_object_endpoints_ipv4 *endpoints_ipv4; + struct pcep_object_endpoints_ipv6 *endpoints_ipv6; + struct pcep_object_objective_function *of = NULL; + enum objfun_type objfun = OBJFUN_UNDEFINED; + + assert(src->ipa_type == dst->ipa_type); + + objs = dll_initialize(); + rp = create_rp(path->req_id); + rp->header.flag_p = true; + + pcep_lib_format_constraints(path, objs); + + /* Objective Function */ + if (path->has_pcc_objfun) { + objfun = path->pcc_objfun; + } + + if (objfun != OBJFUN_UNDEFINED) { + of = pcep_obj_create_objective_function(objfun, NULL); + assert(of != NULL); + of->header.flag_p = path->enforce_pcc_objfun; + dll_append(objs, of); + } + + if (IS_IPADDR_V6(src)) { + endpoints_ipv6 = pcep_obj_create_endpoint_ipv6(&src->ipaddr_v6, + &dst->ipaddr_v6); + endpoints_ipv6->header.flag_p = true; + return pcep_msg_create_request_ipv6(rp, endpoints_ipv6, objs); + } else { + endpoints_ipv4 = pcep_obj_create_endpoint_ipv4(&src->ipaddr_v4, + &dst->ipaddr_v4); + endpoints_ipv4->header.flag_p = true; + return pcep_msg_create_request(rp, endpoints_ipv4, objs); + } +} + +struct pcep_message *pcep_lib_format_error(int error_type, int error_value, + struct path *path) +{ + double_linked_list *objs, *srp_tlvs; + struct pcep_object_srp *srp; + struct pcep_object_tlv_header *tlv; + + if ((path == NULL) || (path->srp_id == 0)) + return pcep_msg_create_error(error_type, error_value); + + objs = dll_initialize(); + srp_tlvs = dll_initialize(); + tlv = (struct pcep_object_tlv_header *)pcep_tlv_create_path_setup_type( + SR_TE_PST); + dll_append(srp_tlvs, tlv); + srp = pcep_obj_create_srp(path->do_remove, path->srp_id, srp_tlvs); + dll_append(objs, srp); + return pcep_msg_create_error_with_objects(error_type, error_value, + objs); +} + +struct pcep_message *pcep_lib_format_request_cancelled(uint32_t reqid) +{ + struct pcep_object_notify *notify; + double_linked_list *objs; + struct pcep_object_rp *rp; + + notify = pcep_obj_create_notify( + PCEP_NOTIFY_TYPE_PENDING_REQUEST_CANCELLED, + PCEP_NOTIFY_VALUE_PCC_CANCELLED_REQUEST); + objs = dll_initialize(); + rp = create_rp(reqid); + dll_append(objs, rp); + + return pcep_msg_create_notify(notify, objs); +} + +struct path *pcep_lib_parse_path(struct pcep_message *msg) +{ + struct path *path; + double_linked_list *objs = msg->obj_list; + double_linked_list_node *node; + + struct pcep_object_header *obj; + struct pcep_object_rp *rp = NULL; + struct pcep_object_srp *srp = NULL; + struct pcep_object_lsp *lsp = NULL; + struct pcep_object_lspa *lspa = NULL; + struct pcep_object_ro *ero = NULL; + struct pcep_object_metric *metric = NULL; + struct pcep_object_bandwidth *bandwidth = NULL; + struct pcep_object_objective_function *of = NULL; + struct pcep_object_endpoints_ipv4 *epv4 = NULL; + struct pcep_object_endpoints_ipv6 *epv6 = NULL; + struct pcep_object_vendor_info *vendor_info = NULL; + + path = pcep_new_path(); + + for (node = objs->head; node != NULL; node = node->next_node) { + obj = (struct pcep_object_header *)node->data; + switch (CLASS_TYPE(obj->object_class, obj->object_type)) { + case CLASS_TYPE(PCEP_OBJ_CLASS_RP, PCEP_OBJ_TYPE_RP): + assert(rp == NULL); + rp = (struct pcep_object_rp *)obj; + pcep_lib_parse_rp(path, rp); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_SRP, PCEP_OBJ_TYPE_SRP): + assert(srp == NULL); + srp = (struct pcep_object_srp *)obj; + pcep_lib_parse_srp(path, srp); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_LSP, PCEP_OBJ_TYPE_LSP): + /* Only support single LSP per message */ + assert(lsp == NULL); + lsp = (struct pcep_object_lsp *)obj; + pcep_lib_parse_lsp(path, lsp); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_LSPA, PCEP_OBJ_TYPE_LSPA): + assert(lspa == NULL); + lspa = (struct pcep_object_lspa *)obj; + pcep_lib_parse_lspa(path, lspa); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO): + /* Only support single ERO per message */ + assert(ero == NULL); + ero = (struct pcep_object_ro *)obj; + pcep_lib_parse_ero(path, ero); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC): + metric = (struct pcep_object_metric *)obj; + pcep_lib_parse_metric(path, metric); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_BANDWIDTH, + PCEP_OBJ_TYPE_BANDWIDTH_REQ): + case CLASS_TYPE(PCEP_OBJ_CLASS_BANDWIDTH, + PCEP_OBJ_TYPE_BANDWIDTH_CISCO): + bandwidth = (struct pcep_object_bandwidth *)obj; + path->has_bandwidth = true; + path->bandwidth = bandwidth->bandwidth; + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH): + path->no_path = true; + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_OF, PCEP_OBJ_TYPE_OF): + of = (struct pcep_object_objective_function *)obj; + path->has_pce_objfun = true; + path->pce_objfun = of->of_code; + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_ENDPOINTS, + PCEP_OBJ_TYPE_ENDPOINT_IPV4): + epv4 = (struct pcep_object_endpoints_ipv4 *)obj; + pcep_lib_parse_endpoints_ipv4(path, epv4); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_ENDPOINTS, + PCEP_OBJ_TYPE_ENDPOINT_IPV6): + epv6 = (struct pcep_object_endpoints_ipv6 *)obj; + pcep_lib_parse_endpoints_ipv6(path, epv6); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_VENDOR_INFO, + PCEP_OBJ_TYPE_VENDOR_INFO): + vendor_info = (struct pcep_object_vendor_info *)obj; + pcep_lib_parse_vendor_info(path, vendor_info); + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_OBJECT, + "Unexpected PCEP object %s (%u) / %s (%u)", + pcep_object_class_name(obj->object_class), + obj->object_class, + pcep_object_type_name(obj->object_class, + obj->object_type), + obj->object_type); + break; + } + } + + return path; +} + +void pcep_lib_parse_capabilities(struct pcep_message *msg, + struct pcep_caps *caps) +{ + double_linked_list *objs = msg->obj_list; + double_linked_list_node *node; + + struct pcep_object_header *obj; + struct pcep_object_open *open = NULL; + + for (node = objs->head; node != NULL; node = node->next_node) { + obj = (struct pcep_object_header *)node->data; + switch (CLASS_TYPE(obj->object_class, obj->object_type)) { + case CLASS_TYPE(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN): + assert(open == NULL); + open = (struct pcep_object_open *)obj; + pcep_lib_parse_open(caps, open); + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_OBJECT, + "Unexpected PCEP object %s (%u) / %s (%u)", + pcep_object_class_name(obj->object_class), + obj->object_class, + pcep_object_type_name(obj->object_class, + obj->object_type), + obj->object_type); + break; + } + } +} + +struct counters_group *pcep_lib_copy_counters(pcep_session *sess) +{ + if (!sess || !sess->pcep_session_counters) { + return NULL; + } + + return copy_counter_group(sess->pcep_session_counters); +} + +void pcep_lib_free_counters(struct counters_group *counters) +{ + free_counter_group(counters); +} + +pcep_session *pcep_lib_copy_pcep_session(pcep_session *sess) +{ + if (!sess) { + return NULL; + } + + pcep_session *copy; + copy = XCALLOC(MTYPE_PCEP, sizeof(*copy)); + memcpy(copy, sess, sizeof(*copy)); + /* These fields should not be accessed */ + copy->num_unknown_messages_time_queue = NULL; + copy->socket_comm_session = NULL; + copy->pcep_session_counters = NULL; + + return copy; +} + +/* ------------ pceplib logging callback ------------ */ + +int pceplib_logging_cb(int priority, const char *fmt, va_list args) +{ + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), fmt, args); + PCEP_DEBUG_PCEPLIB(priority, "pceplib: %s", buffer); + return 0; +} + +/* ------------ Internal Functions ------------ */ + +double_linked_list *pcep_lib_format_path(struct pcep_caps *caps, + struct path *path) +{ + struct in_addr addr_null; + double_linked_list *objs, *srp_tlvs, *lsp_tlvs, *ero_objs; + struct pcep_object_tlv_header *tlv; + struct pcep_object_ro_subobj *ero_obj; + struct pcep_object_srp *srp; + struct pcep_object_lsp *lsp; + struct pcep_object_ro *ero; + uint32_t encoded_binding_sid; + char binding_sid_lsp_tlv_data[6]; + + memset(&addr_null, 0, sizeof(addr_null)); + + objs = dll_initialize(); + + if (path->plsp_id != 0) { + /* SRP object */ + srp_tlvs = dll_initialize(); + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_path_setup_type(SR_TE_PST); + assert(tlv != NULL); + dll_append(srp_tlvs, tlv); + srp = pcep_obj_create_srp(path->do_remove, path->srp_id, + srp_tlvs); + assert(srp != NULL); + srp->header.flag_p = true; + dll_append(objs, srp); + } + + /* LSP object */ + lsp_tlvs = dll_initialize(); + + if (path->plsp_id == 0 || IS_IPADDR_NONE(&path->nbkey.endpoint) + || IS_IPADDR_NONE(&path->pcc_addr)) { + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_ipv4_lsp_identifiers( + &addr_null, &addr_null, 0, 0, &addr_null); + } else { + assert(path->pcc_addr.ipa_type + == path->nbkey.endpoint.ipa_type); + if (IS_IPADDR_V4(&path->pcc_addr)) { + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_ipv4_lsp_identifiers( + &path->pcc_addr.ipaddr_v4, + &path->nbkey.endpoint.ipaddr_v4, 0, 0, + &path->pcc_addr.ipaddr_v4); + } else { + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_ipv6_lsp_identifiers( + &path->pcc_addr.ipaddr_v6, + &path->nbkey.endpoint.ipaddr_v6, 0, 0, + &path->pcc_addr.ipaddr_v6); + } + } + assert(tlv != NULL); + dll_append(lsp_tlvs, tlv); + if (path->name != NULL) { + tlv = (struct pcep_object_tlv_header *) + /*FIXME: Remove the typecasty when pceplib is changed + to take a const char* */ + pcep_tlv_create_symbolic_path_name((char *)path->name, + strlen(path->name)); + assert(tlv != NULL); + dll_append(lsp_tlvs, tlv); + } + if ((path->plsp_id != 0) && (path->binding_sid != MPLS_LABEL_NONE)) { + memset(binding_sid_lsp_tlv_data, 0, 2); + encoded_binding_sid = htonl(path->binding_sid << 12); + memcpy(binding_sid_lsp_tlv_data + 2, &encoded_binding_sid, 4); + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_tlv_arbitrary( + binding_sid_lsp_tlv_data, + sizeof(binding_sid_lsp_tlv_data), + PCEP_OBJ_TYPE_CISCO_BSID); + assert(tlv != NULL); + dll_append(lsp_tlvs, tlv); + } + lsp = pcep_obj_create_lsp( + path->plsp_id, path->status, path->was_created /* C Flag */, + path->go_active /* A Flag */, path->was_removed /* R Flag */, + path->is_synching /* S Flag */, path->is_delegated /* D Flag */, + lsp_tlvs); + assert(lsp != NULL); + lsp->header.flag_p = true; + dll_append(objs, lsp); + /* ERO object */ + ero_objs = dll_initialize(); + for (struct path_hop *hop = path->first_hop; hop != NULL; + hop = hop->next) { + uint32_t sid; + + /* Only supporting MPLS hops with both sid and nai */ + assert(hop->is_mpls); + assert(hop->has_sid); + + if (hop->has_attribs) { + sid = ENCODE_SR_ERO_SID(hop->sid.mpls.label, + hop->sid.mpls.traffic_class, + hop->sid.mpls.is_bottom, + hop->sid.mpls.ttl); + } else { + sid = ENCODE_SR_ERO_SID(hop->sid.mpls.label, 0, 0, 0); + } + + ero_obj = NULL; + if (hop->has_nai) { + assert(hop->nai.type != PCEP_SR_SUBOBJ_NAI_ABSENT); + assert(hop->nai.type + != PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY); + assert(hop->nai.type != PCEP_SR_SUBOBJ_NAI_UNKNOWN); + switch (hop->nai.type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_ipv4_node( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + &hop->nai.local_addr.ipaddr_v4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_ipv6_node( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + &hop->nai.local_addr.ipaddr_v6); + break; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_ipv4_adj( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + &hop->nai.local_addr.ipaddr_v4, + &hop->nai.remote_addr + .ipaddr_v4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_ipv6_adj( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + &hop->nai.local_addr.ipaddr_v6, + &hop->nai.remote_addr + .ipaddr_v6); + break; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + hop->nai.local_addr.ipaddr_v4 + .s_addr, + hop->nai.local_iface, + hop->nai.remote_addr.ipaddr_v4 + .s_addr, + hop->nai.remote_iface); + break; + default: + break; + } + } + if (ero_obj == NULL) { + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_nonai( + hop->is_loose, sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls); /* M Flag */ + } + dll_append(ero_objs, ero_obj); + } + ero = pcep_obj_create_ero(ero_objs); + assert(ero != NULL); + ero->header.flag_p = true; + dll_append(objs, ero); + + if (path->plsp_id == 0) { + return objs; + } + + pcep_lib_format_constraints(path, objs); + + return objs; +} + +void pcep_lib_format_constraints(struct path *path, double_linked_list *objs) +{ + struct pcep_object_metric *metric; + struct pcep_object_bandwidth *bandwidth; + struct pcep_object_lspa *lspa; + + /* LSPA object */ + if (path->has_affinity_filters) { + lspa = pcep_obj_create_lspa( + path->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY - 1], + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY - 1], + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL - 1], + DEFAULT_LSAP_SETUP_PRIO, DEFAULT_LSAP_HOLDING_PRIO, + DEFAULT_LSAP_LOCAL_PRETECTION); + assert(lspa != NULL); + lspa->header.flag_p = true; + dll_append(objs, lspa); + } + + /* Bandwidth Objects */ + if (path->has_bandwidth) { + /* Requested Bandwidth */ + bandwidth = pcep_obj_create_bandwidth(path->bandwidth); + assert(bandwidth != NULL); + bandwidth->header.flag_p = path->enforce_bandwidth; + dll_append(objs, bandwidth); + } + + /* Metric Objects */ + for (struct path_metric *m = path->first_metric; m != NULL; + m = m->next) { + metric = pcep_obj_create_metric(m->type, m->is_bound, + m->is_computed, m->value); + assert(metric != NULL); + metric->header.flag_p = m->enforce; + dll_append(objs, metric); + } +} + +void pcep_lib_parse_open(struct pcep_caps *caps, struct pcep_object_open *open) +{ + double_linked_list *tlvs = open->header.tlv_list; + double_linked_list_node *node; + struct pcep_object_tlv_header *tlv_header; + + caps->is_stateful = false; + caps->supported_ofs_are_known = false; + caps->supported_ofs = 0; + + for (node = tlvs->head; node != NULL; node = node->next_node) { + tlv_header = (struct pcep_object_tlv_header *)node->data; + switch (tlv_header->type) { + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + pcep_lib_parse_open_pce_capability(caps, tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + break; + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + pcep_lib_parse_open_objfun_list(caps, tlv_header); + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected OPEN's TLV %s (%u)", + pcep_tlv_type_name(tlv_header->type), + tlv_header->type); + break; + } + } +} + +void pcep_lib_parse_open_pce_capability( + struct pcep_caps *caps, struct pcep_object_tlv_header *tlv_header) +{ + struct pcep_object_tlv_stateful_pce_capability *tlv; + tlv = (struct pcep_object_tlv_stateful_pce_capability *)tlv_header; + caps->is_stateful = tlv->flag_u_lsp_update_capability; +} + +void pcep_lib_parse_open_objfun_list(struct pcep_caps *caps, + struct pcep_object_tlv_header *tlv_header) +{ + double_linked_list_node *node; + struct pcep_object_tlv_of_list *tlv; + tlv = (struct pcep_object_tlv_of_list *)tlv_header; + uint16_t of_code; + caps->supported_ofs_are_known = true; + for (node = tlv->of_list->head; node != NULL; node = node->next_node) { + of_code = *(uint16_t *)node->data; + if (of_code >= 32) { + zlog_warn( + "Ignoring unexpected objective function with code %u", + of_code); + continue; + } + SET_FLAG(caps->supported_ofs, of_code); + } +} + +void pcep_lib_parse_rp(struct path *path, struct pcep_object_rp *rp) +{ + double_linked_list *tlvs = rp->header.tlv_list; + double_linked_list_node *node; + struct pcep_object_tlv_header *tlv; + + if (tlvs == NULL) { + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected Empty RP's TLV plsp-id:(%d)", + path ? (int32_t)path->plsp_id : -1); + return; + } + /* We ignore the other flags and priority for now */ + path->req_id = rp->request_id; + path->has_pce_objfun = false; + path->pce_objfun = OBJFUN_UNDEFINED; + + for (node = tlvs->head; node != NULL; node = node->next_node) { + tlv = (struct pcep_object_tlv_header *)node->data; + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + // TODO: enforce the path setup type is SR_TE_PST + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected RP's TLV %s (%u)", + pcep_tlv_type_name(tlv->type), tlv->type); + break; + } + } +} + +void pcep_lib_parse_srp(struct path *path, struct pcep_object_srp *srp) +{ + double_linked_list *tlvs = srp->header.tlv_list; + double_linked_list_node *node; + struct pcep_object_tlv_header *tlv; + + path->do_remove = srp->flag_lsp_remove; + path->srp_id = srp->srp_id_number; + + if (tlvs == NULL) { + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected Empty SRP's TLV plsp-id:(%d)", + path ? (int32_t)path->plsp_id : -1); + return; + } + for (node = tlvs->head; node != NULL; node = node->next_node) { + tlv = (struct pcep_object_tlv_header *)node->data; + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + // TODO: enforce the path setup type is SR_TE_PST + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected SRP's TLV %s (%u)", + pcep_tlv_type_name(tlv->type), tlv->type); + break; + } + } +} + +void pcep_lib_parse_lsp(struct path *path, struct pcep_object_lsp *lsp) +{ + double_linked_list *tlvs = lsp->header.tlv_list; + double_linked_list_node *node; + struct pcep_object_tlv_header *tlv; + struct pcep_object_tlv_symbolic_path_name *name; + struct pcep_object_tlv_arbitrary *arb_tlv; + + path->plsp_id = lsp->plsp_id; + path->status = lsp->operational_status; + path->go_active = lsp->flag_a; + path->was_created = lsp->flag_c; + path->was_removed = lsp->flag_r; + path->is_synching = lsp->flag_s; + path->is_delegated = lsp->flag_d; + + if (tlvs == NULL) { + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected Empty LSP's TLV plsp-id:(%d)", + path ? (int32_t)path->plsp_id : -1); + return; + } + + for (node = tlvs->head; node != NULL; node = node->next_node) { + tlv = (struct pcep_object_tlv_header *)node->data; + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + name = (struct pcep_object_tlv_symbolic_path_name *)tlv; + pcep_lib_parse_lsp_symbolic_name(path, name); + break; + case PCEP_OBJ_TYPE_CISCO_BSID: + arb_tlv = (struct pcep_object_tlv_arbitrary *)tlv; + memcpy(&path->binding_sid, arb_tlv->data + 2, + sizeof(path->binding_sid)); + path->binding_sid = ntohl(path->binding_sid); + path->binding_sid = (path->binding_sid >> 12); + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected LSP TLV %s (%u)", + pcep_tlv_type_name(tlv->type), tlv->type); + break; + } + } +} + +void pcep_lib_parse_lsp_symbolic_name( + struct path *path, struct pcep_object_tlv_symbolic_path_name *tlv) +{ + uint16_t size = tlv->symbolic_path_name_length; + assert(path->name == NULL); + size = size > MAX_PATH_NAME_SIZE ? MAX_PATH_NAME_SIZE : size; + path->name = XCALLOC(MTYPE_PCEP, size); + strlcpy((char *)path->name, tlv->symbolic_path_name, size + 1); +} + +void pcep_lib_parse_lspa(struct path *path, struct pcep_object_lspa *lspa) +{ + path->has_affinity_filters = true; + path->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY - 1] = + lspa->lspa_exclude_any; + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY - 1] = + lspa->lspa_include_any; + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL - 1] = + lspa->lspa_include_all; +} + +void pcep_lib_parse_metric(struct path *path, struct pcep_object_metric *obj) +{ + struct path_metric *metric; + + metric = pcep_new_metric(); + metric->type = obj->type; + metric->is_bound = obj->flag_b; + metric->is_computed = obj->flag_c; + metric->value = obj->value; + metric->next = path->first_metric; + path->first_metric = metric; +} + +void pcep_lib_parse_endpoints_ipv4(struct path *path, + struct pcep_object_endpoints_ipv4 *obj) +{ + SET_IPADDR_V4(&path->pcc_addr); + path->pcc_addr.ipaddr_v4 = obj->src_ipv4; + SET_IPADDR_V4(&path->nbkey.endpoint); + path->nbkey.endpoint.ipaddr_v4 = obj->dst_ipv4; +} + +void pcep_lib_parse_endpoints_ipv6(struct path *path, + struct pcep_object_endpoints_ipv6 *obj) +{ + SET_IPADDR_V6(&path->pcc_addr); + path->pcc_addr.ipaddr_v6 = obj->src_ipv6; + SET_IPADDR_V6(&path->nbkey.endpoint); + path->nbkey.endpoint.ipaddr_v6 = obj->dst_ipv6; +} + +void pcep_lib_parse_vendor_info(struct path *path, + struct pcep_object_vendor_info *obj) +{ + if (obj->enterprise_number == ENTERPRISE_NUMBER_CISCO + && obj->enterprise_specific_info == ENTERPRISE_COLOR_CISCO) + path->nbkey.color = obj->enterprise_specific_info1; + else + path->nbkey.color = 0; +} + +void pcep_lib_parse_ero(struct path *path, struct pcep_object_ro *ero) +{ + struct path_hop *hop = NULL; + double_linked_list *objs = ero->sub_objects; + double_linked_list_node *node; + struct pcep_object_ro_subobj *obj; + + if (objs == NULL) { + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected Empty ERO's sub_obj plsp-id:(%d)", + path ? (int32_t)path->plsp_id : -1); + return; + } + for (node = objs->tail; node != NULL; node = node->prev_node) { + obj = (struct pcep_object_ro_subobj *)node->data; + switch (obj->ro_subobj_type) { + case RO_SUBOBJ_TYPE_SR: + hop = pcep_lib_parse_ero_sr( + hop, (struct pcep_ro_subobj_sr *)obj); + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_ERO_SUBOBJ, + "Unexpected ERO sub-object %s (%u)", + pcep_ro_type_name(obj->ro_subobj_type), + obj->ro_subobj_type); + break; + } + } + + path->first_hop = hop; +} + +struct path_hop *pcep_lib_parse_ero_sr(struct path_hop *next, + struct pcep_ro_subobj_sr *sr) +{ + struct path_hop *hop = NULL; + union sid sid; + + /* Only support IPv4 node with SID */ + assert(!sr->flag_s); + + if (sr->flag_m) { + sid.mpls = (struct sid_mpls){ + .label = GET_SR_ERO_SID_LABEL(sr->sid), + .traffic_class = GET_SR_ERO_SID_TC(sr->sid), + .is_bottom = GET_SR_ERO_SID_S(sr->sid), + .ttl = GET_SR_ERO_SID_TTL(sr->sid)}; + } else { + sid.value = sr->sid; + } + + hop = pcep_new_hop(); + *hop = (struct path_hop){.next = next, + .is_loose = + sr->ro_subobj.flag_subobj_loose_hop, + .has_sid = !sr->flag_s, + .is_mpls = sr->flag_m, + .has_attribs = sr->flag_c, + .sid = sid, + .has_nai = !sr->flag_f, + .nai = {.type = sr->nai_type}}; + + if (!sr->flag_f) { + assert(sr->nai_list != NULL); + double_linked_list_node *n = sr->nai_list->head; + assert(n != NULL); + assert(n->data != NULL); + switch (sr->nai_type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + hop->nai.local_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.local_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + hop->nai.local_addr.ipa_type = IPADDR_V6; + memcpy(&hop->nai.local_addr.ipaddr_v6, n->data, + sizeof(struct in6_addr)); + break; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + hop->nai.local_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.local_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.remote_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.remote_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + hop->nai.local_addr.ipa_type = IPADDR_V6; + memcpy(&hop->nai.local_addr.ipaddr_v6, n->data, + sizeof(struct in6_addr)); + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.remote_addr.ipa_type = IPADDR_V6; + memcpy(&hop->nai.remote_addr.ipaddr_v6, n->data, + sizeof(struct in6_addr)); + break; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + hop->nai.local_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.local_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.local_iface = *(uint32_t *)n->data; + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.remote_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.remote_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.remote_iface = *(uint32_t *)n->data; + break; + default: + hop->has_nai = false; + flog_warn(EC_PATH_PCEP_UNEXPECTED_SR_NAI, + "Unexpected SR segment NAI type %s (%u)", + pcep_nai_type_name(sr->nai_type), + sr->nai_type); + break; + } + } + + return hop; +} + +struct counters_group *copy_counter_group(struct counters_group *from) +{ + int size, i; + struct counters_group *result; + if (from == NULL) + return NULL; + assert(from->max_subgroups >= from->num_subgroups); + result = XCALLOC(MTYPE_PCEP, sizeof(*result)); + memcpy(result, from, sizeof(*result)); + size = sizeof(struct counters_subgroup *) * (from->max_subgroups + 1); + result->subgroups = XCALLOC(MTYPE_PCEP, size); + for (i = 0; i <= from->max_subgroups; i++) + result->subgroups[i] = + copy_counter_subgroup(from->subgroups[i]); + return result; +} + +struct counters_subgroup *copy_counter_subgroup(struct counters_subgroup *from) +{ + int size, i; + struct counters_subgroup *result; + if (from == NULL) + return NULL; + assert(from->max_counters >= from->num_counters); + result = XCALLOC(MTYPE_PCEP, sizeof(*result)); + memcpy(result, from, sizeof(*result)); + size = sizeof(struct counter *) * (from->max_counters + 1); + result->counters = XCALLOC(MTYPE_PCEP, size); + for (i = 0; i <= from->max_counters; i++) + result->counters[i] = copy_counter(from->counters[i]); + return result; +} + +struct counter *copy_counter(struct counter *from) +{ + struct counter *result; + if (from == NULL) + return NULL; + result = XCALLOC(MTYPE_PCEP, sizeof(*result)); + memcpy(result, from, sizeof(*result)); + return result; +} + +void free_counter_group(struct counters_group *group) +{ + int i; + if (group == NULL) + return; + for (i = 0; i <= group->max_subgroups; i++) + free_counter_subgroup(group->subgroups[i]); + XFREE(MTYPE_PCEP, group->subgroups); + XFREE(MTYPE_PCEP, group); +} + +void free_counter_subgroup(struct counters_subgroup *subgroup) +{ + int i; + if (subgroup == NULL) + return; + for (i = 0; i <= subgroup->max_counters; i++) + free_counter(subgroup->counters[i]); + XFREE(MTYPE_PCEP, subgroup->counters); + XFREE(MTYPE_PCEP, subgroup); +} + +void free_counter(struct counter *counter) +{ + if (counter == NULL) + return; + XFREE(MTYPE_PCEP, counter); +} diff --git a/pathd/path_pcep_lib.h b/pathd/path_pcep_lib.h new file mode 100644 index 0000000..524f385 --- /dev/null +++ b/pathd/path_pcep_lib.h @@ -0,0 +1,49 @@ +/* + * 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 + */ + +#ifndef _PATH_PCEP_LIB_H_ +#define _PATH_PCEP_LIB_H_ + +#include <stdbool.h> +#include "pceplib/pcep_pcc_api.h" +#include "frr_pthread.h" +#include "pathd/path_pcep.h" + +int pcep_lib_initialize(struct frr_pthread *fpt); +void pcep_lib_finalize(void); +pcep_session * +pcep_lib_connect(struct ipaddr *src_addr, int src_port, struct ipaddr *dst_addr, + int dst_port, short msd, + const struct pcep_config_group_opts *pcep_options); +void pcep_lib_disconnect(pcep_session *sess); +struct pcep_message *pcep_lib_format_report(struct pcep_caps *caps, + struct path *path); +struct pcep_message *pcep_lib_format_request(struct pcep_caps *caps, + struct path *path); +struct pcep_message *pcep_lib_format_request_cancelled(uint32_t reqid); + +struct pcep_message *pcep_lib_format_error(int error_type, int error_value, + struct path *path); +struct path *pcep_lib_parse_path(struct pcep_message *msg); +void pcep_lib_parse_capabilities(struct pcep_message *msg, + struct pcep_caps *caps); +struct counters_group *pcep_lib_copy_counters(pcep_session *sess); +void pcep_lib_free_counters(struct counters_group *counters); +pcep_session *pcep_lib_copy_pcep_session(pcep_session *sess); + +#endif // _PATH_PCEP_LIB_H_ diff --git a/pathd/path_pcep_pcc.c b/pathd/path_pcep_pcc.c new file mode 100644 index 0000000..a1c56f9 --- /dev/null +++ b/pathd/path_pcep_pcc.c @@ -0,0 +1,2002 @@ +/* + * 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 "lib/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.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 + +pthread_mutex_t g_pcc_info_mtx = PTHREAD_MUTEX_INITIALIZER; + +/* 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_initiate(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 continue_pcep_lsp_update(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct path *path, void *payload); +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, + struct path *trigger_path); +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 struct req_entry *pop_req_no_reqid(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) +{ + 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 %pIA:%d due to missing PCC IPv4 address", + &pcc_state->pce_opts->addr, + 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 %pIA:%d due to missing PCC IPv6 address", + &pcc_state->pce_opts->addr, + 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 %pIA:%d due to missing PCC address", + &pcc_state->pce_opts->addr, + 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 %pIA:%d from %pIA:%d", + &pcc_state->pce_opts->addr, + pcc_state->pce_opts->port, + &pcc_state->pcc_addr_tr, + 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, + bool is_stable) +{ + if ((pcc_state->status != PCEP_PCC_OPERATING) + || (!pcc_state->caps.is_stateful)) { + pcep_free_path(path); + return; + } + + PCEP_DEBUG("(%s)%s Send report for candidate path %s", __func__, + pcc_state->tag, path->name); + + /* ODL and Cisco requires the first reported + * LSP to have a DOWN status, the later status changes + * will be comunicated through hook calls. + */ + enum pcep_lsp_operational_status real_status = path->status; + path->status = PCEP_LSP_OPERATIONAL_DOWN; + send_report(pcc_state, path); + + /* If no update is expected and the real status wasn't down, we need to + * send a second report with the real status */ + if (is_stable && (real_status != PCEP_LSP_OPERATIONAL_DOWN)) { + PCEP_DEBUG("(%s)%s Send report for candidate path (!DOWN) %s", + __func__, pcc_state->tag, path->name); + path->status = real_status; + send_report(pcc_state, path); + } + + pcep_free_path(path); +} + + +void pcep_pcc_send_error(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct pcep_error *error, + bool sub_type) +{ + + PCEP_DEBUG("(%s) Send error after PcInitiated ", __func__); + + + send_pcep_error(pcc_state, error->error_type, error->error_value, + error->path); + pcep_free_path(error->path); + XFREE(MTYPE_PCEP, error); +} +/* ------------ Timeout handler ------------ */ + +void pcep_pcc_timeout_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + enum pcep_ctrl_timeout_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; + /* Removed as response to a PcInitiated 'R'emove*/ + /* RFC 8281 #5.4 LSP Deletion*/ + path->do_remove = path->was_removed; + 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) { + pthread_mutex_lock(&g_pcc_info_mtx); + // 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; + } + } + } + } + pthread_mutex_unlock(&g_pcc_info_mtx); + } + + 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; + if(pcc_state->pcc_opts){ + 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; + pthread_mutex_lock(&g_pcc_info_mtx); + pcc_info->is_best_multi_pce = pcc_state->is_best; + pcc_info->previous_best = pcc_state->previous_best; + pthread_mutex_unlock(&g_pcc_info_mtx); + pcc_info->precedence = + pcc_state->pce_opts ? pcc_state->pce_opts->precedence : 0; + if(pcc_state->pcc_addr_tr.ipa_type != IPADDR_NONE){ + 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) +{ + struct path *path; + path = pcep_lib_parse_path(msg); + lookup_nbkey(pcc_state, path); + pcep_thread_refine_path(ctrl_state, pcc_state->id, + &continue_pcep_lsp_update, path, NULL); +} + +void continue_pcep_lsp_update(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path, + void *payload) +{ + char err[MAX_ERROR_MSG_SIZE] = {0}; + + 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) +{ + char err[MAX_ERROR_MSG_SIZE] = ""; + struct path *path; + + path = pcep_lib_parse_path(msg); + + if (!pcc_state->pce_opts->config_opts.pce_initiated) { + /* PCE Initiated is not enabled */ + flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "Not allowed PCE initiated path received: %s", + format_pcep_message(msg)); + send_pcep_error(pcc_state, PCEP_ERRT_LSP_INSTANTIATE_ERROR, + PCEP_ERRV_UNACCEPTABLE_INSTANTIATE_ERROR, path); + return; + } + + if (path->do_remove) { + // lookup in nbkey sequential as no endpoint + struct nbkey_map_data *key; + char endpoint[46]; + + frr_each (nbkey_map, &pcc_state->nbkey_map, key) { + ipaddr2str(&key->nbkey.endpoint, endpoint, + sizeof(endpoint)); + flog_warn( + EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "FOR_EACH nbkey [color (%d) endpoint (%s)] path [plsp_id (%d)] ", + key->nbkey.color, endpoint, path->plsp_id); + if (path->plsp_id == key->plspid) { + flog_warn( + EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "FOR_EACH MATCH nbkey [color (%d) endpoint (%s)] path [plsp_id (%d)] ", + key->nbkey.color, endpoint, + path->plsp_id); + path->nbkey = key->nbkey; + break; + } + } + } else { + if (path->first_hop == NULL /*ero sets first_hop*/) { + /* If the PCC receives a PCInitiate message without an + * ERO and the R flag in the SRP object != zero, then it + * MUST send a PCErr message with Error-type=6 + * (Mandatory Object missing) and Error-value=9 (ERO + * object missing). */ + flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "ERO object missing or incomplete : %s", + format_pcep_message(msg)); + send_pcep_error(pcc_state, + PCEP_ERRT_LSP_INSTANTIATE_ERROR, + PCEP_ERRV_INTERNAL_ERROR, path); + return; + } + + if (path->plsp_id != 0) { + /* If the PCC receives a PCInitiate message with a + * non-zero PLSP-ID and the R flag in the SRP object set + * to zero, then it MUST send a PCErr message with + * Error-type=19 (Invalid Operation) and Error-value=8 + * (Non-zero PLSP-ID in the LSP Initiate Request) */ + flog_warn( + EC_PATH_PCEP_PROTOCOL_ERROR, + "PCE initiated path with non-zero PLSP ID: %s", + format_pcep_message(msg)); + send_pcep_error(pcc_state, PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_INIT_NON_ZERO_PLSP_ID, + path); + return; + } + + if (path->name == NULL) { + /* If the PCC receives a PCInitiate message without a + * SYMBOLIC-PATH-NAME TLV, then it MUST send a PCErr + * message with Error-type=10 (Reception of an invalid + * object) and Error-value=8 (SYMBOLIC-PATH-NAME TLV + * missing) */ + flog_warn( + EC_PATH_PCEP_PROTOCOL_ERROR, + "PCE initiated path without symbolic name: %s", + format_pcep_message(msg)); + send_pcep_error( + pcc_state, PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_SYMBOLIC_PATH_NAME_TLV_MISSING, path); + return; + } + } + + /* TODO: If there is a conflict with the symbolic path name of an + * existing LSP, the PCC MUST send a PCErr message with Error-type=23 + * (Bad Parameter value) and Error-value=1 (SYMBOLIC-PATH-NAME in + * use) */ + + specialize_incoming_path(pcc_state, path); + /* TODO: Validate the PCC address received from the PCE is valid */ + PCEP_DEBUG("%s Received LSP initiate", pcc_state->tag); + PCEP_DEBUG_PATH("%s", format_path(path)); + + if (validate_incoming_path(pcc_state, path, err, sizeof(err))) { + pcep_thread_initiate_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); + send_pcep_error(pcc_state, PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_NOT_PCE_INITIATED, path); + pcep_free_path(path); + } +} + +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); + if (path->no_path) { + req = pop_req_no_reqid(pcc_state, path->req_id); + } else { + 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, NULL); + 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); + free_req_entry(req); + pcep_free_path(path); + return; + } 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, req->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->ipaddr_v4 = pcc_state->pcc_addr_v4; + taddr->ipa_type = IPADDR_V4; + } + } else { + if (CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) { + taddr->ipaddr_v6 = pcc_state->pcc_addr_v6; + taddr->ipa_type = IPADDR_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 path *trigger_path) +{ + 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, trigger_path); + 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) +{ + if (IS_IPADDR_NONE(&path->pcc_addr)) + 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, NULL); + 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; + struct pcep_message *msg; + + if (!pcc_state->is_best) { + return; + } + + specialize_outgoing_path(pcc_state, req->path); + + PCEP_DEBUG( + "%s Sending computation request %d for path %s to %pIA (retry %d)", + pcc_state->tag, req->path->req_id, req->path->name, + &req->path->nbkey.endpoint, 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; + + timeout = pcc_state->pce_opts->config_opts.pcep_request_time_seconds; + 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) +{ + 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 %pIA (retry %d)", + pcc_state->tag, req->path->req_id, req->path->name, + &req->path->nbkey.endpoint, 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; +} + +struct req_entry *pop_req_no_reqid(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); + + 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) +{ + struct req_entry key = {.path = path}; + struct req_entry *req; + + + PCEP_DEBUG_PATH("(%s) %s", format_path(path), __func__); + /* Looking for request without result */ + if (path->no_path || !path->first_hop) { + PCEP_DEBUG_PATH("%s Path : no_path|!first_hop", __func__); + /* ...and already was handle */ + req = RB_FIND(req_entry_head, &pcc_state->requests, &key); + if (!req) { + /* we must purge remaining reqid */ + PCEP_DEBUG_PATH("%s Purge pending reqid: no_path(%s)", + __func__, + path->no_path ? "TRUE" : "FALSE"); + if (lookup_reqid(pcc_state, path) != 0) { + PCEP_DEBUG_PATH("%s Purge pending reqid: DONE ", + __func__); + remove_reqid_mapping(pcc_state, path); + return true; + } else { + return false; + } + } + } + + + 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); +} diff --git a/pathd/path_pcep_pcc.h b/pathd/path_pcep_pcc.h new file mode 100644 index 0000000..9e712ba --- /dev/null +++ b/pathd/path_pcep_pcc.h @@ -0,0 +1,149 @@ +/* + * 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 + */ + +#ifndef _PATH_PCEP_PCC_H_ +#define _PATH_PCEP_PCC_H_ + +#include "typesafe.h" +#include "pathd/path_pcep.h" + +enum pcc_status { + PCEP_PCC_INITIALIZED = 0, + PCEP_PCC_DISCONNECTED, + PCEP_PCC_CONNECTING, + PCEP_PCC_SYNCHRONIZING, + PCEP_PCC_OPERATING +}; + +PREDECL_HASH(plspid_map); +PREDECL_HASH(nbkey_map); +PREDECL_HASH(req_map); + +struct plspid_map_data { + struct plspid_map_item mi; + struct lsp_nb_key nbkey; + uint32_t plspid; +}; + +struct nbkey_map_data { + struct nbkey_map_item mi; + struct lsp_nb_key nbkey; + uint32_t plspid; +}; + +struct req_map_data { + struct req_map_item mi; + struct lsp_nb_key nbkey; + uint32_t reqid; +}; + +struct req_entry { + RB_ENTRY(req_entry) entry; + struct thread *t_retry; + int retry_count; + bool was_sent; + struct path *path; +}; +RB_HEAD(req_entry_head, req_entry); +RB_PROTOTYPE(req_entry_head, req_entry, entry, req_entry_compare); + +struct pcc_state { + int id; + char tag[MAX_TAG_SIZE]; + enum pcc_status status; + uint16_t flags; +#define F_PCC_STATE_HAS_IPV4 0x0002 +#define F_PCC_STATE_HAS_IPV6 0x0004 + struct pcc_opts *pcc_opts; + struct pce_opts *pce_opts; + struct in_addr pcc_addr_v4; + struct in6_addr pcc_addr_v6; + /* PCC transport source address */ + struct ipaddr pcc_addr_tr; + char *originator; + pcep_session *sess; + uint32_t retry_count; + bool synchronized; + struct thread *t_reconnect; + struct thread *t_update_best; + struct thread *t_session_timeout; + uint32_t next_reqid; + uint32_t next_plspid; + struct plspid_map_head plspid_map; + struct nbkey_map_head nbkey_map; + struct req_map_head req_map; + struct req_entry_head requests; + struct pcep_caps caps; + bool is_best; + bool previous_best; +}; + +struct pcc_state *pcep_pcc_initialize(struct ctrl_state *ctrl_state, + int pcc_id); +void pcep_pcc_finalize(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +int pcep_pcc_enable(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state); +int pcep_pcc_disable(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +int pcep_pcc_update(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, + struct pcc_opts *pcc_opts, struct pce_opts *pce_opts); +void pcep_pcc_reconnect(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +void pcep_pcc_pcep_event_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + pcep_event *event); +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); +void pcep_pcc_timeout_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + enum pcep_ctrl_timeout_type type, void *param); +void pcep_pcc_sync_path(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path); +void pcep_pcc_sync_done(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +/* Send a report explicitly. When doing so the PCC may send multiple reports + * due to expectations from vendors for the first report to be with a DOWN + * status. The parameter is_stable is used for that purpose as a hint wheter + * to expect an update for the report */ +void pcep_pcc_send_report(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path, + bool is_stable); +void pcep_pcc_send_error(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct pcep_error *path, + bool is_stable); +int pcep_pcc_multi_pce_sync_path(struct ctrl_state *ctrl_state, int pcc_id, + struct pcc_state **pcc_state_list); +int pcep_pcc_multi_pce_remove_pcc(struct ctrl_state *ctrl_state, + struct pcc_state **pcc_state_list); +int pcep_pcc_timer_update_best_pce(struct ctrl_state *ctrl_state, int pcc_id); +int pcep_pcc_calculate_best_pce(struct pcc_state **pcc); +int pcep_pcc_get_pcc_id_by_ip_port(struct pcc_state **pcc, + struct pce_opts *pce_opts); +int pcep_pcc_get_pcc_id_by_idx(struct pcc_state **pcc, int idx); +struct pcc_state *pcep_pcc_get_pcc_by_id(struct pcc_state **pcc, int id); +struct pcc_state *pcep_pcc_get_pcc_by_name(struct pcc_state **pcc, + const char *pce_name); +int pcep_pcc_get_pcc_idx_by_id(struct pcc_state **pcc, int id); +int pcep_pcc_get_free_pcc_idx(struct pcc_state **pcc); +int pcep_pcc_get_pcc_id(struct pcc_state *pcc); +void pcep_pcc_copy_pcc_info(struct pcc_state **pcc, + struct pcep_pcc_info *pcc_info); + +#endif // _PATH_PCEP_PCC_H_ diff --git a/pathd/path_ted.c b/pathd/path_ted.c new file mode 100644 index 0000000..316255a --- /dev/null +++ b/pathd/path_ted.c @@ -0,0 +1,721 @@ +/* + * Copyright (C) 2020 Volta Networks, 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 Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <zebra.h> + +#include <stdlib.h> + +#include "memory.h" +#include "log.h" +#include "command.h" +#include "prefix.h" +#include <lib/json.h> + +#include "pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_ted.h" + +#ifndef VTYSH_EXTRACT_PL +#include "pathd/path_ted_clippy.c" +#endif + +static struct ls_ted *path_ted_create_ted(void); +static void path_ted_register_vty(void); +static void path_ted_unregister_vty(void); +static uint32_t path_ted_start_importing_igp(const char *daemon_str); +static uint32_t path_ted_stop_importing_igp(void); +static enum zclient_send_status path_ted_link_state_sync(void); +static void path_ted_timer_handler_sync(struct thread *thread); +static void path_ted_timer_handler_refresh(struct thread *thread); +static int path_ted_cli_debug_config_write(struct vty *vty); +static int path_ted_cli_debug_set_all(uint32_t flags, bool set); + +extern struct zclient *zclient; + +struct ted_state ted_state_g = {}; + +/* + * path_path_ted public API function implementations + */ + +void path_ted_init(struct thread_master *master) +{ + ted_state_g.main = master; + ted_state_g.link_state_delay_interval = TIMER_RETRY_DELAY; + ted_state_g.segment_list_refresh_interval = TIMER_RETRY_DELAY; + path_ted_register_vty(); + path_ted_segment_list_refresh(); +} + +uint32_t path_ted_teardown(void) +{ + PATH_TED_DEBUG("%s : TED [%p]", __func__, ted_state_g.ted); + path_ted_unregister_vty(); + path_ted_stop_importing_igp(); + ls_ted_del_all(&ted_state_g.ted); + path_ted_timer_sync_cancel(); + path_ted_timer_refresh_cancel(); + return 0; +} + +/** + * Set all needed to receive igp data. + * + * @return true if ok + * + */ +uint32_t path_ted_start_importing_igp(const char *daemon_str) +{ + uint32_t status = 0; + + if (strcmp(daemon_str, "ospfv2") == 0) + ted_state_g.import = IMPORT_OSPFv2; + else if (strcmp(daemon_str, "ospfv3") == 0) { + ted_state_g.import = IMPORT_UNKNOWN; + return 1; + } else if (strcmp(daemon_str, "isis") == 0) + ted_state_g.import = IMPORT_ISIS; + else { + ted_state_g.import = IMPORT_UNKNOWN; + return 1; + } + + if (ls_register(zclient, false /*client*/) != 0) { + PATH_TED_ERROR("%s: PATHD-TED: Unable to register Link State", + __func__); + ted_state_g.import = IMPORT_UNKNOWN; + status = 1; + } else { + if (path_ted_link_state_sync() != -1) { + PATH_TED_DEBUG("%s: PATHD-TED: Importing %s data ON", + __func__, + PATH_TED_IGP_PRINT(ted_state_g.import)); + } else { + PATH_TED_WARN("%s: PATHD-TED: Importing %s data OFF", + __func__, + PATH_TED_IGP_PRINT(ted_state_g.import)); + ted_state_g.import = IMPORT_UNKNOWN; + } + } + return status; +} + +/** + * Unset all needed to receive igp data. + * + * @return true if ok + * + */ +uint32_t path_ted_stop_importing_igp(void) +{ + uint32_t status = 0; + + if (ted_state_g.import != IMPORT_UNKNOWN) { + if (ls_unregister(zclient, false /*client*/) != 0) { + PATH_TED_ERROR( + "%s: PATHD-TED: Unable to unregister Link State", + __func__); + status = 1; + } else { + ted_state_g.import = IMPORT_UNKNOWN; + PATH_TED_DEBUG("%s: PATHD-TED: Importing igp data OFF", + __func__); + } + path_ted_timer_sync_cancel(); + } + return status; +} +/** + * Check for ted status + * + * @return true if ok + * + */ +bool path_ted_is_initialized(void) +{ + if (ted_state_g.ted == NULL) { + PATH_TED_WARN("PATHD TED ls_ted not initialized"); + return false; + } + + return true; +} + +/** + * Creates an empty ted + * + * @param void + * + * @return Ptr to ted or NULL + */ +struct ls_ted *path_ted_create_ted() +{ + struct ls_ted *ted = ls_ted_new(TED_KEY, TED_NAME, TED_ASN); + + if (ted == NULL) { + PATH_TED_ERROR("%s Unable to initialize TED Key [%d] ASN [%d] Name [%s]", + __func__, TED_KEY, TED_ASN, TED_NAME); + } else { + PATH_TED_INFO("%s Initialize TED Key [%d] ASN [%d] Name [%s]", + __func__, TED_KEY, TED_ASN, TED_NAME); + } + + return ted; +} + +uint32_t path_ted_rcvd_message(struct ls_message *msg) +{ + if (!path_ted_is_initialized()) + return 1; + + if (msg == NULL) { + PATH_TED_ERROR("%s: [rcv ted] TED received NULL message ", + __func__); + return 1; + } + + if (path_ted_get_current_igp(msg->data.node->adv.origin)) + return 1; + + switch (msg->type) { + case LS_MSG_TYPE_NODE: + ls_msg2vertex(ted_state_g.ted, msg, true /*hard delete*/); + break; + + case LS_MSG_TYPE_ATTRIBUTES: + ls_msg2edge(ted_state_g.ted, msg, true /*Ä¥ard delete*/); + break; + + case LS_MSG_TYPE_PREFIX: + ls_msg2subnet(ted_state_g.ted, msg, true /*hard delete*/); + break; + + default: + PATH_TED_DEBUG( + "%s: [rcv ted] TED received unknown message type [%d]", + __func__, msg->type); + break; + } + return 0; +} + +uint32_t path_ted_query_type_f(struct ipaddr *local, struct ipaddr *remote) +{ + uint32_t sid = MPLS_LABEL_NONE; + struct ls_edge *edge; + uint64_t key; + + if (!path_ted_is_initialized()) + return MPLS_LABEL_NONE; + + if (!local || !remote) + return MPLS_LABEL_NONE; + + switch (local->ipa_type) { + case IPADDR_V4: + /* We have local and remote ip */ + /* so check all attributes in ted */ + key = ((uint64_t)ntohl(local->ip._v4_addr.s_addr)) & 0xffffffff; + edge = ls_find_edge_by_key(ted_state_g.ted, key); + if (edge) { + if (edge->attributes->standard.remote.s_addr + == remote->ip._v4_addr.s_addr + && CHECK_FLAG(edge->attributes->flags, + LS_ATTR_ADJ_SID)) { + sid = edge->attributes->adj_sid[0] + .sid; /* from primary */ + break; + } + } + break; + case IPADDR_V6: + key = (uint64_t)ntohl(local->ip._v6_addr.s6_addr32[2]) << 32 | + (uint64_t)ntohl(local->ip._v6_addr.s6_addr32[3]); + edge = ls_find_edge_by_key(ted_state_g.ted, key); + if (edge) { + if ((0 == memcmp(&edge->attributes->standard.remote6, + &remote->ip._v6_addr, + sizeof(remote->ip._v6_addr)) && + CHECK_FLAG(edge->attributes->flags, + LS_ATTR_ADJ_SID6))) { + sid = edge->attributes->adj_sid[ADJ_PRI_IPV6] + .sid; /* from primary */ + break; + } + } + break; + case IPADDR_NONE: + break; + } + + return sid; +} + +uint32_t path_ted_query_type_c(struct prefix *prefix, uint8_t algo) +{ + uint32_t sid = MPLS_LABEL_NONE; + struct ls_subnet *subnet; + + if (!path_ted_is_initialized()) + return MPLS_LABEL_NONE; + + if (!prefix) + return MPLS_LABEL_NONE; + + switch (prefix->family) { + case AF_INET: + case AF_INET6: + subnet = ls_find_subnet(ted_state_g.ted, *prefix); + if (subnet) { + if ((CHECK_FLAG(subnet->ls_pref->flags, LS_PREF_SR)) + && (subnet->ls_pref->sr.algo == algo)) + sid = subnet->ls_pref->sr.sid; + } + break; + default: + break; + } + + return sid; +} + +uint32_t path_ted_query_type_e(struct prefix *prefix, uint32_t iface_id) +{ + uint32_t sid = MPLS_LABEL_NONE; + struct ls_subnet *subnet; + struct listnode *lst_node; + struct ls_edge *edge; + + if (!path_ted_is_initialized()) + return MPLS_LABEL_NONE; + + if (!prefix) + return MPLS_LABEL_NONE; + + switch (prefix->family) { + case AF_INET: + case AF_INET6: + subnet = ls_find_subnet(ted_state_g.ted, *prefix); + if (subnet && subnet->vertex + && subnet->vertex->outgoing_edges) { + /* from the vertex linked in subnet */ + /* loop over outgoing edges */ + for (ALL_LIST_ELEMENTS_RO( + subnet->vertex->outgoing_edges, lst_node, + edge)) { + /* and look for ifaceid */ + /* so get sid of attribute */ + if (CHECK_FLAG(edge->attributes->flags, + LS_ATTR_LOCAL_ID) + && edge->attributes->standard.local_id + == iface_id) { + sid = subnet->ls_pref->sr.sid; + break; + } + } + } + break; + default: + break; + } + + return sid; +} + +DEFPY (debug_path_ted, + debug_path_ted_cmd, + "[no] debug pathd mpls-te", + NO_STR + DEBUG_STR + "path debugging\n" + "ted debugging\n") +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + bool no_debug = (no != NULL); + + DEBUG_MODE_SET(&ted_state_g.dbg, mode, !no); + DEBUG_FLAGS_SET(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC, !no_debug); + return CMD_SUCCESS; +} + +/* + * Following are vty command functions. + */ +/* clang-format off */ +DEFUN (path_ted_on, + path_ted_on_cmd, + "mpls-te on", + NO_STR + "Enable the TE database (TED) functionality\n") +/* clang-format on */ +{ + + if (ted_state_g.enabled) { + PATH_TED_DEBUG("%s: PATHD-TED: Enabled ON -> ON.", __func__); + return CMD_SUCCESS; + } + + ted_state_g.ted = path_ted_create_ted(); + ted_state_g.enabled = true; + PATH_TED_DEBUG("%s: PATHD-TED: Enabled OFF -> ON.", __func__); + + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFUN (no_path_ted, + no_path_ted_cmd, + "no mpls-te [on]", + NO_STR + NO_STR + "Disable the TE Database functionality\n") +/* clang-format on */ +{ + if (!ted_state_g.enabled) { + PATH_TED_DEBUG("%s: PATHD-TED: OFF -> OFF", __func__); + return CMD_SUCCESS; + } + + /* Remove TED */ + ls_ted_del_all(&ted_state_g.ted); + ted_state_g.enabled = false; + PATH_TED_DEBUG("%s: PATHD-TED: ON -> OFF", __func__); + ted_state_g.import = IMPORT_UNKNOWN; + if (ls_unregister(zclient, false /*client*/) != 0) { + vty_out(vty, "Unable to unregister Link State\n"); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY(path_ted_import, + path_ted_import_cmd, + "mpls-te import <ospfv2|ospfv3|isis>$import_daemon", + "Enable the TE database (TED) fill with remote igp data\n" + "import\n" + "Origin ospfv2\n" + "Origin ospfv3\n" + "Origin isis\n") +/* clang-format on */ +{ + + if (ted_state_g.enabled) + if (path_ted_start_importing_igp(import_daemon)) { + vty_out(vty, "Unable to start importing\n"); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFUN (no_path_ted_import, + no_path_ted_import_cmd, + "no mpls-te import", + NO_STR + NO_STR + "Disable the TE Database fill with remote igp data\n") +/* clang-format on */ +{ + + if (ted_state_g.import) { + if (path_ted_stop_importing_igp()) { + vty_out(vty, "Unable to stop importing\n"); + return CMD_WARNING; + } else { + PATH_TED_DEBUG( + "%s: PATHD-TED: Importing igp data already OFF", + __func__); + } + } + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (show_pathd_ted_db, + show_pathd_ted_db_cmd, + "show pathd ted database <verbose|json>$ver_json ", + "show command\n" + "pathd daemon\n" + "traffic eng\n" + "database\n" + "verbose output\n" + "Show complete received TED database\n") +/* clang-format on */ +{ + bool st_json = false; + json_object *json = NULL; + + if (!ted_state_g.enabled) { + vty_out(vty, "Traffic Engineering database is not enabled\n"); + return CMD_WARNING; + } + if (strcmp(ver_json, "json") == 0) { + st_json = true; + json = json_object_new_object(); + } + /* Show the complete TED */ + ls_show_ted(ted_state_g.ted, vty, json, !st_json); + if (st_json) + vty_json(vty, json); + return CMD_SUCCESS; +} + +/* + * Config Write functions + */ + +int path_ted_cli_debug_config_write(struct vty *vty) +{ + if (DEBUG_MODE_CHECK(&ted_state_g.dbg, DEBUG_MODE_CONF)) { + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) + vty_out(vty, "debug pathd mpls-te\n"); + return 1; + } + return 0; +} + +int path_ted_cli_debug_set_all(uint32_t flags, bool set) +{ + DEBUG_FLAGS_SET(&ted_state_g.dbg, flags, set); + + /* If all modes have been turned off, don't preserve options. */ + if (!DEBUG_MODE_CHECK(&ted_state_g.dbg, DEBUG_MODE_ALL)) + DEBUG_CLEAR(&ted_state_g.dbg); + + return 0; +} + +/** + * Help fn to show ted related configuration + * + * @param vty + * + * @return Status + */ +uint32_t path_ted_config_write(struct vty *vty) +{ + + if (ted_state_g.enabled) { + vty_out(vty, " mpls-te on\n"); + switch (ted_state_g.import) { + case IMPORT_ISIS: + vty_out(vty, " mpls-te import isis\n"); + break; + case IMPORT_OSPFv2: + vty_out(vty, " mpls-te import ospfv2\n"); + break; + case IMPORT_OSPFv3: + vty_out(vty, " mpls-te import ospfv3\n"); + break; + default: + break; + } + } + return 0; +} + +/** + * Register the fn's for CLI and hook for config show + * + * @param void + * + */ +static void path_ted_register_vty(void) +{ + install_element(VIEW_NODE, &show_pathd_ted_db_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &path_ted_on_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &path_ted_import_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_import_cmd); + + install_element(CONFIG_NODE, &debug_path_ted_cmd); + install_element(ENABLE_NODE, &debug_path_ted_cmd); + + hook_register(nb_client_debug_config_write, + path_ted_cli_debug_config_write); + hook_register(nb_client_debug_set_all, path_ted_cli_debug_set_all); +} + +/** + * UnRegister the fn's for CLI and hook for config show + * + * @param void + * + */ +static void path_ted_unregister_vty(void) +{ + uninstall_element(VIEW_NODE, &show_pathd_ted_db_cmd); + uninstall_element(SR_TRAFFIC_ENG_NODE, &path_ted_on_cmd); + uninstall_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_cmd); + uninstall_element(SR_TRAFFIC_ENG_NODE, &path_ted_import_cmd); + uninstall_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_import_cmd); +} + +/** + * Ask igp for a complete TED so far + * + * @param void + * + * @return zclient status + */ +enum zclient_send_status path_ted_link_state_sync(void) +{ + enum zclient_send_status status; + + status = ls_request_sync(zclient); + if (status == -1) { + PATH_TED_ERROR( + "%s: PATHD-TED: Opaque error asking for TED sync ", + __func__); + return status; + } else { + PATH_TED_DEBUG("%s: PATHD-TED: Opaque asked for TED sync ", + __func__); + } + thread_add_timer(ted_state_g.main, path_ted_timer_handler_sync, + &ted_state_g, ted_state_g.link_state_delay_interval, + &ted_state_g.t_link_state_sync); + + return status; +} + +/** + * Timer cb for check link state sync + * + * @param thread Current thread + * + * @return status + */ +void path_ted_timer_handler_sync(struct thread *thread) +{ + /* data unpacking */ + struct ted_state *data = THREAD_ARG(thread); + + assert(data != NULL); + /* Retry the sync */ + path_ted_link_state_sync(); +} + +/** + * refresg segment list and create timer to keep up updated + * + * @param void + * + * @return status + */ +int path_ted_segment_list_refresh(void) +{ + int status = 0; + + path_ted_timer_refresh_cancel(); + thread_add_timer(ted_state_g.main, path_ted_timer_handler_refresh, + &ted_state_g, + ted_state_g.segment_list_refresh_interval, + &ted_state_g.t_segment_list_refresh); + + return status; +} + +/** + * Timer cb for refreshing sid in segment lists + * + * @param void + * + * @return status + */ +void path_ted_timer_handler_refresh(struct thread *thread) +{ + if (!path_ted_is_initialized()) + return; + + PATH_TED_DEBUG("%s: PATHD-TED: Refresh sid from current TED", __func__); + /* data unpacking */ + struct ted_state *data = THREAD_ARG(thread); + + assert(data != NULL); + + srte_policy_update_ted_sid(); +} + +/** + * Cancel sync timer + * + * @param void + * + * @return void status + */ +void path_ted_timer_sync_cancel(void) +{ + if (ted_state_g.t_link_state_sync != NULL) { + thread_cancel(&ted_state_g.t_link_state_sync); + ted_state_g.t_link_state_sync = NULL; + } +} + +/** + * Cancel refresh timer + * + * @param void + * + * @return void status + */ +void path_ted_timer_refresh_cancel(void) +{ + if (ted_state_g.t_segment_list_refresh != NULL) { + thread_cancel(&ted_state_g.t_segment_list_refresh); + ted_state_g.t_segment_list_refresh = NULL; + } +} + +/** + * Check which igp is configured + * + * @param igp who want to check against config- + * + * @return status + */ +uint32_t path_ted_get_current_igp(uint32_t igp) +{ + switch (igp) { + case ISIS_L1: + case ISIS_L2: + if (ted_state_g.import != IMPORT_ISIS) { + PATH_TED_ERROR( + "%s: [rcv ted] Incorrect igp origin wait (%s) got (%s) ", + __func__, + PATH_TED_IGP_PRINT(ted_state_g.import), + LS_IGP_PRINT(igp)); + return 1; + } + break; + case OSPFv2: + if (ted_state_g.import != IMPORT_OSPFv2) { + PATH_TED_ERROR( + "%s: [rcv ted] Incorrect igp origin wait (%s) got (%s) ", + __func__, + PATH_TED_IGP_PRINT(ted_state_g.import), + LS_IGP_PRINT(igp)); + return 1; + } + break; + case STATIC: + break; + } + return 0; +} diff --git a/pathd/path_ted.h b/pathd/path_ted.h new file mode 100644 index 0000000..c6897b1 --- /dev/null +++ b/pathd/path_ted.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020 Volta Networks, 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 Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +#ifndef _PATH_TED_H +#define _PATH_TED_H + +#ifdef __cplusplus + +extern "C" { +#endif + +#include <zebra.h> + +#include <stdbool.h> + +#include <debug.h> +#include "linklist.h" +#include "log.h" +#include "command.h" +#include "stream.h" +#include "prefix.h" +#include "zclient.h" +#include "link_state.h" + +extern struct ted_state ted_state_g; +#define TIMER_RETRY_DELAY 5 /* Timeout in seconds between ls sync request */ +#define TED_KEY 1 +#define TED_ASN 1 +#define TED_NAME "PATHD TED" + +enum igp_import { + IMPORT_UNKNOWN = 0, + IMPORT_ISIS, + IMPORT_OSPFv2, + IMPORT_OSPFv3 +}; +struct ted_state { + struct thread_master *main; + /* Status of TED: enable or disable */ + bool enabled; + /* From which igp is going to receive data */ + enum igp_import import; + /* The TED itself as in link_state.h */ + struct ls_ted *ted; + /* Timer for ted sync */ + struct thread *t_link_state_sync; + /* Timer for refresh sid in segment list */ + struct thread *t_segment_list_refresh; + /* delay interval in seconds */ + uint32_t link_state_delay_interval; + /* delay interval refresh in seconds */ + uint32_t segment_list_refresh_interval; + struct debug dbg; +}; +/* Debug flags. */ +#define PATH_TED_DEBUG_BASIC 0x01 +#define PATH_TED_DEBUG(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) \ + DEBUGD(&ted_state_g.dbg, "mpls-te: " fmt, ##__VA_ARGS__); \ + } while (0) + +#define PATH_TED_ERROR(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) \ + DEBUGE(&ted_state_g.dbg, "mpls-te: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PATH_TED_WARN(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) \ + DEBUGW(&ted_state_g.dbg, "mpls-te: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PATH_TED_INFO(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) \ + DEBUGI(&ted_state_g.dbg, "mpls-te: " fmt, ##__VA_ARGS__); \ + } while (0) + +/* TED management functions */ +bool path_ted_is_initialized(void); +void path_ted_init(struct thread_master *master); +uint32_t path_ted_teardown(void); +void path_ted_timer_sync_cancel(void); +void path_ted_timer_refresh_cancel(void); +int path_ted_segment_list_refresh(void); + +/* TED configuration functions */ +uint32_t path_ted_config_write(struct vty *vty); + +/* TED util functions */ +/* clang-format off */ +#define LS_MSG_EVENT_PRINT(event) event == LS_MSG_EVENT_ADD?"add"\ + : event == LS_MSG_EVENT_DELETE?"del"\ + : event == LS_MSG_EVENT_UPDATE?"upd"\ + : event == LS_MSG_EVENT_SYNC?"syn"\ + : event == LS_MSG_EVENT_SYNC?"und" : "none" +#define LS_MSG_TYPE_PRINT(type) type == LS_MSG_TYPE_NODE?"node"\ + : type == LS_MSG_TYPE_ATTRIBUTES?"att"\ + : type == LS_MSG_TYPE_PREFIX?"pre" : "none" +#define LS_IGP_PRINT(type) type == ISIS_L1?"ISIS_L1"\ + : type == ISIS_L2?"ISIS_L2"\ + : type == DIRECT?"DIRECT"\ + : type == STATIC?"STATIC"\ + : type == OSPFv2?"OSPFv2" : "none" +#define PATH_TED_IGP_PRINT(type) type == IMPORT_OSPFv2?"OSPFv2"\ + : type == IMPORT_OSPFv3?"OSPFv3"\ + : type == IMPORT_ISIS?"ISIS" : "none" +/* clang-format on */ + + +uint32_t path_ted_get_current_igp(uint32_t); +/* TED Query functions */ + +/* + * Type of queries from draft-ietf-spring-segment-routing-policy-07 for types + * f,c,e + */ + +/** + * Search for sid based in prefix and optional algo + * + * @param prefix Net prefix to resolv + * @param algo Algorithm for link state + * + * @return sid of attribute + */ +uint32_t path_ted_query_type_c(struct prefix *prefix, uint8_t algo); + +/** + * Search for sid based in prefix and interface id + * + * @param prefix Net prefix to resolv + * @param iface_id The interface id + * + * @return sid of attribute + */ +uint32_t path_ted_query_type_e(struct prefix *prefix, uint32_t iface_id); + +/** + * Search for sid based in local, remote pair + * + * @param local local ip of attribute + * @param remote remote ip of attribute + * + * @return sid of attribute + */ +uint32_t path_ted_query_type_f(struct ipaddr *local, struct ipaddr *remote); + + +/** + * Handle the received opaque msg + * + * @param msg Holds the ted data + * + * @return sid of attribute + */ +uint32_t path_ted_rcvd_message(struct ls_message *msg); + +#ifdef __cplusplus +} +#endif + +#endif /* _PATH_TED_H */ diff --git a/pathd/path_zebra.c b/pathd/path_zebra.c new file mode 100644 index 0000000..a98532c --- /dev/null +++ b/pathd/path_zebra.c @@ -0,0 +1,360 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include "thread.h" +#include "log.h" +#include "lib_errors.h" +#include "if.h" +#include "prefix.h" +#include "zclient.h" +#include "network.h" +#include "stream.h" +#include "linklist.h" +#include "nexthop.h" +#include "vrf.h" +#include "typesafe.h" + +#include "pathd/pathd.h" +#include "pathd/path_ted.h" +#include "pathd/path_zebra.h" +#include "lib/command.h" +#include "lib/link_state.h" + +static int path_zebra_opaque_msg_handler(ZAPI_CALLBACK_ARGS); + +struct zclient *zclient; +static struct zclient *zclient_sync; + +/* Global Variables */ +bool g_has_router_id_v4 = false; +bool g_has_router_id_v6 = false; +struct in_addr g_router_id_v4; +struct in6_addr g_router_id_v6; +pthread_mutex_t g_router_id_v4_mtx = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t g_router_id_v6_mtx = PTHREAD_MUTEX_INITIALIZER; + +/** + * Gives the IPv4 router ID received from Zebra. + * + * @param router_id The in_addr strucure where to store the router id + * @return true if the router ID was available, false otherwise + */ +bool get_ipv4_router_id(struct in_addr *router_id) +{ + bool retval = false; + assert(router_id != NULL); + pthread_mutex_lock(&g_router_id_v4_mtx); + if (g_has_router_id_v4) { + memcpy(router_id, &g_router_id_v4, sizeof(*router_id)); + retval = true; + } + pthread_mutex_unlock(&g_router_id_v4_mtx); + return retval; +} + +/** + * Gives the IPv6 router ID received from Zebra. + * + * @param router_id The in6_addr strucure where to store the router id + * @return true if the router ID was available, false otherwise + */ +bool get_ipv6_router_id(struct in6_addr *router_id) +{ + bool retval = false; + assert(router_id != NULL); + pthread_mutex_lock(&g_router_id_v6_mtx); + if (g_has_router_id_v6) { + memcpy(router_id, &g_router_id_v6, sizeof(*router_id)); + retval = true; + } + pthread_mutex_unlock(&g_router_id_v6_mtx); + return retval; +} + +static void path_zebra_connected(struct zclient *zclient) +{ + struct srte_policy *policy; + + zclient_send_reg_requests(zclient, VRF_DEFAULT); + zclient_send_router_id_update(zclient, ZEBRA_ROUTER_ID_ADD, AFI_IP6, + VRF_DEFAULT); + + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + struct srte_candidate *candidate; + struct srte_segment_list *segment_list; + + candidate = policy->best_candidate; + if (!candidate) + continue; + + segment_list = candidate->lsp->segment_list; + if (!segment_list) + continue; + + path_zebra_add_sr_policy(policy, segment_list); + } +} + +static int path_zebra_sr_policy_notify_status(ZAPI_CALLBACK_ARGS) +{ + struct zapi_sr_policy zapi_sr_policy; + struct srte_policy *policy; + struct srte_candidate *best_candidate_path; + + if (zapi_sr_policy_notify_status_decode(zclient->ibuf, &zapi_sr_policy)) + return -1; + + policy = srte_policy_find(zapi_sr_policy.color, + &zapi_sr_policy.endpoint); + if (!policy) + return -1; + + best_candidate_path = policy->best_candidate; + if (!best_candidate_path) + return -1; + + srte_candidate_status_update(best_candidate_path, + zapi_sr_policy.status); + + return 0; +} + +/* Router-id update message from zebra. */ +static int path_zebra_router_id_update(ZAPI_CALLBACK_ARGS) +{ + struct prefix pref; + const char *family; + char buf[PREFIX2STR_BUFFER]; + zebra_router_id_update_read(zclient->ibuf, &pref); + if (pref.family == AF_INET) { + pthread_mutex_lock(&g_router_id_v4_mtx); + memcpy(&g_router_id_v4, &pref.u.prefix4, + sizeof(g_router_id_v4)); + g_has_router_id_v4 = true; + inet_ntop(AF_INET, &g_router_id_v4, buf, sizeof(buf)); + pthread_mutex_unlock(&g_router_id_v4_mtx); + family = "IPv4"; + } else if (pref.family == AF_INET6) { + pthread_mutex_lock(&g_router_id_v6_mtx); + memcpy(&g_router_id_v6, &pref.u.prefix6, + sizeof(g_router_id_v6)); + g_has_router_id_v6 = true; + inet_ntop(AF_INET6, &g_router_id_v6, buf, sizeof(buf)); + pthread_mutex_unlock(&g_router_id_v6_mtx); + family = "IPv6"; + } else { + zlog_warn("Unexpected router ID address family for vrf %u: %u", + vrf_id, pref.family); + return 0; + } + zlog_info("%s Router Id updated for VRF %u: %s", family, vrf_id, buf); + return 0; +} + +/** + * Adds a segment routing policy to Zebra. + * + * @param policy The policy to add + * @param segment_list The segment list for the policy + */ +void path_zebra_add_sr_policy(struct srte_policy *policy, + struct srte_segment_list *segment_list) +{ + struct zapi_sr_policy zp = {}; + struct srte_segment_entry *segment; + + zp.color = policy->color; + zp.endpoint = policy->endpoint; + strlcpy(zp.name, policy->name, sizeof(zp.name)); + zp.segment_list.type = ZEBRA_LSP_SRTE; + zp.segment_list.local_label = policy->binding_sid; + zp.segment_list.label_num = 0; + RB_FOREACH (segment, srte_segment_entry_head, &segment_list->segments) + zp.segment_list.labels[zp.segment_list.label_num++] = + segment->sid_value; + policy->status = SRTE_POLICY_STATUS_GOING_UP; + + (void)zebra_send_sr_policy(zclient, ZEBRA_SR_POLICY_SET, &zp); +} + +/** + * Deletes a segment policy from Zebra. + * + * @param policy The policy to remove + */ +void path_zebra_delete_sr_policy(struct srte_policy *policy) +{ + struct zapi_sr_policy zp = {}; + + zp.color = policy->color; + zp.endpoint = policy->endpoint; + strlcpy(zp.name, policy->name, sizeof(zp.name)); + zp.segment_list.type = ZEBRA_LSP_SRTE; + zp.segment_list.local_label = policy->binding_sid; + zp.segment_list.label_num = 0; + policy->status = SRTE_POLICY_STATUS_DOWN; + + (void)zebra_send_sr_policy(zclient, ZEBRA_SR_POLICY_DELETE, &zp); +} + +/** + * Allocates a label from Zebra's label manager. + * + * @param label the label to be allocated + * @return 0 if the label has been allocated, -1 otherwise + */ +int path_zebra_request_label(mpls_label_t label) +{ + int ret; + uint32_t start, end; + + ret = lm_get_label_chunk(zclient_sync, 0, label, 1, &start, &end); + if (ret < 0) { + zlog_warn("%s: error getting label range!", __func__); + return -1; + } + + return 0; +} + +/** + * Releases a previously allocated label from Zebra's label manager. + * + * @param label The label to release + * @return 0 ifthe label has beel released, -1 otherwise + */ +void path_zebra_release_label(mpls_label_t label) +{ + int ret; + + ret = lm_release_label_chunk(zclient_sync, label, label); + if (ret < 0) + zlog_warn("%s: error releasing label range!", __func__); +} + +static void path_zebra_label_manager_connect(void) +{ + /* Connect to label manager. */ + while (zclient_socket_connect(zclient_sync) < 0) { + zlog_warn("%s: error connecting synchronous zclient!", + __func__); + sleep(1); + } + set_nonblocking(zclient_sync->sock); + + /* Send hello to notify zebra this is a synchronous client */ + while (zclient_send_hello(zclient_sync) < 0) { + zlog_warn("%s: Error sending hello for synchronous zclient!", + __func__); + sleep(1); + } + + while (lm_label_manager_connect(zclient_sync, 0) != 0) { + zlog_warn("%s: error connecting to label manager!", __func__); + sleep(1); + } +} + +static int path_zebra_opaque_msg_handler(ZAPI_CALLBACK_ARGS) +{ + int ret = 0; + struct stream *s; + struct zapi_opaque_msg info; + + s = zclient->ibuf; + + if (zclient_opaque_decode(s, &info) != 0) + return -1; + + switch (info.type) { + case LINK_STATE_UPDATE: + case LINK_STATE_SYNC: + /* Start receiving ls data so cancel request sync timer */ + path_ted_timer_sync_cancel(); + + struct ls_message *msg = ls_parse_msg(s); + + if (msg) { + zlog_debug("%s: [rcv ted] ls (%s) msg (%s)-(%s) !", + __func__, + info.type == LINK_STATE_UPDATE + ? "LINK_STATE_UPDATE" + : "LINK_STATE_SYNC", + LS_MSG_TYPE_PRINT(msg->type), + LS_MSG_EVENT_PRINT(msg->event)); + } else { + zlog_err( + "%s: [rcv ted] Could not parse LinkState stream message.", + __func__); + return -1; + } + + ret = path_ted_rcvd_message(msg); + ls_delete_msg(msg); + /* Update local configuration after process update. */ + path_ted_segment_list_refresh(); + break; + default: + zlog_debug("%s: [rcv ted] unknown opaque event (%d) !", + __func__, info.type); + break; + } + + return ret; +} + +static zclient_handler *const path_handlers[] = { + [ZEBRA_SR_POLICY_NOTIFY_STATUS] = path_zebra_sr_policy_notify_status, + [ZEBRA_ROUTER_ID_UPDATE] = path_zebra_router_id_update, + [ZEBRA_OPAQUE_MESSAGE] = path_zebra_opaque_msg_handler, +}; + +/** + * Initializes Zebra asynchronous connection. + * + * @param master The master thread + */ +void path_zebra_init(struct thread_master *master) +{ + struct zclient_options options = zclient_options_default; + options.synchronous = true; + + /* Initialize asynchronous zclient. */ + zclient = zclient_new(master, &zclient_options_default, path_handlers, + array_size(path_handlers)); + zclient_init(zclient, ZEBRA_ROUTE_SRTE, 0, &pathd_privs); + zclient->zebra_connected = path_zebra_connected; + + /* Initialize special zclient for synchronous message exchanges. */ + zclient_sync = zclient_new(master, &options, NULL, 0); + zclient_sync->sock = -1; + zclient_sync->redist_default = ZEBRA_ROUTE_SRTE; + zclient_sync->instance = 1; + zclient_sync->privs = &pathd_privs; + + /* Connect to the LM. */ + path_zebra_label_manager_connect(); +} + +void path_zebra_stop(void) +{ + zclient_stop(zclient); + zclient_free(zclient); +} diff --git a/pathd/path_zebra.h b/pathd/path_zebra.h new file mode 100644 index 0000000..683fcf1 --- /dev/null +++ b/pathd/path_zebra.h @@ -0,0 +1,35 @@ +/* + * 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 + */ + +#ifndef _FRR_PATH_MPLS_H_ +#define _FRR_PATH_MPLS_H_ + +#include <zebra.h> +#include "pathd/pathd.h" + +bool get_ipv4_router_id(struct in_addr *router_id); +bool get_ipv6_router_id(struct in6_addr *router_id); +void path_zebra_add_sr_policy(struct srte_policy *policy, + struct srte_segment_list *segment_list); +void path_zebra_delete_sr_policy(struct srte_policy *policy); +int path_zebra_request_label(mpls_label_t label); +void path_zebra_release_label(mpls_label_t label); +void path_zebra_init(struct thread_master *master); +void path_zebra_stop(void); + +#endif /* _FRR_PATH_MPLS_H_ */ diff --git a/pathd/pathd.c b/pathd/pathd.c new file mode 100644 index 0000000..e9d7cc6 --- /dev/null +++ b/pathd/pathd.c @@ -0,0 +1,1419 @@ +/* + * 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 + */ + +#include <zebra.h> + +#include "memory.h" +#include "log.h" +#include "lib_errors.h" +#include "network.h" +#include "libfrr.h" + +#include "pathd/pathd.h" +#include "pathd/path_zebra.h" +#include "pathd/path_debug.h" +#include "pathd/path_ted.h" + +#define HOOK_DELAY 3 + +DEFINE_MGROUP(PATHD, "pathd"); + +DEFINE_MTYPE_STATIC(PATHD, PATH_SEGMENT_LIST, "Segment List"); +DEFINE_MTYPE_STATIC(PATHD, PATH_SR_POLICY, "SR Policy"); +DEFINE_MTYPE_STATIC(PATHD, PATH_SR_CANDIDATE, "SR Policy candidate path"); + +DEFINE_HOOK(pathd_candidate_created, (struct srte_candidate * candidate), + (candidate)); +DEFINE_HOOK(pathd_candidate_updated, (struct srte_candidate * candidate), + (candidate)); +DEFINE_HOOK(pathd_candidate_removed, (struct srte_candidate * candidate), + (candidate)); + +static void trigger_pathd_candidate_created(struct srte_candidate *candidate); +static void trigger_pathd_candidate_created_timer(struct thread *thread); +static void trigger_pathd_candidate_updated(struct srte_candidate *candidate); +static void trigger_pathd_candidate_updated_timer(struct thread *thread); +static void trigger_pathd_candidate_removed(struct srte_candidate *candidate); +static const char * +srte_candidate_metric_name(enum srte_candidate_metric_type type); + +static void srte_set_metric(struct srte_metric *metric, float value, + bool required, bool is_bound, bool is_computed); +static void srte_unset_metric(struct srte_metric *metric); + + +/* Generate rb-tree of Segment List Segment instances. */ +static inline int srte_segment_entry_compare(const struct srte_segment_entry *a, + const struct srte_segment_entry *b) +{ + return a->index - b->index; +} +RB_GENERATE(srte_segment_entry_head, srte_segment_entry, entry, + srte_segment_entry_compare) + +/* Generate rb-tree of Segment List instances. */ +static inline int srte_segment_list_compare(const struct srte_segment_list *a, + const struct srte_segment_list *b) +{ + return strcmp(a->name, b->name); +} +RB_GENERATE(srte_segment_list_head, srte_segment_list, entry, + srte_segment_list_compare) + +struct srte_segment_list_head srte_segment_lists = + RB_INITIALIZER(&srte_segment_lists); + +/* Generate rb-tree of Candidate Path instances. */ +static inline int srte_candidate_compare(const struct srte_candidate *a, + const struct srte_candidate *b) +{ + return a->preference - b->preference; +} +RB_GENERATE(srte_candidate_head, srte_candidate, entry, srte_candidate_compare) + +/* Generate rb-tree of SR Policy instances. */ +static inline int srte_policy_compare(const struct srte_policy *a, + const struct srte_policy *b) +{ + return sr_policy_compare(&a->endpoint, &b->endpoint, a->color, + b->color); +} +RB_GENERATE(srte_policy_head, srte_policy, entry, srte_policy_compare) + +struct srte_policy_head srte_policies = RB_INITIALIZER(&srte_policies); + +/** + * Adds a segment list to pathd. + * + * @param name The name of the segment list to add + * @return The added segment list + */ +struct srte_segment_list *srte_segment_list_add(const char *name) +{ + struct srte_segment_list *segment_list; + + segment_list = XCALLOC(MTYPE_PATH_SEGMENT_LIST, sizeof(*segment_list)); + strlcpy(segment_list->name, name, sizeof(segment_list->name)); + RB_INIT(srte_segment_entry_head, &segment_list->segments); + RB_INSERT(srte_segment_list_head, &srte_segment_lists, segment_list); + + return segment_list; +} + +/** + * Deletes a segment list from pathd. + * + * The given segment list structure will be freed and should not be used anymore + * after calling this function. + * + * @param segment_list the segment list to remove from pathd. + */ +void srte_segment_list_del(struct srte_segment_list *segment_list) +{ + struct srte_segment_entry *segment, *safe_seg; + RB_FOREACH_SAFE (segment, srte_segment_entry_head, + &segment_list->segments, safe_seg) { + srte_segment_entry_del(segment); + } + RB_REMOVE(srte_segment_list_head, &srte_segment_lists, segment_list); + XFREE(MTYPE_PATH_SEGMENT_LIST, segment_list); +} + +/** + * Search for a segment list by name. + * + * @param name The name of the segment list to look for + * @return The segment list if found, NULL otherwise + */ +struct srte_segment_list *srte_segment_list_find(const char *name) +{ + struct srte_segment_list search; + + strlcpy(search.name, name, sizeof(search.name)); + return RB_FIND(srte_segment_list_head, &srte_segment_lists, &search); +} + +/** + * Adds a segment to a segment list. + * + * @param segment_list The segment list the segment should be added to + * @param index The index of the added segment in the segment list + * @return The added segment + */ +struct srte_segment_entry * +srte_segment_entry_add(struct srte_segment_list *segment_list, uint32_t index) +{ + struct srte_segment_entry *segment; + + segment = XCALLOC(MTYPE_PATH_SEGMENT_LIST, sizeof(*segment)); + segment->segment_list = segment_list; + segment->index = index; + RB_INSERT(srte_segment_entry_head, &segment_list->segments, segment); + + return segment; +} + +/** + * Deletes a segment from a segment list. + * + * @param segment The segment to be removed + */ +void srte_segment_entry_del(struct srte_segment_entry *segment) +{ + RB_REMOVE(srte_segment_entry_head, &segment->segment_list->segments, + segment); + XFREE(MTYPE_PATH_SEGMENT_LIST, segment); +} + +/** + * Set the node or adjacency identifier of a segment. + * + * @param segment The segment for which the NAI should be set + * @param type The type of the NAI + * @param type The address of the node or the local address of the adjacency + * @param type The local interface index of the unumbered adjacency + * @param type The remote address of the adjacency + * @param type The remote interface index of the unumbered adjacency + */ +int srte_segment_entry_set_nai(struct srte_segment_entry *segment, + enum srte_segment_nai_type type, + struct ipaddr *local_ip, uint32_t local_iface, + struct ipaddr *remote_ip, uint32_t remote_iface, + uint8_t algo, uint8_t pref_len) +{ + + int32_t status = 0; + struct prefix pre = {0}; + + if (!segment || !local_ip || !remote_ip) + return 1; + + segment->nai_type = type; + memcpy(&segment->nai_local_addr, local_ip, sizeof(struct ipaddr)); + + switch (type) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + memcpy(&segment->nai_remote_addr, remote_ip, + sizeof(struct ipaddr)); + status = srte_ted_do_query_type_f(segment, local_ip, remote_ip); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + memcpy(&segment->nai_remote_addr, remote_ip, + sizeof(struct ipaddr)); + segment->nai_local_iface = local_iface; + segment->nai_remote_iface = remote_iface; + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + pre.family = AF_INET6; + pre.prefixlen = pref_len; + pre.u.prefix6 = local_ip->ip._v6_addr; + segment->nai_local_prefix_len = pref_len; + segment->nai_algorithm = algo; + status = srte_ted_do_query_type_c(segment, &pre, algo); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + pre.family = AF_INET; + pre.prefixlen = pref_len; + pre.u.prefix4 = local_ip->ip._v4_addr; + segment->nai_local_prefix_len = pref_len; + segment->nai_algorithm = algo; + status = srte_ted_do_query_type_c(segment, &pre, algo); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + pre.family = AF_INET6; + pre.prefixlen = pref_len; + pre.u.prefix6 = local_ip->ip._v6_addr; + segment->nai_local_prefix_len = pref_len; + segment->nai_local_iface = local_iface; + status = srte_ted_do_query_type_e(segment, &pre, local_iface); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + pre.family = AF_INET; + pre.prefixlen = pref_len; + pre.u.prefix4 = local_ip->ip._v4_addr; + segment->nai_local_prefix_len = pref_len; + segment->nai_local_iface = local_iface; + status = srte_ted_do_query_type_e(segment, &pre, local_iface); + break; + default: + segment->nai_local_addr.ipa_type = IPADDR_NONE; + segment->nai_local_iface = 0; + segment->nai_remote_addr.ipa_type = IPADDR_NONE; + segment->nai_remote_iface = 0; + } + return status; +} + +/** + * Mark segment as modified depending in protocol and sid conditions + * + * @param protocol_origin Origin of the segment list + * @param s_list Ptr to segment list with flags,sid to modidy + * @param s_entry Ptr to segment entry with sid to modidy + * @param ted_sid The sid from ted query + * @return void + */ +void srte_segment_set_local_modification(struct srte_segment_list *s_list, + struct srte_segment_entry *s_entry, + uint32_t ted_sid) +{ + if (!s_list || !s_entry) + return; + + if (s_list->protocol_origin == SRTE_ORIGIN_LOCAL + && s_entry->sid_value != ted_sid) { + s_entry->sid_value = ted_sid; + SET_FLAG(s_list->flags, F_SEGMENT_LIST_MODIFIED); + } +} + +/** + * Add a policy to pathd. + * + * WARNING: The color 0 is a special case as it is the no-color. + * + * @param color The color of the policy. + * @param endpoint The IP address of the policy endpoint + * @return The created policy + */ +struct srte_policy *srte_policy_add(uint32_t color, struct ipaddr *endpoint, + enum srte_protocol_origin origin, + const char *originator) +{ + struct srte_policy *policy; + + policy = XCALLOC(MTYPE_PATH_SR_POLICY, sizeof(*policy)); + policy->color = color; + policy->endpoint = *endpoint; + policy->binding_sid = MPLS_LABEL_NONE; + policy->protocol_origin = origin; + if (originator != NULL) + strlcpy(policy->originator, originator, + sizeof(policy->originator)); + + RB_INIT(srte_candidate_head, &policy->candidate_paths); + RB_INSERT(srte_policy_head, &srte_policies, policy); + + return policy; +} + +/** + * Delete a policy from pathd. + * + * The given policy structure will be freed and should never be used again + * after calling this function. + * + * @param policy The policy to be removed + */ +void srte_policy_del(struct srte_policy *policy) +{ + struct srte_candidate *candidate; + + path_zebra_delete_sr_policy(policy); + + path_zebra_release_label(policy->binding_sid); + + while (!RB_EMPTY(srte_candidate_head, &policy->candidate_paths)) { + candidate = + RB_ROOT(srte_candidate_head, &policy->candidate_paths); + trigger_pathd_candidate_removed(candidate); + srte_candidate_del(candidate); + } + + RB_REMOVE(srte_policy_head, &srte_policies, policy); + XFREE(MTYPE_PATH_SR_POLICY, policy); +} + +/** + * Search for a policy by color and endpoint. + * + * WARNING: The color 0 is a special case as it is the no-color. + * + * @param color The color of the policy to look for + * @param endpoint The endpoint of the policy to look for + * @return The policy if found, NULL otherwise + */ +struct srte_policy *srte_policy_find(uint32_t color, struct ipaddr *endpoint) +{ + struct srte_policy search; + + search.color = color; + search.endpoint = *endpoint; + return RB_FIND(srte_policy_head, &srte_policies, &search); +} + +/* + * After new data from igp,local and pce the segment list : + * Mark as invalid for origin pce if cannot be validated + * Updated for origin local + */ +int srte_policy_update_ted_sid(void) +{ + + int number_of_sid_clashed = 0; + struct srte_segment_list *s_list; + struct srte_segment_entry *s_entry; + + if (!path_ted_is_initialized()) + return 0; + if (RB_EMPTY(srte_segment_list_head, &srte_segment_lists)) + return 0; + + RB_FOREACH (s_list, srte_segment_list_head, &srte_segment_lists) { + if (CHECK_FLAG(s_list->flags, F_SEGMENT_LIST_DELETED)) + continue; + RB_FOREACH (s_entry, srte_segment_entry_head, + &s_list->segments) { + PATH_TED_DEBUG( + "%s:PATHD-TED: SL: Name: %s index:(%d) sid:(%d) prefix_len:(%d) local iface:(%d) algorithm:(%d)", + __func__, s_list->name, s_entry->index, + s_entry->sid_value, + s_entry->nai_local_prefix_len, + s_entry->nai_local_iface, + s_entry->nai_algorithm); + struct prefix prefix_cli = {0}; + + switch (s_entry->nai_type) { + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + number_of_sid_clashed += + srte_ted_do_query_type_f( + s_entry, + &s_entry->nai_local_addr, + &s_entry->nai_remote_addr); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + prefix_cli.family = AF_INET6; + prefix_cli.prefixlen = + s_entry->nai_local_prefix_len; + prefix_cli.u.prefix6 = + s_entry->nai_local_addr.ip._v6_addr; + number_of_sid_clashed += + srte_ted_do_query_type_e( + s_entry, &prefix_cli, + s_entry->nai_local_iface); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + prefix_cli.family = AF_INET; + prefix_cli.prefixlen = + s_entry->nai_local_prefix_len; + prefix_cli.u.prefix4 = + s_entry->nai_local_addr.ip._v4_addr; + number_of_sid_clashed += + srte_ted_do_query_type_e( + s_entry, &prefix_cli, + s_entry->nai_local_iface); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + prefix_cli.family = AF_INET6; + prefix_cli.prefixlen = + s_entry->nai_local_prefix_len; + prefix_cli.u.prefix6 = + s_entry->nai_local_addr.ip._v6_addr; + number_of_sid_clashed += + srte_ted_do_query_type_c( + s_entry, &prefix_cli, + s_entry->nai_algorithm); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + prefix_cli.family = AF_INET; + prefix_cli.prefixlen = + s_entry->nai_local_prefix_len; + prefix_cli.u.prefix4 = + s_entry->nai_local_addr.ip._v4_addr; + number_of_sid_clashed += + srte_ted_do_query_type_c( + s_entry, &prefix_cli, + s_entry->nai_algorithm); + break; + default: + break; + } + } + if (number_of_sid_clashed) { + SET_FLAG(s_list->flags, F_SEGMENT_LIST_SID_CONFLICT); + number_of_sid_clashed = 0; + } else + UNSET_FLAG(s_list->flags, F_SEGMENT_LIST_SID_CONFLICT); + } + srte_apply_changes(); + + return 0; +} + +/** + * Update a policy binding SID. + * + * @param policy The policy for which the SID should be updated + * @param binding_sid The new binding SID for the given policy + */ +void srte_policy_update_binding_sid(struct srte_policy *policy, + uint32_t binding_sid) +{ + if (policy->binding_sid != MPLS_LABEL_NONE) + path_zebra_release_label(policy->binding_sid); + + policy->binding_sid = binding_sid; + + /* Reinstall the Binding-SID if necessary. */ + if (policy->best_candidate) + path_zebra_add_sr_policy( + policy, policy->best_candidate->lsp->segment_list); +} + +/** + * Gives the policy best candidate path. + * + * @param policy The policy we want the best candidate path from + * @return The best candidate path + */ +static struct srte_candidate * +srte_policy_best_candidate(const struct srte_policy *policy) +{ + struct srte_candidate *candidate; + + RB_FOREACH_REVERSE (candidate, srte_candidate_head, + &policy->candidate_paths) { + /* search for highest preference with existing segment list */ + if (!CHECK_FLAG(candidate->flags, F_CANDIDATE_DELETED) + && candidate->lsp->segment_list + && (!CHECK_FLAG(candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_SID_CONFLICT))) + return candidate; + } + + return NULL; +} + +void srte_clean_zebra(void) +{ + struct srte_policy *policy, *safe_pol; + + RB_FOREACH_SAFE (policy, srte_policy_head, &srte_policies, safe_pol) + srte_policy_del(policy); + + path_zebra_stop(); +} + +/** + * Apply changes defined by setting the policies, candidate paths + * and segment lists modification flags NEW, MODIFIED and DELETED. + * + * This allows the northbound code to delay all the side effects of adding + * modifying and deleting them to the end. + * + * Example of marking an object as modified: + * `SET_FLAG(obj->flags, F_XXX_MODIFIED)` + */ +void srte_apply_changes(void) +{ + struct srte_policy *policy, *safe_pol; + struct srte_segment_list *segment_list, *safe_sl; + + RB_FOREACH_SAFE (policy, srte_policy_head, &srte_policies, safe_pol) { + if (CHECK_FLAG(policy->flags, F_POLICY_DELETED)) { + srte_policy_del(policy); + continue; + } + srte_policy_apply_changes(policy); + UNSET_FLAG(policy->flags, F_POLICY_NEW); + UNSET_FLAG(policy->flags, F_POLICY_MODIFIED); + } + + RB_FOREACH_SAFE (segment_list, srte_segment_list_head, + &srte_segment_lists, safe_sl) { + if (CHECK_FLAG(segment_list->flags, F_SEGMENT_LIST_DELETED)) { + srte_segment_list_del(segment_list); + continue; + } + UNSET_FLAG(segment_list->flags, F_SEGMENT_LIST_NEW); + UNSET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + } +} + +/** + * Apply changes defined by setting the given policy and its candidate paths + * modification flags NEW, MODIFIED and DELETED. + * + * In moste cases `void srte_apply_changes(void)` should be used instead, + * this function will not handle the changes of segment lists used by the + * policy. + * + * @param policy The policy changes has to be applied to. + */ +void srte_policy_apply_changes(struct srte_policy *policy) +{ + struct srte_candidate *candidate, *safe; + struct srte_candidate *old_best_candidate; + struct srte_candidate *new_best_candidate; + char endpoint[46]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + /* Get old and new best candidate path. */ + old_best_candidate = policy->best_candidate; + new_best_candidate = srte_policy_best_candidate(policy); + + if (new_best_candidate != old_best_candidate) { + /* TODO: add debug guard. */ + zlog_debug( + "SR-TE(%s, %u): best candidate changed from %s to %s", + endpoint, policy->color, + old_best_candidate ? old_best_candidate->name : "none", + new_best_candidate ? new_best_candidate->name : "none"); + + if (old_best_candidate) { + policy->best_candidate = NULL; + UNSET_FLAG(old_best_candidate->flags, F_CANDIDATE_BEST); + SET_FLAG(old_best_candidate->flags, + F_CANDIDATE_MODIFIED); + + /* + * Rely on replace semantics if there's a new best + * candidate. + */ + if (!new_best_candidate) + path_zebra_delete_sr_policy(policy); + } + if (new_best_candidate) { + policy->best_candidate = new_best_candidate; + SET_FLAG(new_best_candidate->flags, F_CANDIDATE_BEST); + SET_FLAG(new_best_candidate->flags, + F_CANDIDATE_MODIFIED); + + path_zebra_add_sr_policy( + policy, new_best_candidate->lsp->segment_list); + } + } else if (new_best_candidate) { + /* The best candidate path did not change, but some of its + * attributes or its segment list may have changed. + */ + + bool candidate_changed = CHECK_FLAG(new_best_candidate->flags, + F_CANDIDATE_MODIFIED); + bool segment_list_changed = + new_best_candidate->lsp->segment_list + && CHECK_FLAG( + new_best_candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_MODIFIED); + + if (candidate_changed || segment_list_changed) { + /* TODO: add debug guard. */ + zlog_debug("SR-TE(%s, %u): best candidate %s changed", + endpoint, policy->color, + new_best_candidate->name); + + path_zebra_add_sr_policy( + policy, new_best_candidate->lsp->segment_list); + } + } + + RB_FOREACH_SAFE (candidate, srte_candidate_head, + &policy->candidate_paths, safe) { + if (CHECK_FLAG(candidate->flags, F_CANDIDATE_DELETED)) { + trigger_pathd_candidate_removed(candidate); + srte_candidate_del(candidate); + continue; + } else if (CHECK_FLAG(candidate->flags, F_CANDIDATE_NEW)) { + trigger_pathd_candidate_created(candidate); + } else if (CHECK_FLAG(candidate->flags, F_CANDIDATE_MODIFIED)) { + trigger_pathd_candidate_updated(candidate); + } else if (candidate->lsp->segment_list + && CHECK_FLAG(candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_MODIFIED)) { + trigger_pathd_candidate_updated(candidate); + } + + UNSET_FLAG(candidate->flags, F_CANDIDATE_NEW); + UNSET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + } +} + +/** + * Adds a candidate path to a policy. + * + * @param policy The policy the candidate path should be added to + * @param preference The preference of the candidate path to be added + * @return The added candidate path + */ +struct srte_candidate *srte_candidate_add(struct srte_policy *policy, + uint32_t preference, + enum srte_protocol_origin origin, + const char *originator) +{ + struct srte_candidate *candidate; + struct srte_lsp *lsp; + + candidate = XCALLOC(MTYPE_PATH_SR_CANDIDATE, sizeof(*candidate)); + lsp = XCALLOC(MTYPE_PATH_SR_CANDIDATE, sizeof(*lsp)); + + candidate->preference = preference; + candidate->policy = policy; + candidate->type = SRTE_CANDIDATE_TYPE_UNDEFINED; + candidate->discriminator = frr_weak_random(); + candidate->protocol_origin = origin; + if (originator != NULL) { + strlcpy(candidate->originator, originator, + sizeof(candidate->originator)); + lsp->protocol_origin = origin; + } + + if (candidate->protocol_origin == SRTE_ORIGIN_PCEP + || candidate->protocol_origin == SRTE_ORIGIN_BGP) { + candidate->type = SRTE_CANDIDATE_TYPE_DYNAMIC; + } + lsp->candidate = candidate; + candidate->lsp = lsp; + + RB_INSERT(srte_candidate_head, &policy->candidate_paths, candidate); + + return candidate; +} + +/** + * Deletes a candidate. + * + * The corresponding LSP will be removed alongside the candidate path. + * The given candidate will be freed and shouldn't be used anymore after the + * calling this function. + * + * @param candidate The candidate path to delete + */ +void srte_candidate_del(struct srte_candidate *candidate) +{ + struct srte_policy *srte_policy = candidate->policy; + + RB_REMOVE(srte_candidate_head, &srte_policy->candidate_paths, + candidate); + + XFREE(MTYPE_PATH_SR_CANDIDATE, candidate->lsp); + XFREE(MTYPE_PATH_SR_CANDIDATE, candidate); +} + +/** + * Sets the bandwidth constraint of given candidate path. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path of which the bandwidth should be changed + * @param bandwidth The Bandwidth constraint to set to the candidate path + * @param required If the constraint is required (true) or only desired (false) + */ +void srte_candidate_set_bandwidth(struct srte_candidate *candidate, + float bandwidth, bool required) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug( + "SR-TE(%s, %u): candidate %s %sconfig bandwidth set to %f B/s", + endpoint, policy->color, candidate->name, + required ? "required " : "", bandwidth); + SET_FLAG(candidate->flags, F_CANDIDATE_HAS_BANDWIDTH); + COND_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_BANDWIDTH, required); + candidate->bandwidth = bandwidth; + + srte_lsp_set_bandwidth(candidate->lsp, bandwidth, required); +} + +/** + * Sets the bandwidth constraint of the given LSP. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The lsp of which the bandwidth should be changed + * @param bandwidth The Bandwidth constraint to set to the candidate path + * @param required If the constraint is required (true) or only desired (false) + */ +void srte_lsp_set_bandwidth(struct srte_lsp *lsp, float bandwidth, + bool required) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s %slsp bandwidth set to %f B/s", + endpoint, policy->color, candidate->name, + required ? "required" : "", bandwidth); + SET_FLAG(lsp->flags, F_CANDIDATE_HAS_BANDWIDTH); + COND_FLAG(lsp->flags, F_CANDIDATE_REQUIRED_BANDWIDTH, required); + lsp->bandwidth = bandwidth; +} + +/** + * Remove a candidate path bandwidth constraint. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path of which the bandwidth should be removed + */ +void srte_candidate_unset_bandwidth(struct srte_candidate *candidate) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s config bandwidth unset", + endpoint, policy->color, candidate->name); + UNSET_FLAG(candidate->flags, F_CANDIDATE_HAS_BANDWIDTH); + UNSET_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_BANDWIDTH); + candidate->bandwidth = 0; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + srte_lsp_unset_bandwidth(candidate->lsp); +} + +/** + * Remove an LSP bandwidth constraint. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The lsp of which the bandwidth should be changed + */ +void srte_lsp_unset_bandwidth(struct srte_lsp *lsp) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s lsp bandwidth unset", endpoint, + policy->color, candidate->name); + UNSET_FLAG(lsp->flags, F_CANDIDATE_HAS_BANDWIDTH); + UNSET_FLAG(lsp->flags, F_CANDIDATE_REQUIRED_BANDWIDTH); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + lsp->bandwidth = 0; +} + +/** + * Sets a candidate path metric constraint. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path of which the metric should be changed + * @param type The metric type + * @param value The metric value + * @param required If the constraint is required (true) or only desired (false) + * @param is_bound If the metric is an indicative value or a strict upper bound + * @param is_computed If the metric was computed or configured + */ +void srte_candidate_set_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type, + float value, bool required, bool is_bound, + bool is_computed) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug( + "SR-TE(%s, %u): candidate %s %sconfig metric %s (%u) set to %f (is-bound: %s; is_computed: %s)", + endpoint, policy->color, candidate->name, + required ? "required " : "", srte_candidate_metric_name(type), + type, value, is_bound ? "true" : "false", + is_computed ? "true" : "false"); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_set_metric(&candidate->metrics[type - 1], value, required, + is_bound, is_computed); + srte_lsp_set_metric(candidate->lsp, type, value, required, is_bound, + is_computed); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); +} + +/** + * Sets an LSP metric constraint. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The LSP of which the metric should be changed + * @param type The metric type + * @param value The metric value + * @param required If the constraint is required (true) or only desired (false) + * @param is_bound If the metric is an indicative value or a strict upper bound + * @param is_computed If the metric was computed or configured + */ +void srte_lsp_set_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type, float value, + bool required, bool is_bound, bool is_computed) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug( + "SR-TE(%s, %u): candidate %s %slsp metric %s (%u) set to %f (is-bound: %s; is_computed: %s)", + endpoint, policy->color, candidate->name, + required ? "required " : "", srte_candidate_metric_name(type), + type, value, is_bound ? "true" : "false", + is_computed ? "true" : "false"); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_set_metric(&lsp->metrics[type - 1], value, required, is_bound, + is_computed); +} + +void srte_set_metric(struct srte_metric *metric, float value, bool required, + bool is_bound, bool is_computed) +{ + SET_FLAG(metric->flags, F_METRIC_IS_DEFINED); + COND_FLAG(metric->flags, F_METRIC_IS_REQUIRED, required); + COND_FLAG(metric->flags, F_METRIC_IS_BOUND, is_bound); + COND_FLAG(metric->flags, F_METRIC_IS_COMPUTED, is_computed); + metric->value = value; +} + +/** + * Removes a candidate path metric constraint. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path from which the metric should be removed + * @param type The metric type + */ +void srte_candidate_unset_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s config metric %s (%u) unset", + endpoint, policy->color, candidate->name, + srte_candidate_metric_name(type), type); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_unset_metric(&candidate->metrics[type - 1]); + srte_lsp_unset_metric(candidate->lsp, type); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); +} + +/** + * Removes a candidate path metric constraint. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The LSP from which the metric should be removed + * @param type The metric type + */ +void srte_lsp_unset_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s lsp metric %s (%u) unset", + endpoint, policy->color, candidate->name, + srte_candidate_metric_name(type), type); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_unset_metric(&lsp->metrics[type - 1]); +} + +void srte_unset_metric(struct srte_metric *metric) +{ + UNSET_FLAG(metric->flags, F_METRIC_IS_DEFINED); + UNSET_FLAG(metric->flags, F_METRIC_IS_BOUND); + UNSET_FLAG(metric->flags, F_METRIC_IS_COMPUTED); + metric->value = 0; +} + +/** + * Sets a candidate path objective function. + * + * @param candidate The candidate path of which the OF should be changed + * @param required If the constraint is required (true) or only desired (false) + * @param type The objective function type + */ +void srte_candidate_set_objfun(struct srte_candidate *candidate, bool required, + enum objfun_type type) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + candidate->objfun = type; + SET_FLAG(candidate->flags, F_CANDIDATE_HAS_OBJFUN); + COND_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN, required); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + zlog_debug("SR-TE(%s, %u): candidate %s %sobjective function set to %s", + endpoint, policy->color, candidate->name, + required ? "required " : "", objfun_type_name(type)); +} + +/** + * Removed the objective function constraint from a candidate path. + * + * @param candidate The candidate path from which the OF should be removed + */ +void srte_candidate_unset_objfun(struct srte_candidate *candidate) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + UNSET_FLAG(candidate->flags, F_CANDIDATE_HAS_OBJFUN); + UNSET_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + candidate->objfun = OBJFUN_UNDEFINED; + zlog_debug( + "SR-TE(%s, %u): candidate %s objective functions preferences unset", + endpoint, policy->color, candidate->name); +} + +static uint32_t filter_type_to_flag(enum affinity_filter_type type) +{ + switch (type) { + case AFFINITY_FILTER_EXCLUDE_ANY: + return F_CANDIDATE_HAS_EXCLUDE_ANY; + case AFFINITY_FILTER_INCLUDE_ANY: + return F_CANDIDATE_HAS_INCLUDE_ANY; + case AFFINITY_FILTER_INCLUDE_ALL: + return F_CANDIDATE_HAS_INCLUDE_ALL; + default: + return 0; + } +} + +static const char *filter_type_name(enum affinity_filter_type type) +{ + switch (type) { + case AFFINITY_FILTER_EXCLUDE_ANY: + return "exclude-any"; + case AFFINITY_FILTER_INCLUDE_ANY: + return "include-any"; + case AFFINITY_FILTER_INCLUDE_ALL: + return "include-all"; + default: + return "unknown"; + } +} + +/** + * Sets a candidate path affinity filter constraint. + * + * @param candidate The candidate path of which the constraint should be changed + * @param type The affinity constraint type to set + * @param filter The bitmask filter of the constraint + */ +void srte_candidate_set_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type, + uint32_t filter) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + assert(type > AFFINITY_FILTER_UNDEFINED); + assert(type <= MAX_AFFINITY_FILTER_TYPE); + SET_FLAG(candidate->flags, filter_type_to_flag(type)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + candidate->affinity_filters[type - 1] = filter; + zlog_debug( + "SR-TE(%s, %u): candidate %s affinity filter %s set to 0x%08x", + endpoint, policy->color, candidate->name, + filter_type_name(type), filter); +} + +/** + * Removes a candidate path affinity filter constraint. + * + * @param candidate The candidate path from which the constraint should be + * removed + * @param type The affinity constraint type to remove + */ +void srte_candidate_unset_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + assert(type > AFFINITY_FILTER_UNDEFINED); + assert(type <= MAX_AFFINITY_FILTER_TYPE); + UNSET_FLAG(candidate->flags, filter_type_to_flag(type)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + candidate->affinity_filters[type - 1] = 0; + zlog_debug("SR-TE(%s, %u): candidate %s affinity filter %s unset", + endpoint, policy->color, candidate->name, + filter_type_name(type)); +} + +/** + * Searches for a candidate path of the given policy. + * + * @param policy The policy to search for candidate path + * @param preference The preference of the candidate path you are looking for + * @return The candidate path if found, NULL otherwise + */ +struct srte_candidate *srte_candidate_find(struct srte_policy *policy, + uint32_t preference) +{ + struct srte_candidate search; + + search.preference = preference; + return RB_FIND(srte_candidate_head, &policy->candidate_paths, &search); +} + +/** + * Searches for a an entry of a given segment list. + * + * @param segment_list The segment list to search for the entry + * @param index The index of the entry you are looking for + * @return The segment list entry if found, NULL otherwise. + */ +struct srte_segment_entry * +srte_segment_entry_find(struct srte_segment_list *segment_list, uint32_t index) +{ + struct srte_segment_entry search; + + search.index = index; + return RB_FIND(srte_segment_entry_head, &segment_list->segments, + &search); +} + +/** + * Updates a candidate status. + * + * @param candidate The candidate of which the status should be updated + * @param status The new candidate path status + */ +void srte_candidate_status_update(struct srte_candidate *candidate, int status) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): zebra updated status to %d", endpoint, + policy->color, status); + switch (status) { + case ZEBRA_SR_POLICY_DOWN: + switch (policy->status) { + /* If the policy is GOING_UP, and zebra faild + to install it, we wait for zebra to retry */ + /* TODO: Add some timeout after which we would + get is back to DOWN and remove the + policy */ + case SRTE_POLICY_STATUS_GOING_UP: + case SRTE_POLICY_STATUS_DOWN: + return; + default: + zlog_debug("SR-TE(%s, %u): policy is DOWN", endpoint, + policy->color); + policy->status = SRTE_POLICY_STATUS_DOWN; + break; + } + break; + case ZEBRA_SR_POLICY_UP: + switch (policy->status) { + case SRTE_POLICY_STATUS_UP: + return; + default: + zlog_debug("SR-TE(%s, %u): policy is UP", endpoint, + policy->color); + policy->status = SRTE_POLICY_STATUS_UP; + break; + } + break; + } + + trigger_pathd_candidate_updated(candidate); +} + +/** + * Flags the segment lists from give originator for removal. + * + * The function srte_apply_changes must be called afterward for + * the segment list to be removed. + * + * @param originator The originator tag of the segment list to be marked + * @param force If the unset should be forced regardless of the originator + */ +void srte_candidate_unset_segment_list(const char *originator, bool force) +{ + if (originator == NULL) { + zlog_warn( + "Cannot unset segment list because originator is NULL"); + return; + } + + zlog_debug("Unset segment lists for originator %s", originator); + + /* Iterate the policies, then iterate each policy's candidate path + * to check the candidate path's segment list originator */ + struct srte_policy *policy; + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + zlog_debug("Unset segment lists checking policy %s", + policy->name); + struct srte_candidate *candidate; + RB_FOREACH (candidate, srte_candidate_head, + &policy->candidate_paths) { + zlog_debug("Unset segment lists checking candidate %s", + candidate->name); + if (candidate->lsp == NULL) { + continue; + } + + /* The candidate->lsp->segment_list is operational data, + * configured by the PCE. We dont want to modify the + * candidate->segment_list, + * which is configuration data. */ + struct srte_segment_list *segment_list = + candidate->lsp->segment_list; + if (segment_list == NULL) { + continue; + } + + if (segment_list->protocol_origin + == SRTE_ORIGIN_LOCAL) { + zlog_warn( + "Cannot unset segment list %s because it was created locally", + segment_list->name); + continue; + } + + /* In the case of last pce,we force the unset + * because we don't have pce by prefix (TODO) is all + * 'global' */ + if (strncmp(segment_list->originator, originator, + sizeof(segment_list->originator)) + == 0 + || force) { + zlog_debug("Unset segment list %s", + segment_list->name); + SET_FLAG(segment_list->flags, + F_SEGMENT_LIST_DELETED); + SET_FLAG(candidate->flags, + F_CANDIDATE_MODIFIED); + candidate->lsp->segment_list = NULL; + } + } + } +} + +/** + * Gives a string representation of given protocol origin enum. + * + * @param origin The enum you want a string representation of + * @return The string representation of given enum + */ +const char *srte_origin2str(enum srte_protocol_origin origin) +{ + switch (origin) { + case SRTE_ORIGIN_PCEP: + return "PCEP"; + case SRTE_ORIGIN_BGP: + return "BGP"; + case SRTE_ORIGIN_LOCAL: + return "Local"; + default: + return "Unknown"; + } +} + +void pathd_shutdown(void) +{ + path_ted_teardown(); + srte_clean_zebra(); + frr_fini(); +} + +void trigger_pathd_candidate_created(struct srte_candidate *candidate) +{ + /* The hook is called asynchronously to let the PCEP module + time to send a response to the PCE before receiving any updates from + pathd. In addition, a minimum amount of time need to pass before + the hook is called to prevent the hook to be called multiple times + from changing the candidate by hand with the console */ + if (candidate->hook_timer != NULL) + return; + thread_add_timer(master, trigger_pathd_candidate_created_timer, + (void *)candidate, HOOK_DELAY, &candidate->hook_timer); +} + +void trigger_pathd_candidate_created_timer(struct thread *thread) +{ + struct srte_candidate *candidate = THREAD_ARG(thread); + candidate->hook_timer = NULL; + hook_call(pathd_candidate_created, candidate); +} + +void trigger_pathd_candidate_updated(struct srte_candidate *candidate) +{ + /* The hook is called asynchronously to let the PCEP module + time to send a response to the PCE before receiving any updates from + pathd. In addition, a minimum amount of time need to pass before + the hook is called to prevent the hook to be called multiple times + from changing the candidate by hand with the console */ + if (candidate->hook_timer != NULL) + return; + thread_add_timer(master, trigger_pathd_candidate_updated_timer, + (void *)candidate, HOOK_DELAY, &candidate->hook_timer); +} + +void trigger_pathd_candidate_updated_timer(struct thread *thread) +{ + struct srte_candidate *candidate = THREAD_ARG(thread); + candidate->hook_timer = NULL; + hook_call(pathd_candidate_updated, candidate); +} + +void trigger_pathd_candidate_removed(struct srte_candidate *candidate) +{ + /* The hook needs to be call synchronously, otherwise the candidate + path will be already deleted when the handler is called */ + if (candidate->hook_timer != NULL) { + thread_cancel(&candidate->hook_timer); + candidate->hook_timer = NULL; + } + hook_call(pathd_candidate_removed, candidate); +} + +const char *srte_candidate_metric_name(enum srte_candidate_metric_type type) +{ + switch (type) { + case SRTE_CANDIDATE_METRIC_TYPE_IGP: + return "IGP"; + case SRTE_CANDIDATE_METRIC_TYPE_TE: + return "TE"; + case SRTE_CANDIDATE_METRIC_TYPE_HC: + return "HC"; + case SRTE_CANDIDATE_METRIC_TYPE_ABC: + return "ABC"; + case SRTE_CANDIDATE_METRIC_TYPE_LMLL: + return "LMLL"; + case SRTE_CANDIDATE_METRIC_TYPE_CIGP: + return "CIGP"; + case SRTE_CANDIDATE_METRIC_TYPE_CTE: + return "CTE"; + case SRTE_CANDIDATE_METRIC_TYPE_PIGP: + return "PIGP"; + case SRTE_CANDIDATE_METRIC_TYPE_PTE: + return "PTE"; + case SRTE_CANDIDATE_METRIC_TYPE_PHC: + return "PHC"; + case SRTE_CANDIDATE_METRIC_TYPE_MSD: + return "MSD"; + case SRTE_CANDIDATE_METRIC_TYPE_PD: + return "PD"; + case SRTE_CANDIDATE_METRIC_TYPE_PDV: + return "PDV"; + case SRTE_CANDIDATE_METRIC_TYPE_PL: + return "PL"; + case SRTE_CANDIDATE_METRIC_TYPE_PPD: + return "PPD"; + case SRTE_CANDIDATE_METRIC_TYPE_PPDV: + return "PPDV"; + case SRTE_CANDIDATE_METRIC_TYPE_PPL: + return "PPL"; + case SRTE_CANDIDATE_METRIC_TYPE_NAP: + return "NAP"; + case SRTE_CANDIDATE_METRIC_TYPE_NLP: + return "NLP"; + case SRTE_CANDIDATE_METRIC_TYPE_DC: + return "DC"; + case SRTE_CANDIDATE_METRIC_TYPE_BNC: + return "BNC"; + default: + return "UNKNOWN"; + } +} + +int32_t srte_ted_do_query_type_c(struct srte_segment_entry *entry, + struct prefix *prefix_cli, uint32_t algo) +{ + int32_t status = 0; + uint32_t ted_sid = MPLS_LABEL_NONE; + + if (!entry || !prefix_cli) + return 0; + + if (!path_ted_is_initialized()) + return 0; + + ted_sid = path_ted_query_type_c(prefix_cli, algo); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_warn(" %s: PATHD-TED: SL: ERROR query C : ted-sid (%d)", + __func__, ted_sid); + } else { + zlog_debug("%s: PATHD-TED: SL: Success query C : ted-sid (%d)", + __func__, ted_sid); + } + if (CHECK_SID(entry->segment_list->protocol_origin, ted_sid, + entry->sid_value)) { + status = PATH_SID_ERROR; + } else + srte_segment_set_local_modification(entry->segment_list, entry, + ted_sid); + return status; +} + +int32_t srte_ted_do_query_type_e(struct srte_segment_entry *entry, + struct prefix *prefix_cli, + uint32_t local_iface) +{ + int32_t status = 0; + uint32_t ted_sid = MPLS_LABEL_NONE; + + if (!entry || !prefix_cli) + return 0; + + if (!path_ted_is_initialized()) + return 0; + + ted_sid = path_ted_query_type_e(prefix_cli, local_iface); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_warn(" %s: PATHD-TED: SL: ERROR query E : ted-sid (%d)", + __func__, ted_sid); + } else { + zlog_debug("%s: PATHD-TED: SL: Success query E : ted-sid (%d)", + __func__, ted_sid); + } + if (CHECK_SID(entry->segment_list->protocol_origin, ted_sid, + entry->sid_value)) { + status = PATH_SID_ERROR; + } else + srte_segment_set_local_modification(entry->segment_list, entry, + ted_sid); + return status; +} + +int32_t srte_ted_do_query_type_f(struct srte_segment_entry *entry, + struct ipaddr *local, struct ipaddr *remote) +{ + int32_t status = 0; + uint32_t ted_sid = MPLS_LABEL_NONE; + + if (!entry || !local || !remote) + return 0; + + if (!path_ted_is_initialized()) + return status; + + ted_sid = path_ted_query_type_f(local, remote); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_warn("%s:SL: ERROR query F : ted-sid (%d)", __func__, + ted_sid); + } else { + zlog_debug("%s:SL: Success query F : ted-sid (%d)", __func__, + ted_sid); + } + if (CHECK_SID(entry->segment_list->protocol_origin, ted_sid, + entry->sid_value)) { + status = PATH_SID_ERROR; + } else + srte_segment_set_local_modification(entry->segment_list, entry, + ted_sid); + return status; +} diff --git a/pathd/pathd.h b/pathd/pathd.h new file mode 100644 index 0000000..81d7aa9 --- /dev/null +++ b/pathd/pathd.h @@ -0,0 +1,487 @@ +/* + * 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 + */ + +#ifndef _FRR_PATHD_H_ +#define _FRR_PATHD_H_ + +#include "lib/memory.h" +#include "lib/mpls.h" +#include "lib/ipaddr.h" +#include "lib/srte.h" +#include "lib/hook.h" +#include "lib/prefix.h" + +#define PATH_SID_ERROR 1 +#define PATH_SID_NO_ERROR 0 +#define CHECK_SID(or, ts, es) \ + ((or == SRTE_ORIGIN_PCEP && (ts == MPLS_LABEL_NONE || es != ts)) \ + || (or == SRTE_ORIGIN_LOCAL && ts == MPLS_LABEL_NONE)) + +DECLARE_MGROUP(PATHD); + +DECLARE_HOOK(pathd_srte_config_write, (struct vty *vty), (vty)); + +enum srte_protocol_origin { + SRTE_ORIGIN_UNDEFINED = 0, + SRTE_ORIGIN_PCEP = 1, + SRTE_ORIGIN_BGP = 2, + SRTE_ORIGIN_LOCAL = 3, +}; + +enum srte_policy_status { + SRTE_POLICY_STATUS_UNKNOWN = 0, + SRTE_POLICY_STATUS_DOWN = 1, + SRTE_POLICY_STATUS_UP = 2, + SRTE_POLICY_STATUS_GOING_DOWN = 3, + SRTE_POLICY_STATUS_GOING_UP = 4 +}; + +enum srte_candidate_type { + SRTE_CANDIDATE_TYPE_UNDEFINED = 0, + SRTE_CANDIDATE_TYPE_EXPLICIT = 1, + SRTE_CANDIDATE_TYPE_DYNAMIC = 2, +}; + +enum srte_candidate_metric_type { + /* IGP metric */ + SRTE_CANDIDATE_METRIC_TYPE_IGP = 1, + /* TE metric */ + SRTE_CANDIDATE_METRIC_TYPE_TE = 2, + /* Hop Counts */ + SRTE_CANDIDATE_METRIC_TYPE_HC = 3, + /* Aggregate bandwidth consumption */ + SRTE_CANDIDATE_METRIC_TYPE_ABC = 4, + /* Load of the most loaded link */ + SRTE_CANDIDATE_METRIC_TYPE_LMLL = 5, + /* Cumulative IGP cost */ + SRTE_CANDIDATE_METRIC_TYPE_CIGP = 6, + /* Cumulative TE cost */ + SRTE_CANDIDATE_METRIC_TYPE_CTE = 7, + /* P2MP IGP metric */ + SRTE_CANDIDATE_METRIC_TYPE_PIGP = 8, + /* P2MP TE metric */ + SRTE_CANDIDATE_METRIC_TYPE_PTE = 9, + /* P2MP hop count metric */ + SRTE_CANDIDATE_METRIC_TYPE_PHC = 10, + /* Segment-ID (SID) Depth */ + SRTE_CANDIDATE_METRIC_TYPE_MSD = 11, + /* Path Delay metric */ + SRTE_CANDIDATE_METRIC_TYPE_PD = 12, + /* Path Delay Variation metric */ + SRTE_CANDIDATE_METRIC_TYPE_PDV = 13, + /* Path Loss metric */ + SRTE_CANDIDATE_METRIC_TYPE_PL = 14, + /* P2MP Path Delay metric */ + SRTE_CANDIDATE_METRIC_TYPE_PPD = 15, + /* P2MP Path Delay variation metric */ + SRTE_CANDIDATE_METRIC_TYPE_PPDV = 16, + /* P2MP Path Loss metric */ + SRTE_CANDIDATE_METRIC_TYPE_PPL = 17, + /* Number of adaptations on a path */ + SRTE_CANDIDATE_METRIC_TYPE_NAP = 18, + /* Number of layers on a path */ + SRTE_CANDIDATE_METRIC_TYPE_NLP = 19, + /* Domain Count metric */ + SRTE_CANDIDATE_METRIC_TYPE_DC = 20, + /* Border Node Count metric */ + SRTE_CANDIDATE_METRIC_TYPE_BNC = 21, +}; +#define MAX_METRIC_TYPE 21 + +enum srte_segment_nai_type { + SRTE_SEGMENT_NAI_TYPE_NONE = 0, + SRTE_SEGMENT_NAI_TYPE_IPV4_NODE = 1, + SRTE_SEGMENT_NAI_TYPE_IPV6_NODE = 2, + SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY = 3, + SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY = 4, + SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY = 5, + SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES = 6, + SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE = 7, + SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE = 8, + SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM = 9, + SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM = 10 +}; + +enum objfun_type { + OBJFUN_UNDEFINED = 0, + /* Minimum Cost Path [RFC5541] */ + OBJFUN_MCP = 1, + /* Minimum Load Path [RFC5541] */ + OBJFUN_MLP = 2, + /* Maximum residual Bandwidth Path [RFC5541] */ + OBJFUN_MBP = 3, + /* Minimize aggregate Bandwidth Consumption [RFC5541] */ + OBJFUN_MBC = 4, + /* Minimize the Load of the most loaded Link [RFC5541] */ + OBJFUN_MLL = 5, + /* Minimize the Cumulative Cost of a set of paths [RFC5541] */ + OBJFUN_MCC = 6, + /* Shortest Path Tree [RFC8306] */ + OBJFUN_SPT = 7, + /* Minimum Cost Tree [RFC8306] */ + OBJFUN_MCT = 8, + /* Minimum Packet Loss Path [RFC8233] */ + OBJFUN_MPLP = 9, + /* Maximum Under-Utilized Path [RFC8233] */ + OBJFUN_MUP = 10, + /* Maximum Reserved Under-Utilized Path [RFC8233] */ + OBJFUN_MRUP = 11, + /* Minimize the number of Transit Domains [RFC8685] */ + OBJFUN_MTD = 12, + /* Minimize the number of Border Nodes [RFC8685] */ + OBJFUN_MBN = 13, + /* Minimize the number of Common Transit Domains [RFC8685] */ + OBJFUN_MCTD = 14, + /* Minimize the number of Shared Links [RFC8800] */ + OBJFUN_MSL = 15, + /* Minimize the number of Shared SRLGs [RFC8800] */ + OBJFUN_MSS = 16, + /* Minimize the number of Shared Nodes [RFC8800] */ + OBJFUN_MSN = 17, +}; +#define MAX_OBJFUN_TYPE 17 + +enum affinity_filter_type { + AFFINITY_FILTER_UNDEFINED = 0, + AFFINITY_FILTER_EXCLUDE_ANY = 1, + AFFINITY_FILTER_INCLUDE_ANY = 2, + AFFINITY_FILTER_INCLUDE_ALL = 3, +}; +#define MAX_AFFINITY_FILTER_TYPE 3 + +struct srte_segment_list; + +struct srte_segment_entry { + RB_ENTRY(srte_segment_entry) entry; + + /* The segment list the entry belong to */ + struct srte_segment_list *segment_list; + + /* Index of the Label. */ + uint32_t index; + + /* Label Value. */ + mpls_label_t sid_value; + + /* NAI Type */ + enum srte_segment_nai_type nai_type; + /* NAI local address when nai type is not NONE */ + struct ipaddr nai_local_addr; + /* NAI local interface when nai type is not IPv4 unnumbered adjacency */ + uint32_t nai_local_iface; + /* NAI local interface when nai type is IPv4 or IPv6 adjacency */ + struct ipaddr nai_remote_addr; + /* NAI remote interface when nai type is not IPv4 unnumbered adjacency + */ + uint32_t nai_remote_iface; + /* Support draft-ietf-spring-segment-routing-policy sl types queries*/ + uint8_t nai_local_prefix_len; + uint8_t nai_algorithm; +}; +RB_HEAD(srte_segment_entry_head, srte_segment_entry); +RB_PROTOTYPE(srte_segment_entry_head, srte_segment_entry, entry, + srte_segment_entry_compare) + +struct srte_segment_list { + RB_ENTRY(srte_segment_list) entry; + + /* Name of the Segment List. */ + char name[64]; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* Nexthops. */ + struct srte_segment_entry_head segments; + + /* Status flags. */ + uint16_t flags; +#define F_SEGMENT_LIST_NEW 0x0002 +#define F_SEGMENT_LIST_MODIFIED 0x0004 +#define F_SEGMENT_LIST_DELETED 0x0008 +#define F_SEGMENT_LIST_SID_CONFLICT 0x0010 +}; +RB_HEAD(srte_segment_list_head, srte_segment_list); +RB_PROTOTYPE(srte_segment_list_head, srte_segment_list, entry, + srte_segment_list_compare) + +struct srte_metric { + uint16_t flags; +#define F_METRIC_IS_DEFINED 0x0001 +#define F_METRIC_IS_REQUIRED 0x0002 +#define F_METRIC_IS_BOUND 0x0004 +#define F_METRIC_IS_COMPUTED 0x0008 + float value; +}; + +/* Runtime information about the candidate path */ +struct srte_lsp { + /* Backpointer to the Candidate Path. */ + struct srte_candidate *candidate; + + /* The associated Segment List. */ + struct srte_segment_list *segment_list; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* The Discriminator */ + uint32_t discriminator; + + /* Flags. */ + uint32_t flags; + + /* Metrics LSP Values */ + struct srte_metric metrics[MAX_METRIC_TYPE]; + + /* Bandwidth Configured Value */ + float bandwidth; + + /* The objective function in used */ + enum objfun_type objfun; +}; + +/* Configured candidate path */ +struct srte_candidate { + RB_ENTRY(srte_candidate) entry; + + /* Backpointer to SR Policy */ + struct srte_policy *policy; + + /* The LSP associated with this candidate path. */ + struct srte_lsp *lsp; + + /* Administrative preference. */ + uint32_t preference; + + /* Symbolic Name. */ + char name[64]; + + /* The associated Segment List. */ + struct srte_segment_list *segment_list; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* The Discriminator */ + uint32_t discriminator; + + /* The Type (explicit or dynamic) */ + enum srte_candidate_type type; + + /* Flags. */ + uint32_t flags; +#define F_CANDIDATE_BEST 0x0001 +#define F_CANDIDATE_NEW 0x0002 +#define F_CANDIDATE_MODIFIED 0x0004 +#define F_CANDIDATE_DELETED 0x0008 +#define F_CANDIDATE_HAS_BANDWIDTH 0x0100 +#define F_CANDIDATE_REQUIRED_BANDWIDTH 0x0200 +#define F_CANDIDATE_HAS_OBJFUN 0x0400 +#define F_CANDIDATE_REQUIRED_OBJFUN 0x0800 +#define F_CANDIDATE_HAS_EXCLUDE_ANY 0x1000 +#define F_CANDIDATE_HAS_INCLUDE_ANY 0x2000 +#define F_CANDIDATE_HAS_INCLUDE_ALL 0x4000 + + /* Metrics Configured Values */ + struct srte_metric metrics[MAX_METRIC_TYPE]; + + /* Bandwidth Configured Value */ + float bandwidth; + + /* Configured objective functions */ + enum objfun_type objfun; + + /* Path constraints attribute filters */ + uint32_t affinity_filters[MAX_AFFINITY_FILTER_TYPE]; + + /* Hooks delaying timer */ + struct thread *hook_timer; +}; + +RB_HEAD(srte_candidate_head, srte_candidate); +RB_PROTOTYPE(srte_candidate_head, srte_candidate, entry, srte_candidate_compare) + +struct srte_policy { + RB_ENTRY(srte_policy) entry; + + /* Color */ + uint32_t color; + + /* Endpoint */ + struct ipaddr endpoint; + + /* Name */ + char name[64]; + + /* Binding SID */ + mpls_label_t binding_sid; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* Operational Status of the policy */ + enum srte_policy_status status; + + /* Best candidate path. */ + struct srte_candidate *best_candidate; + + /* Candidate Paths */ + struct srte_candidate_head candidate_paths; + /* Status flags. */ + uint16_t flags; +#define F_POLICY_NEW 0x0002 +#define F_POLICY_MODIFIED 0x0004 +#define F_POLICY_DELETED 0x0008 + /* SRP id for PcInitiated support */ + int srp_id; +}; +RB_HEAD(srte_policy_head, srte_policy); +RB_PROTOTYPE(srte_policy_head, srte_policy, entry, srte_policy_compare) + +DECLARE_HOOK(pathd_candidate_created, (struct srte_candidate * candidate), + (candidate)); +DECLARE_HOOK(pathd_candidate_updated, (struct srte_candidate * candidate), + (candidate)); +DECLARE_HOOK(pathd_candidate_removed, (struct srte_candidate * candidate), + (candidate)); + +extern struct srte_segment_list_head srte_segment_lists; +extern struct srte_policy_head srte_policies; +extern struct zebra_privs_t pathd_privs; + +/* master thread, defined in path_main.c */ +extern struct thread_master *master; + +/* pathd.c */ +struct srte_segment_list *srte_segment_list_add(const char *name); +void srte_segment_list_del(struct srte_segment_list *segment_list); +struct srte_segment_list *srte_segment_list_find(const char *name); +struct srte_segment_entry * +srte_segment_entry_add(struct srte_segment_list *segment_list, uint32_t index); +void srte_segment_entry_del(struct srte_segment_entry *segment); +int srte_segment_entry_set_nai(struct srte_segment_entry *segment, + enum srte_segment_nai_type type, + struct ipaddr *local_ip, uint32_t local_iface, + struct ipaddr *remote_ip, uint32_t remote_iface, + uint8_t algo, uint8_t pref_len); +void srte_segment_set_local_modification(struct srte_segment_list *s_list, + struct srte_segment_entry *s_entry, + uint32_t ted_sid); +struct srte_policy *srte_policy_add(uint32_t color, struct ipaddr *endpoint, + enum srte_protocol_origin origin, + const char *originator); +void srte_policy_del(struct srte_policy *policy); +struct srte_policy *srte_policy_find(uint32_t color, struct ipaddr *endpoint); +int srte_policy_update_ted_sid(void); +void srte_policy_update_binding_sid(struct srte_policy *policy, + uint32_t binding_sid); +void srte_apply_changes(void); +void srte_clean_zebra(void); +void srte_policy_apply_changes(struct srte_policy *policy); +struct srte_candidate *srte_candidate_add(struct srte_policy *policy, + uint32_t preference, + enum srte_protocol_origin origin, + const char *originator); +void srte_candidate_del(struct srte_candidate *candidate); +void srte_candidate_set_bandwidth(struct srte_candidate *candidate, + float bandwidth, bool required); +void srte_candidate_unset_bandwidth(struct srte_candidate *candidate); +void srte_candidate_set_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type, + float value, bool required, bool is_cound, + bool is_computed); +void srte_candidate_unset_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type); +void srte_candidate_set_objfun(struct srte_candidate *candidate, bool required, + enum objfun_type type); +void srte_candidate_unset_objfun(struct srte_candidate *candidate); +void srte_candidate_set_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type, + uint32_t filter); +void srte_candidate_unset_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type); +void srte_lsp_set_bandwidth(struct srte_lsp *lsp, float bandwidth, + bool required); +void srte_lsp_unset_bandwidth(struct srte_lsp *lsp); +void srte_lsp_set_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type, float value, + bool required, bool is_cound, bool is_computed); +void srte_lsp_unset_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type); +struct srte_candidate *srte_candidate_find(struct srte_policy *policy, + uint32_t preference); +struct srte_segment_entry * +srte_segment_entry_find(struct srte_segment_list *segment_list, uint32_t index); +void srte_candidate_status_update(struct srte_candidate *candidate, int status); +void srte_candidate_unset_segment_list(const char *originator, bool force); +const char *srte_origin2str(enum srte_protocol_origin origin); +void pathd_shutdown(void); + +/* path_cli.c */ +void path_cli_init(void); + + +/** + * Search for sid based in prefix and algorithm + * + * @param Prefix The prefix to use + * @param algo Algorithm we want to query for + * @param ted_sid Sid to query + * + * @return void + */ +int32_t srte_ted_do_query_type_c(struct srte_segment_entry *entry, + struct prefix *prefix_cli, uint32_t algo); + +/** + * Search for sid based in prefix and interface id + * + * @param Prefix The prefix to use + * @param local_iface The id of interface + * @param ted_sid Sid to query + * + * @return void + */ +int32_t srte_ted_do_query_type_e(struct srte_segment_entry *entry, + struct prefix *prefix_cli, + uint32_t local_iface); +/** + * Search for sid based in local and remote ip + * + * @param entry entry to update + * @param local Local addr for query + * @param remote Local addr for query + * + * @return void + */ +int32_t srte_ted_do_query_type_f(struct srte_segment_entry *entry, + struct ipaddr *local, struct ipaddr *remote); +#endif /* _FRR_PATHD_H_ */ diff --git a/pathd/subdir.am b/pathd/subdir.am new file mode 100644 index 0000000..f339c79 --- /dev/null +++ b/pathd/subdir.am @@ -0,0 +1,85 @@ +# +# pathd +# + +if PATHD +noinst_LIBRARIES += pathd/libpath.a +sbin_PROGRAMS += pathd/pathd +vtysh_scan += \ + pathd/path_cli.c \ + pathd/path_ted.c \ + #end +vtysh_daemons += pathd +# TODO add man page +#man8 += $(MANBUILD)/pathd.8 + +if PATHD_PCEP +vtysh_scan += pathd/path_pcep_cli.c +module_LTLIBRARIES += pathd/pathd_pcep.la +endif + +endif + +pathd_libpath_a_SOURCES = \ + pathd/path_cli.c \ + pathd/path_debug.c \ + pathd/path_errors.c \ + pathd/path_nb.c \ + pathd/path_nb_config.c \ + pathd/path_nb_state.c \ + pathd/path_ted.c \ + pathd/path_zebra.c \ + pathd/pathd.c \ + # end + +clippy_scan += \ + pathd/path_cli.c \ + pathd/path_pcep_cli.c \ + pathd/path_ted.c \ + # end + +noinst_HEADERS += \ + pathd/path_debug.h \ + pathd/path_errors.h \ + pathd/path_nb.h \ + pathd/path_pcep.h \ + pathd/path_pcep_cli.h \ + pathd/path_pcep_controller.h \ + pathd/path_pcep_debug.h \ + pathd/path_pcep_lib.h \ + pathd/path_pcep_config.h \ + pathd/path_pcep_pcc.h \ + pathd/path_ted.h \ + pathd/path_zebra.h \ + pathd/pathd.h \ + # end + +pathd_pathd_SOURCES = \ + pathd/path_main.c \ + # end +nodist_pathd_pathd_SOURCES = \ + yang/frr-pathd.yang.c \ + # end +pathd_pathd_LDADD = pathd/libpath.a lib/libfrr.la -lm $(LIBCAP) + +pathd_pathd_pcep_la_SOURCES = \ + pathd/path_pcep.c \ + pathd/path_pcep_cli.c \ + pathd/path_pcep_controller.c \ + pathd/path_pcep_debug.c \ + pathd/path_pcep_lib.c \ + pathd/path_pcep_config.c \ + pathd/path_pcep_pcc.c \ + # end + +if PATHD_PCEP +pathd_pathd_pcep_la_CPPFLAGS = -I./pceplib $(AM_CPPFLAGS) +pathd_pathd_pcep_la_LIBADD = pceplib/libpcep_pcc.la +else +pathd_pathd_pcep_la_CPPFLAGS = $(AM_CPPFLAGS) +pathd_pathd_pcep_la_LIBADD = +endif + + +#pathd_pathd_pcep_la_CFLAGS = $(AM_CFLAGS) +pathd_pathd_pcep_la_LDFLAGS = $(MODULE_LDFLAGS) |