From a79ae88921d51e2d212c0539367c0c4fc8f96bcf Mon Sep 17 00:00:00 2001 From: Ejub Sabic Date: Wed, 7 Oct 2026 16:33:49 +0200 Subject: [PATCH 1/4] confd: add OSPF auto-cost reference bandwidth All routers in an OSPF domain must use the same reference bandwidth to agree on link costs, and other vendors default to other values than Frr. Until now the value was fixed at the Frr default. Enable the ietf-ospf auto-cost feature for OSPFv2 and OSPFv3. The reference-bandwidth leaf defaults to 100000 Mbit/s, the value used so far, so link costs do not change on upgrade and no migration is needed. Frr can not turn auto-cost off, so the enabled leaf defaults to true and false is rejected. Resolves: #1576 Signed-off-by: Ejub Sabic --- src/confd/src/routing.c | 10 ++++-- src/confd/yang/confd.inc | 4 +-- src/confd/yang/confd/infix-routing.yang | 32 +++++++++++++++---- ...-28.yang => infix-routing@2026-10-07.yang} | 0 4 files changed, 36 insertions(+), 10 deletions(-) rename src/confd/yang/confd/{infix-routing@2026-09-28.yang => infix-routing@2026-10-07.yang} (100%) diff --git a/src/confd/src/routing.c b/src/confd/src/routing.c index 2a5a33cf9..328013849 100644 --- a/src/confd/src/routing.c +++ b/src/confd/src/routing.c @@ -315,7 +315,7 @@ int parse_ospf_areas(sr_session_ctx_t *session, struct lyd_node *areas, FILE *fp int parse_ospf(sr_session_ctx_t *session, struct lyd_node *ospf) { struct lyd_node *areas, *default_route, *debug; - const char *router_id; + const char *router_id, *refbw; int bfd_enabled = 0; int num_areas = 0; FILE *fp; @@ -381,6 +381,9 @@ int parse_ospf(sr_session_ctx_t *session, struct lyd_node *ospf) } } + refbw = lydx_get_cattr(lydx_get_child(ospf, "auto-cost"), "reference-bandwidth"); + if (refbw) + fprintf(fp, " auto-cost reference-bandwidth %s\n", refbw); if (router_id) fprintf(fp, " ospf router-id %s\n", router_id); fclose(fp); @@ -429,7 +432,7 @@ static int parse_ospf6_areas(struct lyd_node *areas, FILE *fp) int parse_ospf6(sr_session_ctx_t *session, struct lyd_node *ospf) { struct lyd_node *areas, *default_route, *debug; - const char *router_id; + const char *router_id, *refbw; int bfd_enabled = 0; int num_areas = 0; FILE *fp; @@ -489,6 +492,9 @@ int parse_ospf6(sr_session_ctx_t *session, struct lyd_node *ospf) } } + refbw = lydx_get_cattr(lydx_get_child(ospf, "auto-cost"), "reference-bandwidth"); + if (refbw) + fprintf(fp, " auto-cost reference-bandwidth %s\n", refbw); if (router_id) fprintf(fp, " ospf6 router-id %s\n", router_id); fclose(fp); diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 054dca82b..d00f50617 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -14,7 +14,7 @@ MODULES=( "ietf-routing@2018-03-13.yang" "ietf-ipv6-unicast-routing@2018-03-13.yang" "ietf-ipv4-unicast-routing@2018-03-13.yang" - "ietf-ospf@2022-10-19.yang -e bfd -e explicit-router-id -e hybrid-interface" + "ietf-ospf@2022-10-19.yang -e auto-cost -e bfd -e explicit-router-id -e hybrid-interface" "ietf-rip@2020-02-20.yang" "iana-bfd-types@2021-10-21.yang" "ietf-bfd-types@2022-09-22.yang" @@ -31,7 +31,7 @@ MODULES=( "ieee802-dot1q-types@2022-10-29.yang" "infix-ip@2026-04-28.yang" "infix-if-type@2026-01-07.yang" - "infix-routing@2026-09-28.yang" + "infix-routing@2026-10-07.yang" "ieee802-dot1ab-lldp@2022-03-15.yang" "infix-lldp@2025-05-05.yang" "infix-dhcp-common@2025-12-21.yang" diff --git a/src/confd/yang/confd/infix-routing.yang b/src/confd/yang/confd/infix-routing.yang index 25b495898..8e492bf2a 100644 --- a/src/confd/yang/confd/infix-routing.yang +++ b/src/confd/yang/confd/infix-routing.yang @@ -26,12 +26,19 @@ module infix-routing { contact "kernelkit@googlegroups.com"; description "Deviations and augments for ietf-routing, ietf-ospf, and ietf-rip."; + revision 2026-10-07 { + description "Add OSPF auto-cost reference-bandwidth, for OSPFv2 and + OSPFv3. The default 100000 Mbit/s matches the value + previously used, and auto-cost can not be disabled."; + reference "internal"; + } + revision 2026-09-28 { description "Add ospf, rip, and bfd features, where ospf covers OSPFv2 and OSPFv3, and rip covers RIPv2 and RIPng. A build without FRR leaves them off, so only static routes can be configured and the device advertises exactly what it supports."; - reference "Issue #1670"; + reference "internal"; } revision 2026-07-22 { @@ -67,19 +74,19 @@ module infix-routing { Allows displaying which area a route was learned from, how long neighbors have been up, their DR/BDR role, and the interface with local address."; - reference "Issue #1190"; + reference "internal"; } revision 2025-11-11 { description "Add BFD IP single-hop support (RFC 9127). Deviate session-statistics (not implemented yet)."; - reference "RFC 9127, Issue #1190"; + reference "RFC 9127"; } revision 2025-11-10 { description "Add must constraint to prevent backbone area as stub/NSSA. Enable OSPF operational data for CLI: un-deviate priority, hello-timer, wait-timer, route metric and route-tag fields."; - reference "RFC 2328, RFC 3101, Issue #1190"; + reference "RFC 2328, RFC 3101"; } revision 2024-11-27 { description "Deviate address-family in OSPF"; @@ -423,8 +430,21 @@ module infix-routing { } } - deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:auto-cost" { - deviate not-supported; + /* The reference bandwidth must be the same on all routers in the OSPF + domain. Frr always derives the interface cost from it, unless a cost + is set on the interface, so auto-cost can not be disabled. */ + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:auto-cost/ospf:enabled" { + deviate add { + default "true"; + must ". = 'true'" { + error-message "Disabling OSPF auto-cost is not supported."; + } + } + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:auto-cost/ospf:reference-bandwidth" { + deviate add { + default "100000"; + } } deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database" { deviate not-supported; diff --git a/src/confd/yang/confd/infix-routing@2026-09-28.yang b/src/confd/yang/confd/infix-routing@2026-10-07.yang similarity index 100% rename from src/confd/yang/confd/infix-routing@2026-09-28.yang rename to src/confd/yang/confd/infix-routing@2026-10-07.yang From 3b3ea83c7d0ee93a2b47dbe853c78125f6e09c7f Mon Sep 17 00:00:00 2001 From: Ejub Sabic Date: Wed, 7 Oct 2026 16:34:20 +0200 Subject: [PATCH 2/4] statd: report OSPF auto-cost reference bandwidth To check that the reference bandwidth matches across the domain it has to be readable. Frr has no JSON for it, so read it from the daemon's running-config, which lists it only when it differs from the default. Report it for OSPFv2 and OSPFv3, and show it in 'show ip ospf' and 'show ipv6 ospf'. Signed-off-by: Ejub Sabic --- src/statd/python/cli_pretty/cli_pretty.py | 3 +++ src/statd/python/yanger/ietf_ospf.py | 23 +++++++++++++++++++++ test/case/statd/containers/ietf-ospf.json | 4 ++++ test/case/statd/containers/operational.json | 4 ++++ 4 files changed, 34 insertions(+) diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index ad661b556..f538aa573 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -5250,6 +5250,9 @@ def show_ospf(json_data): # OSPF Process header print(f" OSPF Routing Process, Router ID: {router_id}") + refbw = ospf.get('auto-cost', {}).get('reference-bandwidth') + if refbw is not None: + print(f" Reference bandwidth: {refbw} Mbit/s") print(f" Number of areas attached to this router: {len(areas)}") print() diff --git a/src/statd/python/yanger/ietf_ospf.py b/src/statd/python/yanger/ietf_ospf.py index 8accac9c0..5e9bd4c89 100644 --- a/src/statd/python/yanger/ietf_ospf.py +++ b/src/statd/python/yanger/ietf_ospf.py @@ -162,6 +162,27 @@ def add_routes(ospf, ipv6=False): insert(ospf, "ietf-ospf:local-rib", "ietf-ospf:route", routes) +def add_auto_cost(ospf, daemon): + """Report the auto-cost reference bandwidth (Mbit/s) of an OSPF daemon. + + Frr has no JSON for it, and its running-config only lists the value + when it differs from the default, 100000 Mbit/s in both ospfd and + ospf6d. Auto-cost is always enabled in Frr.""" + refbw = 100000 + + cmd = ("vtysh", "-c", f"show running-config {daemon}") + for line in HOST.run(cmd, default="").splitlines(): + words = line.split() + if words[:2] == ["auto-cost", "reference-bandwidth"]: + refbw = int(words[2]) + break + + ospf["ietf-ospf:auto-cost"] = { + "enabled": True, + "reference-bandwidth": refbw, + } + + def add_areas(control_protocols): """Populate OSPF status""" cmd = ['/usr/libexec/statd/ospf-status'] @@ -308,6 +329,7 @@ def add_areas(control_protocols): area["ietf-ospf:interfaces"]["ietf-ospf:interface"] = interfaces areas.append(area) + add_auto_cost(control_protocol["ietf-ospf:ospf"], "ospfd") add_routes(control_protocol["ietf-ospf:ospf"]) control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"]["ietf-ospf:area"] = areas insert(control_protocols, "control-plane-protocol", [control_protocol]) @@ -389,6 +411,7 @@ def add_areas6(control_protocols): area["ietf-ospf:interfaces"]["ietf-ospf:interface"] = interfaces areas.append(area) + add_auto_cost(control_protocol["ietf-ospf:ospf"], "ospf6d") add_routes(control_protocol["ietf-ospf:ospf"], ipv6=True) control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"]["ietf-ospf:area"] = areas insert(control_protocols, "control-plane-protocol", [control_protocol]) diff --git a/test/case/statd/containers/ietf-ospf.json b/test/case/statd/containers/ietf-ospf.json index d9129e242..889fe05e7 100644 --- a/test/case/statd/containers/ietf-ospf.json +++ b/test/case/statd/containers/ietf-ospf.json @@ -113,6 +113,10 @@ }, "ietf-ospf:router-id": "10.1.1.1", "ietf-ospf:address-family": "ipv4", + "ietf-ospf:auto-cost": { + "enabled": true, + "reference-bandwidth": 100000 + }, "ietf-ospf:local-rib": { "ietf-ospf:route": [ { diff --git a/test/case/statd/containers/operational.json b/test/case/statd/containers/operational.json index c210c8a8f..15892196b 100644 --- a/test/case/statd/containers/operational.json +++ b/test/case/statd/containers/operational.json @@ -790,6 +790,10 @@ } ] }, + "ietf-ospf:auto-cost": { + "enabled": true, + "reference-bandwidth": 100000 + }, "ietf-ospf:local-rib": { "ietf-ospf:route": [ { From f1d5a882bfdacfce1481c76f6391faba5d1a2abf Mon Sep 17 00:00:00 2001 From: Ejub Sabic Date: Wed, 7 Oct 2026 16:34:52 +0200 Subject: [PATCH 3/4] test: add OSPF auto-cost reference bandwidth test Verify that the interface cost follows the reference bandwidth, for OSPFv2 and OSPFv3. The expected cost is derived from the link speed, so the test works on hardware and on virtual links of unknown speed. On a 1 Gbit/s link the reference bandwidths 100000, 10000 and 1000 Mbit/s give the costs 100, 10 and 1. Signed-off-by: Ejub Sabic --- test/case/routing/Readme.adoc | 5 + test/case/routing/all.yaml | 3 + test/case/routing/ospf_auto_cost/Readme.adoc | 6 + test/case/routing/ospf_auto_cost/ospfv2.adoc | 41 ++++ test/case/routing/ospf_auto_cost/ospfv2.py | 1 + test/case/routing/ospf_auto_cost/ospfv3.adoc | 41 ++++ test/case/routing/ospf_auto_cost/ospfv3.py | 1 + test/case/routing/ospf_auto_cost/test.py | 196 ++++++++++++++++++ test/case/routing/ospf_auto_cost/test.yaml | 11 + test/case/routing/ospf_auto_cost/topology.dot | 35 ++++ test/case/routing/ospf_auto_cost/topology.svg | 62 ++++++ test/infamy/route.py | 10 + 12 files changed, 412 insertions(+) create mode 100644 test/case/routing/ospf_auto_cost/Readme.adoc create mode 100644 test/case/routing/ospf_auto_cost/ospfv2.adoc create mode 120000 test/case/routing/ospf_auto_cost/ospfv2.py create mode 100644 test/case/routing/ospf_auto_cost/ospfv3.adoc create mode 120000 test/case/routing/ospf_auto_cost/ospfv3.py create mode 100755 test/case/routing/ospf_auto_cost/test.py create mode 100644 test/case/routing/ospf_auto_cost/test.yaml create mode 100644 test/case/routing/ospf_auto_cost/topology.dot create mode 100644 test/case/routing/ospf_auto_cost/topology.svg diff --git a/test/case/routing/Readme.adoc b/test/case/routing/Readme.adoc index 6152bc0ad..4b2df2077 100644 --- a/test/case/routing/Readme.adoc +++ b/test/case/routing/Readme.adoc @@ -10,6 +10,7 @@ Tests verifying standard routing protocols and configuration: - OSPF with BFD (Bidirectional Forwarding Detection) - OSPF default route advertisement and propagation - OSPF debug logging configuration and verification + - OSPF auto-cost reference bandwidth and interface cost - OSPF point-to-multipoint hybrid (broadcast) interface type - OSPF point-to-multipoint (non-broadcast) interface type with static neighbors - RIP basic neighbor discovery and route exchange @@ -48,6 +49,10 @@ include::ospf_debug/Readme.adoc[] <<< +include::ospf_auto_cost/Readme.adoc[] + +<<< + include::ospf_point_to_multipoint_hybrid/Readme.adoc[] <<< diff --git a/test/case/routing/all.yaml b/test/case/routing/all.yaml index cbd53ccd7..c240cc709 100644 --- a/test/case/routing/all.yaml +++ b/test/case/routing/all.yaml @@ -29,6 +29,9 @@ - name: OSPF Debug Logging case: ospf_debug/test.py +- name: OSPF Auto-Cost Reference Bandwidth + suite: ospf_auto_cost/test.yaml + - name: OSPF Point-to-Multipoint Hybrid suite: ospf_point_to_multipoint_hybrid/test.yaml diff --git a/test/case/routing/ospf_auto_cost/Readme.adoc b/test/case/routing/ospf_auto_cost/Readme.adoc new file mode 100644 index 000000000..2322b7ef7 --- /dev/null +++ b/test/case/routing/ospf_auto_cost/Readme.adoc @@ -0,0 +1,6 @@ +include::ospfv2.adoc[] + +<<< + +include::ospfv3.adoc[] + diff --git a/test/case/routing/ospf_auto_cost/ospfv2.adoc b/test/case/routing/ospf_auto_cost/ospfv2.adoc new file mode 100644 index 000000000..7110cc19c --- /dev/null +++ b/test/case/routing/ospf_auto_cost/ospfv2.adoc @@ -0,0 +1,41 @@ +=== OSPFv2 Auto-Cost Reference Bandwidth + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/ospf_auto_cost] + +==== Description + +Verifies that the OSPFv2 interface cost follows the configured auto-cost +reference bandwidth. The reference bandwidth must be the same on all +routers in an OSPF domain, so it is changed on both R1 and R2. + +The cost of an interface is the reference bandwidth divided by the link +speed, with 1 as the lowest cost. A link of unknown speed, e.g., a virtual +link, counts as 10000 Mbit/s. On a 1 Gbit/s link the cost is: + +|=== +| Reference bandwidth | Cost +| 100000 Mbit/s (default) | 100 +| 10000 Mbit/s | 10 +| 1000 Mbit/s | 1 +|=== + +The test reads the link speed on each router to calculate the expected +cost, and reads the reference bandwidth back from the operational +datastore. + +==== Topology + +image::topology.svg[OSPFv2 Auto-Cost Reference Bandwidth topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUTs +. Configure OSPFv2 on R1 and R2 +. Wait for OSPFv2 adjacency between R1 and R2 +. Verify default reference bandwidth 100000 Mbit/s and link cost +. Set reference bandwidth 10000 Mbit/s on R1 and R2 +. Verify reference bandwidth 10000 Mbit/s and link cost +. Set reference bandwidth 1000 Mbit/s on R1 and R2 +. Verify reference bandwidth 1000 Mbit/s and link cost + + diff --git a/test/case/routing/ospf_auto_cost/ospfv2.py b/test/case/routing/ospf_auto_cost/ospfv2.py new file mode 120000 index 000000000..946566431 --- /dev/null +++ b/test/case/routing/ospf_auto_cost/ospfv2.py @@ -0,0 +1 @@ +test.py \ No newline at end of file diff --git a/test/case/routing/ospf_auto_cost/ospfv3.adoc b/test/case/routing/ospf_auto_cost/ospfv3.adoc new file mode 100644 index 000000000..cf9cec253 --- /dev/null +++ b/test/case/routing/ospf_auto_cost/ospfv3.adoc @@ -0,0 +1,41 @@ +=== OSPFv3 Auto-Cost Reference Bandwidth + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/ospf_auto_cost] + +==== Description + +Verifies that the OSPFv3 interface cost follows the configured auto-cost +reference bandwidth. The reference bandwidth must be the same on all +routers in an OSPF domain, so it is changed on both R1 and R2. + +The cost of an interface is the reference bandwidth divided by the link +speed, with 1 as the lowest cost. A link of unknown speed, e.g., a virtual +link, counts as 10000 Mbit/s. On a 1 Gbit/s link the cost is: + +|=== +| Reference bandwidth | Cost +| 100000 Mbit/s (default) | 100 +| 10000 Mbit/s | 10 +| 1000 Mbit/s | 1 +|=== + +The test reads the link speed on each router to calculate the expected +cost, and reads the reference bandwidth back from the operational +datastore. + +==== Topology + +image::topology.svg[OSPFv3 Auto-Cost Reference Bandwidth topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUTs +. Configure OSPFv3 on R1 and R2 +. Wait for OSPFv3 adjacency between R1 and R2 +. Verify default reference bandwidth 100000 Mbit/s and link cost +. Set reference bandwidth 10000 Mbit/s on R1 and R2 +. Verify reference bandwidth 10000 Mbit/s and link cost +. Set reference bandwidth 1000 Mbit/s on R1 and R2 +. Verify reference bandwidth 1000 Mbit/s and link cost + + diff --git a/test/case/routing/ospf_auto_cost/ospfv3.py b/test/case/routing/ospf_auto_cost/ospfv3.py new file mode 120000 index 000000000..946566431 --- /dev/null +++ b/test/case/routing/ospf_auto_cost/ospfv3.py @@ -0,0 +1 @@ +test.py \ No newline at end of file diff --git a/test/case/routing/ospf_auto_cost/test.py b/test/case/routing/ospf_auto_cost/test.py new file mode 100755 index 000000000..73e70434f --- /dev/null +++ b/test/case/routing/ospf_auto_cost/test.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +"""{version} Auto-Cost Reference Bandwidth + +Verifies that the {version} interface cost follows the configured auto-cost +reference bandwidth. The reference bandwidth must be the same on all +routers in an OSPF domain, so it is changed on both R1 and R2. + +The cost of an interface is the reference bandwidth divided by the link +speed, with 1 as the lowest cost. A link of unknown speed, e.g., a virtual +link, counts as 10000 Mbit/s. On a 1 Gbit/s link the cost is: + +|=== +| Reference bandwidth | Cost +| 100000 Mbit/s (default) | 100 +| 10000 Mbit/s | 10 +| 1000 Mbit/s | 1 +|=== + +The test reads the link speed on each router to calculate the expected +cost, and reads the reference bandwidth back from the operational +datastore. +""" + +import infamy +import infamy.route as route +from infamy.util import until, parallel + + +class ArgumentParser(infamy.ArgumentParser): + def __init__(self): + super().__init__() + self.add_argument("--version", type=str.lower, choices=["ospfv2", "ospfv3"]) + + +PARAM = { + "ospfv2": { + "af": "ipv4", + "len": 24, + "R1link": "192.168.50.1", + "R2link": "192.168.50.2", + }, + "ospfv3": { + "af": "ipv6", + "len": 64, + "R1link": "2001:db8:50::1", + "R2link": "2001:db8:50::2", + }, +} + +ROUTER_ID = {"R1": "1.1.1.1", "R2": "2.2.2.2"} + +# Frr uses this link speed (Mbit/s) when the speed is unknown +UNKNOWN_SPEED = 10000 + + +def config_target(target, name, link, p, proto): + target.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": link, + "enabled": True, + p["af"]: { + "forwarding": True, + "address": [{ + "ip": p[f"{name}link"], + "prefix-length": p["len"] + }] + } + }] + } + }, + "ietf-routing": { + "routing": { + "control-plane-protocols": { + "control-plane-protocol": [{ + "type": proto, + "name": "default", + "ospf": { + "explicit-router-id": ROUTER_ID[name], + "areas": { + "area": [{ + "area-id": "0.0.0.0", + "interfaces": { + "interface": [{ + "enabled": True, + "name": link, + "hello-interval": 1, + "dead-interval": 3 + }] + } + }] + } + } + }] + } + } + } + }) + + +def set_reference_bandwidth(target, refbw, proto): + target.put_config_dicts({ + "ietf-routing": { + "routing": { + "control-plane-protocols": { + "control-plane-protocol": [{ + "type": proto, + "name": "default", + "ospf": { + "auto-cost": { + "reference-bandwidth": refbw + } + } + }] + } + } + } + }) + + +def link_speed(target, link): + """Link speed in Mbit/s, as used by Frr to calculate the cost""" + speed = target.get_iface(link).get("speed") + if not speed: + return UNKNOWN_SPEED + + return int(speed) // 1000000 + + +def expected_cost(refbw, speed): + """Same rounding as Frr, with 1 as the lowest cost""" + return max(1, int(refbw / speed + 0.5)) + + +def wait_adjacency(target, link, neighbor, proto): + until(lambda: route.ospf_get_neighbor(target, "0.0.0.0", link, ROUTER_ID[neighbor], + proto=proto), attempts=200) + + +def verify(target, link, refbw, proto): + speed = link_speed(target, link) + cost = expected_cost(refbw, speed) + print(f"Expecting reference bandwidth {refbw} Mbit/s and cost {cost} " + f"on {link} ({speed} Mbit/s)") + + until(lambda: route.ospf_get_reference_bandwidth(target, proto) == refbw and + route.ospf_get_interface_cost(target, "0.0.0.0", link, proto) == cost) + + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUTs"): + env = infamy.Env(args=ArgumentParser()) + version = env.args.version + param = PARAM[version] + proto = f"infix-routing:{version}" + + R1, R2 = parallel(lambda: env.attach("R1", "mgmt"), + lambda: env.attach("R2", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2) + + _, R1link = env.ltop.xlate("R1", "link") + _, R2link = env.ltop.xlate("R2", "link") + + def set_both(refbw): + parallel(lambda: set_reference_bandwidth(R1, refbw, proto), + lambda: set_reference_bandwidth(R2, refbw, proto)) + + def verify_both(refbw): + parallel(lambda: verify(R1, R1link, refbw, proto), + lambda: verify(R2, R2link, refbw, proto)) + + with test.step(f"Configure {version} on R1 and R2"): + parallel(lambda: config_target(R1, "R1", R1link, param, proto), + lambda: config_target(R2, "R2", R2link, param, proto)) + + with test.step(f"Wait for {version} adjacency between R1 and R2"): + parallel(lambda: wait_adjacency(R1, R1link, "R2", proto), + lambda: wait_adjacency(R2, R2link, "R1", proto)) + + with test.step("Verify default reference bandwidth 100000 Mbit/s and link cost"): + verify_both(100000) + + with test.step("Set reference bandwidth 10000 Mbit/s on R1 and R2"): + set_both(10000) + + with test.step("Verify reference bandwidth 10000 Mbit/s and link cost"): + verify_both(10000) + + with test.step("Set reference bandwidth 1000 Mbit/s on R1 and R2"): + set_both(1000) + + with test.step("Verify reference bandwidth 1000 Mbit/s and link cost"): + verify_both(1000) + + test.succeed() diff --git a/test/case/routing/ospf_auto_cost/test.yaml b/test/case/routing/ospf_auto_cost/test.yaml new file mode 100644 index 000000000..f069ddb9e --- /dev/null +++ b/test/case/routing/ospf_auto_cost/test.yaml @@ -0,0 +1,11 @@ +--- +- settings: + test-spec: .adoc + +- name: OSPFv2 Auto-Cost Reference Bandwidth + case: ospfv2.py + opts: ["--version", "OSPFv2"] + +- name: OSPFv3 Auto-Cost Reference Bandwidth + case: ospfv3.py + opts: ["--version", "OSPFv3"] diff --git a/test/case/routing/ospf_auto_cost/topology.dot b/test/case/routing/ospf_auto_cost/topology.dot new file mode 100644 index 000000000..b9fb38909 --- /dev/null +++ b/test/case/routing/ospf_auto_cost/topology.dot @@ -0,0 +1,35 @@ +graph "ospf_auto_cost" { + labelloc="b"; + label="IPv4 addresses shown, the IPv6 run uses the matching 2001:db8: prefixes"; + fontname="DejaVu Serif, Book"; + layout="neato"; + overlap="false"; + esep="+20"; + size=10 + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + PC [ + label="PC | { mgmt1 | mgmt2 }", + pos="20,30!", + requires="controller", + ]; + + R1 [ + label="{ mgmt | link } | R1 \n router-id 1.1.1.1", + pos="160,60!", + + requires="infix", + ]; + R2 [ + label="{ link | mgmt } | R2 \n router-id 2.2.2.2", + pos="160,30!", + + requires="infix", + ]; + + PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"] + PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"] + R1:link -- R2:link [headlabel="192.168.50.2/24", taillabel="192.168.50.1/24", labeldistance=1, fontcolor="black", color="black"] +} diff --git a/test/case/routing/ospf_auto_cost/topology.svg b/test/case/routing/ospf_auto_cost/topology.svg new file mode 100644 index 000000000..2a7b3e606 --- /dev/null +++ b/test/case/routing/ospf_auto_cost/topology.svg @@ -0,0 +1,62 @@ + + + + + + +ospf_auto_cost + +IPv4 addresses shown, the IPv6 run uses the matching 2001:db8: prefixes + + +PC + +PC + +mgmt1 + +mgmt2 + + + +R1 + +mgmt + +link + +R1 + router-id 1.1.1.1 + + + +PC:mgmt1--R1:mgmt + + + + +R2 + +link + +mgmt + +R2 + router-id 2.2.2.2 + + + +PC:mgmt2--R2:mgmt + + + + +R1:link--R2:link + +192.168.50.2/24 +192.168.50.1/24 + + + diff --git a/test/infamy/route.py b/test/infamy/route.py index 093e8bd4e..232086461 100644 --- a/test/infamy/route.py +++ b/test/infamy/route.py @@ -132,6 +132,16 @@ def ospf_get_interface_passive(target, area_id, ifname, proto="infix-routing:osp return ospf_interface.get("passive", False) +def ospf_get_interface_cost(target, area_id, ifname, proto="infix-routing:ospfv2"): + ospf_interface = _get_ospf_status_area_interface(target, area_id, ifname, proto) + return ospf_interface.get("cost") + + +def ospf_get_reference_bandwidth(target, proto="infix-routing:ospfv2"): + ospf = _get_ospf_status(target, proto) + return ospf.get("auto-cost", {}).get("reference-bandwidth") + + def ospf_is_area_nssa(target, area_id, proto="infix-routing:ospfv2"): area = _get_ospf_status_area(target, area_id, proto) if area.get("area-type", "") == "ietf-ospf:nssa-area": From a46aefa0fcff3be33a1e9bccf9f01a9158016516 Mon Sep 17 00:00:00 2001 From: Ejub Sabic Date: Wed, 7 Oct 2026 16:35:18 +0200 Subject: [PATCH 4/4] doc: describe OSPF auto-cost reference bandwidth Explain why the reference bandwidth must match across the domain, how the interface cost is calculated, and where to read the value. Signed-off-by: Ejub Sabic --- doc/ChangeLog.md | 3 +++ doc/routing.md | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 89de004c4..a473a12c4 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -14,6 +14,9 @@ All notable changes to the project are documented in this file. `ripng`/`ospfv3` type and the IPv6 address-family - Support building without Frr, which also drops bash. The minimal defconfigs are now built this way, with static routing only, issue #1670 +- Configurable OSPF auto-cost reference bandwidth, for OSPFv2 and OSPFv3, + issue #1576. The default, 100000 Mbit/s, is the value used before, so + link costs do not change on upgrade. The CLI shows it in `show ip ospf` - Check for a new software release on demand: `upgrade` without a bundle in the CLI, the Check now button on the WebUI dashboard, or the `check-update` RPC, see [Unattended Software Updates][unattended] diff --git a/doc/routing.md b/doc/routing.md index b4af8e5bf..d0516a3b0 100644 --- a/doc/routing.md +++ b/doc/routing.md @@ -215,11 +215,13 @@ admin@example:/> ### OSPF global settings In addition to *area* and *interface* specific settings, OSPF provides -global settings for route redistribution and OSPF router identifier. +global settings for route redistribution, OSPF router identifier, and +the reference bandwidth used to calculate interface costs.
admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf
 admin@example:/config/routing/…/ospf/> set ?
   area                     List of OSPF areas.
