From 17e52f61cebdd6a9c9664e51c11ecdaf6ccfcd70 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 2a5a33cf9f..3280138494 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 f6a55600f4..69dd496a8a 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-10-03.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 25b4958984..8e492bf2aa 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 205226023f89bbc6f228b58594f3ab10c186c6df 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 d4eff3fde1..919e27630d 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -5273,6 +5273,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 c94f265607..cf47ef64da 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 control_protocols.setdefault("control-plane-protocol", []).append(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 control_protocols.setdefault("control-plane-protocol", []).append(control_protocol) diff --git a/test/case/statd/containers/ietf-ospf.json b/test/case/statd/containers/ietf-ospf.json index d9129e242f..889fe05e70 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 c210c8a8ff..15892196bc 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 58576c8710ac84276f4a5d973d2e5a9f06b98267 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 4926aebcfd..032baabda6 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 @@ -49,6 +50,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 85762217f2..f2151c61e3 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 0000000000..2322b7ef78 --- /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 0000000000..7110cc19c6 --- /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 0000000000..9465664311 --- /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 0000000000..cf9cec2535 --- /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 0000000000..9465664311 --- /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 0000000000..73e70434f3 --- /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 0000000000..f069ddb9e4 --- /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 0000000000..b9fb38909a --- /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 0000000000..2a7b3e6063 --- /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 fe054a1af2..181661c8a1 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 7b5c82a404a659f36b7d2ca29fa497c4daf09662 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 | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index e585de412c..5f647c7f03 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -67,6 +67,9 @@ All notable changes to the project are documented in this file. - WiFi channel survey is on request: `show hardware survey`, the Scan channels button on the WebUI WiFi page, or the `channel-survey` action. The always-on `survey` container under the radio is gone +- 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` ### Fixes diff --git a/doc/routing.md b/doc/routing.md index b4af8e5bf3..d0cc1a6820 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,23 @@ 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.
+- (auto-cost) 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. 
+  Auto-cost can not be disabled, but a cost set manually per interface takes precedence.
+
+`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