summaryrefslogtreecommitdiffstats
path: root/doc/developer/northbound
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /doc/developer/northbound
parentInitial commit. (diff)
downloadfrr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz
frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'doc/developer/northbound')
-rw-r--r--doc/developer/northbound/advanced-topics.rst294
-rw-r--r--doc/developer/northbound/architecture.rst275
-rw-r--r--doc/developer/northbound/demos.rst27
-rw-r--r--doc/developer/northbound/images/arch-after.pngbin0 -> 18651 bytes
-rw-r--r--doc/developer/northbound/images/arch-before.pngbin0 -> 4360 bytes
-rw-r--r--doc/developer/northbound/images/ly-ctx.pngbin0 -> 7242 bytes
-rw-r--r--doc/developer/northbound/images/lyd-node.pngbin0 -> 21699 bytes
-rw-r--r--doc/developer/northbound/images/lys-node.pngbin0 -> 18018 bytes
-rw-r--r--doc/developer/northbound/images/nb-layer.pngbin0 -> 25388 bytes
-rw-r--r--doc/developer/northbound/images/transactions.pngbin0 -> 21532 bytes
-rw-r--r--doc/developer/northbound/links.rst233
-rw-r--r--doc/developer/northbound/northbound.rst21
-rw-r--r--doc/developer/northbound/operational-data-rpcs-and-notifications.rst565
-rw-r--r--doc/developer/northbound/plugins-sysrepo.rst137
-rw-r--r--doc/developer/northbound/ppr-basic-test-topology.rst1632
-rw-r--r--doc/developer/northbound/ppr-mpls-basic-test-topology.rst1991
-rw-r--r--doc/developer/northbound/retrofitting-configuration-commands.rst1897
-rw-r--r--doc/developer/northbound/transactional-cli.rst244
-rw-r--r--doc/developer/northbound/yang-module-translator.rst629
-rw-r--r--doc/developer/northbound/yang-tools.rst112
20 files changed, 8057 insertions, 0 deletions
diff --git a/doc/developer/northbound/advanced-topics.rst b/doc/developer/northbound/advanced-topics.rst
new file mode 100644
index 0000000..bee29a9
--- /dev/null
+++ b/doc/developer/northbound/advanced-topics.rst
@@ -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
index 0000000..e571971
--- /dev/null
+++ b/doc/developer/northbound/architecture.rst
@@ -0,0 +1,275 @@
+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.
+
+.. figure:: images/arch-before.png
+ :alt: diagram of northbound architecture prior to nbapi conversion
+
+ 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).
+
+.. figure:: images/arch-after.png
+ :alt: diagram of northbound architecture after nbapi conversion
+
+ 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.
+
+.. figure:: images/nb-layer.png
+ :alt: diagram of northbound architecture internals
+
+ 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
+-----------------------
+
+.. figure:: images/lys-node.png
+ :alt: diagram of libyanbg's lys_node data structure
+
+ ``libyang's`` lys_node data structure
+
+
+.. figure:: images/lyd-node.png
+ :alt: diagram of libyanbg's lyd_node data structure
+
+ ``libyang's`` lyd_node data structure
+
+
+.. figure:: images/ly-ctx.png
+ :alt: diagram of libyanbg's ly_ctx data structure
+
+ ``libyang's`` ly_ctx data structure
+
+
+.. figure:: images/transactions.png
+ :alt: diagram showing how configuration transactions work
+
+ 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:
+
+.. code-block:: console
+
+ 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.
diff --git a/doc/developer/northbound/demos.rst b/doc/developer/northbound/demos.rst
new file mode 100644
index 0000000..876bd25
--- /dev/null
+++ b/doc/developer/northbound/demos.rst
@@ -0,0 +1,27 @@
+Transactional CLI
+-----------------
+
+This short demo shows some of the capabilities of the new transactional
+CLI:
+
+|asciicast1|
+
+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
+
+|asciicast2|
+
+.. |asciicast1| image:: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1.png
+ :target: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1
+.. |asciicast2| image:: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv.png
+ :target: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv
diff --git a/doc/developer/northbound/images/arch-after.png b/doc/developer/northbound/images/arch-after.png
new file mode 100644
index 0000000..01e6ae6
--- /dev/null
+++ b/doc/developer/northbound/images/arch-after.png
Binary files differ
diff --git a/doc/developer/northbound/images/arch-before.png b/doc/developer/northbound/images/arch-before.png
new file mode 100644
index 0000000..ab2bb0d
--- /dev/null
+++ b/doc/developer/northbound/images/arch-before.png
Binary files differ
diff --git a/doc/developer/northbound/images/ly-ctx.png b/doc/developer/northbound/images/ly-ctx.png
new file mode 100644
index 0000000..4d4e138
--- /dev/null
+++ b/doc/developer/northbound/images/ly-ctx.png
Binary files differ
diff --git a/doc/developer/northbound/images/lyd-node.png b/doc/developer/northbound/images/lyd-node.png
new file mode 100644
index 0000000..4ba2b48
--- /dev/null
+++ b/doc/developer/northbound/images/lyd-node.png
Binary files differ
diff --git a/doc/developer/northbound/images/lys-node.png b/doc/developer/northbound/images/lys-node.png
new file mode 100644
index 0000000..e9e46e7
--- /dev/null
+++ b/doc/developer/northbound/images/lys-node.png
Binary files differ
diff --git a/doc/developer/northbound/images/nb-layer.png b/doc/developer/northbound/images/nb-layer.png
new file mode 100644
index 0000000..4aa1fd6
--- /dev/null
+++ b/doc/developer/northbound/images/nb-layer.png
Binary files differ
diff --git a/doc/developer/northbound/images/transactions.png b/doc/developer/northbound/images/transactions.png
new file mode 100644
index 0000000..d18faf4
--- /dev/null
+++ b/doc/developer/northbound/images/transactions.png
Binary files differ
diff --git a/doc/developer/northbound/links.rst b/doc/developer/northbound/links.rst
new file mode 100644
index 0000000..e80374c
--- /dev/null
+++ b/doc/developer/northbound/links.rst
@@ -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
index 0000000..7dddf06
--- /dev/null
+++ b/doc/developer/northbound/northbound.rst
@@ -0,0 +1,21 @@
+.. _northbound:
+
+**************
+Northbound API
+**************
+
+.. toctree::
+ :maxdepth: 2
+
+ architecture
+ transactional-cli
+ retrofitting-configuration-commands
+ operational-data-rpcs-and-notifications
+ plugins-sysrepo
+ advanced-topics
+ yang-tools
+ yang-module-translator
+ demos
+ links
+ ppr-basic-test-topology
+ ppr-mpls-basic-test-topology
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
index 0000000..554bc17
--- /dev/null
+++ b/doc/developer/northbound/operational-data-rpcs-and-notifications.rst
@@ -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
index 0000000..186c3a0
--- /dev/null
+++ b/doc/developer/northbound/plugins-sysrepo.rst
@@ -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
index 0000000..a680ed7
--- /dev/null
+++ b/doc/developer/northbound/ppr-basic-test-topology.rst
@@ -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
index 0000000..cedb795
--- /dev/null
+++ b/doc/developer/northbound/ppr-mpls-basic-test-topology.rst
@@ -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
index 0000000..b407246
--- /dev/null
+++ b/doc/developer/northbound/retrofitting-configuration-commands.rst
@@ -0,0 +1,1897 @@
+Retrofitting Configuration Commands
+-----------------------------------
+
+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 :doc:`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
index 0000000..439bb6a
--- /dev/null
+++ b/doc/developer/northbound/transactional-cli.rst
@@ -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
index 0000000..aa527ce
--- /dev/null
+++ b/doc/developer/northbound/yang-module-translator.rst
@@ -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
index 0000000..346efca
--- /dev/null
+++ b/doc/developer/northbound/yang-tools.rst
@@ -0,0 +1,112 @@
+Yang Tools
+~~~~~~~~~~
+
+Here's some information about various tools for working with yang
+models.
+
+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