diff --git a/docs/caveats.md b/docs/caveats.md
index d6fc16c400..43cacd9af1 100644
--- a/docs/caveats.md
+++ b/docs/caveats.md
@@ -245,6 +245,7 @@ Other caveats:
* The default MTU value is 1500 to match the implementation defaults from other vendors and enable things like seamless OSPF peering.
* *netlab* uses Cumulus VX 5.3 containers created by Michael Kashin and downloaded from his Docker Hub account. These containers are severely out-of-date, are not tested in our integration tests, and might not work as expected.
* Some features - such as VRF route leaking and route advertisement in the default VRF (used in certain EVPN scenarios) - are not supported by NVUE, and require the use of custom config *snippets*. Only one such snippet is supported per configuration file (e.g. /etc/frr/frr.conf), which means a topology using a combination of multiple features that all require *snippets* will not work.
+* Cumulus NVUE does not support OSPFv3
(caveats-os10)=
## Dell OS10
@@ -253,6 +254,7 @@ Other caveats:
* Sadly, it's also **NOT** possible to use *VRRP* on a *Virtual Network* interface (but *anycast* gateway is supported).
* At the same time, the *anycast* gateway is not supported on plain *ethernet* interfaces, so you need to use *VRRP* there.
* Dell OS10 only allows configuring of the EVPN RD in the form `X.X.X.X:N.` By default, *netlab* uses `N:M` for L3VNI, so on this platform the L3VNI RD is derived from the Router-ID and the VRF ID as `router-id:vrf-id` (and the one generated by *netlab* is not used).
+* OSPF NSSA areas are not supported for OSPFv3
### VRRP Caveats
Netlab enables VRRPv3 by default on Dell OS10, overriding any platform defaults. If you need VRRPv2, check out the [vrrp.version](plugin-vrrp-version) plugin
diff --git a/docs/plugins/ospf.areas.md b/docs/plugins/ospf.areas.md
index 3dbe0e21da..19b236f5b4 100644
--- a/docs/plugins/ospf.areas.md
+++ b/docs/plugins/ospf.areas.md
@@ -9,10 +9,12 @@ The plugin also supports suppressing inter-area routes in stub/NSSA areas, resul
The plugin includes Jinja2 templates for the following platforms:
| Operating system | Stub/NSSA
areas | Totally
stubby areas | Area ranges |
-|-----------|:-:|:-:|:-:|
-| Arista EOS | ✅ [❗](caveats-eos) |✅|✅|
-| FRR |✅|✅|✅ [❗](caveats-frr) |
-| JunOS |✅|✅|✅ |
+|--------------|:-:|:-:|:-:|
+| Arista EOS |✅ [❗](caveats-eos) |✅|✅|
+| Cumulus NVUE |✅|✅|✅ [❗](caveats-cumulus-nvue) |
+| Dell OS10 |✅|✅|✅ [❗](caveats-os10) |
+| FRR |✅|✅|✅ [❗](caveats-frr) |
+| JunOS |✅|✅|✅|
## Specifying OSPF Area Parameters
diff --git a/netsim/devices/cumulus_nvue.py b/netsim/devices/cumulus_nvue.py
index 09ec072479..658ee7e6a5 100644
--- a/netsim/devices/cumulus_nvue.py
+++ b/netsim/devices/cumulus_nvue.py
@@ -6,7 +6,7 @@
from . import _Quirks, report_quirk
# from .cumulus import Cumulus # This causes Cumulus_Nvue to get skipped
from .cumulus import check_ospf_vrf_default
-from ..utils import log
+from ..utils import log, routing as _rp_utils
from ..augment import devices
from .. import data
import netaddr
@@ -154,6 +154,21 @@ def mark_shared_mlag_vtep(node: Box, topology: Box) -> None:
node.vxlan._shared_vtep = n.name
return
+def nvue_check_nssa_summarize(node: Box) -> None:
+ for (odata,_,_) in _rp_utils.rp_data(node,'ospf'):
+ if 'areas' not in odata:
+ continue
+ for area in odata.areas:
+ if area.kind != 'nssa':
+ continue
+ if 'external_range' in area or 'external_filter' in area:
+ report_quirk(
+ f'{node.name} cannot summarize type-7 NSSA routes (area {area.area})',
+ more_hints = [ 'Cumulus cannot configure NSSA type-7 ranges, FRR version too old' ],
+ node=node,
+ category=Warning,
+ quirk='ospf_nssa_range')
+
class Cumulus_Nvue(_Quirks):
@classmethod
@@ -166,6 +181,7 @@ def device_quirks(self, node: Box, topology: Box) -> None:
nvue_check_ospf_passive_in_vrf(node)
nvue_check_ospf_vrf_loopbacks(node)
nvue_check_ospfv3(node)
+ nvue_check_nssa_summarize(node)
nvue_merge_ospf_loopbacks(node)
if 'stp' in mods:
diff --git a/netsim/devices/dellos10.py b/netsim/devices/dellos10.py
index 3e424357d7..062564b586 100644
--- a/netsim/devices/dellos10.py
+++ b/netsim/devices/dellos10.py
@@ -4,7 +4,7 @@
from box import Box,BoxList
from . import _Quirks,need_ansible_collection,report_quirk
-from ..utils import log
+from ..utils import log, routing as _rp_utils
from ..augment import devices
def check_vlan_ospf(node: Box, iflist: BoxList, vname: str) -> None:
@@ -99,6 +99,27 @@ def check_expanded_communities(node:Box, topology: Box) -> None:
quirk='non-standard_communities',
node=node)
+def check_nssa_area_limitations(node: Box) -> None:
+ for (odata,_,_) in _rp_utils.rp_data(node,'ospf'):
+ if 'areas' not in odata:
+ continue
+ for area in odata.areas:
+ if area.kind != 'nssa':
+ continue
+ if 'ipv6' in odata.af:
+ report_quirk(
+ f'{node.name} cannot configure NSSA type areas for OSPFv3 (area {area.area})',
+ more_hints = [ 'Dell OS10 does not support NSSA for OSPFv3' ],
+ node=node,
+ quirk='ospfv3_nssa')
+ if 'external_range' in area or 'external_filter' in area:
+ report_quirk(
+ f'{node.name} cannot summarize type-7 NSSA routes (area {area.area})',
+ more_hints = [ 'Dell OS10 cannot configure NSSA type-7 ranges' ],
+ node=node,
+ category=Warning,
+ quirk='ospf_nssa_range')
+
class OS10(_Quirks):
@classmethod
@@ -109,6 +130,7 @@ def device_quirks(self, node: Box, topology: Box) -> None:
for vname,vdata in node.get('vrfs',{}).items():
check_vlan_ospf(node,vdata.get('ospf.interfaces',[]),vname)
check_ospf_originate_default(node)
+ check_nssa_area_limitations(node)
if 'gateway' in mods:
if 'anycast' in node.get('gateway',{}):
diff --git a/netsim/extra/ospf.areas/cumulus_nvue.j2 b/netsim/extra/ospf.areas/cumulus_nvue.j2
new file mode 100644
index 0000000000..67ad8fdb81
--- /dev/null
+++ b/netsim/extra/ospf.areas/cumulus_nvue.j2
@@ -0,0 +1,44 @@
+---
+{% macro area_config(adata,af,abr) %}
+{% set kind = 'normal' if adata.kind == 'regular' else adata.kind %}
+ {{ adata.area }}:
+ type: {{ 'totally-' if not adata.inter_area else '' }}{{ kind }}
+{% if adata.kind in ['stub','nssa'] and adata.default.cost is defined and af == 'ipv4' %}
+ default-lsa-cost: {{ adata.default.cost }}
+{% endif %}
+{% if abr %}
+{% for range in adata.range|default([])+adata.filter|default([]) if af in range %}
+{% if loop.first %}
+ range:
+{% endif %}
+ {{ range[af] }}:
+ suppress: {{ 'on' if range in adata.filter|default([]) else 'off' }}
+{% endfor %}
+{% endif %}
+{% endmacro %}
+
+{% macro ospf_area_config(odata,vrf='') %}
+{% for af in ['ipv4'] if odata.af[af] is defined %}
+{% if loop.first %}
+- set:
+ vrf:
+ {{ vrf if vrf else 'default' }}:
+ router:
+ ospf:
+ area:
+{% endif %}
+{% for adata in odata.areas %}
+{{ area_config(adata,af,odata._abr|default(false)) -}}
+{% endfor %}
+{% endfor %}
+{% endmacro %}
+
+{% if ospf.areas is defined %}
+{{ ospf_area_config(ospf,'') }}
+{% endif %}
+{% if vrfs is defined %}
+{% for vname,vdata in vrfs.items() if vdata.ospf.areas is defined %}
+{{ ospf_area_config(vdata.ospf,vname) }}
+{% endfor %}
+{% endif %}
+
diff --git a/netsim/extra/ospf.areas/defaults.yml b/netsim/extra/ospf.areas/defaults.yml
index 1e0577c56c..046e155f49 100644
--- a/netsim/extra/ospf.areas/defaults.yml
+++ b/netsim/extra/ospf.areas/defaults.yml
@@ -2,6 +2,8 @@
#
---
devices:
+ cumulus_nvue.features.ospf.areas: True
+ dellos10.features.ospf.areas: True
frr.features.ospf.areas: True
eos.features.ospf.areas: True
junos.features.ospf.areas: True
diff --git a/netsim/extra/ospf.areas/dellos10.j2 b/netsim/extra/ospf.areas/dellos10.j2
new file mode 100644
index 0000000000..b299db5db0
--- /dev/null
+++ b/netsim/extra/ospf.areas/dellos10.j2
@@ -0,0 +1,42 @@
+{% macro area_config(adata,af,abr) %}
+{% if adata.kind == 'stub' %}
+ area {{ adata.area }} stub {% if not adata.inter_area %}no-summary{% endif +%}
+{% endif %}
+{% if adata.kind == 'nssa' and af == 'ipv4' %}
+ area {{ adata.area }} nssa {% if not adata.inter_area %}no-summary{% endif +%}
+{% if abr and adata.default|default(false) %}
+ area {{ adata.area }} nssa default-information-originate
+{% endif %}
+{% endif %}
+{% if adata.kind in ['stub','nssa'] and adata.default.cost is defined and af == 'ipv4' %}
+ area {{ adata.area }} default-cost {{ adata.default.cost }}
+{% endif %}
+{% if abr %}
+{% for range in adata.range|default([]) if af in range %}
+ area {{ adata.area }} range {{ range[af] }}
+{% endfor %}
+{% for range in adata.filter|default([]) if af in range %}
+ area {{ adata.area }} range {{ range[af] }} no-advertise
+{% endfor %}
+{% endif %}
+{% endmacro %}
+
+{% macro ospf_area_config(odata,vrf='',pid=1) %}
+{% for af in ['ipv4','ipv6'] if odata.af[af] is defined %}
+{% set proto = 'ospf' if af == 'ipv4' else 'ospfv3' %}
+router {{ proto }} {{ pid }}{% if vrf %} vrf {{ vrf }}{% endif +%}
+{% for adata in odata.areas %}
+{{ area_config(adata,af,odata._abr|default(false)) -}}
+{% endfor %}
+ exit
+{% endfor %}
+{% endmacro %}
+
+{% if ospf.areas is defined %}
+{{ ospf_area_config(ospf,'',ospf.process|default(1)) }}
+{% endif %}
+{% if vrfs is defined %}
+{% for vname,vdata in vrfs.items() if vdata.ospf.areas is defined %}
+{{ ospf_area_config(vdata.ospf,vname,vdata.vrfidx) }}
+{% endfor %}
+{% endif %}