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 %}