Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/caveats.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,16 @@ ansible_httpapi_port: 80
* An OSPFv3 ABR running FRR release 10.3 does not originate summary external routes from NSSA areas
* As of FRRouting release 10.6.0, the FRR implementation of EVPN for VXLAN-over-IPv6 is incompatible with the Arista EOS implementation due to different encodings of the PMSI_TUNNEL_ATTRIBUTE.

(caveats-ocnos)=
## IP Infusion OcNOS

* _netlab_ runs IP Infusion OcNOS as a *vrnetlab* container (*containerlab* kind `ipinfusion_ocnos`). Download the OcNOS VM image for KVM (free, registration required) from the [IP Infusion web site](https://www.ipinfusion.com/free-software-demos/?vm=KVM&src=netlab). Decompress it (`xz -d`), copy the `.qcow2` file into the `ipinfusion/ocnos` directory of the [vrnetlab fork](https://github.com/srl-labs/vrnetlab), and run `make docker-image`.
* We tested _netlab_ with the `OcNOS-SP-PLUS-x86-7.0.0-262-GA.qcow2` image; `make docker-image` builds it into the `vrnetlab/ipinfusion_ocnos:7.0.0-262` container image.
* _netlab_ uses the default credentials of the *containerlab* kind (`admin`/`admin@123`).
* The management interface (`eth0`) is in the `management` VRF, so the *vrnetlab* management subnet (`10.0.0.0/24`) does not conflict with the _netlab_ loopback addresses. The data-plane interfaces start with `eth1`.
* The loopback interface `lo` always has 127.0.0.1/8 as its primary IPv4 address; _netlab_ configures the loopback IPv4 address as a secondary address. Additional loopback interfaces are named `loopback<n>`.
* OcNOS assigns OSPFv2 areas with **network** statements in the OSPF routing process.

(caveats-junos)=
## Common Junos caveats

Expand Down
13 changes: 12 additions & 1 deletion docs/dev/config/netmiko.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This document describes how the *netmiko* configuration method works and which d
| Cisco IOS/IOS XE[^IOS] | `ios.yml` | `cisco_ios` | Ansible |
| Cisco Nexus OS | `nxos.yml` | `cisco_nxos` | **netmiko** |
| Cisco IOS XR[^XR] | `xr.yml` | `cisco_xr` | **netmiko** |
| IP Infusion OcNOS | `ocnos.yml` | `ipinfusion_ocnos` | **netmiko** |

[^IOS]: Including all devices using **ios** as their parent device: Cisco IOSv, IOSvL2, CSR 1000v, Catalyst 8000v, IOL, and IOL L2 image.

Expand Down Expand Up @@ -107,6 +108,16 @@ netmiko:
step_config: True
```

**IP Infusion OcNOS** (`ocnos.yml`):

```
netmiko:
device_type: ipinfusion_ocnos
error_regexp: # OcNOS errors start with '%'; '% Warning' lines are informational
- "^%(?! Warning).*"
step_config: true
```

```{tip}
The **error_regexp** regular expression should match the whole error message, not just its first few characters. The matched text is included in the error message _netlab_ displays when the configuration deployment fails.
```
Expand Down Expand Up @@ -183,7 +194,7 @@ You can use any integration test (see [](dev-config-initial-tests)) to test the
netlab up -d eos -s defaults.devices.eos.group_vars.netlab_config_mode=netmiko <test>
```

The `tests/config` directory contains topologies that can be used to test the error detection on Cisco IOS (`ios-netmiko.yml`) and Nexus OS (`nxos-netmiko.yml`) with the **netlab config** command. For example, to verify that the *netmiko* configuration method detects invalid configuration commands on Cisco IOS:
The `tests/config` directory contains topologies that can be used to test the error detection on Cisco IOS (`ios-netmiko.yml`), Nexus OS (`nxos-netmiko.yml`), and OcNOS (`ocnos-netmiko.yml`) with the **netlab config** command. For example, to verify that the *netmiko* configuration method detects invalid configuration commands on Cisco IOS:

* Start the lab with `netlab up ios-netmiko.yml`
* Execute `netlab config invalid` to deploy an invalid configuration command; the configuration deployment should fail.
3 changes: 2 additions & 1 deletion docs/labs/clab.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Lab topology file created by **[netlab up](../netlab/up.md)** or **[netlab creat
| Extreme VOSS | vrnetlab/extreme_voss:9.4.0.0 |
| Fortinet FortiOS | vrnetlab/vr-fortios:7.4.8 |
| FRR | quay.io/frrouting/frr:10.1.2 |
| IP Infusion OcNOS [❗](caveats-ocnos) | vrnetlab/ipinfusion_ocnos:7.0.0-262 |
| Juniper vMX | vrnetlab/vr-vmx:18.2R1.9 |
| Juniper vPTX | vrnetlab/juniper_vjunosevolved:23.4R2-S2.1 |
| Juniper vSRX | vrnetlab/vr-vsrx:23.1R1.8 |
Expand All @@ -69,7 +70,7 @@ Lab topology file created by **[netlab up](../netlab/up.md)** or **[netlab creat
* Follow Cisco's documentation to [install the IOS XRd container](build-iosxr-clab) (you have to change topology defaults to use Cisco IOS XRd vRouter), making sure the container image name matches the one _netlab_ uses (alternatively, [change the default image name](default-device-image) for the IOS XRd container).
* Cisco 8000v containerlab image (once you manage to get it) has to be [installed](https://containerlab.dev/manual/kinds/c8000/#getting-cisco-8000-containerlab-docker-images) with the **docker image load** command.

You can also use [vrnetlab](https://github.com/srl-labs/vrnetlab) to build VM-in-container images for Catalyst 8000v, Cisco CSR 1000v, Cisco IOSv, Cisco IOS on Linux (including layer-2 image), Nexus 9300v, IOS XR, Mikrotik RouterOS, Arista vEOS, Juniper vMX, vPTX, vQFX, and a few other devices.
You can also use [vrnetlab](https://github.com/srl-labs/vrnetlab) to build VM-in-container images for Catalyst 8000v, Cisco CSR 1000v, Cisco IOSv, Cisco IOS on Linux (including layer-2 image), Nexus 9300v, IOS XR, Mikrotik RouterOS, Arista vEOS, IP Infusion OcNOS, Juniper vMX, vPTX, vQFX, and a few other devices.

```{warning}
* You might have to change the default loopback address pool when using _vrnetlab_ images. See [](clab-vrnetlab) for details.
Expand Down
7 changes: 7 additions & 0 deletions docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
| Fortinet FortiOS [❗](caveats-fortios) | fortios | minimal |
| FRRouting (FRR) [❗](caveats-frr) | frr | full |
| [Generic Linux host](generic-linux-devices) | linux | full |
| IP Infusion OcNOS [❗](caveats-ocnos) | ocnos | minimal |
| Juniper cRPD | crpd | full |
| Juniper cSRX [❗](caveats-csrx) | csrx | minimal |
| Juniper vMX [❗](caveats-vmx) | vmx | best effort |
Expand Down Expand Up @@ -138,6 +139,7 @@ You cannot use all supported network devices with all virtualization providers.
| Fortinet FortiOS | [✅](build-fortios) | ✅ |
| FRR | [✅](build-frr)[❗](caveats-frr) | ✅ |
| Generic Linux (Ubuntu/Alpine) | [❗](labs/linux.md) | ✅ |
| IP Infusion OcNOS | ❌ | ✅[❗](caveats-ocnos) |
| Juniper cRPD | ❌ | ✅ |
| Juniper cSRX | ❌ | ✅ |
| Juniper vMX | ❌ | ✅[❗](clab-vrnetlab) |
Expand Down Expand Up @@ -306,6 +308,7 @@ The following system-wide features are configured on supported network operating
| Fortinet FortiOS | ✅ | ❌ | ✅ | ✅ | ✅ |
| FRR | ✅ | ✅[^HIF] | ❌ | ✅ | ✅ |
| Generic Linux | ✅ | ✅[^HIF] | ✅[❗](linux-lldp) | ✅ | ✅ |
| IP Infusion OcNOS | ✅ | ❌ | ❌ | ✅ | ✅ |
| Juniper cSRX | ✅ | ✅ | ❌ | ❌ | ❌ |
| Junos[^Junos] | ✅ | ❌ | ✅ | ✅ | ✅ |
| Mikrotik RouterOS 6 | ✅ | ✅ | ✅[❗](caveats-routeros6) | ✅ | ✅ |
Expand Down Expand Up @@ -340,6 +343,7 @@ The following interface parameters are configured on supported network operating
| Fortinet FortiOS | ✅ | ✅ | ✅[❗](caveats-fortios) | ❌ |
| FRR | ✅ | ✅ | ✅ | ✅ |
| Generic Linux | ❌ | ❌ | ✅ | ❌ |
| IP Infusion OcNOS | ✅ | ❌ | ✅ | ✅ |
| Juniper cSRX | ✅ | ❌ | ✅ | ❌ |
| Junos[^Junos] | ✅ | ✅ | ✅ | ❌ |
| Mikrotik RouterOS 6 | ✅ | ❌ | ✅ | ❌ |
Expand Down Expand Up @@ -371,6 +375,7 @@ The following interface addresses are supported on various platforms; most daemo
| Fortinet FortiOS | ✅ | ✅ | ❌ | ❌ |
| FRR | ✅ | ✅ | ✅ | ✅ |
| Generic Linux | ✅ | ✅ | ❌ | ❌ |
| IP Infusion OcNOS | ✅ | ✅ | ❌ | ❌ |
| Juniper cSRX | ✅ | ✅ | ❌ | ❌ |
| Junos[^Junos] | ✅ | ✅ | ✅ | ❌ |
| Mikrotik RouterOS 6 | ✅ | ✅ | ❌ | ❌ |
Expand Down Expand Up @@ -421,6 +426,7 @@ Routing protocol [configuration modules](module-reference.md) are supported on t
| Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ |
| Fortinet FortiOS | ✅ [❗](caveats-fortios) | ❌ | ❌ | ✅ | ❌ |
| FRR | ✅ | ✅ | ❌ | ✅ | ✅ |
| IP Infusion OcNOS | ✅ | ✅ | ❌ | ✅ | ❌ |
| Junos[^Junos] | ✅ | ✅ | ❌ | ✅ | ❌ |
| Mikrotik RouterOS 6 | ✅ | ❌ | ❌ | ✅ | ❌ |
| Mikrotik RouterOS 7 | ✅ | ❌ | ❌ | ✅ | ❌ |
Expand Down Expand Up @@ -550,6 +556,7 @@ Core *netlab* functionality and all multi-protocol routing protocol configuratio
| Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ |
| Fortinet FortiOS | ✅ | ❌ | ❌ | ✅ | ❌ |
| FRR | ✅ | ✅ | ❌ | ✅ | ❌ |
| IP Infusion OcNOS | ✅ | ✅ | ❌ | ✅ | ❌ |
| Junos[^Junos] | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| Mikrotik RouterOS 6 | ❌ | ❌ | ❌ | ✅ | ❌ |
| Mikrotik RouterOS 7 | ✅ | ❌ | ❌ | ✅ | ❌ |
Expand Down
6 changes: 6 additions & 0 deletions netsim/ansible/tasks/deploy-config/ocnos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: "ocnos_config: deploying {{ netsim_action }} from {{ config_template }}"
ipinfusion.ocnos.ocnos_config:
src: "{{ config_template }}"
save_when: "{{ 'modified' if netlab_save_config|default(True) else 'never' }}"
tags: [ print_action, always ]
9 changes: 9 additions & 0 deletions netsim/ansible/tasks/fetch-config/ocnos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# fetch IP Infusion OcNOS configuration with ocnos_command
#
---
- ipinfusion.ocnos.ocnos_command:
commands: [ show running-config ]
register: ocnos_running_config

- set_fact:
ansible_net_config: "{{ ocnos_running_config.stdout[0] }}"
32 changes: 32 additions & 0 deletions netsim/ansible/templates/bgp/ocnos.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{% import "ocnos.macro.j2" as bgpcfg %}
{#
Legacy bgp.originate (IPv4 prefixes): OcNOS advertises a "network" prefix only when it
is in the routing table, so add a discard route for each originated prefix
#}
{% for pfx in bgp.originate|default([]) %}
ip route {{ pfx }} null
{% endfor %}
!
router bgp {{ bgp.as }}
{% if bgp.router_id is defined %}
bgp router-id {{ bgp.router_id }}
{% endif %}
{% if bgp.rr|default(False) and bgp.rr_cluster_id|default(False) %}
bgp cluster-id {{ bgp.rr_cluster_id }}
{% endif %}
{% for n in bgp.neighbors %}
{% for af in ['ipv4','ipv6'] if n[af] is defined %}
{{ bgpcfg.neighbor_global(n,n[af]) }}
{% endfor %}
{% endfor %}
{% for af in ['ipv4','ipv6'] if bgp[af] is defined %}
address-family {{ af }} unicast
{% for pfx in bgp.advertise|default([]) if af in pfx %}
network {{ pfx[af] }}
{% endfor %}
{% for n in bgp.neighbors if n.activate[af]|default(false) %}
{{ bgpcfg.neighbor_af(n,n[af],bgp) }}
{% endfor %}
exit-address-family
{% endfor %}
!
29 changes: 29 additions & 0 deletions netsim/ansible/templates/bgp/ocnos.macro.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{#
Global BGP neighbor definition. OcNOS sends standard and extended communities by default
("send-community" on an enabled type is rejected), and advertisement-interval is a
router-level neighbor setting.
#}
{% macro neighbor_global(n,ip) %}
neighbor {{ ip }} remote-as {{ n.as }}
neighbor {{ ip }} description {{ n.name }}
neighbor {{ ip }} advertisement-interval 0
{% if n._source_intf is defined %}
neighbor {{ ip }} update-source {{ n._source_intf.ifname }}
{% endif %}
{%- endmacro %}
{#
Address family BGP neighbor definition
#}
{% macro neighbor_af(n,ip,bgp) %}
neighbor {{ ip }} activate
{% if n.next_hop_self|default(False) %}
neighbor {{ ip }} next-hop-self
{% endif %}
{% if n.rr_client|default(False) %}
neighbor {{ ip }} route-reflector-client
{% endif %}
{% set c_list = bgp.community[n.type]|default([]) %}
{% for c_type in ['standard','extended'] if c_type not in c_list %}
no neighbor {{ ip }} send-community {{ c_type }}
{% endfor %}
{%- endmacro %}
24 changes: 24 additions & 0 deletions netsim/ansible/templates/initial/ocnos.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
hostname {{ inventory_hostname.replace("_","-") }}
!
{% for l in netlab_interfaces %}
interface {{ l.ifname }}
{% if l.name is defined %}
description {{ l.name|replace('"','') }}{{ " [" + l.role + "]" if l.role is defined else "" }}
{% elif l.type|default("") == "stub" %}
description Stub interface
{% endif %}
{% if l.mtu is defined and l.type|default("") != "loopback" %}
mtu {{ l.mtu }}
{% endif %}
{% if 'ipv4' in l and l.ipv4 is string %}
{# "lo" already has 127.0.0.1/8 as its primary address #}
ip address {{ l.ipv4 }}{{ " secondary" if l.ifname == "lo" else "" }}
{% endif %}
{% if 'ipv6' in l and l.ipv6 is string %}
ipv6 address {{ l.ipv6 }}
{% endif %}
{% if l.type|default("") != "loopback" %}
{{ "shutdown" if l.shutdown|default(False) else "no shutdown" }}
{% endif %}
!
{% endfor %}
41 changes: 41 additions & 0 deletions netsim/ansible/templates/isis/ocnos.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{#
OcNOS uses "level-2-only" where netlab uses "level-2", and "isis metric" takes narrow

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

That's perfectly OK. Configuration keywords are device-specific in many cases.

metrics only (1-63); wide metrics are set with "isis wide-metric".
#}
{% set lvl = { 'level-1': 'level-1', 'level-2': 'level-2-only', 'level-1-2': 'level-1-2' } %}
{% set mt_lvl = { 'level-1': 'level-1', 'level-2': 'level-2', 'level-1-2': 'level-1-2' } %}
{% set wide = isis.wide_metrics|default(True) %}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

wide_metrics is not a valid isis attribute, and (according to documentation ;) netlab IS-IS module supports only wide metrics anyway.

!
router isis {{ isis.instance }}
is-type {{ lvl[isis.type]|default(isis.type) }}
net {{ isis.net }}
metric-style {{ 'wide' if wide else 'narrow' }}
dynamic-hostname
{% for l in netlab_interfaces if l.isis.passive|default(False) and 'vrf' not in l %}
passive-interface {{ l.ifname }}
{% endfor %}
{% if isis.af.ipv6 is defined %}
address-family ipv6 unicast
multi-topology {{ mt_lvl[isis.type]|default('level-1-2') }}
exit-address-family
{% endif %}
!
{% for l in netlab_interfaces if 'isis' in l and 'vrf' not in l %}
interface {{ l.ifname }}
{% if 'ipv4' in l and isis.af.ipv4 is defined %}
ip router isis {{ isis.instance }}
{% endif %}
{% if 'ipv6' in l and isis.af.ipv6 is defined %}
ipv6 router isis {{ isis.instance }}
{% endif %}
{% if l.isis.network_type is defined %}
isis network {{ l.isis.network_type }}
{% endif %}
{% if l.isis.type is defined %}
isis circuit-type {{ lvl[l.isis.type]|default(l.isis.type) }}
{% endif %}
{% if l.isis.metric is defined %}
isis {{ 'wide-metric' if wide else 'metric' }} {{ l.isis.metric }}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

No need to deal with both (unless you want to have extra flexibility)

{% endif %}
!
{% endfor %}
79 changes: 79 additions & 0 deletions netsim/ansible/templates/ospf/ocnos.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{#
OcNOS assigns OSPF areas with "network" statements in the router configuration,
not with interface commands. A host prefix (interface address) is accepted and
normalized to the subnet.
#}
{% set ospf_pid = ospf.process|default(1) %}
{% if ospf.af.ipv4 is defined %}
router ospf {{ ospf_pid }}
{% if ospf.router_id is defined %}
ospf router-id {{ ospf.router_id }}
{% endif %}
{% if ospf.reference_bandwidth is defined %}
auto-cost reference-bandwidth {{ ospf.reference_bandwidth }}
{% endif %}
{% for l in netlab_interfaces if 'ospf' in l and l.ipv4 is string and 'vrf' not in l %}
network {{ l.ipv4 }} area {{ l.ospf.area }}
{% endfor %}
{% for l in netlab_interfaces if l.ospf.passive|default(False) and 'vrf' not in l %}
passive-interface {{ l.ifname }} enable
{% endfor %}
!
{% for l in netlab_interfaces if 'ospf' in l and l.ipv4 is string and 'vrf' not in l %}
interface {{ l.ifname }}
{% if l.ospf.network_type is defined %}
ip ospf network {{ l.ospf.network_type }}
{% endif %}
{% if l.ospf.cost is defined %}
ip ospf cost {{ l.ospf.cost }}
{% endif %}
{% if l.ospf.timers.hello is defined %}
ip ospf hello-interval {{ l.ospf.timers.hello }}
{% endif %}
{% if l.ospf.timers.dead is defined %}
ip ospf dead-interval {{ l.ospf.timers.dead }}
{% endif %}
{% if l.ospf.priority is defined %}
ip ospf priority {{ l.ospf.priority }}
{% endif %}
{% if l.ospf.password is defined %}
ip ospf authentication
ip ospf authentication-key {{ l.ospf.password }}
{% endif %}
!
{% endfor %}
{% endif %}
{#
OSPFv3: the process tag is the OSPF process ID; interfaces join an area with
"ipv6 router ospf area <area> tag <tag>"
#}
{% if ospf.af.ipv6 is defined %}
router ipv6 ospf {{ ospf_pid }}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Please add the reference bandwidth if it's available for OSPFv3, otherwise just ignore this comment. It's a best-effort attribute anyway.

{% if ospf.router_id is defined %}
router-id {{ ospf.router_id }}
{% endif %}
{% for l in netlab_interfaces if l.ospf.passive|default(False) and 'ipv6' in l and 'vrf' not in l %}
passive-interface {{ l.ifname }} enable
{% endfor %}
!
{% for l in netlab_interfaces if 'ospf' in l and 'ipv6' in l and 'vrf' not in l %}
interface {{ l.ifname }}
ipv6 router ospf area {{ l.ospf.area }} tag {{ ospf_pid }}
{% if l.ospf.network_type is defined %}
ipv6 ospf network {{ l.ospf.network_type }}
{% endif %}
{% if l.ospf.cost is defined %}
ipv6 ospf cost {{ l.ospf.cost }}
{% endif %}
{% if l.ospf.timers.hello is defined %}
ipv6 ospf hello-interval {{ l.ospf.timers.hello }}
{% endif %}
{% if l.ospf.timers.dead is defined %}
ipv6 ospf dead-interval {{ l.ospf.timers.dead }}
{% endif %}
{% if l.ospf.priority is defined %}
ipv6 ospf priority {{ l.ospf.priority }}
{% endif %}
!
{% endfor %}
{% endif %}
Loading