+  auto-cost                Interface auto-cost configuration state.
   default-route-advertise  Distribute default route to network
   explicit-router-id       Defined in RFC 2328.  A 32-bit number
   redistribute             Redistribute protocols into OSPF
@@ -242,6 +244,24 @@ admin@example:/config/routing/…/ospf/> set
   router will distribute a default route even when it lacks a default
   route. The default route will be distributed as an *external type-2*
   (E2) route.
+- Reference bandwidth: Routers from different vendors only agree on
+  the shortest path if they use the same reference bandwidth.  The cost
+  of an interface without an explicit `cost` is the reference bandwidth
+  divided by the link speed, rounded, with 1 as the lowest cost.  The
+  default is 100000 Mbit/s, which gives a 1 Gbit/s link cost 100.  Use
+  `set auto-cost reference-bandwidth 10000` (Mbit/s) to change it.  A
+  link of unknown speed, e.g., a virtual link, counts as 10000 Mbit/s.
+  Auto-cost can not be disabled.
+
+`show ip ospf` shows the reference bandwidth in use, and `show ipv6
+ospf` does the same for OSPFv3.
+
+
admin@example:/> show ip ospf
+ OSPF Routing Process, Router ID: 192.168.10.1
+ Reference bandwidth: 100000 Mbit/s
+ Number of areas attached to this router: 1
+ ...
+
### Debug OSPFv2