Skip to content

Writing New Features

Renato Westphal edited this page Sep 8, 2023 · 4 revisions

Routing protocol stacks are in a constant state of evolution and development. New IETF drafts and RFCs come out on a regular basis specifying protocol extensions and enhancements, and several important features are still lacking implementation, as it can be seen in the Project Wishlist. As such, it's expected that the Holo codebase will be regularly updated to support an expanding range of features.

While the nature of protocol features may vary, this document serves as a practical guide for implementing any new feature. It's worth noting that not all the listed steps will apply to every new feature. Moreover, the order of these steps don't need to be be rigidly followed. Depending on the specific feature and its complexity, it might be easier to do a little bit of everything simultaneously. Nonetheless, the suggested step order offers a useful roadmap for the development process.

A preliminary step involves a thorough understanding of the protocol feature before diving into its implementation. A good implementation requires a clear understanding of the task at hand. Reading the relevant RFCs or drafts at least once and experimenting with the feature using other vendors, whenever possible, is highly recommended.

Step 1: Update YANG modules

Holo natively implements standard YANG modules from IETF. Those modules define which configuration options are available, what state information is exposed to the user, among other things. When implementing a new feature, it's essential to check whether the IETF modules already cover that feature. In some cases, the feature is supported as long as a "feature" statement is enabled. For example:

  feature graceful-restart {
    description
      "Graceful OSPF restart as defined in RFCs 3623 and 5187.";
    reference
      "RFC 3623: Graceful OSPF Restart
       RFC 5187: OSPFv3 Graceful Restart";
  }

In such cases, update the YANG_FEATURES global variable to indicate the feature's implementation:

