]> git.puffer.fish Git - mirror/frr.git/commitdiff
doc: add northbound api arch docs 14448/head
authorQuentin Young <qlyoung@qlyoung.net>
Wed, 20 Sep 2023 01:36:04 +0000 (21:36 -0400)
committerQuentin Young <qlyoung@qlyoung.net>
Wed, 20 Sep 2023 01:36:04 +0000 (21:36 -0400)
Signed-off-by: Quentin Young <qlyoung@qlyoung.net>
16 files changed:
doc/developer/index.rst
doc/developer/northbound/_sidebar.rst [new file with mode: 0644]
doc/developer/northbound/advanced-topics.rst [new file with mode: 0644]
doc/developer/northbound/architecture.rst [new file with mode: 0644]
doc/developer/northbound/demos.rst [new file with mode: 0644]
doc/developer/northbound/links.rst [new file with mode: 0644]
doc/developer/northbound/northbound.rst [new file with mode: 0644]
doc/developer/northbound/operational-data-rpcs-and-notifications.rst [new file with mode: 0644]
doc/developer/northbound/plugins-sysrepo.rst [new file with mode: 0644]
doc/developer/northbound/ppr-basic-test-topology.rst [new file with mode: 0644]
doc/developer/northbound/ppr-mpls-basic-test-topology.rst [new file with mode: 0644]
doc/developer/northbound/retrofitting-configuration-commands.rst [new file with mode: 0644]
doc/developer/northbound/transactional-cli.rst [new file with mode: 0644]
doc/developer/northbound/yang-module-translator.rst [new file with mode: 0644]
doc/developer/northbound/yang-tools.rst [new file with mode: 0644]
doc/developer/subdir.am

index 5da7bc416849ed4ba2a61bc9c3d70dc1106a86af..c2123f1ad20978b50bf87de1f8902cff69b966e7 100644 (file)
@@ -22,3 +22,4 @@ FRRouting Developer's Guide
    path
    pceplib
    link-state
