From 4d7b695d3abe80fbb1b666aed8faa1b9e9387fdb Mon Sep 17 00:00:00 2001 From: Sebastien Merle Date: Fri, 31 Jul 2020 18:04:20 +0200 Subject: [PATCH] pathd: New SR-TE policy management daemon This new daemon manages Segment-Routing Traffic-Engineering (SR-TE) Policies and installs them into zebra. It provides the usual yang support and vtysh commands to define or change SR-TE Policies. In a nutshell SR-TE Policies provide the possibility to steer traffic through a (possibly dynamic) list of Segment Routing segments to the endpoint of the policy. This list of segments is part of a Candidate Path which again belongs to the SR-TE Policy. SR-TE Policies are uniquely identified by their color and endpoint. The color can be used to e.g. match BGP communities on incoming traffic. There can be multiple Candidate Paths for a single policy, the active Candidate Path is chosen according to certain conditions of which the most important is its preference. Candidate Paths can be explicit (fixed list of segments) or dynamic (list of segment comes from e.g. PCEP, see below). Configuration example: segment-routing traffic-eng segment-list SL index 10 mpls label 1111 index 20 mpls label 2222 ! policy color 4 endpoint 10.10.10.4 name POL4 binding-sid 104 candidate-path preference 100 name exp explicit segment-list SL candidate-path preference 200 name dyn dynamic ! ! ! There is an important connection between dynamic Candidate Paths and the overall topic of Path Computation. Later on for pathd a dynamic module will be introduced that is capable of communicating via the PCEP protocol with a PCE (Path Computation Element) which again is capable of calculating paths according to its local TED (Traffic Engineering Database). This dynamic module will be able to inject the mentioned dynamic Candidate Paths into pathd based on calculated paths from a PCE. https://tools.ietf.org/html/draft-ietf-spring-segment-routing-policy-06 Co-authored-by: Sebastien Merle Co-authored-by: Renato Westphal Co-authored-by: GalaxyGorilla Co-authored-by: Emanuele Di Pascale Signed-off-by: Sebastien Merle --- Makefile.am | 1 + configure.ac | 7 + doc/developer/index.rst | 1 + doc/developer/path-internals-daemon.rst | 115 ++ doc/developer/path-internals.rst | 10 + doc/developer/path.rst | 11 + doc/developer/subdir.am | 3 + doc/user/index.rst | 1 + doc/user/pathd.rst | 233 ++++ doc/user/subdir.am | 1 + lib/command.c | 12 + lib/command.h | 5 + lib/ferr.h | 2 + lib/yang.c | 1 + pathd/.gitignore | 2 + pathd/Makefile | 10 + pathd/path_cli.c | 1108 ++++++++++++++++ pathd/path_debug.c | 121 ++ pathd/path_debug.h | 44 + pathd/path_errors.c | 43 + pathd/path_errors.h | 30 + pathd/path_main.c | 154 +++ pathd/path_memory.c | 25 + pathd/path_memory.h | 26 + pathd/path_nb.c | 351 +++++ pathd/path_nb.h | 130 ++ pathd/path_nb_config.c | 731 +++++++++++ pathd/path_nb_state.c | 189 +++ pathd/path_zebra.c | 296 +++++ pathd/path_zebra.h | 34 + pathd/pathd.c | 1127 +++++++++++++++++ pathd/pathd.conf.sample | 33 + pathd/pathd.h | 412 ++++++ pathd/subdir.am | 48 + redhat/frr.spec.in | 25 +- .../topotests/isis-sr-te-topo1/dst/zebra.conf | 19 + .../topotests/isis-sr-te-topo1/rt1/bgpd.conf | 16 + .../topotests/isis-sr-te-topo1/rt1/isisd.conf | 30 + .../topotests/isis-sr-te-topo1/rt1/pathd.conf | 21 + .../step1/show_mpls_table_with_candidate.ref | 91 ++ .../show_mpls_table_without_candidate.ref | 74 ++ .../rt1/step2/show_operational_data.ref | 13 + .../show_operational_data_with_candidate.ref | 20 + ...operational_data_with_single_candidate.ref | 20 + ...w_operational_data_with_two_candidates.ref | 25 + .../rt1/step4/show_mpls_table.ref | 20 + .../rt1/step4/show_mpls_table_add_segment.ref | 21 + .../step4/show_mpls_table_change_segment.ref | 21 + .../step5/show_ip_route_bgp_active_srte.ref | 29 + .../step5/show_ip_route_bgp_inactive_srte.ref | 38 + .../step5/show_operational_data_active.ref | 20 + .../step5/show_operational_data_inactive.ref | 20 + .../topotests/isis-sr-te-topo1/rt1/zebra.conf | 19 + .../topotests/isis-sr-te-topo1/rt2/isisd.conf | 41 + .../topotests/isis-sr-te-topo1/rt2/zebra.conf | 25 + .../topotests/isis-sr-te-topo1/rt3/isisd.conf | 41 + .../topotests/isis-sr-te-topo1/rt3/zebra.conf | 25 + .../topotests/isis-sr-te-topo1/rt4/isisd.conf | 48 + .../topotests/isis-sr-te-topo1/rt4/zebra.conf | 28 + .../topotests/isis-sr-te-topo1/rt5/isisd.conf | 48 + .../topotests/isis-sr-te-topo1/rt5/zebra.conf | 28 + .../topotests/isis-sr-te-topo1/rt6/bgpd.conf | 12 + .../topotests/isis-sr-te-topo1/rt6/isisd.conf | 36 + .../topotests/isis-sr-te-topo1/rt6/pathd.conf | 21 + .../step1/show_mpls_table_with_candidate.ref | 91 ++ .../show_mpls_table_without_candidate.ref | 74 ++ .../rt6/step2/show_operational_data.ref | 13 + .../show_operational_data_with_candidate.ref | 19 + ...operational_data_with_single_candidate.ref | 19 + ...w_operational_data_with_two_candidates.ref | 23 + .../rt6/step4/show_mpls_table.ref | 20 + .../topotests/isis-sr-te-topo1/rt6/zebra.conf | 27 + .../isis-sr-te-topo1/test_isis_sr_te_topo1.py | 525 ++++++++ tests/topotests/lib/topogen.py | 2 + tests/topotests/lib/topotest.py | 1 + tools/frr-reload.py | 163 ++- tools/frr.in | 2 +- vtysh/vtysh.c | 162 +++ vtysh/vtysh.h | 3 +- yang/frr-pathd.yang | 480 +++++++ yang/subdir.am | 4 + 81 files changed, 7828 insertions(+), 12 deletions(-) create mode 100644 doc/developer/path-internals-daemon.rst create mode 100644 doc/developer/path-internals.rst create mode 100644 doc/developer/path.rst create mode 100644 doc/user/pathd.rst create mode 100644 pathd/.gitignore create mode 100644 pathd/Makefile create mode 100644 pathd/path_cli.c create mode 100644 pathd/path_debug.c create mode 100644 pathd/path_debug.h create mode 100644 pathd/path_errors.c create mode 100644 pathd/path_errors.h create mode 100644 pathd/path_main.c create mode 100644 pathd/path_memory.c create mode 100644 pathd/path_memory.h create mode 100644 pathd/path_nb.c create mode 100644 pathd/path_nb.h create mode 100644 pathd/path_nb_config.c create mode 100644 pathd/path_nb_state.c create mode 100644 pathd/path_zebra.c create mode 100644 pathd/path_zebra.h create mode 100644 pathd/pathd.c create mode 100644 pathd/pathd.conf.sample create mode 100644 pathd/pathd.h create mode 100644 pathd/subdir.am create mode 100644 tests/topotests/isis-sr-te-topo1/dst/zebra.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/bgpd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/isisd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/pathd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step1/show_mpls_table_with_candidate.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step1/show_mpls_table_without_candidate.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step2/show_operational_data.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step2/show_operational_data_with_candidate.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step3/show_operational_data_with_single_candidate.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step3/show_operational_data_with_two_candidates.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table_add_segment.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table_change_segment.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step5/show_ip_route_bgp_active_srte.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step5/show_ip_route_bgp_inactive_srte.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step5/show_operational_data_active.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/step5/show_operational_data_inactive.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt1/zebra.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt2/isisd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt2/zebra.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt3/isisd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt3/zebra.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt4/isisd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt4/zebra.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt5/isisd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt5/zebra.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/bgpd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/isisd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/pathd.conf create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/step1/show_mpls_table_with_candidate.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/step1/show_mpls_table_without_candidate.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/step2/show_operational_data.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/step2/show_operational_data_with_candidate.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/step3/show_operational_data_with_single_candidate.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/step3/show_operational_data_with_two_candidates.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/step4/show_mpls_table.ref create mode 100644 tests/topotests/isis-sr-te-topo1/rt6/zebra.conf create mode 100755 tests/topotests/isis-sr-te-topo1/test_isis_sr_te_topo1.py create mode 100644 yang/frr-pathd.yang diff --git a/Makefile.am b/Makefile.am index b6e8a2bce8..90c8407010 100644 --- a/Makefile.am +++ b/Makefile.am @@ -158,6 +158,7 @@ include bfdd/subdir.am include yang/subdir.am include yang/libyang_plugins/subdir.am include vrrpd/subdir.am +include pathd/subdir.am include vtysh/subdir.am include tests/subdir.am diff --git a/configure.ac b/configure.ac index acedf7573d..168a9041df 100755 --- a/configure.ac +++ b/configure.ac @@ -560,6 +560,8 @@ AC_ARG_ENABLE([fabricd], AS_HELP_STRING([--disable-fabricd], [do not build fabricd])) AC_ARG_ENABLE([vrrpd], AS_HELP_STRING([--disable-vrrpd], [do not build vrrpd])) +AC_ARG_ENABLE([pathd], + AS_HELP_STRING([--disable-pathd], [do not build pathd])) AC_ARG_ENABLE([bgp-announce], AS_HELP_STRING([--disable-bgp-announce], [turn off BGP route announcement])) AC_ARG_ENABLE([bgp-vnc], @@ -1675,6 +1677,10 @@ else esac fi +AS_IF([test "$enable_pathd" != "no"], [ + AC_DEFINE([HAVE_PATHD], [1], [pathd]) +]) + 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 @@ -2489,6 +2495,7 @@ AM_CONDITIONAL([SHARPD], [test "$enable_sharpd" = "yes"]) AM_CONDITIONAL([STATICD], [test "$enable_staticd" != "no"]) AM_CONDITIONAL([FABRICD], [test "$enable_fabricd" != "no"]) AM_CONDITIONAL([VRRPD], [test "$enable_vrrpd" != "no"]) +AM_CONDITIONAL([PATHD], [test "$enable_pathd" != "no"]) AC_CONFIG_FILES([Makefile],[ test "$enable_dev_build" = "yes" && makefile_devbuild="--dev-build" diff --git a/doc/developer/index.rst b/doc/developer/index.rst index 5a7da806ff..60a7d505a7 100644 --- a/doc/developer/index.rst +++ b/doc/developer/index.rst @@ -18,3 +18,4 @@ FRRouting Developer's Guide ospf zebra vtysh + pathd diff --git a/doc/developer/path-internals-daemon.rst b/doc/developer/path-internals-daemon.rst new file mode 100644 index 0000000000..29f017284f --- /dev/null +++ b/doc/developer/path-internals-daemon.rst @@ -0,0 +1,115 @@ +PATHD Internals +=============== + +Architecture +------------ + +Overview +........ + +The pathd deamon manages the segment routing policies, it owns the data +structures representing them and can load modules that manipulate them like the +PCEP module. Its responsibility is to select a candidate path for each +configured policy and to install it into Zebra. + +Zebra +..... + +Zebra manages policies that are active or pending to be activated due to the +next hop not being available yet. In zebra, policy data structures and APIs are +defined in `zebra_srte.[hc]`. + +The responsibilities of Zebra are: + + - Store the policies' segment list. + - Install the policies when their next-hop is available. + - Notify other daemons of the status of the policies. + +Adding and removing policies is done using the commands `ZEBRA_SR_POLICY_SET` +and `ZEBRA_SR_POLICY_DELETE` as parameter of the function `zebra_send_sr_policy` +all defined in `zclient.[hc]`. + +If the first segment of the policy is an unknown label, it is kept until +notified by the mpls hooks `zebra_mpls_label_created`, and then it is installed. + +To get notified when a policy status changes, a client can implement the +`sr_policy_notify_status` callback defined in `zclient.[hc]`. + +For encoding/decoding the various data structures used to comunicate with zebra, +the following functions are available from `zclient.[hc]`: +`zapi_sr_policy_encode`, `zapi_sr_policy_decode` and +`zapi_sr_policy_notify_status_decode`. + + +Pathd +..... + + +The pathd daemon manages all the possible candidate paths for the segment +routing policies and selects the best one following the +`segment routing policy draft `_. +It also supports loadable modules for handling dynamic candidate paths and the +creation of new policies and candidate paths at runtime. + +The responsibilities of the pathd base daemon, not including any optional +modules, are: + + - Store the policies and all the possible candidate paths for them. + - Select the best candidate path for each policy and send it to Zebra. + - Provide VTYSH configuration to set up policies and candidate paths. + - Provide a Northbound API to manipulate **configured** policies and candidate paths. + - Handle loadable modules for extending the functionality. + - Provide an API to the loadable module to manipulate policies and candidate paths. + + +Threading Model +--------------- + +The daemon runs completely inside the main thread using FRR event model, there +is no threading involved. + + +Source Code +----------- + +Internal Data Structures +........................ + +The main data structures for policies and candidate paths are defined in +`pathd.h` and implemented in `pathd.c`. + +When modifying these structures, either directly or through the functions +exported by `pathd.h`, nothing should be deleted/freed right away. The deletion +or modification flags must be set and when all the changes are done, the +function `srte_apply_changes` must be called. When called, a new candidate path +may be elected and sent to Zebra, and all the structures flagged as deleted +will be freed. In addition, a hook will be called so dynamic modules can perform +any required action when the elected candidate path changes. + + +Northbound API +.............. + +The northbound API is defined in `path_nb.[ch]` and implemented in +`path_nb_config.c` for configuration data and `path_nb_state.c` for operational +data. + + +Command Line Client +................... + +The command-line client (VTYSH) is implemented in `path_cli.c`. + + +Interface with Zebra +.................... + +All the functions interfacing with Zebra are defined and implemented in +`path_zebra.[hc]`. + + +Loadable Module API +................... + +For the time being, the API the loadable module uses is defined by `pathd.h`, +but in the future, it should be moved to a dedicated include file. diff --git a/doc/developer/path-internals.rst b/doc/developer/path-internals.rst new file mode 100644 index 0000000000..980d359453 --- /dev/null +++ b/doc/developer/path-internals.rst @@ -0,0 +1,10 @@ +.. _path_internals: + +********* +Internals +********* + +.. toctree:: + :maxdepth: 2 + + path-internals-daemon diff --git a/doc/developer/path.rst b/doc/developer/path.rst new file mode 100644 index 0000000000..b6d2438c58 --- /dev/null +++ b/doc/developer/path.rst @@ -0,0 +1,11 @@ +.. _path: + +***** +PATHD +***** + +.. toctree:: + :maxdepth: 2 + + path-internals + diff --git a/doc/developer/subdir.am b/doc/developer/subdir.am index 3c0d203007..400555a2e8 100644 --- a/doc/developer/subdir.am +++ b/doc/developer/subdir.am @@ -46,6 +46,9 @@ dev_RSTFILES = \ doc/developer/packaging-debian.rst \ doc/developer/packaging-redhat.rst \ doc/developer/packaging.rst \ + doc/developer/path-internals-daemon.rst \ + doc/developer/path-internals.rst \ + doc/developer/path.rst \ doc/developer/rcu.rst \ doc/developer/static-linking.rst \ doc/developer/testing.rst \ diff --git a/doc/user/index.rst b/doc/user/index.rst index 8ac997f8dd..993acf3b4c 100644 --- a/doc/user/index.rst +++ b/doc/user/index.rst @@ -50,6 +50,7 @@ Protocols nhrpd ospfd ospf6d + pathd pim pbr ripd diff --git a/doc/user/pathd.rst b/doc/user/pathd.rst new file mode 100644 index 0000000000..0c825d423b --- /dev/null +++ b/doc/user/pathd.rst @@ -0,0 +1,233 @@ +.. _path: + +**** +PATH +**** + +:abbr:`PATH` is a daemon that handles the installation and deletion +of Segment Routing (SR) Policies. + + +.. _starting-path: + +Starting PATH +============= + +Default configuration file for *pathd* is :file:`pathd.conf`. The typical +location of :file:`pathd.conf` is |INSTALL_PREFIX_ETC|/pathd.conf. + +If the user is using integrated config, then :file:`pathd.conf` need not be +present and the :file:`frr.conf` is read instead. + +.. program:: pathd + +:abbr:`PATH` supports all the common FRR daemon start options which are +documented elsewhere. + + +Pathd Configuration +=================== + +Example: + +.. code-block:: frr + + segment-routing + traffic-eng + segment-list SL1 + index 10 mpls label 16010 + index 20 mpls label 16030 + ! + policy color 1 endpoint 1.1.1.1 + name default + binding-sid 4000 + candidate-path preference 100 name CP1 explicit segment-list SL1 + candidate-path preference 200 name CP2 dynamic + affinity include-any 0x000000FF + bandwidth 100000 + metric bound msd 16 required + metric te 10 + objective-function mcp required + ! + ! + ! + + +.. _path-commands: + +Configuration Commands +---------------------- + +.. index:: segment-routing +.. clicmd:: segment-routing + + Configure segment routing. + +.. index:: traffic-eng +.. clicmd:: traffic-eng + + Configure segment routing traffic engineering. + +.. index:: [no] segment-list NAME +.. clicmd:: [no] segment-list NAME + + Delete or start a segment list definition. + + +.. index:: [no] index INDEX mpls label LABEL [nai node ADDRESS] +.. clicmd:: [no] index INDEX mpls label LABEL [nai node ADDRESS] + + Delete or specify a segment in a segment list definition. + + +.. index:: [no] policy color COLOR endpoint ENDPOINT +.. clicmd:: [no] policy color COLOR endpoint ENDPOINT + + Delete or start a policy definition. + + +.. index:: name NAME +.. clicmd:: name NAME + + Specify the policy name. + + +.. index:: binding-sid LABEL +.. clicmd:: binding-sid LABEL + + Specify the policy SID. + + +.. index:: [no] candidate-path preference PREFERENCE name NAME explicit segment-list SEGMENT-LIST-NAME +.. clicmd:: [no] candidate-path preference PREFERENCE name NAME explicit segment-list SEGMENT-LIST-NAME + + Delete or define an explicit candidate path. + + +.. index:: [no] candidate-path preference PREFERENCE name NAME dynamic +.. clicmd:: [no] candidate-path preference PREFERENCE name NAME dynamic + + Delete or start a dynamic candidate path definition. + + +.. index:: [no] affinity {exclude-any|include-any|include-all} BITPATTERN +.. clicmd:: [no] affinity {exclude-any|include-any|include-all} BITPATTERN + + Delete or specify an affinity constraint for a dynamic candidate path. + + +.. index:: [no] bandwidth BANDWIDTH [required] +.. clicmd:: [no] bandwidth BANDWIDTH [required] + + Delete or specify a bandwidth constraint for a dynamic candidate path. + + +.. index:: [no] metric [bound] METRIC VALUE [required] +.. clicmd:: [no] metric [bound] METRIC VALUE [required] + + Delete or specify a metric constraint for a dynamic candidate path. + + The possible metrics are: + - igp: IGP metric + - te: TE metric + - hc: Hop Counts + - abc: Aggregate bandwidth consumption + - mll: Load of the most loaded link + - igp: Cumulative IGP cost + - cte: Cumulative TE cost + - igp: P2MP IGP metric + - pte: P2MP TE metric + - phc: P2MP hop count metric + - msd: Segment-ID (SID) Depth + - pd: Path Delay metric + - pdv: Path Delay Variation metric + - pl: Path Loss metric + - ppd: P2MP Path Delay metric + - pdv: P2MP Path Delay variation metric + - ppl: P2MP Path Loss metric + - nap: Number of adaptations on a path + - nlp: Number of layers on a path + - dc: Domain Count metric + - bnc: Border Node Count metric + + +.. index:: [no] objective-function OBJFUN1 [required] +.. clicmd:: [no] objective-function OBJFUN1 [required] + + Delete or specify a PCEP objective function constraint for a dynamic + candidate path. + + The possible functions are: + - mcp: Minimum Cost Path [RFC5541] + - mlp: Minimum Load Path [RFC5541] + - mbp: Maximum residual Bandwidth Path [RFC5541] + - mbc: Minimize aggregate Bandwidth Consumption [RFC5541] + - mll: Minimize the Load of the most loaded Link [RFC5541] + - mcc: Minimize the Cumulative Cost of a set of paths [RFC5541] + - spt: Shortest Path Tree [RFC8306] + - mct: Minimum Cost Tree [RFC8306] + - mplp: Minimum Packet Loss Path [RFC8233] + - mup: Maximum Under-Utilized Path [RFC8233] + - mrup: Maximum Reserved Under-Utilized Path [RFC8233] + - mtd: Minimize the number of Transit Domains [RFC8685] + - mbn: Minimize the number of Border Nodes [RFC8685] + - mctd: Minimize the number of Common Transit Domains [RFC8685] + - msl: Minimize the number of Shared Links [RFC8800] + - mss: Minimize the number of Shared SRLGs [RFC8800] + - msn: Minimize the number of Shared Nodes [RFC8800] + + +Introspection Commands +---------------------- + +.. index:: show sr-te policy [detail] +.. clicmd:: show sr-te policy [detail] + + Display the segment routing policies. + +.. code-block:: frr + + router# show sr-te policy + + Endpoint Color Name BSID Status + ------------------------------------------ + 1.1.1.1 1 default 4000 Active + + +.. code-block:: frr + + router# show sr-te policy detail + + Endpoint: 1.1.1.1 Color: 1 Name: LOW_DELAY BSID: 4000 Status: Active + Preference: 100 Name: cand1 Type: explicit Segment-List: sl1 Protocol-Origin: Local + * Preference: 200 Name: cand1 Type: dynamic Segment-List: 32453452 Protocol-Origin: PCEP + +The asterisk (*) marks the best, e.g. active, candidate path. Note that for segment-lists which are +retrieved via PCEP a random number based name is generated. + + +Usage with BGP route-maps +========================= + +It is possible to steer traffic 'into' a segment routing policy for routes +learned through BGP using route-maps: + +.. code-block:: frr + + route-map SET_SR_POLICY permit 10 + set sr-te color 1 + ! + router bgp 1 + bgp router-id 2.2.2.2 + neighbor 1.1.1.1 remote-as 1 + neighbor 1.1.1.1 update-source lo + ! + address-family ipv4 unicast + neighbor 1.1.1.1 next-hop-self + neighbor 1.1.1.1 route-map SET_SR_POLICY in + redistribute static + exit-address-family + ! + ! + +In this case, the SR Policy with color `1` and endpoint `1.1.1.1` is selected. diff --git a/doc/user/subdir.am b/doc/user/subdir.am index dd7a193e34..a78d261863 100644 --- a/doc/user/subdir.am +++ b/doc/user/subdir.am @@ -27,6 +27,7 @@ user_RSTFILES = \ doc/user/ospf_fundamentals.rst \ doc/user/overview.rst \ doc/user/packet-dumps.rst \ + doc/user/pathd.rst \ doc/user/pim.rst \ doc/user/ripd.rst \ doc/user/pbr.rst \ diff --git a/lib/command.c b/lib/command.c index 87110157f6..eeb14b734d 100644 --- a/lib/command.c +++ b/lib/command.c @@ -863,6 +863,18 @@ enum node_type node_parent(enum node_type node) case BFD_PROFILE_NODE: ret = BFD_NODE; break; + case SR_TRAFFIC_ENG_NODE: + ret = SEGMENT_ROUTING_NODE; + break; + case SR_SEGMENT_LIST_NODE: + ret = SR_TRAFFIC_ENG_NODE; + break; + case SR_POLICY_NODE: + ret = SR_TRAFFIC_ENG_NODE; + break; + case SR_CANDIDATE_DYN_NODE: + ret = SR_POLICY_NODE; + break; default: ret = CONFIG_NODE; break; diff --git a/lib/command.h b/lib/command.h index 1b0504101c..2eae9a1b35 100644 --- a/lib/command.h +++ b/lib/command.h @@ -145,6 +145,11 @@ enum node_type { PROTOCOL_NODE, /* protocol filtering node */ MPLS_NODE, /* MPLS config node */ PW_NODE, /* Pseudowire config node */ + SEGMENT_ROUTING_NODE, /* Segment routing root node */ + SR_TRAFFIC_ENG_NODE, /* SR Traffic Engineering node */ + 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 */ VTY_NODE, /* Vty node. */ FPM_NODE, /* Dataplane FPM node. */ LINK_PARAMS_NODE, /* Link-parameters node */ diff --git a/lib/ferr.h b/lib/ferr.h index a89b595e87..4e95431cea 100644 --- a/lib/ferr.h +++ b/lib/ferr.h @@ -132,6 +132,8 @@ struct ferr { #define VTYSH_FRR_END 0x0FFFFFFF #define WATCHFRR_FERR_START 0x10000001 #define WATCHFRR_FERR_END 0x10FFFFFF +#define PATH_FERR_START 0x11000001 +#define PATH_FERR_END 0x11FFFFFF #define ZEBRA_FERR_START 0xF1000001 #define ZEBRA_FERR_END 0xF1FFFFFF #define END_FERR 0xFFFFFFFF diff --git a/lib/yang.c b/lib/yang.c index a3e2a395d7..383dc9f5eb 100644 --- a/lib/yang.c +++ b/lib/yang.c @@ -92,6 +92,7 @@ static const char *const frr_native_modules[] = { "frr-isisd", "frr-vrrpd", "frr-zebra", + "frr-pathd", }; /* clang-format on */ diff --git a/pathd/.gitignore b/pathd/.gitignore new file mode 100644 index 0000000000..95f4a99794 --- /dev/null +++ b/pathd/.gitignore @@ -0,0 +1,2 @@ +libpath.a +pathd diff --git a/pathd/Makefile b/pathd/Makefile new file mode 100644 index 0000000000..b681a9ab1c --- /dev/null +++ b/pathd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. pathd/pathd +%: ALWAYS + @$(MAKE) -s -C .. pathd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/pathd/path_cli.c b/pathd/path_cli.c new file mode 100644 index 0000000000..a55e6ba406 --- /dev/null +++ b/pathd/path_cli.c @@ -0,0 +1,1108 @@ +/* + * 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 "log.h" +#include "command.h" +#include "mpls.h" +#include "northbound_cli.h" +#include "termtable.h" + +#include "pathd/pathd.h" +#include "pathd/path_nb.h" +#include "pathd/path_memory.h" +#ifndef VTYSH_EXTRACT_PL +#include "pathd/path_cli_clippy.c" +#endif + +#define XPATH_MAXATTRSIZE 64 +#define XPATH_MAXKEYSIZE 42 +#define XPATH_POLICY_BASELEN 100 +#define XPATH_POLICY_MAXLEN (XPATH_POLICY_BASELEN + XPATH_MAXATTRSIZE) +#define XPATH_CANDIDATE_BASELEN (XPATH_POLICY_BASELEN + XPATH_MAXKEYSIZE) +#define XPATH_CANDIDATE_MAXLEN (XPATH_CANDIDATE_BASELEN + XPATH_MAXATTRSIZE) + + +static int config_write_segment_routing(struct vty *vty); +static int config_write_traffic_eng(struct vty *vty); +static int config_write_segment_lists(struct vty *vty); +static int config_write_sr_policies(struct vty *vty); + +DEFINE_MTYPE_STATIC(PATHD, PATH_CLI, "Client") + +/* Vty node structures. */ +static struct cmd_node segment_routing_node = { + .name = "segment-routing", + .node = SEGMENT_ROUTING_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-sr)# ", + .config_write = config_write_segment_routing, +}; + +static struct cmd_node sr_traffic_eng_node = { + .name = "sr traffic-eng", + .node = SR_TRAFFIC_ENG_NODE, + .parent_node = SEGMENT_ROUTING_NODE, + .prompt = "%s(config-sr-te)# ", + .config_write = config_write_traffic_eng, +}; + +static struct cmd_node srte_segment_list_node = { + .name = "srte segment-list", + .node = SR_SEGMENT_LIST_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-segment-list)# ", + .config_write = config_write_segment_lists, +}; + +static struct cmd_node srte_policy_node = { + .name = "srte policy", + .node = SR_POLICY_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-policy)# ", + .config_write = config_write_sr_policies, +}; + +static struct cmd_node srte_candidate_dyn_node = { + .name = "srte candidate-dyn", + .node = SR_CANDIDATE_DYN_NODE, + .parent_node = SR_POLICY_NODE, + .prompt = "%s(config-sr-te-candidate)# ", +}; + + +/* + * Show SR-TE info + */ +DEFPY(show_srte_policy, + show_srte_policy_cmd, + "show sr-te policy", + SHOW_STR + "SR-TE info\n" + "SR-TE Policy\n") +{ + struct ttable *tt; + struct srte_policy *policy; + char *table; + + if (RB_EMPTY(srte_policy_head, &srte_policies)) { + vty_out(vty, "No SR Policies to display.\n\n"); + return CMD_SUCCESS; + } + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Endpoint|Color|Name|BSID|Status"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + char endpoint[46]; + char binding_sid[16] = "-"; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + if (policy->binding_sid != MPLS_LABEL_NONE) + snprintf(binding_sid, sizeof(binding_sid), "%u", + policy->binding_sid); + + ttable_add_row(tt, "%s|%u|%s|%s|%s", endpoint, policy->color, + policy->name, binding_sid, + policy->status == SRTE_POLICY_STATUS_UP + ? "Active" + : "Inactive"); + } + + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + + ttable_del(tt); + + return CMD_SUCCESS; +} + +/* + * Show detailed SR-TE info + */ +DEFPY(show_srte_policy_detail, + show_srte_policy_detail_cmd, + "show sr-te policy detail", + SHOW_STR + "SR-TE info\n" + "SR-TE Policy\n" + "Show a detailed summary\n") +{ + struct srte_policy *policy; + + if (RB_EMPTY(srte_policy_head, &srte_policies)) { + vty_out(vty, "No SR Policies to display.\n\n"); + return CMD_SUCCESS; + } + + vty_out(vty, "\n"); + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + struct srte_candidate *candidate; + char endpoint[46]; + char binding_sid[16] = "-"; + char *segment_list_info; + static char undefined_info[] = "(undefined)"; + static char created_by_pce_info[] = "(created by PCE)"; + + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + if (policy->binding_sid != MPLS_LABEL_NONE) + snprintf(binding_sid, sizeof(binding_sid), "%u", + policy->binding_sid); + vty_out(vty, + "Endpoint: %s Color: %u Name: %s BSID: %s Status: %s\n", + endpoint, policy->color, policy->name, binding_sid, + policy->status == SRTE_POLICY_STATUS_UP ? "Active" + : "Inactive"); + + RB_FOREACH (candidate, srte_candidate_head, + &policy->candidate_paths) { + struct srte_segment_list *segment_list; + + segment_list = candidate->lsp->segment_list; + if (segment_list == NULL) + segment_list_info = undefined_info; + else if (segment_list->protocol_origin + == SRTE_ORIGIN_PCEP) + segment_list_info = created_by_pce_info; + else + segment_list_info = + candidate->lsp->segment_list->name; + + vty_out(vty, + " %s Preference: %d Name: %s Type: %s Segment-List: %s Protocol-Origin: %s\n", + CHECK_FLAG(candidate->flags, F_CANDIDATE_BEST) + ? "*" + : " ", + candidate->preference, candidate->name, + candidate->type == SRTE_CANDIDATE_TYPE_EXPLICIT + ? "explicit" + : "dynamic", + segment_list_info, + srte_origin2str( + candidate->lsp->protocol_origin)); + } + + vty_out(vty, "\n"); + } + + return CMD_SUCCESS; +} + +DEFPY_NOSH( + segment_routing_list, + segment_routing_cmd, + "segment-routing", + "Configure segment routing\n") +{ + VTY_PUSH_CONTEXT_NULL(SEGMENT_ROUTING_NODE); + return CMD_SUCCESS; +} + +DEFPY_NOSH( + sr_traffic_eng_list, + sr_traffic_eng_cmd, + "traffic-eng", + "Configure SR traffic engineering\n") +{ + VTY_PUSH_CONTEXT_NULL(SR_TRAFFIC_ENG_NODE); + return CMD_SUCCESS; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list + */ +DEFPY_NOSH( + srte_segment_list, + srte_segment_list_cmd, + "segment-list WORD$name", + "Segment List\n" + "Segment List Name\n") +{ + char xpath[XPATH_MAXLEN]; + int ret; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']/protocol-origin", + name); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, "local"); + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']/originator", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, "config"); + + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) { + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']", name); + VTY_PUSH_XPATH(SR_SEGMENT_LIST_NODE, xpath); + } + + return ret; +} + +DEFPY(srte_no_segment_list, + srte_no_segment_list_cmd, + "no segment-list WORD$name", + NO_STR + "Segment List\n" + "Segment List Name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_segment_list(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " segment-list %s\n", + yang_dnode_get_string(dnode, "./name")); +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/segment + */ +DEFPY(srte_segment_list_segment, + srte_segment_list_segment_cmd, + "index (0-4294967295)$index mpls label (16-1048575)$label " + "[nai$has_nai <" + "node " + ">]", + "Index\n" + "Index Value\n" + "MPLS or IP Label\n" + "Label\n" + "Label Value\n" + "Segment NAI\n" + "NAI node identifier\n" + "NAI IPv4 node identifier\n" + "NAI IPv6 node identifier\n") +{ + char xpath[XPATH_MAXLEN]; + const char *node_id; + + snprintf(xpath, sizeof(xpath), "./segment[index='%s']", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath, sizeof(xpath), "./segment[index='%s']/sid-value", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, label_str); + + if (has_nai != NULL) { + snprintf(xpath, sizeof(xpath), "./segment[index='%s']/nai/type", + index_str); + if (node_ipv4_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv4_node"); + node_id = node_ipv4_str; + } else if (node_ipv6_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv6_node"); + node_id = node_ipv6_str; + } else { + return CMD_ERR_NO_MATCH; + } + snprintf(xpath, sizeof(xpath), + "./segment[index='%s']/nai/local-address", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, node_id); + } else { + snprintf(xpath, sizeof(xpath), "./segment[index='%s']/nai", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_segment_list_no_segment, + srte_segment_list_no_segment_cmd, + "no index (0-4294967295)$index", + NO_STR + "Index\n" + "Index Value\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./segment[index='%s']", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_segment_list_segment(struct vty *vty, + struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " index %s mpls label %s", + yang_dnode_get_string(dnode, "./index"), + yang_dnode_get_string(dnode, "./sid-value")); + if (yang_dnode_exists(dnode, "./nai")) { + struct ipaddr addr; + switch (yang_dnode_get_enum(dnode, "./nai/type")) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + yang_dnode_get_ip(&addr, dnode, "./nai/local-address"); + vty_out(vty, " nai node %pI4", &addr.ipaddr_v4); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + yang_dnode_get_ip(&addr, dnode, "./nai/local-address"); + vty_out(vty, " nai node %pI6", &addr.ipaddr_v6); + break; + default: + break; + } + } + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-pathd:pathd/policy + */ +DEFPY_NOSH( + srte_policy, + srte_policy_cmd, + "policy color (0-4294967295)$num endpoint $endpoint", + "Segment Routing Policy\n" + "SR Policy color\n" + "SR Policy color value\n" + "SR Policy endpoint\n" + "SR Policy endpoint IPv4 address\n" + "SR Policy endpoint IPv6 address\n") +{ + char xpath[XPATH_POLICY_BASELEN]; + int ret; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/policy[color='%s'][endpoint='%s']", + num_str, endpoint_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(SR_POLICY_NODE, xpath); + + return ret; +} + +DEFPY(srte_no_policy, + srte_no_policy_cmd, + "no policy color (0-4294967295)$num endpoint $endpoint", + NO_STR + "Segment Routing Policy\n" + "SR Policy color\n" + "SR Policy color value\n" + "SR Policy endpoint\n" + "SR Policy endpoint IPv4 address\n" + "SR Policy endpoint IPv6 address\n") +{ + char xpath[XPATH_POLICY_BASELEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/policy[color='%s'][endpoint='%s']", + num_str, endpoint_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_policy(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " policy color %s endpoint %s\n", + yang_dnode_get_string(dnode, "./color"), + yang_dnode_get_string(dnode, "./endpoint")); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/name + */ +DEFPY(srte_policy_name, + srte_policy_name_cmd, + "name WORD$name", + "Segment Routing Policy name\n" + "SR Policy name value\n") +{ + nb_cli_enqueue_change(vty, "./name", NB_OP_CREATE, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_policy_no_name, + srte_policy_no_name_cmd, + "no name [WORD]", + NO_STR + "Segment Routing Policy name\n" + "SR Policy name value\n") +{ + nb_cli_enqueue_change(vty, "./name", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + + +void cli_show_srte_policy_name(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " name %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/binding-sid + */ +DEFPY(srte_policy_binding_sid, + srte_policy_binding_sid_cmd, + "binding-sid (16-1048575)$label", + "Segment Routing Policy Binding-SID\n" + "SR Policy Binding-SID label\n") +{ + nb_cli_enqueue_change(vty, "./binding-sid", NB_OP_CREATE, label_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_policy_no_binding_sid, + srte_policy_no_binding_sid_cmd, + "no binding-sid [(16-1048575)]", + NO_STR + "Segment Routing Policy Binding-SID\n" + "SR Policy Binding-SID label\n") +{ + nb_cli_enqueue_change(vty, "./binding-sid", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_policy_binding_sid(struct vty *vty, + struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " binding-sid %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path + */ +DEFPY(srte_policy_candidate_exp, + srte_policy_candidate_exp_cmd, + "candidate-path preference (0-4294967295)$preference name WORD$name \ + explicit segment-list WORD$list_name", + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Explicit Path\n" + "List of SIDs\n" + "Name of the Segment List\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, preference_str); + nb_cli_enqueue_change(vty, "./name", NB_OP_MODIFY, name); + nb_cli_enqueue_change(vty, "./protocol-origin", NB_OP_MODIFY, "local"); + nb_cli_enqueue_change(vty, "./originator", NB_OP_MODIFY, "config"); + nb_cli_enqueue_change(vty, "./type", NB_OP_MODIFY, "explicit"); + nb_cli_enqueue_change(vty, "./segment-list-name", NB_OP_MODIFY, + list_name); + return nb_cli_apply_changes(vty, "./candidate-path[preference='%s']", + preference_str); +} + +DEFPY_NOSH( + srte_policy_candidate_dyn, + srte_policy_candidate_dyn_cmd, + "candidate-path preference (0-4294967295)$preference name WORD$name dynamic", + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Dynamic Path\n") +{ + char xpath[XPATH_CANDIDATE_BASELEN]; + int ret; + + snprintf(xpath, sizeof(xpath), "%s/candidate-path[preference='%s']", + VTY_CURR_XPATH, preference_str); + + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, preference_str); + nb_cli_enqueue_change(vty, "./name", NB_OP_MODIFY, name); + nb_cli_enqueue_change(vty, "./protocol-origin", NB_OP_MODIFY, "local"); + nb_cli_enqueue_change(vty, "./originator", NB_OP_MODIFY, "config"); + nb_cli_enqueue_change(vty, "./type", NB_OP_MODIFY, "dynamic"); + ret = nb_cli_apply_changes(vty, "./candidate-path[preference='%s']", + preference_str); + + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(SR_CANDIDATE_DYN_NODE, xpath); + + return ret; +} + +DEFPY(srte_candidate_bandwidth, + srte_candidate_bandwidth_cmd, + "bandwidth BANDWIDTH$value [required$required]", + "Define a bandwidth constraint\n" + "Bandwidth value\n" + "Required constraint\n") +{ + nb_cli_enqueue_change(vty, "./constraints/bandwidth/required", + NB_OP_MODIFY, required ? "true" : "false"); + nb_cli_enqueue_change(vty, "./constraints/bandwidth/value", + NB_OP_MODIFY, value); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_bandwidth, + srte_candidate_no_bandwidth_cmd, + "no bandwidth [BANDWIDTH$value] [required$required]", + NO_STR + "Remove a bandwidth constraint\n" + "Bandwidth value\n" + "Required constraint\n") +{ + nb_cli_enqueue_change(vty, "./constraints/bandwidth", NB_OP_DESTROY, + NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_affinity_filter, + srte_candidate_affinity_filter_cmd, + "affinity {exclude-any|include-any|include-all}$type BITPATTERN$value", + "Affinity constraint\n" + "Exclude any matching link\n" + "Include any matching link\n" + "Include all matching links\n" + "Attribute filter bit pattern as an hexadecimal value from 0x00000000 to 0xFFFFFFFF\n") +{ + uint32_t filter; + char xpath[XPATH_CANDIDATE_MAXLEN]; + char decimal_value[11]; + + if (sscanf(value, "0x%x", &filter) != 1) { + vty_out(vty, "affinity type: fscanf: %s\n", + safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + snprintf(decimal_value, sizeof(decimal_value), "%u", filter); + snprintf(xpath, sizeof(xpath), "./constraints/affinity/%s", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, decimal_value); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_affinity_filter, + srte_candidate_no_affinity_filter_cmd, + "no affinity {exclude-any|include-any|include-all}$type [BITPATTERN$value]", + NO_STR + "Affinity constraint\n" + "Exclude any matching link\n" + "Include any matching link\n" + "Include all matching links\n" + "Attribute filter bit pattern as an hexadecimal value from 0x00000000 to 0xFFFFFFFF\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./constraints/affinity/%s", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_metric, + srte_candidate_metric_cmd, + "metric [bound$bound] $type METRIC$value [required$required]", + "Define a metric constraint\n" + "If the metric is bounded\n" + "IGP metric\n" + "TE metric\n" + "Hop Counts\n" + "Aggregate bandwidth consumption\n" + "Load of the most loaded link\n" + "Cumulative IGP cost\n" + "Cumulative TE cost\n" + "P2MP IGP metric\n" + "P2MP TE metric\n" + "P2MP hop count metric\n" + "Segment-ID (SID) Depth.\n" + "Path Delay metric\n" + "Path Delay Variation metric\n" + "Path Loss metric\n" + "P2MP Path Delay metric\n" + "P2MP Path Delay variation metric\n" + "P2MP Path Loss metric\n" + "Number of adaptations on a path\n" + "Number of layers on a path\n" + "Domain Count metric\n" + "Border Node Count metric\n" + "Metric value\n" + "Required constraint\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + snprintf(xpath, sizeof(xpath), "./constraints/metrics[type='%s']/value", + type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, value); + snprintf(xpath, sizeof(xpath), + "./constraints/metrics[type='%s']/is-bound", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + (bound != NULL) ? "true" : "false"); + snprintf(xpath, sizeof(xpath), + "./constraints/metrics[type='%s']/required", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + required ? "true" : "false"); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_metric, + srte_candidate_no_metric_cmd, + "no metric [bound] $type [METRIC$value] [required$required]", + NO_STR + "Remove a metric constraint\n" + "If the metric is bounded\n" + "IGP metric\n" + "TE metric\n" + "Hop Counts\n" + "Aggregate bandwidth consumption\n" + "Load of the most loaded link\n" + "Cumulative IGP cost\n" + "Cumulative TE cost\n" + "P2MP IGP metric\n" + "P2MP TE metric\n" + "P2MP hop count metric\n" + "Segment-ID (SID) Depth.\n" + "Path Delay metric\n" + "Path Delay Variation metric\n" + "Path Loss metric\n" + "P2MP Path Delay metric\n" + "P2MP Path Delay variation metric\n" + "P2MP Path Loss metric\n" + "Number of adaptations on a path\n" + "Number of layers on a path\n" + "Domain Count metric\n" + "Border Node Count metric\n" + "Metric value\n" + "Required constraint\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + snprintf(xpath, sizeof(xpath), "./constraints/metrics[type='%s']", + type); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_policy_no_candidate, + srte_policy_no_candidate_cmd, + "no candidate-path\ + preference (0-4294967295)$preference\ + [name WORD\ + <\ + explicit segment-list WORD\ + |dynamic\ + >]", + NO_STR + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Explicit Path\n" + "List of SIDs\n" + "Name of the Segment List\n" + "Dynamic Path\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, "./candidate-path[preference='%s']", + preference_str); +} + +DEFPY(srte_candidate_objfun, + srte_candidate_objfun_cmd, + "objective-function $type [required$required]", + "Define an objective function constraint\n" + "Minimum Cost Path\n" + "Minimum Load Path\n" + "Maximum residual Bandwidth Path\n" + "Minimize aggregate Bandwidth Consumption\n" + "Minimize the Load of the most loaded Link\n" + "Minimize the Cumulative Cost of a set of paths\n" + "Shortest Path Tree\n" + "Minimum Cost Tree\n" + "Minimum Packet Loss Path\n" + "Maximum Under-Utilized Path\n" + "Maximum Reserved Under-Utilized Path\n" + "Minimize the number of Transit Domains\n" + "Minimize the number of Border Nodes\n" + "Minimize the number of Common Transit Domains\n" + "Minimize the number of Shared Links\n" + "Minimize the number of Shared SRLGs\n" + "Minimize the number of Shared Nodes\n" + "Required constraint\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + nb_cli_enqueue_change(vty, "./constraints/objective-function", + NB_OP_DESTROY, NULL); + snprintf(xpath, sizeof(xpath), + "./constraints/objective-function/required"); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + required ? "true" : "false"); + nb_cli_enqueue_change(vty, "./constraints/objective-function/type", + NB_OP_MODIFY, type); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_objfun, + srte_candidate_no_objfun_cmd, + "no objective-function [] [required$required]", + NO_STR + "Remove an objective function constraint\n" + "Minimum Cost Path\n" + "Minimum Load Path\n" + "Maximum residual Bandwidth Path\n" + "Minimize aggregate Bandwidth Consumption\n" + "Minimize the Load of the most loaded Link\n" + "Minimize the Cumulative Cost of a set of paths\n" + "Shortest Path Tree\n" + "Minimum Cost Tree\n" + "Minimum Packet Loss Path\n" + "Maximum Under-Utilized Path\n" + "Maximum Reserved Under-Utilized Path\n" + "Minimize the number of Transit Domains\n" + "Minimize the number of Border Nodes\n" + "Minimize the number of Common Transit Domains\n" + "Minimize the number of Shared Links\n" + "Minimize the number of Shared SRLGs\n" + "Minimize the number of Shared Nodes\n" + "Required constraint\n") +{ + nb_cli_enqueue_change(vty, "./constraints/objective-function", + NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +static const char *objfun_type_name(enum objfun_type type) +{ + switch (type) { + case OBJFUN_MCP: + return "mcp"; + case OBJFUN_MLP: + return "mlp"; + case OBJFUN_MBP: + return "mbp"; + case OBJFUN_MBC: + return "mbc"; + case OBJFUN_MLL: + return "mll"; + case OBJFUN_MCC: + return "mcc"; + case OBJFUN_SPT: + return "spt"; + case OBJFUN_MCT: + return "mct"; + case OBJFUN_MPLP: + return "mplp"; + case OBJFUN_MUP: + return "mup"; + case OBJFUN_MRUP: + return "mrup"; + case OBJFUN_MTD: + return "mtd"; + case OBJFUN_MBN: + return "mbn"; + case OBJFUN_MCTD: + return "mctd"; + case OBJFUN_MSL: + return "msl"; + case OBJFUN_MSS: + return "mss"; + case OBJFUN_MSN: + return "msn"; + default: + return NULL; + } +} + +DEFPY_NOSH(show_debugging_pathd, show_debugging_pathd_cmd, + "show debugging [pathd]", + SHOW_STR + "State of each debugging option\n" + "pathd module debugging\n") +{ + /* nothing to do here */ + return CMD_SUCCESS; +} + +static const char *metric_type_name(enum srte_candidate_metric_type type) +{ + switch (type) { + case SRTE_CANDIDATE_METRIC_TYPE_IGP: + return "igp"; + case SRTE_CANDIDATE_METRIC_TYPE_TE: + return "te"; + case SRTE_CANDIDATE_METRIC_TYPE_HC: + return "hc"; + case SRTE_CANDIDATE_METRIC_TYPE_ABC: + return "abc"; + case SRTE_CANDIDATE_METRIC_TYPE_LMLL: + return "lmll"; + case SRTE_CANDIDATE_METRIC_TYPE_CIGP: + return "cigp"; + case SRTE_CANDIDATE_METRIC_TYPE_CTE: + return "cte"; + case SRTE_CANDIDATE_METRIC_TYPE_PIGP: + return "pigp"; + case SRTE_CANDIDATE_METRIC_TYPE_PTE: + return "pte"; + case SRTE_CANDIDATE_METRIC_TYPE_PHC: + return "phc"; + case SRTE_CANDIDATE_METRIC_TYPE_MSD: + return "msd"; + case SRTE_CANDIDATE_METRIC_TYPE_PD: + return "pd"; + case SRTE_CANDIDATE_METRIC_TYPE_PDV: + return "pdv"; + case SRTE_CANDIDATE_METRIC_TYPE_PL: + return "pl"; + case SRTE_CANDIDATE_METRIC_TYPE_PPD: + return "ppd"; + case SRTE_CANDIDATE_METRIC_TYPE_PPDV: + return "ppdv"; + case SRTE_CANDIDATE_METRIC_TYPE_PPL: + return "ppl"; + case SRTE_CANDIDATE_METRIC_TYPE_NAP: + return "nap"; + case SRTE_CANDIDATE_METRIC_TYPE_NLP: + return "nlp"; + case SRTE_CANDIDATE_METRIC_TYPE_DC: + return "dc"; + case SRTE_CANDIDATE_METRIC_TYPE_BNC: + return "bnc"; + default: + return NULL; + } +} + +static void config_write_float(struct vty *vty, float value) +{ + if (fabs(truncf(value) - value) < FLT_EPSILON) { + vty_out(vty, " %d", (int)value); + return; + } else { + vty_out(vty, " %f", value); + } +} + +static void config_write_metric(struct vty *vty, + enum srte_candidate_metric_type type, + float value, bool required, bool is_bound) +{ + const char *name = metric_type_name(type); + if (name == NULL) + return; + vty_out(vty, " metric %s%s", is_bound ? "bound " : "", + metric_type_name(type)); + config_write_float(vty, value); + vty_out(vty, required ? " required" : ""); + vty_out(vty, "\n"); +} + +static int config_write_metric_cb(const struct lyd_node *dnode, void *arg) +{ + struct vty *vty = arg; + enum srte_candidate_metric_type type; + bool required, is_bound = false; + float value; + + type = yang_dnode_get_enum(dnode, "./type"); + value = (float)yang_dnode_get_dec64(dnode, "./value"); + required = yang_dnode_get_bool(dnode, "./required"); + if (yang_dnode_exists(dnode, "./is-bound")) + is_bound = yang_dnode_get_bool(dnode, "./is-bound"); + + config_write_metric(vty, type, value, required, is_bound); + return YANG_ITER_CONTINUE; +} + +void cli_show_srte_policy_candidate_path(struct vty *vty, + struct lyd_node *dnode, + bool show_defaults) +{ + float bandwidth; + uint32_t affinity; + bool required; + enum objfun_type objfun_type; + const char *type = yang_dnode_get_string(dnode, "./type"); + + vty_out(vty, " candidate-path preference %s name %s %s", + yang_dnode_get_string(dnode, "./preference"), + yang_dnode_get_string(dnode, "./name"), type); + if (strmatch(type, "explicit")) + vty_out(vty, " segment-list %s", + yang_dnode_get_string(dnode, "./segment-list-name")); + vty_out(vty, "\n"); + + if (strmatch(type, "dynamic")) { + if (yang_dnode_exists(dnode, "./constraints/bandwidth")) { + bandwidth = (float)yang_dnode_get_dec64( + dnode, "./constraints/bandwidth/value"); + required = yang_dnode_get_bool( + dnode, "./constraints/bandwidth/required"); + vty_out(vty, " %sbandwidth", + required ? "required " : ""); + config_write_float(vty, bandwidth); + vty_out(vty, "\n"); + } + if (yang_dnode_exists(dnode, + "./constraints/affinity/exclude-any")) { + affinity = yang_dnode_get_uint32( + dnode, "./constraints/affinity/exclude-any"); + vty_out(vty, " affinity exclude-any 0x%08x\n", + affinity); + } + if (yang_dnode_exists(dnode, + "./constraints/affinity/include-any")) { + affinity = yang_dnode_get_uint32( + dnode, "./constraints/affinity/include-any"); + vty_out(vty, " affinity include-any 0x%08x\n", + affinity); + } + if (yang_dnode_exists(dnode, + "./constraints/affinity/include-all")) { + affinity = yang_dnode_get_uint32( + dnode, "./constraints/affinity/include-all"); + vty_out(vty, " affinity include-all 0x%08x\n", + affinity); + } + yang_dnode_iterate(config_write_metric_cb, vty, dnode, + "./constraints/metrics"); + if (yang_dnode_exists(dnode, + "./constraints/objective-function")) { + objfun_type = yang_dnode_get_enum(dnode, + "./constraints/objective-function/type"); + required = yang_dnode_get_bool(dnode, + "./constraints/objective-function/required"); + vty_out(vty, " objective-function %s%s\n", + objfun_type_name(objfun_type), + required ? " required" : ""); + } + } +} + +static int config_write_dnode(const struct lyd_node *dnode, void *arg) +{ + struct vty *vty = arg; + + nb_cli_show_dnode_cmds(vty, (struct lyd_node *)dnode, false); + + return YANG_ITER_CONTINUE; +} + +int config_write_segment_routing(struct vty *vty) +{ + vty_out(vty, "segment-routing\n"); + return 1; +} + +int config_write_traffic_eng(struct vty *vty) +{ + vty_out(vty, " traffic-eng\n"); + return 1; +} + +int config_write_segment_lists(struct vty *vty) +{ + yang_dnode_iterate(config_write_dnode, vty, running_config->dnode, + "/frr-pathd:pathd/srte/segment-list"); + + return 1; +} + +int config_write_sr_policies(struct vty *vty) +{ + yang_dnode_iterate(config_write_dnode, vty, running_config->dnode, + "/frr-pathd:pathd/srte/policy"); + + return 1; +} + +void path_cli_init(void) +{ + install_node(&segment_routing_node); + install_node(&sr_traffic_eng_node); + install_node(&srte_segment_list_node); + install_node(&srte_policy_node); + install_node(&srte_candidate_dyn_node); + install_default(SEGMENT_ROUTING_NODE); + install_default(SR_TRAFFIC_ENG_NODE); + install_default(SR_SEGMENT_LIST_NODE); + install_default(SR_POLICY_NODE); + install_default(SR_CANDIDATE_DYN_NODE); + + install_element(ENABLE_NODE, &show_debugging_pathd_cmd); + install_element(ENABLE_NODE, &show_srte_policy_cmd); + install_element(ENABLE_NODE, &show_srte_policy_detail_cmd); + + install_element(CONFIG_NODE, &segment_routing_cmd); + install_element(SEGMENT_ROUTING_NODE, &sr_traffic_eng_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_segment_list_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_no_segment_list_cmd); + install_element(SR_SEGMENT_LIST_NODE, + &srte_segment_list_segment_cmd); + install_element(SR_SEGMENT_LIST_NODE, + &srte_segment_list_no_segment_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_policy_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_no_policy_cmd); + install_element(SR_POLICY_NODE, &srte_policy_name_cmd); + install_element(SR_POLICY_NODE, &srte_policy_no_name_cmd); + install_element(SR_POLICY_NODE, &srte_policy_binding_sid_cmd); + install_element(SR_POLICY_NODE, &srte_policy_no_binding_sid_cmd); + install_element(SR_POLICY_NODE, &srte_policy_candidate_exp_cmd); + install_element(SR_POLICY_NODE, &srte_policy_candidate_dyn_cmd); + install_element(SR_POLICY_NODE, &srte_policy_no_candidate_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_bandwidth_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_bandwidth_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_affinity_filter_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_affinity_filter_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_metric_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_metric_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_objfun_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_objfun_cmd); +} diff --git a/pathd/path_debug.c b/pathd/path_debug.c new file mode 100644 index 0000000000..df0550715a --- /dev/null +++ b/pathd/path_debug.c @@ -0,0 +1,121 @@ +/* + * 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_debug.h" + +THREAD_DATA char _debug_buff[DEBUG_BUFF_SIZE]; + +/** + * Gives the string representation of an srte_protocol_origin enum value. + * + * @param origin The enum value to convert to string + * @return a constant string representation of the enum value + */ +const char *srte_protocol_origin_name(enum srte_protocol_origin origin) +{ + switch (origin) { + case SRTE_ORIGIN_UNDEFINED: + return "UNDEFINED"; + case SRTE_ORIGIN_PCEP: + return "PCEP"; + case SRTE_ORIGIN_BGP: + return "BGP"; + case SRTE_ORIGIN_LOCAL: + return "LOCAL"; + default: + return "UNKNOWN"; + } +} + +/** + * Gives the string representation of an srte_candidate_type enum value. + * + * @param origin The enum value to convert to string + * @return a constant string representation of the enum value + */ +const char *srte_candidate_type_name(enum srte_candidate_type type) +{ + switch (type) { + case SRTE_CANDIDATE_TYPE_EXPLICIT: + return "EXPLICIT"; + case SRTE_CANDIDATE_TYPE_DYNAMIC: + return "DYNAMIC"; + case SRTE_CANDIDATE_TYPE_UNDEFINED: + return "UNDEFINED"; + default: + return "UNKNOWN"; + } +} + +/** + * Gives the string representation of an objfun_type enum value. + * + * @param origin The enum value to convert to string + * @return a constant string representation of the enum value + */ +const char *objfun_type_name(enum objfun_type type) +{ + switch (type) { + case OBJFUN_UNDEFINED: + return "UNDEFINED"; + case OBJFUN_MCP: + return "MCP"; + case OBJFUN_MLP: + return "MLP"; + case OBJFUN_MBP: + return "MBP"; + case OBJFUN_MBC: + return "MBC"; + case OBJFUN_MLL: + return "MLL"; + case OBJFUN_MCC: + return "MCC"; + case OBJFUN_SPT: + return "SPT"; + case OBJFUN_MCT: + return "MCT"; + case OBJFUN_MPLP: + return "MPLP"; + case OBJFUN_MUP: + return "MUP"; + case OBJFUN_MRUP: + return "MRUP"; + case OBJFUN_MTD: + return "MTD"; + case OBJFUN_MBN: + return "MBN"; + case OBJFUN_MCTD: + return "MCTD"; + case OBJFUN_MSL: + return "MSL"; + case OBJFUN_MSS: + return "MSS"; + case OBJFUN_MSN: + return "MSN"; + default: + return "UNKNOWN"; + } +} diff --git a/pathd/path_debug.h b/pathd/path_debug.h new file mode 100644 index 0000000000..d9cfcb6ba2 --- /dev/null +++ b/pathd/path_debug.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 NetDEF, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PATH_DEBUG_H_ +#define _PATH_DEBUG_H_ + +#include "pathd/pathd.h" + +#ifdef __GNUC__ +#define THREAD_DATA __thread +#else +#define THREAD_DATA +#endif + +#define DEBUG_IDENT_SIZE 4 +#define DEBUG_BUFF_SIZE 4096 +#define TUP(A, B) ((((uint32_t)(A)) << 16) | ((uint32_t)(B))) +#define PATHD_FORMAT_INIT() _debug_buff[0] = 0 +#define PATHD_FORMAT(fmt, ...) \ + csnprintfrr(_debug_buff, DEBUG_BUFF_SIZE, fmt, ##__VA_ARGS__) +#define PATHD_FORMAT_FINI() _debug_buff + +extern THREAD_DATA char _debug_buff[DEBUG_BUFF_SIZE]; + +const char *srte_protocol_origin_name(enum srte_protocol_origin origin); +const char *srte_candidate_type_name(enum srte_candidate_type type); +const char *objfun_type_name(enum objfun_type type); + +#endif // _PATH_DEBUG_H_ \ No newline at end of file diff --git a/pathd/path_errors.c b/pathd/path_errors.c new file mode 100644 index 0000000000..f266b6b4ea --- /dev/null +++ b/pathd/path_errors.c @@ -0,0 +1,43 @@ +/* + * pathd-specific error messages. + * Copyright (C) 2020 NetDEF, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "lib/ferr.h" +#include "path_errors.h" + +/* clang-format off */ +static struct log_ref ferr_path_err[] = { + { + .code = END_FERR, + } +}; + +static struct log_ref ferr_path_warn[] = { + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void path_error_init(void) +{ + log_ref_add(ferr_path_err); + log_ref_add(ferr_path_warn); +} diff --git a/pathd/path_errors.h b/pathd/path_errors.h new file mode 100644 index 0000000000..366878f300 --- /dev/null +++ b/pathd/path_errors.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 NetDEF, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __PATH_ERRORS_H__ +#define __PATH_ERRORS_H__ + +#include "lib/ferr.h" + +enum path_log_refs { + EC_PATH_PCEP_INIT = PATH_FERR_START, +}; + +extern void path_error_init(void); + +#endif diff --git a/pathd/path_main.c b/pathd/path_main.c new file mode 100644 index 0000000000..8b7d4aba48 --- /dev/null +++ b/pathd/path_main.c @@ -0,0 +1,154 @@ +/* + * 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 "getopt.h" +#include "thread.h" +#include "command.h" +#include "log.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "libfrr.h" +#include "vrf.h" +#include "filter.h" + +#include "pathd.h" +#include "path_nb.h" +#include "path_zebra.h" +#include "path_errors.h" + +char backup_config_file[256]; + +zebra_capabilities_t _caps_p[] = {}; + +struct zebra_privs_t pathd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +struct option longopts[] = {{0}}; + +/* Master of threads. */ +struct thread_master *master; + +static struct frr_daemon_info pathd_di; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); + + /* Reload config file. */ + vty_read_config(NULL, pathd_di.config_file, config_default); +} + +/* SIGINT / SIGTERM handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct quagga_signal_t path_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *pathd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_pathd_info, +}; + +#define PATH_VTY_PORT 2621 + +FRR_DAEMON_INFO(pathd, PATH, .vty_port = PATH_VTY_PORT, + + .proghelp = "Implementation of PATH.", + + .signals = path_signals, .n_signals = array_size(path_signals), + + .privs = &pathd_privs, .yang_modules = pathd_yang_modules, + .n_yang_modules = array_size(pathd_yang_modules), ) + +int main(int argc, char **argv, char **envp) +{ + frr_preinit(&pathd_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + break; + } + } + + master = frr_init(); + + access_list_init(); + + path_error_init(); + path_zebra_init(master); + path_cli_init(); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/pathd/path_memory.c b/pathd/path_memory.c new file mode 100644 index 0000000000..ad4904a298 --- /dev/null +++ b/pathd/path_memory.c @@ -0,0 +1,25 @@ +/* + * 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_memory.h" + +DEFINE_MGROUP(PATHD, "pathd") diff --git a/pathd/path_memory.h b/pathd/path_memory.h new file mode 100644 index 0000000000..e2f6787f66 --- /dev/null +++ b/pathd/path_memory.h @@ -0,0 +1,26 @@ +/* + * 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_MEMORY_H_ +#define _FRR_PATH_MEMORY_H_ + +#include "memory.h" + +DECLARE_MGROUP(PATHD) + +#endif /* _FRR_PATH_MEMORY_H_ */ diff --git a/pathd/path_nb.c b/pathd/path_nb.c new file mode 100644 index 0000000000..a210e31b9c --- /dev/null +++ b/pathd/path_nb.c @@ -0,0 +1,351 @@ +/* + * 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 "northbound.h" +#include "libfrr.h" + +#include "pathd/path_nb.h" + +static int iter_objfun_cb(const struct lyd_node *dnode, void *arg); +static int dummy_create(struct nb_cb_create_args *args); +static int dummy_modify(struct nb_cb_modify_args *args); +static int dummy_destroy(struct nb_cb_destroy_args *args); + +struct of_cb_pref { + uint32_t index; + enum objfun_type type; + struct of_cb_pref *next; +}; + +struct of_cb_args { + struct of_cb_pref *first; + uint32_t free_slot; + struct of_cb_pref prefs[MAX_OBJFUN_TYPE]; +}; + +/* clang-format off */ +const struct frr_yang_module_info frr_pathd_info = { + .name = "frr-pathd", + .nodes = { + { + .xpath = "/frr-pathd:pathd", + .cbs = { + .apply_finish = pathd_apply_finish, + }, + .priority = NB_DFLT_PRIORITY + 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list", + .cbs = { + .create = pathd_srte_segment_list_create, + .cli_show = cli_show_srte_segment_list, + .destroy = pathd_srte_segment_list_destroy, + .get_next = pathd_srte_segment_list_get_next, + .get_keys = pathd_srte_segment_list_get_keys, + .lookup_entry = pathd_srte_segment_list_lookup_entry, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/protocol-origin", + .cbs = { + .modify = pathd_srte_segment_list_protocol_origin_modify, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/originator", + .cbs = { + .modify = pathd_srte_segment_list_originator_modify, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment", + .cbs = { + .create = pathd_srte_segment_list_segment_create, + .cli_show = cli_show_srte_segment_list_segment, + .destroy = pathd_srte_segment_list_segment_destroy, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/sid-value", + .cbs = { + .modify = pathd_srte_segment_list_segment_sid_value_modify, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_segment_list_segment_nai_destroy, + .apply_finish = pathd_srte_segment_list_segment_nai_apply_finish + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/type", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/local-address", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/local-interface", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/remote-address", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/remote-interface", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy", + .cbs = { + .create = pathd_srte_policy_create, + .cli_show = cli_show_srte_policy, + .destroy = pathd_srte_policy_destroy, + .get_next = pathd_srte_policy_get_next, + .get_keys = pathd_srte_policy_get_keys, + .lookup_entry = pathd_srte_policy_lookup_entry, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/name", + .cbs = { + .modify = pathd_srte_policy_name_modify, + .cli_show = cli_show_srte_policy_name, + .destroy = pathd_srte_policy_name_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/binding-sid", + .cbs = { + .modify = pathd_srte_policy_binding_sid_modify, + .cli_show = cli_show_srte_policy_binding_sid, + .destroy = pathd_srte_policy_binding_sid_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/is-operational", + .cbs = { + .get_elem = pathd_srte_policy_is_operational_get_elem + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path", + .cbs = { + .create = pathd_srte_policy_candidate_path_create, + .cli_show = cli_show_srte_policy_candidate_path, + .destroy = pathd_srte_policy_candidate_path_destroy, + .get_next = pathd_srte_policy_candidate_path_get_next, + .get_keys = pathd_srte_policy_candidate_path_get_keys, + .lookup_entry = pathd_srte_policy_candidate_path_lookup_entry, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/name", + .cbs = { + .modify = pathd_srte_policy_candidate_path_name_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/is-best-candidate-path", + .cbs = { + .get_elem = pathd_srte_policy_candidate_path_is_best_candidate_path_get_elem, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/protocol-origin", + .cbs = { + .modify = pathd_srte_policy_candidate_path_protocol_origin_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/originator", + .cbs = { + .modify = pathd_srte_policy_candidate_path_originator_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/discriminator", + .cbs = { + .get_elem = pathd_srte_policy_candidate_path_discriminator_get_elem, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/type", + .cbs = { + .modify = pathd_srte_policy_candidate_path_type_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/segment-list-name", + .cbs = { + .destroy = pathd_srte_policy_candidate_path_segment_list_name_destroy, + .modify = pathd_srte_policy_candidate_path_segment_list_name_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_policy_candidate_path_bandwidth_destroy, + .apply_finish = pathd_srte_policy_candidate_path_bandwidth_apply_finish + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth/required", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth/value", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/exclude-any", + .cbs = { + .modify = pathd_srte_policy_candidate_path_exclude_any_modify, + .destroy = pathd_srte_policy_candidate_path_exclude_any_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-any", + .cbs = { + .modify = pathd_srte_policy_candidate_path_include_any_modify, + .destroy = pathd_srte_policy_candidate_path_include_any_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-all", + .cbs = { + .modify = pathd_srte_policy_candidate_path_include_all_modify, + .destroy = pathd_srte_policy_candidate_path_include_all_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_policy_candidate_path_metrics_destroy, + .apply_finish = pathd_srte_policy_candidate_path_metrics_apply_finish + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/value", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/required", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/is-bound", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/is-computed", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_policy_candidate_path_objfun_destroy, + .apply_finish = pathd_srte_policy_candidate_path_objfun_apply_finish + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function/required", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function/type", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = NULL, + }, + } +}; + +void iter_objfun_prefs(const struct lyd_node *dnode, const char* path, + of_pref_cp_t fun, void *arg) +{ + struct of_cb_args args = {0}; + struct of_cb_pref *p; + + yang_dnode_iterate(iter_objfun_cb, &args, dnode, path); + for (p = args.first; p != NULL; p = p->next) + fun(p->type, arg); +} + +int iter_objfun_cb(const struct lyd_node *dnode, void *arg) +{ + struct of_cb_args *of_arg = arg; + struct of_cb_pref *pref; + struct of_cb_pref **p; + + if (of_arg->free_slot >= MAX_OBJFUN_TYPE) + return YANG_ITER_STOP; + + pref = &of_arg->prefs[of_arg->free_slot++]; + + pref->index = yang_dnode_get_uint32(dnode, "./index"); + pref->type = yang_dnode_get_enum(dnode, "./type"); + + /* Simplistic insertion sort */ + p = &of_arg->first; + while (true) { + if (*p == NULL) { + *p = pref; + break; + } + if ((*p)->index >= pref->index) { + pref->next = *p; + *p = pref; + break; + } + p = &(*p)->next; + } + + return YANG_ITER_CONTINUE; +} + +int dummy_create(struct nb_cb_create_args *args) +{ + return NB_OK; +} + +int dummy_modify(struct nb_cb_modify_args *args) +{ + return NB_OK; +} + +int dummy_destroy(struct nb_cb_destroy_args *args) +{ + return NB_OK; +} diff --git a/pathd/path_nb.h b/pathd/path_nb.h new file mode 100644 index 0000000000..3a0b3863ce --- /dev/null +++ b/pathd/path_nb.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 NetDEF, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_PATH_NB_H_ +#define _FRR_PATH_NB_H_ + +#include "pathd/pathd.h" + +extern const struct frr_yang_module_info frr_pathd_info; + +/* Mandatory callbacks. */ +int pathd_srte_segment_list_create(struct nb_cb_create_args *args); +int pathd_srte_segment_list_destroy(struct nb_cb_destroy_args *args); + +const void *pathd_srte_segment_list_get_next(struct nb_cb_get_next_args *args); +int pathd_srte_segment_list_get_keys(struct nb_cb_get_keys_args *args); +const void * +pathd_srte_segment_list_lookup_entry(struct nb_cb_lookup_entry_args *args); + +int pathd_srte_segment_list_segment_create(struct nb_cb_create_args *args); +int pathd_srte_segment_list_segment_destroy(struct nb_cb_destroy_args *args); +int pathd_srte_segment_list_protocol_origin_modify( + struct nb_cb_modify_args *args); +int pathd_srte_segment_list_originator_modify(struct nb_cb_modify_args *args); +int pathd_srte_segment_list_segment_sid_value_modify( + struct nb_cb_modify_args *args); +int pathd_srte_segment_list_segment_nai_destroy( + struct nb_cb_destroy_args *args); +void pathd_srte_segment_list_segment_nai_apply_finish( + struct nb_cb_apply_finish_args *args); +int pathd_srte_policy_create(struct nb_cb_create_args *args); +int pathd_srte_policy_destroy(struct nb_cb_destroy_args *args); +const void *pathd_srte_policy_get_next(struct nb_cb_get_next_args *args); +int pathd_srte_policy_get_keys(struct nb_cb_get_keys_args *args); +const void * +pathd_srte_policy_lookup_entry(struct nb_cb_lookup_entry_args *args); +int pathd_srte_policy_name_modify(struct nb_cb_modify_args *args); +int pathd_srte_policy_name_destroy(struct nb_cb_destroy_args *args); +int pathd_srte_policy_binding_sid_modify(struct nb_cb_modify_args *args); +int pathd_srte_policy_binding_sid_destroy(struct nb_cb_destroy_args *args); +struct yang_data * +pathd_srte_policy_is_operational_get_elem(struct nb_cb_get_elem_args *args); +int pathd_srte_policy_candidate_path_create(struct nb_cb_create_args *args); +int pathd_srte_policy_candidate_path_destroy(struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_name_modify( + struct nb_cb_modify_args *args); +const void * +pathd_srte_policy_candidate_path_get_next(struct nb_cb_get_next_args *args); +int pathd_srte_policy_candidate_path_get_keys(struct nb_cb_get_keys_args *args); +const void *pathd_srte_policy_candidate_path_lookup_entry( + struct nb_cb_lookup_entry_args *args); +void pathd_srte_policy_candidate_path_bandwidth_apply_finish( + struct nb_cb_apply_finish_args *args); +int pathd_srte_policy_candidate_path_bandwidth_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_exclude_any_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_exclude_any_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_include_any_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_include_any_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_include_all_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_include_all_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_metrics_destroy( + struct nb_cb_destroy_args *args); +void pathd_srte_policy_candidate_path_metrics_apply_finish( + struct nb_cb_apply_finish_args *args); +int pathd_srte_policy_candidate_path_objfun_destroy( + struct nb_cb_destroy_args *args); +void pathd_srte_policy_candidate_path_objfun_apply_finish( + struct nb_cb_apply_finish_args *args); +struct yang_data * +pathd_srte_policy_candidate_path_is_best_candidate_path_get_elem( + struct nb_cb_get_elem_args *args); +int pathd_srte_policy_candidate_path_protocol_origin_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_originator_modify( + struct nb_cb_modify_args *args); +struct yang_data *pathd_srte_policy_candidate_path_discriminator_get_elem( + struct nb_cb_get_elem_args *args); +int pathd_srte_policy_candidate_path_type_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_segment_list_name_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_segment_list_name_destroy( + struct nb_cb_destroy_args *args); + +/* Optional 'apply_finish' callbacks. */ +void pathd_apply_finish(struct nb_cb_apply_finish_args *args); + +/* Optional 'cli_show' callbacks. */ +void cli_show_srte_segment_list(struct vty *vty, struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_segment_list_segment(struct vty *vty, struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy(struct vty *vty, struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_name(struct vty *vty, struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_binding_sid(struct vty *vty, struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_candidate_path(struct vty *vty, + struct lyd_node *dnode, + bool show_defaults); + +/* Utility functions */ +typedef void (*of_pref_cp_t)(enum objfun_type type, void *arg); +void iter_objfun_prefs(const struct lyd_node *dnode, const char *path, + of_pref_cp_t fun, void *arg); + +#endif /* _FRR_PATH_NB_H_ */ diff --git a/pathd/path_nb_config.c b/pathd/path_nb_config.c new file mode 100644 index 0000000000..669db169ae --- /dev/null +++ b/pathd/path_nb_config.c @@ -0,0 +1,731 @@ +/* + * 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 "northbound.h" +#include "libfrr.h" + +#include "pathd/path_zebra.h" +#include "pathd/path_nb.h" + +/* + * XPath: /frr-pathd:pathd + */ +void pathd_apply_finish(struct nb_cb_apply_finish_args *args) +{ + srte_apply_changes(); +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list + */ +int pathd_srte_segment_list_create(struct nb_cb_create_args *args) +{ + struct srte_segment_list *segment_list; + const char *name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "./name"); + segment_list = srte_segment_list_add(name); + nb_running_set_entry(args->dnode, segment_list); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_NEW); + + return NB_OK; +} + +int pathd_srte_segment_list_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_segment_list *segment_list; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_unset_entry(args->dnode); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_DELETED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/protocol-origin + */ +int pathd_srte_segment_list_protocol_origin_modify( + struct nb_cb_modify_args *args) +{ + struct srte_segment_list *segment_list; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_get_entry(args->dnode, NULL, true); + segment_list->protocol_origin = yang_dnode_get_enum(args->dnode, NULL); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/originator + */ +int pathd_srte_segment_list_originator_modify(struct nb_cb_modify_args *args) +{ + struct srte_segment_list *segment_list; + const char *originator; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_get_entry(args->dnode, NULL, true); + originator = yang_dnode_get_string(args->dnode, NULL); + strlcpy(segment_list->originator, originator, + sizeof(segment_list->originator)); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/segment + */ +int pathd_srte_segment_list_segment_create(struct nb_cb_create_args *args) +{ + struct srte_segment_list *segment_list; + struct srte_segment_entry *segment; + uint32_t index; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_get_entry(args->dnode, NULL, true); + index = yang_dnode_get_uint32(args->dnode, "./index"); + segment = srte_segment_entry_add(segment_list, index); + nb_running_set_entry(args->dnode, segment); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + +int pathd_srte_segment_list_segment_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_unset_entry(args->dnode); + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + srte_segment_entry_del(segment); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/segment/sid-value + */ +int pathd_srte_segment_list_segment_sid_value_modify( + struct nb_cb_modify_args *args) +{ + mpls_label_t sid_value; + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_get_entry(args->dnode, NULL, true); + sid_value = yang_dnode_get_uint32(args->dnode, NULL); + segment->sid_value = sid_value; + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + +int pathd_srte_segment_list_segment_nai_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_get_entry(args->dnode, NULL, true); + segment->nai_type = SRTE_SEGMENT_NAI_TYPE_NONE; + segment->nai_local_addr.ipa_type = IPADDR_NONE; + segment->nai_local_iface = 0; + segment->nai_remote_addr.ipa_type = IPADDR_NONE; + segment->nai_remote_iface = 0; + + return NB_OK; +} + +void pathd_srte_segment_list_segment_nai_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_segment_entry *segment; + enum srte_segment_nai_type type; + struct ipaddr local_addr, remote_addr; + uint32_t local_iface = 0, remote_iface = 0; + + segment = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "./type"); + + yang_dnode_get_ip(&local_addr, args->dnode, "./local-address"); + + switch (type) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + yang_dnode_get_ip(&remote_addr, args->dnode, + "./remote-address"); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + yang_dnode_get_ip(&remote_addr, args->dnode, + "./remote-address"); + local_iface = + yang_dnode_get_uint32(args->dnode, "./local-interface"); + remote_iface = yang_dnode_get_uint32(args->dnode, + "./remote-interface"); + break; + default: + break; + } + + srte_segment_entry_set_nai(segment, type, &local_addr, local_iface, + &remote_addr, remote_iface); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy + */ +int pathd_srte_policy_create(struct nb_cb_create_args *args) +{ + struct srte_policy *policy; + uint32_t color; + struct ipaddr endpoint; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + color = yang_dnode_get_uint32(args->dnode, "./color"); + yang_dnode_get_ip(&endpoint, args->dnode, "./endpoint"); + policy = srte_policy_add(color, &endpoint); + + nb_running_set_entry(args->dnode, policy); + SET_FLAG(policy->flags, F_POLICY_NEW); + + return NB_OK; +} + +int pathd_srte_policy_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_policy *policy; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_unset_entry(args->dnode); + SET_FLAG(policy->flags, F_POLICY_DELETED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/name + */ +int pathd_srte_policy_name_modify(struct nb_cb_modify_args *args) +{ + struct srte_policy *policy; + const char *name; + + if (args->event != NB_EV_APPLY && args->event != NB_EV_VALIDATE) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + + if (args->event == NB_EV_VALIDATE) { + /* the policy name is fixed after setting it once */ + if (strlen(policy->name) > 0) { + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "The SR Policy name is fixed!"); + return NB_ERR_RESOURCE; + } else + return NB_OK; + } + + name = yang_dnode_get_string(args->dnode, NULL); + strlcpy(policy->name, name, sizeof(policy->name)); + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + + return NB_OK; +} + +int pathd_srte_policy_name_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_policy *policy; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + policy->name[0] = '\0'; + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/binding-sid + */ +int pathd_srte_policy_binding_sid_modify(struct nb_cb_modify_args *args) +{ + struct srte_policy *policy; + mpls_label_t binding_sid; + + policy = nb_running_get_entry(args->dnode, NULL, true); + binding_sid = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + break; + case NB_EV_PREPARE: + if (path_zebra_request_label(binding_sid) < 0) + return NB_ERR_RESOURCE; + break; + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + srte_policy_update_binding_sid(policy, binding_sid); + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + break; + } + + return NB_OK; +} + +int pathd_srte_policy_binding_sid_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_policy *policy; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + srte_policy_update_binding_sid(policy, MPLS_LABEL_NONE); + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path + */ +int pathd_srte_policy_candidate_path_create(struct nb_cb_create_args *args) +{ + struct srte_policy *policy; + struct srte_candidate *candidate; + uint32_t preference; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + preference = yang_dnode_get_uint32(args->dnode, "./preference"); + candidate = srte_candidate_add(policy, preference); + nb_running_set_entry(args->dnode, candidate); + SET_FLAG(candidate->flags, F_CANDIDATE_NEW); + + return NB_OK; +} + +int pathd_srte_policy_candidate_path_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_unset_entry(args->dnode); + SET_FLAG(candidate->flags, F_CANDIDATE_DELETED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/name + */ +int pathd_srte_policy_candidate_path_name_modify(struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + const char *name; + char xpath[XPATH_MAXLEN]; + char xpath_buf[XPATH_MAXLEN - 3]; + + if (args->event != NB_EV_APPLY && args->event != NB_EV_VALIDATE) + return NB_OK; + + /* the candidate name is fixed after setting it once, this is checked + * here */ + if (args->event == NB_EV_VALIDATE) { + /* first get the precise path to the candidate path */ + yang_dnode_get_path(args->dnode, xpath_buf, sizeof(xpath_buf)); + snprintf(xpath, sizeof(xpath), "%s%s", xpath_buf, "/.."); + + candidate = nb_running_get_entry_non_rec(NULL, xpath, false); + + /* then check if it exists and if the name was provided */ + if (candidate && strlen(candidate->name) > 0) { + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "The candidate name is fixed!"); + return NB_ERR_RESOURCE; + } else + return NB_OK; + } + + candidate = nb_running_get_entry(args->dnode, NULL, true); + + name = yang_dnode_get_string(args->dnode, NULL); + strlcpy(candidate->name, name, sizeof(candidate->name)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + + +static int affinity_filter_modify(struct nb_cb_modify_args *args, + enum affinity_filter_type type) +{ + uint32_t filter; + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + filter = yang_dnode_get_uint32(args->dnode, NULL); + srte_candidate_set_affinity_filter(candidate, type, filter); + + return NB_OK; +} + +static int affinity_filter_destroy(struct nb_cb_destroy_args *args, + enum affinity_filter_type type) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + srte_candidate_unset_affinity_filter(candidate, type); + + return NB_OK; +} + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/exclude-any + */ + +int pathd_srte_policy_candidate_path_exclude_any_modify( + struct nb_cb_modify_args *args) +{ + return affinity_filter_modify(args, AFFINITY_FILTER_EXCLUDE_ANY); +} + +int pathd_srte_policy_candidate_path_exclude_any_destroy( + struct nb_cb_destroy_args *args) +{ + return affinity_filter_destroy(args, AFFINITY_FILTER_EXCLUDE_ANY); +} + + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-any + */ +int pathd_srte_policy_candidate_path_include_any_modify( + struct nb_cb_modify_args *args) +{ + return affinity_filter_modify(args, AFFINITY_FILTER_INCLUDE_ANY); +} + +int pathd_srte_policy_candidate_path_include_any_destroy( + struct nb_cb_destroy_args *args) +{ + return affinity_filter_destroy(args, AFFINITY_FILTER_INCLUDE_ANY); +} + + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-all + */ +int pathd_srte_policy_candidate_path_include_all_modify( + struct nb_cb_modify_args *args) +{ + return affinity_filter_modify(args, AFFINITY_FILTER_INCLUDE_ALL); +} + +int pathd_srte_policy_candidate_path_include_all_destroy( + struct nb_cb_destroy_args *args) +{ + return affinity_filter_destroy(args, AFFINITY_FILTER_INCLUDE_ALL); +} + + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics + */ +int pathd_srte_policy_candidate_path_metrics_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + enum srte_candidate_metric_type type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + + type = yang_dnode_get_enum(args->dnode, "./type"); + srte_candidate_unset_metric(candidate, type); + + return NB_OK; +} + +void pathd_srte_policy_candidate_path_metrics_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_candidate *candidate; + enum srte_candidate_metric_type type; + float value; + bool required, is_bound = false, is_computed = false; + + assert(args->context != NULL); + + candidate = nb_running_get_entry(args->dnode, NULL, true); + + type = yang_dnode_get_enum(args->dnode, "./type"); + value = (float)yang_dnode_get_dec64(args->dnode, "./value"); + required = yang_dnode_get_bool(args->dnode, "./required"); + if (yang_dnode_exists(args->dnode, "./is-bound")) + is_bound = yang_dnode_get_bool(args->dnode, "./is-bound"); + if (yang_dnode_exists(args->dnode, "./is-computed")) + is_computed = yang_dnode_get_bool(args->dnode, "./is-computed"); + + srte_candidate_set_metric(candidate, type, value, required, is_bound, + is_computed); +} + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function + */ +int pathd_srte_policy_candidate_path_objfun_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + + candidate = nb_running_get_entry(args->dnode, NULL, true); + srte_candidate_unset_objfun(candidate); + + return NB_OK; +} + +void pathd_srte_policy_candidate_path_objfun_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_candidate *candidate; + enum objfun_type type; + bool required; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + required = yang_dnode_get_bool(args->dnode, "./required"); + type = yang_dnode_get_enum(args->dnode, "./type"); + srte_candidate_set_objfun(candidate, required, type); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/protocol-origin + */ +int pathd_srte_policy_candidate_path_protocol_origin_modify( + struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + enum srte_protocol_origin protocol_origin; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + protocol_origin = yang_dnode_get_enum(args->dnode, NULL); + candidate->protocol_origin = protocol_origin; + candidate->lsp->protocol_origin = protocol_origin; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/originator + */ +int pathd_srte_policy_candidate_path_originator_modify( + struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + const char *originator; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + originator = yang_dnode_get_string(args->dnode, NULL); + strlcpy(candidate->originator, originator, + sizeof(candidate->originator)); + strlcpy(candidate->lsp->originator, originator, + sizeof(candidate->lsp->originator)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/type + */ +int pathd_srte_policy_candidate_path_type_modify(struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + enum srte_candidate_type type; + char xpath[XPATH_MAXLEN]; + char xpath_buf[XPATH_MAXLEN - 3]; + + if (args->event != NB_EV_APPLY && args->event != NB_EV_VALIDATE) + return NB_OK; + + /* the candidate type is fixed after setting it once, this is checked + * here */ + if (args->event == NB_EV_VALIDATE) { + /* first get the precise path to the candidate path */ + yang_dnode_get_path(args->dnode, xpath_buf, sizeof(xpath_buf)); + snprintf(xpath, sizeof(xpath), "%s%s", xpath_buf, "/.."); + + candidate = nb_running_get_entry_non_rec(NULL, xpath, false); + + /* then check if it exists and if the type was provided */ + if (candidate + && candidate->type != SRTE_CANDIDATE_TYPE_UNDEFINED) { + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "The candidate type is fixed!"); + return NB_ERR_RESOURCE; + } else + return NB_OK; + } + + candidate = nb_running_get_entry(args->dnode, NULL, true); + + type = yang_dnode_get_enum(args->dnode, NULL); + candidate->type = type; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/segment-list-name + */ +int pathd_srte_policy_candidate_path_segment_list_name_modify( + struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + const char *segment_list_name; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + segment_list_name = yang_dnode_get_string(args->dnode, NULL); + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate->segment_list = srte_segment_list_find(segment_list_name); + candidate->lsp->segment_list = candidate->segment_list; + assert(candidate->segment_list); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +int pathd_srte_policy_candidate_path_segment_list_name_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + candidate->segment_list = NULL; + candidate->lsp->segment_list = NULL; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth + */ +void pathd_srte_policy_candidate_path_bandwidth_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_candidate *candidate; + float value; + bool required; + + assert(args->context != NULL); + + candidate = nb_running_get_entry(args->dnode, NULL, true); + value = (float)yang_dnode_get_dec64(args->dnode, "./value"); + required = yang_dnode_get_bool(args->dnode, "./required"); + srte_candidate_set_bandwidth(candidate, value, required); +} + +int pathd_srte_policy_candidate_path_bandwidth_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + srte_candidate_unset_bandwidth(candidate); + return NB_OK; +} diff --git a/pathd/path_nb_state.c b/pathd/path_nb_state.c new file mode 100644 index 0000000000..60f04f45c1 --- /dev/null +++ b/pathd/path_nb_state.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2020 NetDEF, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "northbound.h" +#include "libfrr.h" + +#include "pathd/pathd.h" +#include "pathd/path_nb.h" + +/* + * XPath: /frr-pathd:pathd/srte/segment-list + */ +const void *pathd_srte_segment_list_get_next(struct nb_cb_get_next_args *args) +{ + struct srte_segment_list *segment_list = + (struct srte_segment_list *)args->list_entry; + + if (args->list_entry == NULL) + segment_list = + RB_MIN(srte_segment_list_head, &srte_segment_lists); + else + segment_list = RB_NEXT(srte_segment_list_head, segment_list); + + return segment_list; +} + +int pathd_srte_segment_list_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct srte_segment_list *segment_list = + (struct srte_segment_list *)args->list_entry; + + args->keys->num = 1; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%s", + segment_list->name); + + return NB_OK; +} + +const void * +pathd_srte_segment_list_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + return srte_segment_list_find(args->keys->key[0]); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy + */ +const void *pathd_srte_policy_get_next(struct nb_cb_get_next_args *args) +{ + struct srte_policy *policy = (struct srte_policy *)args->list_entry; + + if (args->list_entry == NULL) + policy = RB_MIN(srte_policy_head, &srte_policies); + else + policy = RB_NEXT(srte_policy_head, policy); + + return policy; +} + +int pathd_srte_policy_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct srte_policy *policy = + (struct srte_policy *)args->list_entry; + + args->keys->num = 2; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%u", + policy->color); + ipaddr2str(&policy->endpoint, args->keys->key[1], + sizeof(args->keys->key[1])); + + return NB_OK; +} + +const void *pathd_srte_policy_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + uint32_t color; + struct ipaddr endpoint; + + color = yang_str2uint32(args->keys->key[0]); + yang_str2ip(args->keys->key[1], &endpoint); + + return srte_policy_find(color, &endpoint); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/is-operational + */ +struct yang_data * +pathd_srte_policy_is_operational_get_elem(struct nb_cb_get_elem_args *args) +{ + struct srte_policy *policy = (struct srte_policy *)args->list_entry; + bool is_operational = false; + + if (policy->status == SRTE_POLICY_STATUS_UP) + is_operational = true; + + return yang_data_new_bool(args->xpath, is_operational); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path + */ +const void * +pathd_srte_policy_candidate_path_get_next(struct nb_cb_get_next_args *args) +{ + struct srte_policy *policy = + (struct srte_policy *)args->parent_list_entry; + struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + if (args->list_entry == NULL) + candidate = + RB_MIN(srte_candidate_head, &policy->candidate_paths); + else + candidate = RB_NEXT(srte_candidate_head, candidate); + + return candidate; +} + +int pathd_srte_policy_candidate_path_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + args->keys->num = 1; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%u", + candidate->preference); + + return NB_OK; +} + +const void *pathd_srte_policy_candidate_path_lookup_entry( + struct nb_cb_lookup_entry_args *args) +{ + struct srte_policy *policy = + (struct srte_policy *)args->parent_list_entry; + uint32_t preference; + + preference = yang_str2uint32(args->keys->key[0]); + + return srte_candidate_find(policy, preference); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate_path/is-best-candidate-path + */ +struct yang_data * +pathd_srte_policy_candidate_path_is_best_candidate_path_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + return yang_data_new_bool( + args->xpath, CHECK_FLAG(candidate->flags, F_CANDIDATE_BEST)); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/discriminator + */ +struct yang_data *pathd_srte_policy_candidate_path_discriminator_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + return yang_data_new_uint32(args->xpath, candidate->discriminator); +} diff --git a/pathd/path_zebra.c b/pathd/path_zebra.c new file mode 100644 index 0000000000..f0a22e4392 --- /dev/null +++ b/pathd/path_zebra.c @@ -0,0 +1,296 @@ +/* + * 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 "thread.h" +#include "log.h" +#include "lib_errors.h" +#include "if.h" +#include "prefix.h" +#include "zclient.h" +#include "network.h" +#include "stream.h" +#include "linklist.h" +#include "nexthop.h" +#include "vrf.h" +#include "typesafe.h" + +#include "pathd/pathd.h" +#include "pathd/path_zebra.h" + +static struct zclient *zclient; +static struct zclient *zclient_sync; + +/* Global Variables */ +bool g_has_router_id_v4 = false; +bool g_has_router_id_v6 = false; +struct in_addr g_router_id_v4; +struct in6_addr g_router_id_v6; +pthread_mutex_t g_router_id_v4_mtx = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t g_router_id_v6_mtx = PTHREAD_MUTEX_INITIALIZER; + +/** + * Gives the IPv4 router ID received from Zebra. + * + * @param router_id The in_addr strucure where to store the router id + * @return true if the router ID was available, false otherwise + */ +bool get_ipv4_router_id(struct in_addr *router_id) +{ + bool retval = false; + assert(router_id != NULL); + pthread_mutex_lock(&g_router_id_v4_mtx); + if (g_has_router_id_v4) { + memcpy(router_id, &g_router_id_v4, sizeof(*router_id)); + retval = true; + } + pthread_mutex_unlock(&g_router_id_v4_mtx); + return retval; +} + +/** + * Gives the IPv6 router ID received from Zebra. + * + * @param router_id The in6_addr strucure where to store the router id + * @return true if the router ID was available, false otherwise + */ +bool get_ipv6_router_id(struct in6_addr *router_id) +{ + bool retval = false; + assert(router_id != NULL); + pthread_mutex_lock(&g_router_id_v6_mtx); + if (g_has_router_id_v6) { + memcpy(router_id, &g_router_id_v6, sizeof(*router_id)); + retval = true; + } + pthread_mutex_unlock(&g_router_id_v6_mtx); + return retval; +} + +static void path_zebra_connected(struct zclient *zclient) +{ + struct srte_policy *policy; + + zclient_send_reg_requests(zclient, VRF_DEFAULT); + zclient_send_router_id_update(zclient, ZEBRA_ROUTER_ID_ADD, AFI_IP6, + VRF_DEFAULT); + + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + struct srte_candidate *candidate; + struct srte_segment_list *segment_list; + + candidate = policy->best_candidate; + if (!candidate) + continue; + + segment_list = candidate->lsp->segment_list; + if (!segment_list) + continue; + + path_zebra_add_sr_policy(policy, segment_list); + } +} + +static int path_zebra_sr_policy_notify_status(ZAPI_CALLBACK_ARGS) +{ + struct zapi_sr_policy zapi_sr_policy; + struct srte_policy *policy; + struct srte_candidate *best_candidate_path; + + if (zapi_sr_policy_notify_status_decode(zclient->ibuf, &zapi_sr_policy)) + return -1; + + policy = srte_policy_find(zapi_sr_policy.color, + &zapi_sr_policy.endpoint); + if (!policy) + return -1; + + best_candidate_path = policy->best_candidate; + if (!best_candidate_path) + return -1; + + srte_candidate_status_update(best_candidate_path, + zapi_sr_policy.status); + + return 0; +} + +/* Router-id update message from zebra. */ +static int path_zebra_router_id_update(ZAPI_CALLBACK_ARGS) +{ + struct prefix pref; + const char *family; + char buf[PREFIX2STR_BUFFER]; + zebra_router_id_update_read(zclient->ibuf, &pref); + if (pref.family == AF_INET) { + pthread_mutex_lock(&g_router_id_v4_mtx); + memcpy(&g_router_id_v4, &pref.u.prefix4, + sizeof(g_router_id_v4)); + g_has_router_id_v4 = true; + inet_ntop(AF_INET, &g_router_id_v4, buf, sizeof(buf)); + pthread_mutex_unlock(&g_router_id_v4_mtx); + family = "IPv4"; + } else if (pref.family == AF_INET6) { + pthread_mutex_lock(&g_router_id_v6_mtx); + memcpy(&g_router_id_v6, &pref.u.prefix6, + sizeof(g_router_id_v6)); + g_has_router_id_v6 = true; + inet_ntop(AF_INET6, &g_router_id_v6, buf, sizeof(buf)); + pthread_mutex_unlock(&g_router_id_v6_mtx); + family = "IPv6"; + } else { + pthread_mutex_unlock(&g_router_id_v4_mtx); + zlog_warn("Unexpected router ID address family for vrf %u: %u", + vrf_id, pref.family); + return 0; + } + pthread_mutex_unlock(&g_router_id_v4_mtx); + zlog_info("%s Router Id updated for VRF %u: %s", family, vrf_id, buf); + return 0; +} + +/** + * Adds a segment routing policy to Zebra. + * + * @param policy The policy to add + * @param segment_list The segment list for the policy + */ +void path_zebra_add_sr_policy(struct srte_policy *policy, + struct srte_segment_list *segment_list) +{ + struct zapi_sr_policy zp = {}; + struct srte_segment_entry *segment; + + zp.color = policy->color; + zp.endpoint = policy->endpoint; + strlcpy(zp.name, policy->name, sizeof(zp.name)); + zp.segment_list.type = ZEBRA_LSP_SRTE; + zp.segment_list.local_label = policy->binding_sid; + zp.segment_list.label_num = 0; + RB_FOREACH (segment, srte_segment_entry_head, &segment_list->segments) + zp.segment_list.labels[zp.segment_list.label_num++] = + segment->sid_value; + policy->status = SRTE_POLICY_STATUS_GOING_UP; + + (void)zebra_send_sr_policy(zclient, ZEBRA_SR_POLICY_SET, &zp); +} + +/** + * Deletes a segment policy from Zebra. + * + * @param policy The policy to remove + */ +void path_zebra_delete_sr_policy(struct srte_policy *policy) +{ + struct zapi_sr_policy zp = {}; + + zp.color = policy->color; + zp.endpoint = policy->endpoint; + strlcpy(zp.name, policy->name, sizeof(zp.name)); + zp.segment_list.type = ZEBRA_LSP_SRTE; + zp.segment_list.local_label = policy->binding_sid; + zp.segment_list.label_num = 0; + policy->status = SRTE_POLICY_STATUS_DOWN; + + (void)zebra_send_sr_policy(zclient, ZEBRA_SR_POLICY_DELETE, &zp); +} + +/** + * Allocates a label from Zebra's label manager. + * + * @param label the label to be allocated + * @return 0 if the label has been allocated, -1 otherwise + */ +int path_zebra_request_label(mpls_label_t label) +{ + int ret; + uint32_t start, end; + + ret = lm_get_label_chunk(zclient_sync, 0, label, 1, &start, &end); + if (ret < 0) { + zlog_warn("%s: error getting label range!", __func__); + return -1; + } + + return 0; +} + +/** + * Releases a previously allocated label from Zebra's label manager. + * + * @param label The label to release + * @return 0 ifthe label has beel released, -1 otherwise + */ +void path_zebra_release_label(mpls_label_t label) +{ + int ret; + + ret = lm_release_label_chunk(zclient_sync, label, label); + if (ret < 0) + zlog_warn("%s: error releasing label range!", __func__); +} + +static void path_zebra_label_manager_connect(void) +{ + /* Connect to label manager. */ + while (zclient_socket_connect(zclient_sync) < 0) { + zlog_warn("%s: error connecting synchronous zclient!", + __func__); + sleep(1); + } + set_nonblocking(zclient_sync->sock); + + /* Send hello to notify zebra this is a synchronous client */ + while (zclient_send_hello(zclient_sync) < 0) { + zlog_warn("%s: Error sending hello for synchronous zclient!", + __func__); + sleep(1); + } + + while (lm_label_manager_connect(zclient_sync, 0) != 0) { + zlog_warn("%s: error connecting to label manager!", __func__); + sleep(1); + } +} + +/** + * Initializes Zebra asynchronous connection. + * + * @param master The master thread + */ +void path_zebra_init(struct thread_master *master) +{ + struct zclient_options options = zclient_options_default; + options.synchronous = true; + + /* Initialize asynchronous zclient. */ + zclient = zclient_new(master, &zclient_options_default); + zclient_init(zclient, ZEBRA_ROUTE_SRTE, 0, &pathd_privs); + zclient->zebra_connected = path_zebra_connected; + zclient->sr_policy_notify_status = path_zebra_sr_policy_notify_status; + zclient->router_id_update = path_zebra_router_id_update; + + /* Initialize special zclient for synchronous message exchanges. */ + zclient_sync = zclient_new(master, &options); + zclient_sync->sock = -1; + zclient_sync->redist_default = ZEBRA_ROUTE_SRTE; + zclient_sync->instance = 1; + zclient_sync->privs = &pathd_privs; + + /* Connect to the LM. */ + path_zebra_label_manager_connect(); +} diff --git a/pathd/path_zebra.h b/pathd/path_zebra.h new file mode 100644 index 0000000000..42a7123dd4 --- /dev/null +++ b/pathd/path_zebra.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 NetDEF, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_PATH_MPLS_H_ +#define _FRR_PATH_MPLS_H_ + +#include +#include "pathd/pathd.h" + +bool get_ipv4_router_id(struct in_addr *router_id); +bool get_ipv6_router_id(struct in6_addr *router_id); +void path_zebra_add_sr_policy(struct srte_policy *policy, + struct srte_segment_list *segment_list); +void path_zebra_delete_sr_policy(struct srte_policy *policy); +int path_zebra_request_label(mpls_label_t label); +void path_zebra_release_label(mpls_label_t label); +void path_zebra_init(struct thread_master *master); + +#endif /* _FRR_PATH_MPLS_H_ */ diff --git a/pathd/pathd.c b/pathd/pathd.c new file mode 100644 index 0000000000..2e2fa86714 --- /dev/null +++ b/pathd/pathd.c @@ -0,0 +1,1127 @@ +/* + * 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 "memory.h" +#include "log.h" +#include "lib_errors.h" + +#include "pathd/pathd.h" +#include "pathd/path_memory.h" +#include "pathd/path_zebra.h" +#include "pathd/path_debug.h" + +#define HOOK_DELAY 3 + +DEFINE_MTYPE_STATIC(PATHD, PATH_SEGMENT_LIST, "Segment List") +DEFINE_MTYPE_STATIC(PATHD, PATH_SR_POLICY, "SR Policy") +DEFINE_MTYPE_STATIC(PATHD, PATH_SR_CANDIDATE, "SR Policy candidate path") + +DEFINE_HOOK(pathd_candidate_created, (struct srte_candidate * candidate), + (candidate)) +DEFINE_HOOK(pathd_candidate_updated, (struct srte_candidate * candidate), + (candidate)) +DEFINE_HOOK(pathd_candidate_removed, (struct srte_candidate * candidate), + (candidate)) + +static void trigger_pathd_candidate_created(struct srte_candidate *candidate); +static int trigger_pathd_candidate_created_timer(struct thread *thread); +static void trigger_pathd_candidate_updated(struct srte_candidate *candidate); +static int trigger_pathd_candidate_updated_timer(struct thread *thread); +static void trigger_pathd_candidate_removed(struct srte_candidate *candidate); +static const char * +srte_candidate_metric_name(enum srte_candidate_metric_type type); + +static void srte_set_metric(struct srte_metric *metric, float value, + bool required, bool is_bound, bool is_computed); +static void srte_unset_metric(struct srte_metric *metric); + + +/* Generate rb-tree of Segment List Segment instances. */ +static inline int srte_segment_entry_compare(const struct srte_segment_entry *a, + const struct srte_segment_entry *b) +{ + return a->index - b->index; +} +RB_GENERATE(srte_segment_entry_head, srte_segment_entry, entry, + srte_segment_entry_compare) + +/* Generate rb-tree of Segment List instances. */ +static inline int srte_segment_list_compare(const struct srte_segment_list *a, + const struct srte_segment_list *b) +{ + return strcmp(a->name, b->name); +} +RB_GENERATE(srte_segment_list_head, srte_segment_list, entry, + srte_segment_list_compare) + +struct srte_segment_list_head srte_segment_lists = + RB_INITIALIZER(&srte_segment_lists); + +/* Generate rb-tree of Candidate Path instances. */ +static inline int srte_candidate_compare(const struct srte_candidate *a, + const struct srte_candidate *b) +{ + return a->preference - b->preference; +} +RB_GENERATE(srte_candidate_head, srte_candidate, entry, srte_candidate_compare) + +/* Generate rb-tree of SR Policy instances. */ +static inline int srte_policy_compare(const struct srte_policy *a, + const struct srte_policy *b) +{ + return sr_policy_compare(&a->endpoint, &b->endpoint, a->color, + b->color); +} +RB_GENERATE(srte_policy_head, srte_policy, entry, srte_policy_compare) + +struct srte_policy_head srte_policies = RB_INITIALIZER(&srte_policies); + +/** + * Adds a segment list to pathd. + * + * @param name The name of the segment list to add + * @return The added segment list + */ +struct srte_segment_list *srte_segment_list_add(const char *name) +{ + struct srte_segment_list *segment_list; + + segment_list = XCALLOC(MTYPE_PATH_SEGMENT_LIST, sizeof(*segment_list)); + strlcpy(segment_list->name, name, sizeof(segment_list->name)); + RB_INIT(srte_segment_entry_head, &segment_list->segments); + RB_INSERT(srte_segment_list_head, &srte_segment_lists, segment_list); + + return segment_list; +} + +/** + * Deletes a segment list from pathd. + * + * The given segment list structure will be freed and should not be used anymore + * after calling this function. + * + * @param segment_list the segment list to remove from pathd. + */ +void srte_segment_list_del(struct srte_segment_list *segment_list) +{ + struct srte_segment_entry *segment, *safe_seg; + RB_FOREACH_SAFE (segment, srte_segment_entry_head, + &segment_list->segments, safe_seg) { + srte_segment_entry_del(segment); + } + RB_REMOVE(srte_segment_list_head, &srte_segment_lists, segment_list); + XFREE(MTYPE_PATH_SEGMENT_LIST, segment_list); +} + +/** + * Search for a segment list by name. + * + * @param name The name of the segment list to look for + * @return The segment list if found, NULL otherwise + */ +struct srte_segment_list *srte_segment_list_find(const char *name) +{ + struct srte_segment_list search; + + strlcpy(search.name, name, sizeof(search.name)); + return RB_FIND(srte_segment_list_head, &srte_segment_lists, &search); +} + +/** + * Adds a segment to a segment list. + * + * @param segment_list The segment list the segment should be added to + * @param index The index of the added segment in the segment list + * @return The added segment + */ +struct srte_segment_entry * +srte_segment_entry_add(struct srte_segment_list *segment_list, uint32_t index) +{ + struct srte_segment_entry *segment; + + segment = XCALLOC(MTYPE_PATH_SEGMENT_LIST, sizeof(*segment)); + segment->segment_list = segment_list; + segment->index = index; + RB_INSERT(srte_segment_entry_head, &segment_list->segments, segment); + + return segment; +} + +/** + * Deletes a segment from a segment list. + * + * @param segment The segment to be removed + */ +void srte_segment_entry_del(struct srte_segment_entry *segment) +{ + RB_REMOVE(srte_segment_entry_head, &segment->segment_list->segments, + segment); + XFREE(MTYPE_PATH_SEGMENT_LIST, segment); +} + +/** + * Set the node or adjacency identifier of a segment. + * + * @param segment The segment for which the NAI should be set + * @param type The type of the NAI + * @param type The address of the node or the local address of the adjacency + * @param type The local interface index of the unumbered adjacency + * @param type The remote address of the adjacency + * @param type The remote interface index of the unumbered adjacency + */ +void srte_segment_entry_set_nai(struct srte_segment_entry *segment, + enum srte_segment_nai_type type, + struct ipaddr *local_ip, uint32_t local_iface, + struct ipaddr *remote_ip, uint32_t remote_iface) +{ + segment->nai_type = type; + memcpy(&segment->nai_local_addr, local_ip, sizeof(struct ipaddr)); + + switch (type) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + memcpy(&segment->nai_remote_addr, remote_ip, + sizeof(struct ipaddr)); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + memcpy(&segment->nai_remote_addr, remote_ip, + sizeof(struct ipaddr)); + segment->nai_local_iface = local_iface; + segment->nai_remote_iface = remote_iface; + break; + default: + segment->nai_local_addr.ipa_type = IPADDR_NONE; + segment->nai_local_iface = 0; + segment->nai_remote_addr.ipa_type = IPADDR_NONE; + segment->nai_remote_iface = 0; + } +} + +/** + * Add a policy to pathd. + * + * WARNING: The color 0 is a special case as it is the no-color. + * + * @param color The color of the policy. + * @param endpoint The IP address of the policy endpoint + * @return The created policy + */ +struct srte_policy *srte_policy_add(uint32_t color, struct ipaddr *endpoint) +{ + struct srte_policy *policy; + + policy = XCALLOC(MTYPE_PATH_SR_POLICY, sizeof(*policy)); + policy->color = color; + policy->endpoint = *endpoint; + policy->binding_sid = MPLS_LABEL_NONE; + RB_INIT(srte_candidate_head, &policy->candidate_paths); + RB_INSERT(srte_policy_head, &srte_policies, policy); + + return policy; +} + +/** + * Delete a policy from pathd. + * + * The given policy structure will be freed and should never be used again + * after calling this function. + * + * @param policy The policy to be removed + */ +void srte_policy_del(struct srte_policy *policy) +{ + struct srte_candidate *candidate; + + path_zebra_delete_sr_policy(policy); + + path_zebra_release_label(policy->binding_sid); + + while (!RB_EMPTY(srte_candidate_head, &policy->candidate_paths)) { + candidate = + RB_ROOT(srte_candidate_head, &policy->candidate_paths); + trigger_pathd_candidate_removed(candidate); + srte_candidate_del(candidate); + } + + RB_REMOVE(srte_policy_head, &srte_policies, policy); + XFREE(MTYPE_PATH_SR_POLICY, policy); +} + +/** + * Search for a policy by color and endpoint. + * + * WARNING: The color 0 is a special case as it is the no-color. + * + * @param color The color of the policy to look for + * @param endpoint The endpoint of the policy to look for + * @return The policy if found, NULL otherwise + */ +struct srte_policy *srte_policy_find(uint32_t color, struct ipaddr *endpoint) +{ + struct srte_policy search; + + search.color = color; + search.endpoint = *endpoint; + return RB_FIND(srte_policy_head, &srte_policies, &search); +} + +/** + * Update a policy binding SID. + * + * @param policy The policy for which the SID should be updated + * @param binding_sid The new binding SID for the given policy + */ +void srte_policy_update_binding_sid(struct srte_policy *policy, + uint32_t binding_sid) +{ + if (policy->binding_sid != MPLS_LABEL_NONE) + path_zebra_release_label(policy->binding_sid); + + policy->binding_sid = binding_sid; + + /* Reinstall the Binding-SID if necessary. */ + if (policy->best_candidate) + path_zebra_add_sr_policy( + policy, policy->best_candidate->lsp->segment_list); +} + +/** + * Gives the policy best candidate path. + * + * @param policy The policy we want the best candidate path from + * @return The best candidate path + */ +static struct srte_candidate * +srte_policy_best_candidate(const struct srte_policy *policy) +{ + struct srte_candidate *candidate; + + RB_FOREACH_REVERSE (candidate, srte_candidate_head, + &policy->candidate_paths) { + /* search for highest preference with existing segment list */ + if (!CHECK_FLAG(candidate->flags, F_CANDIDATE_DELETED) + && candidate->lsp->segment_list) + return candidate; + } + + return NULL; +} + +/** + * Apply changes defined by setting the policies, candidate paths + * and segment lists modification flags NEW, MODIFIED and DELETED. + * + * This allows the northbound code to delay all the side effects of adding + * modifying and deleting them to the end. + * + * Example of marking an object as modified: + * `SET_FLAG(obj->flags, F_XXX_MODIFIED)` + */ +void srte_apply_changes(void) +{ + struct srte_policy *policy, *safe_pol; + struct srte_segment_list *segment_list, *safe_sl; + + RB_FOREACH_SAFE (policy, srte_policy_head, &srte_policies, safe_pol) { + if (CHECK_FLAG(policy->flags, F_POLICY_DELETED)) { + srte_policy_del(policy); + continue; + } + srte_policy_apply_changes(policy); + UNSET_FLAG(policy->flags, F_POLICY_NEW); + UNSET_FLAG(policy->flags, F_POLICY_MODIFIED); + } + + RB_FOREACH_SAFE (segment_list, srte_segment_list_head, + &srte_segment_lists, safe_sl) { + if (CHECK_FLAG(segment_list->flags, F_SEGMENT_LIST_DELETED)) { + srte_segment_list_del(segment_list); + continue; + } + UNSET_FLAG(segment_list->flags, F_SEGMENT_LIST_NEW); + UNSET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + } +} + +/** + * Apply changes defined by setting the given policy and its candidate paths + * modification flags NEW, MODIFIED and DELETED. + * + * In moste cases `void srte_apply_changes(void)` should be used instead, + * this function will not handle the changes of segment lists used by the + * policy. + * + * @param policy The policy changes has to be applied to. + */ +void srte_policy_apply_changes(struct srte_policy *policy) +{ + struct srte_candidate *candidate, *safe; + struct srte_candidate *old_best_candidate; + struct srte_candidate *new_best_candidate; + char endpoint[46]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + /* Get old and new best candidate path. */ + old_best_candidate = policy->best_candidate; + new_best_candidate = srte_policy_best_candidate(policy); + + if (new_best_candidate != old_best_candidate) { + /* TODO: add debug guard. */ + zlog_debug( + "SR-TE(%s, %u): best candidate changed from %s to %s", + endpoint, policy->color, + old_best_candidate ? old_best_candidate->name : "none", + new_best_candidate ? new_best_candidate->name : "none"); + + if (old_best_candidate) { + policy->best_candidate = NULL; + UNSET_FLAG(old_best_candidate->flags, F_CANDIDATE_BEST); + SET_FLAG(old_best_candidate->flags, + F_CANDIDATE_MODIFIED); + + /* + * Rely on replace semantics if there's a new best + * candidate. + */ + if (!new_best_candidate) + path_zebra_delete_sr_policy(policy); + } + if (new_best_candidate) { + policy->best_candidate = new_best_candidate; + SET_FLAG(new_best_candidate->flags, F_CANDIDATE_BEST); + SET_FLAG(new_best_candidate->flags, + F_CANDIDATE_MODIFIED); + + path_zebra_add_sr_policy( + policy, new_best_candidate->lsp->segment_list); + } + } else if (new_best_candidate) { + /* The best candidate path did not change, but some of its + * attributes or its segment list may have changed. + */ + + bool candidate_changed = CHECK_FLAG(new_best_candidate->flags, + F_CANDIDATE_MODIFIED); + bool segment_list_changed = + new_best_candidate->lsp->segment_list + && CHECK_FLAG( + new_best_candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_MODIFIED); + + if (candidate_changed || segment_list_changed) { + /* TODO: add debug guard. */ + zlog_debug("SR-TE(%s, %u): best candidate %s changed", + endpoint, policy->color, + new_best_candidate->name); + + path_zebra_add_sr_policy( + policy, new_best_candidate->lsp->segment_list); + } + } + + RB_FOREACH_SAFE (candidate, srte_candidate_head, + &policy->candidate_paths, safe) { + if (CHECK_FLAG(candidate->flags, F_CANDIDATE_DELETED)) { + trigger_pathd_candidate_removed(candidate); + srte_candidate_del(candidate); + continue; + } else if (CHECK_FLAG(candidate->flags, F_CANDIDATE_NEW)) { + trigger_pathd_candidate_created(candidate); + } else if (CHECK_FLAG(candidate->flags, F_CANDIDATE_MODIFIED)) { + trigger_pathd_candidate_updated(candidate); + } else if (candidate->lsp->segment_list + && CHECK_FLAG(candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_MODIFIED)) { + trigger_pathd_candidate_updated(candidate); + } + + UNSET_FLAG(candidate->flags, F_CANDIDATE_NEW); + UNSET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + } +} + +/** + * Adds a candidate path to a policy. + * + * @param policy The policy the candidate path should be added to + * @param preference The preference of the candidate path to be added + * @return The added candidate path + */ +struct srte_candidate *srte_candidate_add(struct srte_policy *policy, + uint32_t preference) +{ + struct srte_candidate *candidate; + struct srte_lsp *lsp; + + candidate = XCALLOC(MTYPE_PATH_SR_CANDIDATE, sizeof(*candidate)); + lsp = XCALLOC(MTYPE_PATH_SR_CANDIDATE, sizeof(*lsp)); + + candidate->preference = preference; + candidate->policy = policy; + candidate->type = SRTE_CANDIDATE_TYPE_UNDEFINED; + candidate->discriminator = rand(); + + lsp->candidate = candidate; + candidate->lsp = lsp; + + RB_INSERT(srte_candidate_head, &policy->candidate_paths, candidate); + + return candidate; +} + +/** + * Deletes a candidate. + * + * The corresponding LSP will be removed alongside the candidate path. + * The given candidate will be freed and shouldn't be used anymore after the + * calling this function. + * + * @param candidate The candidate path to delete + */ +void srte_candidate_del(struct srte_candidate *candidate) +{ + struct srte_policy *srte_policy = candidate->policy; + + RB_REMOVE(srte_candidate_head, &srte_policy->candidate_paths, + candidate); + + XFREE(MTYPE_PATH_SR_CANDIDATE, candidate->lsp); + XFREE(MTYPE_PATH_SR_CANDIDATE, candidate); +} + +/** + * Sets the bandwidth constraint of given candidate path. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path of which the bandwidth should be changed + * @param bandwidth The Bandwidth constraint to set to the candidate path + * @param required If the constraint is required (true) or only desired (false) + */ +void srte_candidate_set_bandwidth(struct srte_candidate *candidate, + float bandwidth, bool required) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug( + "SR-TE(%s, %u): candidate %s %sconfig bandwidth set to %f B/s", + endpoint, policy->color, candidate->name, + required ? "required " : "", bandwidth); + SET_FLAG(candidate->flags, F_CANDIDATE_HAS_BANDWIDTH); + COND_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_BANDWIDTH, required); + candidate->bandwidth = bandwidth; + + srte_lsp_set_bandwidth(candidate->lsp, bandwidth, required); +} + +/** + * Sets the bandwidth constraint of the given LSP. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The lsp of which the bandwidth should be changed + * @param bandwidth The Bandwidth constraint to set to the candidate path + * @param required If the constraint is required (true) or only desired (false) + */ +void srte_lsp_set_bandwidth(struct srte_lsp *lsp, float bandwidth, + bool required) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s %slsp bandwidth set to %f B/s", + endpoint, policy->color, candidate->name, + required ? "required" : "", bandwidth); + SET_FLAG(lsp->flags, F_CANDIDATE_HAS_BANDWIDTH); + COND_FLAG(lsp->flags, F_CANDIDATE_REQUIRED_BANDWIDTH, required); + lsp->bandwidth = bandwidth; +} + +/** + * Remove a candidate path bandwidth constraint. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path of which the bandwidth should be removed + */ +void srte_candidate_unset_bandwidth(struct srte_candidate *candidate) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s config bandwidth unset", + endpoint, policy->color, candidate->name); + UNSET_FLAG(candidate->flags, F_CANDIDATE_HAS_BANDWIDTH); + UNSET_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_BANDWIDTH); + candidate->bandwidth = 0; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + srte_lsp_unset_bandwidth(candidate->lsp); +} + +/** + * Remove an LSP bandwidth constraint. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The lsp of which the bandwidth should be changed + */ +void srte_lsp_unset_bandwidth(struct srte_lsp *lsp) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s lsp bandwidth unset", endpoint, + policy->color, candidate->name); + UNSET_FLAG(lsp->flags, F_CANDIDATE_HAS_BANDWIDTH); + UNSET_FLAG(lsp->flags, F_CANDIDATE_REQUIRED_BANDWIDTH); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + lsp->bandwidth = 0; +} + +/** + * Sets a candidate path metric constraint. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path of which the metric should be changed + * @param type The metric type + * @param value The metric value + * @param required If the constraint is required (true) or only desired (false) + * @param is_bound If the metric is an indicative value or a strict upper bound + * @param is_computed If the metric was computed or configured + */ +void srte_candidate_set_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type, + float value, bool required, bool is_bound, + bool is_computed) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug( + "SR-TE(%s, %u): candidate %s %sconfig metric %s (%u) set to %f " + "(is-bound: %s; is_computed: %s)", + endpoint, policy->color, candidate->name, + required ? "required " : "", srte_candidate_metric_name(type), + type, value, is_bound ? "true" : "false", + is_computed ? "true" : "false"); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_set_metric(&candidate->metrics[type - 1], value, required, + is_bound, is_computed); + srte_lsp_set_metric(candidate->lsp, type, value, required, is_bound, + is_computed); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); +} + +/** + * Sets an LSP metric constraint. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The LSP of which the metric should be changed + * @param type The metric type + * @param value The metric value + * @param required If the constraint is required (true) or only desired (false) + * @param is_bound If the metric is an indicative value or a strict upper bound + * @param is_computed If the metric was computed or configured + */ +void srte_lsp_set_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type, float value, + bool required, bool is_bound, bool is_computed) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug( + "SR-TE(%s, %u): candidate %s %slsp metric %s (%u) set to %f " + "(is-bound: %s; is_computed: %s)", + endpoint, policy->color, candidate->name, + required ? "required " : "", srte_candidate_metric_name(type), + type, value, is_bound ? "true" : "false", + is_computed ? "true" : "false"); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_set_metric(&lsp->metrics[type - 1], value, required, is_bound, + is_computed); +} + +void srte_set_metric(struct srte_metric *metric, float value, bool required, + bool is_bound, bool is_computed) +{ + SET_FLAG(metric->flags, F_METRIC_IS_DEFINED); + COND_FLAG(metric->flags, F_METRIC_IS_REQUIRED, required); + COND_FLAG(metric->flags, F_METRIC_IS_BOUND, is_bound); + COND_FLAG(metric->flags, F_METRIC_IS_COMPUTED, is_computed); + metric->value = value; +} + +/** + * Removes a candidate path metric constraint. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path from which the metric should be removed + * @param type The metric type + */ +void srte_candidate_unset_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s config metric %s (%u) unset", + endpoint, policy->color, candidate->name, + srte_candidate_metric_name(type), type); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_unset_metric(&candidate->metrics[type - 1]); + srte_lsp_unset_metric(candidate->lsp, type); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); +} + +/** + * Removes a candidate path metric constraint. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The LSP from which the metric should be removed + * @param type The metric type + */ +void srte_lsp_unset_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): candidate %s lsp metric %s (%u) unset", + endpoint, policy->color, candidate->name, + srte_candidate_metric_name(type), type); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_unset_metric(&lsp->metrics[type - 1]); +} + +void srte_unset_metric(struct srte_metric *metric) +{ + UNSET_FLAG(metric->flags, F_METRIC_IS_DEFINED); + UNSET_FLAG(metric->flags, F_METRIC_IS_BOUND); + UNSET_FLAG(metric->flags, F_METRIC_IS_COMPUTED); + metric->value = 0; +} + +/** + * Sets a candidate path objective function. + * + * @param candidate The candidate path of which the OF should be changed + * @param required If the constraint is required (true) or only desired (false) + * @param type The objective function type + */ +void srte_candidate_set_objfun(struct srte_candidate *candidate, bool required, + enum objfun_type type) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + candidate->objfun = type; + SET_FLAG(candidate->flags, F_CANDIDATE_HAS_OBJFUN); + COND_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN, required); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + zlog_debug("SR-TE(%s, %u): candidate %s %sobjective function set to %s", + endpoint, policy->color, candidate->name, + required ? "required " : "", objfun_type_name(type)); +} + +/** + * Removed the objective function constraint from a candidate path. + * + * @param candidate The candidate path from which the OF should be removed + */ +void srte_candidate_unset_objfun(struct srte_candidate *candidate) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + UNSET_FLAG(candidate->flags, F_CANDIDATE_HAS_OBJFUN); + UNSET_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + candidate->objfun = OBJFUN_UNDEFINED; + zlog_debug( + "SR-TE(%s, %u): candidate %s objective functions preferences unset", + endpoint, policy->color, candidate->name); +} + +static uint32_t filter_type_to_flag(enum affinity_filter_type type) +{ + switch (type) { + case AFFINITY_FILTER_EXCLUDE_ANY: + return F_CANDIDATE_HAS_EXCLUDE_ANY; + case AFFINITY_FILTER_INCLUDE_ANY: + return F_CANDIDATE_HAS_INCLUDE_ANY; + case AFFINITY_FILTER_INCLUDE_ALL: + return F_CANDIDATE_HAS_INCLUDE_ALL; + default: + return 0; + } +} + +static const char *filter_type_name(enum affinity_filter_type type) +{ + switch (type) { + case AFFINITY_FILTER_EXCLUDE_ANY: + return "exclude-any"; + case AFFINITY_FILTER_INCLUDE_ANY: + return "include-any"; + case AFFINITY_FILTER_INCLUDE_ALL: + return "include-all"; + default: + return "unknown"; + } +} + +/** + * Sets a candidate path affinity filter constraint. + * + * @param candidate The candidate path of which the constraint should be changed + * @param type The affinity constraint type to set + * @param filter The bitmask filter of the constraint + */ +void srte_candidate_set_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type, + uint32_t filter) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + assert(type > AFFINITY_FILTER_UNDEFINED); + assert(type <= MAX_AFFINITY_FILTER_TYPE); + SET_FLAG(candidate->flags, filter_type_to_flag(type)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + candidate->affinity_filters[type - 1] = filter; + zlog_debug( + "SR-TE(%s, %u): candidate %s affinity filter %s set to 0x%08x", + endpoint, policy->color, candidate->name, + filter_type_name(type), filter); +} + +/** + * Removes a candidate path affinity filter constraint. + * + * @param candidate The candidate path from which the constraint should be + * removed + * @param type The affinity constraint type to remove + */ +void srte_candidate_unset_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + assert(type > AFFINITY_FILTER_UNDEFINED); + assert(type <= MAX_AFFINITY_FILTER_TYPE); + UNSET_FLAG(candidate->flags, filter_type_to_flag(type)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + candidate->affinity_filters[type - 1] = 0; + zlog_debug("SR-TE(%s, %u): candidate %s affinity filter %s unset", + endpoint, policy->color, candidate->name, + filter_type_name(type)); +} + +/** + * Searches for a candidate path of the given policy. + * + * @param policy The policy to search for candidate path + * @param preference The preference of the candidate path you are looking for + * @return The candidate path if found, NULL otherwise + */ +struct srte_candidate *srte_candidate_find(struct srte_policy *policy, + uint32_t preference) +{ + struct srte_candidate search; + + search.preference = preference; + return RB_FIND(srte_candidate_head, &policy->candidate_paths, &search); +} + +/** + * Searches for a an entry of a given segment list. + * + * @param segment_list The segment list to search for the entry + * @param index The index of the entry you are looking for + * @return The segment list entry if found, NULL otherwise. + */ +struct srte_segment_entry * +srte_segment_entry_find(struct srte_segment_list *segment_list, uint32_t index) +{ + struct srte_segment_entry search; + + search.index = index; + return RB_FIND(srte_segment_entry_head, &segment_list->segments, + &search); +} + +/** + * Updates a candidate status. + * + * @param candidate The candidate of which the status should be updated + * @param status The new candidate path status + */ +void srte_candidate_status_update(struct srte_candidate *candidate, int status) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[46]; + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + zlog_debug("SR-TE(%s, %u): zebra updated status to %d", endpoint, + policy->color, status); + switch (status) { + case ZEBRA_SR_POLICY_DOWN: + switch (policy->status) { + /* If the policy is GOING_UP, and zebra faild + to install it, we wait for zebra to retry */ + /* TODO: Add some timeout after which we would + get is back to DOWN and remove the + policy */ + case SRTE_POLICY_STATUS_GOING_UP: + case SRTE_POLICY_STATUS_DOWN: + return; + default: + zlog_debug("SR-TE(%s, %u): policy is DOWN", endpoint, + policy->color); + policy->status = SRTE_POLICY_STATUS_DOWN; + break; + } + break; + case ZEBRA_SR_POLICY_UP: + switch (policy->status) { + case SRTE_POLICY_STATUS_UP: + return; + default: + zlog_debug("SR-TE(%s, %u): policy is UP", endpoint, + policy->color); + policy->status = SRTE_POLICY_STATUS_UP; + break; + } + break; + } + + trigger_pathd_candidate_updated(candidate); +} + +/** + * Flags the segment lists from give originator for removal. + * + * The function srte_apply_changes must be called afterward for + * the segment list to be removed. + * + * @param originator The originator tag of the segment list to be marked + * @param force If the unset should be forced regardless of the originator + */ +void srte_candidate_unset_segment_list(const char *originator, bool force) +{ + if (originator == NULL) { + zlog_warn( + "Cannot unset segment list because originator is NULL"); + return; + } + + zlog_debug("Unset segment lists for originator %s", originator); + + /* Iterate the policies, then iterate each policy's candidate path + * to check the candidate path's segment list originator */ + struct srte_policy *policy; + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + zlog_debug("Unset segment lists checking policy %s", + policy->name); + struct srte_candidate *candidate; + RB_FOREACH (candidate, srte_candidate_head, + &policy->candidate_paths) { + zlog_debug("Unset segment lists checking candidate %s", + candidate->name); + if (candidate->lsp == NULL) { + continue; + } + + /* The candidate->lsp->segment_list is operational data, + * configured by the PCE. We dont want to modify the + * candidate->segment_list, + * which is configuration data. */ + struct srte_segment_list *segment_list = + candidate->lsp->segment_list; + if (segment_list == NULL) { + continue; + } + + if (segment_list->protocol_origin + == SRTE_ORIGIN_LOCAL) { + zlog_warn( + "Cannot unset segment list %s because it " + "was created locally", + segment_list->name); + continue; + } + + /* In the case of last pce,we force the unset + * because we don't have pce by prefix (TODO) is all + * 'global' */ + if (strncmp(segment_list->originator, originator, + sizeof(segment_list->originator)) + == 0 + || force) { + zlog_debug("Unset segment list %s", + segment_list->name); + SET_FLAG(segment_list->flags, + F_SEGMENT_LIST_DELETED); + SET_FLAG(candidate->flags, + F_CANDIDATE_MODIFIED); + candidate->lsp->segment_list = NULL; + } + } + } +} + +/** + * Gives a string representation of given protocol origin enum. + * + * @param origin The enum you want a string representation of + * @return The string representation of given enum + */ +const char *srte_origin2str(enum srte_protocol_origin origin) +{ + switch (origin) { + case SRTE_ORIGIN_PCEP: + return "PCEP"; + case SRTE_ORIGIN_BGP: + return "BGP"; + case SRTE_ORIGIN_LOCAL: + return "Local"; + default: + return "Unknown"; + } +} + +void trigger_pathd_candidate_created(struct srte_candidate *candidate) +{ + /* The hook is called asynchronously to let the PCEP module + time to send a response to the PCE before receiving any updates from + pathd. In addition, a minimum amount of time need to pass before + the hook is called to prevent the hook to be called multiple times + from changing the candidate by hand with the console */ + if (candidate->hook_timer != NULL) + return; + thread_add_timer(master, trigger_pathd_candidate_created_timer, + (void *)candidate, HOOK_DELAY, &candidate->hook_timer); +} + +int trigger_pathd_candidate_created_timer(struct thread *thread) +{ + struct srte_candidate *candidate = THREAD_ARG(thread); + candidate->hook_timer = NULL; + return hook_call(pathd_candidate_created, candidate); +} + +void trigger_pathd_candidate_updated(struct srte_candidate *candidate) +{ + /* The hook is called asynchronously to let the PCEP module + time to send a response to the PCE before receiving any updates from + pathd. In addition, a minimum amount of time need to pass before + the hook is called to prevent the hook to be called multiple times + from changing the candidate by hand with the console */ + if (candidate->hook_timer != NULL) + return; + thread_add_timer(master, trigger_pathd_candidate_updated_timer, + (void *)candidate, HOOK_DELAY, &candidate->hook_timer); +} + +int trigger_pathd_candidate_updated_timer(struct thread *thread) +{ + struct srte_candidate *candidate = THREAD_ARG(thread); + candidate->hook_timer = NULL; + return hook_call(pathd_candidate_updated, candidate); +} + +void trigger_pathd_candidate_removed(struct srte_candidate *candidate) +{ + /* The hook needs to be call synchronously, otherwise the candidate + path will be already deleted when the handler is called */ + if (candidate->hook_timer != NULL) { + thread_cancel(&candidate->hook_timer); + candidate->hook_timer = NULL; + } + hook_call(pathd_candidate_removed, candidate); +} + +const char *srte_candidate_metric_name(enum srte_candidate_metric_type type) +{ + switch (type) { + case SRTE_CANDIDATE_METRIC_TYPE_IGP: + return "IGP"; + case SRTE_CANDIDATE_METRIC_TYPE_TE: + return "TE"; + case SRTE_CANDIDATE_METRIC_TYPE_HC: + return "HC"; + case SRTE_CANDIDATE_METRIC_TYPE_ABC: + return "ABC"; + case SRTE_CANDIDATE_METRIC_TYPE_LMLL: + return "LMLL"; + case SRTE_CANDIDATE_METRIC_TYPE_CIGP: + return "CIGP"; + case SRTE_CANDIDATE_METRIC_TYPE_CTE: + return "CTE"; + case SRTE_CANDIDATE_METRIC_TYPE_PIGP: + return "PIGP"; + case SRTE_CANDIDATE_METRIC_TYPE_PTE: + return "PTE"; + case SRTE_CANDIDATE_METRIC_TYPE_PHC: + return "PHC"; + case SRTE_CANDIDATE_METRIC_TYPE_MSD: + return "MSD"; + case SRTE_CANDIDATE_METRIC_TYPE_PD: + return "PD"; + case SRTE_CANDIDATE_METRIC_TYPE_PDV: + return "PDV"; + case SRTE_CANDIDATE_METRIC_TYPE_PL: + return "PL"; + case SRTE_CANDIDATE_METRIC_TYPE_PPD: + return "PPD"; + case SRTE_CANDIDATE_METRIC_TYPE_PPDV: + return "PPDV"; + case SRTE_CANDIDATE_METRIC_TYPE_PPL: + return "PPL"; + case SRTE_CANDIDATE_METRIC_TYPE_NAP: + return "NAP"; + case SRTE_CANDIDATE_METRIC_TYPE_NLP: + return "NLP"; + case SRTE_CANDIDATE_METRIC_TYPE_DC: + return "DC"; + case SRTE_CANDIDATE_METRIC_TYPE_BNC: + return "BNC"; + default: + return "UNKNOWN"; + } +} diff --git a/pathd/pathd.conf.sample b/pathd/pathd.conf.sample new file mode 100644 index 0000000000..fc7acafc1f --- /dev/null +++ b/pathd/pathd.conf.sample @@ -0,0 +1,33 @@ +! Default pathd configuration sample +! +password frr +log stdout + +segment-routing + traffic-eng + segment-list test1 + index 10 mpls label 123 + index 20 mpls label 456 + ! + segment-list test2 + index 10 mpls label 321 + index 20 mpls label 654 + ! + policy color 1 endpoint 1.1.1.1 + name one + binding-sid 100 + candidate-path preference 100 name test1 explicit segment-list test1 + candidate-path preference 200 name test2 explicit segment-list test2 + ! + policy color 2 endpoint 2.2.2.2 + name two + binding-sid 101 + candidate-path preference 100 name def explicit segment-list test2 + candidate-path preference 200 name dyn dynamic + bandwidth 12345 + metric bound abc 16 required + metric te 10 + ! + ! + ! +! diff --git a/pathd/pathd.h b/pathd/pathd.h new file mode 100644 index 0000000000..4879239db8 --- /dev/null +++ b/pathd/pathd.h @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2020 NetDEF, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_PATHD_H_ +#define _FRR_PATHD_H_ + +#include "lib/mpls.h" +#include "lib/ipaddr.h" +#include "lib/srte.h" +#include "lib/hook.h" + +enum srte_protocol_origin { + SRTE_ORIGIN_UNDEFINED = 0, + SRTE_ORIGIN_PCEP = 1, + SRTE_ORIGIN_BGP = 2, + SRTE_ORIGIN_LOCAL = 3, +}; + +enum srte_policy_status { + SRTE_POLICY_STATUS_UNKNOWN = 0, + SRTE_POLICY_STATUS_DOWN = 1, + SRTE_POLICY_STATUS_UP = 2, + SRTE_POLICY_STATUS_GOING_DOWN = 3, + SRTE_POLICY_STATUS_GOING_UP = 4 +}; + +enum srte_candidate_type { + SRTE_CANDIDATE_TYPE_UNDEFINED = 0, + SRTE_CANDIDATE_TYPE_EXPLICIT = 1, + SRTE_CANDIDATE_TYPE_DYNAMIC = 2, +}; + +enum srte_candidate_metric_type { + /* IGP metric */ + SRTE_CANDIDATE_METRIC_TYPE_IGP = 1, + /* TE metric */ + SRTE_CANDIDATE_METRIC_TYPE_TE = 2, + /* Hop Counts */ + SRTE_CANDIDATE_METRIC_TYPE_HC = 3, + /* Aggregate bandwidth consumption */ + SRTE_CANDIDATE_METRIC_TYPE_ABC = 4, + /* Load of the most loaded link */ + SRTE_CANDIDATE_METRIC_TYPE_LMLL = 5, + /* Cumulative IGP cost */ + SRTE_CANDIDATE_METRIC_TYPE_CIGP = 6, + /* Cumulative TE cost */ + SRTE_CANDIDATE_METRIC_TYPE_CTE = 7, + /* P2MP IGP metric */ + SRTE_CANDIDATE_METRIC_TYPE_PIGP = 8, + /* P2MP TE metric */ + SRTE_CANDIDATE_METRIC_TYPE_PTE = 9, + /* P2MP hop count metric */ + SRTE_CANDIDATE_METRIC_TYPE_PHC = 10, + /* Segment-ID (SID) Depth */ + SRTE_CANDIDATE_METRIC_TYPE_MSD = 11, + /* Path Delay metric */ + SRTE_CANDIDATE_METRIC_TYPE_PD = 12, + /* Path Delay Variation metric */ + SRTE_CANDIDATE_METRIC_TYPE_PDV = 13, + /* Path Loss metric */ + SRTE_CANDIDATE_METRIC_TYPE_PL = 14, + /* P2MP Path Delay metric */ + SRTE_CANDIDATE_METRIC_TYPE_PPD = 15, + /* P2MP Path Delay variation metric */ + SRTE_CANDIDATE_METRIC_TYPE_PPDV = 16, + /* P2MP Path Loss metric */ + SRTE_CANDIDATE_METRIC_TYPE_PPL = 17, + /* Number of adaptations on a path */ + SRTE_CANDIDATE_METRIC_TYPE_NAP = 18, + /* Number of layers on a path */ + SRTE_CANDIDATE_METRIC_TYPE_NLP = 19, + /* Domain Count metric */ + SRTE_CANDIDATE_METRIC_TYPE_DC = 20, + /* Border Node Count metric */ + SRTE_CANDIDATE_METRIC_TYPE_BNC = 21, +}; +#define MAX_METRIC_TYPE 21 + +enum srte_segment_nai_type { + SRTE_SEGMENT_NAI_TYPE_NONE = 0, + SRTE_SEGMENT_NAI_TYPE_IPV4_NODE = 1, + SRTE_SEGMENT_NAI_TYPE_IPV6_NODE = 2, + SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY = 3, + SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY = 4, + SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY = 5 +}; + +enum objfun_type { + OBJFUN_UNDEFINED = 0, + /* Minimum Cost Path [RFC5541] */ + OBJFUN_MCP = 1, + /* Minimum Load Path [RFC5541] */ + OBJFUN_MLP = 2, + /* Maximum residual Bandwidth Path [RFC5541] */ + OBJFUN_MBP = 3, + /* Minimize aggregate Bandwidth Consumption [RFC5541] */ + OBJFUN_MBC = 4, + /* Minimize the Load of the most loaded Link [RFC5541] */ + OBJFUN_MLL = 5, + /* Minimize the Cumulative Cost of a set of paths [RFC5541] */ + OBJFUN_MCC = 6, + /* Shortest Path Tree [RFC8306] */ + OBJFUN_SPT = 7, + /* Minimum Cost Tree [RFC8306] */ + OBJFUN_MCT = 8, + /* Minimum Packet Loss Path [RFC8233] */ + OBJFUN_MPLP = 9, + /* Maximum Under-Utilized Path [RFC8233] */ + OBJFUN_MUP = 10, + /* Maximum Reserved Under-Utilized Path [RFC8233] */ + OBJFUN_MRUP = 11, + /* Minimize the number of Transit Domains [RFC8685] */ + OBJFUN_MTD = 12, + /* Minimize the number of Border Nodes [RFC8685] */ + OBJFUN_MBN = 13, + /* Minimize the number of Common Transit Domains [RFC8685] */ + OBJFUN_MCTD = 14, + /* Minimize the number of Shared Links [RFC8800] */ + OBJFUN_MSL = 15, + /* Minimize the number of Shared SRLGs [RFC8800] */ + OBJFUN_MSS = 16, + /* Minimize the number of Shared Nodes [RFC8800] */ + OBJFUN_MSN = 17, +}; +#define MAX_OBJFUN_TYPE 17 + +enum affinity_filter_type { + AFFINITY_FILTER_UNDEFINED = 0, + AFFINITY_FILTER_EXCLUDE_ANY = 1, + AFFINITY_FILTER_INCLUDE_ANY = 2, + AFFINITY_FILTER_INCLUDE_ALL = 3, +}; +#define MAX_AFFINITY_FILTER_TYPE 3 + +struct srte_segment_list; + +struct srte_segment_entry { + RB_ENTRY(srte_segment_entry) entry; + + /* The segment list the entry belong to */ + struct srte_segment_list *segment_list; + + /* Index of the Label. */ + uint32_t index; + + /* Label Value. */ + mpls_label_t sid_value; + + /* NAI Type */ + enum srte_segment_nai_type nai_type; + /* NAI local address when nai type is not NONE */ + struct ipaddr nai_local_addr; + /* NAI local interface when nai type is not IPv4 unnumbered adjacency */ + uint32_t nai_local_iface; + /* NAI local interface when nai type is IPv4 or IPv6 adjacency */ + struct ipaddr nai_remote_addr; + /* NAI remote interface when nai type is not IPv4 unnumbered adjacency + */ + uint32_t nai_remote_iface; +}; +RB_HEAD(srte_segment_entry_head, srte_segment_entry); +RB_PROTOTYPE(srte_segment_entry_head, srte_segment_entry, entry, + srte_segment_entry_compare) + +struct srte_segment_list { + RB_ENTRY(srte_segment_list) entry; + + /* Name of the Segment List. */ + char name[64]; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* Nexthops. */ + struct srte_segment_entry_head segments; + + /* Status flags. */ + uint16_t flags; +#define F_SEGMENT_LIST_NEW 0x0002 +#define F_SEGMENT_LIST_MODIFIED 0x0004 +#define F_SEGMENT_LIST_DELETED 0x0008 +}; +RB_HEAD(srte_segment_list_head, srte_segment_list); +RB_PROTOTYPE(srte_segment_list_head, srte_segment_list, entry, + srte_segment_list_compare) + +struct srte_metric { + uint16_t flags; +#define F_METRIC_IS_DEFINED 0x0001 +#define F_METRIC_IS_REQUIRED 0x0002 +#define F_METRIC_IS_BOUND 0x0004 +#define F_METRIC_IS_COMPUTED 0x0008 + float value; +}; + +/* Runtime information about the candidate path */ +struct srte_lsp { + /* Backpointer to the Candidate Path. */ + struct srte_candidate *candidate; + + /* The associated Segment List. */ + struct srte_segment_list *segment_list; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* The Discriminator */ + uint32_t discriminator; + + /* Flags. */ + uint32_t flags; + + /* Metrics LSP Values */ + struct srte_metric metrics[MAX_METRIC_TYPE]; + + /* Bandwidth Configured Value */ + float bandwidth; + + /* The objective function in used */ + enum objfun_type objfun; +}; + +/* Configured candidate path */ +struct srte_candidate { + RB_ENTRY(srte_candidate) entry; + + /* Backpointer to SR Policy */ + struct srte_policy *policy; + + /* The LSP associated with this candidate path. */ + struct srte_lsp *lsp; + + /* Administrative preference. */ + uint32_t preference; + + /* Symbolic Name. */ + char name[64]; + + /* The associated Segment List. */ + struct srte_segment_list *segment_list; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* The Discriminator */ + uint32_t discriminator; + + /* The Type (explicit or dynamic) */ + enum srte_candidate_type type; + + /* Flags. */ + uint32_t flags; +#define F_CANDIDATE_BEST 0x0001 +#define F_CANDIDATE_NEW 0x0002 +#define F_CANDIDATE_MODIFIED 0x0004 +#define F_CANDIDATE_DELETED 0x0008 +#define F_CANDIDATE_HAS_BANDWIDTH 0x0100 +#define F_CANDIDATE_REQUIRED_BANDWIDTH 0x0200 +#define F_CANDIDATE_HAS_OBJFUN 0x0400 +#define F_CANDIDATE_REQUIRED_OBJFUN 0x0800 +#define F_CANDIDATE_HAS_EXCLUDE_ANY 0x1000 +#define F_CANDIDATE_HAS_INCLUDE_ANY 0x2000 +#define F_CANDIDATE_HAS_INCLUDE_ALL 0x4000 + + /* Metrics Configured Values */ + struct srte_metric metrics[MAX_METRIC_TYPE]; + + /* Bandwidth Configured Value */ + float bandwidth; + + /* Configured objective functions */ + enum objfun_type objfun; + + /* Path constraints attribute filters */ + uint32_t affinity_filters[MAX_AFFINITY_FILTER_TYPE]; + + /* Hooks delaying timer */ + struct thread *hook_timer; +}; + +RB_HEAD(srte_candidate_head, srte_candidate); +RB_PROTOTYPE(srte_candidate_head, srte_candidate, entry, srte_candidate_compare) + +struct srte_policy { + RB_ENTRY(srte_policy) entry; + + /* Color */ + uint32_t color; + + /* Endpoint */ + struct ipaddr endpoint; + + /* Name */ + char name[64]; + + /* Binding SID */ + mpls_label_t binding_sid; + + /* Operational Status of the policy */ + enum srte_policy_status status; + + /* Best candidate path. */ + struct srte_candidate *best_candidate; + + /* Candidate Paths */ + struct srte_candidate_head candidate_paths; + /* Status flags. */ + uint16_t flags; +#define F_POLICY_NEW 0x0002 +#define F_POLICY_MODIFIED 0x0004 +#define F_POLICY_DELETED 0x0008 +}; +RB_HEAD(srte_policy_head, srte_policy); +RB_PROTOTYPE(srte_policy_head, srte_policy, entry, srte_policy_compare) + +DECLARE_HOOK(pathd_candidate_created, (struct srte_candidate * candidate), + (candidate)) +DECLARE_HOOK(pathd_candidate_updated, (struct srte_candidate * candidate), + (candidate)) +DECLARE_HOOK(pathd_candidate_removed, (struct srte_candidate * candidate), + (candidate)) + +extern struct srte_segment_list_head srte_segment_lists; +extern struct srte_policy_head srte_policies; +extern struct zebra_privs_t pathd_privs; + +/* master thread, defined in path_main.c */ +extern struct thread_master *master; + +/* pathd.c */ +struct srte_segment_list *srte_segment_list_add(const char *name); +void srte_segment_list_del(struct srte_segment_list *segment_list); +struct srte_segment_list *srte_segment_list_find(const char *name); +struct srte_segment_entry * +srte_segment_entry_add(struct srte_segment_list *segment_list, uint32_t index); +void srte_segment_entry_del(struct srte_segment_entry *segment); +void srte_segment_entry_set_nai(struct srte_segment_entry *segment, + enum srte_segment_nai_type type, + struct ipaddr *local_ip, uint32_t local_iface, + struct ipaddr *remote_ip, + uint32_t remote_iface); +struct srte_policy *srte_policy_add(uint32_t color, struct ipaddr *endpoint); +void srte_policy_del(struct srte_policy *policy); +struct srte_policy *srte_policy_find(uint32_t color, struct ipaddr *endpoint); +void srte_policy_update_binding_sid(struct srte_policy *policy, + uint32_t binding_sid); +void srte_apply_changes(void); +void srte_policy_apply_changes(struct srte_policy *policy); +struct srte_candidate *srte_candidate_add(struct srte_policy *policy, + uint32_t preference); +void srte_candidate_del(struct srte_candidate *candidate); +void srte_candidate_set_bandwidth(struct srte_candidate *candidate, + float bandwidth, bool required); +void srte_candidate_unset_bandwidth(struct srte_candidate *candidate); +void srte_candidate_set_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type, + float value, bool required, bool is_cound, + bool is_computed); +void srte_candidate_unset_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type); +void srte_candidate_set_objfun(struct srte_candidate *candidate, bool required, + enum objfun_type type); +void srte_candidate_unset_objfun(struct srte_candidate *candidate); +void srte_candidate_set_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type, + uint32_t filter); +void srte_candidate_unset_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type); +void srte_lsp_set_bandwidth(struct srte_lsp *lsp, float bandwidth, + bool required); +void srte_lsp_unset_bandwidth(struct srte_lsp *lsp); +void srte_lsp_set_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type, float value, + bool required, bool is_cound, bool is_computed); +void srte_lsp_unset_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type); +struct srte_candidate *srte_candidate_find(struct srte_policy *policy, + uint32_t preference); +struct srte_segment_entry * +srte_segment_entry_find(struct srte_segment_list *segment_list, uint32_t index); +void srte_candidate_status_update(struct srte_candidate *candidate, int status); +void srte_candidate_unset_segment_list(const char *originator, bool force); +const char *srte_origin2str(enum srte_protocol_origin origin); + +/* path_cli.c */ +void path_cli_init(void); + +#endif /* _FRR_PATHD_H_ */ diff --git a/pathd/subdir.am b/pathd/subdir.am new file mode 100644 index 0000000000..23399b9d81 --- /dev/null +++ b/pathd/subdir.am @@ -0,0 +1,48 @@ +# +# pathd +# + +if PATHD +noinst_LIBRARIES += pathd/libpath.a +sbin_PROGRAMS += pathd/pathd +dist_examples_DATA += pathd/pathd.conf.sample +vtysh_scan += $(top_srcdir)/pathd/path_cli.c +vtysh_daemons += pathd +# TODO add man page +#man8 += $(MANBUILD)/pathd.8 +endif + +pathd_libpath_a_SOURCES = \ + pathd/path_cli.c \ + pathd/path_debug.c \ + pathd/path_errors.c \ + pathd/path_main.c \ + pathd/path_memory.c \ + pathd/path_nb.c \ + pathd/path_nb_config.c \ + pathd/path_nb_state.c \ + pathd/path_zebra.c \ + pathd/pathd.c \ + # end + +clippy_scan += \ + pathd/path_cli.c \ + # end + +noinst_HEADERS += \ + pathd/path_debug.h \ + pathd/path_errors.h \ + pathd/path_memory.h \ + pathd/path_nb.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) diff --git a/redhat/frr.spec.in b/redhat/frr.spec.in index 4ebc504b8a..02c272f47c 100644 --- a/redhat/frr.spec.in +++ b/redhat/frr.spec.in @@ -27,6 +27,7 @@ %{!?with_vrrpd: %global with_vrrpd 1 } %{!?with_rtadv: %global with_rtadv 1 } %{!?with_watchfrr: %global with_watchfrr 1 } +%{!?with_pathd: %global with_pathd 1 } # user and group %{!?frr_user: %global frr_user frr } @@ -87,7 +88,7 @@ %{!?frr_gid: %global frr_gid 92 } %{!?vty_gid: %global vty_gid 85 } -%define daemon_list zebra ripd ospfd bgpd isisd ripngd ospf6d pbrd staticd bfdd fabricd +%define daemon_list zebra ripd ospfd bgpd isisd ripngd ospf6d pbrd staticd bfdd fabricd pathd %if %{with_ldpd} %define daemon_ldpd ldpd @@ -143,7 +144,13 @@ %define daemon_bfdd "" %endif -%define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} %{daemon_bfdd} %{daemon_vrrpd} +%if %{with_pathd} + %define daemon_pathd pathd +%else + %define daemon_pathd "" +%endif + +%define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} %{daemon_bfdd} %{daemon_vrrpd} %{daemon_pathd} #release sub-revision (the two digits after the CONFDATE) %{!?release_rev: %global release_rev 01 } @@ -397,6 +404,11 @@ routing state through standard SNMP MIBs. --enable-bfdd \ %else --disable-bfdd \ +%endif +%if %{with_pathd} + --enable-pathd \ +%else + --disable-pathd \ %endif --enable-snmp # end @@ -526,6 +538,9 @@ zebra_spec_add_service fabricd 2618/tcp "Fabricd vty" %if %{with_vrrpd} zebra_spec_add_service vrrpd 2619/tcp "VRRPd vty" %endif +%if %{with_pathd} + zebra_spec_add_service pathd 2620/tcp "Pathd vty" +%endif %if "%{initsystem}" == "systemd" for daemon in %all_daemons ; do @@ -681,6 +696,9 @@ fi %if %{with_bfdd} %{_sbindir}/bfdd %endif +%if %{with_pathd} + %{_sbindir}/pathd +%endif %{_libdir}/libfrr.so* %{_libdir}/libfrrcares* %{_libdir}/libfrrospf* @@ -798,6 +816,9 @@ sed -i 's/ -M rpki//' %{_sysconfdir}/frr/daemons - migrate route-maps to use northbound interface - plus countless bug fixes and other improvements +* Mon Jun 15 2020 Sascha Kattelmann +- Add Pathd support + * Wed May 06 2020 David Lamparter - 7.3.1 - upstream 7.3.1 diff --git a/tests/topotests/isis-sr-te-topo1/dst/zebra.conf b/tests/topotests/isis-sr-te-topo1/dst/zebra.conf new file mode 100644 index 0000000000..e873ac8a5c --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/dst/zebra.conf @@ -0,0 +1,19 @@ +log file zebra.log +! +hostname dst +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 9.9.9.2/32 + ipv6 address 2001:db8:1066::2/128 +! +interface eth-rt6 + ip address 10.0.11.2/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-sr-te-topo1/rt1/bgpd.conf b/tests/topotests/isis-sr-te-topo1/rt1/bgpd.conf new file mode 100644 index 0000000000..efc03701b5 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/bgpd.conf @@ -0,0 +1,16 @@ +log file bgpd.log +! +router bgp 1 + bgp router-id 1.1.1.1 + neighbor 6.6.6.6 remote-as 1 + neighbor 6.6.6.6 update-source lo + ! + address-family ipv4 unicast + redistribute static + neighbor 6.6.6.6 next-hop-self + neighbor 6.6.6.6 route-map SET_SR_POLICY in + exit-address-family +! +route-map SET_SR_POLICY permit 10 + set sr-te color 1 +! diff --git a/tests/topotests/isis-sr-te-topo1/rt1/isisd.conf b/tests/topotests/isis-sr-te-topo1/rt1/isisd.conf new file mode 100644 index 0000000000..70ae1b07f5 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/isisd.conf @@ -0,0 +1,30 @@ +password 1 +hostname rt1 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-sw1 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 3 +! +router isis 1 + net 49.0000.0000.0000.0001.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 1.1.1.1/32 index 10 + segment-routing prefix 2001:db8:1000::1/128 index 11 +! diff --git a/tests/topotests/isis-sr-te-topo1/rt1/pathd.conf b/tests/topotests/isis-sr-te-topo1/rt1/pathd.conf new file mode 100644 index 0000000000..911971496e --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/pathd.conf @@ -0,0 +1,21 @@ +log file pathd.log +! +hostname rt1 +! +segment-routing + traffic-eng + segment-list default + index 10 mpls label 16050 + index 20 mpls label 16060 + ! + segment-list test + index 10 mpls label 16020 + index 20 mpls label 16040 + index 30 mpls label 16060 + ! + policy color 1 endpoint 6.6.6.6 + name default + binding-sid 1111 + ! + ! +! \ No newline at end of file diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step1/show_mpls_table_with_candidate.ref b/tests/topotests/isis-sr-te-topo1/rt1/step1/show_mpls_table_with_candidate.ref new file mode 100644 index 0000000000..d4b27d157d --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step1/show_mpls_table_with_candidate.ref @@ -0,0 +1,91 @@ +{ + "1111":{ + "inLabel":1111, + "installed":true, + "nexthops":[ + { + "type":"SR-TE", + "outLabel":16050, + "outLabelStack":[ + 16050, + 16060 + ], + "distance":150, + "installed":true, + "nexthop":"10.0.1.3" + } + ] + }, + "16020":{ + "inLabel":16020, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16020, + "distance":150, + "installed":true, + "nexthop":"10.0.1.2" + } + ] + }, + "16030":{ + "inLabel":16030, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16030, + "distance":150, + "installed":true, + "nexthop":"10.0.1.3" + } + ] + }, + "16040":{ + "inLabel":16040, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16040, + "distance":150, + "installed":true, + "nexthop":"10.0.1.2" + } + ] + }, + "16050":{ + "inLabel":16050, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16050, + "distance":150, + "installed":true, + "nexthop":"10.0.1.3" + } + ] + }, + "16060":{ + "inLabel":16060, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16060, + "distance":150, + "installed":true, + "nexthop":"10.0.1.3" + }, + { + "type":"SR (IS-IS)", + "outLabel":16060, + "distance":150, + "installed":true, + "nexthop":"10.0.1.2" + } + ] + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step1/show_mpls_table_without_candidate.ref b/tests/topotests/isis-sr-te-topo1/rt1/step1/show_mpls_table_without_candidate.ref new file mode 100644 index 0000000000..5fe58d0824 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step1/show_mpls_table_without_candidate.ref @@ -0,0 +1,74 @@ +{ + "16020":{ + "inLabel":16020, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16020, + "distance":150, + "installed":true, + "nexthop":"10.0.1.2" + } + ] + }, + "16030":{ + "inLabel":16030, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16030, + "distance":150, + "installed":true, + "nexthop":"10.0.1.3" + } + ] + }, + "16040":{ + "inLabel":16040, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16040, + "distance":150, + "installed":true, + "nexthop":"10.0.1.2" + } + ] + }, + "16050":{ + "inLabel":16050, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16050, + "distance":150, + "installed":true, + "nexthop":"10.0.1.3" + } + ] + }, + "16060":{ + "inLabel":16060, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16060, + "distance":150, + "installed":true, + "nexthop":"10.0.1.3" + }, + { + "type":"SR (IS-IS)", + "outLabel":16060, + "distance":150, + "installed":true, + "nexthop":"10.0.1.2" + } + ] + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step2/show_operational_data.ref b/tests/topotests/isis-sr-te-topo1/rt1/step2/show_operational_data.ref new file mode 100644 index 0000000000..4ef8d946f2 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step2/show_operational_data.ref @@ -0,0 +1,13 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "6.6.6.6", + "is-operational": false + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step2/show_operational_data_with_candidate.ref b/tests/topotests/isis-sr-te-topo1/rt1/step2/show_operational_data_with_candidate.ref new file mode 100644 index 0000000000..9b28f6a42b --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step2/show_operational_data_with_candidate.ref @@ -0,0 +1,20 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "6.6.6.6", + "is-operational": true, + "candidate-path": [ + { + "preference": 100, + "discriminator": "*", + "is-best-candidate-path": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step3/show_operational_data_with_single_candidate.ref b/tests/topotests/isis-sr-te-topo1/rt1/step3/show_operational_data_with_single_candidate.ref new file mode 100644 index 0000000000..9b28f6a42b --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step3/show_operational_data_with_single_candidate.ref @@ -0,0 +1,20 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "6.6.6.6", + "is-operational": true, + "candidate-path": [ + { + "preference": 100, + "discriminator": "*", + "is-best-candidate-path": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step3/show_operational_data_with_two_candidates.ref b/tests/topotests/isis-sr-te-topo1/rt1/step3/show_operational_data_with_two_candidates.ref new file mode 100644 index 0000000000..249117198a --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step3/show_operational_data_with_two_candidates.ref @@ -0,0 +1,25 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "6.6.6.6", + "is-operational": true, + "candidate-path": [ + { + "preference": 100, + "discriminator": "*", + "is-best-candidate-path": false + }, + { + "preference": 200, + "discriminator": "*", + "is-best-candidate-path": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table.ref b/tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table.ref new file mode 100644 index 0000000000..21f71f1254 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table.ref @@ -0,0 +1,20 @@ +{ + "1111":{ + "inLabel":1111, + "installed":true, + "nexthops":[ + { + "type":"SR-TE", + "outLabel":16020, + "outLabelStack":[ + 16020, + 16040, + 16060 + ], + "distance":150, + "installed":true, + "nexthop":"10.0.1.2" + } + ] + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table_add_segment.ref b/tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table_add_segment.ref new file mode 100644 index 0000000000..3635c89efb --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table_add_segment.ref @@ -0,0 +1,21 @@ +{ + "1111":{ + "inLabel":1111, + "installed":true, + "nexthops":[ + { + "type":"SR-TE", + "outLabel":16020, + "outLabelStack":[ + 16020, + 16040, + 16050, + 16060 + ], + "distance":150, + "installed":true, + "nexthop":"10.0.1.2" + } + ] + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table_change_segment.ref b/tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table_change_segment.ref new file mode 100644 index 0000000000..5712d210d4 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step4/show_mpls_table_change_segment.ref @@ -0,0 +1,21 @@ +{ + "1111":{ + "inLabel":1111, + "installed":true, + "nexthops":[ + { + "type":"SR-TE", + "outLabel":16020, + "outLabelStack":[ + 16020, + 16040, + 16030, + 16060 + ], + "distance":150, + "installed":true, + "nexthop":"10.0.1.2" + } + ] + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step5/show_ip_route_bgp_active_srte.ref b/tests/topotests/isis-sr-te-topo1/rt1/step5/show_ip_route_bgp_active_srte.ref new file mode 100644 index 0000000000..5a76246e50 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step5/show_ip_route_bgp_active_srte.ref @@ -0,0 +1,29 @@ +{ + "9.9.9.2\/32":[ + { + "prefix":"9.9.9.2\/32", + "protocol":"bgp", + "installed":true, + "nexthops":[ + { + "ip":"6.6.6.6", + "afi":"ipv4", + "active":true, + "recursive":true, + "srteColor":1 + }, + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true, + "labels":[ + 16050, + 16060 + ] + } + ] + } + ] +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step5/show_ip_route_bgp_inactive_srte.ref b/tests/topotests/isis-sr-te-topo1/rt1/step5/show_ip_route_bgp_inactive_srte.ref new file mode 100644 index 0000000000..09d5958305 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step5/show_ip_route_bgp_inactive_srte.ref @@ -0,0 +1,38 @@ +{ + "9.9.9.2\/32":[ + { + "prefix":"9.9.9.2\/32", + "protocol":"bgp", + "installed":true, + "nexthops":[ + { + "ip":"6.6.6.6", + "afi":"ipv4", + "active":true, + "recursive":true, + "srteColor":1 + }, + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true, + "labels":[ + 16060 + ] + }, + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true, + "labels":[ + 16060 + ] + } + ] + } + ] +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step5/show_operational_data_active.ref b/tests/topotests/isis-sr-te-topo1/rt1/step5/show_operational_data_active.ref new file mode 100644 index 0000000000..e26039b835 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step5/show_operational_data_active.ref @@ -0,0 +1,20 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "6.6.6.6", + "is-operational": true, + "candidate-path": [ + { + "preference": 100, + "discriminator": "*", + "is-best-candidate-path": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/step5/show_operational_data_inactive.ref b/tests/topotests/isis-sr-te-topo1/rt1/step5/show_operational_data_inactive.ref new file mode 100644 index 0000000000..01505c0318 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/step5/show_operational_data_inactive.ref @@ -0,0 +1,20 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "6.6.6.6", + "is-operational": false, + "candidate-path": [ + { + "preference": 100, + "discriminator": "*", + "is-best-candidate-path": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt1/zebra.conf b/tests/topotests/isis-sr-te-topo1/rt1/zebra.conf new file mode 100644 index 0000000000..9d71d3005f --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt1/zebra.conf @@ -0,0 +1,19 @@ +log file zebra.log +! +hostname rt1 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 1.1.1.1/32 + ipv6 address 2001:db8:1000::1/128 +! +interface eth-sw1 + ip address 10.0.1.1/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-sr-te-topo1/rt2/isisd.conf b/tests/topotests/isis-sr-te-topo1/rt2/isisd.conf new file mode 100644 index 0000000000..733f26bc62 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt2/isisd.conf @@ -0,0 +1,41 @@ +hostname rt2 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-sw1 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 3 +! +interface eth-rt4-1 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt4-2 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 1 + net 49.0000.0000.0000.0002.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 2.2.2.2/32 index 20 no-php-flag + segment-routing prefix 2001:db8:1000::2/128 index 21 no-php-flag +! diff --git a/tests/topotests/isis-sr-te-topo1/rt2/zebra.conf b/tests/topotests/isis-sr-te-topo1/rt2/zebra.conf new file mode 100644 index 0000000000..dcb0686dc2 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt2/zebra.conf @@ -0,0 +1,25 @@ +log file zebra.log +! +hostname rt2 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 2.2.2.2/32 + ipv6 address 2001:db8:1000::2/128 +! +interface eth-sw1 + ip address 10.0.1.2/24 +! +interface eth-rt4-1 + ip address 10.0.2.2/24 +! +interface eth-rt4-2 + ip address 10.0.3.2/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-sr-te-topo1/rt3/isisd.conf b/tests/topotests/isis-sr-te-topo1/rt3/isisd.conf new file mode 100644 index 0000000000..2395906cbf --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt3/isisd.conf @@ -0,0 +1,41 @@ +hostname rt3 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-sw1 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 3 +! +interface eth-rt5-1 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt5-2 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 1 + net 49.0000.0000.0000.0003.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 3.3.3.3/32 index 30 no-php-flag + segment-routing prefix 2001:db8:1000::3/128 index 31 no-php-flag +! diff --git a/tests/topotests/isis-sr-te-topo1/rt3/zebra.conf b/tests/topotests/isis-sr-te-topo1/rt3/zebra.conf new file mode 100644 index 0000000000..3254529386 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt3/zebra.conf @@ -0,0 +1,25 @@ +log file zebra.log +! +hostname rt3 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 3.3.3.3/32 + ipv6 address 2001:db8:1000::3/128 +! +interface eth-sw1 + ip address 10.0.1.3/24 +! +interface eth-rt5-1 + ip address 10.0.4.3/24 +! +interface eth-rt5-2 + ip address 10.0.5.3/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-sr-te-topo1/rt4/isisd.conf b/tests/topotests/isis-sr-te-topo1/rt4/isisd.conf new file mode 100644 index 0000000000..07a7867cbb --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt4/isisd.conf @@ -0,0 +1,48 @@ +hostname rt4 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-rt2-1 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt2-2 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt5 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt6 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 1 + net 49.0000.0000.0000.0004.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 4.4.4.4/32 index 40 no-php-flag + segment-routing prefix 2001:db8:1000::4/128 index 41 no-php-flag +! diff --git a/tests/topotests/isis-sr-te-topo1/rt4/zebra.conf b/tests/topotests/isis-sr-te-topo1/rt4/zebra.conf new file mode 100644 index 0000000000..4945897e9d --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt4/zebra.conf @@ -0,0 +1,28 @@ +log file zebra.log +! +hostname rt4 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 4.4.4.4/32 + ipv6 address 2001:db8:1000::4/128 +! +interface eth-rt2-1 + ip address 10.0.2.4/24 +! +interface eth-rt2-2 + ip address 10.0.3.4/24 +! +interface eth-rt5 + ip address 10.0.6.4/24 +! +interface eth-rt6 + ip address 10.0.7.4/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-sr-te-topo1/rt5/isisd.conf b/tests/topotests/isis-sr-te-topo1/rt5/isisd.conf new file mode 100644 index 0000000000..b0fcdede07 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt5/isisd.conf @@ -0,0 +1,48 @@ +hostname rt5 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-rt3-1 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt3-2 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt4 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt6 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 1 + net 49.0000.0000.0000.0005.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 5.5.5.5/32 index 50 no-php-flag + segment-routing prefix 2001:db8:1000::5/128 index 51 no-php-flag +! diff --git a/tests/topotests/isis-sr-te-topo1/rt5/zebra.conf b/tests/topotests/isis-sr-te-topo1/rt5/zebra.conf new file mode 100644 index 0000000000..4cfea1a59f --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt5/zebra.conf @@ -0,0 +1,28 @@ +log file zebra.log +! +hostname rt5 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 5.5.5.5/32 + ipv6 address 2001:db8:1000::5/128 +! +interface eth-rt3-1 + ip address 10.0.4.5/24 +! +interface eth-rt3-2 + ip address 10.0.5.5/24 +! +interface eth-rt4 + ip address 10.0.6.5/24 +! +interface eth-rt6 + ip address 10.0.8.5/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-sr-te-topo1/rt6/bgpd.conf b/tests/topotests/isis-sr-te-topo1/rt6/bgpd.conf new file mode 100644 index 0000000000..e72ee52fce --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/bgpd.conf @@ -0,0 +1,12 @@ +log file bgpd.log +! +router bgp 1 + bgp router-id 6.6.6.6 + neighbor 1.1.1.1 remote-as 1 + neighbor 1.1.1.1 update-source lo + ! + address-family ipv4 unicast + redistribute static + neighbor 1.1.1.1 next-hop-self + exit-address-family +! diff --git a/tests/topotests/isis-sr-te-topo1/rt6/isisd.conf b/tests/topotests/isis-sr-te-topo1/rt6/isisd.conf new file mode 100644 index 0000000000..3be24ad24c --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/isisd.conf @@ -0,0 +1,36 @@ +hostname rt6 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-rt4 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt5 + ip router isis 1 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 1 + net 49.0000.0000.0000.0006.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 6.6.6.6/32 index 60 + segment-routing prefix 2001:db8:1000::6/128 index 61 +! diff --git a/tests/topotests/isis-sr-te-topo1/rt6/pathd.conf b/tests/topotests/isis-sr-te-topo1/rt6/pathd.conf new file mode 100644 index 0000000000..3bada7147c --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/pathd.conf @@ -0,0 +1,21 @@ +log file pathd.log +! +hostname rt6 +! +segment-routing + traffic-eng + segment-list default + index 10 mpls label 16020 + index 20 mpls label 16010 + ! + segment-list test + index 10 mpls label 16050 + index 20 mpls label 16030 + index 30 mpls label 16010 + ! + policy color 1 endpoint 1.1.1.1 + name default + binding-sid 6666 + ! + ! +! diff --git a/tests/topotests/isis-sr-te-topo1/rt6/step1/show_mpls_table_with_candidate.ref b/tests/topotests/isis-sr-te-topo1/rt6/step1/show_mpls_table_with_candidate.ref new file mode 100644 index 0000000000..2bb000346f --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/step1/show_mpls_table_with_candidate.ref @@ -0,0 +1,91 @@ +{ + "6666":{ + "inLabel":6666, + "installed":true, + "nexthops":[ + { + "type":"SR-TE", + "outLabel":16020, + "outLabelStack":[ + 16020, + 16010 + ], + "distance":150, + "installed":true, + "nexthop":"10.0.7.4" + } + ] + }, + "16010": { + "inLabel": 16010, + "installed": true, + "nexthops": [ + { + "distance": 150, + "installed": true, + "nexthop": "10.0.7.4", + "outLabel": 16010, + "type": "SR (IS-IS)" + }, + { + "distance": 150, + "installed": true, + "nexthop": "10.0.8.5", + "outLabel": 16010, + "type": "SR (IS-IS)" + } + ] + }, + "16020":{ + "inLabel":16020, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16020, + "distance":150, + "installed":true, + "nexthop":"10.0.7.4" + } + ] + }, + "16030":{ + "inLabel":16030, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16030, + "distance":150, + "installed":true, + "nexthop":"10.0.8.5" + } + ] + }, + "16040":{ + "inLabel":16040, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16040, + "distance":150, + "installed":true, + "nexthop":"10.0.7.4" + } + ] + }, + "16050":{ + "inLabel":16050, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16050, + "distance":150, + "installed":true, + "nexthop":"10.0.8.5" + } + ] + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt6/step1/show_mpls_table_without_candidate.ref b/tests/topotests/isis-sr-te-topo1/rt6/step1/show_mpls_table_without_candidate.ref new file mode 100644 index 0000000000..348f7761eb --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/step1/show_mpls_table_without_candidate.ref @@ -0,0 +1,74 @@ +{ + "16010": { + "inLabel": 16010, + "installed": true, + "nexthops": [ + { + "distance": 150, + "installed": true, + "nexthop": "10.0.7.4", + "outLabel": 16010, + "type": "SR (IS-IS)" + }, + { + "distance": 150, + "installed": true, + "nexthop": "10.0.8.5", + "outLabel": 16010, + "type": "SR (IS-IS)" + } + ] + }, + "16020":{ + "inLabel":16020, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16020, + "distance":150, + "installed":true, + "nexthop":"10.0.7.4" + } + ] + }, + "16030":{ + "inLabel":16030, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16030, + "distance":150, + "installed":true, + "nexthop":"10.0.8.5" + } + ] + }, + "16040":{ + "inLabel":16040, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16040, + "distance":150, + "installed":true, + "nexthop":"10.0.7.4" + } + ] + }, + "16050":{ + "inLabel":16050, + "installed":true, + "nexthops":[ + { + "type":"SR (IS-IS)", + "outLabel":16050, + "distance":150, + "installed":true, + "nexthop":"10.0.8.5" + } + ] + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt6/step2/show_operational_data.ref b/tests/topotests/isis-sr-te-topo1/rt6/step2/show_operational_data.ref new file mode 100644 index 0000000000..241c80bdd7 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/step2/show_operational_data.ref @@ -0,0 +1,13 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "1.1.1.1", + "is-operational": false + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt6/step2/show_operational_data_with_candidate.ref b/tests/topotests/isis-sr-te-topo1/rt6/step2/show_operational_data_with_candidate.ref new file mode 100644 index 0000000000..20ea69e386 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/step2/show_operational_data_with_candidate.ref @@ -0,0 +1,19 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "1.1.1.1", + "is-operational": true, + "candidate-path": [ + { + "preference": 100, + "is-best-candidate-path": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt6/step3/show_operational_data_with_single_candidate.ref b/tests/topotests/isis-sr-te-topo1/rt6/step3/show_operational_data_with_single_candidate.ref new file mode 100644 index 0000000000..20ea69e386 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/step3/show_operational_data_with_single_candidate.ref @@ -0,0 +1,19 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "1.1.1.1", + "is-operational": true, + "candidate-path": [ + { + "preference": 100, + "is-best-candidate-path": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt6/step3/show_operational_data_with_two_candidates.ref b/tests/topotests/isis-sr-te-topo1/rt6/step3/show_operational_data_with_two_candidates.ref new file mode 100644 index 0000000000..10cafe9091 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/step3/show_operational_data_with_two_candidates.ref @@ -0,0 +1,23 @@ +{ + "frr-pathd:pathd": { + "srte": { + "policy": [ + { + "color": 1, + "endpoint": "1.1.1.1", + "is-operational": true, + "candidate-path": [ + { + "preference": 100, + "is-best-candidate-path": false + }, + { + "preference": 200, + "is-best-candidate-path": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt6/step4/show_mpls_table.ref b/tests/topotests/isis-sr-te-topo1/rt6/step4/show_mpls_table.ref new file mode 100644 index 0000000000..95bf995e2e --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/step4/show_mpls_table.ref @@ -0,0 +1,20 @@ +{ + "6666":{ + "inLabel":6666, + "installed":true, + "nexthops":[ + { + "type":"SR-TE", + "outLabel":16050, + "outLabelStack":[ + 16050, + 16030, + 16010 + ], + "distance":150, + "installed":true, + "nexthop":"10.0.8.5" + } + ] + } +} diff --git a/tests/topotests/isis-sr-te-topo1/rt6/zebra.conf b/tests/topotests/isis-sr-te-topo1/rt6/zebra.conf new file mode 100644 index 0000000000..32c6e6c4e0 --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/rt6/zebra.conf @@ -0,0 +1,27 @@ +log file zebra.log +! +hostname rt6 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 6.6.6.6/32 + ipv6 address 2001:db8:1000::6/128 +! +interface eth-rt4 + ip address 10.0.7.6/24 +! +interface eth-rt5 + ip address 10.0.8.6/24 +! +interface eth-dst + ip address 10.0.11.1/24 +! +ip forwarding +! +ip route 9.9.9.2/32 10.0.11.2 +! +line vty +! diff --git a/tests/topotests/isis-sr-te-topo1/test_isis_sr_te_topo1.py b/tests/topotests/isis-sr-te-topo1/test_isis_sr_te_topo1.py new file mode 100755 index 0000000000..5e5c0f9fee --- /dev/null +++ b/tests/topotests/isis-sr-te-topo1/test_isis_sr_te_topo1.py @@ -0,0 +1,525 @@ +#!/usr/bin/env python + +# +# test_isis_sr_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_isis_sr_te_topo1.py: + + +---------+ + | | + | RT1 | + | 1.1.1.1 | + | | + +---------+ + |eth-sw1 + | + | + | + +---------+ | +---------+ + | | | | | + | RT2 |eth-sw1 | eth-sw1| RT3 | + | 2.2.2.2 +----------+----------+ 3.3.3.3 | + | | 10.0.1.0/24 | | + +---------+ +---------+ + eth-rt4-1| |eth-rt4-2 eth-rt5-1| |eth-rt5-2 + | | | | + 10.0.2.0/24| |10.0.3.0/24 10.0.4.0/24| |10.0.5.0/24 + | | | | + eth-rt2-1| |eth-rt2-2 eth-rt3-1| |eth-rt3-2 + +---------+ +---------+ + | | | | + | RT4 | 10.0.6.0/24 | RT5 | + | 4.4.4.4 +---------------------+ 5.5.5.5 | + | |eth-rt5 eth-rt4| | + +---------+ +---------+ + eth-rt6| |eth-rt6 + | | + 10.0.7.0/24| |10.0.8.0/24 + | +---------+ | + | | | | + | | RT6 | | + +----------+ 6.6.6.6 +-----------+ + eth-rt4| |eth-rt5 + +---------+ + |eth-dst (.1) + | + |10.0.11.0/24 + | + |eth-rt6 (.2) + +---------+ + | | + | DST | + | 9.9.9.2 | + | | + +---------+ + +""" + +import os +import sys +import pytest +import json +import re +from time import sleep +from functools import partial + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, '../')) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. +from mininet.topo import Topo + +class TemplateTopo(Topo): + "Test topology builder" + def build(self, *_args, **_opts): + "Build function" + tgen = get_topogen(self) + + # + # Define FRR Routers + # + for router in ['rt1', 'rt2', 'rt3', 'rt4', 'rt5', 'rt6', 'dst']: + tgen.add_router(router) + + # + # Define connections + # + switch = tgen.add_switch('s1') + switch.add_link(tgen.gears['rt1'], nodeif="eth-sw1") + switch.add_link(tgen.gears['rt2'], nodeif="eth-sw1") + switch.add_link(tgen.gears['rt3'], nodeif="eth-sw1") + + switch = tgen.add_switch('s2') + switch.add_link(tgen.gears['rt2'], nodeif="eth-rt4-1") + switch.add_link(tgen.gears['rt4'], nodeif="eth-rt2-1") + + switch = tgen.add_switch('s3') + switch.add_link(tgen.gears['rt2'], nodeif="eth-rt4-2") + switch.add_link(tgen.gears['rt4'], nodeif="eth-rt2-2") + + switch = tgen.add_switch('s4') + switch.add_link(tgen.gears['rt3'], nodeif="eth-rt5-1") + switch.add_link(tgen.gears['rt5'], nodeif="eth-rt3-1") + + switch = tgen.add_switch('s5') + switch.add_link(tgen.gears['rt3'], nodeif="eth-rt5-2") + switch.add_link(tgen.gears['rt5'], nodeif="eth-rt3-2") + + switch = tgen.add_switch('s6') + switch.add_link(tgen.gears['rt4'], nodeif="eth-rt5") + switch.add_link(tgen.gears['rt5'], nodeif="eth-rt4") + + switch = tgen.add_switch('s7') + switch.add_link(tgen.gears['rt4'], nodeif="eth-rt6") + switch.add_link(tgen.gears['rt6'], nodeif="eth-rt4") + + switch = tgen.add_switch('s8') + switch.add_link(tgen.gears['rt5'], nodeif="eth-rt6") + switch.add_link(tgen.gears['rt6'], nodeif="eth-rt5") + + switch = tgen.add_switch('s9') + switch.add_link(tgen.gears['rt6'], nodeif="eth-dst") + switch.add_link(tgen.gears['dst'], nodeif="eth-rt6") + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(TemplateTopo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.iteritems(): + router.load_config( + TopoRouter.RD_ZEBRA, + os.path.join(CWD, '{}/zebra.conf'.format(rname)) + ) + router.load_config( + TopoRouter.RD_ISIS, + os.path.join(CWD, '{}/isisd.conf'.format(rname)) + ) + router.load_config( + TopoRouter.RD_PATH, + os.path.join(CWD, '{}/pathd.conf'.format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, + os.path.join(CWD, '{}/bgpd.conf'.format(rname)) + ) + + tgen.start_router() + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + +def setup_testcase(msg): + logger.info(msg) + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + return tgen + +def print_cmd_result(rname, command): + print(get_topogen().gears[rname].vtysh_cmd(command, isjson=False)) + +def compare_json_test(router, command, reference, exact): + output = router.vtysh_cmd(command, isjson=True) + result = topotest.json_cmp(output, reference) + + # Note: topotest.json_cmp() just checks on inclusion of keys. + # For exact matching also compare the other way around. + if not result and exact: + return topotest.json_cmp(reference, output) + else: + return result + +def cmp_json_output(rname, command, reference, exact=False): + "Compare router JSON output" + + logger.info('Comparing router "%s" "%s" output', rname, command) + + tgen = get_topogen() + filename = '{}/{}/{}'.format(CWD, rname, reference) + expected = json.loads(open(filename).read()) + + # Run test function until we get an result. Wait at most 60 seconds. + test_func = partial(compare_json_test, + tgen.gears[rname], command, expected, exact) + _, diff = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assertmsg = '"{}" JSON output mismatches the expected result'.format(rname) + assert diff is None, assertmsg + +def cmp_json_output_exact(rname, command, reference): + return cmp_json_output(rname, command, reference, True) + +def add_candidate_path(rname, endpoint, pref, name, segment_list='default'): + get_topogen().net[rname].cmd(''' \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "policy color 1 endpoint ''' + endpoint + '''" \ + -c "candidate-path preference ''' + str(pref) + ''' name ''' + name + ''' explicit segment-list ''' + segment_list + '''"''') + +def delete_candidate_path(rname, endpoint, pref): + get_topogen().net[rname].cmd(''' \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "policy color 1 endpoint ''' + endpoint + '''" \ + -c "no candidate-path preference ''' + str(pref) + '''"''') + +def add_segment(rname, name, index, label): + get_topogen().net[rname].cmd(''' \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "segment-list ''' + name + '''" \ + -c "index ''' + str(index) + ''' mpls label ''' + str(label) + '''"''') + +def delete_segment(rname, name, index): + get_topogen().net[rname].cmd(''' \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "segment-list ''' + name + '''" \ + -c "no index ''' + str(index) + '''"''') + +def create_sr_policy(rname, endpoint, bsid): + get_topogen().net[rname].cmd(''' \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "policy color 1 endpoint ''' + endpoint + '''" \ + -c "name default" \ + -c "binding-sid ''' + str(bsid) + '''"''') + +def delete_sr_policy(rname, endpoint): + get_topogen().net[rname].cmd(''' \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "no policy color 1 endpoint ''' + endpoint + '''"''') + +def create_prefix_sid(rname, prefix, sid): + get_topogen().net[rname].cmd(''' \ + vtysh -c "conf t" \ + -c "router isis 1" \ + -c "segment-routing prefix ''' + prefix + " index " + str(sid) + '''"''') + +def delete_prefix_sid(rname, prefix): + get_topogen().net[rname].cmd(''' \ + vtysh -c "conf t" \ + -c "router isis 1" \ + -c "no segment-routing prefix "''' + prefix) + +# +# Step 1 +# +# Checking the MPLS table using a single SR Policy and a single Candidate Path +# +def test_srte_init_step1(): + setup_testcase("Test (step 1): wait for IS-IS convergence / label distribution") + + for rname in ['rt1', 'rt6']: + cmp_json_output(rname, + "show mpls table json", + "step1/show_mpls_table_without_candidate.ref") + +def test_srte_add_candidate_check_mpls_table_step1(): + setup_testcase("Test (step 1): check MPLS table regarding the added Candidate Path") + + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + add_candidate_path(rname, endpoint, 100, 'default') + cmp_json_output(rname, + "show mpls table json", + "step1/show_mpls_table_with_candidate.ref") + delete_candidate_path(rname, endpoint, 100) + +def test_srte_reinstall_sr_policy_check_mpls_table_step1(): + setup_testcase("Test (step 1): check MPLS table after the SR Policy was removed and reinstalled") + + for rname, endpoint, bsid in [('rt1', '6.6.6.6', 1111), ('rt6', '1.1.1.1', 6666)]: + add_candidate_path(rname, endpoint, 100, 'default') + delete_sr_policy(rname, endpoint) + cmp_json_output(rname, + "show mpls table json", + "step1/show_mpls_table_without_candidate.ref") + create_sr_policy(rname, endpoint, bsid) + add_candidate_path(rname, endpoint, 100, 'default') + cmp_json_output(rname, + "show mpls table json", + "step1/show_mpls_table_with_candidate.ref") + delete_candidate_path(rname, endpoint, 100) + +# +# Step 2 +# +# Checking pathd operational data using a single SR Policy and a single Candidate Path +# +def test_srte_bare_policy_step2(): + setup_testcase("Test (step 2): bare SR Policy should not be operational") + + for rname in ['rt1', 'rt6']: + cmp_json_output_exact(rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step2/show_operational_data.ref") + +def test_srte_add_candidate_check_operational_data_step2(): + setup_testcase("Test (step 2): add single Candidate Path, SR Policy should be operational") + + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + add_candidate_path(rname, endpoint, 100, 'default') + cmp_json_output(rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step2/show_operational_data_with_candidate.ref") + +def test_srte_config_remove_candidate_check_operational_data_step2(): + setup_testcase("Test (step 2): remove single Candidate Path, SR Policy should not be operational anymore") + + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + delete_candidate_path(rname, endpoint, 100) + cmp_json_output_exact(rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step2/show_operational_data.ref") + +# +# Step 3 +# +# Testing the Candidate Path selection +# +def test_srte_add_two_candidates_step3(): + setup_testcase("Test (step 3): second Candidate Path has higher Priority") + + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + for pref, cand_name in [('100', 'first'), ('200', 'second')]: + add_candidate_path(rname, endpoint, pref, cand_name) + cmp_json_output(rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step3/show_operational_data_with_two_candidates.ref") + + # cleanup + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + for pref in ['100', '200']: + delete_candidate_path(rname, endpoint, pref) + +def test_srte_add_two_candidates_with_reverse_priority_step3(): + setup_testcase("Test (step 3): second Candidate Path has lower Priority") + + # Use reversed priorities here + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + for pref, cand_name in [('200', 'first'), ('100', 'second')]: + add_candidate_path(rname, endpoint, pref, cand_name) + cmp_json_output(rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step3/show_operational_data_with_two_candidates.ref") + + # cleanup + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + for pref in ['100', '200']: + delete_candidate_path(rname, endpoint, pref) + +def test_srte_remove_best_candidate_step3(): + setup_testcase("Test (step 3): delete the Candidate Path with higher priority") + + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + for pref, cand_name in [('100', 'first'), ('200', 'second')]: + add_candidate_path(rname, endpoint, pref, cand_name) + + # Delete candidate with higher priority + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + delete_candidate_path(rname, endpoint, 200) + + # Candidate with lower priority should get active now + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + cmp_json_output(rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step3/show_operational_data_with_single_candidate.ref") + # cleanup + delete_candidate_path(rname, endpoint, 100) + +# +# Step 4 +# +# Checking MPLS table with a single SR Policy and a Candidate Path with different Segment Lists and other modifications +# +def test_srte_change_segment_list_check_mpls_table_step4(): + setup_testcase("Test (step 4): check MPLS table for changed Segment List") + + for rname, endpoint in [('rt1', '6.6.6.6'), ('rt6', '1.1.1.1')]: + add_candidate_path(rname, endpoint, 100, 'default') + # now change the segment list name + add_candidate_path(rname, endpoint, 100, 'default', 'test') + cmp_json_output(rname, + "show mpls table json", + "step4/show_mpls_table.ref") + delete_candidate_path(rname, endpoint, 100) + +def test_srte_segment_list_add_segment_check_mpls_table_step4(): + setup_testcase("Test (step 4): check MPLS table for added (then changed and finally deleted) segment") + + add_candidate_path('rt1', '6.6.6.6', 100, 'default', 'test') + + # first add a new segment + add_segment('rt1', 'test', 25, 16050) + cmp_json_output('rt1', + "show mpls table json", + "step4/show_mpls_table_add_segment.ref") + + # ... then change it ... + add_segment('rt1', 'test', 25, 16030) + cmp_json_output('rt1', + "show mpls table json", + "step4/show_mpls_table_change_segment.ref") + + # ... and finally delete it + delete_segment('rt1', 'test', 25) + cmp_json_output('rt1', + "show mpls table json", + "step4/show_mpls_table.ref") + delete_candidate_path('rt1', '6.6.6.6', 100) + +# +# Step 5 +# +# Checking the nexthop using a single SR Policy and a Candidate Path with configured route-map +# +def test_srte_route_map_with_sr_policy_check_nextop_step5(): + setup_testcase("Test (step 5): recursive nexthop learned through BGP neighbour should be aligned with SR Policy from route-map") + + # (re-)build the SR Policy two times to ensure that reinstalling still works + for i in [1,2]: + cmp_json_output('rt1', + "show ip route bgp json", + "step5/show_ip_route_bgp_inactive_srte.ref") + + delete_sr_policy('rt1', '6.6.6.6') + cmp_json_output('rt1', + "show ip route bgp json", + "step5/show_ip_route_bgp_inactive_srte.ref") + + create_sr_policy('rt1', '6.6.6.6', 1111) + cmp_json_output('rt1', + "show ip route bgp json", + "step5/show_ip_route_bgp_inactive_srte.ref") + + add_candidate_path('rt1', '6.6.6.6', 100, 'default') + cmp_json_output('rt1', + "show ip route bgp json", + "step5/show_ip_route_bgp_active_srte.ref") + + delete_candidate_path('rt1', '6.6.6.6', 100) + +def test_srte_route_map_with_sr_policy_reinstall_prefix_sid_check_nextop_step5(): + setup_testcase("Test (step 5): remove and re-install prefix SID on fist path element and check SR Policy activity") + + # first add a candidate path so the SR Policy is active + add_candidate_path('rt1', '6.6.6.6', 100, 'default') + cmp_json_output('rt1', + "show yang operational-data /frr-pathd:pathd pathd", + "step5/show_operational_data_active.ref") + + # delete prefix SID from first element of the configured path and check + # if the SR Policy is inactive since the label can't be resolved anymore + delete_prefix_sid('rt5', "5.5.5.5/32") + cmp_json_output('rt1', + "show yang operational-data /frr-pathd:pathd pathd", + "step5/show_operational_data_inactive.ref") + cmp_json_output('rt1', + "show ip route bgp json", + "step5/show_ip_route_bgp_inactive_srte.ref") + + # re-create the prefix SID and check if the SR Policy is active + create_prefix_sid('rt5', "5.5.5.5/32", 50) + cmp_json_output('rt1', + "show yang operational-data /frr-pathd:pathd pathd", + "step5/show_operational_data_active.ref") + cmp_json_output('rt1', + "show ip route bgp json", + "step5/show_ip_route_bgp_active_srte.ref") + +# Memory leak test template +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip('Memory leak test/report is disabled') + + tgen.report_memory_leaks() + +if __name__ == '__main__': + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index eaf7f90479..86f06b2af7 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -552,6 +552,7 @@ class TopoRouter(TopoGear): RD_SHARP = 14 RD_BABEL = 15 RD_PBRD = 16 + RD_PATH = 17 RD = { RD_ZEBRA: "zebra", RD_RIP: "ripd", @@ -569,6 +570,7 @@ class TopoRouter(TopoGear): RD_SHARP: "sharpd", RD_BABEL: "babeld", RD_PBRD: "pbrd", + RD_PATH: 'pathd', } def __init__(self, tgen, cls, name, **params): diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 20d60ebbef..d1e76866a7 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -1096,6 +1096,7 @@ class Router(Node): "sharpd": 0, "babeld": 0, "pbrd": 0, + 'pathd': 0 } self.daemons_options = {"zebra": ""} self.reportCores = True diff --git a/tools/frr-reload.py b/tools/frr-reload.py index 3121551ee5..137a3d5a03 100755 --- a/tools/frr-reload.py +++ b/tools/frr-reload.py @@ -626,6 +626,22 @@ end ctx_keys = [] current_context_lines = [] + elif ( + line == "exit" + and len(ctx_keys) > 1 + and ctx_keys[0].startswith("segment-routing") + ): + self.save_contexts(ctx_keys, current_context_lines) + + # Start a new context + ctx_keys = ctx_keys[:-1] + current_context_lines = [] + log.debug( + "LINE %-50s: popping segment routing sub-context to ctx%-50s", + line, + ctx_keys + ) + elif line in ["exit-address-family", "exit", "exit-vnc"]: # if this exit is for address-family ipv4 unicast, ignore the pop if main_ctx_key: @@ -637,7 +653,7 @@ end log.debug( "LINE %-50s: popping from subcontext to ctx%-50s", line, - ctx_keys, + ctx_keys ) elif line in ["exit-vni", "exit-ldp-if"]: @@ -727,6 +743,68 @@ end ) ctx_keys.append(line) + elif ( + line.startswith("traffic-eng") + and len(ctx_keys) == 1 + and ctx_keys[0].startswith("segment-routing") + ): + + # Save old context first + self.save_contexts(ctx_keys, current_context_lines) + current_context_lines = [] + log.debug( + "LINE %-50s: entering segment routing sub-context, append to ctx_keys", line + ) + ctx_keys.append(line) + + elif ( + line.startswith("segment-list ") + 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 = [] + log.debug( + "LINE %-50s: entering segment routing sub-context, append to ctx_keys", line + ) + ctx_keys.append(line) + + elif ( + line.startswith("policy ") + 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 = [] + log.debug( + "LINE %-50s: entering segment routing sub-context, append to ctx_keys", line + ) + ctx_keys.append(line) + + elif ( + line.startswith("candidate-path ") + and line.endswith(" dynamic") + and len(ctx_keys) == 3 + and ctx_keys[0].startswith("segment-routing") + and ctx_keys[1].startswith("traffic-eng") + and ctx_keys[2].startswith("policy") + ): + + # 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 candidate-path 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) @@ -1244,6 +1322,9 @@ def compare_context_objects(newconf, running): # Compare the two Config objects to find the lines that we need to add/del lines_to_add = [] lines_to_del = [] + pollist_to_del = [] + seglist_to_del = [] + candidates_to_add = [] delete_bgpd = False # Find contexts that are in newconf but not in running @@ -1326,6 +1407,32 @@ def compare_context_objects(newconf, running): (running_ctx_keys[:1], None) in lines_to_del): continue + # Segment routing and traffic engineering never need to be deleted + elif ( + len(running_ctx_keys) > 1 + and len(running_ctx_keys) < 3 + and running_ctx_keys[0].startswith('segment-routing') + ): + 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 ( + len(running_ctx_keys) == 3 + and running_ctx_keys[0].startswith('segment-routing') + and running_ctx_keys[2].startswith('segment-list') + ): + seglist_to_del.append((running_ctx_keys, None)) + + # Policies must be deleted after there candidate path, to be sure + # we add them to a separate array that is going to be appended at the end + elif ( + len(running_ctx_keys) == 3 + and running_ctx_keys[0].startswith('segment-routing') + and running_ctx_keys[2].startswith('policy') + ): + pollist_to_del.append((running_ctx_keys, None)) + # Non-global context elif running_ctx_keys and not any( "address-family" in key for key in running_ctx_keys @@ -1340,6 +1447,14 @@ def compare_context_objects(newconf, running): for line in running_ctx.lines: lines_to_del.append((running_ctx_keys, line)) + # if we have some policies commands to delete, append them to lines_to_del + if len(pollist_to_del) > 0: + lines_to_del.extend(pollist_to_del) + + # if we have some segment list commands to delete, append them to lines_to_del + if len(seglist_to_del) > 0: + lines_to_del.extend(seglist_to_del) + # Find the lines within each context to add # Find the lines within each context to del for (newconf_ctx_keys, newconf_ctx) in iteritems(newconf.contexts): @@ -1349,7 +1464,19 @@ def compare_context_objects(newconf, running): for line in newconf_ctx.lines: if line not in running_ctx.dlines: - lines_to_add.append((newconf_ctx_keys, line)) + + # candidate paths can only be added after the policy and segment list, + # so add them to a separate array that is going to be appended at the end + if ( + len(newconf_ctx_keys) == 3 + and newconf_ctx_keys[0].startswith('segment-routing') + and newconf_ctx_keys[2].startswith('policy ') + and line.startswith('candidate-path ') + ): + candidates_to_add.append((newconf_ctx_keys, line)) + + else: + lines_to_add.append((newconf_ctx_keys, line)) for line in running_ctx.lines: if line not in newconf_ctx.dlines: @@ -1358,10 +1485,27 @@ def compare_context_objects(newconf, running): for (newconf_ctx_keys, newconf_ctx) in iteritems(newconf.contexts): if newconf_ctx_keys not in running.contexts: - lines_to_add.append((newconf_ctx_keys, None)) - for line in newconf_ctx.lines: - lines_to_add.append((newconf_ctx_keys, line)) + # candidate paths can only be added after the policy and segment list, + # so add them to a separate array that is going to be appended at the end + if ( + len(newconf_ctx_keys) == 4 + and newconf_ctx_keys[0].startswith('segment-routing') + and newconf_ctx_keys[3].startswith('candidate-path') + ): + candidates_to_add.append((newconf_ctx_keys, None)) + for line in newconf_ctx.lines: + candidates_to_add.append((newconf_ctx_keys, line)) + + else: + lines_to_add.append((newconf_ctx_keys, None)) + + for line in newconf_ctx.lines: + lines_to_add.append((newconf_ctx_keys, line)) + + # if we have some candidate paths commands to add, append them to lines_to_add + if len(candidates_to_add) > 0: + lines_to_add.extend(candidates_to_add) (lines_to_add, lines_to_del) = check_for_exit_vrf(lines_to_add, lines_to_del) (lines_to_add, lines_to_del) = ignore_delete_re_add_lines( @@ -1523,10 +1667,11 @@ if __name__ == "__main__": "staticd", "vrrpd", "ldpd", + "pathd", ]: - log.error( - "Daemon %s is not a valid option for 'show running-config'" % args.daemon - ) + msg = "Daemon %s is not a valid option for 'show running-config'" % args.daemon + print(msg) + log.error(msg) sys.exit(1) vtysh = Vtysh(args.bindir, args.confdir, args.vty_socket, args.pathspace) @@ -1574,6 +1719,8 @@ if __name__ == "__main__": else: running.load_from_show_running(args.daemon) + + (lines_to_add, lines_to_del) = compare_context_objects(newconf, running) lines_to_configure = [] diff --git a/tools/frr.in b/tools/frr.in index b860797d5b..889c075f81 100755 --- a/tools/frr.in +++ b/tools/frr.in @@ -27,7 +27,7 @@ FRR_DEFAULT_PROFILE="@DFLT_NAME@" # traditional / datacenter # Local Daemon selection may be done by using /etc/frr/daemons. # See /usr/share/doc/frr/README.Debian.gz for further information. # Keep zebra first and do not list watchfrr! -DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd vrrpd" +DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd vrrpd pathd" MAX_INSTANCES=5 RELOAD_SCRIPT="$D_PATH/frr-reload.py" diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index c78132508f..93a63cd964 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -139,6 +139,7 @@ struct vtysh_client vtysh_client[] = { {.fd = -1, .name = "staticd", .flag = VTYSH_STATICD, .next = NULL}, {.fd = -1, .name = "bfdd", .flag = VTYSH_BFDD, .next = NULL}, {.fd = -1, .name = "vrrpd", .flag = VTYSH_VRRPD, .next = NULL}, + {.fd = -1, .name = "pathd", .flag = VTYSH_PATHD, .next = NULL}, }; /* Searches for client by name, returns index */ @@ -538,6 +539,11 @@ static int vtysh_execute_func(const char *line, int pager) || saved_node == LDP_IPV6_IFACE_NODE) && (tried == 1)) { vtysh_execute("exit"); + } else if ((saved_node == SR_SEGMENT_LIST_NODE + || saved_node == SR_POLICY_NODE + || saved_node == SR_CANDIDATE_DYN_NODE) + && (tried > 0)) { + vtysh_execute("exit"); } else if (tried) { vtysh_execute("end"); vtysh_execute("configure"); @@ -689,6 +695,7 @@ int vtysh_mark_file(const char *filename) int ret; vector vline; int tried = 0; + bool ending; const struct cmd_element *cmd; int saved_ret, prev_node; int lineno = 0; @@ -740,6 +747,12 @@ int vtysh_mark_file(const char *filename) vty->node = LDP_L2VPN_NODE; } break; + case SR_CANDIDATE_DYN_NODE: + if (strncmp(vty_buf_copy, " ", 2)) { + vty_out(vty, " exit\n"); + vty->node = SR_POLICY_NODE; + } + break; default: break; } @@ -812,6 +825,23 @@ int vtysh_mark_file(const char *filename) } else if ((prev_node == BFD_PEER_NODE) && (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)) + && (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); + if (ending) + tried--; + while (tried-- > 0) + vty_out(vty, "exit\n"); + if (ending) + vty_out(vty, "end\n"); } else if (tried) { vty_out(vty, "end\n"); } @@ -1219,6 +1249,41 @@ static struct cmd_node pw_node = { .prompt = "%s(config-pw)# ", }; +static struct cmd_node segment_routing_node = { + .name = "segment-routing", + .node = SEGMENT_ROUTING_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-sr)# ", +}; + +static struct cmd_node sr_traffic_eng_node = { + .name = "sr traffic-eng", + .node = SR_TRAFFIC_ENG_NODE, + .parent_node = SEGMENT_ROUTING_NODE, + .prompt = "%s(config-sr-te)# ", +}; + +static struct cmd_node srte_segment_list_node = { + .name = "srte segment-list", + .node = SR_SEGMENT_LIST_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-segment-list)# ", +}; + +static struct cmd_node srte_policy_node = { + .name = "srte policy", + .node = SR_POLICY_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-policy)# ", +}; + +static struct cmd_node srte_candidate_dyn_node = { + .name = "srte candidate-dyn", + .node = SR_CANDIDATE_DYN_NODE, + .parent_node = SR_POLICY_NODE, + .prompt = "%s(config-sr-te-candidate)# ", +}; + static struct cmd_node vrf_node = { .name = "vrf", .node = VRF_NODE, @@ -1974,6 +2039,60 @@ DEFUNSH(VTYSH_FABRICD, router_openfabric, router_openfabric_cmd, "router openfab } #endif /* HAVE_FABRICD */ +#if defined(HAVE_PATHD) +DEFUNSH(VTYSH_PATHD, segment_routing, segment_routing_cmd, + "segment-routing", + "Configure segment routing\n") +{ + vty->node = SEGMENT_ROUTING_NODE; + return CMD_SUCCESS; +} + +DEFUNSH(VTYSH_PATHD, sr_traffic_eng, sr_traffic_eng_cmd, + "traffic-eng", + "Configure SR traffic engineering\n") +{ + vty->node = SR_TRAFFIC_ENG_NODE; + return CMD_SUCCESS; +} + +DEFUNSH(VTYSH_PATHD, srte_segment_list, srte_segment_list_cmd, + "segment-list WORD$name", + "Segment List\n" + "Segment List Name\n") +{ + vty->node = SR_SEGMENT_LIST_NODE; + return CMD_SUCCESS; +} + +DEFUNSH(VTYSH_PATHD, srte_policy, srte_policy_cmd, + "policy color (0-4294967295) endpoint ", + "Segment Routing Policy\n" + "SR Policy color\n" + "SR Policy color value\n" + "SR Policy endpoint\n" + "SR Policy endpoint IPv4 address\n" + "SR Policy endpoint IPv6 address\n") +{ + vty->node = SR_POLICY_NODE; + return CMD_SUCCESS; +} + +DEFUNSH(VTYSH_PATHD, srte_policy_candidate_dyn_path, + srte_policy_candidate_dyn_path_cmd, + "candidate-path preference (0-4294967295) name WORD dynamic", + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Dynamic Path\n") +{ + vty->node = SR_CANDIDATE_DYN_NODE; + return CMD_SUCCESS; +} +#endif /* HAVE_PATHD */ + DEFUNSH(VTYSH_RMAP, vtysh_route_map, vtysh_route_map_cmd, "route-map WORD (1-65535)", "Create route-map or enter route-map command mode\n" @@ -2347,6 +2466,18 @@ DEFUNSH(VTYSH_KEYS, vtysh_quit_keys, vtysh_quit_keys_cmd, "quit", return vtysh_exit_keys(self, vty, argc, argv); } +DEFUNSH(VTYSH_PATHD, vtysh_exit_pathd, vtysh_exit_pathd_cmd, "exit", + "Exit current mode and down to previous mode\n") +{ + return vtysh_exit(vty); +} + +DEFUNSH(VTYSH_PATHD, vtysh_quit_pathd, vtysh_quit_pathd_cmd, "quit", + "Exit current mode and down to previous mode\n") +{ + return vtysh_exit_pathd(self, vty, argc, argv); +} + DEFUNSH(VTYSH_ALL, vtysh_exit_line_vty, vtysh_exit_line_vty_cmd, "exit", "Exit current mode and down to previous mode\n") { @@ -4144,6 +4275,37 @@ void vtysh_init_vty(void) install_element(BFD_PROFILE_NODE, &vtysh_end_all_cmd); #endif /* HAVE_BFDD */ +#if defined(HAVE_PATHD) + install_node(&segment_routing_node); + install_node(&sr_traffic_eng_node); + install_node(&srte_segment_list_node); + install_node(&srte_policy_node); + install_node(&srte_candidate_dyn_node); + + install_element(SEGMENT_ROUTING_NODE, &vtysh_exit_pathd_cmd); + install_element(SEGMENT_ROUTING_NODE, &vtysh_quit_pathd_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &vtysh_exit_pathd_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &vtysh_quit_pathd_cmd); + install_element(SR_SEGMENT_LIST_NODE, &vtysh_exit_pathd_cmd); + install_element(SR_SEGMENT_LIST_NODE, &vtysh_quit_pathd_cmd); + install_element(SR_POLICY_NODE, &vtysh_exit_pathd_cmd); + install_element(SR_POLICY_NODE, &vtysh_quit_pathd_cmd); + install_element(SR_CANDIDATE_DYN_NODE, &vtysh_exit_pathd_cmd); + install_element(SR_CANDIDATE_DYN_NODE, &vtysh_quit_pathd_cmd); + + install_element(SEGMENT_ROUTING_NODE, &vtysh_end_all_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &vtysh_end_all_cmd); + install_element(SR_SEGMENT_LIST_NODE, &vtysh_end_all_cmd); + install_element(SR_POLICY_NODE, &vtysh_end_all_cmd); + install_element(SR_CANDIDATE_DYN_NODE, &vtysh_end_all_cmd); + + install_element(CONFIG_NODE, &segment_routing_cmd); + install_element(SEGMENT_ROUTING_NODE, &sr_traffic_eng_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_segment_list_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_policy_cmd); + install_element(SR_POLICY_NODE, &srte_policy_candidate_dyn_path_cmd); +#endif /* HAVE_PATHD */ + /* keychain */ install_node(&keychain_node); install_element(CONFIG_NODE, &key_chain_cmd); diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h index d2675a81b9..9683518b67 100644 --- a/vtysh/vtysh.h +++ b/vtysh/vtysh.h @@ -43,6 +43,7 @@ DECLARE_MGROUP(MVTYSH) #define VTYSH_BFDD 0x10000 #define VTYSH_FABRICD 0x20000 #define VTYSH_VRRPD 0x40000 +#define VTYSH_PATHD 0x80000 #define VTYSH_WAS_ACTIVE (-2) @@ -51,7 +52,7 @@ DECLARE_MGROUP(MVTYSH) /* watchfrr is not in ALL since library CLI functions should not be * run on it (logging & co. should stay in a fixed/frozen config, and * things like prefix lists are not even initialised) */ -#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_SHARPD|VTYSH_PBRD|VTYSH_STATICD|VTYSH_BFDD|VTYSH_FABRICD|VTYSH_VRRPD +#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_SHARPD|VTYSH_PBRD|VTYSH_STATICD|VTYSH_BFDD|VTYSH_FABRICD|VTYSH_VRRPD|VTYSH_PATHD #define VTYSH_ACL VTYSH_BFDD|VTYSH_BABELD|VTYSH_BGPD|VTYSH_EIGRPD|VTYSH_ISISD|VTYSH_FABRICD|VTYSH_LDPD|VTYSH_NHRPD|VTYSH_OSPF6D|VTYSH_OSPFD|VTYSH_PBRD|VTYSH_PIMD|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_VRRPD|VTYSH_ZEBRA #define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_EIGRPD|VTYSH_FABRICD #define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_PBRD|VTYSH_FABRICD|VTYSH_VRRPD diff --git a/yang/frr-pathd.yang b/yang/frr-pathd.yang new file mode 100644 index 0000000000..03f0d3b024 --- /dev/null +++ b/yang/frr-pathd.yang @@ -0,0 +1,480 @@ +module frr-pathd { + yang-version 1.1; + namespace "http://frrouting.org/yang/pathd"; + prefix frr-pathd; + + import ietf-inet-types { + prefix inet; + } + import ietf-yang-types { + prefix yang; + } + import ietf-routing-types { + prefix rt-types; + } + import frr-interface { + prefix frr-interface; + } + + organization + "Free Range Routing"; + contact + "FRR Users List: + FRR Development List: "; + description + "This module defines a model for managing FRR pathd daemon."; + + revision 2018-11-06 { + description + "Initial revision."; + } + + typedef protocol-origin-type { + description + "Indication for the protocol origin of an object."; + type enumeration { + enum pcep { + value 1; + description "The object was created through PCEP"; + } + enum bgp { + value 2; + description "The object was created through GBP"; + } + enum local { + value 3; + description "The object was created through CLI, Yang model via Netconf, gRPC, etc"; + } + } + } + + typedef originator-type { + type string { + length "1..64"; + } + description + "Identifier of the originator of an object, could be 'config', '1.1.1.1:4189' or '2001:db8:85a3::8a2e:370:7334:4189'"; + } + + container pathd { + container srte { + list segment-list { + key "name"; + description "Segment-list properties"; + leaf name { + type string { + length "1..64"; + } + description "Segment-list name"; + } + leaf protocol-origin { + type protocol-origin-type; + mandatory true; + description + "Indication for the protocol origin of the segment list."; + } + leaf originator { + type originator-type; + mandatory true; + description "Originator of the segment list"; + } + list segment { + key "index"; + description "Configure Segment/hop at the index"; + leaf index { + type uint32; + description "Segment index"; + } + leaf sid-value { + type rt-types:mpls-label; + mandatory true; + description "MPLS label value"; + } + container nai { + presence "The segement has a Node or Adjacency Identifier"; + leaf type { + description "NAI type"; + mandatory true; + type enumeration { + enum ipv4_node { + value 1; + description "IPv4 node identifier"; + } + enum ipv6_node { + value 2; + description "IPv6 node identifier"; + } + enum ipv4_adjacency { + value 3; + description "IPv4 adjacency"; + } + enum ipv6_adjacency { + value 4; + description "IPv6 adjacency"; + } + enum ipv4_unnumbered_adjacency { + value 5; + description "IPv4 unnumbered adjacency"; + } + } + } + leaf local-address { + type inet:ip-address; + mandatory true; + } + leaf local-interface { + type uint32; + mandatory true; + when "../type = 'ipv4_unnumbered_adjacency'"; + } + leaf remote-address { + type inet:ip-address; + mandatory true; + when "../type = 'ipv4_adjacency' or ../type = 'ipv6_adjacency' or ../type = 'ipv4_unnumbered_adjacency'"; + } + leaf remote-interface { + type uint32; + mandatory true; + when "../type = 'ipv4_unnumbered_adjacency'"; + } + } + } + } + list policy { + key "color endpoint"; + unique "name"; + leaf color { + type uint32; + description + "Color of the SR Policy."; + } + leaf endpoint { + type inet:ip-address; + description + "Indication for the endpoint of the SR Policy."; + } + leaf name { + type string { + length "1..64"; + } + description + "Name of the SR Policy."; + } + leaf binding-sid { + type rt-types:mpls-label; + description + "BSID of the SR Policy."; + } + leaf is-operational { + type boolean; + config false; + description + "True if a valid candidate path of this policy is operational in zebra, False otherwise"; + } + list candidate-path { + unique "name"; + description + "List of Candidate Paths of the SR Policy."; + key "preference"; + leaf preference { + type uint32; + description + "Administrative preference."; + } + leaf name { + type string { + length "1..64"; + } + mandatory true; + description + "Symbolic Name of the Candidate Path."; + } + leaf is-best-candidate-path { + type boolean; + config false; + description + "True if the candidate path is the best candidate path, False otherwise"; + } + leaf protocol-origin { + type protocol-origin-type; + mandatory true; + description + "Indication for the protocol origin of the Candidate Path."; + } + leaf originator { + type originator-type; + mandatory true; + description "Originator of the candidate path"; + } + leaf discriminator { + type uint32; + config false; + description "Candidate path distinguisher"; + } + leaf type { + description + "Type of the Candidate Path."; + mandatory true; + type enumeration { + enum explicit { + value 1; + } + enum dynamic { + value 2; + } + } + } + leaf segment-list-name { + type leafref { + path ../../../segment-list/name; + } + description + "The name of the Segment List to use as LSP."; + } + container constraints { + when "../type = 'dynamic'"; + description + "Generic dynamic path constraints"; + container bandwidth { + presence "If the candidate has a bandwidth constraint"; + description + "The bandwidth needed by the candidate path."; + leaf required { + type boolean; + default "true"; + description + "If the bandwidth limitation is a requirement or only a suggestion"; + } + leaf value { + mandatory true; + type decimal64 { + fraction-digits 6; + } + } + } + container affinity { + description + "Affinity let you configure how the links should be used when calculating a path."; + leaf exclude-any { + type uint32; + description + "A 32-bit vector representing a set of attribute filters which renders a link unacceptable."; + } + leaf include-any { + type uint32; + description + "A 32-bit vector representing a set of attribute filters which renders a link acceptable. A null set (all bits set to zero) automatically passes."; + } + leaf include-all { + type uint32; + description + "A 32-bit vector representing a set of attribute filters which must be present for a link to be acceptable. A null set (all bits set to zero) automatically passes."; + } + } + list metrics { + key "type"; + leaf type { + description + "Type of the metric."; + type enumeration { + enum igp { + value 1; + description "IGP metric"; + } + enum te { + value 2; + description "TE metric"; + } + enum hc { + value 3; + description "Hop Counts"; + } + enum abc { + value 4; + description "Aggregate bandwidth consumption"; + } + enum lmll { + value 5; + description "Load of the most loaded link"; + } + enum cigp { + value 6; + description "Cumulative IGP cost"; + } + enum cte { + value 7; + description "Cumulative TE cost"; + } + enum pigp { + value 8; + description "P2MP IGP metric"; + } + enum pte { + value 9; + description "P2MP TE metric"; + } + enum phc { + value 10; + description "P2MP hop count metric"; + } + enum msd { + value 11; + description "Segment-ID (SID) Depth"; + } + enum pd { + value 12; + description "Path Delay metric"; + } + enum pdv { + value 13; + description "Path Delay Variation metric"; + } + enum pl { + value 14; + description "Path Loss metric"; + } + enum ppd { + value 15; + description "P2MP Path Delay metric"; + } + enum ppdv { + value 16; + description "P2MP Path Delay variation metric"; + } + enum ppl { + value 17; + description "P2MP Path Loss metric"; + } + enum nap { + value 18; + description "Number of adaptations on a path"; + } + enum nlp { + value 19; + description "Number of layers on a path"; + } + enum dc { + value 20; + description "Domain Count metric"; + } + enum bnc { + value 21; + description "Border Node Count metric"; + } + } + } + leaf required { + type boolean; + default "true"; + description + "If the metric is a requirement, or if it is only a suggestion"; + } + leaf is-bound { + type boolean; + description + "Defines if the value is a bound (a maximum) for the path metric that must not be exceeded."; + } + leaf is-computed { + type boolean; + description + "Defines if the value has been generated by the originator of the path."; + } + leaf value { + mandatory true; + type decimal64 { + fraction-digits 6; + } + } + } + container objective-function { + presence "If the candidate has an objective function constraint"; + description + "Define objective function constraint as a list of prefered functions"; + leaf required { + type boolean; + default "true"; + description + "If an objective function is a requirement, or if it is only a suggestion"; + } + leaf type { + description + "Type of objective function."; + mandatory true; + type enumeration { + enum mcp { + value 1; + description "Minimum Cost Path"; + } + enum mlp { + value 2; + description "Minimum Load Path"; + } + enum mbp { + value 3; + description "Maximum residual Bandwidth Path"; + } + enum mbc { + value 4; + description "Minimize aggregate Bandwidth Consumption"; + } + enum mll { + value 5; + description "Minimize the Load of the most loaded Link"; + } + enum mcc { + value 6; + description "Minimize the Cumulative Cost of a set of paths"; + } + enum spt { + value 7; + description "Shortest Path Tree"; + } + enum mct { + value 8; + description "Minimum Cost Tree"; + } + enum mplp { + value 9; + description "Minimum Packet Loss Path"; + } + enum mup { + value 10; + description "Maximum Under-Utilized Path"; + } + enum mrup { + value 11; + description "Maximum Reserved Under-Utilized Path"; + } + enum mtd { + value 12; + description "Minimize the number of Transit Domains"; + } + enum mbn { + value 13; + description "Minimize the number of Border Nodes"; + } + enum mctd { + value 14; + description "Minimize the number of Common Transit Domains"; + } + enum msl { + value 15; + description "Minimize the number of Shared Links"; + } + enum mss { + value 16; + description "Minimize the number of Shared SRLGs"; + } + enum msn { + value 17; + description "Minimize the number of Shared Nodes"; + } + } + } + } + } + } + } + } + } +} diff --git a/yang/subdir.am b/yang/subdir.am index 5be93dc4e9..47fc508901 100644 --- a/yang/subdir.am +++ b/yang/subdir.am @@ -82,3 +82,7 @@ dist_yangmodels_DATA += yang/frr-bgp-bmp.yang dist_yangmodels_DATA += yang/frr-bgp-types.yang dist_yangmodels_DATA += yang/frr-bgp.yang endif + +if PATHD +dist_yangmodels_DATA += yang/frr-pathd.yang +endif