// All features currently supported.
pub static YANG_FEATURES: Lazy<HashMap<&'static str, Vec<&'static str>>> =
    Lazy::new(|| {
        hashmap! {
            [snip]
            "ietf-ospf" => vec![
                [snip]
                "graceful-restart",

It's also possible that the feature is supported by an extension module not yet imported into the Holo codebase. In this case, import the module to the holo-yang/modules/ietf directory, and update the YANG_EMBEDDED_MODULES and YANG_IMPLEMENTED_MODULES global variables accordingly. Also, the yang_modules() function from protocol-crate/src/northbound/mod.rs needs to be updated to include the new module.

For discovering IETF YANG modules, visit the following GitHub repository: https://github.com/YangModels/yang. You'll find RFC modules in the standard/ietf/RFC directory and draft modules in the experimental/ietf-extracted-YANG-modules directory. Additionally, the yangcatalog website offers a user-friendly interface to locate the modules you need.

If the IETF YANG modules lack support for the feature, or offer only minimal support, you may need to augment these modules with new nodes. Augmentation modules are located under holo-yang/modules/augmentations. Similar to importing IETF modules, adding new augmentation modules requires updating the YANG_EMBEDDED_MODULES and YANG_IMPLEMENTED_MODULES global variables.

In some cases, you may not want to implement all YANG nodes associated with the new feature. In such instances, update the deviations module of the corresponding IETF module and include "not-supported" deviations as needed. You can facilitate this process by using the yang_deviations tool.

Additionally, you may need to remove existing "not-supported" deviations that are no longer desirable. As an example, once OSPF NSSA is implemented, several NSSA "not-supported" deviations will need to be removed from ietf-ospf-holo-deviations.yang. This is because NSSA is an integral part of the OSPF module instead of being modeled as a feature, so "not-supported" deviations were the only way to remove them from the OSPF main module.

Step 2: Generate skeleton northbound callbacks

After updating the YANG model, which may involve enabling module features, adding augmentations, or removing deviations, proceed to generate skeleton northbound callbacks for the new YANG nodes.

To simplify this process, it's highly recommended to use the yang_callbacks tool, as demonstrated in the example below:

$ cd holo-tools/
$ cargo run --bin yang_callbacks -- --type config --module ietf-ospf > ietf-ospf.config.rs
$ cargo run --bin yang_callbacks -- --type state --module ietf-ospf > ietf-ospf.state.rs
$ cargo run --bin yang_callbacks -- --type rpc --module ietf-ospf > ietf-ospf.rpc.rs

In this example, the generated files will contain skeleton callbacks for all YANG nodes of the ietf-ospf YANG module. Following this, the callbacks relevant to the new YANG nodes should be manually selected and integrated into the appropriate files located within the protocol-crate/src/northbound directory.

Step 3: Extend the "packet" module

Many features require updates to the "packet" module to accommodate new types of protocol messages or modifications to existing message formats, such as the introduction of new flags or TLVs. In such scenarios, it's necessary to update the protocol "packet" module accordingly. This may involve introducing new data structures or extending existing ones by adding extra fields. In either case, it is essential to write code for encoding and decoding the new information based on the newly introduced or revised data structures.

To ensure everything works as intended, it's essential to create new packet unit tests within the protocol-crate/tests/packet subdirectory. These tests will ensure that the updated data structures can be encoded/decoded to/from a byte sequence in network byte order as expected.

Step 4: Update data structures

At this step, the protocol data structures need to be updated to include data required by the new feature. This includes both configuration and state data. There's usually a correspondence between the YANG additions and required data structure updates, so it's a good idea to start by examining the YANG modules. When you make the right choices regarding data structures, the implementation of the feature will follow a smooth and natural course.

Step 5: Implement the northbound callbacks

Depending on the YANG module updates, the following actions may be required:

  • Implementing northbound configuration callbacks.
  • Implementing northbound state callbacks.
  • Implementing northbound RPC callbacks.
  • Implementing northbound notifications.

Please refer to the Northbound Framework page for details on how to get that done.

Step 6: Add event messages

Occasionally, when implementing a new feature, you may need to introduce additional protocol events, such as timers. In such situations, the following steps are necessary:

  • Navigate to the protocol-crate/src/tasks.rs file and add a new message type to the ProtocolMsg enum.
  • Create a corresponding helper function responsible for initiating the new timer within the same tasks.rs file.
  • Modify the ProtocolInputChannelsTx and ProtocolInputChannelsRx structs in the protocol-crate/src/instance.rs file to include new channels that the protocol instance will use to exchange the event message with the timer task. The protocol_input_channels() function also needs to be updated to initialize those channels.

When making changes to network packet formats or introducing new message types, there's typically no need to create additional event messages. Usually, a single "NetworkRx" event suffices to handle all received packets.

Other event types include inter-protocol message exchanges. Some examples include BFD peer status updates and RLFA label updates. In those cases, the internal bus (ibus) is used to allow the protocol instance to communicate with other components. Adding new inter-process event messages requires updating the IbusMsg enum located in the holo-utils/src/ibus.rs file.

Step 7: Implement feature logic

With all the groundwork completed, it's time to start implementing the actual feature logic. The specifics of this step can vary widely based on the task at hand. In many cases, you may need to update the events.rs file to handle new message types, timer events, or adjust the handling of existing events. Changes to helper functions and protocol FSMs are also common when introducing new features. Conversely, for tasks like implementing packet authentication modes, your focus may be primarily on updating the "packet" module.

Step 8: Do interoperability tests

Whenever possible, it's highly recommended to conduct interoperability tests against other implementations to confirm that the implemented feature complies with its specification. For this purpose, the containerlab tool is highly recommended, as it simplifies interoperability testing through lightweight container-based networking labs. A repository containing a variety of pre-configured containerlab topologies is available here.

Step 9: Write conformance tests

For any newly introduced feature, it is imperative to create corresponding conformance tests. These tests serve the dual purpose of demonstrating the feature's functionality and serving as regression tests for future modifications. Ideally, the conformance tests should encompass error path scenarios, ensuring that the code responds appropriately.

For detailed guidance on writing conformance tests, please refer to the Data-Driven Testing page.

Getting Help

Feel free to drop by Holo's Discord server whenever you have questions or need assistance.

Clone this wiki locally