From efba0985fcddee80df257147f7e398b52dad089b Mon Sep 17 00:00:00 2001 From: Sebastien Merle Date: Fri, 16 Oct 2020 16:55:51 +0200 Subject: [PATCH] pathd: Add optional support for PCEP to pathd This new dynamic module makes pathd behave as a PCC for dynamic candidate path using the external library pcpelib https://github.com/volta-networks/pceplib . The candidate paths defined as dynamic will trigger computation requests to the configured PCE, and the PCE response will be used to update the policy. It supports multiple PCE. The one with smaller precedence will be elected as the master PCE, and only if the connection repeatedly fails, the PCC will switch to another PCE. Example of configuration: segment-routing traffic-eng pcep pce-config CONF source-address ip 10.10.10.10 sr-draft07 ! pce PCE1 config CONF address ip 1.1.1.1 ! pce PCE2 config CONF address ip 2.2.2.2 ! pcc peer PCE1 precedence 10 peer PCE2 precedence 20 ! ! ! ! Co-authored-by: Brady Johnson Co-authored-by: Emanuele Di Pascale Co-authored-by: GalaxyGorilla Co-authored-by: Javier Garcia Co-authored-by: Renato Westphal Co-authored-by: Sebastien Merle Signed-off-by: Sebastien Merle --- configure.ac | 18 + doc/developer/path-internals-pcep.rst | 193 ++ doc/developer/path-internals.rst | 1 + doc/developer/subdir.am | 1 + .../pcep_module_threading_overview.svg | 481 ++++ doc/user/pathd.rst | 210 ++ lib/command.c | 12 + lib/command.h | 4 + lib/northbound.h | 3 + pathd/path_errors.c | 91 + pathd/path_errors.h | 15 + pathd/path_pcep.c | 339 +++ pathd/path_pcep.h | 326 +++ pathd/path_pcep_cli.c | 2029 +++++++++++++++++ pathd/path_pcep_cli.h | 27 + pathd/path_pcep_config.c | 435 ++++ pathd/path_pcep_config.h | 44 + pathd/path_pcep_controller.c | 1077 +++++++++ pathd/path_pcep_controller.h | 165 ++ pathd/path_pcep_debug.c | 1771 ++++++++++++++ pathd/path_pcep_debug.h | 56 + pathd/path_pcep_lib.c | 1146 ++++++++++ pathd/path_pcep_lib.h | 48 + pathd/path_pcep_memory.c | 27 + pathd/path_pcep_memory.h | 28 + pathd/path_pcep_pcc.c | 1817 +++++++++++++++ pathd/path_pcep_pcc.h | 141 ++ pathd/pathd.conf.sample | 8 + pathd/subdir.am | 37 +- tools/frr-reload.py | 96 + vtysh/vtysh.c | 129 +- 31 files changed, 10762 insertions(+), 13 deletions(-) create mode 100644 doc/developer/path-internals-pcep.rst create mode 100644 doc/figures/pcep_module_threading_overview.svg create mode 100644 pathd/path_pcep.c create mode 100644 pathd/path_pcep.h create mode 100644 pathd/path_pcep_cli.c create mode 100644 pathd/path_pcep_cli.h create mode 100644 pathd/path_pcep_config.c create mode 100644 pathd/path_pcep_config.h create mode 100644 pathd/path_pcep_controller.c create mode 100644 pathd/path_pcep_controller.h create mode 100644 pathd/path_pcep_debug.c create mode 100644 pathd/path_pcep_debug.h create mode 100644 pathd/path_pcep_lib.c create mode 100644 pathd/path_pcep_lib.h create mode 100644 pathd/path_pcep_memory.c create mode 100644 pathd/path_pcep_memory.h create mode 100644 pathd/path_pcep_pcc.c create mode 100644 pathd/path_pcep_pcc.h diff --git a/configure.ac b/configure.ac index 168a9041df..0cbf4d22e6 100755 --- a/configure.ac +++ b/configure.ac @@ -627,6 +627,8 @@ AC_ARG_ENABLE([pcreposix], AS_HELP_STRING([--enable-pcreposix], [enable using PCRE Posix libs for regex functions])) AC_ARG_ENABLE([fpm], AS_HELP_STRING([--enable-fpm], [enable Forwarding Plane Manager support])) +AC_ARG_ENABLE([pcep], + AS_HELP_STRING([--enable-pcep], [enable PCEP support for pathd])) AC_ARG_ENABLE([systemd], AS_HELP_STRING([--enable-systemd], [enable Systemd support])) AC_ARG_ENABLE([werror], @@ -1681,6 +1683,10 @@ AS_IF([test "$enable_pathd" != "no"], [ AC_DEFINE([HAVE_PATHD], [1], [pathd]) ]) +AS_IF([test "$enable_pcep" != "no"], [ + AC_DEFINE([HAVE_PATHD_PCEP], [1], [pathd-pcep]) +]) + if test "$ac_cv_lib_json_c_json_object_get" = "no" -a "$BFDD" = "bfdd"; then AC_MSG_ERROR(["you must use json-c library to use bfdd"]) fi @@ -2473,6 +2479,18 @@ AM_CONDITIONAL([IRDP], [$IRDP]) AM_CONDITIONAL([FPM], [test "$enable_fpm" = "yes"]) AM_CONDITIONAL([HAVE_PROTOBUF], [test "$enable_protobuf" = "yes"]) AM_CONDITIONAL([HAVE_PROTOBUF3], [$PROTO3]) + +dnl PCEP plugin +AM_CONDITIONAL([HAVE_PATHD_PCEP], [test "$enable_pcep" = "yes"]) +AS_IF([test "$enable_pcep" = "yes"], [ + AC_CHECK_LIB([pcep_pcc], [initialize_pcc], [ + PATHD_PCEP_LIBS="-lpcep_pcc" + ],[ + AC_MSG_ERROR([PCEP library libpcep_pcc not found]) + ]) + AC_SUBST([PATHD_PCEP_LIBS]) +]) + dnl daemons AM_CONDITIONAL([VTYSH], [test "$VTYSH" = "vtysh"]) AM_CONDITIONAL([ZEBRA], [test "$enable_zebra" != "no"]) diff --git a/doc/developer/path-internals-pcep.rst b/doc/developer/path-internals-pcep.rst new file mode 100644 index 0000000000..ca318314f1 --- /dev/null +++ b/doc/developer/path-internals-pcep.rst @@ -0,0 +1,193 @@ +PCEP Module Internals +===================== + +Introduction +------------ + +The PCEP module for the pathd daemon implements the PCEP protocol described in +:rfc:`5440` to update the policies and candidate paths. + +The protocol encoding/decoding and the basic session management is handled by +the `pceplib external library 1.2 `_. + +Together with pceplib, this module supports at least partially: + + - :rfc:`5440` + + Most of the protocol defined in the RFC is implemented. + All the messages can be parsed, but this was only tested in the context + of segment routing. Only a very small subset of metric types can be + configured, and there is a known issue with some Cisco routers not + following the IANA numbers for metrics. + + - :rfc:`8231` + + Support delegation of candidate path after performing the initial + computation request. If the PCE does not respond or cannot compute + a path, an empty candidate path is delegated to the PCE. + Only tested in the context of segment routing. + + - :rfc:`8408` + + Only used to comunicate the support for segment routing to the PCE. + + - :rfc:`8664` + + All the NAI types are implemented, but only the MPLS NAI are supported. + If the PCE provide segments that are not MPLS labels, the PCC will + return an error. + +Note that pceplib supports more RFCs and drafts, see pceplib +`README `_ +for more details. + + +Architecture +------------ + +Overview +........ + +The module is separated into multiple layers: + + - pathd interface + - command-line console + - controller + - PCC + - pceplib interface + +The pathd interface handles all the interactions with the daemon API. + +The command-line console handles all the VTYSH configuration commands. + +The controller manages the multiple PCC connections and the interaction between +them and the daemon interface. + +The PCC handles a single connection to a PCE through a pceplib session. + +The pceplib interface abstracts the API of the pceplib. + +.. figure:: ../figures/pcep_module_threading_overview.svg + + +Threading Model +--------------- + +The module requires multiple threads to cooperate: + + - The main thread used by the pathd daemon. + - The controller pthread used to isolate the PCC from the main thread. + - The possible threads started in the pceplib library. + +To ensure thread safety, all the controller and PCC state data structures can +only be read and modified in the controller thread, and all the global data +structures can only be read and modified in the main thread. Most of the +interactions between these threads are done through FRR timers and events. + +The controller is the bridge between the two threads, all the functions that +**MUST** be called from the main thread start with the prefix `pcep_ctrl_` and +all the functions that **MUST** be called from the controller thread start +with the prefix `pcep_thread_`. When an asynchronous action must be taken in +a different thread, an FRR event is sent to the thread. If some synchronous +operation is needed, the calling thread will block and run a callback in the +other thread, there the result is **COPIED** and returned to the calling thread. + +No function other than the controller functions defined for it should be called +from the main thread. The only exception being some utility functions from +`path_pcep_lib.[hc]`. + +All the calls to pathd API functions **MUST** be performed in the main thread, +for that, the controller sends FRR events handled in function +`path_pcep.c:pcep_main_event_handler`. + +For the same reason, the console client only runs in the main thread. It can +freely use the global variable, but **MUST** use controller's `pcep_ctrl_` +functions to interact with the PCCs. + + +Source Code +----------- + +Generic Data Structures +....................... + +The data structures are defined in multiple places, and where they are defined +dictates where they can be used. + +The data structures defined in `path_pcep.h` can be used anywhere in the module. + +Internally, throughout the module, the `struct path` data structure is used +to describe PCEP messages. It is a simplified flattened structure that can +represent multiple complex PCEP message types. The conversion from this +structure to the PCEP data structures used by pceplib is done in the pceplib +interface layer. + +The data structures defined in `path_pcep_controller.h` should only be used +in `path_pcep_controller.c`. Even if a structure pointer is passed as a parameter +to functions defined in `path_pcep_pcc.h`, these should consider it as an opaque +data structure only used to call back controller functions. + +The same applies to the structures defined in `path_pcep_pcc.h`, even if the +controller owns a reference to this data structure, it should never read or +modify it directly, it should be considered an opaque structure. + +The global data structure can be accessed from the pathd interface layer +`path_pcep.c` and the command line client code `path_pcep_cli.c`. + + +Interface With Pathd +.................... + +All the functions calling or called by the pathd daemon are implemented in +`path_pcep.c`. These functions **MUST** run in the main FRR thread, and +all the interactions with the controller and the PCCs **MUST** pass through +the controller's `pcep_ctrl_` prefixed functions. + +To handle asynchronous events from the PCCs, a callback is passed to +`pcep_ctrl_initialize` that is called in the FRR main thread context. + + +Command Line Client +................... + +All the command line configuration commands (VTYSH) are implemented in +`path_pcep_cli.c`. All the functions there run in the main FRR thread and +can freely access the global variables. All the interaction with the +controller's and the PCCs **MUST** pass through the controller `pcep_ctrl_` +prefixed functions. + + +Debugging Helpers +................. + +All the functions formating data structures for debugging and logging purposes +are implemented in `path_pcep_debug.[hc]`. + + +Interface with pceplib +...................... + +All the functions calling the pceplib external library are defined in +`path_pcep_lib.[hc]`. Some functions are called from the main FRR thread, like +`pcep_lib_initialize`, `pcep_lib_finalize`; some can be called from either +thread, like `pcep_lib_free_counters`; some function must be called from the +controller thread, like `pcep_lib_connect`. This will probably be formalized +later on with function prefix like done in the controller. + + +Controller +.......... + +The controller is defined and implemented in `path_pcep_controller.[hc]`. +Part of the controller code runs in FRR main thread and part runs in its own +FRR pthread started to isolate the main thread from the PCCs' event loop. +To communicate between the threads it uses FRR events, timers and +`thread_execute` calls. + + +PCC +... + +Each PCC instance owns its state and runs in the controller thread. They are +defined and implemented in `path_pcep_pcc.[hc]`. All the interactions with +the daemon must pass through some controller's `pcep_thread_` prefixed function. diff --git a/doc/developer/path-internals.rst b/doc/developer/path-internals.rst index 980d359453..2c2df0f378 100644 --- a/doc/developer/path-internals.rst +++ b/doc/developer/path-internals.rst @@ -8,3 +8,4 @@ Internals :maxdepth: 2 path-internals-daemon + path-internals-pcep diff --git a/doc/developer/subdir.am b/doc/developer/subdir.am index 400555a2e8..f131ba89d9 100644 --- a/doc/developer/subdir.am +++ b/doc/developer/subdir.am @@ -47,6 +47,7 @@ dev_RSTFILES = \ doc/developer/packaging-redhat.rst \ doc/developer/packaging.rst \ doc/developer/path-internals-daemon.rst \ + doc/developer/path-internals-pcep.rst \ doc/developer/path-internals.rst \ doc/developer/path.rst \ doc/developer/rcu.rst \ diff --git a/doc/figures/pcep_module_threading_overview.svg b/doc/figures/pcep_module_threading_overview.svg new file mode 100644 index 0000000000..4d2d2a254a --- /dev/null +++ b/doc/figures/pcep_module_threading_overview.svg @@ -0,0 +1,481 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MAIN PTHREAD + + + + + + CONTROLLER PTHREAD + + + + + + + + CLI + + + + + + + + PATHDINTERFACE + + + + + + + + PCC + + + + + + + + + + + + + + VTYSH + + + + + + Northbound + + + + + + pathd API + + + + + + + + + + + + + + PCEPLIBINTERFACE + + + + + + + + + + + + + + + + + + + + + + + + CONTROLLER + + + + + + pcep_ctrl_XXX + + + + + + pcep_thread_XXX + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pceplib API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/user/pathd.rst b/doc/user/pathd.rst index 0c825d423b..5e210e2489 100644 --- a/doc/user/pathd.rst +++ b/doc/user/pathd.rst @@ -25,6 +25,33 @@ present and the :file:`frr.conf` is read instead. documented elsewhere. +PCEP Support +============ + +To build the PCC for pathd, the externall library `pceplib 1.2 `_ is required. + +To build FRR with support for PCEP the following steps must be followed: + + - Checkout and build pceplib: + +``` +$ git clone https://github.com/volta-networks/pceplib +$ cd pceplib +$ make +$ make install +$ export PCEPLIB_ROOT=$PWD +``` + + - Configure FRR with the extra parameters: + +``` +--enable-pcep LDFLAGS="-L${PCEPLIB_ROOT}/install/lib" CPPFLAGS="-I${PCEPLIB_ROOT}/install/include" +``` + +To start pathd with pcep support the extra parameter `-M pathd_pcep` should be +passed to the pathd daemon. + + Pathd Configuration =================== @@ -32,6 +59,7 @@ Example: .. code-block:: frr + debug pathd pcep basic segment-routing traffic-eng segment-list SL1 @@ -49,6 +77,25 @@ Example: metric te 10 objective-function mcp required ! + pcep + pce-config GROUP1 + source-address 1.1.1.1 + tcp-md5-auth secret + timer keep-alive 30 + ! + pce PCE1 + config GROUP1 + address ip 10.10.10.10 + ! + pce PCE2 + config GROUP1 + address ip 9.9.9.9 + ! + pcc + peer PCE1 precedence 10 + peer PCE2 precedence 20 + ! + ! ! ! @@ -177,6 +224,121 @@ Configuration Commands - msn: Minimize the number of Shared Nodes [RFC8800] +.. index:: [no] debug pathd pcep [basic|path|message|pceplib] +.. clicmd:: [no] debug pathd pcep [basic|path|message|pceplib] + + Enable or disable debugging for the pcep module: + + - basic: Enable basic PCEP logging + - path: Log the path structures + - message: Log the PCEP messages + - pceplib: Enable pceplib logging + + +.. index:: pcep +.. clicmd:: pcep + + Configure PCEP support. + + +.. index:: [no] cep-config NAME +.. clicmd:: [no] pce-config NAME + + Define a shared PCE configuration that can be used in multiple PCE + declarations. + + +.. index:: [no] pce NAME +.. clicmd:: [no] pce NAME + + Define or delete a PCE definition. + + +.. index:: config WORD +.. clicmd:: config WORD + + Select a shared configuration. If not defined, the default + configuration will be used. + + +.. index:: address [port (1024-65535)] +.. clicmd:: address [port (1024-65535)] + + Define the address and port of the PCE. + + If not specified, the port is the standard PCEP port 4189. + + This should be specified in the PCC peer definition. + + +.. index:: source-address [ip A.B.C.D | ipv6 X:X::X:X] [port PORT] +.. clicmd:: source-address [ip A.B.C.D | ipv6 X:X::X:X] [port PORT] + + Define the address and/or port of the PCC as seen by the PCE. + This can be used in a configuration group or a PCC peer declaration. + + If not specified, the source address will be the router identifier selected + by zebra, and the port will be the standard PCEP port 4189. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. index:: tcp-md5-auth WORD +.. clicmd:: tcp-md5-auth WORD + + Enable TCP MD5 security with the given secret. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. index:: sr-draft07 +.. clicmd:: sr-draft07 + + Specify if a PCE only support segment routing draft 7, this flag will limit + the PCC behavior to this draft. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. index:: pce-initiated +.. clicmd:: pce-initiated + + Specify if PCE-initiated LSP should be allowed for this PCE. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. index:: 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)] +.. clicmd:: 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)] + + Specify the PCEP timers. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. index:: [no] pcc +.. clicmd:: [no] pcc + + Disable or start the definition of a PCC. + + +.. index:: msd (1-32) +.. clicmd:: msd (1-32) + + Specify the maximum SID depth in a PCC definition. + + +.. index:: [no] peer WORD [precedence (1-255)] +.. clicmd:: [no] peer WORD [precedence (1-255)] + + Specify a peer and its precedence in a PCC definition. + + Introspection Commands ---------------------- @@ -206,6 +368,54 @@ The asterisk (*) marks the best, e.g. active, candidate path. Note that for segm retrieved via PCEP a random number based name is generated. +.. index:: show debugging pathd +.. clicmd:: show debugging pathd + + Display the current status of the pathd debugging. + + +.. index:: show debugging pathd-pcep +.. clicmd:: show debugging pathd-pcep + + Display the current status of the pcep module debugging. + + +.. index:: show sr-te pcep counters +.. clicmd:: show sr-te pcep counters + + Display the counters from pceplib. + + +.. index:: show sr-te pcep pce-config [NAME] +.. clicmd:: show sr-te pcep pce-config [NAME] + + Display a shared configuration. if no name is specified, the default + configuration will be displayed. + + +.. index:: show sr-te pcep pcc +.. clicmd:: show sr-te pcep pcc + + Display PCC information. + + +.. index:: show sr-te pcep session [NAME] +.. clicmd:: show sr-te pcep session [NAME] + + Display the information of a PCEP session, if not name is specified all the + sessions will be displayed. + + +Utility Commands +---------------- + +.. index:: clear sr-te pcep session [NAME] +.. clicmd:: clear sr-te pcep session [NAME] + + Reset the pcep session by disconnecting from the PCE and performing the + normal reconnection process. No configuration is changed. + + Usage with BGP route-maps ========================= diff --git a/lib/command.c b/lib/command.c index eeb14b734d..f40fe6e2c5 100644 --- a/lib/command.c +++ b/lib/command.c @@ -875,6 +875,18 @@ enum node_type node_parent(enum node_type node) case SR_CANDIDATE_DYN_NODE: ret = SR_POLICY_NODE; break; + case PCEP_NODE: + ret = SR_TRAFFIC_ENG_NODE; + break; + case PCEP_PCE_CONFIG_NODE: + ret = PCEP_NODE; + break; + case PCEP_PCE_NODE: + ret = PCEP_NODE; + break; + case PCEP_PCC_NODE: + ret = PCEP_NODE; + break; default: ret = CONFIG_NODE; break; diff --git a/lib/command.h b/lib/command.h index 2eae9a1b35..bfe64a7235 100644 --- a/lib/command.h +++ b/lib/command.h @@ -150,6 +150,10 @@ enum node_type { SR_SEGMENT_LIST_NODE, /* SR segment list config node */ SR_POLICY_NODE, /* SR policy config node */ SR_CANDIDATE_DYN_NODE, /* SR dynamic candidate path config node */ + PCEP_NODE, /* PCEP node */ + PCEP_PCE_CONFIG_NODE, /* PCE shared configuration node */ + PCEP_PCE_NODE, /* PCE configuration node */ + PCEP_PCC_NODE, /* PCC configuration node */ VTY_NODE, /* Vty node. */ FPM_NODE, /* Dataplane FPM node. */ LINK_PARAMS_NODE, /* Link-parameters node */ diff --git a/lib/northbound.h b/lib/northbound.h index 3f6e4dc46e..c37d66d580 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -600,6 +600,7 @@ enum nb_client { NB_CLIENT_CONFD, NB_CLIENT_SYSREPO, NB_CLIENT_GRPC, + NB_CLIENT_PCEP, }; /* Northbound context. */ @@ -621,6 +622,8 @@ struct nb_context { } sysrepo; struct { } grpc; + struct { + } pcep; } client_data; #endif }; diff --git a/pathd/path_errors.c b/pathd/path_errors.c index f266b6b4ea..f8560a848c 100644 --- a/pathd/path_errors.c +++ b/pathd/path_errors.c @@ -24,15 +24,106 @@ /* 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_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 = "Receved 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 */ diff --git a/pathd/path_errors.h b/pathd/path_errors.h index 366878f300..72e127f26b 100644 --- a/pathd/path_errors.h +++ b/pathd/path_errors.h @@ -23,6 +23,21 @@ 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_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); diff --git a/pathd/path_pcep.c b/pathd/path_pcep.c new file mode 100644 index 0000000000..2f9ff4f0f0 --- /dev/null +++ b/pathd/path_pcep.c @@ -0,0 +1,339 @@ +/* + * 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 +#include + +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "version.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" +#include "termtable.h" + +#include "pathd/pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep_memory.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" + + +/* + * 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_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); + +/* 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); +} + + +/* ------------ 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_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_update_candidate(struct path *path) +{ + struct path *resp = NULL; + int ret = 0; + + ret = path_pcep_config_update_path(path); + if (ret != PATH_NB_ERR && path->srp_id != 0) { + /* 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; + if ((resp = path_pcep_config_get_path(&path->nbkey))) { + resp->srp_id = path->srp_id; + real_status = resp->status; + resp->status = PCEP_LSP_OPERATIONAL_DOWN; + pcep_ctrl_send_report(pcep_g->fpt, path->pcc_id, resp); + /* If the update did not have any effect and the real + * status is not DOWN, we need to send a second report + * so the PCE is aware of the real status. This is due + * to the fact that NO notification will be received + * if the update did not apply any changes */ + if ((ret == PATH_NB_NO_CHANGE) + && (real_status != PCEP_LSP_OPERATIONAL_DOWN)) { + resp->status = real_status; + resp->srp_id = 0; + pcep_ctrl_send_report(pcep_g->fpt, path->pcc_id, + resp); + } + pcep_free_path(resp); + } + } + 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 ------------ */ + +int pcep_module_late_init(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; + + 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_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 0000000000..1896c265c1 --- /dev/null +++ b/pathd/path_pcep.h @@ -0,0 +1,326 @@ +/* + * 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 +#include +#include +#include +#include +#include "mpls.h" +#include "pathd/pathd.h" +#include "pathd/path_pcep_memory.h" + +#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[TCP_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; + +/* 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 0000000000..f80814f17c --- /dev/null +++ b/pathd/path_pcep_cli.c @@ -0,0 +1,2029 @@ +/* + * 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 +#include +#include + +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "version.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" +#include "termtable.h" + +#include "pathd/pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep_memory.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(); +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, + .config_write = pcep_cli_pcep_config_write, + .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, + .config_write = pcep_cli_pcc_config_write, + .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, + .config_write = pcep_cli_pce_config_write, + .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, + .config_write = pcep_cli_pcep_pce_config_write, + .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; + } + } + strncpy(pce_opts_cli->pce_opts.config_opts.tcp_md5_auth, + tcp_md5_auth_str, TCP_MD5SIG_MAXKEYLEN); + + 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; + tm_info = localtime(&group->start_time); + strftime(tm_buffer, sizeof(tm_buffer), "%Y-%m-%d %H:%M:%S", tm_info); + + vty_out(vty, "PCEP counters since %s (%luh %lum %lus):\n", tm_buffer, + diff_time / 3600, (diff_time / 60) % 60, 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; + } + + strncpy(pce_config->tcp_md5_auth, tcp_md5_auth, TCP_MD5SIG_MAXKEYLEN); + + 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(struct in6_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(struct pcc_opts)); + 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, " MultiPCE precedence %d, best candidate\n", + ((pcc_info->precedence > 0) ? pcc_info->precedence + : DEFAULT_PCE_PRECEDENCE)); + } else { + vty_out(vty, " MultiPCE precedence %d\n", + ((pcc_info->precedence > 0) ? pcc_info->precedence + : DEFAULT_PCE_PRECEDENCE)); + } + + /* 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 %ld seconds, since %d-%02d-%02d %02d:%02d:%02d UTC\n", + (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"); + 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) { + return lines; + } + + 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) { + csnprintfrr(buf, sizeof(buf), " %s %d", + PCEP_VTYSH_ARG_PRECEDENCE, + pce_opts->precedence); + } + vty_out(vty, "%s\n", buf); + lines++; + buf[0] = 0; + } + + 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'; + } + + 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; + } + + 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, + "[no] pce-config WORD$name", + NO_STR + "Shared configuration\n" + "Shared configuration name\n") +{ + if (no == NULL) + return path_pcep_cli_pcep_pce_config(vty, name); + 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 [$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, + "[no] pce WORD$name", + NO_STR + "PCE configuration, address sub-config is mandatory\n" + "PCE name\n") +{ + if (no == NULL) + return path_pcep_cli_pce(vty, name); + 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 [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, + "[no] pcc", + NO_STR + "PCC configuration\n") +{ + if (no != NULL) { + return path_pcep_cli_pcc_delete(vty); + } else { + return path_pcep_cli_pcc(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(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_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_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_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 0000000000..0b101ab215 --- /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 0000000000..989223ebc3 --- /dev/null +++ b/pathd/path_pcep_config.c @@ -0,0 +1,435 @@ +/* + * 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 +#include +#include +#include +#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 + + +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); + +static int path_pcep_config_lookup_cb(struct thread *t) +{ + struct path *path = THREAD_ARG(t); + struct srte_candidate *candidate = lookup_candidate(&path->nbkey); + struct srte_lsp *lsp; + + if (candidate == NULL) + return 0; + + 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; + + return 0; +} + +void path_pcep_config_lookup(struct path *path) +{ + /* + * Configuration access is strictly done via the main thread + */ + thread_execute(master, path_pcep_config_lookup_cb, path, 0); +} + +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 = 0, + .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: + 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_update_path(struct path *path) +{ + assert(path != NULL); + assert(path->nbkey.preference != 0); + assert(path->nbkey.endpoint.ipa_type == IPADDR_V4); + + 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; + + // first clean up old segment list if present + if (candidate->lsp->segment_list) { + SET_FLAG(candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_DELETED); + candidate->lsp->segment_list = NULL; + } + + if (path->first_hop != NULL) { + 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) + 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); + } + } + + 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, 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; + } + + 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) +{ + 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; + 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 0000000000..de29ab29c1 --- /dev/null +++ b/pathd/path_pcep_config.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_PCEP_CONFIG_H_ +#define _PATH_PCEP_CONFIG_H_ + +#include +#include + +#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 */ +void path_pcep_config_lookup(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_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 0000000000..e467a79a87 --- /dev/null +++ b/pathd/path_pcep_controller.c @@ -0,0 +1,1077 @@ +/* + * 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 + +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "version.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" + +#include "pathd/pathd.h" +#include "pathd/path_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 + +#define min(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a <= _b ? _a : _b; \ + }) + + +/* 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 +}; + +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; +}; + +/* Synchronous call arguments */ + +struct get_counters_args { + struct ctrl_state *ctrl_state; + int pcc_id; + struct counters_group *counters; +}; + +struct send_report_args { + struct ctrl_state *ctrl_state; + int pcc_id; + struct path *path; +}; + +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); + +/* Internal Functions Called From Controller Thread */ +static int pcep_thread_finish_event_handler(struct thread *thread); +static int pcep_thread_get_counters_callback(struct thread *t); +static int pcep_thread_send_report_callback(struct thread *t); +static int pcep_thread_get_pcep_session_callback(struct thread *t); +static int pcep_thread_get_pcc_info_callback(struct thread *t); + +/* 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 int 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 int 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); + +/* 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 int 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"); + 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 get_counters_args args = { + .ctrl_state = ctrl_state, .pcc_id = pcc_id, .counters = NULL}; + thread_execute(ctrl_state->self, pcep_thread_get_counters_callback, + &args, 0); + return args.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 get_pcep_session_args args = {.ctrl_state = ctrl_state, + .pcc_id = pcc_id, + .pcep_session = NULL}; + thread_execute(ctrl_state->self, pcep_thread_get_pcep_session_callback, + &args, 0); + return args.pcep_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 *args = XCALLOC(MTYPE_PCEP, sizeof(*args)); + args->ctrl_state = ctrl_state; + strncpy(args->pce_name, pce_name, sizeof(args->pce_name)); + thread_execute(ctrl_state->self, pcep_thread_get_pcc_info_callback, + args, 0); + + return args; +} + +void pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, + struct path *path) +{ + /* Sends a report stynchronously */ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + struct send_report_args args = { + .ctrl_state = ctrl_state, .pcc_id = pcc_id, .path = path}; + thread_execute(ctrl_state->self, pcep_thread_send_report_callback, + &args, 0); +} + +/* ------------ 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; +} + + +/* ------------ 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_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; +} + +/* ------------ Internal Functions Called From Controller Thread ------------ */ + +int 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); + return 0; +} + +int pcep_thread_get_counters_callback(struct thread *t) +{ + struct get_counters_args *args = THREAD_ARG(t); + assert(args != NULL); + struct ctrl_state *ctrl_state = args->ctrl_state; + assert(ctrl_state != NULL); + struct pcc_state *pcc_state; + + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, args->pcc_id); + if (pcc_state) { + args->counters = pcep_lib_copy_counters(pcc_state->sess); + } else { + args->counters = NULL; + } + + return 0; +} + +int pcep_thread_send_report_callback(struct thread *t) +{ + struct send_report_args *args = THREAD_ARG(t); + assert(args != NULL); + struct ctrl_state *ctrl_state = args->ctrl_state; + assert(ctrl_state != NULL); + struct pcc_state *pcc_state; + + if (args->pcc_id == 0) { + for (int i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + pcep_pcc_send_report(ctrl_state, + ctrl_state->pcc[i], + args->path); + } + } + } else { + pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, args->pcc_id); + pcep_pcc_send_report(ctrl_state, pcc_state, args->path); + } + + return 0; +} + +int pcep_thread_get_pcep_session_callback(struct thread *t) +{ + struct get_pcep_session_args *args = THREAD_ARG(t); + assert(args != NULL); + struct ctrl_state *ctrl_state = args->ctrl_state; + assert(ctrl_state != NULL); + struct pcc_state *pcc_state; + + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, args->pcc_id); + if (pcc_state) { + args->pcep_session = + pcep_lib_copy_pcep_session(pcc_state->sess); + } + + return 0; +} + +int pcep_thread_get_pcc_info_callback(struct thread *t) +{ + struct pcep_pcc_info *args = THREAD_ARG(t); + assert(args != NULL); + struct ctrl_state *ctrl_state = args->ctrl_state; + assert(ctrl_state != NULL); + + pcep_pcc_copy_pcc_info(ctrl_state->pcc, args); + + return 0; +} + +/* ------------ 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); +} + +int 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); + + int ret = 0; + 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 ret; + 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 ret; + 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 */ + ret = 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; + } + + return ret; +} + +int 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); + + return 0; +} + +/* ------------ 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; +} + +int 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); + + int ret = 0; + + /* Possible sub-type values */ + enum pcep_pathd_event_type path_event_type = PCEP_PATH_UNDEFINED; + + /* Possible payload values */ + struct path *path = NULL; + struct pcc_opts *pcc_opts = NULL; + struct pce_opts *pce_opts = NULL; + struct pcc_state *pcc_state = NULL; + + switch (type) { + case EV_UPDATE_PCC_OPTS: + assert(payload != NULL); + pcc_opts = (struct pcc_opts *)payload; + ret = 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; + ret = pcep_thread_event_update_pce_options(ctrl_state, pcc_id, + pce_opts); + break; + case EV_REMOVE_PCC: + pce_opts = (struct pce_opts *)payload; + ret = pcep_thread_event_remove_pcc(ctrl_state, pce_opts); + if (ret == 0) { + ret = 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; + ret = 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: + ret = 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); + ret = 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; + default: + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Unexpected event received in controller thread: %u", + type); + break; + } + + return ret; +} + +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; +} + +int 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); + + return 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 = rand(), 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 0000000000..8f25ccc1eb --- /dev/null +++ b/pathd/path_pcep_controller.h @@ -0,0 +1,165 @@ +/* + * 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" + + +enum pcep_main_event_type { + PCEP_MAIN_EVENT_UNDEFINED = 0, + PCEP_MAIN_EVENT_START_SYNC, + 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); + +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 int (*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; + 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); + +/* Synchronously send a report, the caller is responsible to free the path, + * If `pcc_id` is `0` the report is sent by all PCCs */ +void pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, + struct path *path); + +/* 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_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); +int pcep_thread_pcep_event(struct thread *thread); +int pcep_thread_pcc_count(struct ctrl_state *ctrl_state); + +#endif // _PATH_PCEP_CONTROLLER_H_ diff --git a/pathd/path_pcep_debug.c b/pathd/path_pcep_debug.c new file mode 100644 index 0000000000..bcaadfe4d8 --- /dev/null +++ b/pathd/path_pcep_debug.c @@ -0,0 +1,1771 @@ +/* + * 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 +#include +#include +#include + +#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_UNKOWN_MSG: + return "UNKOWN_MSG"; + 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_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"; + 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(); +} + +const char *format_yang_dnode(struct lyd_node *dnode) +{ + char *buff; + int len; + + lyd_print_mem(&buff, dnode, LYD_JSON, LYP_FORMAT); + len = strlen(buff); + memcpy(_debug_buff, buff, len); + free(buff); + return _debug_buff; +} + +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: \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: \n", ps2, "", + state->main); + } + if (state->self == NULL) { + PATHD_FORMAT("%*sself: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*sself: \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: %pI4(%u)/%pI4(%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: \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 0000000000..68b29ab657 --- /dev/null +++ b/pathd/path_pcep_debug.h @@ -0,0 +1,56 @@ +/* + * 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 +#include +#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); +const char *format_yang_dnode(struct lyd_node *dnode); + +#endif // _PATH_PCEP_DEBUG_H_ diff --git a/pathd/path_pcep_lib.c b/pathd/path_pcep_lib.c new file mode 100644 index 0000000000..fc72be8979 --- /dev/null +++ b/pathd/path_pcep_lib.c @@ -0,0 +1,1146 @@ +/* + * 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 +#include +#include +#include "pathd/path_errors.h" +#include "pathd/path_memory.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_lib.h" +#include "pathd/path_pcep_debug.h" +#include "pathd/path_pcep_memory.h" + +#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 + +/* 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 int pcep_lib_socket_read_ready(struct thread *thread); +static int 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_metric(struct path *path, + struct pcep_object_metric *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 = false; + 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 != NULL + && pcep_options->tcp_md5_auth[0] != '\0') { + config->is_tcp_auth_md5 = true; + strncpy(config->tcp_authentication_str, + pcep_options->tcp_md5_auth, TCP_MD5SIG_MAXKEYLEN); + } 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 + */ + +int pcep_lib_socket_write_ready(struct thread *thread) +{ + struct pcep_ctrl_socket_data *data = THREAD_ARG(thread); + assert(data != NULL); + + int retval = pceplib_external_socket_write(data->fd, data->payload); + XFREE(MTYPE_PCEP, data); + + return retval; +} + +int pcep_lib_socket_read_ready(struct thread *thread) +{ + struct pcep_ctrl_socket_data *data = THREAD_ARG(thread); + assert(data != NULL); + + int retval = pceplib_external_socket_read(data->fd, data->payload); + XFREE(MTYPE_PCEP, data); + + return retval; +} + +/* 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"); + 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) +{ + return pcep_msg_create_error(error_type, error_value); +} + +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; + + 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; + 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), 65505); + 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; + + /* 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; + + 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; + + 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) + return; + + for (node = tlvs->head; node != NULL; node = node->next_node) { + tlv = (struct pcep_object_tlv_header *)node->data; + switch (tlv->type) { + 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_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_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; + + 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 0000000000..3bea28432d --- /dev/null +++ b/pathd/path_pcep_lib.h @@ -0,0 +1,48 @@ +/* + * 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 +#include +#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 *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_memory.c b/pathd/path_pcep_memory.c new file mode 100644 index 0000000000..8f608090a6 --- /dev/null +++ b/pathd/path_pcep_memory.c @@ -0,0 +1,27 @@ +/* + * 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 + +#include + +#include "pathd/path_pcep_memory.h" + +DEFINE_MTYPE(PATHD, PCEP, "PCEP module") +DEFINE_MTYPE(PATHD, PCEPLIB_INFRA, "PCEPlib Infrastructure") +DEFINE_MTYPE(PATHD, PCEPLIB_MESSAGES, "PCEPlib PCEP Messages") diff --git a/pathd/path_pcep_memory.h b/pathd/path_pcep_memory.h new file mode 100644 index 0000000000..05c5e2d82b --- /dev/null +++ b/pathd/path_pcep_memory.h @@ -0,0 +1,28 @@ +/* + * 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_PCEP_MEMORY_H_ +#define _FRR_PATH_PCEP_MEMORY_H_ + +#include "pathd/path_memory.h" + +DECLARE_MTYPE(PCEP) +DECLARE_MTYPE(PCEPLIB_INFRA) +DECLARE_MTYPE(PCEPLIB_MESSAGES) + +#endif /* _FRR_PATH_PCEP_MEMORY_H_ */ diff --git a/pathd/path_pcep_pcc.c b/pathd/path_pcep_pcc.c new file mode 100644 index 0000000000..6bb5ce4bd1 --- /dev/null +++ b/pathd/path_pcep_pcc.c @@ -0,0 +1,1817 @@ +/* + * 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 + +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "version.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" + +#include "pathd/pathd.h" +#include "pathd/path_zebra.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep_memory.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_controller.h" +#include "pathd/path_pcep_lib.h" +#include "pathd/path_pcep_config.h" +#include "pathd/path_pcep_debug.h" + + +/* The number of time we will skip connecting if we are missing the PCC + * address for an inet family different from the selected transport one*/ +#define OTHER_FAMILY_MAX_RETRIES 4 +#define MAX_ERROR_MSG_SIZE 256 +#define MAX_COMPREQ_TRIES 3 + + +/* PCEP Event Handler */ +static void handle_pcep_open(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); +static void handle_pcep_message(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); +static void handle_pcep_lsp_update(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); +static void handle_pcep_lsp_initiate(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); +static void handle_pcep_comp_reply(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); + +/* Internal Functions */ +static const char *ipaddr_type_name(struct ipaddr *addr); +static bool filter_path(struct pcc_state *pcc_state, struct path *path); +static void select_pcc_addresses(struct pcc_state *pcc_state); +static void select_transport_address(struct pcc_state *pcc_state); +static void update_tag(struct pcc_state *pcc_state); +static void update_originator(struct pcc_state *pcc_state); +static void schedule_reconnect(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static void schedule_session_timeout(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static void cancel_session_timeout(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static void send_pcep_message(struct pcc_state *pcc_state, + struct pcep_message *msg); +static void send_pcep_error(struct pcc_state *pcc_state, + enum pcep_error_type error_type, + enum pcep_error_value error_value); +static void send_report(struct pcc_state *pcc_state, struct path *path); +static void send_comp_request(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct req_entry *req); +static void cancel_comp_requests(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static void cancel_comp_request(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct req_entry *req); +static void specialize_outgoing_path(struct pcc_state *pcc_state, + struct path *path); +static void specialize_incoming_path(struct pcc_state *pcc_state, + struct path *path); +static bool validate_incoming_path(struct pcc_state *pcc_state, + struct path *path, char *errbuff, + size_t buffsize); +static void set_pcc_address(struct pcc_state *pcc_state, + struct lsp_nb_key *nbkey, struct ipaddr *addr); +static int compare_pcc_opts(struct pcc_opts *lhs, struct pcc_opts *rhs); +static int compare_pce_opts(struct pce_opts *lhs, struct pce_opts *rhs); +static int get_previous_best_pce(struct pcc_state **pcc); +static int get_best_pce(struct pcc_state **pcc); +static int get_pce_count_connected(struct pcc_state **pcc); +static bool update_best_pce(struct pcc_state **pcc, int best); + +/* Data Structure Helper Functions */ +static void lookup_plspid(struct pcc_state *pcc_state, struct path *path); +static void lookup_nbkey(struct pcc_state *pcc_state, struct path *path); +static void free_req_entry(struct req_entry *req); +static struct req_entry *push_new_req(struct pcc_state *pcc_state, + struct path *path); +static void repush_req(struct pcc_state *pcc_state, struct req_entry *req); +static struct req_entry *pop_req(struct pcc_state *pcc_state, uint32_t reqid); +static bool add_reqid_mapping(struct pcc_state *pcc_state, struct path *path); +static void remove_reqid_mapping(struct pcc_state *pcc_state, + struct path *path); +static uint32_t lookup_reqid(struct pcc_state *pcc_state, struct path *path); +static bool has_pending_req_for(struct pcc_state *pcc_state, struct path *path); + +/* Data Structure Callbacks */ +static int plspid_map_cmp(const struct plspid_map_data *a, + const struct plspid_map_data *b); +static uint32_t plspid_map_hash(const struct plspid_map_data *e); +static int nbkey_map_cmp(const struct nbkey_map_data *a, + const struct nbkey_map_data *b); +static uint32_t nbkey_map_hash(const struct nbkey_map_data *e); +static int req_map_cmp(const struct req_map_data *a, + const struct req_map_data *b); +static uint32_t req_map_hash(const struct req_map_data *e); + +/* Data Structure Declarations */ +DECLARE_HASH(plspid_map, struct plspid_map_data, mi, plspid_map_cmp, + plspid_map_hash) +DECLARE_HASH(nbkey_map, struct nbkey_map_data, mi, nbkey_map_cmp, + nbkey_map_hash) +DECLARE_HASH(req_map, struct req_map_data, mi, req_map_cmp, req_map_hash) + +static inline int req_entry_compare(const struct req_entry *a, + const struct req_entry *b) +{ + return a->path->req_id - b->path->req_id; +} +RB_GENERATE(req_entry_head, req_entry, entry, req_entry_compare) + + +/* ------------ API Functions ------------ */ + +struct pcc_state *pcep_pcc_initialize(struct ctrl_state *ctrl_state, int index) +{ + struct pcc_state *pcc_state = XCALLOC(MTYPE_PCEP, sizeof(*pcc_state)); + + pcc_state->id = index; + pcc_state->status = PCEP_PCC_DISCONNECTED; + pcc_state->next_reqid = 1; + pcc_state->next_plspid = 1; + + RB_INIT(req_entry_head, &pcc_state->requests); + + update_tag(pcc_state); + update_originator(pcc_state); + + PCEP_DEBUG("%s PCC initialized", pcc_state->tag); + + return pcc_state; +} + +void pcep_pcc_finalize(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + PCEP_DEBUG("%s PCC finalizing...", pcc_state->tag); + + pcep_pcc_disable(ctrl_state, pcc_state); + + if (pcc_state->pcc_opts != NULL) { + XFREE(MTYPE_PCEP, pcc_state->pcc_opts); + pcc_state->pcc_opts = NULL; + } + if (pcc_state->pce_opts != NULL) { + XFREE(MTYPE_PCEP, pcc_state->pce_opts); + pcc_state->pce_opts = NULL; + } + if (pcc_state->originator != NULL) { + XFREE(MTYPE_PCEP, pcc_state->originator); + pcc_state->originator = NULL; + } + + if (pcc_state->t_reconnect != NULL) { + thread_cancel(&pcc_state->t_reconnect); + pcc_state->t_reconnect = NULL; + } + + if (pcc_state->t_update_best != NULL) { + thread_cancel(&pcc_state->t_update_best); + pcc_state->t_update_best = NULL; + } + + if (pcc_state->t_session_timeout != NULL) { + thread_cancel(&pcc_state->t_session_timeout); + pcc_state->t_session_timeout = NULL; + } + + XFREE(MTYPE_PCEP, pcc_state); +} + +int compare_pcc_opts(struct pcc_opts *lhs, struct pcc_opts *rhs) +{ + int retval; + + if (lhs == NULL) { + return 1; + } + + if (rhs == NULL) { + return -1; + } + + retval = lhs->port - rhs->port; + if (retval != 0) { + return retval; + } + + retval = lhs->msd - rhs->msd; + if (retval != 0) { + return retval; + } + + if (IS_IPADDR_V4(&lhs->addr)) { + retval = memcmp(&lhs->addr.ipaddr_v4, &rhs->addr.ipaddr_v4, + sizeof(lhs->addr.ipaddr_v4)); + if (retval != 0) { + return retval; + } + } else if (IS_IPADDR_V6(&lhs->addr)) { + retval = memcmp(&lhs->addr.ipaddr_v6, &rhs->addr.ipaddr_v6, + sizeof(lhs->addr.ipaddr_v6)); + if (retval != 0) { + return retval; + } + } + + return 0; +} + +int compare_pce_opts(struct pce_opts *lhs, struct pce_opts *rhs) +{ + if (lhs == NULL) { + return 1; + } + + if (rhs == NULL) { + return -1; + } + + int retval = lhs->port - rhs->port; + if (retval != 0) { + return retval; + } + + retval = strcmp(lhs->pce_name, rhs->pce_name); + if (retval != 0) { + return retval; + } + + retval = lhs->precedence - rhs->precedence; + if (retval != 0) { + return retval; + } + + retval = memcmp(&lhs->addr, &rhs->addr, sizeof(lhs->addr)); + if (retval != 0) { + return retval; + } + + return 0; +} + +int pcep_pcc_update(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, + struct pcc_opts *pcc_opts, struct pce_opts *pce_opts) +{ + int ret = 0; + + // If the options did not change, then there is nothing to do + if ((compare_pce_opts(pce_opts, pcc_state->pce_opts) == 0) + && (compare_pcc_opts(pcc_opts, pcc_state->pcc_opts) == 0)) { + return ret; + } + + if ((ret = pcep_pcc_disable(ctrl_state, pcc_state))) { + XFREE(MTYPE_PCEP, pcc_opts); + XFREE(MTYPE_PCEP, pce_opts); + return ret; + } + + if (pcc_state->pcc_opts != NULL) { + XFREE(MTYPE_PCEP, pcc_state->pcc_opts); + } + if (pcc_state->pce_opts != NULL) { + XFREE(MTYPE_PCEP, pcc_state->pce_opts); + } + + pcc_state->pcc_opts = pcc_opts; + pcc_state->pce_opts = pce_opts; + + if (IS_IPADDR_V4(&pcc_opts->addr)) { + pcc_state->pcc_addr_v4 = pcc_opts->addr.ipaddr_v4; + SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4); + } else { + UNSET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4); + } + + if (IS_IPADDR_V6(&pcc_opts->addr)) { + memcpy(&pcc_state->pcc_addr_v6, &pcc_opts->addr.ipaddr_v6, + sizeof(struct in6_addr)); + SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6); + } else { + UNSET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6); + } + + update_tag(pcc_state); + update_originator(pcc_state); + + return pcep_pcc_enable(ctrl_state, pcc_state); +} + +void pcep_pcc_reconnect(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + if (pcc_state->status == PCEP_PCC_DISCONNECTED) + pcep_pcc_enable(ctrl_state, pcc_state); +} + +int pcep_pcc_enable(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state) +{ + char pcc_buff[40]; + char pce_buff[40]; + + assert(pcc_state->status == PCEP_PCC_DISCONNECTED); + assert(pcc_state->sess == NULL); + + if (pcc_state->t_reconnect != NULL) { + thread_cancel(&pcc_state->t_reconnect); + pcc_state->t_reconnect = NULL; + } + + select_transport_address(pcc_state); + + /* Even though we are connecting using IPv6. we want to have an IPv4 + * address so we can handle candidate path with IPv4 endpoints */ + if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) { + if (pcc_state->retry_count < OTHER_FAMILY_MAX_RETRIES) { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "skipping connection to PCE %s:%d due to " + "missing PCC IPv4 address", + ipaddr2str(&pcc_state->pce_opts->addr, + pce_buff, sizeof(pce_buff)), + pcc_state->pce_opts->port); + schedule_reconnect(ctrl_state, pcc_state); + return 0; + } else { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "missing IPv4 PCC address, IPv4 candidate " + "paths will be ignored"); + } + } + + /* Even though we are connecting using IPv4. we want to have an IPv6 + * address so we can handle candidate path with IPv6 endpoints */ + if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) { + if (pcc_state->retry_count < OTHER_FAMILY_MAX_RETRIES) { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "skipping connection to PCE %s:%d due to " + "missing PCC IPv6 address", + ipaddr2str(&pcc_state->pce_opts->addr, + pce_buff, sizeof(pce_buff)), + pcc_state->pce_opts->port); + schedule_reconnect(ctrl_state, pcc_state); + return 0; + } else { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "missing IPv6 PCC address, IPv6 candidate " + "paths will be ignored"); + } + } + + /* Even if the maximum retries to try to have all the familly addresses + * have been spent, we still need the one for the transport familly */ + if (pcc_state->pcc_addr_tr.ipa_type == IPADDR_NONE) { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "skipping connection to PCE %s:%d due to missing " + "PCC address", + ipaddr2str(&pcc_state->pce_opts->addr, pce_buff, + sizeof(pce_buff)), + pcc_state->pce_opts->port); + schedule_reconnect(ctrl_state, pcc_state); + return 0; + } + + PCEP_DEBUG("%s PCC connecting", pcc_state->tag); + pcc_state->sess = pcep_lib_connect( + &pcc_state->pcc_addr_tr, pcc_state->pcc_opts->port, + &pcc_state->pce_opts->addr, pcc_state->pce_opts->port, + pcc_state->pcc_opts->msd, &pcc_state->pce_opts->config_opts); + + if (pcc_state->sess == NULL) { + flog_warn(EC_PATH_PCEP_LIB_CONNECT, + "failed to connect to PCE %s:%d from %s:%d", + ipaddr2str(&pcc_state->pce_opts->addr, pce_buff, + sizeof(pce_buff)), + pcc_state->pce_opts->port, + ipaddr2str(&pcc_state->pcc_addr_tr, pcc_buff, + sizeof(pcc_buff)), + pcc_state->pcc_opts->port); + schedule_reconnect(ctrl_state, pcc_state); + return 0; + } + + // In case some best pce alternative were waiting to activate + if (pcc_state->t_update_best != NULL) { + thread_cancel(&pcc_state->t_update_best); + pcc_state->t_update_best = NULL; + } + + pcc_state->status = PCEP_PCC_CONNECTING; + + return 0; +} + +int pcep_pcc_disable(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state) +{ + switch (pcc_state->status) { + case PCEP_PCC_DISCONNECTED: + return 0; + case PCEP_PCC_CONNECTING: + case PCEP_PCC_SYNCHRONIZING: + case PCEP_PCC_OPERATING: + PCEP_DEBUG("%s Disconnecting PCC...", pcc_state->tag); + cancel_comp_requests(ctrl_state, pcc_state); + pcep_lib_disconnect(pcc_state->sess); + /* No need to remove if any PCEs is connected */ + if (get_pce_count_connected(ctrl_state->pcc) == 0) { + pcep_thread_remove_candidate_path_segments(ctrl_state, + pcc_state); + } + pcc_state->sess = NULL; + pcc_state->status = PCEP_PCC_DISCONNECTED; + return 0; + default: + return 1; + } +} + +void pcep_pcc_sync_path(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path) +{ + if (pcc_state->status == PCEP_PCC_SYNCHRONIZING) { + path->is_synching = true; + } else if (pcc_state->status == PCEP_PCC_OPERATING) + path->is_synching = false; + else + return; + + path->go_active = true; + + /* Accumulate the dynamic paths without any LSP so computation + * requests can be performed after synchronization */ + if ((path->type == SRTE_CANDIDATE_TYPE_DYNAMIC) + && (path->first_hop == NULL) + && !has_pending_req_for(pcc_state, path)) { + PCEP_DEBUG("%s Scheduling computation request for path %s", + pcc_state->tag, path->name); + push_new_req(pcc_state, path); + return; + } + + /* Synchronize the path if the PCE supports LSP updates and the + * endpoint address familly is supported */ + if (pcc_state->caps.is_stateful) { + if (filter_path(pcc_state, path)) { + PCEP_DEBUG("%s Synchronizing path %s", pcc_state->tag, + path->name); + send_report(pcc_state, path); + } else { + PCEP_DEBUG( + "%s Skipping %s candidate path %s " + "synchronization", + pcc_state->tag, + ipaddr_type_name(&path->nbkey.endpoint), + path->name); + } + } +} + +void pcep_pcc_sync_done(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + struct req_entry *req; + + if (pcc_state->status != PCEP_PCC_SYNCHRONIZING + && pcc_state->status != PCEP_PCC_OPERATING) + return; + + if (pcc_state->caps.is_stateful + && pcc_state->status == PCEP_PCC_SYNCHRONIZING) { + struct path *path = pcep_new_path(); + *path = (struct path){.name = NULL, + .srp_id = 0, + .plsp_id = 0, + .status = PCEP_LSP_OPERATIONAL_DOWN, + .do_remove = false, + .go_active = false, + .was_created = false, + .was_removed = false, + .is_synching = false, + .is_delegated = false, + .first_hop = NULL, + .first_metric = NULL}; + send_report(pcc_state, path); + pcep_free_path(path); + } + + pcc_state->synchronized = true; + pcc_state->status = PCEP_PCC_OPERATING; + + PCEP_DEBUG("%s Synchronization done", pcc_state->tag); + + /* Start the computation request accumulated during synchronization */ + RB_FOREACH (req, req_entry_head, &pcc_state->requests) { + send_comp_request(ctrl_state, pcc_state, req); + } +} + +void pcep_pcc_send_report(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path) +{ + if (pcc_state->status != PCEP_PCC_OPERATING) + return; + + if (pcc_state->caps.is_stateful) { + PCEP_DEBUG("%s Send report for candidate path %s", + pcc_state->tag, path->name); + send_report(pcc_state, path); + } +} + +/* ------------ Timeout handler ------------ */ + +void pcep_pcc_timeout_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + enum pcep_ctrl_timer_type type, void *param) +{ + struct req_entry *req; + + switch (type) { + case TO_COMPUTATION_REQUEST: + assert(param != NULL); + req = (struct req_entry *)param; + pop_req(pcc_state, req->path->req_id); + flog_warn(EC_PATH_PCEP_COMPUTATION_REQUEST_TIMEOUT, + "Computation request %d timeout", req->path->req_id); + cancel_comp_request(ctrl_state, pcc_state, req); + if (req->retry_count++ < MAX_COMPREQ_TRIES) { + repush_req(pcc_state, req); + send_comp_request(ctrl_state, pcc_state, req); + return; + } + if (pcc_state->caps.is_stateful) { + struct path *path; + PCEP_DEBUG( + "%s Delegating undefined dynamic path %s to PCE %s", + pcc_state->tag, req->path->name, + pcc_state->originator); + path = pcep_copy_path(req->path); + path->is_delegated = true; + send_report(pcc_state, path); + free_req_entry(req); + } + break; + default: + break; + } +} + + +/* ------------ Pathd event handler ------------ */ + +void pcep_pcc_pathd_event_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + enum pcep_pathd_event_type type, + struct path *path) +{ + struct req_entry *req; + + if (pcc_state->status != PCEP_PCC_OPERATING) + return; + + /* Skipping candidate path with endpoint that do not match the + * configured or deduced PCC IP version */ + if (!filter_path(pcc_state, path)) { + PCEP_DEBUG("%s Skipping %s candidate path %s event", + pcc_state->tag, + ipaddr_type_name(&path->nbkey.endpoint), path->name); + return; + } + + switch (type) { + case PCEP_PATH_CREATED: + if (has_pending_req_for(pcc_state, path)) { + PCEP_DEBUG( + "%s Candidate path %s created, computation request already sent", + pcc_state->tag, path->name); + return; + } + PCEP_DEBUG("%s Candidate path %s created", pcc_state->tag, + path->name); + if ((path->first_hop == NULL) + && (path->type == SRTE_CANDIDATE_TYPE_DYNAMIC)) { + req = push_new_req(pcc_state, path); + send_comp_request(ctrl_state, pcc_state, req); + } else if (pcc_state->caps.is_stateful) + send_report(pcc_state, path); + return; + case PCEP_PATH_UPDATED: + PCEP_DEBUG("%s Candidate path %s updated", pcc_state->tag, + path->name); + if (pcc_state->caps.is_stateful) + send_report(pcc_state, path); + return; + case PCEP_PATH_REMOVED: + PCEP_DEBUG("%s Candidate path %s removed", pcc_state->tag, + path->name); + path->was_removed = true; + if (pcc_state->caps.is_stateful) + send_report(pcc_state, path); + return; + default: + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Unexpected pathd event received by pcc %s: %u", + pcc_state->tag, type); + return; + } +} + + +/* ------------ PCEP event handler ------------ */ + +void pcep_pcc_pcep_event_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, pcep_event *event) +{ + PCEP_DEBUG("%s Received PCEP event: %s", pcc_state->tag, + pcep_event_type_name(event->event_type)); + switch (event->event_type) { + case PCC_CONNECTED_TO_PCE: + assert(PCEP_PCC_CONNECTING == pcc_state->status); + PCEP_DEBUG("%s Connection established", pcc_state->tag); + pcc_state->status = PCEP_PCC_SYNCHRONIZING; + pcc_state->retry_count = 0; + pcc_state->synchronized = false; + PCEP_DEBUG("%s Starting PCE synchronization", pcc_state->tag); + cancel_session_timeout(ctrl_state, pcc_state); + pcep_pcc_calculate_best_pce(ctrl_state->pcc); + pcep_thread_start_sync(ctrl_state, pcc_state->id); + break; + case PCC_SENT_INVALID_OPEN: + PCEP_DEBUG("%s Sent invalid OPEN message", pcc_state->tag); + PCEP_DEBUG( + "%s Reconciling values: keep alive (%d) dead timer (%d) seconds ", + pcc_state->tag, + pcc_state->sess->pcc_config + .keep_alive_pce_negotiated_timer_seconds, + pcc_state->sess->pcc_config + .dead_timer_pce_negotiated_seconds); + pcc_state->pce_opts->config_opts.keep_alive_seconds = + pcc_state->sess->pcc_config + .keep_alive_pce_negotiated_timer_seconds; + pcc_state->pce_opts->config_opts.dead_timer_seconds = + pcc_state->sess->pcc_config + .dead_timer_pce_negotiated_seconds; + break; + + case PCC_RCVD_INVALID_OPEN: + PCEP_DEBUG("%s Received invalid OPEN message", pcc_state->tag); + PCEP_DEBUG_PCEP("%s PCEP message: %s", pcc_state->tag, + format_pcep_message(event->message)); + break; + case PCE_DEAD_TIMER_EXPIRED: + case PCE_CLOSED_SOCKET: + case PCE_SENT_PCEP_CLOSE: + case PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED: + case PCC_PCEP_SESSION_CLOSED: + case PCC_RCVD_MAX_INVALID_MSGS: + case PCC_RCVD_MAX_UNKOWN_MSGS: + pcep_pcc_disable(ctrl_state, pcc_state); + schedule_reconnect(ctrl_state, pcc_state); + schedule_session_timeout(ctrl_state, pcc_state); + break; + case MESSAGE_RECEIVED: + PCEP_DEBUG_PCEP("%s Received PCEP message: %s", pcc_state->tag, + format_pcep_message(event->message)); + if (pcc_state->status == PCEP_PCC_CONNECTING) { + if (event->message->msg_header->type == PCEP_TYPE_OPEN) + handle_pcep_open(ctrl_state, pcc_state, + event->message); + break; + } + assert(pcc_state->status == PCEP_PCC_SYNCHRONIZING + || pcc_state->status == PCEP_PCC_OPERATING); + handle_pcep_message(ctrl_state, pcc_state, event->message); + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEPLIB_EVENT, + "Unexpected event from pceplib: %s", + format_pcep_event(event)); + break; + } +} + + +/*------------------ Multi-PCE --------------------- */ + +/* Internal util function, returns true if sync is necessary, false otherwise */ +bool update_best_pce(struct pcc_state **pcc, int best) +{ + PCEP_DEBUG(" recalculating pce precedence "); + if (best) { + struct pcc_state *best_pcc_state = + pcep_pcc_get_pcc_by_id(pcc, best); + if (best_pcc_state->previous_best != best_pcc_state->is_best) { + PCEP_DEBUG(" %s Resynch best (%i) previous best (%i)", + best_pcc_state->tag, best_pcc_state->id, + best_pcc_state->previous_best); + return true; + } else { + PCEP_DEBUG( + " %s No Resynch best (%i) previous best (%i)", + best_pcc_state->tag, best_pcc_state->id, + best_pcc_state->previous_best); + } + } else { + PCEP_DEBUG(" No best pce available, all pce seem disconnected"); + } + + return false; +} + +int get_best_pce(struct pcc_state **pcc) +{ + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts) { + if (pcc[i]->is_best == true) { + return pcc[i]->id; + } + } + } + return 0; +} + +int get_pce_count_connected(struct pcc_state **pcc) +{ + int count = 0; + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts + && pcc[i]->status != PCEP_PCC_DISCONNECTED) { + count++; + } + } + return count; +} + +int get_previous_best_pce(struct pcc_state **pcc) +{ + int previous_best_pce = -1; + + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts && pcc[i]->previous_best == true + && pcc[i]->status != PCEP_PCC_DISCONNECTED) { + previous_best_pce = i; + break; + } + } + return previous_best_pce != -1 ? pcc[previous_best_pce]->id : 0; +} + +/* Called by path_pcep_controller EV_REMOVE_PCC + * Event handler when a PCC is removed. */ +int pcep_pcc_multi_pce_remove_pcc(struct ctrl_state *ctrl_state, + struct pcc_state **pcc) +{ + int new_best_pcc_id = -1; + new_best_pcc_id = pcep_pcc_calculate_best_pce(pcc); + if (new_best_pcc_id) { + if (update_best_pce(ctrl_state->pcc, new_best_pcc_id) == true) { + pcep_thread_start_sync(ctrl_state, new_best_pcc_id); + } + } + + return 0; +} + +/* Called by path_pcep_controller EV_SYNC_PATH + * Event handler when a path is sync'd. */ +int pcep_pcc_multi_pce_sync_path(struct ctrl_state *ctrl_state, int pcc_id, + struct pcc_state **pcc) +{ + int previous_best_pcc_id = -1; + + if (pcc_id == get_best_pce(pcc)) { + previous_best_pcc_id = get_previous_best_pce(pcc); + if (previous_best_pcc_id != 0) { + /* while adding new pce, path has to resync to the + * previous best. pcep_thread_start_sync() will be + * called by the calling function */ + if (update_best_pce(ctrl_state->pcc, + previous_best_pcc_id) + == true) { + cancel_comp_requests( + ctrl_state, + pcep_pcc_get_pcc_by_id( + pcc, previous_best_pcc_id)); + pcep_thread_start_sync(ctrl_state, + previous_best_pcc_id); + } + } + } + + return 0; +} + +/* Called by path_pcep_controller when the TM_CALCULATE_BEST_PCE + * timer expires */ +int pcep_pcc_timer_update_best_pce(struct ctrl_state *ctrl_state, int pcc_id) +{ + int ret = 0; + /* resync whatever was the new best */ + int prev_best = get_best_pce(ctrl_state->pcc); + int best_id = pcep_pcc_calculate_best_pce(ctrl_state->pcc); + if (best_id && prev_best != best_id) { // Avoid Multiple call + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, best_id); + if (update_best_pce(ctrl_state->pcc, pcc_state->id) == true) { + pcep_thread_start_sync(ctrl_state, pcc_state->id); + } + } + + return ret; +} + +/* Called by path_pcep_controller::pcep_thread_event_update_pce_options() + * Returns the best PCE id */ +int pcep_pcc_calculate_best_pce(struct pcc_state **pcc) +{ + int best_precedence = 255; // DEFAULT_PCE_PRECEDENCE; + int best_pce = -1; + int one_connected_pce = -1; + int previous_best_pce = -1; + int step_0_best = -1; + int step_0_previous = -1; + int pcc_count = 0; + + // Get state + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts) { + zlog_debug( + "multi-pce: calculate all : i (%i) is_best (%i) previous_best (%i) ", + i, pcc[i]->is_best, pcc[i]->previous_best); + pcc_count++; + + if (pcc[i]->is_best == true) { + step_0_best = i; + } + if (pcc[i]->previous_best == true) { + step_0_previous = i; + } + } + } + + if (!pcc_count) { + return 0; + } + + // Calculate best + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts + && pcc[i]->status != PCEP_PCC_DISCONNECTED) { + one_connected_pce = i; // In case none better + if (pcc[i]->pce_opts->precedence <= best_precedence) { + if (best_pce != -1 + && pcc[best_pce]->pce_opts->precedence + == pcc[i]->pce_opts + ->precedence) { + if (ipaddr_cmp( + &pcc[i]->pce_opts->addr, + &pcc[best_pce] + ->pce_opts->addr) + > 0) + // collide of precedences so + // compare ip + best_pce = i; + } else { + if (!pcc[i]->previous_best) { + best_precedence = + pcc[i]->pce_opts + ->precedence; + best_pce = i; + } + } + } + } + } + + zlog_debug( + "multi-pce: calculate data : sb (%i) sp (%i) oc (%i) b (%i) ", + step_0_best, step_0_previous, one_connected_pce, best_pce); + + // Changed of state so ... + if (step_0_best != best_pce) { + // Calculate previous + previous_best_pce = step_0_best; + // Clean state + if (step_0_best != -1) { + pcc[step_0_best]->is_best = false; + } + if (step_0_previous != -1) { + pcc[step_0_previous]->previous_best = false; + } + + // Set previous + if (previous_best_pce != -1 + && pcc[previous_best_pce]->status + == PCEP_PCC_DISCONNECTED) { + pcc[previous_best_pce]->previous_best = true; + zlog_debug("multi-pce: previous best pce (%i) ", + previous_best_pce + 1); + } + + + // Set best + if (best_pce != -1) { + pcc[best_pce]->is_best = true; + zlog_debug("multi-pce: best pce (%i) ", best_pce + 1); + } else { + if (one_connected_pce != -1) { + best_pce = one_connected_pce; + pcc[one_connected_pce]->is_best = true; + zlog_debug( + "multi-pce: one connected best pce (default) (%i) ", + one_connected_pce + 1); + } else { + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts) { + best_pce = i; + pcc[i]->is_best = true; + zlog_debug( + "(disconnected) best pce (default) (%i) ", + i + 1); + break; + } + } + } + } + } + + return ((best_pce == -1) ? 0 : pcc[best_pce]->id); +} + +int pcep_pcc_get_pcc_id_by_ip_port(struct pcc_state **pcc, + struct pce_opts *pce_opts) +{ + if (pcc == NULL) { + return 0; + } + + for (int idx = 0; idx < MAX_PCC; idx++) { + if (pcc[idx]) { + if ((ipaddr_cmp((const struct ipaddr *)&pcc[idx] + ->pce_opts->addr, + (const struct ipaddr *)&pce_opts->addr) + == 0) + && pcc[idx]->pce_opts->port == pce_opts->port) { + zlog_debug("found pcc_id (%d) idx (%d)", + pcc[idx]->id, idx); + return pcc[idx]->id; + } + } + } + return 0; +} + +int pcep_pcc_get_pcc_id_by_idx(struct pcc_state **pcc, int idx) +{ + if (pcc == NULL || idx < 0) { + return 0; + } + + return pcc[idx] ? pcc[idx]->id : 0; +} + +struct pcc_state *pcep_pcc_get_pcc_by_id(struct pcc_state **pcc, int id) +{ + if (pcc == NULL || id < 0) { + return NULL; + } + + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i]) { + if (pcc[i]->id == id) { + zlog_debug("found id (%d) pcc_idx (%d)", + pcc[i]->id, i); + return pcc[i]; + } + } + } + + return NULL; +} + +struct pcc_state *pcep_pcc_get_pcc_by_name(struct pcc_state **pcc, + const char *pce_name) +{ + if (pcc == NULL || pce_name == NULL) { + return NULL; + } + + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] == NULL) { + continue; + } + + if (strcmp(pcc[i]->pce_opts->pce_name, pce_name) == 0) { + return pcc[i]; + } + } + + return NULL; +} + +int pcep_pcc_get_pcc_idx_by_id(struct pcc_state **pcc, int id) +{ + if (pcc == NULL) { + return -1; + } + + for (int idx = 0; idx < MAX_PCC; idx++) { + if (pcc[idx]) { + if (pcc[idx]->id == id) { + zlog_debug("found pcc_id (%d) array_idx (%d)", + pcc[idx]->id, idx); + return idx; + } + } + } + + return -1; +} + +int pcep_pcc_get_free_pcc_idx(struct pcc_state **pcc) +{ + assert(pcc != NULL); + + for (int idx = 0; idx < MAX_PCC; idx++) { + if (pcc[idx] == NULL) { + zlog_debug("new pcc_idx (%d)", idx); + return idx; + } + } + + return -1; +} + +int pcep_pcc_get_pcc_id(struct pcc_state *pcc) +{ + return ((pcc == NULL) ? 0 : pcc->id); +} + +void pcep_pcc_copy_pcc_info(struct pcc_state **pcc, + struct pcep_pcc_info *pcc_info) +{ + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_name(pcc, pcc_info->pce_name); + if (!pcc_state) { + return; + } + + pcc_info->ctrl_state = NULL; + pcc_info->msd = pcc_state->pcc_opts->msd; + pcc_info->pcc_port = pcc_state->pcc_opts->port; + pcc_info->next_plspid = pcc_state->next_plspid; + pcc_info->next_reqid = pcc_state->next_reqid; + pcc_info->status = pcc_state->status; + pcc_info->pcc_id = pcc_state->id; + pcc_info->is_best_multi_pce = pcc_state->is_best; + pcc_info->precedence = + pcc_state->pce_opts ? pcc_state->pce_opts->precedence : 0; + memcpy(&pcc_info->pcc_addr, &pcc_state->pcc_addr_tr, + sizeof(struct ipaddr)); +} + + +/*------------------ PCEP Message handlers --------------------- */ + +void handle_pcep_open(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct pcep_message *msg) +{ + assert(msg->msg_header->type == PCEP_TYPE_OPEN); + pcep_lib_parse_capabilities(msg, &pcc_state->caps); + PCEP_DEBUG("PCE capabilities: %s, %s%s", + pcc_state->caps.is_stateful ? "stateful" : "stateless", + pcc_state->caps.supported_ofs_are_known + ? (pcc_state->caps.supported_ofs == 0 + ? "no objective functions supported" + : "supported objective functions are ") + : "supported objective functions are unknown", + format_objfun_set(pcc_state->caps.supported_ofs)); +} + +void handle_pcep_message(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct pcep_message *msg) +{ + if (pcc_state->status != PCEP_PCC_OPERATING) + return; + + switch (msg->msg_header->type) { + case PCEP_TYPE_INITIATE: + handle_pcep_lsp_initiate(ctrl_state, pcc_state, msg); + break; + case PCEP_TYPE_UPDATE: + handle_pcep_lsp_update(ctrl_state, pcc_state, msg); + break; + case PCEP_TYPE_PCREP: + handle_pcep_comp_reply(ctrl_state, pcc_state, msg); + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_MESSAGE, + "Unexpected pcep message from pceplib: %s", + format_pcep_message(msg)); + break; + } +} + +void handle_pcep_lsp_update(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg) +{ + char err[MAX_ERROR_MSG_SIZE] = ""; + struct path *path; + path = pcep_lib_parse_path(msg); + lookup_nbkey(pcc_state, path); + /* TODO: Investigate if this is safe to do in the controller thread */ + path_pcep_config_lookup(path); + specialize_incoming_path(pcc_state, path); + PCEP_DEBUG("%s Received LSP update", pcc_state->tag); + PCEP_DEBUG_PATH("%s", format_path(path)); + + if (validate_incoming_path(pcc_state, path, err, sizeof(err))) + pcep_thread_update_path(ctrl_state, pcc_state->id, path); + else { + /* FIXME: Monitor the amount of errors from the PCE and + * possibly disconnect and blacklist */ + flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "Unsupported PCEP protocol feature: %s", err); + pcep_free_path(path); + } +} + +void handle_pcep_lsp_initiate(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg) +{ + PCEP_DEBUG("%s Received LSP initiate, not supported yet", + pcc_state->tag); + + /* TODO when we support both PCC and PCE initiated sessions, + * we should first check the session type before + * rejecting this message. */ + send_pcep_error(pcc_state, PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_NOT_PCE_INITIATED); +} + +void handle_pcep_comp_reply(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg) +{ + char err[MAX_ERROR_MSG_SIZE] = ""; + struct req_entry *req; + struct path *path; + + path = pcep_lib_parse_path(msg); + req = pop_req(pcc_state, path->req_id); + if (req == NULL) { + /* TODO: check the rate of bad computation reply and close + * the connection if more that a given rate. + */ + PCEP_DEBUG( + "%s Received computation reply for unknown request " + "%d", + pcc_state->tag, path->req_id); + PCEP_DEBUG_PATH("%s", format_path(path)); + send_pcep_error(pcc_state, PCEP_ERRT_UNKNOWN_REQ_REF, + PCEP_ERRV_UNASSIGNED); + return; + } + + /* Cancel the computation request timeout */ + pcep_thread_cancel_timer(&req->t_retry); + + /* Transfer relevent metadata from the request to the response */ + path->nbkey = req->path->nbkey; + path->plsp_id = req->path->plsp_id; + path->type = req->path->type; + path->name = XSTRDUP(MTYPE_PCEP, req->path->name); + specialize_incoming_path(pcc_state, path); + + PCEP_DEBUG("%s Received computation reply %d (no-path: %s)", + pcc_state->tag, path->req_id, + path->no_path ? "true" : "false"); + PCEP_DEBUG_PATH("%s", format_path(path)); + + if (path->no_path) { + PCEP_DEBUG("%s Computation for path %s did not find any result", + pcc_state->tag, path->name); + } else if (validate_incoming_path(pcc_state, path, err, sizeof(err))) { + /* Updating a dynamic path will automatically delegate it */ + pcep_thread_update_path(ctrl_state, pcc_state->id, path); + free_req_entry(req); + return; + } else { + /* FIXME: Monitor the amount of errors from the PCE and + * possibly disconnect and blacklist */ + flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "Unsupported PCEP protocol feature: %s", err); + } + + pcep_free_path(path); + + /* Delegate the path regardless of the outcome */ + /* TODO: For now we are using the path from the request, when + * pathd API is thread safe, we could get a new path */ + if (pcc_state->caps.is_stateful) { + PCEP_DEBUG("%s Delegating undefined dynamic path %s to PCE %s", + pcc_state->tag, path->name, pcc_state->originator); + path = pcep_copy_path(req->path); + path->is_delegated = true; + send_report(pcc_state, path); + pcep_free_path(path); + } + + free_req_entry(req); +} + + +/* ------------ Internal Functions ------------ */ + +const char *ipaddr_type_name(struct ipaddr *addr) +{ + if (IS_IPADDR_V4(addr)) + return "IPv4"; + if (IS_IPADDR_V6(addr)) + return "IPv6"; + return "undefined"; +} + +bool filter_path(struct pcc_state *pcc_state, struct path *path) +{ + return (IS_IPADDR_V4(&path->nbkey.endpoint) + && CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) + || (IS_IPADDR_V6(&path->nbkey.endpoint) + && CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)); +} + +void select_pcc_addresses(struct pcc_state *pcc_state) +{ + /* If no IPv4 address was specified, try to get one from zebra */ + if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) { + if (get_ipv4_router_id(&pcc_state->pcc_addr_v4)) { + SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4); + } + } + + /* If no IPv6 address was specified, try to get one from zebra */ + if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) { + if (get_ipv6_router_id(&pcc_state->pcc_addr_v6)) { + SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6); + } + } +} + +void select_transport_address(struct pcc_state *pcc_state) +{ + struct ipaddr *taddr = &pcc_state->pcc_addr_tr; + + select_pcc_addresses(pcc_state); + + taddr->ipa_type = IPADDR_NONE; + + /* Select a transport source address in function of the configured PCE + * address */ + if (IS_IPADDR_V4(&pcc_state->pce_opts->addr)) { + if (CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) { + taddr->ipa_type = IPADDR_V4; + taddr->ipaddr_v4 = pcc_state->pcc_addr_v4; + } + } else { + if (CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) { + taddr->ipa_type = IPADDR_V6; + taddr->ipaddr_v6 = pcc_state->pcc_addr_v6; + } + } +} + +void update_tag(struct pcc_state *pcc_state) +{ + if (pcc_state->pce_opts != NULL) { + assert(!IS_IPADDR_NONE(&pcc_state->pce_opts->addr)); + if (IS_IPADDR_V6(&pcc_state->pce_opts->addr)) { + snprintfrr(pcc_state->tag, sizeof(pcc_state->tag), + "%pI6:%i (%u)", + &pcc_state->pce_opts->addr.ipaddr_v6, + pcc_state->pce_opts->port, pcc_state->id); + } else { + snprintfrr(pcc_state->tag, sizeof(pcc_state->tag), + "%pI4:%i (%u)", + &pcc_state->pce_opts->addr.ipaddr_v4, + pcc_state->pce_opts->port, pcc_state->id); + } + } else { + snprintfrr(pcc_state->tag, sizeof(pcc_state->tag), "(%u)", + pcc_state->id); + } +} + +void update_originator(struct pcc_state *pcc_state) +{ + char *originator; + if (pcc_state->originator != NULL) { + XFREE(MTYPE_PCEP, pcc_state->originator); + pcc_state->originator = NULL; + } + if (pcc_state->pce_opts == NULL) + return; + originator = XCALLOC(MTYPE_PCEP, 52); + assert(!IS_IPADDR_NONE(&pcc_state->pce_opts->addr)); + if (IS_IPADDR_V6(&pcc_state->pce_opts->addr)) { + snprintfrr(originator, 52, "%pI6:%i", + &pcc_state->pce_opts->addr.ipaddr_v6, + pcc_state->pce_opts->port); + } else { + snprintfrr(originator, 52, "%pI4:%i", + &pcc_state->pce_opts->addr.ipaddr_v4, + pcc_state->pce_opts->port); + } + pcc_state->originator = originator; +} + +void schedule_reconnect(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + pcc_state->retry_count++; + pcep_thread_schedule_reconnect(ctrl_state, pcc_state->id, + pcc_state->retry_count, + &pcc_state->t_reconnect); + if (pcc_state->retry_count == 1) { + pcep_thread_schedule_sync_best_pce( + ctrl_state, pcc_state->id, + pcc_state->pce_opts->config_opts + .delegation_timeout_seconds, + &pcc_state->t_update_best); + } +} + +void schedule_session_timeout(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + /* No need to schedule timeout if multiple PCEs are connected */ + if (get_pce_count_connected(ctrl_state->pcc)) { + PCEP_DEBUG_PCEP( + "schedule_session_timeout not setting timer for multi-pce mode"); + + return; + } + + pcep_thread_schedule_session_timeout( + ctrl_state, pcep_pcc_get_pcc_id(pcc_state), + pcc_state->pce_opts->config_opts + .session_timeout_inteval_seconds, + &pcc_state->t_session_timeout); +} + +void cancel_session_timeout(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + /* No need to schedule timeout if multiple PCEs are connected */ + if (pcc_state->t_session_timeout == NULL) { + PCEP_DEBUG_PCEP("cancel_session_timeout timer thread NULL"); + return; + } + + PCEP_DEBUG_PCEP("Cancel session_timeout timer"); + pcep_thread_cancel_timer(&pcc_state->t_session_timeout); + pcc_state->t_session_timeout = NULL; +} + +void send_pcep_message(struct pcc_state *pcc_state, struct pcep_message *msg) +{ + if (pcc_state->sess != NULL) { + PCEP_DEBUG_PCEP("%s Sending PCEP message: %s", pcc_state->tag, + format_pcep_message(msg)); + send_message(pcc_state->sess, msg, true); + } +} + +void send_pcep_error(struct pcc_state *pcc_state, + enum pcep_error_type error_type, + enum pcep_error_value error_value) +{ + struct pcep_message *msg; + PCEP_DEBUG("%s Sending PCEP error type %s (%d) value %s (%d)", + pcc_state->tag, pcep_error_type_name(error_type), error_type, + pcep_error_value_name(error_type, error_value), error_value); + msg = pcep_lib_format_error(error_type, error_value); + send_pcep_message(pcc_state, msg); +} + +void send_report(struct pcc_state *pcc_state, struct path *path) +{ + struct pcep_message *report; + + path->req_id = 0; + specialize_outgoing_path(pcc_state, path); + PCEP_DEBUG_PATH("%s Sending path %s: %s", pcc_state->tag, path->name, + format_path(path)); + report = pcep_lib_format_report(&pcc_state->caps, path); + send_pcep_message(pcc_state, report); +} + +/* Updates the path for the PCE, updating the delegation and creation flags */ +void specialize_outgoing_path(struct pcc_state *pcc_state, struct path *path) +{ + bool is_delegated = false; + bool was_created = false; + + lookup_plspid(pcc_state, path); + + set_pcc_address(pcc_state, &path->nbkey, &path->pcc_addr); + path->sender = pcc_state->pcc_addr_tr; + + /* TODO: When the pathd API have a way to mark a path as + * delegated, use it instead of considering all dynamic path + * delegated. We need to disable the originator check for now, + * because path could be delegated without having any originator yet */ + // if ((path->originator == NULL) + // || (strcmp(path->originator, pcc_state->originator) == 0)) { + // is_delegated = (path->type == SRTE_CANDIDATE_TYPE_DYNAMIC) + // && (path->first_hop != NULL); + // /* it seems the PCE consider updating an LSP a creation ?!? + // at least Cisco does... */ + // was_created = path->update_origin == SRTE_ORIGIN_PCEP; + // } + is_delegated = (path->type == SRTE_CANDIDATE_TYPE_DYNAMIC); + was_created = path->update_origin == SRTE_ORIGIN_PCEP; + + path->pcc_id = pcc_state->id; + path->go_active = is_delegated && pcc_state->is_best; + path->is_delegated = is_delegated && pcc_state->is_best; + path->was_created = was_created; +} + +/* Updates the path for the PCC */ +void specialize_incoming_path(struct pcc_state *pcc_state, struct path *path) +{ + set_pcc_address(pcc_state, &path->nbkey, &path->pcc_addr); + path->sender = pcc_state->pce_opts->addr; + path->pcc_id = pcc_state->id; + path->update_origin = SRTE_ORIGIN_PCEP; + path->originator = XSTRDUP(MTYPE_PCEP, pcc_state->originator); +} + +/* Ensure the path can be handled by the PCC and if not, sends an error */ +bool validate_incoming_path(struct pcc_state *pcc_state, struct path *path, + char *errbuff, size_t buffsize) +{ + struct path_hop *hop; + enum pcep_error_type err_type = 0; + enum pcep_error_value err_value = PCEP_ERRV_UNASSIGNED; + + for (hop = path->first_hop; hop != NULL; hop = hop->next) { + /* Hops without SID are not supported */ + if (!hop->has_sid) { + snprintfrr(errbuff, buffsize, "SR segment without SID"); + err_type = PCEP_ERRT_RECEPTION_OF_INV_OBJECT; + err_value = PCEP_ERRV_DISJOINTED_CONF_TLV_MISSING; + break; + } + /* Hops with non-MPLS SID are not supported */ + if (!hop->is_mpls) { + snprintfrr(errbuff, buffsize, + "SR segment with non-MPLS SID"); + err_type = PCEP_ERRT_RECEPTION_OF_INV_OBJECT; + err_value = PCEP_ERRV_UNSUPPORTED_NAI; + break; + } + } + + if (err_type != 0) { + send_pcep_error(pcc_state, err_type, err_value); + return false; + } + + return true; +} + +void send_comp_request(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct req_entry *req) +{ + assert(req != NULL); + + if (req->t_retry) + return; + + assert(req->path != NULL); + assert(req->path->req_id > 0); + assert(RB_FIND(req_entry_head, &pcc_state->requests, req) == req); + assert(lookup_reqid(pcc_state, req->path) == req->path->req_id); + + int timeout; + char buff[40]; + struct pcep_message *msg; + + if (!pcc_state->is_best) { + return; + } + /* TODO: Add a timer to retry the computation request ? */ + + specialize_outgoing_path(pcc_state, req->path); + + PCEP_DEBUG( + "%s Sending computation request %d for path %s to %s (retry %d)", + pcc_state->tag, req->path->req_id, req->path->name, + ipaddr2str(&req->path->nbkey.endpoint, buff, sizeof(buff)), + req->retry_count); + PCEP_DEBUG_PATH("%s Computation request path %s: %s", pcc_state->tag, + req->path->name, format_path(req->path)); + + msg = pcep_lib_format_request(&pcc_state->caps, req->path); + send_pcep_message(pcc_state, msg); + req->was_sent = true; + + /* TODO: Enable this back when the pcep config changes are merged back + */ + // timeout = pcc_state->pce_opts->config_opts.pcep_request_time_seconds; + timeout = 30; + pcep_thread_schedule_timeout(ctrl_state, pcc_state->id, + TO_COMPUTATION_REQUEST, timeout, + (void *)req, &req->t_retry); +} + +void cancel_comp_requests(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + struct req_entry *req, *safe_req; + + RB_FOREACH_SAFE (req, req_entry_head, &pcc_state->requests, safe_req) { + cancel_comp_request(ctrl_state, pcc_state, req); + RB_REMOVE(req_entry_head, &pcc_state->requests, req); + remove_reqid_mapping(pcc_state, req->path); + free_req_entry(req); + } +} + +void cancel_comp_request(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct req_entry *req) +{ + char buff[40]; + struct pcep_message *msg; + + if (req->was_sent) { + /* TODO: Send a computation request cancelation + * notification to the PCE */ + pcep_thread_cancel_timer(&req->t_retry); + } + + PCEP_DEBUG( + "%s Canceling computation request %d for path %s to %s (retry %d)", + pcc_state->tag, req->path->req_id, req->path->name, + ipaddr2str(&req->path->nbkey.endpoint, buff, sizeof(buff)), + req->retry_count); + PCEP_DEBUG_PATH("%s Canceled computation request path %s: %s", + pcc_state->tag, req->path->name, + format_path(req->path)); + + msg = pcep_lib_format_request_cancelled(req->path->req_id); + send_pcep_message(pcc_state, msg); +} + +void set_pcc_address(struct pcc_state *pcc_state, struct lsp_nb_key *nbkey, + struct ipaddr *addr) +{ + select_pcc_addresses(pcc_state); + if (IS_IPADDR_V6(&nbkey->endpoint)) { + assert(CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)); + addr->ipa_type = IPADDR_V6; + addr->ipaddr_v6 = pcc_state->pcc_addr_v6; + } else if (IS_IPADDR_V4(&nbkey->endpoint)) { + assert(CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)); + addr->ipa_type = IPADDR_V4; + addr->ipaddr_v4 = pcc_state->pcc_addr_v4; + } else { + addr->ipa_type = IPADDR_NONE; + } +} + + +/* ------------ Data Structure Helper Functions ------------ */ + +void lookup_plspid(struct pcc_state *pcc_state, struct path *path) +{ + struct plspid_map_data key, *plspid_mapping; + struct nbkey_map_data *nbkey_mapping; + + if (path->nbkey.color != 0) { + key.nbkey = path->nbkey; + plspid_mapping = plspid_map_find(&pcc_state->plspid_map, &key); + if (plspid_mapping == NULL) { + plspid_mapping = + XCALLOC(MTYPE_PCEP, sizeof(*plspid_mapping)); + plspid_mapping->nbkey = key.nbkey; + plspid_mapping->plspid = pcc_state->next_plspid; + plspid_map_add(&pcc_state->plspid_map, plspid_mapping); + nbkey_mapping = + XCALLOC(MTYPE_PCEP, sizeof(*nbkey_mapping)); + nbkey_mapping->nbkey = key.nbkey; + nbkey_mapping->plspid = pcc_state->next_plspid; + nbkey_map_add(&pcc_state->nbkey_map, nbkey_mapping); + pcc_state->next_plspid++; + // FIXME: Send some error to the PCE isntead of crashing + assert(pcc_state->next_plspid <= 1048576); + } + path->plsp_id = plspid_mapping->plspid; + } +} + +void lookup_nbkey(struct pcc_state *pcc_state, struct path *path) +{ + struct nbkey_map_data key, *mapping; + // TODO: Should give an error to the PCE instead of crashing + assert(path->plsp_id != 0); + key.plspid = path->plsp_id; + mapping = nbkey_map_find(&pcc_state->nbkey_map, &key); + assert(mapping != NULL); + path->nbkey = mapping->nbkey; +} + +void free_req_entry(struct req_entry *req) +{ + pcep_free_path(req->path); + XFREE(MTYPE_PCEP, req); +} + +struct req_entry *push_new_req(struct pcc_state *pcc_state, struct path *path) +{ + struct req_entry *req; + + req = XCALLOC(MTYPE_PCEP, sizeof(*req)); + req->retry_count = 0; + req->path = pcep_copy_path(path); + repush_req(pcc_state, req); + + return req; +} + +void repush_req(struct pcc_state *pcc_state, struct req_entry *req) +{ + uint32_t reqid = pcc_state->next_reqid; + void *res; + + req->was_sent = false; + req->path->req_id = reqid; + res = RB_INSERT(req_entry_head, &pcc_state->requests, req); + assert(res == NULL); + assert(add_reqid_mapping(pcc_state, req->path) == true); + + pcc_state->next_reqid += 1; + /* Wrapping is allowed, but 0 is not a valid id */ + if (pcc_state->next_reqid == 0) + pcc_state->next_reqid = 1; +} + +struct req_entry *pop_req(struct pcc_state *pcc_state, uint32_t reqid) +{ + struct path path = {.req_id = reqid}; + struct req_entry key = {.path = &path}; + struct req_entry *req; + + req = RB_FIND(req_entry_head, &pcc_state->requests, &key); + if (req == NULL) + return NULL; + RB_REMOVE(req_entry_head, &pcc_state->requests, req); + remove_reqid_mapping(pcc_state, req->path); + + return req; +} + +bool add_reqid_mapping(struct pcc_state *pcc_state, struct path *path) +{ + struct req_map_data *mapping; + mapping = XCALLOC(MTYPE_PCEP, sizeof(*mapping)); + mapping->nbkey = path->nbkey; + mapping->reqid = path->req_id; + if (req_map_add(&pcc_state->req_map, mapping) != NULL) { + XFREE(MTYPE_PCEP, mapping); + return false; + } + return true; +} + +void remove_reqid_mapping(struct pcc_state *pcc_state, struct path *path) +{ + struct req_map_data key, *mapping; + key.nbkey = path->nbkey; + mapping = req_map_find(&pcc_state->req_map, &key); + if (mapping != NULL) { + req_map_del(&pcc_state->req_map, mapping); + XFREE(MTYPE_PCEP, mapping); + } +} + +uint32_t lookup_reqid(struct pcc_state *pcc_state, struct path *path) +{ + struct req_map_data key, *mapping; + key.nbkey = path->nbkey; + mapping = req_map_find(&pcc_state->req_map, &key); + if (mapping != NULL) + return mapping->reqid; + return 0; +} + +bool has_pending_req_for(struct pcc_state *pcc_state, struct path *path) +{ + return lookup_reqid(pcc_state, path) != 0; +} + + +/* ------------ Data Structure Callbacks ------------ */ + +#define CMP_RETURN(A, B) \ + if (A != B) \ + return (A < B) ? -1 : 1 + +static uint32_t hash_nbkey(const struct lsp_nb_key *nbkey) +{ + uint32_t hash; + hash = jhash_2words(nbkey->color, nbkey->preference, 0x55aa5a5a); + switch (nbkey->endpoint.ipa_type) { + case IPADDR_V4: + return jhash(&nbkey->endpoint.ipaddr_v4, + sizeof(nbkey->endpoint.ipaddr_v4), hash); + case IPADDR_V6: + return jhash(&nbkey->endpoint.ipaddr_v6, + sizeof(nbkey->endpoint.ipaddr_v6), hash); + default: + return hash; + } +} + +static int cmp_nbkey(const struct lsp_nb_key *a, const struct lsp_nb_key *b) +{ + CMP_RETURN(a->color, b->color); + int cmp = ipaddr_cmp(&a->endpoint, &b->endpoint); + if (cmp != 0) + return cmp; + CMP_RETURN(a->preference, b->preference); + return 0; +} + +int plspid_map_cmp(const struct plspid_map_data *a, + const struct plspid_map_data *b) +{ + return cmp_nbkey(&a->nbkey, &b->nbkey); +} + +uint32_t plspid_map_hash(const struct plspid_map_data *e) +{ + return hash_nbkey(&e->nbkey); +} + +int nbkey_map_cmp(const struct nbkey_map_data *a, + const struct nbkey_map_data *b) +{ + CMP_RETURN(a->plspid, b->plspid); + return 0; +} + +uint32_t nbkey_map_hash(const struct nbkey_map_data *e) +{ + return e->plspid; +} + +int req_map_cmp(const struct req_map_data *a, const struct req_map_data *b) +{ + return cmp_nbkey(&a->nbkey, &b->nbkey); +} + +uint32_t req_map_hash(const struct req_map_data *e) +{ + return hash_nbkey(&e->nbkey); +} diff --git a/pathd/path_pcep_pcc.h b/pathd/path_pcep_pcc.h new file mode 100644 index 0000000000..a466d92d50 --- /dev/null +++ b/pathd/path_pcep_pcc.h @@ -0,0 +1,141 @@ +/* + * 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_timer_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); +void pcep_pcc_send_report(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path); +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/pathd.conf.sample b/pathd/pathd.conf.sample index fc7acafc1f..9fe7d2d6e3 100644 --- a/pathd/pathd.conf.sample +++ b/pathd/pathd.conf.sample @@ -29,5 +29,13 @@ segment-routing metric te 10 ! ! + pcep + pcc-peer PCE1 + address ip 127.0.0.1 + sr-draft07 + ! + pcc + peer PCE1 + ! ! ! diff --git a/pathd/subdir.am b/pathd/subdir.am index 23399b9d81..7403787fbe 100644 --- a/pathd/subdir.am +++ b/pathd/subdir.am @@ -10,6 +10,12 @@ vtysh_scan += $(top_srcdir)/pathd/path_cli.c vtysh_daemons += pathd # TODO add man page #man8 += $(MANBUILD)/pathd.8 + +if HAVE_PATHD_PCEP +vtysh_scan += $(top_srcdir)/pathd/path_pcep_cli.c +module_LTLIBRARIES += pathd/pathd_pcep.la +endif + endif pathd_libpath_a_SOURCES = \ @@ -29,20 +35,45 @@ clippy_scan += \ pathd/path_cli.c \ # end +if HAVE_PATHD_PCEP +clippy_scan += \ + pathd/path_pcep_cli.c \ + # end +endif + noinst_HEADERS += \ pathd/path_debug.h \ pathd/path_errors.h \ pathd/path_memory.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_memory.h \ + pathd/path_pcep_config.h \ + pathd/path_pcep_pcc.h \ pathd/path_zebra.h \ pathd/pathd.h \ # end -pathd/path_cli_clippy.c: $(CLIPPY_DEPS) -pathd/path_cli.$(OBJEXT): pathd/path_cli_clippy.c - pathd_pathd_SOURCES = pathd/path_main.c 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_memory.c \ + pathd/path_pcep_config.c \ + pathd/path_pcep_pcc.c \ + # end +pathd_pathd_pcep_la_CFLAGS = $(WERROR) +pathd_pathd_pcep_la_LDFLAGS = -avoid-version -module -shared -export-dynamic +pathd_pathd_pcep_la_LIBADD = @PATHD_PCEP_LIBS@ diff --git a/tools/frr-reload.py b/tools/frr-reload.py index 137a3d5a03..21dc93954e 100755 --- a/tools/frr-reload.py +++ b/tools/frr-reload.py @@ -681,6 +681,27 @@ end current_context_lines = [] new_ctx = False log.debug("LINE %-50s: entering new context, %-50s", line, ctx_keys) + + elif ( + line.startswith("peer ") + and len(ctx_keys) == 4 + and ctx_keys[0].startswith("segment-routing") + and ctx_keys[1].startswith("traffic-eng") + and ctx_keys[2].startswith("pcep") + and ctx_keys[3].startswith("pcc") + ): + # If there is no precedence, we add the default one (255) so + # the line is not removed and added back + m = re.search('peer ([^ ]*)', line) + if (m != None): + (name,) = m.groups() + line = "peer %s precedence 255" % (name,) + + current_context_lines.append(line) + log.debug( + "LINE %-50s: append to current_context_lines, %-50s", line, ctx_keys + ) + elif ( line.startswith("address-family ") or line.startswith("vnc defaults") @@ -805,6 +826,73 @@ end ) ctx_keys.append(line) + elif ( + line.startswith("pcep") + and len(ctx_keys) == 2 + and ctx_keys[0].startswith("segment-routing") + and ctx_keys[1].startswith("traffic-eng") + ): + + # Save old context first + self.save_contexts(ctx_keys, current_context_lines) + current_context_lines = [] + main_ctx_key = copy.deepcopy(ctx_keys) + log.debug( + "LINE %-50s: entering pcep sub-context, append to ctx_keys", line + ) + ctx_keys.append(line) + + elif ( + line.startswith("pce-config ") + and len(ctx_keys) == 3 + and ctx_keys[0].startswith("segment-routing") + and ctx_keys[1].startswith("traffic-eng") + and ctx_keys[2].startswith("pcep") + ): + + # Save old context first + self.save_contexts(ctx_keys, current_context_lines) + current_context_lines = [] + main_ctx_key = copy.deepcopy(ctx_keys) + log.debug( + "LINE %-50s: entering pce-config sub-context, append to ctx_keys", line + ) + ctx_keys.append(line) + + elif ( + line.startswith("pce ") + and len(ctx_keys) == 3 + and ctx_keys[0].startswith("segment-routing") + and ctx_keys[1].startswith("traffic-eng") + and ctx_keys[2].startswith("pcep") + ): + + # Save old context first + self.save_contexts(ctx_keys, current_context_lines) + current_context_lines = [] + main_ctx_key = copy.deepcopy(ctx_keys) + log.debug( + "LINE %-50s: entering pce sub-context, append to ctx_keys", line + ) + ctx_keys.append(line) + + elif ( + line.startswith("pcc") + and len(ctx_keys) == 3 + and ctx_keys[0].startswith("segment-routing") + and ctx_keys[1].startswith("traffic-eng") + and ctx_keys[2].startswith("pcep") + ): + + # Save old context first + self.save_contexts(ctx_keys, current_context_lines) + current_context_lines = [] + main_ctx_key = copy.deepcopy(ctx_keys) + log.debug( + "LINE %-50s: entering pcc sub-context, append to ctx_keys", line + ) + ctx_keys.append(line) + else: # Continuing in an existing context, add non-commented lines to it current_context_lines.append(line) @@ -1415,6 +1503,14 @@ def compare_context_objects(newconf, running): ): continue + # Neither the pcep command + elif ( + len(running_ctx_keys) == 3 + and running_ctx_keys[0].startswith('segment-routing') + and running_ctx_keys[2].startswith('pcep4') + ): + continue + # Segment lists can only be deleted after we removed all the candidate paths that # use them, so add them to a separate array that is going to be appended at the end elif ( diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 93a63cd964..22eff40cd6 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -541,8 +541,12 @@ static int vtysh_execute_func(const char *line, int pager) vtysh_execute("exit"); } else if ((saved_node == SR_SEGMENT_LIST_NODE || saved_node == SR_POLICY_NODE - || saved_node == SR_CANDIDATE_DYN_NODE) - && (tried > 0)) { + || saved_node == SR_CANDIDATE_DYN_NODE + || saved_node == PCEP_NODE + || saved_node == PCEP_PCE_CONFIG_NODE + || saved_node == PCEP_PCE_NODE + || saved_node == PCEP_PCC_NODE) + && (tried > 0)) { vtysh_execute("exit"); } else if (tried) { vtysh_execute("end"); @@ -826,16 +830,24 @@ int vtysh_mark_file(const char *filename) && (tried == 1)) { vty_out(vty, "exit\n"); } else if (((prev_node == SEGMENT_ROUTING_NODE) - || (prev_node == SR_TRAFFIC_ENG_NODE) - || (prev_node == SR_SEGMENT_LIST_NODE) - || (prev_node == SR_POLICY_NODE) - || (prev_node == SR_CANDIDATE_DYN_NODE)) + || (prev_node == SR_TRAFFIC_ENG_NODE) + || (prev_node == SR_SEGMENT_LIST_NODE) + || (prev_node == SR_POLICY_NODE) + || (prev_node == SR_CANDIDATE_DYN_NODE) + || (prev_node == PCEP_NODE) + || (prev_node == PCEP_PCE_CONFIG_NODE) + || (prev_node == PCEP_PCE_NODE) + || (prev_node == PCEP_PCC_NODE)) && (tried > 0)) { ending = (vty->node != SEGMENT_ROUTING_NODE) - && (vty->node != SR_TRAFFIC_ENG_NODE) - && (vty->node != SR_SEGMENT_LIST_NODE) - && (vty->node != SR_POLICY_NODE) - && (vty->node != SR_CANDIDATE_DYN_NODE); + && (vty->node != SR_TRAFFIC_ENG_NODE) + && (vty->node != SR_SEGMENT_LIST_NODE) + && (vty->node != SR_POLICY_NODE) + && (vty->node != SR_CANDIDATE_DYN_NODE) + && (vty->node != PCEP_NODE) + && (vty->node != PCEP_PCE_CONFIG_NODE) + && (vty->node != PCEP_PCE_NODE) + && (vty->node != PCEP_PCC_NODE); if (ending) tried--; while (tried-- > 0) @@ -1284,6 +1296,34 @@ static struct cmd_node srte_candidate_dyn_node = { .prompt = "%s(config-sr-te-candidate)# ", }; +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-peer", + .node = PCEP_PCE_NODE, + .parent_node = PCEP_NODE, + .prompt = "%s(config-sr-te-pcep-pce-peer)# ", +}; + +static struct cmd_node pcep_pce_config_node = { + .name = "srte pcep pce-config", + .node = PCEP_PCE_CONFIG_NODE, + .parent_node = PCEP_NODE, + .prompt = "%s(pcep-sr-te-pcep-pce-config)# ", +}; + static struct cmd_node vrf_node = { .name = "vrf", .node = VRF_NODE, @@ -2091,6 +2131,48 @@ DEFUNSH(VTYSH_PATHD, srte_policy_candidate_dyn_path, vty->node = SR_CANDIDATE_DYN_NODE; return CMD_SUCCESS; } + +#if defined(HAVE_PATHD_PCEP) + +DEFUNSH(VTYSH_PATHD, pcep, pcep_cmd, + "pcep", + "Configure SR pcep\n") +{ + vty->node = PCEP_NODE; + return CMD_SUCCESS; +} + +DEFUNSH(VTYSH_PATHD, pcep_cli_pcc, pcep_cli_pcc_cmd, + "[no] pcc", + NO_STR + "PCC configuration\n") +{ + vty->node = PCEP_PCC_NODE; + return CMD_SUCCESS; +} + +DEFUNSH(VTYSH_PATHD, pcep_cli_pce, pcep_cli_pce_cmd, + "[no] pce WORD", + NO_STR + "PCE configuration\n" + "Peer name\n") +{ + vty->node = PCEP_PCE_NODE; + return CMD_SUCCESS; +} + +DEFUNSH(VTYSH_PATHD, pcep_cli_pcep_pce_config, pcep_cli_pcep_pce_config_cmd, + "[no] pce-config WORD", + NO_STR + "PCEP peer Configuration Group\n" + "PCEP peer Configuration Group name\n") +{ + vty->node = PCEP_PCE_CONFIG_NODE; + return CMD_SUCCESS; +} + +#endif /* HAVE_PATHD_PCEP */ + #endif /* HAVE_PATHD */ DEFUNSH(VTYSH_RMAP, vtysh_route_map, vtysh_route_map_cmd, @@ -4304,6 +4386,33 @@ void vtysh_init_vty(void) install_element(SR_TRAFFIC_ENG_NODE, &srte_segment_list_cmd); install_element(SR_TRAFFIC_ENG_NODE, &srte_policy_cmd); install_element(SR_POLICY_NODE, &srte_policy_candidate_dyn_path_cmd); + +#if defined(HAVE_PATHD_PCEP) + install_node(&pcep_node); + install_node(&pcep_pcc_node); + install_node(&pcep_pce_node); + install_node(&pcep_pce_config_node); + + install_element(PCEP_NODE, &vtysh_exit_pathd_cmd); + install_element(PCEP_NODE, &vtysh_quit_pathd_cmd); + install_element(PCEP_PCC_NODE, &vtysh_exit_pathd_cmd); + install_element(PCEP_PCC_NODE, &vtysh_quit_pathd_cmd); + install_element(PCEP_PCE_NODE, &vtysh_exit_pathd_cmd); + install_element(PCEP_PCE_NODE, &vtysh_quit_pathd_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &vtysh_exit_pathd_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &vtysh_quit_pathd_cmd); + + install_element(PCEP_NODE, &vtysh_end_all_cmd); + install_element(PCEP_PCC_NODE, &vtysh_end_all_cmd); + install_element(PCEP_PCE_NODE, &vtysh_end_all_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &vtysh_end_all_cmd); + + install_element(SR_TRAFFIC_ENG_NODE, &pcep_cmd); + install_element(PCEP_NODE, &pcep_cli_pcc_cmd); + install_element(PCEP_NODE, &pcep_cli_pcep_pce_config_cmd); + install_element(PCEP_NODE, &pcep_cli_pce_cmd); +#endif /* HAVE_PATHD_PCEP */ + #endif /* HAVE_PATHD */ /* keychain */