+   northbound/northbound
diff --git a/doc/developer/northbound/_sidebar.rst b/doc/developer/northbound/_sidebar.rst
new file mode 100644 (file)
index 0000000..f2bca0b
--- /dev/null
@@ -0,0 +1,15 @@
+Northbound:
+~~~~~~~~~~~
+
+-  [[Architecture]]
+-  [[Transactional CLI]]
+-  [[Retrofitting Configuration Commands]]
+-  [[Operational data, RPCs and Notifications]]
+-  [[Plugins - ConfD]]
+-  [[Plugins: Sysrepo]]
+-  [[Plugins - Writing Your Own]]
+-  [[YANG module translator]]
+-  [[Advanced topics]]
+-  [[YANG tools]]
+-  [[Demos]]
+-  [[Links]]
diff --git a/doc/developer/northbound/advanced-topics.rst b/doc/developer/northbound/advanced-topics.rst
new file mode 100644 (file)
index 0000000..bee29a9
--- /dev/null
@@ -0,0 +1,294 @@
+Auto-generated CLI commands
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In order to have less code to maintain, it should be possible to write a
+tool that auto-generates CLI commands based on the FRR YANG models. As a
+matter of fact, there are already a number of NETCONF-based CLIs that do
+exactly that (e.g. `Clixon <https://github.com/clicon/clixon>`__,
+ConfD’s CLI).
+
+The problem however is that there isn’t an exact one-to-one mapping
+between the existing CLI commands and the corresponding YANG nodes from
+the native models. As an example, ripd’s
+``timers basic (5-2147483647) (5-2147483647) (5-2147483647)`` command
+changes three YANG leaves at the same time. In order to auto-generate
+CLI commands and retain their original form, it’s necessary to add
+annotations in the YANG modules to specify how the commands should look
+like. Without YANG annotations, the CLI auto-generator will generate a
+command for each YANG leaf, (leaf-)list and presence-container. The
+ripd’s ``timers basic`` command, for instance, would become three
+different commands, which would be undesirable.
+
+   This Tail-f’s®
+   `document <http://info.tail-f.com/hubfs/Whitepapers/Tail-f_ConfD-CLI__Cfg_Mode_App_Note_Rev%20C.pdf>`__
+   shows how to customize ConfD auto-generated CLI commands using YANG
+   annotations.
+
+The good news is that *libyang* allows users to create plugins to
+implement their own YANG extensions, which can be used to implement CLI
+annotations. If done properly, a CLI generator can save FRR developers
+from writing and maintaining hundreds if not thousands of DEFPYs!
+
+CLI on a separate program
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The flexible design of the northbound architecture opens the door to
+move the CLI to a separate program in the long-term future. Some
+advantages of doing so would be: \* Treat the CLI as just another
+northbound client, instead of having CLI commands embedded in the
+binaries of all FRR daemons. \* Improved robustness: bugs in CLI
+commands (e.g. null-pointer dereferences) or in the CLI code itself
+wouldn’t affect the FRR daemons. \* Foster innovation by allowing other
+CLI programs to be implemented, possibly using higher level programming
+languages.
+
+The problem, however, is that the northbound retrofitting process will
+convert only the CLI configuration commands and EXEC commands in a first
+moment. Retrofitting the “show” commands is a completely different story
+and shouldn’t happen anytime soon. This should hinder progress towards
+moving the CLI to a separate program.
+
+Proposed feature: confirmed commits
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Confirmed commits allow the user to request an automatic rollback to the
+previous configuration if the commit operation is not confirmed within a
+number of minutes. This is particularly useful when the user is
+accessing the CLI through the network (e.g. using SSH) and any
+configuration change might cause an unexpected loss of connectivity
+between the user and the router (e.g. misconfiguration of a routing
+protocol). By using a confirmed commit, the user can rest assured the
+connectivity will be restored after the given timeout expires, avoiding
+the need to access the router physically to fix the problem.
+
+Example of how this feature could be provided in the CLI:
+``commit confirmed [minutes <1-60>]``. The ability to do confirmed
+commits should also be exposed in the northbound API so that the
+northbound plugins can also take advantage of it (in the case of the
+Sysrepo and ConfD plugins, confirmed commits are implemented externally
+in the *netopeer2-server* and *confd* daemons, respectively).
+
+Proposed feature: enable/disable configuration commands/sections
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Since the ``lyd_node`` data structure from *libyang* can hold private
+data, it should be possible to mark configuration commands or sections
+as active or inactive. This would allow CLI users to leverage this
+feature to disable parts of the running configuration without actually
+removing the associated commands, and then re-enable the disabled
+configuration commands or sections later when necessary. Example:
+
+::
+
+   ripd(config)# show configuration running
+   Configuration:
+   [snip]
+   !
+   router rip
+    default-metric 2
+    distance 80
+    network eth0
+    network eth1
+   !
+   end
+   ripd(config)# disable router rip
+   ripd(config)# commit
+   % Configuration committed successfully (Transaction ID #7).
+
+   ripd(config)# show configuration running
+   Configuration:
+   [snip]
+   !
+   !router rip
+    !default-metric 2
+    !distance 80
+    !network eth0
+    !network eth1
+   !
+   end
+   ripd(config)# enable router rip
+   ripd(config)# commit
+   % Configuration committed successfully (Transaction ID #8).
+
+   ripd(config)# show configuration running
+   [snip]
+   frr defaults traditional
+   !
+   router rip
+    default-metric 2
+    distance 80
+    network eth0
+    network eth1
+   !
+   end
+
+This capability could be useful in a number of occasions, like disabling
+configuration commands that are no longer necessary (e.g. ACLs) but that
+might be necessary at a later point in the future. Other example is
+allowing users to disable a configuration section for testing purposes,
+and then re-enable it easily without needing to copy and paste any
+command.
+
+Configuration reloads
+~~~~~~~~~~~~~~~~~~~~~
+
+Given the limitations of the previous northbound architecture, the FRR
+daemons didn’t have the ability to reload their configuration files by
+themselves. The SIGHUP handler of most daemons would only re-read the
+configuration file and merge it into the running configuration. In most
+cases, however, what is desired is to replace the running configuration
+by the updated configuration file. The *frr-reload.py* script was
+written to work around this problem and it does it well to a certain
+extent. The problem with the *frr-reload.py* script is that it’s full of
+special cases here and there, which makes it fragile and unreliable.
+Maintaining the script is also an additional burden for FRR developers,
+few of whom are familiar with its code or know when it needs to be
+updated to account for a new feature.
+
+In the new northbound architecture, reloading the configuration file can
+be easily implemented using a configuration transaction. Once the FRR
+northbound retrofitting process is complete, all daemons should have the
+ability to reload their configuration files upon receiving the SIGHUP
+signal, or when the ``configuration load [...] replace`` command is
+used. Once that point is reached, the *frr-reload.py* script will no
+longer be necessary and should be removed from the FRR repository.
+
+Configuration changes coming from the kernel
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This
+`post <http://discuss.tail-f.com/t/who-should-not-set-configuration-once-a-system-is-up-and-running/111>`__
+from the Tail-f’s® forum describes the problem of letting systems
+configure themselves behind the users back. Here are some selected
+snippets from it: > Traditionally, northbound interface users are the
+ones in charge of providing configuration data for systems. > > In some
+systems, we see a deviation from this traditional practice; allowing
+systems to configure “themselves” behind the scenes (or behind the users
+back). > > While there might be a business case for such a practice,
+this kind of configuration remains “dangerous” from northbound users
+perspective and makes systems hard to predict and even harder to debug.
+(…) > > With the advent of transactional Network configuration, this
+practice can not work anymore. The fact that systems are given the right
+to change configuration is a key here in breaking transactional
+configuration in a Network.
+
+FRR is immune to some of the problems described in the aforementioned
+post. Management clients can configure interfaces that don’t yet exist,
+and once an interface is deleted from the kernel, its configuration is
+retained in FRR.
+
+There are however some cases where information learned from the kernel
+(e.g. using netlink) can affect the running configuration of all FRR
+daemons. Examples: interface rename events, VRF rename events, interface
+being moved to a different VRF, etc. In these cases, since these events
+can’t be ignored, the best we can do is to send YANG notifications to
+the management clients to inform about the configuration changes. The
+management clients should then be prepared to handle such notifications
+and react accordingly.
+
+Interfaces and VRFs
+~~~~~~~~~~~~~~~~~~~
+
+As of now zebra doesn’t have the ability to create VRFs or virtual
+interfaces in the kernel. The ``vrf`` and ``interface`` commands only
+create pre-provisioned VRFs and interfaces that are only activated when
+the corresponding information is learned from the kernel. When
+configuring FRR using an external management client, like a NETCONF
+client, it might be desirable to actually create functional VRFs and
+virtual interfaces (e.g. VLAN subinterfaces, bridges, etc) that are
+installed in the kernel using OS-specific APIs (e.g. netlink, routing
+socket, etc). Work needs to be done in this area to make this possible.
+
+Shared configuration objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One of the existing problems in FRR is that it’s hard to ensure that all
+daemons are in sync with respect to the shared configuration objects
+(e.g. interfaces, VRFs, route-maps, ACLs, etc). When a route-map is
+configured using *vtysh*, the same command is sent to all relevant
+daemons (the daemons that implement route-maps), which ensures
+synchronization among them. The problem is when a daemon starts after
+the route-maps are created. In this case this daemon wouldn’t be aware
+of the previously configured route-maps (unlike the other daemons),
+which can lead to a lot of confusion and unexpected problems.
+
+With the new northbound architecture, configuration objects can be
+manipulated using higher level abstractions, which opens more
+possibilities to solve this decades-long problem. As an example, one
+solution would be to make the FRR daemons fetch the shared configuration
+objects from zebra using the ZAPI interface during initialization. The
+shared configuration objects could be requested using a list of XPaths
+expressions in the ``ZEBRA_HELLO`` message, which zebra would respond by
+sending the shared configuration objects encoded in the JSON format.
+This solution however doesn’t address the case where zebra starts or
+restarts after the other FRR daemons. Other solution would be to store
+the shared configuration objects in the northbound SQL database and make
+all daemons fetch these objects from there. So far no work has been made
+on this area as more investigation needs to be done.
+
+vtysh support
+~~~~~~~~~~~~~
+
+As explained in the [[Transactional CLI]] page, all commands introduced
+by the transactional CLI are not yet available in *vtysh*. This needs to
+be addressed in the short term future. Some challenges for doing that
+work include: \* How to display configurations (running, candidates and
+rollbacks) in a more clever way? The implementation of the
+``show running-config`` command in *vtysh* is not something that should
+be followed as an example. A better idea would be to fetch the desired
+configuration from all daemons (encoded in JSON for example), merge them
+all into a single ``lyd_node`` variable and then display the combined
+configurations from this variable (the configuration merges would
+transparently take care of combining the shared configuration objects).
+In order to be able to manipulate the JSON configurations, *vtysh* will
+need to load the YANG modules from all daemons at startup (this might
+have a minimal impact on startup time). The only issue with this
+approach is that the ``cli_show()`` callbacks from all daemons are
+embedded in their binaries and thus not accessible externally. It might
+be necessary to compile these callbacks on a separate shared library so
+that they are accessible to *vtysh* too. Other than that, displaying the
+combined configurations in the JSON/XML formats should be
+straightforward. \* With the current design, transaction IDs are
+per-daemon and not global across all FRR daemons. This means that the
+same transaction ID can represent different transactions on different
+daemons. Given this observation, how to implement the
+``rollback configuration`` command in *vtysh*? The easy solution would
+be to add a ``daemon WORD`` argument to specify the context of the
+rollback, but per-daemon rollbacks would certainly be confusing and
+convoluted to end users. A better idea would be to attack the root of
+the problem: change configuration transactions to be global instead of
+being per-daemon. This involves a bigger change in the northbound
+architecture, and would have implications on how transactions are stored
+in the SQL database (daemon-specific and shared configuration objects
+would need to have their own tables or columns). \* Loading
+configuration files in the JSON or XML formats will be tricky, as
+*vtysh* will need to know which sections of the configuration should be
+sent to which daemons. *vtysh* will either need to fetch the YANG
+modules implemented by all daemons at runtime or obtain this information
+at compile-time somehow.
+
+Detecting type mismatches at compile-time
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As described in the [[Retrofitting Configuration Commands]] page, the
+northbound configuration callbacks detect type mismatches at runtime
+when fetching data from the the ``dnode`` parameter (which represents
+the configuration node being created, modified, deleted or moved). When
+a type mismatch is detected, the program aborts and displays a backtrace
+showing where the problem happened. It would be desirable to detect such
+type mismatches at compile-time, the earlier the problems are detected
+the sooner they are fixed.
+
+One possible solution to this problem would be to auto-generate C
+structures from the YANG models and provide a function that converts a
+libyang’s ``lyd_node`` variable to a C structure containing the same
+information. The northbound callbacks could then fetch configuration
+data from this C structure, which would naturally lead to type
+mismatches being detected at compile time. One of the challenges of
+doing this would be the handling of YANG lists and leaf-lists. It would
+be necessary to use dynamic data structures like hashes or rb-trees to
+hold all elements of the lists and leaf-lists, and the process of
+converting a ``lyd_node`` to an auto-generated C-structure could be
+expensive. At this point it’s unclear if it’s worth adding more
+complexity in the northbound architecture to solve this specific
+problem.
diff --git a/doc/developer/northbound/architecture.rst b/doc/developer/northbound/architecture.rst
new file mode 100644 (file)
index 0000000..f551ce9
--- /dev/null
@@ -0,0 +1,283 @@
+Introduction
+------------
+
+The goal of the new northbound API is to provide a better interface to
+configure and monitor FRR programatically. The current design based on
+CLI commands is no longer adequate in a world where computer networks
+are becoming increasingly bigger, more diverse and more complex. Network
+scripting using *expect* and screen scraping techniques is too primitive
+and unreliable to be used in large-scale networks. What is proposed is
+to modernize FRR to turn it into an API-first routing stack, and
+reposition the CLI on top of this API. The most important change,
+however, is not the API that will be provided to external users. In
+fact, multiple APIs will be supported and users will have the ability to
+write custom management APIs if necessary. The biggest change is the
+introduction of a model-driven management architecture based on the
+`YANG <https://tools.ietf.org/html/rfc7950>`__ modeling language.
+Instead of writing code tied to any particular user interface
+(e.g. DEFUNs), YANG allows us to write API-agnostic code (in the form of
+callbacks) that can be used by any management interface. As an example,
+it shouldn’t matter if a set of configuration changes is coming from a
+`NETCONF <https://tools.ietf.org/html/rfc6241>`__ session or from a CLI
+terminal, the same callbacks should be called to process the
+configuration changes regardless of where they came from. This
+model-driven design ensures feature parity across all management
+interfaces supported by FRR.
+
+Quoting RFC 7950: > YANG is a language originally designed to model data
+for the NETCONF protocol. A YANG module defines hierarchies of data that
+can be used for NETCONF-based operations, including configuration, state
+data, RPCs, and notifications. This allows a complete description of all
+data sent between a NETCONF client and server. Although out of scope for
+this specification, YANG can also be used with protocols other than
+NETCONF.
+
+While the YANG and NETCONF specifications are tightly coupled with one
+another, both are independent to a certain extent and are evolving
+separately. Examples of other management protocols that use YANG include
+`RESTCONF <https://tools.ietf.org/html/rfc8040>`__,
+`gNMI <https://github.com/openconfig/reference/tree/master/rpc/gnmi>`__
+and
+`CoAP <https://www.ietf.org/archive/id/draft-vanderstok-core-comi-11.txt>`__.
+
+In addition to being management-protocol independent, some other
+advantages of using YANG in FRR are listed below: \* Have a formal
+contract between FRR and application developers (management clients). A
+management client that has access to the FRR YANG models knows about all
+existing configuration options available for use. This information can
+be used to auto-generate user-friendly interfaces like Web-UIs, custom
+CLIs and even code bindings for several different programming languages.
+Using `PyangBind <https://github.com/robshakir/pyangbind>`__, for
+example, it’s possible to generate Python class hierarchies from YANG
+models and use these classes to instantiate objects that mirror the
+structure of the YANG modules and can be serialized/deserialized using
+different encoding formats. \* Support different encoding formats for
+instance data. Currently only JSON and XML are supported, but
+`GPB <https://developers.google.com/protocol-buffers/>`__ and
+`CBOR <http://cbor.io/>`__ are other viable options in the long term.
+Additional encoding formats can be implemented in the *libyang* library
+for optimal performance, or externally by translating data to/from one
+of the supported formats (with a performance penalty). \* Have a formal
+mechanism to introduce backward-incompatible changes based on `semantic
+versioning <http://www.openconfig.net/docs/semver/>`__ (not part of the
+YANG standard, which allows backward-compatible module updates only). \*
+Provide seamless support to the industry-standard NETCONF/RESTCONF
+protocols as alternative management APIs. If FRR configuration/state
+data is modeled using YANG, supporting YANG-based protocols like NETCONF
+and RESTCONF is much easier.
+
+As important as shifting to a model-driven management paradigm, the new
+northbound architecture also introduces the concept of configuration
+transactions. Configuration transactions allow management clients to
+commit multiple configuration changes at the same time and rest assured
+that either all changes will be applied or none will (all-or-nothing).
+Configuration transactions are implemented as pseudo-atomic operations
+and facilitate automation by removing the burden of error recovery from
+the management side. Another property of configuration transactions is
+that the configuration changes are always processed in a pre-defined
+order to ensure consistency. Configuration transactions that encompass
+multiple network devices are called network-wide transactions and are
+also supported by the new northbound architecture. When FRR is built
+using the ``--enable-config-rollbacks`` option, all committed
+transactions are recorded in the FRR rollback log, which can reside
+either in memory (volatile) or on persistent storage.
+
+   Network-wide Transactions is the most important leap in network
+   management technology since SNMP. The error recovery and sequencing
+   tasks are removed from the manager side. This is usually more than
+   half the cost in a mature system; more than the entire cost of the
+   managed devices.
+   `[source] <https://www.nanog.org/sites/default/files/tuesday_tutorial_moberg_netconf_35.pdf>`__.
+
+Figures 1 and 2 below illustrate the old and new northbound architecture
+of FRR, respectively. As it can be seen, in the old architecture the CLI
+was the only interface used to configure and monitor FRR (the SNMP
+plugin was’t taken into account given the small number of implemented
+MIBs). This means that the only way to automate FRR was by writing
+scripts that send CLI commands and parse the text output (which usually
+doesn’t have any structure) using screen scraping and regular
+expressions.
+
++-----------------------------------------+
+| |space-1.jpg|                           |
++=========================================+
+| *Figure 1: old northbound architecture* |
++-----------------------------------------+
+
+The new northbound architectures, on the other hand, features a
+multitude of different management APIs, all of them connected to the
+northbound layer of the FRR daemons. By default, only the CLI interface
+is compiled built-in in the FRR daemons. The other management interfaces
+are provided as optional plugins and need to be loaded during the daemon
+initialization (e.g. *zebra -M confd*). This design makes it possible to
+integrate FRR with different NETCONF solutions without introducing
+vendor lock-in. The [[Plugins - Writing Your Own]] page explains how to
+write custom northbound plugins that can be tailored to all needs
+(e.g. support custom transport protocols, different data encoding
+formats, fine-grained access control, etc).
+
++-----------------------------------------+
+| |space-1.jpg|                           |
++=========================================+
+| *Figure 2: new northbound architecture* |
++-----------------------------------------+
+
+Figure 3 shows the internal view of the FRR northbound architecture. In
+this image we can see that northbound layer is an abstract entity
+positioned between the northbound callbacks and the northbound clients.
+The northbound layer is responsible to process the requests coming from
+the northbound clients and call the appropriate callbacks to satisfy
+these requests. The northbound plugins communicate with the northbound
+layer through a public API, which allow users to write third-party
+plugins that can be maintained separately. The northbound plugins, in
+turn, have their own APIs to communicate with external management
+clients.
+
++---------------------------------------------------------+
+| |space-1.jpg|                                           |
++=========================================================+
+| *Figure 3: new northbound architecture - internal view* |
++---------------------------------------------------------+
+
+Initially the CLI (and all of its commands) will be maintained inside
+the FRR daemons. In the long term, however, the goal is to move the CLI
+to a separate program just like any other management client. The
+[[Advanced Topics]] page describes the motivations and challenges of
+doing that. Last but not least, the *libyang* block inside the
+northbound layer is the engine that makes everything possible. The
+*libyang* library will be described in more detail in the following
+sections.
+
+YANG models
+-----------
+
+The main decision to be made when using YANG is which models to
+implement. There’s a general consensus that using standard models is
+preferable over using custom (native) models. The reasoning is that
+applications based on standard models can be reused for all network
+appliances that support those models, whereas the same doesn’t apply for
+applications written based on custom models.
+
+That said, there are multiple standards bodies publishing YANG models
+and unfortunately not all of them are converging (or at least not yet).
+In the context of FRR, which is a routing stack, the two sets of YANG
+models that would make sense to implement are the ones from IETF and
+from the OpenConfig working group. The question that arises is: which
+one of them should we commit to? Or should we try to support both
+somehow, at the cost of extra development efforts?
+
+Another problem, from an implementation point of view, is that it’s
+challenging to adapt the existing code base to match standard models. A
+more reasonable solution, at least in a first moment, would be to use
+YANG deviations and augmentations to do the opposite: adapt the standard
+models to the existing code. In practice however this is not as simple
+as it seems. There are cases where the differences are too substantial
+to be worked around without restructuring the code by changing its data
+structures and their relationships. As an example, the *ietf-rip* model
+places per-interface RIP configuration parameters inside the
+*control-plane-protocol* list (which is augmented by *ietf-rip*). This
+means that it’s impossible to configure RIP interface parameters without
+first configuring a RIP routing instance. The *ripd* daemon on the other
+hand allows the operator to configure RIP interface parameters even if
+``router rip`` is not configured. If we were to implement the *ietf-rip*
+module natively, we’d need to change ripd’s CLI commands (and the
+associated code) to reflect the new configuration hierarchy.
+
+Taking into account that FRR has a huge code base and that the
+northbound retrofitting process per-se will cause a lot of impact, it
+was decided to take a conservative approach and write custom YANG models
+for FRR modeled after the existing CLI commands. Having YANG models that
+closely mirror the CLI commands will allow the FRR developers to
+retrofit the code base much more easily, without introducing
+backward-incompatible changes in the CLI and reducing the likelihood of
+introducing bugs. The [[Retrofitting Configuration Commands]] page
+explains in detail how to convert configuration commands to the new
+northbound model.
+
+Even though having native YANG models is not the ideal solution, it will
+be already a big step forward for FRR to migrate to a model-driven
+management architecture, with support for configuration transactions and
+multiple management interfaces, including NETCONF and RESTCONF (through
+the northbound plugins).
+
+The new northbound also features an experimental YANG module translator
+that will allow users to translate to and from standard YANG models by
+using translation tables. The [[YANG module translator]] page describes
+this mechanism in more detail. At this point it’s unclear what can be
+achieved through module translation and if that can be considered as a
+definitive solution to support standard models or not.
+
+Northbound Architecture
+-----------------------
+
++-----------------------------------------------+
+| |space-1.jpg|                                 |
++===============================================+
+| *Figure 4: libyang’s lys_node data structure* |
++-----------------------------------------------+
+
++-----------------------------------------------+
+| |space-1.jpg|                                 |
++===============================================+
+| *Figure 5: libyang’s lyd_node data structure* |
++-----------------------------------------------+
+
++---------------------------------------------+
+| |space-1.jpg|                               |
++=============================================+
+| *Figure 6: libyang’s ly_ctx data structure* |
++---------------------------------------------+
+
++----------------------------------------+
+| |space-1.jpg|                          |
++========================================+
+| *Figure 7: configuration transactions* |
++----------------------------------------+
+
+Testing
+-------
+
+The new northbound adds the libyang library as a new mandatory
+dependency for FRR. To obtain and install this library, follow the steps
+below:
+
+::
+
+   $ git clone https://github.com/CESNET/libyang
+   $ cd libyang
+   $ git checkout devel
+   $ mkdir build ; cd build
+   $ cmake -DENABLE_LYD_PRIV=ON ..
+   $ make
+   $ sudo make install
+
+..
+
+   NOTE: first make sure to install the libyang
+   `requirements <https://github.com/CESNET/libyang#build-requirements>`__.
+
+FRR needs libyang from version 0.16.7 or newer, which is maintained in
+the ``devel`` branch. libyang 0.15.x is maintained in the ``master``
+branch and doesn’t contain one small feature used by FRR (the
+``LY_CTX_DISABLE_SEARCHDIR_CWD`` flag). FRR also makes use of the
+libyang’s ``ENABLE_LYD_PRIV`` feature, which is disabled by default and
+needs to be enabled at compile time.
+
+It’s advisable (but not required) to install sqlite3 and build FRR with
+``--enable-config-rollbacks`` in order to have access to the
+configuration rollback feature.
+
+To test the northbound, the suggested method is to use the
+[[Transactional CLI]] with the *ripd* daemon and play with the new
+commands. The ``debug northbound`` command can be used to see which
+northbound callbacks are called in response to the ``commit`` command.
+For reference, the [[Demos]] page shows a small demonstration of the
+transactional CLI in action and what it’s capable of.
+
+.. |space-1.jpg| image:: https://s22.postimg.cc/se52j8awh/arch-before.png
+.. |space-1.jpg| image:: https://s22.postimg.cc/fziaiwboh/arch-after.png
+.. |space-1.jpg| image:: https://s22.postimg.cc/qmc3ocmep/nb-layer.png
+.. |space-1.jpg| image:: https://s22.postimg.cc/z4ljsodht/lys_node.png
+.. |space-1.jpg| image:: https://s22.postimg.cc/6eynw1h7l/lyd_node.png
+.. |space-1.jpg| image:: https://s22.postimg.cc/5cohdhiyp/ly_ctx.png
+.. |space-1.jpg| image:: https://s22.postimg.cc/8waf3bgjl/transactions.png
diff --git a/doc/developer/northbound/demos.rst b/doc/developer/northbound/demos.rst
new file mode 100644 (file)
index 0000000..21ab43a
--- /dev/null
@@ -0,0 +1,25 @@
+Transactional CLI
+-----------------
+
+This short demo shows some of the capabilities of the new transactional
+CLI: |asciicast|
+
+ConfD + NETCONF + Cisco YDK
+---------------------------
+
+This is a very simple demo of *ripd* being configured by a python
+script. The script uses NETCONF to communicate with *ripd*, which has
+the ConfD plugin loaded. The most interesting part, however, is the fact
+that the python script is not using handcrafted XML payloads to
+configure *ripd*. Instead, the script is using python bindings generated
+using Cisco’s YANG Development Kit (YDK).
+
+-  Script used in the demo:
+   https://gist.github.com/rwestphal/defa9bd1ccf216ab082d4711ae402f95
+
+|asciicast|
+
+.. |asciicast| image:: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1.png
+   :target: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1
+.. |asciicast| image:: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv.png
+   :target: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv
diff --git a/doc/developer/northbound/links.rst b/doc/developer/northbound/links.rst
new file mode 100644 (file)
index 0000000..e80374c
--- /dev/null
@@ -0,0 +1,233 @@
+RFCs
+~~~~
+
+-  `RFC 7950 - The YANG 1.1 Data Modeling
+   Language <https://tools.ietf.org/html/rfc7950>`__
+-  `RFC 7951 - JSON Encoding of Data Modeled with
+   YANG <https://tools.ietf.org/html/rfc7951>`__
+-  `RFC 8342 - Network Management Datastore Architecture
+   (NMDA) <https://tools.ietf.org/html/rfc8342>`__
+-  `RFC 6087 - Guidelines for Authors and Reviewers of YANG Data Model
+   Documents <https://tools.ietf.org/html/rfc6087>`__
+-  `RFC 8340 - YANG Tree
+   Diagrams <https://tools.ietf.org/html/rfc8340>`__
+-  `RFC 6991 - Common YANG Data
+   Types <https://tools.ietf.org/html/rfc6991>`__
+-  `RFC 6241 - Network Configuration Protocol
+   (NETCONF) <https://tools.ietf.org/html/rfc6241>`__
+-  `RFC 8040 - RESTCONF
+   Protocol <https://tools.ietf.org/html/rfc8040>`__
+
+YANG models
+~~~~~~~~~~~
+
+-  Collection of several YANG models, including models from standards
+   organizations such as the IETF and vendor specific models:
+   https://github.com/YangModels/yang
+-  OpenConfig: https://github.com/openconfig/public
+
+Presentations
+~~~~~~~~~~~~~
+
+-  FRR Advanced Northbound API (May 2018)
+
+   -  Slides:
+      https://www.dropbox.com/s/zhybthruwocbqaw/netdef-frr-northbound.pdf?dl=1
+
+-  Ok, We Got Data Models, Now What?
+
+   -  Video: https://www.youtube.com/watch?v=2oqkiZ83vAA
+   -  Slides:
+      https://www.nanog.org/sites/default/files/20161017_Alvarez_Ok_We_Got_v1.pdf
+
+-  Data Model-Driven Management: Latest Industry and Tool Developments
+
+   -  Video: https://www.youtube.com/watch?v=n_oKGJ_jgYQ
+   -  Slides:
+      https://pc.nanog.org/static/published/meetings/NANOG72/1559/20180219_Claise_Data_Modeling-Driven_Management__v1.pdf
+
+-  Network Automation And Programmability: Reality Versus The Vendor
+   Hype When Considering Legacy And NFV Networks
+
+   -  Video: https://www.youtube.com/watch?v=N5wbYncUS9o
+   -  Slides:
+      https://www.nanog.org/sites/default/files/1_Moore_Network_Automation_And_Programmability.pdf
+
+-  Lightning Talk: The API is the new CLI?
+
+   -  Video: https://www.youtube.com/watch?v=ngi0erGNi58
+   -  Slides:
+      https://pc.nanog.org/static/published/meetings/NANOG72/1638/20180221_Grundemann_Lightning_Talk_The_v1.pdf
+
+-  Lightning Talk: OpenConfig - progress toward vendor-neutral network
+   management
+
+   -  Video: https://www.youtube.com/watch?v=10rSUbeMmT4
+   -  Slides:
+      https://pc.nanog.org/static/published/meetings/NANOG71/1535/20171004_Shaikh_Lightning_Talk_Openconfig_v1.pdf
+
+-  Getting started with OpenConfig
+
+   -  Video: https://www.youtube.com/watch?v=L7trUNK8NJI
+   -  Slides:
+      https://pc.nanog.org/static/published/meetings/NANOG71/1456/20171003_Alvarez_Getting_Started_With_v1.pdf
+
+-  Why NETCONF and YANG
+
+   -  Video: https://www.youtube.com/watch?v=mp4h8aSTba8
+
+-  NETCONF and YANG Concepts
+
+   -  Video: https://www.youtube.com/watch?v=UwYYvT7DBvg
+
+-  NETCONF Tutorial
+
+   -  Video: https://www.youtube.com/watch?v=N4vov1mI14U
+
+Whitepapers
+~~~~~~~~~~~
+
+-  Automating Network and Service Configuration Using NETCONF and YANG:
+   http://www.tail-f.com/wordpress/wp-content/uploads/2013/02/Tail-f-Presentation-Netconf-Yang.pdf
+-  Creating the Programmable Network: The Business Case for NETCONF/YANG
+   in Network Devices:
+   http://www.tail-f.com/wordpress/wp-content/uploads/2013/10/HR-Tail-f-NETCONF-WP-10-08-13.pdf
+-  NETCONF/YANG: What’s Holding Back Adoption & How to Accelerate It:
+   https://www.oneaccess-net.com/images/public/wp_heavy_reading.pdf
+-  Achieving Automation with YANG Modeling Technologies:
+   https://www.cisco.com/c/dam/en/us/products/collateral/cloud-systems-management/network-services-orchestrator/idc-achieving-automation-wp.pdf
+
+Blog posts and podcasts
+~~~~~~~~~~~~~~~~~~~~~~~
+
+-  OpenConfig and IETF YANG Models: Can they converge? -
+   http://rob.sh/post/215/
+-  OpenConfig: Standardized Models For Networking -
+   https://packetpushers.net/openconfig-standardized-models-networking/
+-  (Podcast) OpenConfig: From Basics to Implementations -
+   https://blog.ipspace.net/2017/02/openconfig-from-basics-to.html
+-  (Podcast) How Did NETCONF Start on Software Gone Wild -
+   https://blog.ipspace.net/2017/12/how-did-netconf-start-on-software-gone.html
+-  YANG Data Models in the Industry: Current State of Affairs (March
+   2018) -
+   https://www.claise.be/2018/03/yang-data-models-in-the-industry-current-state-of-affairs-march-2018/
+-  Why Data Model-driven Telemetry is the only useful Telemetry? -
+   https://www.claise.be/2018/02/why-data-model-driven-telemetry-is-the-only-useful-telemetry/
+-  NETCONF versus RESTCONF: Capabilitity Comparisons for Data
+   Model-driven Management -
+   https://www.claise.be/2017/10/netconf-versus-restconf-capabilitity-comparisons-for-data-model-driven-management-2/
+-  An Introduction to NETCONF/YANG -
+   https://www.fir3net.com/Networking/Protocols/an-introduction-to-netconf-yang.html
+-  Network Automation and the Rise of NETCONF -
+   https://medium.com/@k.okasha/network-automation-and-the-rise-of-netconf-e96cc33fe28
+-  YANG and the Road to a Model Driven Network -
+   https://medium.com/@k.okasha/yang-and-road-to-a-model-driven-network-e9e52d47148d
+
+Software
+~~~~~~~~
+
+libyang
+^^^^^^^
+
+   libyang is a YANG data modelling language parser and toolkit written
+   (and providing API) in C.
+
+-  GitHub page: https://github.com/CESNET/libyang
+-  Documentaion: https://netopeer.liberouter.org/doc/libyang/master/
+
+pyang
+^^^^^
+
+   pyang is a YANG validator, transformator and code generator, written
+   in python. It can be used to validate YANG modules for correctness,
+   to transform YANG modules into other formats, and to generate code
+   from the modules.
+
+-  GitHub page: https://github.com/mbj4668/pyang
+-  Documentaion: https://github.com/mbj4668/pyang/wiki/Documentation
+
+ncclient
+^^^^^^^^
+
+   ncclient is a Python library that facilitates client-side scripting
+   and application development around the NETCONF protocol.
+
+-  GitHub page: https://github.com/ncclient/ncclient
+-  Documentaion: https://ncclient.readthedocs.io/en/latest/
+
+YDK
+^^^
+
+   ydk-gen is a developer tool that can generate API’s that are modeled
+   in YANG. Currently, it generates language binding for Python, Go and
+   C++ with planned support for other language bindings in the future.
+
+-  GitHub pages:
+
+   -  Generator: https://github.com/CiscoDevNet/ydk-gen
+   -  Python: https://github.com/CiscoDevNet/ydk-py
+
+      -  Python samples: https://github.com/CiscoDevNet/ydk-py-samples
+
+   -  Go: https://github.com/CiscoDevNet/ydk-go
+   -  C++: https://github.com/CiscoDevNet/ydk-cpp
+
+-  Documentation:
+
+   -  Python: http://ydk.cisco.com/py/docs/
+   -  Go: http://ydk.cisco.com/go/docs/
+   -  C++: http://ydk.cisco.com/cpp/docs/
+
+-  (Blog post) Simplifying Network Programmability with Model-Driven
+   APIs:
+   https://blogs.cisco.com/sp/simplifying-network-programmability-with-model-driven-apis
+-  (Video introduction) Infrastructure as a Code Using YANG, OpenConfig
+   and YDK: https://www.youtube.com/watch?v=G1b6vJW1R5w
+
+pyangbind
+^^^^^^^^^
+
+   A plugin for pyang that creates Python bindings for a YANG model.
+
+-  GitHub page: https://github.com/robshakir/pyangbind
+-  Documentation: http://pynms.io/pyangbind/
+
+ConfD
+^^^^^
+
+-  Official webpage (for ConfD Basic):
+   http://www.tail-f.com/confd-basic/
+-  Training Videos: http://www.tail-f.com/confd-training-videos/
+-  Forum: http://discuss.tail-f.com/
+
+Sysrepo
+^^^^^^^
+
+   Sysrepo is an YANG-based configuration and operational state data
+   store for Unix/Linux applications.
+
+-  GitHub page: https://github.com/sysrepo/sysrepo
+-  Official webpage: http://www.sysrepo.org/
+-  Documentation: http://www.sysrepo.org/static/doc/html/
+
+Netopeer2
+^^^^^^^^^
+
+   Netopeer2 is a set of tools implementing network configuration tools
+   based on the NETCONF Protocol. This is the second generation of the
+   toolset, originally available as the Netopeer project. Netopeer2 is
+   based on the new generation of the NETCONF and YANG libraries -
+   libyang and libnetconf2. The Netopeer server uses sysrepo as a
+   NETCONF datastore implementation.
+
+-  GitHub page: https://github.com/CESNET/Netopeer2
+
+Clixon
+^^^^^^
+
+   Clixon is an automatic configuration manager where you generate
+   interactive CLI, NETCONF, RESTCONF and embedded databases with
+   transaction support from a YANG specification.
+
+-  GitHub page: https://github.com/clicon/clixon
+-  Project page: http://www.clicon.org/
diff --git a/doc/developer/northbound/northbound.rst b/doc/developer/northbound/northbound.rst
new file mode 100644 (file)
index 0000000..c5e30f1
--- /dev/null
@@ -0,0 +1,21 @@
+.. _northbound:
+
+**************
+Northbound API
+**************
+
+.. toctree::
+   :maxdepth: 2
+
+   advanced-topics
+   architecture
+   demos
+   links
+   operational-data-rpcs-and-notifications
+   plugins-sysrepo
+   ppr-basic-test-topology
+   ppr-mpls-basic-test-topology
+   retrofitting-configuration-commands
+   transactional-cli
+   yang-module-translator
+   yang-tools
diff --git a/doc/developer/northbound/operational-data-rpcs-and-notifications.rst b/doc/developer/northbound/operational-data-rpcs-and-notifications.rst
new file mode 100644 (file)
index 0000000..554bc17
--- /dev/null
@@ -0,0 +1,565 @@
+Operational data
+~~~~~~~~~~~~~~~~
+
+Writing API-agnostic code for YANG-modeled operational data is
+challenging. ConfD and Sysrepo, for instance, have completely different
+APIs to fetch operational data. So how can we write API-agnostic
+callbacks that can be used by both the ConfD and Sysrepo plugins, and
+any other northbound client that might be written in the future?
+
+As an additional requirement, the callbacks must be designed in a way
+that makes in-place XPath filtering possible. As an example, a
+management client might want to retrieve only a subset of a large YANG
+list (e.g. a BGP table), and for optimal performance it should be
+possible to filter out the unwanted elements locally in the managed
+devices instead of returning all elements and performing the filtering
+on the management application.
+
+To meet all these requirements, the four callbacks below were introduced
+in the northbound architecture:
+
+.. code:: c
+
+           /*
+            * Operational data callback.
+            *
+            * The callback function should return the value of a specific leaf or
+            * inform if a typeless value (presence containers or leafs of type
+            * empty) exists or not.
+            *
+            * xpath
+            *    YANG data path of the data we want to get
+            *
+            * list_entry
+            *    pointer to list entry
+            *
+            * Returns:
+            *    pointer to newly created yang_data structure, or NULL to indicate
+            *    the absence of data
+            */
+           struct yang_data *(*get_elem)(const char *xpath, void *list_entry);
+
+           /*
+            * Operational data callback for YANG lists.
+            *
+            * The callback function should return the next entry in the list. The
+            * 'list_entry' parameter will be NULL on the first invocation.
+            *
+            * list_entry
+            *    pointer to a list entry
+            *
+            * Returns:
+            *    pointer to the next entry in the list, or NULL to signal that the
+            *    end of the list was reached
+            */
+           void *(*get_next)(void *list_entry);
+
+           /*
+            * Operational data callback for YANG lists.
+            *
+            * The callback function should fill the 'keys' parameter based on the
+            * given list_entry.
+            *
+            * list_entry
+            *    pointer to a list entry
+            *
+            * keys
+            *    structure to be filled based on the attributes of the provided
+            *    list entry
+            *
+            * Returns:
+            *    NB_OK on success, NB_ERR otherwise
+            */
+           int (*get_keys)(void *list_entry, struct yang_list_keys *keys);
+
+           /*
+            * Operational data callback for YANG lists.
+            *
+            * The callback function should return a list entry based on the list
+            * keys given as a parameter.
+            *
+            * keys
+            *    structure containing the keys of the list entry
+            *
+            * Returns:
+            *    a pointer to the list entry if found, or NULL if not found
+            */
+           void *(*lookup_entry)(struct yang_list_keys *keys);
+
+These callbacks were designed to provide maximum flexibility, and borrow
+a lot of ideas from the ConfD API. Each callback does one and only one
+task, they are indivisible primitives that can be combined in several
+different ways to iterate over operational data. The extra flexibility
+certainly has a performance cost, but it’s the price to pay if we want
+to expose FRR operational data using several different management
+interfaces (e.g. NETCONF via either ConfD or Sysrepo+Netopeer2). In the
+future it might be possible to introduce optional callbacks that do
+things like returning multiple objects at once. They would provide
+enhanced performance when iterating over large lists, but their use
+would be limited by the northbound plugins that can be integrated with
+them.
+
+   NOTE: using the northbound callbacks as a base, the ConfD plugin can
+   provide up to 100 objects between each round trip between FRR and the
+   *confd* daemon. Preliminary tests showed FRR taking ~7 seconds
+   (asynchronously, without blocking the main pthread) to return a RIP
+   table containing 100k routes to a NETCONF client connected to *confd*
+   (JSON was used as the encoding format). Work needs to be done to find
+   the bottlenecks and optimize this operation.
+
+The [[Plugins - Writing Your Own]] page explains how the northbound
+plugins can fetch operational data using the aforementioned northbound
+callbacks, and how in-place XPath filtering can be implemented.
+
+Example
+^^^^^^^
+
+Now let’s move to an example to show how these callbacks are implemented
+in practice. The following YANG container is part of the *ietf-rip*
+module and contains operational data about RIP neighbors:
+
+.. code:: yang
+
+         container neighbors {
+           description
+             "Neighbor information.";
+           list neighbor {
+             key "address";
+             description
+               "A RIP neighbor.";
+             leaf address {
+               type inet:ipv4-address;
+               description
+                 "IP address that a RIP neighbor is using as its
+                  source address.";
+             }
+             leaf last-update {
+               type yang:date-and-time;
+               description
+                 "The time when the most recent RIP update was
+                  received from this neighbor.";
+             }
+             leaf bad-packets-rcvd {
+               type yang:counter32;
+               description
+                 "The number of RIP invalid packets received from
+                  this neighbor which were subsequently discarded
+                  for any reason (e.g. a version 0 packet, or an
+                  unknown command type).";
+             }
+             leaf bad-routes-rcvd {
+               type yang:counter32;
+               description
+                 "The number of routes received from this neighbor,
+                  in valid RIP packets, which were ignored for any
+                  reason (e.g. unknown address family, or invalid
+                  metric).";
+             }
+           }
+         }
+
+We know that this is operational data because the ``neighbors``
+container is within the ``state`` container, which has the
+``config false;`` property (which is applied recursively).
+
+As expected, the ``gen_northbound_callbacks`` tool also generates
+skeleton callbacks for nodes that represent operational data:
+
+.. code:: c
+
+                   {
+                           .xpath = "/frr-ripd:ripd/state/neighbors/neighbor",
+                           .cbs.get_next = ripd_state_neighbors_neighbor_get_next,
+                           .cbs.get_keys = ripd_state_neighbors_neighbor_get_keys,
+                           .cbs.lookup_entry = ripd_state_neighbors_neighbor_lookup_entry,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/address",
+                           .cbs.get_elem = ripd_state_neighbors_neighbor_address_get_elem,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/last-update",
+                           .cbs.get_elem = ripd_state_neighbors_neighbor_last_update_get_elem,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/bad-packets-rcvd",
+                           .cbs.get_elem = ripd_state_neighbors_neighbor_bad_packets_rcvd_get_elem,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/bad-routes-rcvd",
+                           .cbs.get_elem = ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem,
+                   },
+
+The ``/frr-ripd:ripd/state/neighbors/neighbor`` list within the
+``neighbors`` container has three different callbacks that need to be
+implemented. Let’s start with the first one, the ``get_next`` callback:
+
+.. code:: c
+
+   static void *ripd_state_neighbors_neighbor_get_next(void *list_entry)
+   {
+           struct listnode *node;
+
+           if (list_entry == NULL)
+                   node = listhead(peer_list);
+           else
+                   node = listnextnode((struct listnode *)list_entry);
+
+           return node;
+   }
+
+Given a list entry, the job of this callback is to find the next element
+from the list. When the ``list_entry`` parameter is NULL, then the first
+element of the list should be returned.
+
+*ripd* uses the ``rip_peer`` structure to represent RIP neighbors, and
+the ``peer_list`` global variable (linked list) is used to store all RIP
+neighbors.
+
+In order to be able to iterate over the list of RIP neighbors, the
+callback returns a ``listnode`` variable instead of a ``rip_peer``
+variable. The ``listnextnode`` macro can then be used to find the next
+element from the linked list.
+
+Now the second callback, ``get_keys``:
+
+.. code:: c
+
+   static int ripd_state_neighbors_neighbor_get_keys(void *list_entry,
+                                                     struct yang_list_keys *keys)
+   {
+           struct listnode *node = list_entry;
+           struct rip_peer *peer = listgetdata(node);
+
+           keys->num = 1;
+           (void)inet_ntop(AF_INET, &peer->addr, keys->key[0].value,
+                           sizeof(keys->key[0].value));
+
+           return NB_OK;
+   }
+
+This one is easy. First, we obtain the RIP neighbor from the
+``listnode`` structure. Then, we fill the ``keys`` parameter according
+to the attributes of the RIP neighbor. In this case, the ``neighbor``
+YANG list has only one key: the neighbor IP address. We then use the
+``inet_ntop()`` function to transform this binary IP address into a
+string (the lingua franca of the FRR northbound).
+
+The last callback for the ``neighbor`` YANG list is the ``lookup_entry``
+callback:
+
+.. code:: c
+
+   static void *
+   ripd_state_neighbors_neighbor_lookup_entry(struct yang_list_keys *keys)
+   {
+           struct in_addr address;
+
+           yang_str2ipv4(keys->key[0].value, &address);
+
+           return rip_peer_lookup(&address);
+   }
+
+This callback is the counterpart of the ``get_keys`` callback: given an
+array of list keys, the associated list entry should be returned. The
+``yang_str2ipv4()`` function is used to convert the list key (an IP
+address) from a string to an ``in_addr`` structure. Then the
+``rip_peer_lookup()`` function is used to find the list entry.
+
+Finally, each YANG leaf inside the ``neighbor`` list has its associated
+``get_elem`` callback:
+
+.. code:: c
+
+   /*
+    * XPath: /frr-ripd:ripd/state/neighbors/neighbor/address
+    */
+   static struct yang_data *
+   ripd_state_neighbors_neighbor_address_get_elem(const char *xpath,
+                                                  void *list_entry)
+   {
+           struct rip_peer *peer = list_entry;
+
+           return yang_data_new_ipv4(xpath, &peer->addr);
+   }
+
+   /*
+    * XPath: /frr-ripd:ripd/state/neighbors/neighbor/last-update
+    */
+   static struct yang_data *
+   ripd_state_neighbors_neighbor_last_update_get_elem(const char *xpath,
+                                                      void *list_entry)
+   {
+           /* TODO: yang:date-and-time is tricky */
+           return NULL;
+   }
+
+   /*
+    * XPath: /frr-ripd:ripd/state/neighbors/neighbor/bad-packets-rcvd
+    */
+   static struct yang_data *
+   ripd_state_neighbors_neighbor_bad_packets_rcvd_get_elem(const char *xpath,
+                                                           void *list_entry)
+   {
+           struct rip_peer *peer = list_entry;
+
+           return yang_data_new_uint32(xpath, peer->recv_badpackets);
+   }
+
+   /*
+    * XPath: /frr-ripd:ripd/state/neighbors/neighbor/bad-routes-rcvd
+    */
+   static struct yang_data *
+   ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem(const char *xpath,
+                                                          void *list_entry)
+   {
+           struct rip_peer *peer = list_entry;
+
+           return yang_data_new_uint32(xpath, peer->recv_badroutes);
+   }
+
+These callbacks receive the list entry as parameter and return the
+corresponding data using the ``yang_data_new_*()`` wrapper functions.
+Not much to explain here.
+
+Iterating over operational data without blocking the main pthread
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+One of the problems we have in FRR is that some “show” commands in the
+CLI can take too long, potentially long enough to the point of
+triggering some protocol timeouts and bringing sessions down.
+
+To avoid this kind of problem, northbound clients are encouraged to do
+one of the following: \* Create a separate pthread for handling requests
+to fetch operational data. \* Iterate over YANG lists and leaf-lists
+asynchronously, returning a maximum number of elements per time instead
+of returning all elements in one shot.
+
+In order to handle both cases correctly, the ``get_next`` callbacks need
+to use locks to prevent the YANG lists from being modified while they
+are being iterated over. If that is not done, the list entry returned by
+this callback can become a dangling pointer when used in another
+callback.
+
+Currently the ConfD and Sysrepo plugins run only in the main pthread.
+The plan in the short-term is to introduce a separate pthread only for
+handling operational data, and use the main pthread only for handling
+configuration changes, RPCs and notifications.
+
+RPCs and Actions
+~~~~~~~~~~~~~~~~
+
+The FRR northbound supports YANG RPCs and Actions through the ``rpc()``
+callback, which is documented as follows in the *lib/northbound.h* file:
+
+.. code:: c
+
+           /*
+            * RPC and action callback.
+            *
+            * Both 'input' and 'output' are lists of 'yang_data' structures. The
+            * callback should fetch all the input parameters from the 'input' list,
+            * and add output parameters to the 'output' list if necessary.
+            *
+            * xpath
+            *    xpath of the YANG RPC or action
+            *
+            * input
+            *    read-only list of input parameters
+            *
+            * output
+            *    list of output parameters to be populated by the callback
+            *
+            * Returns:
+            *    NB_OK on success, NB_ERR otherwise
+            */
+           int (*rpc)(const char *xpath, const struct list *input,
+                      struct list *output);
+
+Note that the same callback is used for both RPCs and actions, which are
+essentially the same thing. In the case of YANG actions, the ``xpath``
+parameter can be consulted to find the data node associated to the
+operation.
+
+As part of the northbound retrofitting process, it’s suggested to model
+some EXEC-level commands using YANG so that their functionality is
+exposed to other management interfaces other than the CLI. As an
+example, if the ``clear bgp`` command is modeled using a YANG RPC, and a
+corresponding ``rpc`` callback is written, then it should be possible to
+clear BGP neighbors using NETCONF and RESTCONF with that RPC (the ConfD
+and Sysrepo plugins have full support for YANG RPCs and actions).
+
+Here’s an example of a very simple RPC modeled using YANG:
+
+.. code:: yang
+
+     rpc clear-rip-route {
+       description
+         "Clears RIP routes from the IP routing table and routes
+          redistributed into the RIP protocol.";
+     }
+
+This RPC doesn’t have any input or output parameters. Below we can see
+the implementation of the corresponding ``rpc`` callback, whose skeleton
+was automatically generated by the ``gen_northbound_callbacks`` tool:
+
+.. code:: c
+
+   /*
+    * XPath: /frr-ripd:clear-rip-route
+    */
+   static int clear_rip_route_rpc(const char *xpath, const struct list *input,
+                                  struct list *output)
+   {
+           struct route_node *rp;
+           struct rip_info *rinfo;
+           struct list *list;
+           struct listnode *listnode;
+
+           /* Clear received RIP routes */
+           for (rp = route_top(rip->table); rp; rp = route_next(rp)) {
+                   list = rp->info;
+                   if (list == NULL)
+                           continue;
+
+                   for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) {
+                           if (!rip_route_rte(rinfo))
+                                   continue;
+
+                           if (CHECK_FLAG(rinfo->flags, RIP_RTF_FIB))
+                                   rip_zebra_ipv4_delete(rp);
+                           break;
+                   }
+
+                   if (rinfo) {
+                           RIP_TIMER_OFF(rinfo->t_timeout);
+                           RIP_TIMER_OFF(rinfo->t_garbage_collect);
+                           listnode_delete(list, rinfo);
+                           rip_info_free(rinfo);
+                   }
+
+                   if (list_isempty(list)) {
+                           list_delete_and_null(&list);
+                           rp->info = NULL;
+                           route_unlock_node(rp);
+                   }
+           }
+
+           return NB_OK;
+   }
+
+If the ``clear-rip-route`` RPC had any input parameters, they would be
+available in the ``input`` list given as a parameter to the callback.
+Similarly, the ``output`` list can be used to append output parameters
+generated by the RPC, if any are defined in the YANG model.
+
+The northbound clients (CLI and northbound plugins) have the
+responsibility to create and delete the ``input`` and ``output`` lists.
+However, in the cases where the RPC or action doesn’t have any input or
+output parameters, the northbound client can pass NULL pointers to the
+``rpc`` callback to avoid creating linked lists unnecessarily. We can
+see this happening in the example below:
+
+.. code:: c
+
+   /*
+    * XPath: /frr-ripd:clear-rip-route
+    */
+   DEFPY (clear_ip_rip,
+          clear_ip_rip_cmd,
+          "clear ip rip",
+          CLEAR_STR
+          IP_STR
+          "Clear IP RIP database\n")
+   {
+           return nb_cli_rpc("/frr-ripd:clear-rip-route", NULL, NULL);
+   }
+
+``nb_cli_rpc()`` is a helper function that merely finds the appropriate
+``rpc`` callback based on the XPath provided in the first argument, and
+map the northbound error code from the ``rpc`` callback to a vty error
+code (e.g. ``CMD_SUCCESS``, ``CMD_WARNING``). The second and third
+arguments provided to the function refer to the ``input`` and ``output``
+lists. In this case, both arguments are set to NULL since the YANG RPC
+in question doesn’t have any input/output parameters.
+
+Notifications
+~~~~~~~~~~~~~
+
+YANG notifations are sent using the ``nb_notification_send()`` function,
+documented in the *lib/northbound.h* file as follows:
+
+.. code:: c
+
+   /*
+    * Send a YANG notification. This is a no-op unless the 'nb_notification_send'
+    * hook was registered by a northbound plugin.
+    *
+    * xpath
+    *    xpath of the YANG notification
+    *
+    * arguments
+    *    linked list containing the arguments that should be sent. This list is
+    *    deleted after being used.
+    *
+    * Returns:
+    *    NB_OK on success, NB_ERR otherwise
+    */
+   extern int nb_notification_send(const char *xpath, struct list *arguments);
+
+The northbound doesn’t use callbacks for notifications because
+notifications are generated locally and sent to the northbound clients.
+This way, whenever a notification needs to be sent, it’s possible to
+call the appropriate function directly instead of finding a callback
+based on the XPath of the YANG notification.
+
+As an example, the *ietf-rip* module contains the following
+notification:
+
+.. code:: yang
+
+     notification authentication-failure {
+       description
+         "This notification is sent when the system
+          receives a PDU with the wrong authentication
+          information.";
+       leaf interface-name {
+         type string;
+         description
+           "Describes the name of the RIP interface.";
+       }
+     }
+
+The following convenience function was implemented in *ripd* to send
+*authentication-failure* YANG notifications:
+
+.. code:: c
+
+   /*
+    * XPath: /frr-ripd:authentication-failure
+    */
+   void ripd_notif_send_auth_failure(const char *ifname)
+   {
+           const char *xpath = "/frr-ripd:authentication-failure";
+           struct list *arguments;
+           char xpath_arg[XPATH_MAXLEN];
+           struct yang_data *data;
+
+           arguments = yang_data_list_new();
+
+           snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath);
+           data = yang_data_new_string(xpath_arg, ifname);
+           listnode_add(arguments, data);
+
+           nb_notification_send(xpath, arguments);
+   }
+
+Now sending the *authentication-failure* YANG notification should be as
+simple as calling the above function and provide the appropriate
+interface name. The notification will be processed by all northbound
+plugins that subscribed a callback to the ``nb_notification_send`` hook.
+The ConfD and Sysrepo plugins, for instance, use this hook to relay the
+notifications to the *confd*/*sysrepod* daemons, which can generate
+NETCONF notifications to subscribed clients. When no northbound plugin
+is loaded, ``nb_notification_send()`` doesn’t do anything and the
+notifications are ignored.
diff --git a/doc/developer/northbound/plugins-sysrepo.rst b/doc/developer/northbound/plugins-sysrepo.rst
new file mode 100644 (file)
index 0000000..186c3a0
--- /dev/null
@@ -0,0 +1,137 @@
+Installation
+------------
+
+Required dependencies
+^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+   # apt-get install git cmake build-essential bison flex libpcre3-dev libev-dev \
+                     libavl-dev libprotobuf-c-dev protobuf-c-compiler libcmocka0 \
+                     libcmocka-dev doxygen libssl-dev libssl-dev libssh-dev
+
+libyang
+^^^^^^^
+
+::
+
+   # apt-get install libyang0.16 libyang-dev
+
+Sysrepo
+^^^^^^^
+
+::
+
+   $ git clone https://github.com/sysrepo/sysrepo.git
+   $ cd sysrepo/
+   $ mkdir build; cd build
+   $ cmake -DCMAKE_BUILD_TYPE=Release -DGEN_LANGUAGE_BINDINGS=OFF .. && make
+   # make install
+
+libnetconf2
+^^^^^^^^^^^
+
+::
+
+   $ git clone https://github.com/CESNET/libnetconf2.git
+   $ cd libnetconf2/
+   $ mkdir build; cd build
+   $ cmake .. && make
+   # make install
+
+netopeer2
+^^^^^^^^^
+
+::
+
+   $ git clone https://github.com/CESNET/Netopeer2.git
+   $ cd Netopeer2
+   $ cd server
+   $ mkdir build; cd build
+   $ cmake .. && make
+   # make install
+
+**Note:** If ``make install`` fails as it can’t find
+``libsysrepo.so.0.7``, then run ``ldconfig`` and try again as it might
+not have updated the lib search path
+
+FRR
+^^^
+
+Build and install FRR using the ``--enable-sysrepo`` configure-time
+option.
+
+Initialization
+--------------
+
+Install the FRR YANG modules in the Sysrepo datastore:
+
+::
+
+   # sysrepoctl --install /usr/local/share/yang/ietf-interfaces@2018-01-09.yang 
+   # sysrepoctl --install /usr/local/share/yang/frr-vrf.yang 
+   # sysrepoctl --install /usr/local/share/yang/frr-interface.yang 
+   # sysrepoctl --install /usr/local/share/yang/frr-route-types.yang 
+   # sysrepoctl --install /usr/local/share/yang/frr-filter.yang 
+   # sysrepoctl --install /usr/local/share/yang/frr-route-map.yang 
+   # sysrepoctl --install /usr/local/share/yang/frr-isisd.yang 
+   # sysrepoctl --install /usr/local/share/yang/frr-ripd.yang
+   # sysrepoctl --install /usr/local/share/yang/frr-ripngd.yang
+   # sysrepoctl -c frr-vrf --owner frr --group frr
+   # sysrepoctl -c frr-interface --owner frr --group frr
+   # sysrepoctl -c frr-route-types --owner frr --group frr
+   # sysrepoctl -c frr-filter --owner frr --group frr
+   # sysrepoctl -c frr-route-map --owner frr --group frr
+   # sysrepoctl -c frr-isisd --owner frr --group frr
+   # sysrepoctl -c frr-ripd --owner frr --group frr
+   # sysrepoctl -c frr-ripngd --owner frr --group frr
+
+Start netopeer2-server:
+
+::
+
+   # netopeer2-server -d &
+
+Start the FRR daemons with the sysrepo module:
+
+::
+
+   # isisd -M sysrepo --log=stdout
+
+Managing the configuration
+--------------------------
+
+The following NETCONF scripts can be used to show and edit the FRR
+configuration:
+https://github.com/rzalamena/ietf-hackathon-brazil-201907/tree/master/netconf-scripts
+
+Example:
+
+::
+
+   # ./netconf-edit.py 127.0.0.1
+   # ./netconf-get-config.py 127.0.0.1
+   <?xml version="1.0" encoding="UTF-8"?><data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><isis xmlns="http://frrouting.org/yang/isisd"><instance><area-tag>testnet</area-tag><is-type>level-1</is-type></instance></isis></data>
+
+..
+
+   NOTE: the ncclient library needs to be installed first:
+   ``apt install -y python3-ncclient``
+
+The *sysrepocfg* tool can also be used to show/edit the FRR
+configuration. Example:
+
+::
+
+   # sysrepocfg --format=json --import=frr-isisd.json --datastore=running frr-isisd
+   # sysrepocfg --format=json --export --datastore=running frr-isisd
+   {
+     "frr-isisd:isis": {
+       "instance": [
+         {
+           "area-tag": "testnet",
+           "is-type": "level-1"
+         }
+       ]
+     }
+   }
diff --git a/doc/developer/northbound/ppr-basic-test-topology.rst b/doc/developer/northbound/ppr-basic-test-topology.rst
new file mode 100644 (file)
index 0000000..582c76c
--- /dev/null
@@ -0,0 +1,1632 @@
+Table of Contents
+~~~~~~~~~~~~~~~~~
+
+-  `Software <#software>`__
+-  `Topology <#topology>`__
+-  `Configuration <#configuration>`__
+
+   -  `CLI <#configuration-cli>`__
+   -  `YANG <#configuration-yang>`__
+
+-  `Verification - Control Plane <#verification-cplane>`__
+-  `Verification - Forwarding Plane <#verification-fplane>`__
+
+Software
+~~~~~~~~
+
+The FRR PPR implementation for IS-IS is available here:
+https://github.com/opensourcerouting/frr/tree/isisd-ppr
+
+Topology
+~~~~~~~~
+
+In this topology we have an IS-IS network consisting of 12 routers. CE1
+and CE2 are the consumer edges, connected to R11 and R14, respectively.
+Three hosts are connected to the CEs using only static routes.
+
+Router R11 advertises 6 PPR TLVs, which corresponds to three
+bi-directional GRE tunnels: \* **6000:1::1 <-> 6000:2::1:** {R11 - R21 -
+R22 - R23 - R14} (IPv6 Node Addresses only) \* **6000:1::2 <->
+6000:2::2:** {R11 - R21 - R32 - R41 - R33 - R23 - R14} (IPv6 Node and
+Interface Addresses) \* **6000:1::3 <-> 6000:2::3:** {R11 - R21 - R99 -
+R23 - R14} (misconfigured path)
+
+PBR rules are configured on R11 and R14 to route the traffic between
+Host 1 and Host 3 using the first PPR tunnel. Traffic between Host 2 and
+Host 3 uses the regular IS-IS shortest path.
+
+Additional information: \* Addresses in the 4000::/16 range refer to
+interface addresses, where the last hextet corresponds to the node ID.
+\* Addresses in the 5000::/16 range refer to loopback addresses, where
+the last hextet corresponds to the node ID. \* Addresses in the
+6000::/16 range refer to PPR-ID addresses.
+
+::
+
+   +-------+       +-------+                                                 +-------+
+   |       |       |       |                                                 |       |
+   | HOST1 |       | HOST2 |                                                 | HOST3 |
+   |       |       |       |                                                 |       |
+   +---+---+       +---+---+                                                 +---+---+
+       |               |                                                         |
+       |fd00:10:1::/64 |                                                         |
+       +-----+  +------+                                           fd00:20:1::/64|
+             |  |fd00:10:2::/64                                                  |
+             |  |                                                                |
+           +-+--+--+                                                         +---+---+
+           |       |                                                         |       |
+           |  CE1  |                                                         |  CE2  |
+           |       |                                                         |       |
+           +---+---+                                                         +---+---+
+               |                                                                 |
+               |                                                                 |
+               |fd00:10:0::/64                                     fd00:20:0::/64|
+               |                                                                 |
+               |                                                                 |
+           +---+---+             +-------+             +-------+             +---+---+
+           |       |4000:101::/64|       |4000:102::/64|       |4000:103::/64|       |
+           |  R11  +-------------+  R12  +-------------+  R13  +-------------+  R14  |
+           |       |             |       |             |       |             |       |
+           +---+---+             +--+-+--+             +--+-+--+             +---+---+
+               |                    | |                   | |                    |
+               |4000:104::/64       | |4000:106::/64      | |4000:108::/64       |
+               +---------+ +--------+ +--------+ +--------+ +--------+ +---------+
+                         | |4000:105::/64      | |4000:107::/64      | |4000:109::/64
+                         | |                   | |                   | |
+                      +--+-+--+             +--+-+--+             +--+-+--+
+                      |       |4000:110::/64|       |4000:111::/64|       |
+                      |  R21  +-------------+  R22  +-------------+  R23  |
+                      |       |             |       |             |       |
+                      +--+-+--+             +--+-+--+             +--+-+--+
+                         | |                   | |                   | |
+                         | |4000:113::/64      | |4000:115::/64      | |4000:117::/64
+               +---------+ +--------+ +--------+ +--------+ +--------+ +---------+
+               |4000:112::/64       | |4000:114::/64      | |4000:116::/64       |
+               |                    | |                   | |                    |
+           +---+---+             +--+-+--+             +--+-+--+             +---+---+
+           |       |4000:118::/64|       |4000:119::/64|       |4000:120::/64|       |
+           |  R31  +-------------+  R32  +-------------+  R33  +-------------+  R34  |
+           |       |             |       |             |       |             |       |
+           +-------+             +---+---+             +---+---+             +-------+
+                                     |                     |
+                                     |4000:121::/64        |
+                                     +----------+----------+
+                                                |
+                                                |
+                                            +---+---+
+                                            |       |
+                                            |  R41  |
+                                            |       |
+                                            +-------+
+
+Configuration
+~~~~~~~~~~~~~
+
+PPR TLV processing needs to be enabled on all IS-IS routers using the
+``ppr on`` command. The advertisements of all PPR TLVs is done by router
+R11.
+
+CLI configuration
+^^^^^^^^^^^^^^^^^
+
+.. code:: yaml
+
+   ---
+
+   routers:
+
+     host1:
+       links:
+         eth-ce1:
+           peer: [ce1, eth-host1]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-ce1
+            ipv6 address fd00:10:1::1/64
+           !
+           ipv6 route ::/0 fd00:10:1::100
+
+     host2:
+       links:
+         eth-ce1:
+           peer: [ce1, eth-host2]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-ce1
+            ipv6 address fd00:10:2::1/64
+           !
+           ipv6 route ::/0 fd00:10:2::100
+
+     host3:
+       links:
+         eth-ce2:
+           peer: [ce2, eth-host3]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-ce2
+            ipv6 address fd00:20:1::1/64
+           !
+           ipv6 route ::/0 fd00:20:1::100
+
+     ce1:
+       links:
+         eth-host1:
+           peer: [host1, eth-ce1]
+         eth-host2:
+           peer: [host2, eth-ce1]
+         eth-rt11:
+           peer: [rt11, eth-ce1]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-host1
+            ipv6 address fd00:10:1::100/64
+           !
+           interface eth-host2
+            ipv6 address fd00:10:2::100/64
+           !
+           interface eth-rt11
+            ipv6 address fd00:10:0::100/64
+           !
+           ipv6 route ::/0 fd00:10:0::11
+
+     ce2:
+       links:
+         eth-host3:
+           peer: [host3, eth-ce2]
+         eth-rt14:
+           peer: [rt14, eth-ce2]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-host3
+            ipv6 address fd00:20:1::100/64
+           !
+           interface eth-rt14
+            ipv6 address fd00:20:0::100/64
+           !
+           ipv6 route ::/0 fd00:20:0::14
+
+     rt11:
+       links:
+         lo-ppr:
+         eth-ce1:
+           peer: [ce1, eth-rt11]
+         eth-rt12:
+           peer: [rt12, eth-rt11]
+         eth-rt21:
+           peer: [rt21, eth-rt11]
+       shell: |
+         # GRE tunnel for preferred packets (PPR)
+         ip -6 tunnel add tun-ppr mode ip6gre remote 6000:2::1 local 6000:1::1 ttl 64
+         ip link set dev tun-ppr up
+         # PBR rules
+         ip -6 rule add from fd00:10:1::/64 to fd00:20:1::/64 iif eth-ce1 lookup 10000
+         ip -6 route add default dev tun-ppr table 10000
+       frr:
+         zebra:
+         staticd:
+         isisd:
+         config: |
+           interface lo-ppr
+            ipv6 address 6000:1::1/128
+            ipv6 address 6000:1::2/128
+            ipv6 address 6000:1::3/128
+           !
+           interface lo
+            ipv6 address 5000::11/128
+            ipv6 router isis 1
+           !
+           interface eth-ce1
+            ipv6 address fd00:10:0::11/64
+           !
+           interface eth-rt12
+            ipv6 address 4000:101::11/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt21
+            ipv6 address 4000:104::11/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           ipv6 route fd00:10::/32 fd00:10:0::100
+           !
+           ppr group VOIP
+            ppr ipv6 6000:1::1/128 prefix 5000::11/128 metric 50
+             pde ipv6-node 5000::14/128
+             pde ipv6-node 5000::23/128
+             pde ipv6-node 5000::22/128
+             pde ipv6-node 5000::21/128
+             pde ipv6-node 5000::11/128
+            !
+            ppr ipv6 6000:2::1/128 prefix 5000::14/128 metric 50
+             pde ipv6-node 5000::11/128
+             pde ipv6-node 5000::21/128
+             pde ipv6-node 5000::22/128
+             pde ipv6-node 5000::23/128
+             pde ipv6-node 5000::14/128
+            !
+           !
+           ppr group INTERFACE_PDES
+            ppr ipv6 6000:1::2/128 prefix 5000::11/128
+             pde ipv6-node 5000::14/128
+             pde ipv6-node 5000::23/128
+             pde ipv6-node 5000::33/128
+             pde ipv6-interface 4000:121::41/64
+             pde ipv6-node 5000::32/128
+             pde ipv6-interface 4000:113::21/64
+             pde ipv6-node 5000::11/128
+            !
+            ppr ipv6 6000:2::2/128 prefix 5000::14/128
+             pde ipv6-node 5000::11/128
+             pde ipv6-node 5000::21/128
+             pde ipv6-node 5000::32/128
+             pde ipv6-interface 4000:121::41/64
+             pde ipv6-node 5000::33/128
+             pde ipv6-interface 4000:116::23/64
+             pde ipv6-node 5000::14/128
+            !
+           !
+           ppr group BROKEN
+            ppr ipv6 6000:1::3/128 prefix 5000::11/128 metric 1500
+             pde ipv6-node 5000::14/128
+             pde ipv6-node 5000::23/128
+             ! non-existing node!!!
+             pde ipv6-node 5000::99/128
+             pde ipv6-node 5000::21/128
+             pde ipv6-node 5000::11/128
+            !
+            ppr ipv6 6000:2::3/128 prefix 5000::14/128 metric 1500
+             pde ipv6-node 5000::11/128
+             pde ipv6-node 5000::21/128
+             ! non-existing node!!!
+             pde ipv6-node 5000::99/128
+             pde ipv6-node 5000::23/128
+             pde ipv6-node 5000::14/128
+            !
+           !
+           router isis 1
+            net 49.0000.0000.0000.0011.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+            ppr advertise VOIP
+            ppr advertise INTERFACE_PDES
+            ppr advertise BROKEN
+           !
+
+     rt12:
+       links:
+         eth-rt11:
+           peer: [rt11, eth-rt12]
+         eth-rt13:
+           peer: [rt13, eth-rt12]
+         eth-rt21:
+           peer: [rt21, eth-rt12]
+         eth-rt22:
+           peer: [rt22, eth-rt12]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::12/128
+            ipv6 router isis 1
+           !
+           interface eth-rt11
+            ipv6 address 4000:101::12/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt13
+            ipv6 address 4000:102::12/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt21
+            ipv6 address 4000:105::12/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:106::12/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0012.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt13:
+       links:
+         eth-rt12:
+           peer: [rt12, eth-rt13]
+         eth-rt14:
+           peer: [rt14, eth-rt13]
+         eth-rt22:
+           peer: [rt22, eth-rt13]
+         eth-rt23:
+           peer: [rt23, eth-rt13]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::13/128
+            ipv6 router isis 1
+           !
+           interface eth-rt12
+            ipv6 address 4000:102::13/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt14
+            ipv6 address 4000:103::13/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:107::13/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt23
+            ipv6 address 4000:108::13/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0013.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt14:
+       links:
+         lo-ppr:
+         eth-ce2:
+           peer: [ce2, eth-rt14]
+         eth-rt13:
+           peer: [rt13, eth-rt14]
+         eth-rt23:
+           peer: [rt23, eth-rt14]
+       shell: |
+         # GRE tunnel for preferred packets (PPR)
+         ip -6 tunnel add tun-ppr mode ip6gre remote 6000:1::1 local 6000:2::1 ttl 64
+         ip link set dev tun-ppr up
+         # PBR rules
+         ip -6 rule add from fd00:20:1::/64 to fd00:10:1::/64 iif eth-ce2 lookup 10000
+         ip -6 route add default dev tun-ppr table 10000
+       frr:
+         zebra:
+         staticd:
+         isisd:
+         config: |
+           interface lo-ppr
+            ipv6 address 6000:2::1/128
+            ipv6 address 6000:2::2/128
+            ipv6 address 6000:2::3/128
+           !
+           interface lo
+            ipv6 address 5000::14/128
+            ipv6 router isis 1
+           !
+           interface eth-ce2
+            ipv6 address fd00:20:0::14/64
+           !
+           interface eth-rt13
+            ipv6 address 4000:103::14/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt23
+            ipv6 address 4000:109::14/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           ipv6 route fd00:20::/32 fd00:20:0::100
+           !
+           router isis 1
+            net 49.0000.0000.0000.0014.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt21:
+       links:
+         eth-rt11:
+           peer: [rt11, eth-rt21]
+         eth-rt12:
+           peer: [rt12, eth-rt21]
+         eth-rt22:
+           peer: [rt22, eth-rt21]
+         eth-rt31:
+           peer: [rt31, eth-rt21]
+         eth-rt32:
+           peer: [rt32, eth-rt21]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::21/128
+            ipv6 router isis 1
+           !
+           interface eth-rt11
+            ipv6 address 4000:104::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt12
+            ipv6 address 4000:105::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:110::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt31
+            ipv6 address 4000:112::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt32
+            ipv6 address 4000:113::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0021.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt22:
+       links:
+         eth-rt12:
+           peer: [rt12, eth-rt22]
+         eth-rt13:
+           peer: [rt13, eth-rt22]
+         eth-rt21:
+           peer: [rt21, eth-rt22]
+         eth-rt23:
+           peer: [rt23, eth-rt22]
+         eth-rt32:
+           peer: [rt32, eth-rt22]
+         eth-rt33:
+           peer: [rt33, eth-rt22]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::22/128
+            ipv6 router isis 1
+           !
+           interface eth-rt12
+            ipv6 address 4000:106::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt13
+            ipv6 address 4000:107::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt21
+            ipv6 address 4000:110::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt23
+            ipv6 address 4000:111::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt32
+            ipv6 address 4000:114::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt33
+            ipv6 address 4000:115::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0022.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt23:
+       links:
+         eth-rt13:
+           peer: [rt13, eth-rt23]
+         eth-rt14:
+           peer: [rt14, eth-rt23]
+         eth-rt22:
+           peer: [rt22, eth-rt23]
+         eth-rt33:
+           peer: [rt33, eth-rt23]
+         eth-rt34:
+           peer: [rt34, eth-rt23]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::23/128
+            ipv6 router isis 1
+           !
+           interface eth-rt13
+            ipv6 address 4000:108::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt14
+            ipv6 address 4000:109::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:111::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt33
+            ipv6 address 4000:116::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt34
+            ipv6 address 4000:117::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0023.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt31:
+       links:
+         eth-rt21:
+           peer: [rt21, eth-rt31]
+         eth-rt32:
+           peer: [rt32, eth-rt31]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::31/128
+            ipv6 router isis 1
+           !
+           interface eth-rt21
+            ipv6 address 4000:112::31/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt32
+            ipv6 address 4000:118::31/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0031.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt32:
+       links:
+         eth-rt21:
+           peer: [rt21, eth-rt32]
+         eth-rt22:
+           peer: [rt22, eth-rt32]
+         eth-rt31:
+           peer: [rt31, eth-rt32]
+         eth-rt33:
+           peer: [rt33, eth-rt32]
+         eth-sw1:
+           peer: [sw1, eth-rt32]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::32/128
+            ipv6 router isis 1
+           !
+           interface eth-rt21
+            ipv6 address 4000:113::32/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:114::32/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt31
+            ipv6 address 4000:118::32/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt33
+            ipv6 address 4000:119::32/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-sw1
+            ipv6 address 4000:121::32/64
+            ipv6 router isis 1
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0032.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt33:
+       links:
+         eth-rt22:
+           peer: [rt22, eth-rt33]
+         eth-rt23:
+           peer: [rt23, eth-rt33]
+         eth-rt32:
+           peer: [rt32, eth-rt33]
+         eth-rt34:
+           peer: [rt34, eth-rt33]
+         eth-sw1:
+           peer: [sw1, eth-rt33]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::33/128
+            ipv6 router isis 1
+           !
+           interface eth-rt22
+            ipv6 address 4000:115::33/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt23
+            ipv6 address 4000:116::33/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt32
+            ipv6 address 4000:119::33/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt34
+            ipv6 address 4000:120::33/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-sw1
+            ipv6 address 4000:121::33/64
+            ipv6 router isis 1
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0033.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt34:
+       links:
+         eth-rt23:
+           peer: [rt23, eth-rt34]
+         eth-rt33:
+           peer: [rt33, eth-rt34]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::34/128
+            ipv6 router isis 1
+           !
+           interface eth-rt23
+            ipv6 address 4000:117::34/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt33
+            ipv6 address 4000:120::34/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0034.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+     rt41:
+       links:
+         eth-sw1:
+           peer: [sw1, eth-rt41]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ipv6 address 5000::41/128
+            ipv6 router isis 1
+           !
+           interface eth-sw1
+            ipv6 address 4000:121::41/64
+            ipv6 router isis 1
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0041.00
+            is-type level-1
+            topology ipv6-unicast
+            ppr on
+           !
+
+   switches:
+     sw1:
+       links:
+         eth-rt32:
+           peer: [rt32, eth-sw1]
+         eth-rt33:
+           peer: [rt33, eth-sw1]
+         eth-rt41:
+           peer: [rt41, eth-sw1]
+
+   frr:
+     base-config: |
+       hostname %(node)
+       password 1
+       log file %(logdir)/%(node).log
+       log commands
+       !
+       debug zebra rib
+       debug isis ppr
+       debug isis events
+       debug isis route-events
+       debug isis spf-events
+       debug isis lsp-gen
+       !
+
+YANG
+^^^^
+
+PPR can also be configured using NETCONF, RESTCONF and gRPC based on the
+following YANG models: \*
+`frr-ppr.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-ppr.yang>`__
+\*
+`frr-isisd.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-isisd.yang>`__
+
+As an example, here’s R11 configuration in the XML format:
+
+.. code:: xml
+
+   <lib xmlns="http://frrouting.org/yang/interface">
+     <interface>
+       <name>lo-ppr</name>
+       <vrf>default</vrf>
+     </interface>
+     <interface>
+       <name>lo</name>
+       <vrf>default</vrf>
+       <isis xmlns="http://frrouting.org/yang/isisd">
+         <area-tag>1</area-tag>
+         <ipv6-routing>true</ipv6-routing>
+       </isis>
+     </interface>
+     <interface>
+       <name>eth-ce1</name>
+       <vrf>default</vrf>
+     </interface>
+     <interface>
+       <name>eth-rt12</name>
+       <vrf>default</vrf>
+       <isis xmlns="http://frrouting.org/yang/isisd">
+         <area-tag>1</area-tag>
+         <ipv6-routing>true</ipv6-routing>
+         <hello>
+           <multiplier>
+             <level-1>3</level-1>
+             <level-2>3</level-2>
+           </multiplier>
+         </hello>
+         <network-type>point-to-point</network-type>
+       </isis>
+     </interface>
+     <interface>
+       <name>eth-rt21</name>
+       <vrf>default</vrf>
+       <isis xmlns="http://frrouting.org/yang/isisd">
+         <area-tag>1</area-tag>
+         <ipv6-routing>true</ipv6-routing>
+         <hello>
+           <multiplier>
+             <level-1>3</level-1>
+             <level-2>3</level-2>
+           </multiplier>
+         </hello>
+         <network-type>point-to-point</network-type>
+       </isis>
+     </interface>
+   </lib>
+   <ppr xmlns="http://frrouting.org/yang/ppr">
+     <group>
+       <name>VOIP</name>
+       <ipv6>
+         <ppr-id>6000:1::1/128</ppr-id>
+         <ppr-prefix>5000::11/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>5000::14/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>    
+         </ppr-pde>                        
+         <ppr-pde>                      
+           <pde-id>5000::23/128</pde-id>       
+           <pde-id-type>ipv6-node</pde-id-type>  
+           <pde-type>topological</pde-type>
+         </ppr-pde>          
+         <ppr-pde>                                           
+           <pde-id>5000::22/128</pde-id>       
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>                    
+         <ppr-pde>                            
+           <pde-id>5000::21/128</pde-id>       
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>    
+         </ppr-pde>                        
+         <ppr-pde>                         
+           <pde-id>5000::11/128</pde-id>            
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>    
+         </ppr-pde>                        
+         <attributes>                   
+           <ppr-metric>50</ppr-metric>         
+         </attributes>                     
+       </ipv6>
+       <ipv6>                                  
+         <ppr-id>6000:2::1/128</ppr-id>
+         <ppr-prefix>5000::14/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>5000::11/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::21/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::22/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::23/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::14/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <attributes>
+           <ppr-metric>50</ppr-metric>
+         </attributes>
+       </ipv6>
+     </group>
+     <group>
+       <name>INTERFACE_PDES</name>
+       <ipv6>
+         <ppr-id>6000:1::2/128</ppr-id>
+         <ppr-prefix>5000::11/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>5000::14/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::23/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::33/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>4000:121::41/64</pde-id>
+           <pde-id-type>ipv6-interface</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::32/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>4000:113::21/64</pde-id>
+           <pde-id-type>ipv6-interface</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::11/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+       </ipv6>
+       <ipv6>
+         <ppr-id>6000:2::2/128</ppr-id>
+         <ppr-prefix>5000::14/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>5000::11/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::21/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::32/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>4000:121::41/64</pde-id>
+           <pde-id-type>ipv6-interface</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::33/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>4000:116::23/64</pde-id>
+           <pde-id-type>ipv6-interface</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::14/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+       </ipv6>
+     </group>
+     <group>
+       <name>BROKEN</name>
+       <ipv6>
+         <ppr-id>6000:1::3/128</ppr-id>
+         <ppr-prefix>5000::11/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>5000::14/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::23/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::99/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::21/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::11/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <attributes>
+           <ppr-metric>1500</ppr-metric>
+         </attributes>
+       </ipv6>
+       <ipv6>
+         <ppr-id>6000:2::3/128</ppr-id>
+         <ppr-prefix>5000::14/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>5000::11/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::21/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::99/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::23/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::14/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <attributes>
+           <ppr-metric>1500</ppr-metric>
+         </attributes>
+       </ipv6>
+     </group>
+   </ppr>
+   <isis xmlns="http://frrouting.org/yang/isisd">
+     <instance>
+       <area-tag>1</area-tag>
+       <area-address>49.0000.0000.0000.0011.00</area-address>
+       <multi-topology>
+         <ipv6-unicast>
+         </ipv6-unicast>
+       </multi-topology>
+       <ppr>
+         <enable>true</enable>
+         <ppr-advertise>
+           <name>VOIP</name>
+         </ppr-advertise>
+         <ppr-advertise>
+           <name>INTERFACE_PDES</name>
+         </ppr-advertise>
+         <ppr-advertise>
+           <name>BROKEN</name>
+         </ppr-advertise>
+       </ppr>
+     </instance>
+   </isis>
+
+Verification - Control Plane
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Verify that R11 has flooded the PPR TLVs correctly to all IS-IS routers:
+
+::
+
+   # show isis database detail 0000.0000.0011
+   Area 1:
+   IS-IS Level-1 link-state database:
+   LSP ID                  PduLen  SeqNumber   Chksum  Holdtime  ATT/P/OL
+   debian.00-00             1233   0x00000009  0x7bd4     683    0/0/0
+     Protocols Supported: IPv4, IPv6
+     Area Address: 49.0000
+     MT Router Info: ipv4-unicast
+     MT Router Info: ipv6-unicast
+     Hostname: debian
+     MT Reachability: 0000.0000.0012.00 (Metric: 10) ipv6-unicast
+     MT Reachability: 0000.0000.0021.00 (Metric: 10) ipv6-unicast
+     MT IPv6 Reachability: 5000::11/128 (Metric: 10) ipv6-unicast
+     MT IPv6 Reachability: 4000:101::/64 (Metric: 10) ipv6-unicast
+     MT IPv6 Reachability: 4000:104::/64 (Metric: 10) ipv6-unicast
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::11/128
+       ID: 6000:1::3/128 (Native IPv6)
+       PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::99/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0
+       Metric: 1500
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::14/128
+       ID: 6000:2::3/128 (Native IPv6)
+       PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::99/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0
+       Metric: 1500
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::11/128
+       ID: 6000:1::2/128 (Native IPv6)
+       PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::33/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 4000:121::41 (IPv6 Interface Address), L:0 N:0 E:0
+       PDE: 5000::32/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 4000:113::21 (IPv6 Interface Address), L:0 N:0 E:0
+       PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0
+       Metric: 0
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::14/128
+       ID: 6000:2::2/128 (Native IPv6)
+       PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::32/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 4000:121::41 (IPv6 Interface Address), L:0 N:0 E:0
+       PDE: 5000::33/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 4000:116::23 (IPv6 Interface Address), L:0 N:0 E:0
+       PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0
+       Metric: 0
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::11/128
+       ID: 6000:1::1/128 (Native IPv6)
+       PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0
+       Metric: 50
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::14/128
+       ID: 6000:2::1/128 (Native IPv6)
+       PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0
+       Metric: 50
+
+The PPR TLVs can also be seen using a modified version of Wireshark as
+seen below:
+
+.. figure:: https://user-images.githubusercontent.com/931662/61582441-9551e500-ab01-11e9-8f6f-400ee3fba927.png
+   :alt: s2
+
+   s2
+
+Using the ``show isis ppr`` command, verify that all routers installed
+the PPR-IDs for the paths they are part of. Example:
+
+Router RT11
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime    
+    --------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Tail-End  -       -         
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Tail-End  -       -         
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Tail-End  -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Head-End  Up      00:45:41  
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Head-End  Up      00:45:41  
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Head-End  Up      00:45:41  
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:2::1/128 [115/50] via fe80::c2a:54ff:fe39:bff7, eth-rt21, 00:01:33
+   I>* 6000:2::2/128 [115/0] via fe80::c2a:54ff:fe39:bff7, eth-rt21, 00:01:33
+   I>* 6000:2::3/128 [115/1500] via fe80::c2a:54ff:fe39:bff7, eth-rt21, 00:01:33
+
+Router RT12
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime  
+    ------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path  -       -       
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Off-Path  -       -       
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Off-Path  -       -       
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path  -       -       
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Off-Path  -       -       
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Off-Path  -       -       
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT13
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime  
+    ------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path  -       -       
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Off-Path  -       -       
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Off-Path  -       -       
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path  -       -       
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Off-Path  -       -       
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Off-Path  -       -       
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT14
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime    
+    --------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Head-End  Up      00:45:45  
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Head-End  Up      00:45:45  
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Head-End  Up      00:45:45  
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Tail-End  -       -         
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Tail-End  -       -         
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Tail-End  -       -         
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::1/128 [115/50] via fe80::58ea:78ff:fe00:92c1, eth-rt23, 00:01:36
+   I>* 6000:1::2/128 [115/0] via fe80::58ea:78ff:fe00:92c1, eth-rt23, 00:01:36
+   I>* 6000:1::3/128 [115/1500] via fe80::58ea:78ff:fe00:92c1, eth-rt23, 00:01:36
+
+Router RT21
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Mid-Point  Up      00:45:46  
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Mid-Point  Up      00:45:46  
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Mid-Point  Up      00:45:46  
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Mid-Point  Up      00:45:46  
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Mid-Point  Up      00:45:46  
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Mid-Point  Down    -         
+
+   # show isis ppr id ipv6 6000:2::3/128 detail
+   Area 1:
+     PPR-ID: 6000:2::3/128 (Native IPv6)
+       PPR-Prefix: 5000::14/128
+       PDEs:
+         5000::11/128 (IPv6 Node Address)
+         5000::21/128 (IPv6 Node Address) [LOCAL]
+         5000::99/128 (IPv6 Node Address) [NEXT]
+         5000::23/128 (IPv6 Node Address)
+         5000::14/128 (IPv6 Node Address)
+       Attributes:
+         Metric: 1500
+       Position: Mid-Point
+       Originator: 0000.0000.0011
+       Level: L1
+       Algorithm: 1
+       MT-ID: ipv4-unicast
+       Status: Down: PDE is unreachable
+       Last change: 00:00:37
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::1/128 [115/50] via fe80::142e:79ff:feeb:cffc, eth-rt11, 00:01:38
+   I>* 6000:1::2/128 [115/0] via fe80::142e:79ff:feeb:cffc, eth-rt11, 00:01:38
+   I>* 6000:1::3/128 [115/1500] via fe80::142e:79ff:feeb:cffc, eth-rt11, 00:01:38
+   I>* 6000:2::1/128 [115/50] via fe80::c88e:7fff:fe5f:a08d, eth-rt22, 00:01:38
+   I>* 6000:2::2/128 [115/0] via fe80::8b2:9eff:fe98:f66a, eth-rt32, 00:01:38
+
+Router RT22
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Mid-Point  Up      00:45:47  
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Off-Path   -       -         
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Off-Path   -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Mid-Point  Up      00:45:47  
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Off-Path   -       -         
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Off-Path   -       -         
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::1/128 [115/50] via fe80::2cb5:edff:fe60:29b1, eth-rt21, 00:01:38
+   I>* 6000:2::1/128 [115/50] via fe80::e8d9:63ff:fea3:177b, eth-rt23, 00:01:38
+
+Router RT23
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Mid-Point  Up      00:45:49  
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Mid-Point  Up      00:45:49  
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Mid-Point  Down    -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Mid-Point  Up      00:45:49  
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Mid-Point  Up      00:45:49  
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Mid-Point  Up      00:45:49  
+
+   # show isis ppr id ipv6 6000:1::3/128 detail
+   Area 1:
+     PPR-ID: 6000:1::3/128 (Native IPv6)
+       PPR-Prefix: 5000::11/128
+       PDEs:
+         5000::14/128 (IPv6 Node Address)
+         5000::23/128 (IPv6 Node Address) [LOCAL]
+         5000::99/128 (IPv6 Node Address) [NEXT]
+         5000::21/128 (IPv6 Node Address)
+         5000::11/128 (IPv6 Node Address)
+       Attributes:
+         Metric: 1500
+       Position: Mid-Point
+       Originator: 0000.0000.0011
+       Level: L1
+       Algorithm: 1
+       MT-ID: ipv4-unicast
+       Status: Down: PDE is unreachable
+       Last change: 00:02:50
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::1/128 [115/50] via fe80::d09f:1bff:fe31:e9c9, eth-rt22, 00:01:40
+   I>* 6000:1::2/128 [115/0] via fe80::c0c3:b3ff:fe9f:b5d3, eth-rt33, 00:01:40
+   I>* 6000:2::1/128 [115/50] via fe80::f40a:66ff:fefc:5c32, eth-rt14, 00:01:40
+   I>* 6000:2::2/128 [115/0] via fe80::f40a:66ff:fefc:5c32, eth-rt14, 00:01:40
+   I>* 6000:2::3/128 [115/1500] via fe80::f40a:66ff:fefc:5c32, eth-rt14, 00:01:40
+
+Router RT31
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime  
+    ------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path  -       -       
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Off-Path  -       -       
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Off-Path  -       -       
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path  -       -       
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Off-Path  -       -       
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Off-Path  -       -       
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT32
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path   -       -         
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Mid-Point  Up      00:45:51  
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Off-Path   -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path   -       -         
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Mid-Point  Up      00:45:51  
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Off-Path   -       -         
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::2/128 [115/0] via 4000:113::21, eth-rt21, 00:01:42
+   I>* 6000:2::2/128 [115/0] via 4000:121::41, eth-sw1, 00:01:42
+
+Router RT33
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path   -       -         
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Mid-Point  Up      00:45:52  
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Off-Path   -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path   -       -         
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Mid-Point  Up      00:45:52  
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Off-Path   -       -         
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::2/128 [115/0] via 4000:121::41, eth-sw1, 00:01:43
+   I>* 6000:2::2/128 [115/0] via 4000:116::23, eth-rt23, 00:01:43
+
+Router RT34
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime  
+    ------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path  -       -       
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Off-Path  -       -       
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Off-Path  -       -       
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path  -       -       
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Off-Path  -       -       
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Off-Path  -       -       
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT41
+'''''''''''
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path   -       -         
+    1     L1     6000:1::2/128 (Native IPv6)  5000::11/128  0       Mid-Point  Up      00:45:55  
+    1     L1     6000:1::3/128 (Native IPv6)  5000::11/128  1500    Off-Path   -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path   -       -         
+    1     L1     6000:2::2/128 (Native IPv6)  5000::14/128  0       Mid-Point  Up      00:45:55  
+    1     L1     6000:2::3/128 (Native IPv6)  5000::14/128  1500    Off-Path   -       -         
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::2/128 [115/0] via fe80::b4b9:60ff:feee:3c73, eth-sw1, 00:01:46
+   I>* 6000:2::2/128 [115/0] via fe80::bc2a:d9ff:fe65:97f2, eth-sw1, 00:01:46
+
+As it can be seen by the output of ``show isis ppr id ipv6 ... detail``,
+routers R21 and R23 couldn’t install the third PPR path because of an
+unreachable PDE (configuration error).
+
+Verification - Forwarding Plane
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+On Router R11, use the ``traceroute`` tool to ensure that the PPR paths
+were installed correctly in the network:
+
+::
+
+   root@rt11:~# traceroute 6000:2::1
+   traceroute to 6000:2::1 (6000:2::1), 30 hops max, 80 byte packets
+    1  4000:104::21 (4000:104::21)  0.612 ms  0.221 ms  0.241 ms
+    2  4000:110::22 (4000:110::22)  0.257 ms  0.113 ms  0.105 ms
+    3  4000:111::23 (4000:111::23)  0.257 ms  0.151 ms  0.098 ms
+    4  6000:2::1 (6000:2::1)  0.346 ms  0.139 ms  0.100 ms
+   root@rt11:~#
+   root@rt11:~# traceroute 6000:2::2
+   traceroute to 6000:2::2 (6000:2::2), 30 hops max, 80 byte packets
+    1  4000:104::21 (4000:104::21)  4.383 ms  4.148 ms  0.044 ms
+    2  4000:113::32 (4000:113::32)  0.272 ms  0.065 ms  0.064 ms
+    3  4000:121::41 (4000:121::41)  0.263 ms  0.101 ms  0.086 ms
+    4  4000:115::33 (4000:115::33)  0.351 ms 4000:119::33 (4000:119::33)  0.249 ms 4000:115::33 (4000:115::33)  0.153 ms
+    5  4000:111::23 (4000:111::23)  0.232 ms  0.293 ms  0.131 ms
+    6  6000:2::2 (6000:2::2)  0.184 ms  0.212 ms  0.140 ms
+   root@rt11:~#
+   root@rt11:~# traceroute 6000:2::3
+   traceroute to 6000:2::3 (6000:2::3), 30 hops max, 80 byte packets
+    1  4000:104::21 (4000:104::21)  1.537 ms !N  1.347 ms !N  1.075 ms !N
+
+The failure on the third traceroute is expected since the 6000:2::3
+PPR-ID is misconfigured.
+
+Now ping Host 3 from Host 1 and use tcpdump or wireshark to verify that
+the ICMP packets are being tunneled using GRE and following the {R11 -
+R21 - R22 - R23 - R14} path. Here’s a wireshark capture between R11 and
+R21:
+
+.. figure:: https://user-images.githubusercontent.com/931662/61582398-d4cc0180-ab00-11e9-83a8-d219f98010b9.png
+   :alt: s1
+
+   s1
+
+Using ``traceroute`` it’s also possible to see that the ICMP packets are
+being tunneled through the IS-IS network:
+
+::
+
+   root@host1:~# traceroute fd00:20:1::1 -s fd00:10:1::1                                                                                                                                                                                        
+   traceroute to fd00:20:1::1 (fd00:20:1::1), 30 hops max, 80 byte packets
+    1  fd00:10:1::100 (fd00:10:1::100)  0.354 ms  0.092 ms  0.031 ms
+    2  fd00:10::11 (fd00:10::11)  0.125 ms  0.022 ms  0.026 ms
+    3  * * *
+    4  * * *
+    5  fd00:20:1::1 (fd00:20:1::1)  0.235 ms  0.106 ms  0.091 ms
diff --git a/doc/developer/northbound/ppr-mpls-basic-test-topology.rst b/doc/developer/northbound/ppr-mpls-basic-test-topology.rst
new file mode 100644 (file)
index 0000000..cedb795
--- /dev/null
@@ -0,0 +1,1991 @@
+Table of Contents
+~~~~~~~~~~~~~~~~~
+
+-  `Software <#software>`__
+-  `Topology <#topology>`__
+-  `Configuration <#configuration>`__
+
+   -  `CLI <#configuration-cli>`__
+   -  `YANG <#configuration-yang>`__
+
+-  `Verification - Control Plane <#verification-cplane>`__
+-  `Verification - Forwarding Plane <#verification-fplane>`__
+
+Software
+~~~~~~~~
+
+The FRR PPR implementation for IS-IS is available here:
+https://github.com/opensourcerouting/frr/tree/isisd-ppr-sr
+
+Topology
+~~~~~~~~
+
+In this topology we have an IS-IS network consisting of 12 routers. CE1
+and CE2 are the consumer edges, connected to R11 and R14, respectively.
+Three hosts are connected to the CEs using only static routes.
+
+Router R11 advertises 6 PPR TLVs: \* **IPv6 prefixes 6000:1::1/128 and
+6000:2::1/128:** {R11 - R21 - R22 - R23 - R14} (IPv6 Node Addresses). \*
+**MPLS SR Prefix-SIDs 500 and 501:** {R11 - R21 - R22 - R23 - R14} (SR
+Prefix-SIDs). \* **MPLS SR Prefix-SIDs 502 and 503:** {R11 - R21 - R31 -
+R32 - R41 - R33 - R34 - R23 - R14} (SR Prefix-SIDs)
+
+PBR rules are configured on R11 and R14 to route the traffic between
+Host 1 and Host 3 using the first PPR tunnel, whereas all other traffic
+between CE1 and CE2 uses the second PPR tunnel.
+
+Additional information: \* Addresses in the 4000::/16 range refer to
+interface addresses, where the last hextet corresponds to the node ID.
+\* Addresses in the 5000::/16 range refer to loopback addresses, where
+the last hextet corresponds to the node ID. \* Addresses in the
+6000::/16 range refer to PPR-ID addresses.
+
+::
+
+   +-------+       +-------+                                                 +-------+
+   |       |       |       |                                                 |       |
+   | HOST1 |       | HOST2 |                                                 | HOST3 |
+   |       |       |       |                                                 |       |
+   +---+---+       +---+---+                                                 +---+---+
+       |               |                                                         |
+       |fd00:10:1::/64 |                                                         |
+       +-----+  +------+                                           fd00:20:1::/64|
+             |  |fd00:10:2::/64                                                  |
+             |  |                                                                |
+           +-+--+--+                                                         +---+---+
+           |       |                                                         |       |
+           |  CE1  |                                                         |  CE2  |
+           |       |                                                         |       |
+           +---+---+                                                         +---+---+
+               |                                                                 |
+               |                                                                 |
+               |fd00:10:0::/64                                     fd00:20:0::/64|
+               |                                                                 |
+               |                                                                 |
+           +---+---+             +-------+             +-------+             +---+---+
+           |       |4000:101::/64|       |4000:102::/64|       |4000:103::/64|       |
+           |  R11  +-------------+  R12  +-------------+  R13  +-------------+  R14  |
+           |       |             |       |             |       |             |       |
+           +---+---+             +--+-+--+             +--+-+--+             +---+---+
+               |                    | |                   | |                    |
+               |4000:104::/64       | |4000:106::/64      | |4000:108::/64       |
+               +---------+ +--------+ +--------+ +--------+ +--------+ +---------+
+                         | |4000:105::/64      | |4000:107::/64      | |4000:109::/64
+                         | |                   | |                   | |
+                      +--+-+--+             +--+-+--+             +--+-+--+
+                      |       |4000:110::/64|       |4000:111::/64|       |
+                      |  R21  +-------------+  R22  +-------------+  R23  |
+                      |       |             |       |             |       |
+                      +--+-+--+             +--+-+--+             +--+-+--+
+                         | |                   | |                   | |
+                         | |4000:113::/64      | |4000:115::/64      | |4000:117::/64
+               +---------+ +--------+ +--------+ +--------+ +--------+ +---------+
+               |4000:112::/64       | |4000:114::/64      | |4000:116::/64       |
+               |                    | |                   | |                    |
+           +---+---+             +--+-+--+             +--+-+--+             +---+---+
+           |       |4000:118::/64|       |4000:119::/64|       |4000:120::/64|       |
+           |  R31  +-------------+  R32  +-------------+  R33  +-------------+  R34  |
+           |       |             |       |             |       |             |       |
+           +-------+             +---+---+             +---+---+             +-------+
+                                     |                     |
+                                     |4000:121::/64        |
+                                     +----------+----------+
+                                                |
+                                                |
+                                            +---+---+
+                                            |       |
+                                            |  R41  |
+                                            |       |
+                                            +-------+
+
+Configuration
+~~~~~~~~~~~~~
+
+PPR TLV processing needs to be enabled on all IS-IS routers using the
+``ppr on`` command. The advertisements of all PPR TLVs is done by router
+R11.
+
+CLI configuration
+^^^^^^^^^^^^^^^^^
+
+.. code:: yaml
+
+   ---
+
+   routers:
+
+     host1:
+       links:
+         eth-ce1:
+           peer: [ce1, eth-host1]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-ce1
+            ipv6 address fd00:10:1::1/64
+           !
+           ipv6 route ::/0 fd00:10:1::100
+
+     host2:
+       links:
+         eth-ce1:
+           peer: [ce1, eth-host2]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-ce1
+            ipv6 address fd00:10:2::1/64
+           !
+           ipv6 route ::/0 fd00:10:2::100
+
+     host3:
+       links:
+         eth-ce2:
+           peer: [ce2, eth-host3]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-ce2
+            ipv6 address fd00:20:1::1/64
+           !
+           ipv6 route ::/0 fd00:20:1::100
+
+     ce1:
+       links:
+         eth-host1:
+           peer: [host1, eth-ce1]
+         eth-host2:
+           peer: [host2, eth-ce1]
+         eth-rt11:
+           peer: [rt11, eth-ce1]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-host1
+            ipv6 address fd00:10:1::100/64
+           !
+           interface eth-host2
+            ipv6 address fd00:10:2::100/64
+           !
+           interface eth-rt11
+            ipv6 address fd00:10:0::100/64
+           !
+           ipv6 route ::/0 fd00:10:0::11 label 16501
+
+     ce2:
+       links:
+         eth-host3:
+           peer: [host3, eth-ce2]
+         eth-rt14:
+           peer: [rt14, eth-ce2]
+       frr:
+         zebra:
+         staticd:
+         config: |
+           interface eth-host3
+            ipv6 address fd00:20:1::100/64
+           !
+           interface eth-rt14
+            ipv6 address fd00:20:0::100/64
+           !
+           ipv6 route ::/0 fd00:20:0::14 label 16500
+
+     rt11:
+       links:
+         lo:
+           mpls: yes
+         lo-ppr:
+         eth-ce1:
+           peer: [ce1, eth-rt11]
+           mpls: yes
+         eth-rt12:
+           peer: [rt12, eth-rt11]
+           mpls: yes
+         eth-rt21:
+           peer: [rt21, eth-rt11]
+           mpls: yes
+       shell: |
+         # GRE tunnel for preferred packets (PPR)
+         ip -6 tunnel add tun-ppr mode ip6gre remote 6000:2::1 local 6000:1::1 ttl 64
+         ip link set dev tun-ppr up
+         # PBR rules
+         ip -6 rule add from fd00:10:1::/64 to fd00:20:1::/64 iif eth-ce1 lookup 10000
+         ip -6 route add default dev tun-ppr table 10000
+       frr:
+         zebra:
+         staticd:
+         isisd:
+         config: |
+           interface lo-ppr
+            ipv6 address 6000:1::1/128
+           !
+           interface lo
+            ip address 10.0.0.11/32
+            ipv6 address 5000::11/128
+            ipv6 router isis 1
+           !
+           interface eth-ce1
+            ipv6 address fd00:10:0::11/64
+           !
+           interface eth-rt12
+            ipv6 address 4000:101::11/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt21
+            ipv6 address 4000:104::11/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           ipv6 route fd00:10::/32 fd00:10:0::100
+           !
+           ppr group PPR_IPV6
+            ppr ipv6 6000:1::1/128 prefix 5000::11/128 metric 50
+             pde ipv6-node 5000::14/128
+             pde ipv6-node 5000::23/128
+             pde ipv6-node 5000::22/128
+             pde ipv6-node 5000::21/128
+             pde ipv6-node 5000::11/128
+            !
+            ppr ipv6 6000:2::1/128 prefix 5000::14/128 metric 50
+             pde ipv6-node 5000::11/128
+             pde ipv6-node 5000::21/128
+             pde ipv6-node 5000::22/128
+             pde ipv6-node 5000::23/128
+             pde ipv6-node 5000::14/128
+            !
+           !
+           ppr group PPR_MPLS_1
+            ppr mpls 500 prefix 5000::11/128
+             pde prefix-sid 14
+             pde prefix-sid 23
+             pde prefix-sid 22
+             pde prefix-sid 21
+             pde prefix-sid 11
+            !
+            ppr mpls 501 prefix 5000::14/128
+             pde prefix-sid 11
+             pde prefix-sid 21
+             pde prefix-sid 22
+             pde prefix-sid 23
+             pde prefix-sid 14
+            !
+           !
+           ppr group PPR_MPLS_2
+            ppr mpls 502 prefix 5000::11/128
+             pde prefix-sid 14
+             pde prefix-sid 23
+             pde prefix-sid 34
+             pde prefix-sid 33
+             pde prefix-sid 41
+             pde prefix-sid 32
+             pde prefix-sid 31
+             pde prefix-sid 21
+             pde prefix-sid 11
+            !
+            ppr mpls 503 prefix 5000::14/128
+             pde prefix-sid 11
+             pde prefix-sid 21
+             pde prefix-sid 31
+             pde prefix-sid 32
+             pde prefix-sid 41
+             pde prefix-sid 33
+             pde prefix-sid 34
+             pde prefix-sid 23
+             pde prefix-sid 14
+            !
+           !
+           router isis 1
+            net 49.0000.0000.0000.0011.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::11/128 index 11 no-php-flag
+            ppr on
+            ppr advertise PPR_IPV6
+            ppr advertise PPR_MPLS_1
+            ppr advertise PPR_MPLS_2
+           !
+
+     rt12:
+       links:
+         lo:
+           mpls: yes
+         eth-rt11:
+           peer: [rt11, eth-rt12]
+           mpls: yes
+         eth-rt13:
+           peer: [rt13, eth-rt12]
+           mpls: yes
+         eth-rt21:
+           peer: [rt21, eth-rt12]
+           mpls: yes
+         eth-rt22:
+           peer: [rt22, eth-rt12]
+           mpls: yes
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.12/32
+            ipv6 address 5000::12/128
+            ipv6 router isis 1
+           !
+           interface eth-rt11
+            ipv6 address 4000:101::12/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt13
+            ipv6 address 4000:102::12/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt21
+            ipv6 address 4000:105::12/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:106::12/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0012.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::12/128 index 12 no-php-flag
+            ppr on
+           !
+
+     rt13:
+       links:
+         lo:
+           mpls: yes
+         eth-rt12:
+           peer: [rt12, eth-rt13]
+           mpls: yes
+         eth-rt14:
+           peer: [rt14, eth-rt13]
+           mpls: yes
+         eth-rt22:
+           peer: [rt22, eth-rt13]
+           mpls: yes
+         eth-rt23:
+           peer: [rt23, eth-rt13]
+           mpls: yes
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.13/32
+            ipv6 address 5000::13/128
+            ipv6 router isis 1
+           !
+           interface eth-rt12
+            ipv6 address 4000:102::13/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt14
+            ipv6 address 4000:103::13/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:107::13/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt23
+            ipv6 address 4000:108::13/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0013.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::13/128 index 13 no-php-flag
+            ppr on
+           !
+
+     rt14:
+       links:
+         lo:
+           mpls: yes
+         lo-ppr:
+         eth-ce2:
+           peer: [ce2, eth-rt14]
+           mpls: yes
+         eth-rt13:
+           peer: [rt13, eth-rt14]
+           mpls: yes
+         eth-rt23:
+           peer: [rt23, eth-rt14]
+           mpls: yes
+       shell: |
+         # GRE tunnel for preferred packets (PPR)
+         ip -6 tunnel add tun-ppr mode ip6gre remote 6000:1::1 local 6000:2::1 ttl 64
+         ip link set dev tun-ppr up
+         # PBR rules
+         ip -6 rule add from fd00:20:1::/64 to fd00:10:1::/64 iif eth-ce2 lookup 10000
+         ip -6 route add default dev tun-ppr table 10000
+       frr:
+         zebra:
+         staticd:
+         isisd:
+         config: |
+           interface lo-ppr
+            ipv6 address 6000:2::1/128
+           !
+           interface lo
+            ip address 10.0.0.14/32
+            ipv6 address 5000::14/128
+            ipv6 router isis 1
+           !
+           interface eth-ce2
+            ipv6 address fd00:20:0::14/64
+           !
+           interface eth-rt13
+            ipv6 address 4000:103::14/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt23
+            ipv6 address 4000:109::14/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           ipv6 route fd00:20::/32 fd00:20:0::100
+           !
+           router isis 1
+            net 49.0000.0000.0000.0014.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::14/128 index 14 no-php-flag
+            ppr on
+           !
+
+     rt21:
+       links:
+         lo:
+           mpls: yes
+         eth-rt11:
+           peer: [rt11, eth-rt21]
+           mpls: yes
+         eth-rt12:
+           peer: [rt12, eth-rt21]
+           mpls: yes
+         eth-rt22:
+           peer: [rt22, eth-rt21]
+           mpls: yes
+         eth-rt31:
+           peer: [rt31, eth-rt21]
+           mpls: yes
+         eth-rt32:
+           peer: [rt32, eth-rt21]
+           mpls: yes
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.21/32
+            ipv6 address 5000::21/128
+            ipv6 router isis 1
+           !
+           interface eth-rt11
+            ipv6 address 4000:104::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt12
+            ipv6 address 4000:105::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:110::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt31
+            ipv6 address 4000:112::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt32
+            ipv6 address 4000:113::21/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0021.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::21/128 index 21 no-php-flag
+            ppr on
+           !
+
+     rt22:
+       links:
+         lo:
+           mpls: yes
+         eth-rt12:
+           peer: [rt12, eth-rt22]
+           mpls: yes
+         eth-rt13:
+           peer: [rt13, eth-rt22]
+           mpls: yes
+         eth-rt21:
+           peer: [rt21, eth-rt22]
+           mpls: yes
+         eth-rt23:
+           peer: [rt23, eth-rt22]
+           mpls: yes
+         eth-rt32:
+           peer: [rt32, eth-rt22]
+           mpls: yes
+         eth-rt33:
+           mpls: yes
+           peer: [rt33, eth-rt22]
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.22/32
+            ipv6 address 5000::22/128
+            ipv6 router isis 1
+           !
+           interface eth-rt12
+            ipv6 address 4000:106::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt13
+            ipv6 address 4000:107::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt21
+            ipv6 address 4000:110::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt23
+            ipv6 address 4000:111::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt32
+            ipv6 address 4000:114::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt33
+            ipv6 address 4000:115::22/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0022.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::22/128 index 22 no-php-flag
+            ppr on
+           !
+
+     rt23:
+       links:
+         lo:
+           mpls: yes
+         eth-rt13:
+           peer: [rt13, eth-rt23]
+           mpls: yes
+         eth-rt14:
+           peer: [rt14, eth-rt23]
+           mpls: yes
+         eth-rt22:
+           peer: [rt22, eth-rt23]
+           mpls: yes
+         eth-rt33:
+           peer: [rt33, eth-rt23]
+           mpls: yes
+         eth-rt34:
+           peer: [rt34, eth-rt23]
+           mpls: yes
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.23/32
+            ipv6 address 5000::23/128
+            ipv6 router isis 1
+           !
+           interface eth-rt13
+            ipv6 address 4000:108::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt14
+            ipv6 address 4000:109::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:111::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt33
+            ipv6 address 4000:116::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt34
+            ipv6 address 4000:117::23/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0023.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing global-block 20000 27999
+            segment-routing prefix 5000::23/128 index 23 no-php-flag
+            ppr on
+           !
+
+     rt31:
+       links:
+         lo:
+           mpls: yes
+         eth-rt21:
+           peer: [rt21, eth-rt31]
+           mpls: yes
+         eth-rt32:
+           peer: [rt32, eth-rt31]
+           mpls: yes
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.31/32
+            ipv6 address 5000::31/128
+            ipv6 router isis 1
+           !
+           interface eth-rt21
+            ipv6 address 4000:112::31/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt32
+            ipv6 address 4000:118::31/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0031.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::31/128 index 31 no-php-flag
+            ppr on
+           !
+
+     rt32:
+       links:
+         lo:
+           mpls: yes
+         eth-rt21:
+           peer: [rt21, eth-rt32]
+           mpls: yes
+         eth-rt22:
+           peer: [rt22, eth-rt32]
+           mpls: yes
+         eth-rt31:
+           peer: [rt31, eth-rt32]
+           mpls: yes
+         eth-rt33:
+           peer: [rt33, eth-rt32]
+           mpls: yes
+         eth-sw1:
+           peer: [sw1, eth-rt32]
+           mpls: yes
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.32/32
+            ipv6 address 5000::32/128
+            ipv6 router isis 1
+           !
+           interface eth-rt21
+            ipv6 address 4000:113::32/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt22
+            ipv6 address 4000:114::32/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt31
+            ipv6 address 4000:118::32/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt33
+            ipv6 address 4000:119::32/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-sw1
+            ipv6 address 4000:121::32/64
+            ipv6 router isis 1
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0032.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::32/128 index 32 no-php-flag
+            ppr on
+           !
+
+     rt33:
+       links:
+         lo:
+           mpls: yes
+         eth-rt22:
+           peer: [rt22, eth-rt33]
+           mpls: yes
+         eth-rt23:
+           peer: [rt23, eth-rt33]
+           mpls: yes
+         eth-rt32:
+           peer: [rt32, eth-rt33]
+           mpls: yes
+         eth-rt34:
+           peer: [rt34, eth-rt33]
+           mpls: yes
+         eth-sw1:
+           peer: [sw1, eth-rt33]
+           mpls: yes
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.33/32
+            ipv6 address 5000::33/128
+            ipv6 router isis 1
+           !
+           interface eth-rt22
+            ipv6 address 4000:115::33/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt23
+            ipv6 address 4000:116::33/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt32
+            ipv6 address 4000:119::33/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt34
+            ipv6 address 4000:120::33/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-sw1
+            ipv6 address 4000:121::33/64
+            ipv6 router isis 1
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0033.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::33/128 index 33 no-php-flag
+            ppr on
+           !
+
+     rt34:
+       links:
+         lo:
+           mpls: yes
+         eth-rt23:
+           peer: [rt23, eth-rt34]
+           mpls: yes
+         eth-rt33:
+           peer: [rt33, eth-rt34]
+           mpls: yes
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.34/32
+            ipv6 address 5000::34/128
+            ipv6 router isis 1
+           !
+           interface eth-rt23
+            ipv6 address 4000:117::34/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           interface eth-rt33
+            ipv6 address 4000:120::34/64
+            ipv6 router isis 1
+            isis network point-to-point
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0034.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::34/128 index 34 no-php-flag
+            ppr on
+           !
+
+     rt41:
+       links:
+         lo:
+           mpls: yes
+         eth-sw1:
+           peer: [sw1, eth-rt41]
+           mpls: yes
+       frr:
+         zebra:
+         isisd:
+         config: |
+           interface lo
+            ip address 10.0.0.41/32
+            ipv6 address 5000::41/128
+            ipv6 router isis 1
+           !
+           interface eth-sw1
+            ipv6 address 4000:121::41/64
+            ipv6 router isis 1
+            isis hello-multiplier 3
+           !
+           router isis 1
+            net 49.0000.0000.0000.0041.00
+            is-type level-1
+            topology ipv6-unicast
+            segment-routing on
+            segment-routing prefix 5000::41/128 index 41 no-php-flag
+            ppr on
+           !
+
+   switches:
+     sw1:
+       links:
+         eth-rt32:
+           peer: [rt32, eth-sw1]
+         eth-rt33:
+           peer: [rt33, eth-sw1]
+         eth-rt41:
+           peer: [rt41, eth-sw1]
+
+   frr:
+     #valgrind: yes
+     base-config: |
+       hostname %(node)
+       password 1
+       log file %(logdir)/%(node).log
+       log commands
+       !
+       debug zebra rib
+       debug isis sr-events
+       debug isis ppr
+       debug isis events
+       debug isis route-events
+       debug isis spf-events
+       debug isis lsp-gen
+       !
+
+..
+
+   NOTE: it’s of fundamental importance to enable MPLS processing on the
+   loopback interfaces, otherwise the tail-end routers of the PPR-MPLS
+   tunnels will drop the labeled packets they receive.
+
+YANG
+^^^^
+
+PPR can also be configured using NETCONF, RESTCONF and gRPC based on the
+following YANG models: \*
+`frr-ppr.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-ppr.yang>`__
+\*
+`frr-isisd.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-isisd.yang>`__
+
+As an example, here’s R11 configuration in the XML format:
+
+.. code:: xml
+
+   <lib xmlns="http://frrouting.org/yang/interface">
+     <interface>
+       <name>lo-ppr</name>
+       <vrf>default</vrf>
+     </interface>
+     <interface>
+       <name>lo</name>
+       <vrf>default</vrf>
+       <isis xmlns="http://frrouting.org/yang/isisd">
+         <area-tag>1</area-tag>
+         <ipv6-routing>true</ipv6-routing>
+       </isis>
+     </interface>
+     <interface>
+       <name>eth-ce1</name>
+       <vrf>default</vrf>
+     </interface>
+     <interface>
+       <name>eth-rt12</name>
+       <vrf>default</vrf>
+       <isis xmlns="http://frrouting.org/yang/isisd">
+         <area-tag>1</area-tag>
+         <ipv6-routing>true</ipv6-routing>
+         <hello>
+           <multiplier>
+             <level-1>3</level-1>
+             <level-2>3</level-2>
+           </multiplier>
+         </hello>
+         <network-type>point-to-point</network-type>
+       </isis>
+     </interface>
+     <interface>
+       <name>eth-rt21</name>
+       <vrf>default</vrf>
+       <isis xmlns="http://frrouting.org/yang/isisd">
+         <area-tag>1</area-tag>
+         <ipv6-routing>true</ipv6-routing>
+         <hello>
+           <multiplier>
+             <level-1>3</level-1>
+             <level-2>3</level-2>
+           </multiplier>
+         </hello>
+         <network-type>point-to-point</network-type>
+       </isis>
+     </interface>
+   </lib>
+   <ppr xmlns="http://frrouting.org/yang/ppr">
+     <group>                                    
+       <name>PPR_IPV6</name>                    
+       <ipv6>                                   
+         <ppr-id>6000:1::1/128</ppr-id>        
+         <ppr-prefix>5000::11/128</ppr-prefix>
+         <ppr-pde>                              
+           <pde-id>5000::14/128</pde-id>        
+           <pde-id-type>ipv6-node</pde-id-type> 
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::23/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::22/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::21/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::11/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <attributes>
+           <ppr-metric>50</ppr-metric>
+         </attributes>
+       </ipv6>
+       <ipv6>
+         <ppr-id>6000:2::1/128</ppr-id>
+         <ppr-prefix>5000::14/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>5000::11/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::21/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::22/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::23/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>5000::14/128</pde-id>
+           <pde-id-type>ipv6-node</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <attributes>
+           <ppr-metric>50</ppr-metric>
+         </attributes>
+       </ipv6>
+     </group>
+     <group>
+       <name>PPR_MPLS_1</name>
+       <mpls>
+         <ppr-id>500</ppr-id>
+         <ppr-prefix>5000::11/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>14</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>23</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>22</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>21</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>11</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+       </mpls>
+       <mpls>
+         <ppr-id>501</ppr-id>
+         <ppr-prefix>5000::14/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>11</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>21</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>22</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>23</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>14</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+       </mpls>
+     </group>
+     <group>
+       <name>PPR_MPLS_2</name>
+       <mpls>
+         <ppr-id>502</ppr-id>
+         <ppr-prefix>5000::11/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>14</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>23</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>34</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>33</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>41</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>32</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>31</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>21</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>11</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+       </mpls>
+       <mpls>
+         <ppr-id>503</ppr-id>
+         <ppr-prefix>5000::14/128</ppr-prefix>
+         <ppr-pde>
+           <pde-id>11</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>21</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>31</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>32</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>41</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>33</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>34</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>23</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+         <ppr-pde>
+           <pde-id>14</pde-id>
+           <pde-id-type>prefix-sid</pde-id-type>
+           <pde-type>topological</pde-type>
+         </ppr-pde>
+       </mpls>
+     </group>
+   </ppr>
+   <isis xmlns="http://frrouting.org/yang/isisd">
+     <instance>
+       <area-tag>1</area-tag>
+       <area-address>49.0000.0000.0000.0011.00</area-address>
+       <multi-topology>
+         <ipv6-unicast>
+         </ipv6-unicast>
+       </multi-topology>
+       <segment-routing>
+         <enabled>true</enabled>
+         <prefix-sid-map>
+           <prefix-sid>
+             <prefix>5000::11/128</prefix>
+             <sid-value>11</sid-value>
+             <last-hop-behavior>no-php</last-hop-behavior>
+           </prefix-sid>
+         </prefix-sid-map>
+       </segment-routing>
+       <ppr>
+         <enable>true</enable>
+         <ppr-advertise>
+           <name>PPR_IPV6</name>
+         </ppr-advertise>
+         <ppr-advertise>
+           <name>PPR_MPLS_1</name>
+         </ppr-advertise>
+         <ppr-advertise>
+           <name>PPR_MPLS_2</name>
+         </ppr-advertise>
+       </ppr>
+     </instance>
+   </isis>
+
+Verification - Control Plane
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Verify that R11 has flooded the PPR TLVs correctly to all IS-IS routers:
+
+::
+
+   # show isis database detail 0000.0000.0011
+   Area 1:
+   IS-IS Level-1 link-state database:
+   LSP ID                  PduLen  SeqNumber   Chksum  Holdtime  ATT/P/OL
+   debian.00-00         *    980   0x00000003  0x3b69     894    0/0/0
+     Protocols Supported: IPv4, IPv6
+     Area Address: 49.0000
+     MT Router Info: ipv4-unicast
+     MT Router Info: ipv6-unicast
+     Hostname: debian
+     TE Router ID: 10.0.0.11
+     Router Capability: 10.0.0.11 , D:0, S:0
+       Segment Routing: I:1 V:1, SRGB Base: 16000 Range: 8000
+         Algorithm: 0: SPF 0: Strict SPF
+     MT Reachability: 0000.0000.0012.00 (Metric: 10) ipv6-unicast
+       Adjacency-SID: 16, Weight: 0, Flags: F:1 B:0, V:1, L:1, S:0, P:0
+     MT Reachability: 0000.0000.0021.00 (Metric: 10) ipv6-unicast
+       Adjacency-SID: 17, Weight: 0, Flags: F:1 B:0, V:1, L:1, S:0, P:0
+     IPv4 Interface Address: 10.0.0.11
+     Extended IP Reachability: 10.0.0.11/32 (Metric: 10)
+     MT IPv6 Reachability: 5000::11/128 (Metric: 10) ipv6-unicast
+       Subtlvs:
+         SR Prefix-SID Index: 11, Algorithm: 0, Flags: NO-PHP
+     MT IPv6 Reachability: 4000:101::/64 (Metric: 10) ipv6-unicast
+     MT IPv6 Reachability: 4000:104::/64 (Metric: 10) ipv6-unicast
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::11/128
+       ID: 6000:1::1/128 (Native IPv6)
+       PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0
+       Metric: 50
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::14/128
+       ID: 6000:2::1/128 (Native IPv6)
+       PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0
+       PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0
+       Metric: 50
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::11/128
+       ID: 500 (MPLS)
+       PDE: 14 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 22 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 11 (SR-MPLS Prefix SID), L:0 N:1 E:0
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::14/128
+       ID: 501 (MPLS)
+       PDE: 11 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 22 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 14 (SR-MPLS Prefix SID), L:0 N:1 E:0
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::11/128
+       ID: 502 (MPLS)
+       PDE: 14 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 34 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 33 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 41 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 32 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 31 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 11 (SR-MPLS Prefix SID), L:0 N:1 E:0
+     PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1
+       PPR Prefix: 5000::14/128
+       ID: 503 (MPLS)
+       PDE: 11 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 31 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 32 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 41 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 33 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 34 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0
+       PDE: 14 (SR-MPLS Prefix SID), L:0 N:1 E:0
+
+Using the ``show isis ppr`` command, verify that all routers installed
+the PPR-IDs for the paths they are part of. Example:
+
+Router RT11
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime    
+    --------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Tail-End  Up      00:00:42  
+    1     L1     501 (MPLS)                   5000::14/128  0       Head-End  Up      00:00:41  
+    1     L1     502 (MPLS)                   5000::11/128  0       Tail-End  Up      00:00:42  
+    1     L1     503 (MPLS)                   5000::14/128  0       Head-End  Up      00:00:41  
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Tail-End  -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Head-End  Up      00:00:41  
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::2065:5ff:fe72:d6c5   implicit-null   
+    17             SR (IS-IS)   fe80::345f:dfff:fea4:913d  implicit-null   
+    16011          SR (IS-IS)   lo                         -               
+    16012          SR (IS-IS)   fe80::2065:5ff:fe72:d6c5   16012           
+    16013          SR (IS-IS)   fe80::2065:5ff:fe72:d6c5   16013           
+    16014          SR (IS-IS)   fe80::2065:5ff:fe72:d6c5   16014           
+    16021          SR (IS-IS)   fe80::345f:dfff:fea4:913d  16021           
+    16022          SR (IS-IS)   fe80::345f:dfff:fea4:913d  16022           
+    16022          SR (IS-IS)   fe80::2065:5ff:fe72:d6c5   16022           
+    16023          SR (IS-IS)   fe80::345f:dfff:fea4:913d  16023           
+    16023          SR (IS-IS)   fe80::2065:5ff:fe72:d6c5   16023           
+    16031          SR (IS-IS)   fe80::345f:dfff:fea4:913d  16031           
+    16032          SR (IS-IS)   fe80::345f:dfff:fea4:913d  16032           
+    16033          SR (IS-IS)   fe80::345f:dfff:fea4:913d  16033           
+    16033          SR (IS-IS)   fe80::2065:5ff:fe72:d6c5   16033           
+    16034          SR (IS-IS)   fe80::345f:dfff:fea4:913d  16034           
+    16034          SR (IS-IS)   fe80::2065:5ff:fe72:d6c5   16034           
+    16041          SR (IS-IS)   fe80::345f:dfff:fea4:913d  16041           
+    16500          PPR (IS-IS)  lo                         -               
+    16501          PPR (IS-IS)  fe80::345f:dfff:fea4:913d  16501           
+    16502          PPR (IS-IS)  lo                         -               
+    16503          PPR (IS-IS)  fe80::345f:dfff:fea4:913d  16503           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:2::1/128 [115/50] via fe80::345f:dfff:fea4:913d, eth-rt21, 00:00:41
+
+Router RT12
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime  
+    ------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Off-Path  -       -       
+    1     L1     501 (MPLS)                   5000::14/128  0       Off-Path  -       -       
+    1     L1     502 (MPLS)                   5000::11/128  0       Off-Path  -       -       
+    1     L1     503 (MPLS)                   5000::14/128  0       Off-Path  -       -       
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path  -       -       
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path  -       -       
+
+   # show mpls table
+    Inbound Label  Type        Nexthop                    Outbound Label  
+    ----------------------------------------------------------------------
+    16             SR (IS-IS)  fe80::60ad:96ff:fe3f:9989  implicit-null   
+    17             SR (IS-IS)  fe80::9cd2:25ff:febc:84c4  implicit-null   
+    18             SR (IS-IS)  fe80::941c:12ff:fe55:8a12  implicit-null   
+    19             SR (IS-IS)  fe80::78a7:59ff:fedc:48b8  implicit-null   
+    16011          SR (IS-IS)  fe80::60ad:96ff:fe3f:9989  16011           
+    16012          SR (IS-IS)  lo                         -               
+    16013          SR (IS-IS)  fe80::9cd2:25ff:febc:84c4  16013           
+    16014          SR (IS-IS)  fe80::9cd2:25ff:febc:84c4  16014           
+    16021          SR (IS-IS)  fe80::941c:12ff:fe55:8a12  16021           
+    16022          SR (IS-IS)  fe80::78a7:59ff:fedc:48b8  16022           
+    16023          SR (IS-IS)  fe80::78a7:59ff:fedc:48b8  16023           
+    16023          SR (IS-IS)  fe80::9cd2:25ff:febc:84c4  16023           
+    16031          SR (IS-IS)  fe80::941c:12ff:fe55:8a12  16031           
+    16032          SR (IS-IS)  fe80::78a7:59ff:fedc:48b8  16032           
+    16032          SR (IS-IS)  fe80::941c:12ff:fe55:8a12  16032           
+    16033          SR (IS-IS)  fe80::78a7:59ff:fedc:48b8  16033           
+    16034          SR (IS-IS)  fe80::78a7:59ff:fedc:48b8  16034           
+    16034          SR (IS-IS)  fe80::9cd2:25ff:febc:84c4  16034           
+    16041          SR (IS-IS)  fe80::78a7:59ff:fedc:48b8  16041           
+    16041          SR (IS-IS)  fe80::941c:12ff:fe55:8a12  16041           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT13
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime  
+    ------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Off-Path  -       -       
+    1     L1     501 (MPLS)                   5000::14/128  0       Off-Path  -       -       
+    1     L1     502 (MPLS)                   5000::11/128  0       Off-Path  -       -       
+    1     L1     503 (MPLS)                   5000::14/128  0       Off-Path  -       -       
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path  -       -       
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path  -       -       
+
+   # show mpls table
+    Inbound Label  Type        Nexthop                    Outbound Label  
+    ----------------------------------------------------------------------
+    16             SR (IS-IS)  fe80::1c70:63ff:fe40:3a35  implicit-null   
+    17             SR (IS-IS)  fe80::20:56ff:feff:b218    implicit-null   
+    18             SR (IS-IS)  fe80::44c5:3fff:fe1e:f34a  implicit-null   
+    19             SR (IS-IS)  fe80::387d:34ff:fe02:87c3  implicit-null   
+    16011          SR (IS-IS)  fe80::20:56ff:feff:b218    16011           
+    16012          SR (IS-IS)  fe80::20:56ff:feff:b218    16012           
+    16013          SR (IS-IS)  lo                         -               
+    16014          SR (IS-IS)  fe80::1c70:63ff:fe40:3a35  16014           
+    16021          SR (IS-IS)  fe80::387d:34ff:fe02:87c3  16021           
+    16021          SR (IS-IS)  fe80::20:56ff:feff:b218    16021           
+    16022          SR (IS-IS)  fe80::387d:34ff:fe02:87c3  16022           
+    16023          SR (IS-IS)  fe80::44c5:3fff:fe1e:f34a  20023           
+    16031          SR (IS-IS)  fe80::387d:34ff:fe02:87c3  16031           
+    16031          SR (IS-IS)  fe80::20:56ff:feff:b218    16031           
+    16032          SR (IS-IS)  fe80::387d:34ff:fe02:87c3  16032           
+    16033          SR (IS-IS)  fe80::44c5:3fff:fe1e:f34a  20033           
+    16033          SR (IS-IS)  fe80::387d:34ff:fe02:87c3  16033           
+    16034          SR (IS-IS)  fe80::44c5:3fff:fe1e:f34a  20034           
+    16041          SR (IS-IS)  fe80::44c5:3fff:fe1e:f34a  20041           
+    16041          SR (IS-IS)  fe80::387d:34ff:fe02:87c3  16041           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT14
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position  Status  Uptime    
+    --------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Head-End  Up      00:00:46  
+    1     L1     501 (MPLS)                   5000::14/128  0       Tail-End  Up      00:00:47  
+    1     L1     502 (MPLS)                   5000::11/128  0       Head-End  Up      00:00:46  
+    1     L1     503 (MPLS)                   5000::14/128  0       Tail-End  Up      00:00:47  
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Head-End  Up      00:00:46  
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Tail-End  -       -         
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::bcb5:99ff:fed7:22ad  implicit-null   
+    17             SR (IS-IS)   fe80::4c7b:a1ff:fe66:6ca7  implicit-null   
+    16011          SR (IS-IS)   fe80::bcb5:99ff:fed7:22ad  16011           
+    16012          SR (IS-IS)   fe80::bcb5:99ff:fed7:22ad  16012           
+    16013          SR (IS-IS)   fe80::bcb5:99ff:fed7:22ad  16013           
+    16014          SR (IS-IS)   lo                         -               
+    16021          SR (IS-IS)   fe80::4c7b:a1ff:fe66:6ca7  20021           
+    16021          SR (IS-IS)   fe80::bcb5:99ff:fed7:22ad  16021           
+    16022          SR (IS-IS)   fe80::4c7b:a1ff:fe66:6ca7  20022           
+    16022          SR (IS-IS)   fe80::bcb5:99ff:fed7:22ad  16022           
+    16023          SR (IS-IS)   fe80::4c7b:a1ff:fe66:6ca7  20023           
+    16031          SR (IS-IS)   fe80::4c7b:a1ff:fe66:6ca7  20031           
+    16031          SR (IS-IS)   fe80::bcb5:99ff:fed7:22ad  16031           
+    16032          SR (IS-IS)   fe80::4c7b:a1ff:fe66:6ca7  20032           
+    16032          SR (IS-IS)   fe80::bcb5:99ff:fed7:22ad  16032           
+    16033          SR (IS-IS)   fe80::4c7b:a1ff:fe66:6ca7  20033           
+    16034          SR (IS-IS)   fe80::4c7b:a1ff:fe66:6ca7  20034           
+    16041          SR (IS-IS)   fe80::4c7b:a1ff:fe66:6ca7  20041           
+    16500          PPR (IS-IS)  fe80::4c7b:a1ff:fe66:6ca7  20500           
+    16501          PPR (IS-IS)  lo                         -               
+    16502          PPR (IS-IS)  fe80::4c7b:a1ff:fe66:6ca7  20502           
+    16503          PPR (IS-IS)  lo                         -               
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::1/128 [115/50] via fe80::4c7b:a1ff:fe66:6ca7, eth-rt23, 00:00:02
+
+Router RT21
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:00:49  
+    1     L1     501 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:00:48  
+    1     L1     502 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:00:49  
+    1     L1     503 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:00:48  
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Mid-Point  Up      00:00:49  
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Mid-Point  Up      00:00:48  
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::b886:2cff:fe84:a76f  implicit-null   
+    17             SR (IS-IS)   fe80::bc7e:bbff:fe7f:ecb0  implicit-null   
+    18             SR (IS-IS)   fe80::e877:a2ff:feb7:4438  implicit-null   
+    19             SR (IS-IS)   fe80::a0c2:82ff:fe39:204c  implicit-null   
+    20             SR (IS-IS)   fe80::ac6a:8aff:fe14:4f36  implicit-null   
+    16011          SR (IS-IS)   fe80::e877:a2ff:feb7:4438  16011           
+    16012          SR (IS-IS)   fe80::a0c2:82ff:fe39:204c  16012           
+    16013          SR (IS-IS)   fe80::ac6a:8aff:fe14:4f36  16013           
+    16013          SR (IS-IS)   fe80::a0c2:82ff:fe39:204c  16013           
+    16014          SR (IS-IS)   fe80::ac6a:8aff:fe14:4f36  16014           
+    16014          SR (IS-IS)   fe80::a0c2:82ff:fe39:204c  16014           
+    16021          SR (IS-IS)   lo                         -               
+    16022          SR (IS-IS)   fe80::ac6a:8aff:fe14:4f36  16022           
+    16023          SR (IS-IS)   fe80::ac6a:8aff:fe14:4f36  16023           
+    16031          SR (IS-IS)   fe80::bc7e:bbff:fe7f:ecb0  16031           
+    16032          SR (IS-IS)   fe80::b886:2cff:fe84:a76f  16032           
+    16033          SR (IS-IS)   fe80::b886:2cff:fe84:a76f  16033           
+    16033          SR (IS-IS)   fe80::ac6a:8aff:fe14:4f36  16033           
+    16034          SR (IS-IS)   fe80::b886:2cff:fe84:a76f  16034           
+    16034          SR (IS-IS)   fe80::ac6a:8aff:fe14:4f36  16034           
+    16041          SR (IS-IS)   fe80::b886:2cff:fe84:a76f  16041           
+    16500          PPR (IS-IS)  fe80::e877:a2ff:feb7:4438  16500           
+    16501          PPR (IS-IS)  fe80::ac6a:8aff:fe14:4f36  16501           
+    16502          PPR (IS-IS)  fe80::e877:a2ff:feb7:4438  16502           
+    16503          PPR (IS-IS)  fe80::bc7e:bbff:fe7f:ecb0  16503           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::1/128 [115/50] via fe80::e877:a2ff:feb7:4438, eth-rt11, 00:00:04
+   I>* 6000:2::1/128 [115/50] via fe80::ac6a:8aff:fe14:4f36, eth-rt22, 00:00:04
+
+Router RT22
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:00:50  
+    1     L1     501 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:00:50  
+    1     L1     502 (MPLS)                   5000::11/128  0       Off-Path   -       -         
+    1     L1     503 (MPLS)                   5000::14/128  0       Off-Path   -       -         
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Mid-Point  Up      00:00:50  
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Mid-Point  Up      00:00:50  
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::3432:84ff:fe9d:2e41  implicit-null   
+    17             SR (IS-IS)   fe80::c436:63ff:feb3:4f5d  implicit-null   
+    18             SR (IS-IS)   fe80::56:41ff:fe53:a6b2    implicit-null   
+    19             SR (IS-IS)   fe80::b423:eaff:fea1:8247  implicit-null   
+    20             SR (IS-IS)   fe80::9c2f:11ff:fe0a:ab34  implicit-null   
+    21             SR (IS-IS)   fe80::7402:b8ff:fee9:682e  implicit-null   
+    16011          SR (IS-IS)   fe80::b423:eaff:fea1:8247  16011           
+    16011          SR (IS-IS)   fe80::3432:84ff:fe9d:2e41  16011           
+    16012          SR (IS-IS)   fe80::3432:84ff:fe9d:2e41  16012           
+    16013          SR (IS-IS)   fe80::c436:63ff:feb3:4f5d  16013           
+    16014          SR (IS-IS)   fe80::56:41ff:fe53:a6b2    20014           
+    16014          SR (IS-IS)   fe80::c436:63ff:feb3:4f5d  16014           
+    16021          SR (IS-IS)   fe80::b423:eaff:fea1:8247  16021           
+    16022          SR (IS-IS)   lo                         -               
+    16023          SR (IS-IS)   fe80::56:41ff:fe53:a6b2    20023           
+    16031          SR (IS-IS)   fe80::9c2f:11ff:fe0a:ab34  16031           
+    16031          SR (IS-IS)   fe80::b423:eaff:fea1:8247  16031           
+    16032          SR (IS-IS)   fe80::9c2f:11ff:fe0a:ab34  16032           
+    16033          SR (IS-IS)   fe80::7402:b8ff:fee9:682e  16033           
+    16034          SR (IS-IS)   fe80::7402:b8ff:fee9:682e  16034           
+    16034          SR (IS-IS)   fe80::56:41ff:fe53:a6b2    20034           
+    16041          SR (IS-IS)   fe80::7402:b8ff:fee9:682e  16041           
+    16041          SR (IS-IS)   fe80::9c2f:11ff:fe0a:ab34  16041           
+    16500          PPR (IS-IS)  fe80::b423:eaff:fea1:8247  16500           
+    16501          PPR (IS-IS)  fe80::56:41ff:fe53:a6b2    20501           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::1/128 [115/50] via fe80::b423:eaff:fea1:8247, eth-rt21, 00:00:06
+   I>* 6000:2::1/128 [115/50] via fe80::56:41ff:fe53:a6b2, eth-rt23, 00:00:06
+
+Router RT23
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:00:52  
+    1     L1     501 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:00:52  
+    1     L1     502 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:00:52  
+    1     L1     503 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:00:52  
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Mid-Point  Up      00:00:52  
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Mid-Point  Up      00:00:52  
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::c4ca:41ff:fe2d:de8c  implicit-null   
+    17             SR (IS-IS)   fe80::a02b:1eff:fed6:97e4  implicit-null   
+    18             SR (IS-IS)   fe80::5c15:8aff:feea:1d07  implicit-null   
+    19             SR (IS-IS)   fe80::a42f:50ff:fe9c:af9f  implicit-null   
+    20             SR (IS-IS)   fe80::d0dc:6eff:fe71:9f19  implicit-null   
+    20011          SR (IS-IS)   fe80::5c15:8aff:feea:1d07  16011           
+    20011          SR (IS-IS)   fe80::a02b:1eff:fed6:97e4  16011           
+    20012          SR (IS-IS)   fe80::5c15:8aff:feea:1d07  16012           
+    20012          SR (IS-IS)   fe80::a02b:1eff:fed6:97e4  16012           
+    20013          SR (IS-IS)   fe80::a02b:1eff:fed6:97e4  16013           
+    20014          SR (IS-IS)   fe80::c4ca:41ff:fe2d:de8c  16014           
+    20021          SR (IS-IS)   fe80::5c15:8aff:feea:1d07  16021           
+    20022          SR (IS-IS)   fe80::5c15:8aff:feea:1d07  16022           
+    20023          SR (IS-IS)   lo                         -               
+    20031          SR (IS-IS)   fe80::a42f:50ff:fe9c:af9f  16031           
+    20031          SR (IS-IS)   fe80::5c15:8aff:feea:1d07  16031           
+    20032          SR (IS-IS)   fe80::a42f:50ff:fe9c:af9f  16032           
+    20032          SR (IS-IS)   fe80::5c15:8aff:feea:1d07  16032           
+    20033          SR (IS-IS)   fe80::a42f:50ff:fe9c:af9f  16033           
+    20034          SR (IS-IS)   fe80::d0dc:6eff:fe71:9f19  16034           
+    20041          SR (IS-IS)   fe80::a42f:50ff:fe9c:af9f  16041           
+    20500          PPR (IS-IS)  fe80::5c15:8aff:feea:1d07  16500           
+    20501          PPR (IS-IS)  fe80::c4ca:41ff:fe2d:de8c  16501           
+    20502          PPR (IS-IS)  fe80::d0dc:6eff:fe71:9f19  16502           
+    20503          PPR (IS-IS)  fe80::c4ca:41ff:fe2d:de8c  16503           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+   Codes: K - kernel route, C - connected, S - static, R - RIPng,
+          O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
+          v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR,
+          f - OpenFabric,
+          > - selected route, * - FIB route, q - queued route, r - rejected route
+
+   I>* 6000:1::1/128 [115/50] via fe80::5c15:8aff:feea:1d07, eth-rt22, 00:00:07
+   I>* 6000:2::1/128 [115/50] via fe80::c4ca:41ff:fe2d:de8c, eth-rt14, 00:00:07
+
+Router RT31
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Off-Path   -       -         
+    1     L1     501 (MPLS)                   5000::14/128  0       Off-Path   -       -         
+    1     L1     502 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:00:54  
+    1     L1     503 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:00:54  
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path   -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path   -       -         
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::a067:c6ff:fe2c:3385  implicit-null   
+    17             SR (IS-IS)   fe80::f46d:c8ff:fe8a:a341  implicit-null   
+    16011          SR (IS-IS)   fe80::a067:c6ff:fe2c:3385  16011           
+    16012          SR (IS-IS)   fe80::a067:c6ff:fe2c:3385  16012           
+    16013          SR (IS-IS)   fe80::f46d:c8ff:fe8a:a341  16013           
+    16013          SR (IS-IS)   fe80::a067:c6ff:fe2c:3385  16013           
+    16014          SR (IS-IS)   fe80::f46d:c8ff:fe8a:a341  16014           
+    16014          SR (IS-IS)   fe80::a067:c6ff:fe2c:3385  16014           
+    16021          SR (IS-IS)   fe80::a067:c6ff:fe2c:3385  16021           
+    16022          SR (IS-IS)   fe80::f46d:c8ff:fe8a:a341  16022           
+    16022          SR (IS-IS)   fe80::a067:c6ff:fe2c:3385  16022           
+    16023          SR (IS-IS)   fe80::f46d:c8ff:fe8a:a341  16023           
+    16023          SR (IS-IS)   fe80::a067:c6ff:fe2c:3385  16023           
+    16031          SR (IS-IS)   lo                         -               
+    16032          SR (IS-IS)   fe80::f46d:c8ff:fe8a:a341  16032           
+    16033          SR (IS-IS)   fe80::f46d:c8ff:fe8a:a341  16033           
+    16034          SR (IS-IS)   fe80::f46d:c8ff:fe8a:a341  16034           
+    16041          SR (IS-IS)   fe80::f46d:c8ff:fe8a:a341  16041           
+    16502          PPR (IS-IS)  fe80::a067:c6ff:fe2c:3385  16502           
+    16503          PPR (IS-IS)  fe80::f46d:c8ff:fe8a:a341  16503           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT32
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Off-Path   -       -         
+    1     L1     501 (MPLS)                   5000::14/128  0       Off-Path   -       -         
+    1     L1     502 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:00:55  
+    1     L1     503 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:00:55  
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path   -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path   -       -         
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::881f:d3ff:febd:9e8c  implicit-null   
+    17             SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  implicit-null   
+    18             SR (IS-IS)   fe80::9863:abff:fed0:d7e   implicit-null   
+    19             SR (IS-IS)   fe80::ec65:d1ff:fe32:b508  implicit-null   
+    20             SR (IS-IS)   fe80::a4e9:77ff:feaa:f690  implicit-null   
+    21             SR (IS-IS)   fe80::40c4:e6ff:fe26:767f  implicit-null   
+    16011          SR (IS-IS)   fe80::881f:d3ff:febd:9e8c  16011           
+    16012          SR (IS-IS)   fe80::40c4:e6ff:fe26:767f  16012           
+    16012          SR (IS-IS)   fe80::881f:d3ff:febd:9e8c  16012           
+    16013          SR (IS-IS)   fe80::40c4:e6ff:fe26:767f  16013           
+    16014          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16014           
+    16014          SR (IS-IS)   fe80::ec65:d1ff:fe32:b508  16014           
+    16014          SR (IS-IS)   fe80::40c4:e6ff:fe26:767f  16014           
+    16021          SR (IS-IS)   fe80::881f:d3ff:febd:9e8c  16021           
+    16022          SR (IS-IS)   fe80::40c4:e6ff:fe26:767f  16022           
+    16023          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16023           
+    16023          SR (IS-IS)   fe80::ec65:d1ff:fe32:b508  16023           
+    16023          SR (IS-IS)   fe80::40c4:e6ff:fe26:767f  16023           
+    16031          SR (IS-IS)   fe80::9863:abff:fed0:d7e   16031           
+    16032          SR (IS-IS)   lo                         -               
+    16033          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16033           
+    16033          SR (IS-IS)   fe80::ec65:d1ff:fe32:b508  16033           
+    16034          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16034           
+    16034          SR (IS-IS)   fe80::ec65:d1ff:fe32:b508  16034           
+    16041          SR (IS-IS)   fe80::a4e9:77ff:feaa:f690  16041           
+    16502          PPR (IS-IS)  fe80::9863:abff:fed0:d7e   16502           
+    16503          PPR (IS-IS)  fe80::a4e9:77ff:feaa:f690  16503           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT33
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Off-Path   -       -         
+    1     L1     501 (MPLS)                   5000::14/128  0       Off-Path   -       -         
+    1     L1     502 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:00:57  
+    1     L1     503 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:00:57  
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path   -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path   -       -         
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::2832:a9ff:fec3:7078  implicit-null   
+    17             SR (IS-IS)   fe80::7806:e1ff:fe72:9b1f  implicit-null   
+    18             SR (IS-IS)   fe80::5476:31ff:fe94:c39   implicit-null   
+    19             SR (IS-IS)   fe80::a4e9:77ff:feaa:f690  implicit-null   
+    20             SR (IS-IS)   fe80::68c9:2ff:fe04:5eba   implicit-null   
+    21             SR (IS-IS)   fe80::d053:97ff:fee2:1711  implicit-null   
+    16011          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16011           
+    16011          SR (IS-IS)   fe80::5476:31ff:fe94:c39   16011           
+    16011          SR (IS-IS)   fe80::d053:97ff:fee2:1711  16011           
+    16012          SR (IS-IS)   fe80::d053:97ff:fee2:1711  16012           
+    16013          SR (IS-IS)   fe80::68c9:2ff:fe04:5eba   20013           
+    16013          SR (IS-IS)   fe80::d053:97ff:fee2:1711  16013           
+    16014          SR (IS-IS)   fe80::68c9:2ff:fe04:5eba   20014           
+    16021          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16021           
+    16021          SR (IS-IS)   fe80::5476:31ff:fe94:c39   16021           
+    16021          SR (IS-IS)   fe80::d053:97ff:fee2:1711  16021           
+    16022          SR (IS-IS)   fe80::d053:97ff:fee2:1711  16022           
+    16023          SR (IS-IS)   fe80::68c9:2ff:fe04:5eba   20023           
+    16031          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16031           
+    16031          SR (IS-IS)   fe80::5476:31ff:fe94:c39   16031           
+    16032          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16032           
+    16032          SR (IS-IS)   fe80::5476:31ff:fe94:c39   16032           
+    16033          SR (IS-IS)   lo                         -               
+    16034          SR (IS-IS)   fe80::7806:e1ff:fe72:9b1f  16034           
+    16041          SR (IS-IS)   fe80::a4e9:77ff:feaa:f690  16041           
+    16502          PPR (IS-IS)  fe80::a4e9:77ff:feaa:f690  16502           
+    16503          PPR (IS-IS)  fe80::7806:e1ff:fe72:9b1f  16503           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT34
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Off-Path   -       -         
+    1     L1     501 (MPLS)                   5000::14/128  0       Off-Path   -       -         
+    1     L1     502 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:00:59  
+    1     L1     503 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:00:59  
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path   -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path   -       -         
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::ac33:5dff:fe99:81ec  implicit-null   
+    17             SR (IS-IS)   fe80::f009:b9ff:fe05:e540  implicit-null   
+    16011          SR (IS-IS)   fe80::ac33:5dff:fe99:81ec  16011           
+    16011          SR (IS-IS)   fe80::f009:b9ff:fe05:e540  20011           
+    16012          SR (IS-IS)   fe80::ac33:5dff:fe99:81ec  16012           
+    16012          SR (IS-IS)   fe80::f009:b9ff:fe05:e540  20012           
+    16013          SR (IS-IS)   fe80::f009:b9ff:fe05:e540  20013           
+    16014          SR (IS-IS)   fe80::f009:b9ff:fe05:e540  20014           
+    16021          SR (IS-IS)   fe80::ac33:5dff:fe99:81ec  16021           
+    16021          SR (IS-IS)   fe80::f009:b9ff:fe05:e540  20021           
+    16022          SR (IS-IS)   fe80::ac33:5dff:fe99:81ec  16022           
+    16022          SR (IS-IS)   fe80::f009:b9ff:fe05:e540  20022           
+    16023          SR (IS-IS)   fe80::f009:b9ff:fe05:e540  20023           
+    16031          SR (IS-IS)   fe80::ac33:5dff:fe99:81ec  16031           
+    16032          SR (IS-IS)   fe80::ac33:5dff:fe99:81ec  16032           
+    16033          SR (IS-IS)   fe80::ac33:5dff:fe99:81ec  16033           
+    16034          SR (IS-IS)   lo                         -               
+    16041          SR (IS-IS)   fe80::ac33:5dff:fe99:81ec  16041           
+    16502          PPR (IS-IS)  fe80::ac33:5dff:fe99:81ec  16502           
+    16503          PPR (IS-IS)  fe80::f009:b9ff:fe05:e540  20503           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Router RT41
+^^^^^^^^^^^
+
+::
+
+   # show isis ppr
+    Area  Level  ID                           Prefix        Metric  Position   Status  Uptime    
+    ---------------------------------------------------------------------------------------------
+    1     L1     500 (MPLS)                   5000::11/128  0       Off-Path   -       -         
+    1     L1     501 (MPLS)                   5000::14/128  0       Off-Path   -       -         
+    1     L1     502 (MPLS)                   5000::11/128  0       Mid-Point  Up      00:01:01  
+    1     L1     503 (MPLS)                   5000::14/128  0       Mid-Point  Up      00:01:01  
+    1     L1     6000:1::1/128 (Native IPv6)  5000::11/128  50      Off-Path   -       -         
+    1     L1     6000:2::1/128 (Native IPv6)  5000::14/128  50      Off-Path   -       -         
+
+   # show mpls table
+    Inbound Label  Type         Nexthop                    Outbound Label  
+    -----------------------------------------------------------------------
+    16             SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  implicit-null   
+    17             SR (IS-IS)   fe80::2832:a9ff:fec3:7078  implicit-null   
+    16011          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16011           
+    16012          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16012           
+    16012          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16012           
+    16013          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16013           
+    16013          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16013           
+    16014          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16014           
+    16021          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16021           
+    16022          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16022           
+    16022          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16022           
+    16023          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16023           
+    16031          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16031           
+    16032          SR (IS-IS)   fe80::2832:a9ff:fec3:7078  16032           
+    16033          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16033           
+    16034          SR (IS-IS)   fe80::1c7e:c3ff:fe5e:7a54  16034           
+    16041          SR (IS-IS)   lo                         -               
+    16502          PPR (IS-IS)  fe80::2832:a9ff:fec3:7078  16502           
+    16503          PPR (IS-IS)  fe80::1c7e:c3ff:fe5e:7a54  16503           
+
+   # show ipv6 route 6000::/16 longer-prefixes isis
+
+Notice how R23 uses a different SRGB compared to the other routers in
+the network. As such, this router install different labels for PPR-IDs
+500 and 501 (e.g. 20500 instead of 16500 using the default SRGB).
+
+Verification - Forwarding Plane
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Ping Host 3 from Host2 and use tcpdump or wireshark to verify that the
+ICMP packets are being tunneled using MPLS LSPs and following the {R11 -
+R21 - R22 - R23 - R14} path. Here’s a wireshark capture between R11 and
+R21:
+
+.. figure:: https://user-images.githubusercontent.com/931662/64057179-2e980080-cb70-11e9-89c3-ff43e6d66cae.png
+   :alt: wireshark
+
+   wireshark
+
+Using ``traceroute`` it’s also possible to see that the ICMP packets are
+being tunneled through the IS-IS network:
+
+::
+
+   root@host2:~# traceroute -n fd00:20:1::1 -s fd00:10:2::1
+   traceroute to fd00:20:1::1 (fd00:20:1::1), 30 hops max, 80 byte packets
+    1  fd00:10:2::100  1.996 ms  1.832 ms  1.725 ms
+    2  * * *
+    3  * * *
+    4  * * *
+    5  * * *
+    6  * * *
+    7  * * *
+    8  fd00:20::100  0.154 ms  0.191 ms  0.116 ms
+    9  fd00:20:1::1  0.125 ms  0.105 ms  0.104 ms
diff --git a/doc/developer/northbound/retrofitting-configuration-commands.rst b/doc/developer/northbound/retrofitting-configuration-commands.rst
new file mode 100644 (file)
index 0000000..c13332b
--- /dev/null
@@ -0,0 +1,1916 @@
+Table of Contents
+-----------------
+
+-  `Introduction <#introduction>`__
+-  `Retrofitting process <#retrofitting-process>`__
+
+   -  `Step 1: writing a YANG module <#step1>`__
+   -  `Step 2: generate skeleton northbound callbacks <#step2>`__
+   -  `Step 3: update the frr_yang_module_info array of all relevant
+      daemons <#step3>`__
+   -  `Step 4: implement the northbound configuration
+      callbacks <#step4>`__
+   -  `Step 5: rewrite the CLI commands as dumb wrappers around the
+      northbound callbacks <#step5>`__
+   -  `Step 6: implement the ``cli_show`` callbacks <#step6>`__
+   -  `Step 7: consolidation <#step7>`__
+
+-  `Final Considerations <#final-considerations>`__
+
+Introduction
+------------
+
+This page explains how to convert existing CLI configuration commands to
+the new northbound model. This documentation is meant to be the primary
+reference for developers working on the northbound retrofitting process.
+We’ll show several examples taken from the ripd northbound conversion to
+illustrate some concepts described herein.
+
+Retrofitting process
+--------------------
+
+Step 1: writing a YANG module
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The first step is to write a YANG module that models faithfully the
+commands that are going to be converted. As explained in the
+[[Architecture]] page, the goal is to introduce the new YANG-based
+Northbound API without introducing backward incompatible changes in the
+CLI. The northbound retrofitting process should be completely
+transparent to FRR users.
+
+The developer is free to choose whether to write a full YANG module or a
+partial YANG module and increment it gradually. For developers who lack
+experience with YANG it’s probably a better idea to model one command at
+time.
+
+It’s recommended to reuse definitions from standard YANG models whenever
+possible to facilitate the process of writing module translators using
+the [[YANG module translator]]. As an example, the frr-ripd YANG module
+incorporated several parts of the IETF RIP YANG module. The repositories
+below contain big collections of YANG models that might be used as a
+reference: \* https://github.com/YangModels/yang \*
+https://github.com/openconfig/public
+
+When writing a YANG module, it’s highly recommended to follow the
+guidelines from `RFC 6087 <https://tools.ietf.org/html/rfc6087>`__. In
+general, most commands should be modeled fairly easy. Here are a few
+guidelines specific to authors of FRR YANG models: \* Use
+presence-containers or lists to model commands that change the CLI node
+(e.g. ``router rip``, ``interface eth0``). This way, if the
+presence-container or list entry is removed, all configuration options
+below them are removed automatically (exactly like the CLI behaves when
+a configuration object is removed using a *no* command). This
+recommendation is orthogonal to the `YANG authoring guidelines for
+OpenConfig
+models <https://github.com/openconfig/public/blob/master/doc/openconfig_style_guide.md>`__
+where the use of presence containers is discouraged. OpenConfig YANG
+models however were not designed to replicate the behavior of legacy CLI
+commands. \* When using YANG lists, be careful to identify what should
+be the key leaves. In the ``offset-list WORD <in|out> (0-16) IFNAME``
+command, for example, both the direction (``<in|out>``) and the
+interface name should be the keys of the list. This can be only known by
+analyzing the data structures used to store the commands. \* For
+clarity, use non-presence containers to group leaves that are associated
+to the same configuration command (as we’ll see later, this also
+facilitate the process of writing ``cli_show`` callbacks). \* YANG
+leaves of type *enumeration* should define explicitly the value of each
+*enum* option based on the value used in the FRR source code. \* Default
+values should be taken from the source code whenever they exist.
+
+Some commands are more difficult to model and demand the use of more
+advanced YANG constructs like *choice*, *when* and *must* statements.
+**One key requirement is that it should be impossible to load an invalid
+JSON/XML configuration to FRR**. The YANG modules should model exactly
+what the CLI accepts in the form of commands, and all restrictions
+imposed by the CLI should be defined in the YANG models whenever
+possible. As we’ll see later, not all constraints can be expressed using
+the YANG language and sometimes we’ll need to resort to code-level
+validation in the northbound callbacks.
+
+   Tip: the [[YANG tools]] page details several tools and commands that
+   might be useful when writing a YANG module, like validating YANG
+   files, indenting YANG files, validating instance data, etc.
+
+In the example YANG snippet below, we can see the use of the *must*
+statement that prevents ripd from redistributing RIP routes into itself.
+Although ripd CLI doesn’t allow the operator to enter *redistribute rip*
+under *router rip*, we don’t have the same protection when configuring
+ripd using other northbound interfaces (e.g. NETCONF). So without this
+constraint it would be possible to feed an invalid configuration to ripd
+(i.e. a bug).
+
+.. code:: yang
+
+         list redistribute {
+           key "protocol";
+           description
+             "Redistributes routes learned from other routing protocols.";
+           leaf protocol {
+             type frr-route-types:frr-route-types-v4;
+             description
+               "Routing protocol.";
+             must '. != "rip"';
+           }
+           [snip]
+         }
+
+In the example below, we use the YANG *choice* statement to ensure that
+either the ``password`` leaf or the ``key-chain`` leaf is configured,
+but not both. This is in accordance to the sanity checks performed by
+the *ip rip authentication* commands.
+
+.. code:: yang
+
+         choice authentication-data {
+           description
+             "Choose whether to use a simple password or a key-chain.";
+           leaf authentication-password {
+             type string {
+               length "1..16";
+             }
+             description
+               "Authentication string.";
+           }
+           leaf authentication-key-chain {
+             type string;
+             description
+               "Key-chain name.";
+           }
+         }
+
+Once finished, the new YANG model should be put into the FRR *yang/* top
+level directory. This will ensure it will be installed automatically by
+``make install``. It’s also encouraged (but not required) to put sample
+configurations under *yang/examples/* using either JSON or XML files.
+
+Step 2: generate skeleton northbound callbacks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use the *gen_northbound_callbacks* tool to generate skeleton callbacks
+for the YANG module. Example:
+
+.. code:: sh
+
+   $ tools/gen_northbound_callbacks frr-ripd > ripd/rip_northbound.c
+
+The tool will look for the given module in the ``YANG_MODELS_PATH``
+directory defined during the installation. For each schema node of the
+YANG module, the tool will generate skeleton callbacks based on the
+properties of the node. Example:
+
+.. code:: c
+
+   /*
+    * XPath: /frr-ripd:ripd/instance
+    */
+   static int ripd_instance_create(enum nb_event event,
+                                   const struct lyd_node *dnode,
+                                   union nb_resource *resource)
+   {
+           /* TODO: implement me. */
+           return NB_OK;
+   }
+
+   static int ripd_instance_delete(enum nb_event event,
+                                   const struct lyd_node *dnode)
+   {
+           /* TODO: implement me. */
+           return NB_OK;
+   }
+
+   /*
+    * XPath: /frr-ripd:ripd/instance/allow-ecmp
+    */
+   static int ripd_instance_allow_ecmp_modify(enum nb_event event,
+                                              const struct lyd_node *dnode,
+                                              union nb_resource *resource)
+   {
+           /* TODO: implement me. */
+           return NB_OK;
+   }
+
+   [snip]
+
+   const struct frr_yang_module_info frr_ripd_info = {
+           .name = "frr-ripd",
+           .nodes = {
+                   {
+                           .xpath = "/frr-ripd:ripd/instance",
+                           .cbs.create = ripd_instance_create,
+                           .cbs.delete = ripd_instance_delete,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/instance/allow-ecmp",
+                           .cbs.modify = ripd_instance_allow_ecmp_modify,
+                   },
+                   [snip]
+                   {
+                           .xpath = "/frr-ripd:ripd/state/routes/route",
+                           .cbs.get_next = ripd_state_routes_route_get_next,
+                           .cbs.get_keys = ripd_state_routes_route_get_keys,
+                           .cbs.lookup_entry = ripd_state_routes_route_lookup_entry,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/state/routes/route/prefix",
+                           .cbs.get_elem = ripd_state_routes_route_prefix_get_elem,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/state/routes/route/next-hop",
+                           .cbs.get_elem = ripd_state_routes_route_next_hop_get_elem,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/state/routes/route/interface",
+                           .cbs.get_elem = ripd_state_routes_route_interface_get_elem,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/state/routes/route/metric",
+                           .cbs.get_elem = ripd_state_routes_route_metric_get_elem,
+                   },
+                   {
+                           .xpath = "/frr-ripd:clear-rip-route",
+                           .cbs.rpc = clear_rip_route_rpc,
+                   },
+                   [snip]
+
+After the C source file is generated, it’s necessary to add a copyright
+header on it and indent the code using ``clang-format``.
+
+Step 3: update the *frr_yang_module_info* array of all relevant daemons
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We must inform the northbound about which daemons will implement the new
+YANG module. This is done by updating the ``frr_daemon_info`` structure
+of these daemons, with help of the ``FRR_DAEMON_INFO`` macro.
+
+When a YANG module is specific to a single daemon, like the frr-ripd
+module, then only the corresponding daemon should be updated. When the
+YANG module is related to a subset of libfrr (e.g. route-maps), then all
+FRR daemons that make use of that subset must be updated.
+
+Example:
+
+.. code:: c
+
+   static const struct frr_yang_module_info *ripd_yang_modules[] = {
+           &frr_interface_info,
+           &frr_ripd_info,
+   };
+    
+   FRR_DAEMON_INFO(ripd, RIP, .vty_port = RIP_VTY_PORT,
+                   [snip]
+                   .yang_modules = ripd_yang_modules,
+                   .n_yang_modules = array_size(ripd_yang_modules), )
+
+Step 4: implement the northbound configuration callbacks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Implementing the northbound configuration callbacks consists mostly of
+copying code from the corresponding CLI commands and make the required
+adaptations.
+
+It’s recommended to convert one command or a small group of related
+commands per commit. Small commits are preferred to facilitate the
+review process. Both “old” and “new” command can coexist without
+problems, so the retrofitting process can happen gradually over time.
+
+The configuration callbacks
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+These are the four main northbound configuration callbacks, as defined
+in the ``lib/northbound.h`` file:
+
+.. code:: c
+
+       /*
+        * Configuration callback.
+        *
+        * A presence container, list entry, leaf-list entry or leaf of type
+        * empty has been created.
+        *
+        * For presence-containers and list entries, the callback is supposed to
+        * initialize the default values of its children (if any) from the YANG
+        * models.
+        *
+        * event
+        *    The transaction phase. Refer to the documentation comments of
+        *    nb_event for more details.
+        *
+        * dnode
+        *    libyang data node that is being created.
+        *
+        * resource
+        *    Pointer to store resource(s) allocated during the NB_EV_PREPARE
+        *    phase. The same pointer can be used during the NB_EV_ABORT and
+        *    NB_EV_APPLY phases to either release or make use of the allocated
+        *    resource(s). It's set to NULL when the event is NB_EV_VALIDATE.
+        *
+        * Returns:
+        *    - NB_OK on success.
+        *    - NB_ERR_VALIDATION when a validation error occurred.
+        *    - NB_ERR_RESOURCE when the callback failed to allocate a resource.
+        *    - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+        *    - NB_ERR for other errors.
+        */
+       int (*create)(enum nb_event event, const struct lyd_node *dnode,
+                 union nb_resource *resource);
+
+       /*
+        * Configuration callback.
+        *
+        * The value of a leaf has been modified.
+        *
+        * List keys don't need to implement this callback. When a list key is
+        * modified, the northbound treats this as if the list was deleted and a
+        * new one created with the updated key value.
+        *
+        * event
+        *    The transaction phase. Refer to the documentation comments of
+        *    nb_event for more details.
+        *
+        * dnode
+        *    libyang data node that is being modified
+        *
+        * resource
+        *    Pointer to store resource(s) allocated during the NB_EV_PREPARE
+        *    phase. The same pointer can be used during the NB_EV_ABORT and
+        *    NB_EV_APPLY phases to either release or make use of the allocated
+        *    resource(s). It's set to NULL when the event is NB_EV_VALIDATE.
+        *
+        * Returns:
+        *    - NB_OK on success.
+        *    - NB_ERR_VALIDATION when a validation error occurred.
+        *    - NB_ERR_RESOURCE when the callback failed to allocate a resource.
+        *    - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+        *    - NB_ERR for other errors.
+        */
+       int (*modify)(enum nb_event event, const struct lyd_node *dnode,
+                 union nb_resource *resource);
+
+       /*
+        * Configuration callback.
+        *
+        * A presence container, list entry, leaf-list entry or optional leaf
+        * has been deleted.
+        *
+        * The callback is supposed to delete the entire configuration object,
+        * including its children when they exist.
+        *
+        * event
+        *    The transaction phase. Refer to the documentation comments of
+        *    nb_event for more details.
+        *
+        * dnode
+        *    libyang data node that is being deleted.
+        *
+        * Returns:
+        *    - NB_OK on success.
+        *    - NB_ERR_VALIDATION when a validation error occurred.
+        *    - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+        *    - NB_ERR for other errors.
+        */
+       int (*delete)(enum nb_event event, const struct lyd_node *dnode);
+
+       /*
+        * Configuration callback.
+        *
+        * A list entry or leaf-list entry has been moved. Only applicable when
+        * the "ordered-by user" statement is present.
+        *
+        * event
+        *    The transaction phase. Refer to the documentation comments of
+        *    nb_event for more details.
+        *
+        * dnode
+        *    libyang data node that is being moved.
+        *
+        * Returns:
+        *    - NB_OK on success.
+        *    - NB_ERR_VALIDATION when a validation error occurred.
+        *    - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+        *    - NB_ERR for other errors.
+        */
+       int (*move)(enum nb_event event, const struct lyd_node *dnode);
+
+Since skeleton northbound callbacks are generated automatically by the
+*gen_northbound_callbacks* tool, the developer doesn’t need to worry
+about which callbacks need to be implemented.
+
+   NOTE: once a daemon starts, it reads its YANG modules and validates
+   that all required northbound callbacks were implemented. If any
+   northbound callback is missing, an error is logged and the program
+   exists.
+
+Transaction phases
+^^^^^^^^^^^^^^^^^^
+
+Configuration transactions and their phases were described in detail in
+the [[Architecture]] page. Here’s the definition of the ``nb_event``
+enumeration as defined in the *lib/northbound.h* file:
+
+.. code:: c
+
+   /* Northbound events. */
+   enum nb_event {
+           /*
+            * The configuration callback is supposed to verify that the changes are
+            * valid and can be applied.
+            */
+           NB_EV_VALIDATE,
+
+           /*
+            * The configuration callback is supposed to prepare all resources
+            * required to apply the changes.
+            */
+           NB_EV_PREPARE,
+
+           /*
+            * Transaction has failed, the configuration callback needs to release
+            * all resources previously allocated.
+            */
+           NB_EV_ABORT,
+
+           /*
+            * The configuration changes need to be applied. The changes can't be
+            * rejected at this point (errors are logged and ignored).
+            */
+           NB_EV_APPLY,
+   };
+
+When converting a CLI command, we must identify all error-prone
+operations and perform them in the ``NB_EV_PREPARE`` phase of the
+northbound callbacks. When the operation in question involves the
+allocation of a specific resource (e.g. file descriptors), we can store
+the allocated resource in the ``resource`` variable given to the
+callback. This way the allocated resource can be obtained in the other
+phases of the transaction using the same parameter.
+
+Here’s the ``create`` northbound callback associated to the
+``router rip`` command:
+
+.. code:: c
+
+   /*
+    * XPath: /frr-ripd:ripd/instance
+    */
+   static int ripd_instance_create(enum nb_event event,
+                                   const struct lyd_node *dnode,
+                                   union nb_resource *resource)
+   {
+           int socket;
+
+           switch (event) {
+           case NB_EV_VALIDATE:
+                   break;
+           case NB_EV_PREPARE:
+                   socket = rip_create_socket();
+                   if (socket < 0)
+                           return NB_ERR_RESOURCE;
+                   resource->fd = socket;
+                   break;
+           case NB_EV_ABORT:
+                   socket = resource->fd;
+                   close(socket);
+                   break;
+           case NB_EV_APPLY:
+                   socket = resource->fd;
+                   rip_create(socket);
+                   break;
+           }
+
+           return NB_OK;
+   }
+
+Note that the socket creation is an error-prone operation since it
+depends on the underlying operating system, so the socket must be
+created during the ``NB_EV_PREPARE`` phase and stored in
+``resource->fd``. This socket is then either closed or used depending on
+the outcome of the preparation phase of the whole transaction.
+
+During the ``NB_EV_VALIDATE`` phase, the northbound callbacks must
+validate if the intended changes are valid. As an example, FRR doesn’t
+allow the operator to deconfigure active interfaces:
+
+.. code:: c
+
+   static int lib_interface_delete(enum nb_event event,
+                                   const struct lyd_node *dnode)
+   {
+           struct interface *ifp;
+
+           ifp = yang_dnode_get_entry(dnode);
+
+           switch (event) {
+           case NB_EV_VALIDATE:
+                   if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) {
+                           zlog_warn("%s: only inactive interfaces can be deleted",
+                                     __func__);
+                           return NB_ERR_VALIDATION;
+                   }
+                   break;
+           case NB_EV_PREPARE:
+           case NB_EV_ABORT:
+                   break;
+           case NB_EV_APPLY:
+                   if_delete(ifp);
+                   break;
+           }
+
+           return NB_OK;
+   }
+
+Note however that it’s preferred to use YANG to model the validation
+constraints whenever possible. Code-level validations should be used
+only to validate constraints that can’t be modeled using the YANG
+language.
+
+Most callbacks don’t need to perform any validations nor perform any
+error-prone operations, so in these cases we can use the following
+pattern to return early if ``event`` is different than ``NB_EV_APPLY``:
+
+.. code:: c
+
+   /*
+    * XPath: /frr-ripd:ripd/instance/distance/default
+    */
+   static int ripd_instance_distance_default_modify(enum nb_event event,
+                                                    const struct lyd_node *dnode,
+                                                    union nb_resource *resource)
+   {
+           if (event != NB_EV_APPLY)
+                   return NB_OK;
+
+           rip->distance = yang_dnode_get_uint8(dnode, NULL);
+
+           return NB_OK;
+   }
+
+During development it’s recommend to use the *debug northbound* command
+to debug configuration transactions and see what callbacks are being
+called. Example:
+
+::
+
+   ripd# conf t
+   ripd(config)# debug northbound
+   ripd(config)# router rip
+   ripd(config-router)# allow-ecmp
+   ripd(config-router)# network eth0
+   ripd(config-router)# redistribute ospf metric 2
+   ripd(config-router)# commit
+   % Configuration committed successfully.
+
+   ripd(config-router)#
+
+Now the ripd log:
+
+::
+
+   2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [create] xpath [/frr-ripd:ripd/instance] value [(none)]
+   2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [modify] xpath [/frr-ripd:ripd/instance/allow-ecmp] value [true]
+   2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [create] xpath [/frr-ripd:ripd/instance/interface[.='eth0']] value [eth0]
+   2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [create] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(none)]
+   2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [modify] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']/metric] value [2]
+   2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [create] xpath [/frr-ripd:ripd/instance] value [(none)]
+   2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [modify] xpath [/frr-ripd:ripd/instance/allow-ecmp] value [true]
+   2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [create] xpath [/frr-ripd:ripd/instance/interface[.='eth0']] value [eth0]
+   2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [create] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(none)]
+   2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [modify] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']/metric] value [2]
+   2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [create] xpath [/frr-ripd:ripd/instance] value [(none)]
+   2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [modify] xpath [/frr-ripd:ripd/instance/allow-ecmp] value [true]
+   2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [create] xpath [/frr-ripd:ripd/instance/interface[.='eth0']] value [eth0]
+   2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [create] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(none)]
+   2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [modify] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']/metric] value [2]
+   2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [apply_finish] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(null)]
+
+Getting the data
+^^^^^^^^^^^^^^^^
+
+One parameter that is common to all northbound configuration callbacks
+is the ``dnode`` parameter. This is a libyang data node structure that
+contains information relative to the configuration change that is being
+performed. For ``create`` callbacks, it contains the configuration node
+that is being added. For ``delete`` callbacks, it contains the
+configuration node that is being deleted. For ``modify`` callbacks, it
+contains the configuration node that is being modified.
+
+In order to get the actual data value out of the ``dnode`` variable, we
+need to use the ``yang_dnode_get_*()`` wrappers documented in
+*lib/yang_wrappers.h*.
+
+The advantage of passing a ``dnode`` structure to the northbound
+callbacks is that the whole candidate being committed is made available,
+so the callbacks can obtain values from other portions of the
+configuration if necessary. This can be done by providing an xpath
+expression to the second parameter of the ``yang_dnode_get_*()``
+wrappers to specify the element we want to get. The example below shows
+a callback that gets the values of two leaves that are part of the same
+list entry:
+
+.. code:: c
+
+   static int
+   ripd_instance_redistribute_metric_modify(enum nb_event event,
+                                            const struct lyd_node *dnode,
+                                            union nb_resource *resource)
+   {
+           int type;
+           uint8_t metric;
+
+           if (event != NB_EV_APPLY)
+                   return NB_OK;
+
+           type = yang_dnode_get_enum(dnode, "../protocol");
+           metric = yang_dnode_get_uint8(dnode, NULL);
+
+           rip->route_map[type].metric_config = true;
+           rip->route_map[type].metric = metric;
+           rip_redistribute_conf_update(type);
+
+           return NB_OK;
+   }
+
+..
+
+   NOTE: if the wrong ``yang_dnode_get_*()`` wrapper is used, the code
+   will log an error and abort. An example would be using
+   ``yang_dnode_get_enum()`` to get the value of a boolean data node.
+
+No need to check if the configuration value has changed
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A common pattern in CLI commands is this:
+
+.. code:: c
+
+   DEFUN (...)
+   {
+           [snip]
+           if (new_value == old_value)
+                   return CMD_SUCCESS;
+           [snip]
+   }
+
+Several commands need to check if the new value entered by the user is
+the same as the one currently configured. Then, if yes, ignore the
+command since nothing was changed.
+
+The northbound callbacks on the other hand don’t need to perform this
+check since they act on effective configuration changes. Using the CLI
+as an example, if the operator enters the same command multiple times,
+the northbound layer will detect that nothing has changed in the
+configuration and will avoid calling the northbound callbacks
+unnecessarily.
+
+In some cases, however, it might be desirable to check for
+inconsistencies and notify the northbound when that happens:
+
+.. code:: c
+
+   /*
+    * XPath: /frr-ripd:ripd/instance/interface
+    */
+   static int ripd_instance_interface_create(enum nb_event event,
+                                             const struct lyd_node *dnode,
+                                             union nb_resource *resource)
+   {
+           const char *ifname;
+
+           if (event != NB_EV_APPLY)
+                   return NB_OK;
+
+           ifname = yang_dnode_get_string(dnode, NULL);
+
+           return rip_enable_if_add(ifname);
+   }
+
+.. code:: c
+
+   /* Add interface to rip_enable_if. */
+   int rip_enable_if_add(const char *ifname)
+   {
+           int ret;
+
+           ret = rip_enable_if_lookup(ifname);
+           if (ret >= 0)
+                   return NB_ERR_INCONSISTENCY;
+
+           vector_set(rip_enable_interface,
+                      XSTRDUP(MTYPE_RIP_INTERFACE_STRING, ifname));
+
+           rip_enable_apply_all(); /* TODOVJ */
+
+           return NB_OK;
+   }
+
+In the example above, the ``rip_enable_if_add()`` function should never
+return ``NB_ERR_INCONSISTENCY`` in normal conditions. This is because
+the northbound layer guarantees that the same interface will never be
+added more than once (except when it’s removed and re-added again). But
+to be on the safe side it’s probably wise to check for internal
+inconsistencies to ensure everything is working as expected.
+
+Default values
+^^^^^^^^^^^^^^
+
+Whenever creating a new presence-container or list entry, it’s usually
+necessary to initialize certain variables to their default values. FRR
+most of the time uses special constants for that purpose
+(e.g. ``RIP_DEFAULT_METRIC_DEFAULT``, ``DFLT_BGP_HOLDTIME``, etc). Now
+that we have YANG models, we want to fetch the default values from these
+models instead. This will allow us to changes default values smoothly
+without needing to touch the code. Better yet, it will allow users to
+create YANG deviations to define custom default values easily.
+
+To fetch default values from the loaded YANG models, use the
+``yang_get_default_*()`` wrapper functions
+(e.g. ``yang_get_default_bool()``) documented in *lib/yang_wrappers.h*.
+
+Example:
+
+.. code:: c
+
+   int rip_create(int socket)
+   {
+           rip = XCALLOC(MTYPE_RIP, sizeof(struct rip));
+
+           /* Set initial values. */
+           rip->ecmp = yang_get_default_bool("%s/allow-ecmp", RIP_INSTANCE);
+           rip->default_metric =
+                   yang_get_default_uint8("%s/default-metric", RIP_INSTANCE);
+           [snip]
+   }
+
+Configuration options are edited individually
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Several CLI commands edit multiple configuration options at the same
+time. Some examples taken from ripd: \*
+``timers basic (5-2147483647) (5-2147483647) (5-2147483647)`` -
+*/frr-ripd:ripd/instance/timers/flush-interval* -
+*/frr-ripd:ripd/instance/timers/holddown-interval* -
+*/frr-ripd:ripd/instance/timers/update-interval* \*
+``distance (1-255) A.B.C.D/M [WORD]`` -
+*/frr-ripd:ripd/instance/distance/source/prefix* -
+*/frr-ripd:ripd/instance/distance/source/distance* -
+*/frr-ripd:ripd/instance/distance/source/access-list*
+
+In the new northbound model, there’s one or more separate callbacks for
+each configuration option. This usually has implications when converting
+code from CLI commands to the northbound commands. An example of this is
+the following commit from ripd:
+`7cf2f2eaf <https://github.com/opensourcerouting/frr/commit/7cf2f2eaf43ef5df294625d1ab4c708db8293510>`__.
+The ``rip_distance_set()`` and ``rip_distance_unset()`` functions were
+torn apart and their code split into a few different callbacks.
+
+For lists and presence-containers, it’s possible to use the
+``yang_dnode_set_entry()`` function to attach user data to a libyang
+data node, and then retrieve this value in the other callbacks (for the
+same node or any of its children) using the ``yang_dnode_get_entry()``
+function. Example:
+
+.. code:: c
+
+   static int ripd_instance_distance_source_create(enum nb_event event,
+                                                   const struct lyd_node *dnode,
+                                                   union nb_resource *resource)
+   {
+           struct prefix_ipv4 prefix;
+           struct route_node *rn;
+
+           if (event != NB_EV_APPLY)
+                   return NB_OK;
+
+           yang_dnode_get_ipv4p(&prefix, dnode, "./prefix");
+
+           /* Get RIP distance node. */
+           rn = route_node_get(rip_distance_table, (struct prefix *)&prefix);
+           rn->info = rip_distance_new();
+           yang_dnode_set_entry(dnode, rn);
+
+           return NB_OK;
+   }
+
+.. code:: c
+
+   static int
+   ripd_instance_distance_source_distance_modify(enum nb_event event,
+                                                 const struct lyd_node *dnode,
+                                                 union nb_resource *resource)
+   {
+           struct route_node *rn;
+           uint8_t distance;
+           struct rip_distance *rdistance;
+
+           if (event != NB_EV_APPLY)
+                   return NB_OK;
+
+           /* Set distance value. */
+           rn = yang_dnode_get_entry(dnode);
+           distance = yang_dnode_get_uint8(dnode, NULL);
+           rdistance = rn->info;
+           rdistance->distance = distance;
+
+           return NB_OK;
+   }
+
+Commands that edit multiple configuration options at the same time can
+also use the ``apply_finish`` optional callback, documented as follows
+in the *lib/northbound.h* file:
+
+.. code:: c
+
+       /*
+        * Optional configuration callback for YANG lists and containers.
+        *
+        * The 'apply_finish' callbacks are called after all other callbacks
+        * during the apply phase (NB_EV_APPLY). These callbacks are called only
+        * under one of the following two cases:
+        * * The container or a list entry has been created;
+        * * Any change is made within the descendants of the list entry or
+        *   container (e.g. a child leaf was modified, created or deleted).
+        *
+        * This callback is useful in the cases where a single event should be
+        * triggered regardless if the container or list entry was changed once
+        * or multiple times.
+        *
+        * dnode
+        *    libyang data node from the YANG list or container.
+        */
+       void (*apply_finish)(const struct lyd_node *dnode);
+
+Here’s an example of how this callback can be used:
+
+.. code:: c
+
+   /*
+    * XPath: /frr-ripd:ripd/instance/timers/
+    */
+   static void ripd_instance_timers_apply_finish(const struct lyd_node *dnode)
+   {
+           /* Reset update timer thread. */
+           rip_event(RIP_UPDATE_EVENT, 0);
+   }
+
+.. code:: c
+
+                   {
+                           .xpath = "/frr-ripd:ripd/instance/timers",
+                           .cbs.apply_finish = ripd_instance_timers_apply_finish,
+                           .cbs.cli_show = cli_show_rip_timers,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/instance/timers/flush-interval",
+                           .cbs.modify = ripd_instance_timers_flush_interval_modify,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/instance/timers/holddown-interval",
+                           .cbs.modify = ripd_instance_timers_holddown_interval_modify,
+                   },
+                   {
+                           .xpath = "/frr-ripd:ripd/instance/timers/update-interval",
+                           .cbs.modify = ripd_instance_timers_update_interval_modify,
+                   },
+
+In this example, we want to call the ``rip_event()`` function only once
+regardless if all RIP timers were modified or only one of them. Without
+the ``apply_finish`` callback we’d need to call ``rip_event()`` in the
+``modify`` callback of each timer (a YANG leaf), resulting in redundant
+call to the ``rip_event()`` function if multiple timers are changed at
+once.
+
+Bonus: libyang user types
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When writing YANG modules, it’s advisable to create derived types for
+data types that are used on multiple places (e.g. MAC addresses, IS-IS
+networks, etc). Here’s how `RFC
+7950 <https://tools.ietf.org/html/rfc7950#page-25>`__ defines derived
+types: > YANG can define derived types from base types using the
+“typedef” > statement. A base type can be either a built-in type or a
+derived > type, allowing a hierarchy of derived types. > > A derived
+type can be used as the argument for the “type” statement. > > YANG
+Example: > > typedef percent { > type uint8 { > range “0 .. 100”; > } >
+} > > leaf completed { > type percent; > }
+
+Derived types are essentially built-in types with imposed restrictions.
+As an example, the ``ipv4-address`` derived type from IETF is defined
+using the ``string`` built-in type with a ``pattern`` constraint (a
+regular expression):
+
+::
+
+      typedef ipv4-address {
+        type string {
+          pattern
+            '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}'
+          +  '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
+          + '(%[\p{N}\p{L}]+)?';
+        }
+        description
+          "The ipv4-address type represents an IPv4 address in
+           dotted-quad notation.  The IPv4 address may include a zone
+           index, separated by a % sign.
+
+           The zone index is used to disambiguate identical address
+           values.  For link-local addresses, the zone index will
+           typically be the interface index number or the name of an
+           interface.  If the zone index is not present, the default
+           zone of the device will be used.
+
+           The canonical format for the zone index is the numerical
+           format";
+      }
+
+Sometimes, however, it’s desirable to have a binary representation of
+the derived type that is different from the associated built-in type.
+Taking the ``ipv4-address`` example above, it would be more convenient
+to manipulate this YANG type using ``in_addr`` structures instead of
+strings. libyang allow us to do that using the user types plugin:
+https://netopeer.liberouter.org/doc/libyang/master/howtoschemaplugins.html#usertypes
+
+Here’s how the the ``ipv4-address`` derived type is implemented in FRR
+(*yang/libyang_plugins/frr_user_types.c*):
+
+.. code:: c
+
+   static int ipv4_address_store_clb(const char *type_name, const char *value_str,
+                                     lyd_val *value, char **err_msg)
+   {
+           value->ptr = malloc(sizeof(struct in_addr));
+           if (!value->ptr)
+                   return 1;
+
+           if (inet_pton(AF_INET, value_str, value->ptr) != 1) {
+                   free(value->ptr);
+                   return 1;
+           }
+
+           return 0;
+   }
+
+.. code:: c
+
+   struct lytype_plugin_list frr_user_types[] = {
+           {"ietf-inet-types", "2013-07-15", "ipv4-address",
+            ipv4_address_store_clb, free},
+           {"ietf-inet-types", "2013-07-15", "ipv4-address-no-zone",
+            ipv4_address_store_clb, free},
+           [snip]
+           {NULL, NULL, NULL, NULL, NULL} /* terminating item */
+   };
+
+Now, in addition to the string representation of the data value, libyang
+will also store the data in the binary format we specified (an
+``in_addr`` structure).
+
+Whenever a new derived type is implemented in FRR, it’s also recommended
+to write new wrappers in the *lib/yang_wrappers.c* file
+(e.g. ``yang_dnode_get_ipv4()``, ``yang_get_default_ipv4()``, etc).
+
+Step 5: rewrite the CLI commands as dumb wrappers around the northbound callbacks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once the northbound callbacks are implemented, we need to rewrite the
+associated CLI commands on top of the northbound layer. This is the
+easiest part of the retrofitting process.
+
+For protocol daemons, it’s recommended to put all CLI commands on a
+separate C file (e.g. *ripd/rip_cli.c*). This helps to keep the code
+more clean by separating the main protocol code from the user interface.
+It should also help when moving the CLI to a separate program in the
+future.
+
+For libfrr commands, it’s not possible to centralize all commands in a
+single file because the *extract.pl* script from *vtysh* treats commands
+differently depending on the file in which they are defined (e.g. DEFUNs
+from *lib/routemap.c* are installed using the ``VTYSH_RMAP`` constant,
+which identifies the daemons that support route-maps). In this case, the
+CLI commands should be rewritten but maintained in the same file.
+
+Since all CLI configuration commands from FRR will need to be rewritten,
+this is an excellent opportunity to rework this part of the code to make
+the commands easier to maintain and extend. These are the three main
+recommendations: 1. Always use DEFPY instead of DEFUN to improve code
+readability. 2. Always try to join multiple DEFUNs into a single DEFPY
+whenever possible. As an example, there’s no need to have both
+``distance (1-255) A.B.C.D/M`` and ``distance (1-255) A.B.C.D/M WORD``
+when a single ``distance (1-255) A.B.C.D/M [WORD]`` would suffice. 3.
+When necessary, create a separate DEFPY for ``no`` commands so that part
+of the configuration command can be made optional for convenience.
+Example:
+``no timers basic [(5-2147483647) (5-2147483647) (5-2147483647)]``. In
+this example, everything after ``no timers basic`` is ignored by FRR, so
+it makes sense to accept ``no timers basic`` as a valid command. But it
+also makes sense to accept all parameters
+(``no timers basic (5-2147483647) (5-2147483647) (5-2147483647)``) to
+make it easier to remove the command just by prefixing a “no” to it.
+
+To rewrite a CLI command as a dumb wrapper around the northbound
+callbacks, use the ``nb_cli_cfg_change()`` function. This function
+accepts as a parameter an array of ``cli_config_change`` structures that
+specify the changes that need to performed on the candidate
+configuration. Here’s the declaration of this structure (taken from the
+*lib/northbound_cli.h* file):
+
+.. code:: c
+
+   struct cli_config_change {
+           /*
+            * XPath (absolute or relative) of the configuration option being
+            * edited.
+            */
+           char xpath[XPATH_MAXLEN];
+
+           /*
+            * Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or
+            * NB_OP_DELETE).
+            */
+           enum nb_operation operation;
+
+           /*
+            * New value of the configuration option. Should be NULL for typeless
+            * YANG data (e.g. presence-containers). For convenience, NULL can also
+            * be used to restore a leaf to its default value.
+            */
+           const char *value;
+   };
+
+The ``nb_cli_cfg_change()`` function positions the CLI command on top on
+top of the northbound layer. Instead of changing the running
+configuration directly, this function changes the candidate
+configuration instead, as described in the [[Transactional CLI]] page.
+When the transactional CLI is not in use (i.e. the default mode), then
+``nb_cli_cfg_change()`` performs an implicit ``commit`` operation after
+changing the candidate configuration.
+
+   NOTE: the ``nb_cli_cfg_change()`` function clones the candidate
+   configuration before actually editing it. This way, if any error
+   happens during the editing, the original candidate is restored to
+   avoid inconsistencies. Either all changes from the configuration
+   command are performed successfully or none are. It’s like a
+   mini-transaction but happening on the candidate configuration (thus
+   the northbound callbacks are not involved).
+
+Other important details to keep in mind while rewriting the CLI
+commands: \* ``nb_cli_cfg_change()`` returns CLI errors codes
+(e.g. ``CMD_SUCCESS``, ``CMD_WARNING``), so the return value of this
+function can be used as the return value of CLI commands. \* Calls to
+``VTY_PUSH_CONTEXT`` and ``VTY_PUSH_CONTEXT_SUB`` should be converted to
+calls to ``VTY_PUSH_XPATH``. Similarly, the following macros aren’t
+necessary anymore and can be removed: ``VTY_DECLVAR_CONTEXT``,
+``VTY_DECLVAR_CONTEXT_SUB``, ``VTY_GET_CONTEXT`` and
+``VTY_CHECK_CONTEXT``. The ``nb_cli_cfg_change()`` functions uses the
+``VTY_CHECK_XPATH`` macro to check if the data node being edited still
+exists before doing anything else.
+
+The examples below provide additional details about how the conversion
+should be done.
+
+Example 1
+^^^^^^^^^
+
+In this first example, the *router rip* command becomes a dumb wrapper
+around the ``ripd_instance_create()`` callback. Note that we don’t need
+to check if the ``/frr-ripd:ripd/instance`` data path already exists
+before trying to create it. The northbound will detect when this
+presence-container already exists and do nothing. The
+``VTY_PUSH_XPATH()`` macro is used to change the vty node and set the
+context for other commands under *router rip*.
+
+.. code:: c
+
+   DEFPY_NOSH (router_rip,
+          router_rip_cmd,
+          "router rip",
+          "Enable a routing process\n"
+          "Routing Information Protocol (RIP)\n")
+   {
+           int ret;
+
+           struct cli_config_change changes[] = {
+                   {
+                           .xpath = "/frr-ripd:ripd/instance",
+                           .operation = NB_OP_CREATE,
+                           .value = NULL,
+                   },
+           };
+
+           ret = nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+           if (ret == CMD_SUCCESS)
+                   VTY_PUSH_XPATH(RIP_NODE, changes[0].xpath);
+
+           return ret;
+   }
+
+Example 2
+^^^^^^^^^
+
+Here we can see the use of relative xpaths (starting with ``./``), which
+are more convenient that absolute xpaths (which would be
+``/frr-ripd:ripd/instance/default-metric`` in this example). This is
+possible because the use of ``VTY_PUSH_XPATH()`` in the *router rip*
+command set the vty base xpath to ``/frr-ripd:ripd/instance``.
+
+.. code:: c
+
+   DEFPY (rip_default_metric,
+          rip_default_metric_cmd,
+          "default-metric (1-16)",
+          "Set a metric of redistribute routes\n"
+          "Default metric\n")
+   {
+           struct cli_config_change changes[] = {
+                   {
+                           .xpath = "./default-metric",
+                           .operation = NB_OP_MODIFY,
+                           .value = default_metric_str,
+                   },
+           };
+
+           return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+   }
+
+In the command below we the ``value`` to NULL to indicate that we want
+to set this leaf to its default value. This is better than hardcoding
+the default value because the default might change in the future. Also,
+users might define custom defaults by using YANG deviations, so it’s
+better to write code that works correctly regardless of the default
+values defined in the YANG models.
+
+.. code:: c
+
+   DEFPY (no_rip_default_metric,
+          no_rip_default_metric_cmd,
+          "no default-metric [(1-16)]",
+          NO_STR
+          "Set a metric of redistribute routes\n"
+          "Default metric\n")
+   {
+           struct cli_config_change changes[] = {
+                   {
+                           .xpath = "./default-metric",
+                           .operation = NB_OP_MODIFY,
+                           .value = NULL,
+                   },
+           };
+
+           return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+   }
+
+Example 3
+^^^^^^^^^
+
+This example shows how one command can change multiple leaves at the
+same time.
+
+.. code:: c
+
+   DEFPY (rip_timers,
+          rip_timers_cmd,
+          "timers basic (5-2147483647)$update (5-2147483647)$timeout (5-2147483647)$garbage",
+          "Adjust routing timers\n"
+          "Basic routing protocol update timers\n"
+          "Routing table update timer value in second. Default is 30.\n"
+          "Routing information timeout timer. Default is 180.\n"
+          "Garbage collection timer. Default is 120.\n")
+   {
+           struct cli_config_change changes[] = {
+                   {
+                           .xpath = "./timers/update-interval",
+                           .operation = NB_OP_MODIFY,
+                           .value = update_str,
+                   },
+                   {
+                           .xpath = "./timers/holddown-interval",
+                           .operation = NB_OP_MODIFY,
+                           .value = timeout_str,
+                   },
+                   {
+                           .xpath = "./timers/flush-interval",
+                           .operation = NB_OP_MODIFY,
+                           .value = garbage_str,
+                   },
+           };
+
+           return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+   }
+
+Example 4
+^^^^^^^^^
+
+This example shows how to create a list entry:
+
+.. code:: c
+
+   DEFPY (rip_distance_source,
+          rip_distance_source_cmd,
+          "distance (1-255) A.B.C.D/M$prefix [WORD$acl]",
+          "Administrative distance\n"
+          "Distance value\n"
+          "IP source prefix\n"
+          "Access list name\n")
+   {
+           char xpath_list[XPATH_MAXLEN];
+           struct cli_config_change changes[] = {
+                   {
+                           .xpath = ".",
+                           .operation = NB_OP_CREATE,
+                   },
+                   {
+                           .xpath = "./distance",
+                           .operation = NB_OP_MODIFY,
+                           .value = distance_str,
+                   },
+                   {
+                           .xpath = "./access-list",
+                           .operation = acl ? NB_OP_MODIFY : NB_OP_DELETE,
+                           .value = acl,
+                   },
+           };
+
+           snprintf(xpath_list, sizeof(xpath_list), "./distance/source[prefix='%s']",
+                    prefix_str);
+
+           return nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes));
+   }
+
+The ``xpath_list`` variable is used to hold the xpath that identifies
+the list entry. The keys of the list entry should be embedded in this
+xpath and don’t need to be part of the array of configuration changes.
+All entries from the ``changes`` array use relative xpaths which are
+based on the xpath of the list entry.
+
+The ``access-list`` optional leaf can be either modified or deleted
+depending whether the optional *WORD* parameter is present or not.
+
+When deleting a list entry, all non-key leaves can be ignored:
+
+.. code:: c
+
+   DEFPY (no_rip_distance_source,
+          no_rip_distance_source_cmd,
+          "no distance (1-255) A.B.C.D/M$prefix [WORD$acl]",
+          NO_STR
+          "Administrative distance\n"
+          "Distance value\n"
+          "IP source prefix\n"
+          "Access list name\n")
+   {
+           char xpath_list[XPATH_MAXLEN];
+           struct cli_config_change changes[] = {
+                   {
+                           .xpath = ".",
+                           .operation = NB_OP_DELETE,
+                   },
+           };
+
+           snprintf(xpath_list, sizeof(xpath_list), "./distance/source[prefix='%s']",
+                    prefix_str);
+
+           return nb_cli_cfg_change(vty, xpath_list, changes, 1);
+   }
+
+Example 5
+^^^^^^^^^
+
+This example shows a DEFPY statement that performs two validations
+before calling ``nb_cli_cfg_change()``:
+
+.. code:: c
+
+   DEFPY (ip_rip_authentication_string,
+          ip_rip_authentication_string_cmd,
+          "ip rip authentication string LINE$password",
+          IP_STR
+          "Routing Information Protocol\n"
+          "Authentication control\n"
+          "Authentication string\n"
+          "Authentication string\n")
+   {
+           struct cli_config_change changes[] = {
+                   {
+                           .xpath = "./frr-ripd:rip/authentication/password",
+                           .operation = NB_OP_MODIFY,
+                           .value = password,
+                   },
+           };      
+           
+           if (strlen(password) > 16) {
+                   vty_out(vty,
+                           "%% RIPv2 authentication string must be shorter than 16\n");
+                   return CMD_WARNING_CONFIG_FAILED;
+           }
+                                       
+           if (yang_dnode_exists(vty->candidate_config->dnode, "%s%s",
+                                 VTY_GET_XPATH,
+                                 "/frr-ripd:rip/authentication/key-chain")) {
+                   vty_out(vty, "%% key-chain configuration exists\n");
+                   return CMD_WARNING_CONFIG_FAILED;
+           }
+
+           return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+   }       
+
+These two validations are not strictly necessary since the configuration
+change is validated using libyang afterwards. The issue with the libyang
+validation is that the error messages from libyang are too verbose:
+
+::
+
+   ripd# conf t
+   ripd(config)# interface eth0
+   ripd(config-if)# ip rip authentication string XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+   % Failed to edit candidate configuration.
+
+   Value "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" does not satisfy the constraint "1..16" (range, length, or pattern).
+   Failed to create node "authentication-password" as a child of "rip".
+   YANG path: /frr-interface:lib/interface[name='eth0'][vrf='Default-IP-Routing-Table']/frr-ripd:rip/authentication-password
+
+On the other hand, the original error message from ripd is much cleaner:
+
+::
+
+   ripd# conf t
+   ripd(config)# interface eth0
+   ripd(config-if)# ip rip authentication string XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+   % RIPv2 authentication string must be shorter than 16
+
+The second validation is a bit more complex. If we try to create the
+``authentication/password`` leaf when the ``authentication/key-chain``
+leaf already exists (both are under a YANG *choice* statement), libyang
+will automatically delete the ``authentication/key-chain`` and create
+``authentication/password`` on its place. This is different from the
+original ripd behavior where the *ip rip authentication key-chain*
+command must be removed before configuring the *ip rip authentication
+string* command.
+
+In the spirit of not introducing any backward-incompatible changes in
+the CLI, converted commands should retain some of their validation
+checks to preserve their original behavior.
+
+Step 6: implement the ``cli_show`` callbacks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The traditional method used by FRR to display the running configuration
+consists of looping through all CLI nodes all call their ``func``
+callbacks one by one, which in turn read the configuration from internal
+variables and dump them to the terminal in the form of CLI commands.
+
+The problem with this approach is twofold. First, since the callbacks
+read the configuration from internal variables, they can’t display
+anything other than the running configuration. Second, they don’t have
+the ability to display default values when requested by the user
+(e.g. *show configuration candidate with-defaults*).
+
+The new northbound architecture solves these problems by introducing a
+new callback: ``cli_show``. Here’s the signature of this function (taken
+from the *lib/northbound.h* file):
+
+.. code:: c
+
+           /*
+            * Optional callback to show the CLI command associated to the given
+            * YANG data node.
+            *
+            * vty
+            *    the vty terminal to dump the configuration to
+            *
+            * dnode
+            *    libyang data node that should be shown in the form of a CLI
+            *    command
+            *
+            * show_defaults
+            *    specify whether to display default configuration values or not.
+            *    This parameter can be ignored most of the time since the
+            *    northbound doesn't call this callback for default leaves or
+            *    non-presence containers that contain only default child nodes.
+            *    The exception are commands associated to multiple configuration
+            *    options, in which case it might be desirable to hide one or more
+            *    parts of the command when this parameter is set to false.
+            */
+           void (*cli_show)(struct vty *vty, struct lyd_node *dnode,
+                            bool show_defaults);
+
+One of the main differences to the old CLI ``func`` callbacks is that
+the ``cli_show`` callbacks are associated to YANG data paths and not to
+CLI nodes. This means we can define one separate callback for each CLI
+command, making the code more modular and easier to maintain (among
+other advantages that will be more clear later). For enhanced code
+readability, it’s recommended to position the ``cli_show`` callbacks
+immediately after their associated command definitions (DEFPYs).
+
+The ``cli_show`` callbacks are used by the ``nb_cli_show_config_cmds()``
+function to display configurations stored inside ``nb_config``
+structures. The configuration being displayed can be anything from the
+running configuration (*show configuration running*), a candidate
+configuration (*show configuration candidate*) or a rollback
+configuration (*show configuration transaction (1-4294967296)*). The
+``nb_cli_show_config_cmds()`` function works by iterating over all data
+nodes from the given configuration and calling the ``cli_show`` callback
+for the nodes where it’s defined. If a list has dozens of entries, the
+``cli_show`` callback associated to this list will be called multiple
+times with the ``dnode`` parameter pointing to different list entries on
+each iteration.
+
+For backward compatibility with the *show running-config* command, we
+can’t get rid of the CLI ``func`` callbacks at this point in time.
+However, we can make the CLI ``func`` callbacks call the corresponding
+``cli_show`` callbacks to avoid code duplication. The
+``nb_cli_show_dnode_cmds()`` function can be used for that purpose. Once
+the CLI retrofitting process finishes for all FRR daemons, we can remove
+the legacy CLI ``func`` callbacks and turn *show running-config* into a
+shorthand for *show configuration running*.
+
+Regarding displaying configuration with default values, this is
+something that is taken care of by the ``nb_cli_show_config_cmds()``
+function itself. When the *show configuration* command is used without
+the *with-defaults* option, ``nb_cli_show_config_cmds()`` will skip
+calling ``cli_show`` callbacks for data nodes that contain only default
+values (e.g. default leaves or non-presence containers that contain only
+default child nodes). There are however some exceptional cases where the
+implementer of the ``cli_show`` callback should take into consideration
+if default values should be displayed or not. This and other concepts
+will be explained in more detail in the examples below.
+
+.. _example-1-1:
+
+Example 1
+^^^^^^^^^
+
+Command: ``default-metric (1-16)``
+
+YANG representation:
+
+.. code:: yang
+
+         leaf default-metric {
+           type uint8 {
+             range "1..16";
+           }
+           default "1";
+           description
+             "Default metric of redistributed routes.";
+         }
+
+Placement of the ``cli_show`` callback:
+
+.. code:: diff
+
+           {
+               .xpath = "/frr-ripd:ripd/instance/default-metric",
+               .cbs.modify = ripd_instance_default_metric_modify,
+   +           .cbs.cli_show = cli_show_rip_default_metric,
+           },
+
+Implementation of the ``cli_show`` callback:
+
+.. code:: c
+
+   void cli_show_rip_default_metric(struct vty *vty, struct lyd_node *dnode,
+                                    bool show_defaults)
+   {
+           vty_out(vty, " default-metric %s\n",
+                   yang_dnode_get_string(dnode, NULL));
+   }
+
+In this first example, the *default-metric* command was modeled using a
+YANG leaf, and we added a new ``cli_show`` callback attached to the YANG
+path of this leaf.
+
+The callback makes use of the ``yang_dnode_get_string()`` function to
+obtain the string value of the configuration option. The following would
+also be possible:
+
+.. code:: c
+
+           vty_out(vty, " default-metric %u\n",
+                   yang_dnode_get_uint8(dnode, NULL));
+
+Both options are possible because libyang stores both a binary
+representation and a textual representation of all values stored in a
+data node (``lyd_node``). For simplicity, it’s recommended to always use
+``yang_dnode_get_string()`` in the ``cli_show`` callbacks.
+
+.. _example-2-1:
+
+Example 2
+^^^^^^^^^
+
+Command: ``router rip``
+
+YANG representation:
+
+.. code:: yang
+
+       container instance {
+         presence "Present if the RIP protocol is enabled.";
+         description
+           "RIP routing instance.";
+         [snip]
+       }
+
+Placement of the ``cli_show`` callback:
+
+.. code:: diff
+
+           {
+               .xpath = "/frr-ripd:ripd/instance",
+               .cbs.create = ripd_instance_create,
+               .cbs.delete = ripd_instance_delete,
+   +           .cbs.cli_show = cli_show_router_rip,
+           },
+
+Implementation of the ``cli_show`` callback:
+
+.. code:: c
+
+   void cli_show_router_rip(struct vty *vty, struct lyd_node *dnode,
+                            bool show_defaults)
+   {
+           vty_out(vty, "!\n");
+           vty_out(vty, "router rip\n");
+   }
+
+In this example, the ``cli_show`` callback doesn’t need to obtain any
+value from the ``dnode`` parameter since presence-containers don’t hold
+any data (apart from their child nodes, but they have their own
+``cli_show`` callbacks).
+
+.. _example-3-1:
+
+Example 3
+^^^^^^^^^
+
+Command: ``timers basic (5-2147483647) (5-2147483647) (5-2147483647)``
+
+YANG representation:
+
+.. code:: yang
+
+         container timers {
+           description
+             "Settings of basic timers";
+           leaf flush-interval {
+             type uint32 {
+               range "5..2147483647";
+             }
+             units "seconds";
+             default "120";
+             description
+               "Interval before a route is flushed from the routing
+                table.";
+           }
+           leaf holddown-interval {
+             type uint32 {
+               range "5..2147483647";
+             }
+             units "seconds";
+             default "180";
+             description
+               "Interval before better routes are released.";
+           }
+           leaf update-interval {
+             type uint32 {
+               range "5..2147483647";
+             }
+             units "seconds";
+             default "30";
+             description
+               "Interval at which RIP updates are sent.";
+           }
+         }
+
+Placement of the ``cli_show`` callback:
+
+.. code:: diff
+
+           {
+   +           .xpath = "/frr-ripd:ripd/instance/timers",
+   +           .cbs.cli_show = cli_show_rip_timers,
+   +       },
+   +       {
+               .xpath = "/frr-ripd:ripd/instance/timers/flush-interval",
+               .cbs.modify = ripd_instance_timers_flush_interval_modify,
+           },
+           {
+               .xpath = "/frr-ripd:ripd/instance/timers/holddown-interval",
+               .cbs.modify = ripd_instance_timers_holddown_interval_modify,
+           },
+           {
+               .xpath = "/frr-ripd:ripd/instance/timers/update-interval",
+               .cbs.modify = ripd_instance_timers_update_interval_modify,
+           },
+
+Implementation of the ``cli_show`` callback:
+
+.. code:: c
+
+   void cli_show_rip_timers(struct vty *vty, struct lyd_node *dnode,
+                            bool show_defaults)
+   {
+           vty_out(vty, " timers basic %s %s %s\n",
+                   yang_dnode_get_string(dnode, "./update-interval"),
+                   yang_dnode_get_string(dnode, "./holddown-interval"),
+                   yang_dnode_get_string(dnode, "./flush-interval"));
+   }
+
+This command is a bit different since it changes three leaves at the
+same time. This means we need to have a single ``cli_show`` callback in
+order to display the three leaves together in the same line.
+
+The new ``cli_show_rip_timers()`` callback was added attached to the
+*timers* non-presence container that groups the three leaves. Without
+the *timers* non-presence container we’d need to display the *timers
+basic* command inside the ``cli_show_router_rip()`` callback, which
+would break our requirement of having a separate ``cli_show`` callback
+for each configuration command.
+
+.. _example-4-1:
+
+Example 4
+^^^^^^^^^
+
+Command:
+``redistribute <kernel|connected|static|ospf|isis|bgp|eigrp|nhrp|table|vnc|babel|sharp> [{metric (0-16)|route-map WORD}]``
+
+YANG representation:
+
+.. code:: yang
+
+         list redistribute {
+           key "protocol";
+           description
+             "Redistributes routes learned from other routing protocols.";
+           leaf protocol {
+             type frr-route-types:frr-route-types-v4;
+             description
+               "Routing protocol.";
+             must '. != "rip"';
+           }
+           leaf route-map {
+             type string {
+               length "1..max";
+             }
+             description
+               "Applies the conditions of the specified route-map to
+                routes that are redistributed into the RIP routing
+                instance.";
+           }
+           leaf metric {
+             type uint8 {
+               range "0..16";
+             }
+             description
+               "Metric used for the redistributed route. If a metric is
+                not specified, the metric configured with the
+                default-metric attribute in RIP router configuration is
+                used. If the default-metric attribute has not been
+                configured, the default metric for redistributed routes
+                is 0.";
+           }
+         }
+
+Placement of the ``cli_show`` callback:
+
+.. code:: diff
+
+           {
+               .xpath = "/frr-ripd:ripd/instance/redistribute",
+               .cbs.create = ripd_instance_redistribute_create,
+               .cbs.delete = ripd_instance_redistribute_delete,
+   +           .cbs.cli_show = cli_show_rip_redistribute,
+           },
+           {
+               .xpath = "/frr-ripd:ripd/instance/redistribute/route-map",
+               .cbs.modify = ripd_instance_redistribute_route_map_modify,
+               .cbs.delete = ripd_instance_redistribute_route_map_delete,
+           },
+           {
+               .xpath = "/frr-ripd:ripd/instance/redistribute/metric",
+               .cbs.modify = ripd_instance_redistribute_metric_modify,
+               .cbs.delete = ripd_instance_redistribute_metric_delete,
+           },
+
+Implementation of the ``cli_show`` callback:
+
+.. code:: c
+
+   void cli_show_rip_redistribute(struct vty *vty, struct lyd_node *dnode,
+                                  bool show_defaults)
+   {
+           vty_out(vty, " redistribute %s",
+                   yang_dnode_get_string(dnode, "./protocol"));
+           if (yang_dnode_exists(dnode, "./metric"))
+                   vty_out(vty, " metric %s",
+                           yang_dnode_get_string(dnode, "./metric"));
+           if (yang_dnode_exists(dnode, "./route-map"))
+                   vty_out(vty, " route-map %s",
+                           yang_dnode_get_string(dnode, "./route-map"));
+           vty_out(vty, "\n");
+   }
+
+Similar to the previous example, the *redistribute* command changes
+several leaves at the same time, and we need a single callback to
+display all leaves in a single line in accordance to the CLI command. In
+this case, the leaves are already grouped by a YANG list so there’s no
+need to add a non-presence container. The new ``cli_show`` callback was
+attached to the YANG path of the list.
+
+It’s also worth noting the use of the ``yang_dnode_exists()`` function
+to check if optional leaves exist in the configuration before displaying
+them.
+
+.. _example-5-1:
+
+Example 5
+^^^^^^^^^
+
+Command:
+``ip rip authentication mode <md5 [auth-length <rfc|old-ripd>]|text>``
+
+YANG representation:
+
+.. code:: yang
+
+         container authentication-scheme {
+           description
+             "Specify the authentication scheme for the RIP interface";
+           leaf mode {
+             type enumeration {
+               [snip]
+             }
+             default "none";
+             description
+               "Specify the authentication mode.";
+           }
+           leaf md5-auth-length {
+             when "../mode = 'md5'";
+             type enumeration {
+               [snip]
+             }
+             default "20";
+             description
+               "MD5 authentication data length.";
+           }
+         }
+
+Placement of the ``cli_show`` callback:
+
+.. code:: diff
+
+   +       {
+   +           .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme",
+   +           .cbs.cli_show = cli_show_ip_rip_authentication_scheme,
+           },
+           {
+               .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/mode",
+               .cbs.modify = lib_interface_rip_authentication_scheme_mode_modify,
+           },
+           {
+               .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/md5-auth-length",
+               .cbs.modify = lib_interface_rip_authentication_scheme_md5_auth_length_modify,
+               .cbs.delete = lib_interface_rip_authentication_scheme_md5_auth_length_delete,
+           },
+
+Implementation of the ``cli_show`` callback:
+
+.. code:: c
+
+   void cli_show_ip_rip_authentication_scheme(struct vty *vty,
+                                              struct lyd_node *dnode,
+                                              bool show_defaults)
+   {
+           switch (yang_dnode_get_enum(dnode, "./mode")) {
+           case RIP_NO_AUTH:
+                   vty_out(vty, " no ip rip authentication mode\n");
+                   break;
+           case RIP_AUTH_SIMPLE_PASSWORD:
+                   vty_out(vty, " ip rip authentication mode text\n");
+                   break;
+           case RIP_AUTH_MD5:
+                   vty_out(vty, " ip rip authentication mode md5");
+                   if (show_defaults
+                       || !yang_dnode_is_default(dnode, "./md5-auth-length")) {
+                           if (yang_dnode_get_enum(dnode, "./md5-auth-length")
+                               == RIP_AUTH_MD5_SIZE)
+                                   vty_out(vty, " auth-length rfc");
+                           else
+                                   vty_out(vty, " auth-length old-ripd");
+                   }
+                   vty_out(vty, "\n");
+                   break;
+           }
+   }
+
+This is the most complex ``cli_show`` callback we have in ripd. Its
+complexity comes from the following: \* The
+``ip rip authentication mode ...`` command changes two YANG leaves at
+the same time. \* Part of the command should be hidden when the
+``show_defaults`` parameter is set to false.
+
+This is the behavior we want to implement:
+
+::
+
+   ripd(config)# interface eth0
+   ripd(config-if)# ip rip authentication mode md5
+   ripd(config-if)#
+   ripd(config-if)# show configuration candidate
+   Configuration:
+   !
+   [snip]
+   !
+   interface eth0
+    ip rip authentication mode md5
+   !
+   end
+   ripd(config-if)#
+   ripd(config-if)# show configuration candidate with-defaults
+   Configuration:
+   !
+   [snip]
+   !
+   interface eth0
+    [snip]
+    ip rip authentication mode md5 auth-length old-ripd
+   !
+   end
+
+Note that ``auth-length old-ripd`` should be hidden unless the
+configuration is shown using the *with-defaults* option. This is why the
+``cli_show_ip_rip_authentication_scheme()`` callback needs to consult
+the value of the *show_defaults* parameter. It’s expected that only a
+very small minority of all ``cli_show`` callbacks will need to consult
+the *show_defaults* parameter (there’s a chance this might be the only
+case!)
+
+In the case of the *timers basic* command seen before, we need to
+display the value of all leaves even if only one of them has a value
+different from the default. Hence the ``cli_show_rip_timers()`` callback
+was able to completely ignore the *show_defaults* parameter.
+
+Step 7: consolidation
+~~~~~~~~~~~~~~~~~~~~~
+
+As mentioned in the fourth step, the northbound retrofitting process can
+happen gradually over time, since both “old” and “new” commands can
+coexist without problems. Once all commands from a given daemon were
+converted, we can proceed to the consolidation step, which consists of
+the following: \* Remove the vty configuration lock, which is enabled by
+default in all daemons. Now multiple users should be able to edit the
+configuration concurrently, using either shared or private candidate
+configurations. \* Reference commit:
+`57dccdb1 <https://github.com/opensourcerouting/frr/commit/57dccdb18b799556214dcfb8943e248c0bf1f6a6>`__.
+\* Stop using the qobj infrastructure to keep track of configuration
+objects. This is not necessary anymore, the northbound uses a similar
+mechanism to keep track of YANG data nodes in the candidate
+configuration. \* Reference commit:
+`4e6d63ce <https://github.com/opensourcerouting/frr/commit/4e6d63cebd988af650c1c29d0f2e5a251c8d2e7a>`__.
+\* Make the daemon SIGHUP handler re-read the configuration file (and
+ensure it’s not doing anything other than that). \* Reference commit:
+`5e57edb4 <https://github.com/opensourcerouting/frr/commit/5e57edb4b71ff03f9a22d9ec1412c3c5167f90cf>`__.
+
+Final Considerations
+--------------------
+
+Testing
+^^^^^^^
+
+Converting CLI commands to the new northbound model can be a complicated
+task for beginners, but the more commands one converts, the easier it
+gets. It’s highly recommended to perform as much testing as possible on
+the converted commands to reduce the likelihood of introducing
+regressions. Tools like topotests, ANVL and the `CLI
+fuzzer <https://github.com/rwestphal/frr-cli-fuzzer>`__ can be used to
+catch hidden bugs that might be present. As usual, it’s also recommended
+to use valgrind and static code analyzers to catch other types of
+problems like memory leaks.
+
+Amount of work
+^^^^^^^^^^^^^^
+
+The output below gives a rough estimate of the total number of
+configuration commands that need to be converted per daemon:
+
+.. code:: sh
+
+   $ for dir in lib zebra bgpd ospfd ospf6d isisd ripd ripngd eigrpd pimd pbrd ldpd nhrpd babeld ; do echo -n "$dir: " && cd $dir && grep -ERn "DEFUN|DEFPY" * | grep -Ev "clippy|show|clear" | wc -l && cd ..; done
+   lib: 302
+   zebra: 181
+   bgpd: 569
+   ospfd: 198
+   ospf6d: 99
+   isisd: 126
+   ripd: 64
+   ripngd: 44
+   eigrpd: 58
+   pimd: 113
+   pbrd: 9
+   ldpd: 46
+   nhrpd: 24
+   babeld: 28
+
+As it can be seen, the northbound retrofitting process will demand a lot
+of work from FRR developers and should take months to complete. Everyone
+is welcome to collaborate!
diff --git a/doc/developer/northbound/transactional-cli.rst b/doc/developer/northbound/transactional-cli.rst
new file mode 100644 (file)
index 0000000..439bb6a
--- /dev/null
@@ -0,0 +1,244 @@
+Table of Contents
+-----------------
+
+-  `Introduction <#introduction>`__
+-  `Configuration modes <#config-modes>`__
+-  `New commands <#retrofitting-process>`__
+
+   -  `commit check <#cmd1>`__
+   -  `commit <#cmd2>`__
+   -  `discard <#cmd3>`__
+   -  `configuration database max-transactions <#cmd4>`__
+   -  `configuration load <#cmd5>`__
+   -  `rollback configuration <#cmd6>`__
+   -  `show configuration candidate <#cmd7>`__
+   -  `show configuration compare <#cmd8>`__
+   -  `show configuration running <#cmd9>`__
+   -  `show configuration transaction <#cmd10>`__
+   -  `show yang module <#cmd11>`__
+   -  `show yang module-translator <#cmd12>`__
+   -  `update <#cmd13>`__
+   -  `yang module-translator load <#cmd14>`__
+   -  `yang module-translator unload <#cmd15>`__
+
+Introduction
+~~~~~~~~~~~~
+
+All FRR daemons have built-in support for the CLI, which can be accessed
+either through local telnet or via the vty socket (e.g. by using
+*vtysh*). This will not change with the introduction of the Northbound
+API. However, a new command-line option will be available for all FRR
+daemons: ``--tcli``. When given, this option makes the daemon start with
+a transactional CLI and configuration commands behave a bit different.
+Instead of editing the running configuration, they will edit the
+candidate configuration. In other words, the configuration commands
+won’t be applied immediately, that has to be done on a separate step
+using the new ``commit`` command.
+
+The transactional CLI simply leverages the new capabilities provided by
+the Northbound API and exposes the concept of candidate configurations
+to CLI users too. When the transactional mode is not used, the
+configuration commands also edit the candidate configuration, but
+there’s an implicit ``commit`` after each command.
+
+In order for the transactional CLI to work, all configuration commands
+need to be converted to the new northbound model. Commands not converted
+to the new northbound model will change the running configuration
+directly since they bypass the FRR northbound layer. For this reason,
+starting a daemon with the transactional CLI is not advisable unless all
+of its commands have already been converted. When that’s not the case,
+we can run into a situation like this:
+
+::
+
+   ospfd(config)# router ospf
+   ospfd(config-router)# ospf router-id 1.1.1.1
+   [segfault in ospfd]
+
+The segfault above can happen if ``router ospf`` edits the candidate
+configuration but ``ospf router-id 1.1.1.1`` edits the running
+configuration. The second command tries to set
+``ospf->router_id_static`` but, since the previous ``router ospf``
+command hasn’t been commited yet, the ``ospf`` global variable is set to
+NULL, which leads to the crash. Besides this problem, having a set of
+commands that edit the candidate configuration and others that edit the
+running configuration is confusing at best. The ``--tcli`` option should
+be used only by developers until the northbound retrofitting process is
+complete.
+
+Configuration modes
+~~~~~~~~~~~~~~~~~~~
+
+When using the transactional CLI (``--tcli``), FRR supports three
+different forms of the ``configure`` command: \* ``configure terminal``:
+in this mode, a single candidate configuration is shared by all users.
+This means that one user might delete a configuration object that’s
+being edited by another user, in which case the CLI will detect and
+report the problem. If one user issues the ``commit`` command, all
+changes done by all users are committed. \* ``configure private``: users
+have a private candidate configuration that is edited separately from
+the other users. The ``commit`` command commits only the changes done by
+the user. \* ``configure exclusive``: similar to ``configure private``,
+but also locks the running configuration to prevent other users from
+changing it. The configuration lock is released when the user exits the
+configuration mode.
+
+When using ``configure terminal`` or ``configure private``, the
+candidate configuration being edited might become outdated if another
+user commits a different candidate configuration on another session.
+TODO: show image to illustrate the problem.
+
+New commands
+~~~~~~~~~~~~
+
+The list below contains the new CLI commands introduced by Northbound
+API. The commands are available when a daemon is started using the
+transactional CLI (``--tcli``). Currently ``vtysh`` doesn’t support any
+of these new commands.
+
+Please refer to the [[Demos]] page to see a demo of the transactional
+CLI in action.
+
+--------------
+
+``commit check``
+''''''''''''''''
+
+Check if the candidate configuration is valid or not.
+
+``commit [force] [comment LINE...]``
+''''''''''''''''''''''''''''''''''''
+
+Commit the changes done in the candidate configuration into the running
+configuration.
+
+Options: \* ``force``: commit even if the candidate configuration is
+outdated. It’s usually a better option to use the ``update`` command
+instead. \* ``comment LINE...``: assign a comment to the configuration
+transaction. This comment is displayed when viewing the recorded
+transactions in the output of the ``show configuration transaction``
+command.
+
+``discard``
+'''''''''''
+
+Discard the changes done in the candidate configuration.
+
+``configuration database max-transactions (1-100)``
+'''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Set the maximum number of transactions to store in the rollback log.
+
+``configuration load <file [<json|xml> [translate WORD]] FILENAME|transaction (1-4294967296)> [replace]``
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Load a new configuration into the candidate configuration. When loading
+the configuration from a file, it’s assumed that the configuration will
+be in the form of CLI commands by default. The ``json`` and ``xml``
+options can be used to load configurations in the JSON and XML formats,
+respectively. It’s also possible to load a configuration from a previous
+transaction by specifying the desired transaction ID
+(``(1-4294967296)``).
+
+Options: \* ``translate WORD``: translate the JSON/XML configuration
+file using the YANG module translator. \* ``replace``: replace the
+candidate by the loaded configuration. The default is to merge the
+loaded configuration into the candidate configuration.
+
+``rollback configuration (1-4294967296)``
+'''''''''''''''''''''''''''''''''''''''''
+
+Roll back the running configuration to a previous configuration
+identified by its transaction ID (``(1-4294967296)``).
+
+``show configuration candidate [<json|xml> [translate WORD]] [<with-defaults|changes>]``
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Show the candidate configuration.
+
+Options: \* ``json``: show the configuration in the JSON format. \*
+``xml``: show the configuration in the XML format. \*
+``translate WORD``: translate the JSON/XML output using the YANG module
+translator. \* ``with-defaults``: show default values that are hidden by
+default. \* ``changes``: show only the changes done in the candidate
+configuration.
+
+``show configuration compare <candidate|running|transaction (1-4294967296)> <candidate|running|transaction (1-4294967296)> [<json|xml> [translate WORD]]``
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Show the difference between two different configurations.
+
+Options: \* ``json``: show the configuration differences in the JSON
+format. \* ``xml``: show the configuration differences in the XML
+format. \* ``translate WORD``: translate the JSON/XML output using the
+YANG module translator.
+
+``show configuration running [<json|xml> [translate WORD]] [with-defaults]``
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Show the running configuration.
+
+Options: \* ``json``: show the configuration in the JSON format. \*
+``xml``: show the configuration in the XML format. \*
+``translate WORD``: translate the JSON/XML output using the YANG module
+translator. \* ``with-defaults``: show default values that are hidden by
+default.
+
+   NOTE: ``show configuration running`` shows only the running
+   configuration as known by the northbound layer. Configuration
+   commands not converted to the new northbound model will not be
+   displayed. To show the full running configuration, the legacy
+   ``show running-config`` command must be used.
+
+``show configuration transaction [(1-4294967296) [<json|xml> [translate WORD]] [changes]]``
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+When a transaction ID (``(1-4294967296)``) is given, show the
+configuration associated to the previously committed transaction.
+
+When a transaction ID is not given, show all recorded transactions in
+the rollback log.
+
+Options: \* ``json``: show the configuration in the JSON format. \*
+``xml``: show the configuration in the XML format. \*
+``translate WORD``: translate the JSON/XML output using the YANG module
+translator. \* ``with-defaults``: show default values that are hidden by
+default. \* ``changes``: show changes compared to the previous
+transaction.
+
+``show yang module [module-translator WORD] [WORD <summary|tree|yang|yin>]``
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+When a YANG module is not given, show all loaded YANG modules.
+Otherwise, show detailed information about the given module.
+
+Options: \* ``module-translator WORD``: change the context to modules
+loaded by the specified YANG module translator. \* ``summary``: display
+summary information about the module. \* ``tree``: display module in the
+tree (RFC 8340) format. \* ``yang``: display module in the YANG format.
+\* ``yin``: display module in the YIN format.
+
+``show yang module-translator``
+'''''''''''''''''''''''''''''''
+
+Show all loaded YANG module translators.
+
+``update``
+''''''''''
+
+Rebase the candidate configuration on top of the latest running
+configuration. Conflicts are resolved automatically by giving preference
+to the changes done in the candidate configuration.
+
+The candidate configuration might be outdated if the running
+configuration was updated after the candidate was created.
+
+``yang module-translator load FILENAME``
+''''''''''''''''''''''''''''''''''''''''
+
+Load a YANG module translator from the filesystem.
+
+``yang module-translator unload WORD``
+''''''''''''''''''''''''''''''''''''''
+
+Unload a YANG module translator identified by its name.
diff --git a/doc/developer/northbound/yang-module-translator.rst b/doc/developer/northbound/yang-module-translator.rst
new file mode 100644 (file)
index 0000000..aa527ce
--- /dev/null
@@ -0,0 +1,629 @@
+Table of Contents
+-----------------
+
+-  `Introduction <#introduction>`__
+-  `Deviation Modules <#deviation-modules>`__
+-  `Translation Tables <#translation-tables>`__
+-  `CLI Demonstration <#cli-demonstration>`__
+-  `Implementation Details <#implementation-details>`__
+
+Introduction
+------------
+
+One key requirement for the FRR northbound architecture is that it
+should be possible to configure/monitor FRR using different sets of YANG
+models. This is especially important considering that the industry
+hasn’t reached a consensus to provide a single source of standard models
+for network management. At this moment both the IETF and OpenConfig
+models are widely implemented and are unlikely to converge, at least not
+in the short term. In the ideal scenario, management applications should
+be able to use either IETF or OpenConfig models to configure and monitor
+FRR programatically (or even both at the same time!).
+
+But how can FRR support multiple sets of YANG models at the same time?
+There must be only a single source of truth that models the existing
+implementation accurately (the native models). Writing different code
+paths or callbacks for different models would be inviable, it would lead
+to a lot of duplicated code and extra maintenance overhead.
+
+In order to support different sets of YANG modules without introducing
+the overhead of writing additional code, the solution is to create a
+mechanism that dynamically translates YANG instance data between
+non-native models to native models and vice-versa. Based on this idea,
+an experimental YANG module translator was implemented within the FRR
+northbound layer. The translator works by translating XPaths at runtime
+using translation tables provided by the user. The translator itself is
+modeled using YANG and users can create translators using simple JSON
+files.
+
+A YANG module translator consists of two components: deviation modules
+and translation tables.
+
+Deviation Modules
+-----------------
+
+The first step when writing a YANG module translator is to create a
+`deviations <https://tools.ietf.org/html/rfc7950#page-131>`__ module for
+each module that is going be translated. This is necessary because in
+most cases it won’t be possible to create a perfect translator that
+covers the non-native models on their entirety. Some non-native modules
+might contain nodes that can’t be mapped to a corresponding node in the
+FRR native models. This is either because the corresponding
+functionality is not implemented in FRR or because it’s modeled in a
+different way that is incompatible.
+
+An an example, *ripd* doesn’t have BFD support yet, so we need to create
+a YANG deviation to modify the *ietf-rip* module and remove the ``bfd``
+container from it:
+
+.. code:: yang
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:interfaces/ietf-rip:interface/ietf-rip:bfd" {
+       deviate not-supported;
+     }
+
+In the example below, while both the *frr-ripd* and *ietf-rip* modules
+support RIP authentication, they model the authentication data in
+different ways, making translation not possible given the constraints of
+the current module translator. A new deviation is necessary to remove
+the ``authentication`` container from the *ietf-rip* module:
+
+.. code:: yang
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:interfaces/ietf-rip:interface/ietf-rip:authentication" {
+       deviate not-supported;
+     }
+
+..
+
+   NOTE: it should be possible to translate the
+   ``ietf-rip:authentication`` container if the *frr-ripd* module is
+   modified to model the corresponding data in a compatible way. Another
+   option is to improve the module translator to make more complex
+   translations possible, instead of requiring one-to-one XPath
+   mappings.
+
+Sometimes creating a mapping between nodes from the native and
+non-native models is possible, but the nodes have different properties
+that need to be normalized to allow the translation. In the example
+below, a YANG deviation is used to change the type and the default value
+from a node from the ``ietf-rip`` module.
+
+.. code:: yang
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:timers/ietf-rip:flush-interval" {
+       deviate replace {
+         default "120";
+       }
+       deviate replace {
+         type uint32;
+       }
+     }
+
+The deviation modules allow the management applications to know which
+parts of the custom modules (e.g. IETF/OC) can be used to configure and
+monitor FRR.
+
+In order to facilitate the process of creating YANG deviation modules,
+the *gen_yang_deviations* tool was created to automate part of the
+process. This tool creates a “not-supported” deviation for all nodes
+from the given non-native module. Example:
+
+::
+
+   $ tools/gen_yang_deviations ietf-rip > yang/ietf/frr-deviations-ietf-rip.yang
+   $ head -n 40 yang/ietf/frr-deviations-ietf-rip.yang
+     deviation "/ietf-rip:clear-rip-route" {
+       deviate not-supported;
+     }
+
+     deviation "/ietf-rip:clear-rip-route/ietf-rip:input" {
+       deviate not-supported;
+     }
+
+     deviation "/ietf-rip:clear-rip-route/ietf-rip:input/ietf-rip:rip-instance" {
+       deviate not-supported;
+     }
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip" {
+       deviate not-supported;
+     }
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route" {
+       deviate not-supported;
+     }
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route/ietf-rip:enabled" {
+       deviate not-supported;
+     }
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route/ietf-rip:route-policy" {
+       deviate not-supported;
+     }
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:default-metric" {
+       deviate not-supported;
+     }
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:distance" {
+       deviate not-supported;
+     }
+
+     deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:triggered-update-threshold" {
+       deviate not-supported;
+     }
+
+Once all existing nodes are listed in the deviation module, it’s easy to
+check the deviations that need to be removed or modified. This is more
+convenient than starting with a blank deviations module and listing
+manually all nodes that need to be deviated.
+
+After removing and/or modifying the auto-generated deviations, the next
+step is to write the module XPath translation table as we’ll see in the
+next section. Before that, it’s possible to use the *yanglint* tool to
+check how the non-native module looks like after applying the
+deviations. Example:
+
+::
+
+   $ yanglint -f tree yang/ietf/ietf-rip@2018-02-03.yang yang/ietf/frr-deviations-ietf-rip.yang
+   module: ietf-rip
+
+     augment /ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol:
+       +--rw rip
+          +--rw originate-default-route
+          |  +--rw enabled?   boolean <false>
+          +--rw default-metric?            uint8 <1>
+          +--rw distance?                  uint8 <0>
+          +--rw timers
+          |  +--rw update-interval?     uint32 <30>
+          |  +--rw holddown-interval?   uint32 <180>
+          |  +--rw flush-interval?      uint32 <120>
+          +--rw interfaces
+          |  +--rw interface* [interface]
+          |     +--rw interface        ietf-interfaces:interface-ref
+          |     +--rw split-horizon?   enumeration <simple>
+          +--ro ipv4
+             +--ro neighbors
+             |  +--ro neighbor* [ipv4-address]
+             |     +--ro ipv4-address        ietf-inet-types:ipv4-address
+             |     +--ro last-update?        ietf-yang-types:date-and-time
+             |     +--ro bad-packets-rcvd?   ietf-yang-types:counter32
+             |     +--ro bad-routes-rcvd?    ietf-yang-types:counter32
+             +--ro routes
+                +--ro route* [ipv4-prefix]
+                   +--ro ipv4-prefix    ietf-inet-types:ipv4-prefix
+                   +--ro next-hop?      ietf-inet-types:ipv4-address
+                   +--ro interface?     ietf-interfaces:interface-ref
+                   +--ro metric?        uint8
+
+     rpcs:
+       +---x clear-rip-route
+
+..
+
+   NOTE: the same output can be obtained using the
+   ``show yang module module-translator ietf ietf-rip tree`` command in
+   FRR once the *ietf* module translator is loaded.
+
+In the example above, it can be seen that the vast majority of the
+*ietf-rip* nodes were removed because of the “not-supported” deviations.
+When a module translator is loaded, FRR calculates the coverage of the
+translator by dividing the number of YANG nodes before applying the
+deviations by the number of YANG nodes after applying the deviations.
+The calculated coverage is displayed in the output of the
+``show yang module-translator`` command:
+
+::
+
+   ripd# show yang module-translator
+    Family  Module           Deviations                      Coverage (%)
+    -----------------------------------------------------------------------
+    ietf    ietf-interfaces  frr-deviations-ietf-interfaces  3.92
+    ietf    ietf-routing     frr-deviations-ietf-routing     1.56
+    ietf    ietf-rip         frr-deviations-ietf-rip         13.60
+
+As it can be seen in the output above, the *ietf* module translator
+covers only ~13% of the original *ietf-rip* module. This is in part
+because the *ietf-rip* module models both RIPv2 and RIPng. Also,
+*ietf-rip.yang* contains several knobs that aren’t implemented in *ripd*
+yet (e.g. BFD support, per-interface timers, statistics, etc). Work can
+be done over time to increase the coverage to a more reasonable number.
+
+Translation Tables
+------------------
+
+Below is an example of a translator for the IETF family of models:
+
+.. code:: json
+
+   {
+     "frr-module-translator:frr-module-translator": {
+       "family": "ietf",
+       "module": [
+         {
+           "name": "ietf-interfaces@2018-01-09",
+           "deviations": "frr-deviations-ietf-interfaces",
+           "mappings": [
+             {
+               "custom": "/ietf-interfaces:interfaces/interface[name='KEY1']",
+               "native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']"
+             },
+             {
+               "custom": "/ietf-interfaces:interfaces/interface[name='KEY1']/description",
+               "native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']/description"
+             }
+           ]
+         },
+         {
+           "name": "ietf-routing@2018-01-25",
+           "deviations": "frr-deviations-ietf-routing",
+           "mappings": [
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']",
+               "native": "/frr-ripd:ripd/instance"
+             }
+           ]
+         },
+         {
+           "name": "ietf-rip@2018-02-03",
+           "deviations": "frr-deviations-ietf-rip",
+           "mappings": [
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/default-metric",
+               "native": "/frr-ripd:ripd/instance/default-metric"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/distance",
+               "native": "/frr-ripd:ripd/instance/distance/default"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/originate-default-route/enabled",
+               "native": "/frr-ripd:ripd/instance/default-information-originate"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/update-interval",
+               "native": "/frr-ripd:ripd/instance/timers/update-interval"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/holddown-interval",
+               "native": "/frr-ripd:ripd/instance/timers/holddown-interval"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/flush-interval",
+               "native": "/frr-ripd:ripd/instance/timers/flush-interval"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']",
+               "native": "/frr-ripd:ripd/instance/interface[.='KEY1']"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']/split-horizon",
+               "native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']/frr-ripd:rip/split-horizon"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']",
+               "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/last-update",
+               "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/last-update"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/bad-packets-rcvd",
+               "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/bad-packets-rcvd"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/bad-routes-rcvd",
+               "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/bad-routes-rcvd"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']",
+               "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/next-hop",
+               "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/next-hop"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/interface",
+               "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/interface"
+             },
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/metric",
+               "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/metric"
+             },
+             {
+               "custom": "/ietf-rip:clear-rip-route",
+               "native": "/frr-ripd:clear-rip-route"
+             }
+           ]
+         }
+       ]
+     }
+   }
+
+The main motivation to use YANG itself to model YANG module translators
+was a practical one: leverage *libyang* to validate the structure of the
+user input (JSON files) instead of doing that manually in the
+*lib/yang_translator.c* file (tedious and error-prone work).
+
+Module translators can be loaded using the following CLI command:
+
+::
+
+   ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json
+   % Module translator "ietf" loaded successfully.
+
+Module translators can also be loaded/unloaded programatically using the
+``yang_translator_load()/yang_translator_unload()`` functions within the
+northbound plugins. These functions are documented in the
+*lib/yang_translator.h* file.
+
+Each module translator must be assigned a “family” identifier
+(e.g. IETF, OpenConfig), and can contain mappings for multiple
+interrelated YANG modules. The mappings consist of pairs of
+custom/native XPath expressions that should be equivalent, despite
+belonging to different YANG modules.
+
+Example:
+
+.. code:: json
+
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/default-metric",
+               "native": "/frr-ripd:ripd/instance/default-metric"
+             },
+
+The nodes pointed by the custom and native XPaths must have compatible
+types. In the case of the example above, both nodes point to a YANG leaf
+of type ``uint8``, so the mapping is valid.
+
+In the example below, the “custom” XPath points to a YANG list
+(typeless), and the “native” XPath points to a YANG leaf-list of
+strings. In this exceptional case, the types are also considered to be
+compatible.
+
+.. code:: json
+
+             {
+               "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']",
+               "native": "/frr-ripd:ripd/instance/interface[.='KEY1']"
+             },
+
+The ``KEY1..KEY4`` values have a special meaning and are used to
+preserve the list keys while performing the XPath translation.
+
+Once a YANG module translator is loaded and validated at a syntactic
+level using *libyang*, further validations are performed to check for
+missing mappings (after loading the deviation modules) and incompatible
+YANG types. Example:
+
+::
+
+   ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json
+   % Failed to load "/usr/local/share/yang/ietf/frr-ietf-translator.json"
+
+   Please check the logs for more details.
+
+::
+
+   2018/09/03 15:18:45 RIP: yang_translator_validate_cb: YANG types are incompatible (xpath: "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/default-metric")
+   2018/09/03 15:18:45 RIP: yang_translator_validate_cb: missing mapping for "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/distance"
+   2018/09/03 15:18:45 RIP: yang_translator_validate: failed to validate "ietf" module translator: 2 error(s)
+
+Overall, this translation mechanism based on XPath mappings is simple
+and functional, but only to a certain extent. The native models need to
+be reasonably similar to the models that are going be translated,
+otherwise the translation is compromised and a good coverage can’t be
+achieved. Other translation techniques must be investigated to address
+this shortcoming and make it possible to create more powerful YANG
+module translators.
+
+YANG module translators can be evaluated based on the following metrics:
+\* Translation potential: is it possible to make complex translations,
+taking several variables into account? \* Complexity: measure of how
+easy or hard it is to write a module translator. \* Speed: measure of
+how fast the translation can be achieved. Translation speed is of
+fundamental importance, especially for operational data. \* Robustness:
+can the translator be checked for inconsistencies at load time? A module
+translator based on scripts wouldn’t fare well on this metric. \*
+Round-trip conversions: can the translated data be translated back to
+the original format without information loss?
+
+CLI Demonstration
+-----------------
+
+As of now the only northbound client that supports the YANG module
+translator is the FRR embedded CLI. The confd and sysrepo plugins need
+to be extended to support the module translator, which might be used not
+only for configuration data, but also for operational data, RPCs and
+notifications.
+
+In this demonstration, we’ll use the CLI ``configuration load`` command
+to load the following JSON configuration file specified using the IETF
+data hierarchy:
+
+.. code:: json
+
+   {
+       "ietf-interfaces:interfaces": {
+           "interface": [
+               {
+                   "description": "Engineering",
+                   "name": "eth0"
+               }
+           ]
+       },
+       "ietf-routing:routing": {
+           "control-plane-protocols": {
+               "control-plane-protocol": [
+                   {
+                       "name": "main",
+                       "type": "ietf-rip:ripv2",
+                       "ietf-rip:rip": {
+                           "default-metric": "2",
+                           "distance": "80",
+                           "interfaces": {
+                               "interface": [
+                                   {
+                                       "interface": "eth0",
+                                       "split-horizon": "poison-reverse"
+                                   }
+                               ]
+                           },
+                           "originate-default-route": {
+                               "enabled": "true"
+                           },
+                           "timers": {
+                               "flush-interval": "241",
+                               "holddown-interval": "181",
+                               "update-interval": "31"
+                           }
+                       }
+                   }
+               ]
+           }
+       }
+   }
+
+In order to load this configuration file, it’s necessary to load the
+IETF module translator first. Then, when entering the
+``configuration load`` command, the ``translate ietf`` parameters must
+be given to specify that the input needs to be translated using the
+previously loaded ``ietf`` module translator. Example:
+
+::
+
+   ripd(config)# configuration load file json /mnt/renato/git/frr/yang/example/ietf-rip.json
+   % Failed to load configuration:
+
+   Unknown element "interfaces".
+   ripd(config)# 
+   ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json
+   % Module translator "ietf" loaded successfully.
+
+   ripd(config)# 
+   ripd(config)# configuration load file json translate ietf /mnt/renato/git/frr/yang/example/ietf-rip.json
+
+Now let’s check the candidate configuration to see if the configuration
+file was loaded successfully:
+
+::
+
+   ripd(config)# show configuration candidate     
+   Configuration:
+   !
+   frr version 5.1-dev
+   frr defaults traditional
+   !
+   interface eth0
+    description Engineering
+    ip rip split-horizon poisoned-reverse
+   !
+   router rip
+    default-metric 2
+    distance 80
+    network eth0
+    default-information originate
+    timers basic 31 181 241
+   !
+   end
+   ripd(config)# show configuration candidate json
+   {
+     "frr-interface:lib": {
+       "interface": [
+         {
+           "name": "eth0",
+           "vrf": "default",
+           "description": "Engineering",
+           "frr-ripd:rip": {
+             "split-horizon": "poison-reverse"
+           }
+         }
+       ]
+     },
+     "frr-ripd:ripd": {
+       "instance": {
+         "default-metric": 2,
+         "distance": {
+           "default": 80
+         },
+         "interface": [
+           "eth0"
+         ],
+         "default-information-originate": true,
+         "timers": {
+           "flush-interval": 241,
+           "holddown-interval": 181,
+           "update-interval": 31
+         }
+       }
+     }
+   }
+
+As it can be seen, the candidate configuration is identical to the one
+defined in the *ietf-rip.json* file, only the structure is different.
+This means that the *ietf-rip.json* file was translated successfully.
+
+The ``ietf`` module translator can also be used to do the translation in
+other direction: transform data from the native format to the IETF
+format. This is shown below by altering the output of the
+``show configuration candidate json`` command using the
+``translate ietf`` parameter:
+
+::
+
+   ripd(config)# show configuration candidate json translate ietf
+   {
+     "ietf-interfaces:interfaces": {
+       "interface": [
+         {
+           "name": "eth0",
+           "description": "Engineering"
+         }
+       ]
+     },
+     "ietf-routing:routing": {
+       "control-plane-protocols": {
+         "control-plane-protocol": [
+           {
+             "type": "ietf-rip:ripv2",
+             "name": "main",
+             "ietf-rip:rip": {
+               "interfaces": {
+                 "interface": [
+                   {
+                     "interface": "eth0",
+                     "split-horizon": "poison-reverse"
+                   }
+                 ]
+               },
+               "default-metric": 2,
+               "distance": 80,
+               "originate-default-route": {
+                 "enabled": true
+               },
+               "timers": {
+                 "flush-interval": 241,
+                 "holddown-interval": 181,
+                 "update-interval": 31
+               }
+             }
+           }
+         ]
+       }
+     }
+   }
+
+As expected, this output is exactly identical to the configuration
+defined in the *ietf-rip.json* file. The module translator was able to
+do a round-trip conversion without information loss.
+
+Implementation Details
+----------------------
+
+A different libyang context is allocated for each YANG module
+translator. This is important to avoid collisions and ensure that
+non-native data can’t be instantiated in the running and candidate
+configurations.
diff --git a/doc/developer/northbound/yang-tools.rst b/doc/developer/northbound/yang-tools.rst
new file mode 100644 (file)
index 0000000..4eb1a2f
--- /dev/null
@@ -0,0 +1,106 @@
+yanglint cheat sheet
+~~~~~~~~~~~~~~~~~~~~
+
+   libyang project includes a feature-rich tool called yanglint(1) for
+   validation and conversion of the schemas and YANG modeled data. The
+   source codes are located at /tools/lint and can be used to explore
+   how an application is supposed to use the libyang library.
+   yanglint(1) binary as well as its man page are installed together
+   with the library itself.
+
+Validate a YANG module:
+
+.. code:: sh
+
+   $ yanglint -p <yang-search-path> module.yang
+
+Generate tree representation of a YANG module:
+
+.. code:: sh
+
+   $ yanglint -p <yang-search-path> -f tree module.yang
+
+Validate JSON/XML instance data:
+
+.. code:: sh
+
+   $ yanglint -p <yang-search-path> module.yang data.{json,xml}
+
+Convert JSON/XML instance data to another format:
+
+.. code:: sh
+
+   $ yanglint -p <yang-search-path> -f xml module.yang data.json
+   $ yanglint -p <yang-search-path> -f json module.yang data.xml
+
+*yanglint* also features an interactive mode which is very useful when
+needing to validate data from multiple modules at the same time. The
+*yanglint* README provides several examples:
+https://github.com/CESNET/libyang/blob/master/tools/lint/examples/README.md
+
+Man page (groff):
+https://github.com/CESNET/libyang/blob/master/tools/lint/yanglint.1
+
+pyang cheat sheet
+~~~~~~~~~~~~~~~~~
+
+   pyang is a YANG validator, transformator and code generator, written
+   in python. It can be used to validate YANG modules for correctness,
+   to transform YANG modules into other formats, and to generate code
+   from the modules.
+
+Obtaining and installing pyang:
+
+.. code:: sh
+
+   $ git clone https://github.com/mbj4668/pyang.git
+   $ cd pyang/
+   $ sudo python setup.py install
+
+Validate a YANG module:
+
+.. code:: sh
+
+   $ pyang --ietf -p <yang-search-path> module.yang
+
+Generate tree representation of a YANG module:
+
+.. code:: sh
+
+   $ pyang -f tree -p <yang-search-path> module.yang
+
+Indent a YANG file:
+
+.. code:: sh
+
+   $ pyang -p <yang-search-path> \
+       --keep-comments -f yang --yang-canonical \
+       module.yang -o module.yang
+
+Generate skeleton instance data: \* XML:
+
+.. code:: sh
+
+   $ pyang -p <yang-search-path> \
+       -f sample-xml-skeleton --sample-xml-skeleton-defaults \
+       module.yang [augmented-module1.yang ...] -o module.xml
+
+-  JSON:
+
+.. code:: sh
+
+   $ pyang -p <yang-search-path> \
+       -f jsonxsl module.yang -o module.xsl
+   $ xsltproc -o module.json module.xsl module.xml
+
+Validate XML instance data (works only with YANG 1.0):
+
+.. code:: sh
+
+   $ yang2dsdl -v module.xml module.yang
+
+vim
+~~~
+
+YANG syntax highlighting for vim:
+https://github.com/nathanalderson/yang.vim
index 840afa9f74fccb6a64b5036c33612727dbb35f09..f052956c5431e43ed5c15e03b9e6534974046497 100644 (file)
@@ -67,6 +67,19 @@ dev_RSTFILES = \
        doc/developer/workflow.rst \
        doc/developer/xrefs.rst \
        doc/developer/zebra.rst \
+       doc/developer/northbound/advanced-topics.rst \
+       doc/developer/northbound/architecture.rst \
+       doc/developer/northbound/demos.rst \
+       doc/developer/northbound/links.rst \
+       doc/developer/northbound/northbound.rst \
+       doc/developer/northbound/operational-data-rpcs-and-notifications.rst \
+       doc/developer/northbound/plugins-sysrepo.rst \
+       doc/developer/northbound/ppr-basic-test-topology.rst \
+       doc/developer/northbound/ppr-mpls-basic-test-topology.rst \
+       doc/developer/northbound/retrofitting-configuration-commands.rst \
+       doc/developer/northbound/transactional-cli.rst \
+       doc/developer/northbound/yang-module-translator.rst \
+       doc/developer/northbound/yang-tools.rst \
        # end
 
 EXTRA_DIST += \