diff --git a/docs/caveats.md b/docs/caveats.md index bf17868b28..01841e111b 100644 --- a/docs/caveats.md +++ b/docs/caveats.md @@ -449,6 +449,14 @@ Implementation limitations in import/export route filters (reported as errors th * VRRPv3 for IPv4 implementation uses the [checksum calculation that is incompatible with most other VRRP implementations](https://blog.ipspace.net/2025/01/sturgeon-law-vrrp-edition/). The `checksum-without-pseudoheader` configuration command does not seem to be available. * Anycast gateway is unsupported +(caveats-csrx)= +## Juniper cSRX + +* cSRX is a native container and should not be confused with vSRX which is a vrnetlab VM in a container. See [Juniper's cSRX documentation](https://www.juniper.net/documentation/us/en/software/csrx/csrx-consolidated-deployment-guide/topics/concept/security-csrx-docker-feature-support.html) for more details. +* cSRX might require a license file to unlock additional features. You can specify the location of the license file with the **clab.license** node parameter or **defaults.devices.csrx.clab.node.license** [device default](topo-defaults). +* cSRX supports up to 17 interfaces: 1 out-of-band management Interface (eth0) and 16 in-band interfaces (ge-0/0/0 to ge-0/0/15). +* Since cSRX does not support any dynamic routing protocols, use the **routing** module and static routes. + (caveats-vmx)= ## Juniper vMX diff --git a/docs/platforms.md b/docs/platforms.md index 292734ff4d..74e60c23b2 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -34,6 +34,7 @@ | FRRouting (FRR) [❗](caveats-frr) | frr | full | | [Generic Linux host](generic-linux-devices) | linux | full | | Juniper cRPD | crpd | full | +| Juniper cSRX [❗](caveats-csrx) | csrx | minimal | | Juniper vMX [❗](caveats-vmx) | vmx | best effort | | Juniper vPTX (vJunos EVO) [❗](caveats-vptx) | vptx | full | | Juniper vSRX 3.0 [❗](caveats-vsrx) | vsrx | best effort | @@ -131,6 +132,7 @@ You cannot use all supported network devices with all virtualization providers. | FRR | [✅](build-frr)[❗](caveats-frr) | ✅[❗](caveats-frr) | ✅ | | Generic Linux (Ubuntu/Alpine) [❗](labs/linux.md) | ✅ | ✅ | ✅ | | Juniper cRPD | ❌ | ❌ | ✅ | +| Juniper cSRX | ❌ | ❌ | ✅ | | Juniper vMX | ❌ | ❌ | ✅[❗](clab-vrnetlab) | | Juniper vPTX | [✅](build-vptx) | ❌ | ✅[❗](clab-vrnetlab) | | Juniper vSRX 3.0 | [✅](build-vsrx) | ✅ | ✅[❗](caveats-vsrx) | @@ -201,6 +203,7 @@ Ansible playbooks included with **netlab** can deploy and collect device configu | Fortinet FortiOS | ✅ | ✅ | | FRR | ✅ [❗](caveats-frr) | ✅[❗](caveats-frr) | | Generic Linux | ✅ | ❌ | +| Juniper cSRX | ✅ | ❌ | | Junos[^Junos] | ✅ | ✅ | | Mikrotik RouterOS 6 | ✅ | ✅ | | Mikrotik RouterOS 7 | ✅ | ✅ | @@ -217,7 +220,7 @@ Ansible playbooks included with **netlab** can deploy and collect device configu [^XE]: Includes Cisco CSR 1000v, Cisco Catalyst 8000v, Cisco IOS-on-Linux (IOL) and IOL Layer-2 image -[^Junos]: Includes cRPD, vMX, vSRX, vPTX, vJunos-switch, and vJunos-router +[^Junos]: Includes cRPD, vMX, vSRX, vPTX, vJunos-switch, and vJunos-router but not cSRX [^SROS]: Includes the Nokia SR-SIM container and the Virtualized 7750 SR and 7950 XRS Simulator (vSIM) virtual machine @@ -286,6 +289,7 @@ The following system-wide features are configured on supported network operating | Fortinet FortiOS | ✅ | ❌ | ✅ | ✅ | ✅ | | FRR | ✅ | ✅[^HIF] | ❌ | ✅ | ✅ | | Generic Linux | ✅ | ✅[^HIF] | ✅[❗](linux-lldp) | ✅ | ✅ | +| Juniper cSRX | ✅ | ✅ | ❌ | ❌ | ❌ | | Junos[^Junos] | ✅ | ❌ | ✅ | ✅ | ✅ | | Mikrotik RouterOS 6 | ✅ | ✅ | ✅[❗](caveats-routeros6) | ✅ | ✅ | | Mikrotik RouterOS 7 | ✅ | ✅ | ✅[❗](caveats-routeros7) | ✅ | ✅ | @@ -317,6 +321,7 @@ The following interface parameters are configured on supported network operating | Fortinet FortiOS | ✅ | ✅ | ✅[❗](caveats-fortios) | ❌ | | FRR | ✅ | ✅ | ✅ | ✅ | | Generic Linux | ❌ | ❌ | ✅ | ❌ | +| Juniper cSRX | ✅ | ❌ | ✅ | ❌ | | Junos[^Junos] | ✅ | ✅ | ✅ | ❌ | | Mikrotik RouterOS 6 | ✅ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 7 | ✅ | ❌ | ✅ | ✅ | @@ -345,6 +350,7 @@ The following interface addresses are supported on various platforms; most daemo | Fortinet FortiOS | ✅ | ✅ | ❌ | ❌ | | FRR | ✅ | ✅ | ✅ | ✅ | | Generic Linux | ✅ | ✅ | ❌ | ❌ | +| Juniper cSRX | ✅ | ✅ | ❌ | ❌ | | Junos[^Junos] | ✅ | ✅ | ✅ | ❌ | | Mikrotik RouterOS 6 | ✅ | ✅ | ❌ | ❌ | | Mikrotik RouterOS 7 | ✅ | ✅ | ❌ | ❌ | @@ -470,8 +476,8 @@ The data plane [configuration modules](module-reference.md) are supported on the | Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | FRR | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Juniper cRPD | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | -| Juniper vMX | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | -| Juniper vPTX | ✅ | ✅ | ✅ [❗](caveats-vptx) | ✅ | ✅ | ❌ | +| Juniper vMX | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | +| Juniper vPTX | ✅ | ✅ | ✅ [❗](caveats-vptx) | ✅ | ✅ | ❌ | | Juniper vSRX 3.0 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | | vJunos-switch | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | vJunos-router | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | diff --git a/netsim/ansible/templates/initial/crpd.j2 b/netsim/ansible/templates/initial/crpd.j2 index b4390fd0e5..6440a283dd 100644 --- a/netsim/ansible/templates/initial/crpd.j2 +++ b/netsim/ansible/templates/initial/crpd.j2 @@ -2,60 +2,6 @@ {% include 'junos.vrf.j2' %} {% endif %} -interfaces { -{% for l in netlab_interfaces|default([]) %} - {{ l.ifname }} { -{% if l.mtu is defined %} - mtu {{ l.mtu }}; -{% endif %} -{% if l.name is defined %} - description "{{ l.name }}{{ " ["+l.role+"]" if l.role is defined else "" }}"; -{% elif l.type|default("") == "stub" %} - description "Stub interface" -{% endif %} - unit 0 { -{# - IPv4 addresses -#} -{% if 'ipv4' in l %} - family inet { -{% if l.ipv4 == True %} - unnumbered-address lo0.0; -{% elif l.ipv4|ansible.utils.ipv4 %} - address {{ l.ipv4 }}; -{% else %} -! Invalid IPv4 address {{ l.ipv4 }} -{% endif %} - } -{% endif %} -{# - IPv6 addresses -#} -{% if 'ipv6' in l %} - family inet6 { -{% if l.ipv6 is string %} - address {{ l.ipv6 }}; -{% endif %} - } -{% endif %} - } - } -{% endfor %} -} -protocols { - lldp { - interface {{ mgmt.ifname|default('fxp0') }} { - disable; - } - interface all; - } -{% for l in interfaces if 'ipv6' in l and l.type != 'loopback' %} -{% if loop.first %} - router-advertisement { -{% endif %} - interface {{ l.ifname }}; -{% if loop.last %} - } -{% endif %} -{% endfor %} -} +{% include 'junos/container_interfaces.j2' %} + +{% include 'junos/lldp.j2' %} diff --git a/netsim/ansible/templates/initial/csrx.j2 b/netsim/ansible/templates/initial/csrx.j2 new file mode 100644 index 0000000000..616e21b8c1 --- /dev/null +++ b/netsim/ansible/templates/initial/csrx.j2 @@ -0,0 +1,15 @@ +{% include 'junos/hosts.j2' %} + +{% include 'junos/container_interfaces.j2' %} + +security { + zones { + security-zone default { + interfaces { + {% for l in netlab_interfaces|default([]) %} + {{ l.ifname }}.0; + {% endfor %} + } + } + } +} diff --git a/netsim/ansible/templates/initial/junos.j2 b/netsim/ansible/templates/initial/junos.j2 index 77b39cf605..63b768636d 100644 --- a/netsim/ansible/templates/initial/junos.j2 +++ b/netsim/ansible/templates/initial/junos.j2 @@ -1,15 +1,4 @@ -system { - host-name {{ inventory_hostname }}; - static-host-mapping { -{% for k,v in hostvars.items() if k != inventory_hostname %} -{% if v.loopback.ipv4 is defined %} - {{ k|replace('_','') }} inet {{ v.loopback.ipv4|ansible.utils.ipaddr('address') }}; -{% elif v.interfaces|default([]) and v.interfaces[0].ipv4|default(False) is string %} - {{ k|replace('_','') }} inet {{ v.interfaces[0].ipv4|ansible.utils.ipaddr('address') }}; -{% endif %} -{% endfor %} - } -} +{% include 'junos/hosts.j2' %} {% if vrfs is defined %} {% include 'junos.vrf.j2' %} @@ -38,7 +27,7 @@ interfaces { {% elif l.type|default("") == "stub" %} description "Stub interface" {% endif %} - + {% if l.bandwidth is defined %} bandwidth {{ l.bandwidth * 1000 }}; {% endif %} @@ -74,24 +63,8 @@ interfaces { mtu {{ l.mtu }}; {% endif %} } -{% endif %} - } -{% endfor %} -} -protocols { - lldp { - interface {{ mgmt.ifname|default('fxp0') }} { - disable; - } - interface all; - } -{% for l in netlab_interfaces if 'ipv6' in l and l.type != 'loopback' %} -{% if loop.first %} - router-advertisement { -{% endif %} - interface {{ l.ifname }}; -{% if loop.last %} +{% endif %} } -{% endif %} {% endfor %} } +{% include 'junos/lldp.j2' %} diff --git a/netsim/ansible/templates/initial/junos/container_interfaces.j2 b/netsim/ansible/templates/initial/junos/container_interfaces.j2 new file mode 100644 index 0000000000..e4f2571c9b --- /dev/null +++ b/netsim/ansible/templates/initial/junos/container_interfaces.j2 @@ -0,0 +1,40 @@ +interfaces { +{% for l in netlab_interfaces|default([]) %} + {{ l.ifname }} { +{% if l.mtu is defined %} + mtu {{ l.mtu }}; +{% endif %} +{% if l.name is defined %} + description "{{ l.name }}{{ " ["+l.role+"]" if l.role is defined else "" }}"; +{% elif l.type|default("") == "stub" %} + description "Stub interface" +{% endif %} + unit 0 { +{# + IPv4 addresses +#} +{% if 'ipv4' in l %} + family inet { +{% if l.ipv4 == True %} + unnumbered-address lo0.0; +{% elif l.ipv4|ansible.utils.ipv4 %} + address {{ l.ipv4 }}; +{% else %} +! Invalid IPv4 address {{ l.ipv4 }} +{% endif %} + } +{% endif %} +{# + IPv6 addresses +#} +{% if 'ipv6' in l %} + family inet6 { +{% if l.ipv6 is string %} + address {{ l.ipv6 }}; +{% endif %} + } +{% endif %} + } + } +{% endfor %} +} diff --git a/netsim/ansible/templates/initial/junos/hosts.j2 b/netsim/ansible/templates/initial/junos/hosts.j2 new file mode 100644 index 0000000000..5356668819 --- /dev/null +++ b/netsim/ansible/templates/initial/junos/hosts.j2 @@ -0,0 +1,12 @@ +system { + host-name {{ inventory_hostname }}; + static-host-mapping { +{% for k,v in hostvars.items() if k != inventory_hostname %} +{% if v.loopback.ipv4 is defined %} + {{ k|replace('_','') }} inet {{ v.loopback.ipv4|ansible.utils.ipaddr('address') }}; +{% elif v.interfaces|default([]) and v.interfaces[0].ipv4|default(False) is string %} + {{ k|replace('_','') }} inet {{ v.interfaces[0].ipv4|ansible.utils.ipaddr('address') }}; +{% endif %} +{% endfor %} + } +} diff --git a/netsim/ansible/templates/initial/junos/lldp.j2 b/netsim/ansible/templates/initial/junos/lldp.j2 new file mode 100644 index 0000000000..e614cb8d8e --- /dev/null +++ b/netsim/ansible/templates/initial/junos/lldp.j2 @@ -0,0 +1,17 @@ +protocols { + lldp { + interface {{ mgmt.ifname|default('fxp0') }} { + disable; + } + interface all; + } +{% for l in netlab_interfaces if 'ipv6' in l and l.type != 'loopback' %} +{% if loop.first %} + router-advertisement { +{% endif %} + interface {{ l.ifname }}; +{% if loop.last %} + } +{% endif %} +{% endfor %} +} diff --git a/netsim/ansible/templates/routing/csrx.j2 b/netsim/ansible/templates/routing/csrx.j2 new file mode 100644 index 0000000000..3554877f21 --- /dev/null +++ b/netsim/ansible/templates/routing/csrx.j2 @@ -0,0 +1,3 @@ +{% if routing.static is defined %} +{% include 'junos/static.j2' %} +{% endif %} diff --git a/netsim/devices/csrx.py b/netsim/devices/csrx.py new file mode 100644 index 0000000000..97d2c31e89 --- /dev/null +++ b/netsim/devices/csrx.py @@ -0,0 +1,23 @@ +# +# Juniper cSRX quirks +# +from box import Box + +from ..utils import log +from . import _Quirks + + +def csrx_port_num(node: Box) -> None: + if_count = len(node.get('interfaces', [])) + node.clab.env.CSRX_PORT_NUM = if_count + 1 # +1 for the management interface + if if_count > 16: + log.error( + f'cSRX supports a maximum of 16 interfaces. Node {node.name} has {if_count} interfaces.', + category=log.IncorrectValue, + module=node.device) + +class CSRX(_Quirks): + + @classmethod + def device_quirks(self, node: Box, topology: Box) -> None: + csrx_port_num(node) diff --git a/netsim/devices/csrx.yml b/netsim/devices/csrx.yml new file mode 100644 index 0000000000..deb9cfdc6e --- /dev/null +++ b/netsim/devices/csrx.yml @@ -0,0 +1,46 @@ +--- +description: Juniper cSRX container +group_vars: + ansible_user: root + ansible_ssh_pass: "clab123" + netlab_device_type: csrx + netlab_check_retries: 20 + +mgmt_if: fxp0 +ifindex_offset: 0 +interface_name: ge-0/0/{ifindex} +mtu: 1500 + +features: + initial: + ipv4: + unnumbered: false # The 'unnumbered-address' family inet command does not work on cSRX + ipv6: + lla: true + routing: + static: + vrf: False + discard: True + +clab: + image: csrx:23.4R1.9 + build: https://containerlab.dev/manual/kinds/csrx/ + node: + kind: juniper_csrx + config_templates: + hosts: /etc/hosts:shared + netlab-config: /config/netlab/netlab-config.sh:sh + interface: + name: eth{ifindex+1} + features: + initial: + config_mode: [ sh ] + group_vars: + netlab_config_mode: sh + netlab_show_command: [ cli, -c, 'show $@' ] + netlab_check_command: who + netlab_ready: [ ssh ] + netlab_default_shebang: '#!/config/netlab/netlab-config.sh' + netlab_config_path: /config/netlab/ + +graphite.icon: firewall diff --git a/netsim/templates/provider/clab/csrx/hosts.j2 b/netsim/templates/provider/clab/csrx/hosts.j2 new file mode 100644 index 0000000000..42d1a3c4bd --- /dev/null +++ b/netsim/templates/provider/clab/csrx/hosts.j2 @@ -0,0 +1 @@ +{% include 'linux/hosts.j2' %} diff --git a/netsim/templates/provider/clab/csrx/netlab-config.j2 b/netsim/templates/provider/clab/csrx/netlab-config.j2 new file mode 100644 index 0000000000..6578ad7f12 --- /dev/null +++ b/netsim/templates/provider/clab/csrx/netlab-config.j2 @@ -0,0 +1,19 @@ +#!/bin/bash +tail -n +2 $1 > /tmp/config.conf # stop shebang being included in pushed config +cat </dev/null; then + echo "Configuration load failed, aborting" + exit 1 +else + cat </dev/null; then + echo "Commit failed, aborting" + exit 1 + fi +fi