Skip to content

Conversation

@nrybowski
Copy link
Member

@nrybowski nrybowski commented Jun 30, 2025

This PR adds the support for Reverse Metric and Reverse TE Metric advertisement through the LLS data block of Hello packets, as defined by RFC 9339.

It depends on RFC 5613 whose support has been added with #77.

TODO

  • Add Reverse (TE) Metric LLS TLVs encoding / decoding
    • Add encoding / decoding tests for OSPFv2
    • Add encoding / decoding tests for OSPFv3
  • Enable Reverse Metric configuration
    • Add YANG model. (There is no YANG model specific to OSPF for now, I will adapt the one for IS-IS.)
    • Extend northbound interface
      • Directly send Hello packet upon Reverse Metric enabling / disabling
    • Extend OSPF configuration to store Reverse Metric configuration
  • Implement Reverse Metric logic
    • Update metric if needed upon Reverse Metric reception
    • Trigger Router LSA origination upon Reverse Metric reception
    • Fallback to provisioned metric once Reverse Metric is not advertised anymore by the neighbor.
  • Enable Reverse TE Metric configuration
    • Add YANG model.
    • Extend northbound interface
      • Directly send Hello packet upon Reverse TE Metric enabling / disabling
    • Extend OSPF configuration to store Reverse TE Metric configuration
  • Implement Reverse TE Metric logic
    • Update metric if needed upon Reverse TE Metric reception
    • Trigger Router LSA origination upon Reverse TE Metric reception
    • Fallback to provisioned metric once Reverse TE Metric is not advertised anymore by the neighbor.

@rwestphal
Copy link
Member

Add YANG model. (There is no YANG model specific to OSPF for now, I will adapt the one for IS-IS.)

Indeed. I see there's [email protected] but no equivalent module for OSPF.

There's draft-ietf-lsr-ospf-yang-augmentation-v1, which adds several augmenting modules to ietf-ospf. We can add our own holo-ospf-reverse-metric and potentially ask the IETF folks to integrate it into that draft. That would probably be welcome on their part, especially since it would come with a working implementation.

Copy link
Member

@rwestphal rwestphal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work. I really like how this is shaping up!

As a preliminary review comment, I think the YANG module could be simplified.

Currently, the interface augmentation looks like this:

     +--rw holo-ospf-reverse-metric:reverse-metric
        +--rw holo-ospf-reverse-metric:enable-receive?     boolean
        +--rw holo-ospf-reverse-metric:enable-advertise?   boolean
        +--rw holo-ospf-reverse-metric:config* [mtid]
           +--rw holo-ospf-reverse-metric:mtid      uint8
           +--rw holo-ospf-reverse-metric:metric?   ospf:ospf-link-metric
           +--rw holo-ospf-reverse-metric:flags
              +--rw holo-ospf-reverse-metric:higher?   boolean
              +--rw holo-ospf-reverse-metric:offset?   boolean

It could be simplified to something like this, more in line with the IS-IS RM module:

     +--rw holo-ospf-reverse-metric:reverse-metric
        +--rw holo-ospf-reverse-metric:enable-receive?     boolean
        +--rw holo-ospf-reverse-metric:metric?   ospf:ospf-link-metric
        +--rw holo-ospf-reverse-metric:flags
          +--rw holo-ospf-reverse-metric:higher?   boolean
          +--rw holo-ospf-reverse-metric:offset?   boolean

Later, once support for RFC 4915 is implemented, a separate augmentation could be added for "interface/topologies/topology". I think that would be cleaner, especially since "mtid" only makes sense for OSPFv2.

Also, the "enable-advertise" leaf can be removed, as the presence of "metric" already implies we want to advertise the reverse metric.

What do you think?

@nrybowski
Copy link
Member Author

Thanks for this first review!

Also, the "enable-advertise" leaf can be removed, as the presence of "metric" already implies we want to advertise the reverse metric.

This was also my initial though but when I implemented the Default trait for ReverseMetricMtCfg, I added a default value for the metric which enables 'by default' the RM. A more clever way to implement that would be storing the metric value in an Option so that the default value is None.
Also, RFC9339 states in Section 7 that "An implementation MUST NOT signal RM to neighbors by default and MUST provide a configuration option to enable the signaling of RM on specific links.", and I understood this sentence as "we have to provide an explicit switch to enable the metric".
I will remove the "enable-advertise" leaf and use an Option to store the RM.

Later, once support for RFC 4915 is implemented, a separate augmentation could be added for "interface/topologies/topology".

I prefer defining a module that completely specifies RFC 9339 and then add a deviation that indicates unsupported augmentations.

I think that would be cleaner, especially since "mtid" only makes sense for OSPFv2.

That's a good point that I completely overlooked. I will add two augmentations then, one that accepts a single RM on the interface and enforce that mt-id equals 0 and one for OSPFv2 that adds a RM to "interface/topologies/topology" as you suggested.

We will then add an 'unsupported' deviation for the last one in holo-ospf-dev.yang.

Does that sounds reasonable to you?

nrybowski added a commit to nrybowski/holo that referenced this pull request Oct 3, 2025
This commit addresses the comment from holo-routing#78 (review)
with the suggestion proposed in holo-routing#78 (comment).

Signed-off-by: Nicolas Rybowski <[email protected]>
This patch adds Reverse Metric and Reverse TE Metric TLVs to LLS data
blocks.

This patch hence depends on RFC 5613, added with
holo-routing#77.

Signed-off-by: Nicolas Rybowski <[email protected]>
This commit adds a custom YANG module to configure
Reverse Metrics per interface and the related callbacks.
It also implements the handling of such Reverse Metric
both at the sender and receiver sides.

Signed-off-by: Nicolas Rybowski <[email protected]>
@rwestphal
Copy link
Member

