Skip to content

Add OSPFv3 support for FortiOS#3451

Merged
ipspace merged 1 commit into
ipspace:devfrom
a-v-popov:fos-ospfv3-pr
Jun 6, 2026
Merged

Add OSPFv3 support for FortiOS#3451
ipspace merged 1 commit into
ipspace:devfrom
a-v-popov:fos-ospfv3-pr

Conversation

@a-v-popov
Copy link
Copy Markdown
Collaborator

@a-v-popov a-v-popov commented Jun 5, 2026

Why

Apart from OSPFv3 support this PR also fixes a bug related to the fact that
the netlab FortiOS template shipped with OSPFv2 only, but this was not recorded.
The device manifest declares neither ospf.ipv4 nor ospf.ipv6, so _routing.py
auto-enables ospf.af.ipv6 on any node that has IPv6 OSPF interfaces. The
single, address-family-agnostic ospf/fortios.j2 then ran its IPv4-only body
against those interfaces and failed in ansible.utils.ipaddr ('width' is undefined)
the moment it tried to format intf.ipv4 on an IPv6-only link.

This surfaced while bringing up IPv6 iBGP labs, which require an IPv6 IGP to
carry loopback reachability — FortiOS had none.

What

ospf/fortios.j2 now renders both OSPF instances from a single
ospf_config(proto, intf_list, ospf) macro, invoked once per active address
family inside the VDOM wrapper:

  • The dispatcher extracts the OSPF interfaces once, splits them by address
    family, and calls the macro with proto='ospf' for the IPv4 set and
    proto='ospf6' for the IPv6 set.
  • proto is both the FortiOS configuration keyword (config router ospf /
    config router ospf6, config ospf-interface / config ospf6-interface)
    and the discriminator for the few places the two instances diverge.
  • Because the interface lists are pre-filtered by address family before the
    macro runs, address-family correctness is established once at the boundary:
    the macro never sees an interface that does not belong in the instance it is
    rendering, which is what makes the original ipaddr failure impossible.

No device-manifest change is required: support is the default, and the
dispatcher's ospf.af.* gates decide which instance renders.

Device-specific constraints (notes for reviewers)

  • OSPFv2 membership is prefix-based. FortiOS binds an interface to its area
    through a prefix in config network, which needs the interface's IPv4 subnet.
    The IPv4 interface list must therefore exclude interfaces without an IPv4
    address; this is enforced by the pre-filtering. OSPFv3 instead binds the area
    with set area-id inside config ospf6-interface.
  • Areas must be defined before they are referenced. set area-id under
    config ospf6-interface fails on the device with "Please input a defined
    area id!"
    unless the area already exists, so config area is emitted before
    the interface stanza. ospf.areas already aggregates every area the
    interfaces use, so the ordering alone is sufficient.
  • Passive interfaces are a top-level set passive-interface list, not a
    per-interface flag.
  • OSPFv3 authentication on FortiOS is IPsec-based; plaintext and MD5 keys do not
    apply, so the password line is emitted for OSPFv2 only.

Testing

Verified live against a FortiOS 8.0.0 DUT (libvirt; FRR probes on clab), one
lab at a time, using the OSPFv2 and OSPFv3 integration suites under
tests/integration/ospf/. After excluding topologies that depend on FortiOS
feature gaps unrelated to this change (the bgp/bfd/vrf modules, VLAN
interfaces, unnumbered and link-local-only IPv6 interfaces, static-route
import), the eligible set is seven OSPFv2 and seven OSPFv3 topologies; all pass.

Test OSPFv2 OSPFv3
01-network PASS PASS
02-areas PASS PASS
03-cost PASS PASS
04-passive PASS PASS
06-lb-prefix n/a PASS (loopback advertised as /128)
30-timers PASS PASS
31-priority PASS (DR election) PASS
32-password PASS (plaintext auth) n/a

Two clarifications on the run:

  • On 30-timers, 31-priority, and 32-password, netlab deliberately skips
    the VRF-specific subtests on FortiOS (VRF OSPF is out of scope) and reports a
    warning; the OSPF subtests themselves pass.
  • 06-lb-prefix checks an OSPFv3 LSA and can report the prefix as absent if it
    is validated before the LSA has propagated on a cold boot; a re-run after a
    short wait confirms the loopback is advertised as /128.

Offline render checks (netlab create) confirm per-address-family membership on
a node with three links — dual-stack, IPv4-only, and IPv6-only: the IPv4-only
interface appears under config router ospf only, the IPv6-only interface under
config router ospf6 only, and the dual-stack interface under both.

Docs

The per-platform OSPF support matrix and the IPv6 address-family table are
updated to mark FortiOS OSPFv3 as supported (docs/module/ospf.md,
docs/platforms.md).

Out of scope

VRF OSPFv3, BFD for OSPFv3, redistribution/import, and default-route
origination are intentionally left out of this basic implementation.

The netlab FortiOS OSPF template supported only OSPFv2, but the device manifest
declared neither ospf.ipv4 nor ospf.ipv6, so _routing.py auto-enabled ospf.af.ipv6
on any node that had IPv6 OSPF interfaces. The single, address-family-agnostic
ospf/fortios.j2 then ran its IPv4-only body against those interfaces and failed
in ansible.utils.ipaddr ('width' is undefined) the moment it formatted
intf.ipv4 on an IPv6-only link. This left IPv6 iBGP labs, which need an IPv6 IGP
to carry loopback reachability, with no usable FortiOS IGP.

Render both OSPF instances from a single ospf_config(proto, intf_list, ospf)
macro, invoked once per active address family. The caller pre-filters the
interface list by address family and passes the macro only the interfaces that
belong in that instance, so address-family correctness is established once at
the boundary rather than re-checked throughout the body. proto ('ospf' or
'ospf6') is both the FortiOS configuration keyword and the discriminator for the
few places the two instances diverge.

Three constraints are not apparent from the diff:

- The OSPFv2 interface list must contain only interfaces that have an IPv4
  address. FortiOS binds an interface to its area through a prefix in
  `config network`, which requires the interface's IPv4 subnet; OSPFv3 instead
  binds the area with `set area-id` inside `config ospf6-interface`.
- `config area` is emitted before the interface stanza, because FortiOS rejects
  `set area-id` for an area that has not yet been defined ("Please input a
  defined area id!").
- OSPFv3 authentication on FortiOS is IPsec-based; plaintext and MD5 keys do not
  apply, so the password line is emitted for OSPFv2 only.

No device-manifest change is required: AF support defaults to enabled unless
the device manifest disables it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@ipspace ipspace left a comment

Choose a reason for hiding this comment

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

Picture perfect. I love the description with detailed explanations and test results.

Thanks a million!

@ipspace ipspace merged commit c2e60f3 into ipspace:dev Jun 6, 2026
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