This was also my initial though but when I implemented the Default trait for ReverseMetricMtCfg, I added a default value for the metric which enables 'by default' the RM. A more clever way to implement that would be storing the metric value in an Option so that the default value is None. Also, RFC9339 states in Section 7 that "An implementation MUST NOT signal RM to neighbors by default and MUST provide a configuration option to enable the signaling of RM on specific links.", and I understood this sentence as "we have to provide an explicit switch to enable the metric". I will remove the "enable-advertise" leaf and use an Option to store the RM.

Awesome. Option is a perfect fit for configuration options without default values.

I prefer defining a module that completely specifies RFC 9339 and then add a deviation that indicates unsupported augmentations.

That's indeed better!

That's a good point that I completely overlooked. I will add two augmentations then, one that accepts a single RM on the interface and enforce that mt-id equals 0 and one for OSPFv2 that adds a RM to "interface/topologies/topology" as you suggested.

We will then add an 'unsupported' deviation for the last one in holo-ospf-dev.yang.

Does that sounds reasonable to you?

Sounds good to me.

Just keep in mind that in "interface/topologies/topology" you can configure a topology name, but not an "mtid", which can be a bit confusing. This is probably because the IETF folks couldn't agree on whether the MT-ID should be explicitly configured or inferred from the selected RIB. In any case, this shouldn't affect your augmentation. I just wanted to point it out in case it causes any confusion :)

This commit addresses the comment from holo-routing#78 (review)
with the suggestion proposed in holo-routing#78 (comment).

Signed-off-by: Nicolas Rybowski <[email protected]>
@nrybowski
Copy link
Member Author

nrybowski commented Oct 3, 2025

Just rebased on master to clear the conflicts. 81df85f contains the changes we just discussed.

Just keep in mind that in "interface/topologies/topology" you can configure a topology name, but not an "mtid", which can be a bit confusing. This is probably because the IETF folks couldn't agree on whether the MT-ID should be explicitly configured or inferred from the selected RIB. In any case, this shouldn't affect your augmentation. I just wanted to point it out in case it causes any confusion :)

The good news is that the "mtid" augmentation for OSPFv2 is gated with the feature "multi-topology" so we don't even need a deviation to "unsupport" the augmentation and we can keep the headache of handling that when RFC 4915 is implemented :)

@rwestphal
Copy link
Member

The good news is that the "mtid" augmentation for OSPFv2 is gated with the feature "multi-topology" so we don't even need a deviation to "unsupport" the augmentation and we can keep the headache of handling that when RFC 4915 is implemented :)

Yeah that makes things easier for us :)

From a quick look, it seems most of the RFC is already implemented. Tomorrow I'll run some tests and do a full review. Looking forward to it :)

Copy link
Member

@rwestphal rwestphal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just ran some tests and it's very interesting to see the reverse metric in action! It's quite a useful feature for operators to have in their toolbox.

Please find my review comments below.

// Dynamic value of the interface cost.
pub cost: u16,
// History of known Reverse Metric received on this interface.
pub rms: BTreeMap<u8, (ReverseMetricFlags, u16)>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think s/History of known/Last known/ would be more accurate.

Also, since we don't support RFC 4915 yet, it would be simpler to replace the BTreeMap with an Option. We don't need to keep track of MT-IDs other than zero. If we were to keep the BTreeMap, we would also need to use a BTreeMap for the cost field.

// If it has not changed, we do not update the metric nor we
// originate a new Router LSA.
// If the Reverse Metric from the neighbor has been updated since
// the last one we received, then we propagate the information.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this code, we are handling changes in the reverse metric, but the case where the RM TLV is no longer present is being ignored.

"RFC 9339: OSPF Reverse Metric, Section 4.";
}
}
leaf mtid {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaf should be removed. The MT-ID is a property of the topology, not from the reverse metric. (yes, "interface/topologies/topology" is missing an "mtid" leaf, but that's a separate issue)

@@ -0,0 +1,186 @@
module holo-ospf-reverse-metric {
yang-version 1.1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: both the IETF and Holo YANG modules use two spaces for indentation, not four.

}
}

impl From<&LlsHelloData> for LlsDataBlock {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this instead of implementing From<LlsHelloData> for LlsDataBlock?

None
};
let advertise_rm = iface.config.reverse_metric.metric.is_some();
let lls = (iface.config.lls_enabled & advertise_rm).then(|| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you meant && and not & :)

In any case, this code can be a bit simpler if we use a mutable lls variable:

        // Handle Reverse Metric (RFC 9339).                      
        let mut lls = None;                          
        let rm_cfg = &iface.config.reverse_metric;
        if iface.config.lls_enabled
            && let Some(metric) = rm_cfg.metric
        {           
            let mut lls_data = LlsHelloData::default();
            let mut flags = ReverseMetricFlags::empty();
            if rm_cfg.higher {
                flags |= ReverseMetricFlags::H;
            }       
            if rm_cfg.offset {
                flags |= ReverseMetricFlags::O;
            }                                              
            lls_data.reverse_metric.insert(0, (flags, metric));
            lls = Some(lls_data);
        }

impl From<&LlsDataBlock> for LlsHelloData {
fn from(value: &LlsDataBlock) -> Self {
let mut reverse_metrics = BTreeMap::new();
value.reverse_metric.iter().for_each(|tlv| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is a matter of style, but I'd prefer sticking to regular for loops instead of using for_each. It would be good to keep the codebase consistent by using a single style.

0x04, 0x00, 0x01, 0x12, 0x34, 0x00, 0x13, 0x00, 0x04, 0x01, 0x02,
0x43, 0x21, 0x00, 0x14, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x56, 0x78,
// 0x00, 0x00, 0x00, 0x03,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unnecessary comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants