From 23f14ad6d102d1e78386db6258077f7af484b73e Mon Sep 17 00:00:00 2001 From: leec-666 Date: Wed, 29 Apr 2026 08:30:08 +0000 Subject: [PATCH 1/8] initial commit --- docs/caveats.md | 7 ++ docs/platforms.md | 14 +++- netsim/ansible/templates/initial/csrx.j2 | 65 +++++++++++++++++++ netsim/ansible/templates/routing/csrx.j2 | 26 ++++++++ netsim/devices/csrx.py | 13 ++++ netsim/devices/csrx.yml | 55 ++++++++++++++++ netsim/templates/provider/clab/csrx/hosts.j2 | 1 + .../provider/clab/csrx/netlab-config.j2 | 19 ++++++ 8 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 netsim/ansible/templates/initial/csrx.j2 create mode 100644 netsim/ansible/templates/routing/csrx.j2 create mode 100644 netsim/devices/csrx.py create mode 100644 netsim/devices/csrx.yml create mode 100644 netsim/templates/provider/clab/csrx/hosts.j2 create mode 100644 netsim/templates/provider/clab/csrx/netlab-config.j2 diff --git a/docs/caveats.md b/docs/caveats.md index bf17868b28..71b1d5f83b 100644 --- a/docs/caveats.md +++ b/docs/caveats.md @@ -441,6 +441,13 @@ Implementation limitations in import/export route filters (reported as errors th * When prepending an AS number different than the local one, JunOS puts the prepended AS at the beginning of the *AS_PATH* while other vendors perform the prepending and then add the device's own AS at the beginning. Most EBGP peers deny a BGP update that does not have the neighbor's AS number at the beginning of the *AS_PATH*. If needed, _netlab_ automatically adds the *local-as* to the prepend list to get it at the beginning of the *AS_PATH*. * *as-path* regex syntax for JunOS has different rules than other vendors. For example, a *null as-path* is represented as `()`. netlab tries some as-path conversion, but sometimes it could be wrong. +(caveats-csrx)= +## Juniper cSRX + +* 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.crpd.clab.node.license** [device default](topo-defaults). +* If using more than 2 data interfaces, `clab.env.CSRX_PORT_NUM` must be increased to the number of interfaces + 1 (because it includes the management interface). Setting it higher will result in none of the data interfaces functioning correctly, e.g. no ARP. +* Since cSRX does not support any dynamic routing protocols, the use of the routing module and static routes is recommended. + (caveats-crpd)= ## Juniper cRPD diff --git a/docs/platforms.md b/docs/platforms.md index 292734ff4d..89c91a23dd 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 | ✅ | ✅ | @@ -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 | ✅ | ✅ | ❌ | ❌ | @@ -391,6 +397,7 @@ Routing protocol [configuration modules](module-reference.md) are supported on t | Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ | | Fortinet FortiOS | ✅ [❗](caveats-fortios) | ❌ | ❌ | ❌ | ❌ | | FRR | ✅ | ✅ | ❌ | ✅ | ✅ | +| Juniper cSRX | ❌ | ❌ | ❌ | ❌ | ❌ | | Junos[^Junos] | ✅ | ✅ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 6 | ✅ | ❌ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 7 | ✅ | ❌ | ❌ | ✅ | ❌ | @@ -420,6 +427,7 @@ These devices support additional control-plane protocols or BGP address families | Dell OS10 | ✅ | ✅ | ❌ | ❌ | | Extreme Networks EXOS | ❌ | ❌ | ❌ | ✅ | | FRR | ✅ | ✅ | ✅ | ❌ | +| Juniper cSRX | ❌ | ❌ | ❌ | ❌ | | Juniper cRPD | ✅ | ❌ | ✅ | ❌ | | Juniper vMX | ✅ | ❌ | ✅ | ✅ | | Juniper vPTX | ✅ | ✅ | ✅ | ✅ | @@ -469,9 +477,10 @@ The data plane [configuration modules](module-reference.md) are supported on the | Dell OS10 | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | FRR | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Juniper cSRX | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | Juniper cRPD | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | -| Juniper vMX | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | -| Juniper vPTX | ✅ | ✅ | ✅ [❗](caveats-vptx) | ✅ | ✅ | ❌ | +| Juniper vMX | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | +| Juniper vPTX | ✅ | ✅ | ✅ [❗](caveats-vptx) | ✅ | ✅ | ❌ | | Juniper vSRX 3.0 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | | vJunos-switch | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | vJunos-router | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | @@ -518,6 +527,7 @@ Core *netlab* functionality and all multi-protocol routing protocol configuratio | Dell OS10 | ✅ | ❌ | ❌ | ✅ | ❌ | | Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ | | FRR | ✅ | ✅ | ❌ | ✅ | ❌ | +| Juniper cSRX | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | Junos[^Junos] | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | Mikrotik RouterOS 6 | ❌ | ❌ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 7 | ✅ | ❌ | ❌ | ✅ | ❌ | diff --git a/netsim/ansible/templates/initial/csrx.j2 b/netsim/ansible/templates/initial/csrx.j2 new file mode 100644 index 0000000000..4568f1ce9d --- /dev/null +++ b/netsim/ansible/templates/initial/csrx.j2 @@ -0,0 +1,65 @@ +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 %} + } +} + +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 %} +} + +security { + zones { + security-zone default { + interfaces { + {% for l in netlab_interfaces|default([]) %} + {{ l.ifname }}.0; + {% endfor %} + } + } + } +} diff --git a/netsim/ansible/templates/routing/csrx.j2 b/netsim/ansible/templates/routing/csrx.j2 new file mode 100644 index 0000000000..21defb5995 --- /dev/null +++ b/netsim/ansible/templates/routing/csrx.j2 @@ -0,0 +1,26 @@ +{% if routing.static is defined %} +{% for sr_data in routing.static %} +{# recursive lookup is triggered by a route without the intf parameter #} +{% set route_resolve = ' resolve' if not 'intf' in sr_data.nexthop else '' %} + routing-options { +{% if 'ipv4' in sr_data %} + static route {{sr_data.ipv4}} { +{% if 'discard' in sr_data.nexthop %} + discard; +{% else %} + next-hop {{sr_data.nexthop.ipv4}}{{route_resolve}}; +{% endif %} + } +{% endif %} +{% if 'ipv6' in sr_data %} + rib inet6.0 static route {{sr_data.ipv6}} { +{% if 'discard' in sr_data.nexthop %} + discard; +{% else %} + next-hop {{sr_data.nexthop.ipv6}}{{route_resolve}}; +{% endif %} + } +{% endif %} + } +{% endfor %} +{% endif %} diff --git a/netsim/devices/csrx.py b/netsim/devices/csrx.py new file mode 100644 index 0000000000..7d917a9253 --- /dev/null +++ b/netsim/devices/csrx.py @@ -0,0 +1,13 @@ +# +# Juniper cSRX quirks +# +from box import Box + +from ..utils import log +from . import _Quirks, report_quirk + +class CSRX(_Quirks): + + @classmethod + def device_quirks(self, node: Box, topology: Box) -> None: + from . import junos diff --git a/netsim/devices/csrx.yml b/netsim/devices/csrx.yml new file mode 100644 index 0000000000..475cd918aa --- /dev/null +++ b/netsim/devices/csrx.yml @@ -0,0 +1,55 @@ +--- +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} +loopback_interface_name: "" # Unsupported on cSRX +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 + env: + # Number of interfaces to create + # cSRX default is 3 (management + 2 data interfaces) + # If more interfaces are required, this must be increased. + # Setting to a higher value than required will break interfaces (no arp etc) + CSRX_PORT_NUM: 3 + 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' +# ansible_connection: docker +# netlab_console_connection: docker + 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 From 1dd0538dc0dace0b734617d05401e3efcd51157f Mon Sep 17 00:00:00 2001 From: leec-666 Date: Wed, 29 Apr 2026 08:35:54 +0000 Subject: [PATCH 2/8] remove reference to Junos quirks --- netsim/devices/csrx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netsim/devices/csrx.py b/netsim/devices/csrx.py index 7d917a9253..1e015be679 100644 --- a/netsim/devices/csrx.py +++ b/netsim/devices/csrx.py @@ -4,10 +4,10 @@ from box import Box from ..utils import log -from . import _Quirks, report_quirk +from . import _Quirks class CSRX(_Quirks): @classmethod def device_quirks(self, node: Box, topology: Box) -> None: - from . import junos + return From 83e54f365c11f5f18c3abf5a7e1f4d2c9f26acff Mon Sep 17 00:00:00 2001 From: leec-666 Date: Wed, 29 Apr 2026 11:19:13 +0000 Subject: [PATCH 3/8] CSRX_PORT_NUM csrx quirk --- netsim/devices/csrx.py | 14 ++++++++++++-- netsim/devices/csrx.yml | 6 ------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/netsim/devices/csrx.py b/netsim/devices/csrx.py index 1e015be679..c5a13d003c 100644 --- a/netsim/devices/csrx.py +++ b/netsim/devices/csrx.py @@ -4,10 +4,20 @@ from box import Box from ..utils import log -from . import _Quirks +from . import _Quirks, report_quirk + +def csrx_port_num(node: Box) -> None: + if_count = len(node.get('interfaces', [])) + node.clab.env.CSRX_PORT_NUM = if_count + 1 + if if_count > 16: + report_quirk( + f'cSRX supports a maximum of 16 interfaces. Node {node.name} has {if_count} interfaces.', + node=node, + category=log.ErrorAbort, + quirk='csrx_port_num') class CSRX(_Quirks): @classmethod def device_quirks(self, node: Box, topology: Box) -> None: - return + csrx_port_num(node) diff --git a/netsim/devices/csrx.yml b/netsim/devices/csrx.yml index 475cd918aa..db68c8c45c 100644 --- a/netsim/devices/csrx.yml +++ b/netsim/devices/csrx.yml @@ -28,12 +28,6 @@ clab: build: https://containerlab.dev/manual/kinds/csrx/ node: kind: juniper_csrx - env: - # Number of interfaces to create - # cSRX default is 3 (management + 2 data interfaces) - # If more interfaces are required, this must be increased. - # Setting to a higher value than required will break interfaces (no arp etc) - CSRX_PORT_NUM: 3 config_templates: hosts: /etc/hosts:shared netlab-config: /config/netlab/netlab-config.sh:sh From c9bf06128a1ff50b2a23b7736e540dc93f8890eb Mon Sep 17 00:00:00 2001 From: leec-666 Date: Wed, 29 Apr 2026 11:27:24 +0000 Subject: [PATCH 4/8] docs --- docs/caveats.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/caveats.md b/docs/caveats.md index 71b1d5f83b..ab8d4acad4 100644 --- a/docs/caveats.md +++ b/docs/caveats.md @@ -445,7 +445,7 @@ Implementation limitations in import/export route filters (reported as errors th ## Juniper cSRX * 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.crpd.clab.node.license** [device default](topo-defaults). -* If using more than 2 data interfaces, `clab.env.CSRX_PORT_NUM` must be increased to the number of interfaces + 1 (because it includes the management interface). Setting it higher will result in none of the data interfaces functioning correctly, e.g. no ARP. +* cSRX container 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, the use of the routing module and static routes is recommended. (caveats-crpd)= From 3a9fec4d5319212dcea3449321bae2dbee606be5 Mon Sep 17 00:00:00 2001 From: leec-666 Date: Wed, 29 Apr 2026 11:27:45 +0000 Subject: [PATCH 5/8] docs typo --- docs/caveats.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/caveats.md b/docs/caveats.md index ab8d4acad4..5705c374a9 100644 --- a/docs/caveats.md +++ b/docs/caveats.md @@ -445,7 +445,7 @@ Implementation limitations in import/export route filters (reported as errors th ## Juniper cSRX * 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.crpd.clab.node.license** [device default](topo-defaults). -* cSRX container 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). +* 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, the use of the routing module and static routes is recommended. (caveats-crpd)= From 97e15f17891557f17d2376aa4e92667aa36fac04 Mon Sep 17 00:00:00 2001 From: leec-666 Date: Thu, 30 Apr 2026 00:49:46 +0000 Subject: [PATCH 6/8] appease ruff --- netsim/devices/csrx.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netsim/devices/csrx.py b/netsim/devices/csrx.py index c5a13d003c..170e7d927e 100644 --- a/netsim/devices/csrx.py +++ b/netsim/devices/csrx.py @@ -6,6 +6,7 @@ from ..utils import log from . import _Quirks, report_quirk + def csrx_port_num(node: Box) -> None: if_count = len(node.get('interfaces', [])) node.clab.env.CSRX_PORT_NUM = if_count + 1 From e7fbaace7bf13c5da72fd6e4d3d86fd192aa2a47 Mon Sep 17 00:00:00 2001 From: leec-666 Date: Fri, 1 May 2026 03:56:56 +0000 Subject: [PATCH 7/8] update --- docs/caveats.md | 15 +++++++------- docs/platforms.md | 8 ++------ netsim/ansible/templates/routing/csrx.j2 | 25 +----------------------- netsim/devices/csrx.py | 13 ++++++------ netsim/devices/csrx.yml | 3 --- 5 files changed, 17 insertions(+), 47 deletions(-) diff --git a/docs/caveats.md b/docs/caveats.md index 5705c374a9..01841e111b 100644 --- a/docs/caveats.md +++ b/docs/caveats.md @@ -441,13 +441,6 @@ Implementation limitations in import/export route filters (reported as errors th * When prepending an AS number different than the local one, JunOS puts the prepended AS at the beginning of the *AS_PATH* while other vendors perform the prepending and then add the device's own AS at the beginning. Most EBGP peers deny a BGP update that does not have the neighbor's AS number at the beginning of the *AS_PATH*. If needed, _netlab_ automatically adds the *local-as* to the prepend list to get it at the beginning of the *AS_PATH*. * *as-path* regex syntax for JunOS has different rules than other vendors. For example, a *null as-path* is represented as `()`. netlab tries some as-path conversion, but sometimes it could be wrong. -(caveats-csrx)= -## Juniper cSRX - -* 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.crpd.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, the use of the routing module and static routes is recommended. - (caveats-crpd)= ## Juniper cRPD @@ -456,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 89c91a23dd..74e60c23b2 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -220,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 @@ -289,7 +289,7 @@ The following system-wide features are configured on supported network operating | Fortinet FortiOS | ✅ | ❌ | ✅ | ✅ | ✅ | | FRR | ✅ | ✅[^HIF] | ❌ | ✅ | ✅ | | Generic Linux | ✅ | ✅[^HIF] | ✅[❗](linux-lldp) | ✅ | ✅ | -| Juniper cSRX | ✅ | ❌ | ❌ | ❌ | ❌ | +| Juniper cSRX | ✅ | ✅ | ❌ | ❌ | ❌ | | Junos[^Junos] | ✅ | ❌ | ✅ | ✅ | ✅ | | Mikrotik RouterOS 6 | ✅ | ✅ | ✅[❗](caveats-routeros6) | ✅ | ✅ | | Mikrotik RouterOS 7 | ✅ | ✅ | ✅[❗](caveats-routeros7) | ✅ | ✅ | @@ -397,7 +397,6 @@ Routing protocol [configuration modules](module-reference.md) are supported on t | Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ | | Fortinet FortiOS | ✅ [❗](caveats-fortios) | ❌ | ❌ | ❌ | ❌ | | FRR | ✅ | ✅ | ❌ | ✅ | ✅ | -| Juniper cSRX | ❌ | ❌ | ❌ | ❌ | ❌ | | Junos[^Junos] | ✅ | ✅ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 6 | ✅ | ❌ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 7 | ✅ | ❌ | ❌ | ✅ | ❌ | @@ -427,7 +426,6 @@ These devices support additional control-plane protocols or BGP address families | Dell OS10 | ✅ | ✅ | ❌ | ❌ | | Extreme Networks EXOS | ❌ | ❌ | ❌ | ✅ | | FRR | ✅ | ✅ | ✅ | ❌ | -| Juniper cSRX | ❌ | ❌ | ❌ | ❌ | | Juniper cRPD | ✅ | ❌ | ✅ | ❌ | | Juniper vMX | ✅ | ❌ | ✅ | ✅ | | Juniper vPTX | ✅ | ✅ | ✅ | ✅ | @@ -477,7 +475,6 @@ The data plane [configuration modules](module-reference.md) are supported on the | Dell OS10 | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | FRR | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Juniper cSRX | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | Juniper cRPD | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | | Juniper vMX | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | | Juniper vPTX | ✅ | ✅ | ✅ [❗](caveats-vptx) | ✅ | ✅ | ❌ | @@ -527,7 +524,6 @@ Core *netlab* functionality and all multi-protocol routing protocol configuratio | Dell OS10 | ✅ | ❌ | ❌ | ✅ | ❌ | | Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ | | FRR | ✅ | ✅ | ❌ | ✅ | ❌ | -| Juniper cSRX | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | Junos[^Junos] | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | Mikrotik RouterOS 6 | ❌ | ❌ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 7 | ✅ | ❌ | ❌ | ✅ | ❌ | diff --git a/netsim/ansible/templates/routing/csrx.j2 b/netsim/ansible/templates/routing/csrx.j2 index 21defb5995..3554877f21 100644 --- a/netsim/ansible/templates/routing/csrx.j2 +++ b/netsim/ansible/templates/routing/csrx.j2 @@ -1,26 +1,3 @@ {% if routing.static is defined %} -{% for sr_data in routing.static %} -{# recursive lookup is triggered by a route without the intf parameter #} -{% set route_resolve = ' resolve' if not 'intf' in sr_data.nexthop else '' %} - routing-options { -{% if 'ipv4' in sr_data %} - static route {{sr_data.ipv4}} { -{% if 'discard' in sr_data.nexthop %} - discard; -{% else %} - next-hop {{sr_data.nexthop.ipv4}}{{route_resolve}}; -{% endif %} - } -{% endif %} -{% if 'ipv6' in sr_data %} - rib inet6.0 static route {{sr_data.ipv6}} { -{% if 'discard' in sr_data.nexthop %} - discard; -{% else %} - next-hop {{sr_data.nexthop.ipv6}}{{route_resolve}}; -{% endif %} - } -{% endif %} - } -{% endfor %} +{% include 'junos/static.j2' %} {% endif %} diff --git a/netsim/devices/csrx.py b/netsim/devices/csrx.py index 170e7d927e..97d2c31e89 100644 --- a/netsim/devices/csrx.py +++ b/netsim/devices/csrx.py @@ -4,18 +4,17 @@ from box import Box from ..utils import log -from . import _Quirks, report_quirk +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 + node.clab.env.CSRX_PORT_NUM = if_count + 1 # +1 for the management interface if if_count > 16: - report_quirk( - f'cSRX supports a maximum of 16 interfaces. Node {node.name} has {if_count} interfaces.', - node=node, - category=log.ErrorAbort, - quirk='csrx_port_num') + 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): diff --git a/netsim/devices/csrx.yml b/netsim/devices/csrx.yml index db68c8c45c..deb9cfdc6e 100644 --- a/netsim/devices/csrx.yml +++ b/netsim/devices/csrx.yml @@ -9,7 +9,6 @@ group_vars: mgmt_if: fxp0 ifindex_offset: 0 interface_name: ge-0/0/{ifindex} -loopback_interface_name: "" # Unsupported on cSRX mtu: 1500 features: @@ -42,8 +41,6 @@ clab: netlab_check_command: who netlab_ready: [ ssh ] netlab_default_shebang: '#!/config/netlab/netlab-config.sh' -# ansible_connection: docker -# netlab_console_connection: docker netlab_config_path: /config/netlab/ graphite.icon: firewall From f643b692f3020c4691a70007623a97da06e99e73 Mon Sep 17 00:00:00 2001 From: leec-666 Date: Fri, 1 May 2026 06:07:01 +0000 Subject: [PATCH 8/8] common junos/crpd/csrx j2 bits --- netsim/ansible/templates/initial/crpd.j2 | 60 +------------------ netsim/ansible/templates/initial/csrx.j2 | 54 +---------------- netsim/ansible/templates/initial/junos.j2 | 35 ++--------- .../initial/junos/container_interfaces.j2 | 40 +++++++++++++ .../ansible/templates/initial/junos/hosts.j2 | 12 ++++ .../ansible/templates/initial/junos/lldp.j2 | 17 ++++++ 6 files changed, 78 insertions(+), 140 deletions(-) create mode 100644 netsim/ansible/templates/initial/junos/container_interfaces.j2 create mode 100644 netsim/ansible/templates/initial/junos/hosts.j2 create mode 100644 netsim/ansible/templates/initial/junos/lldp.j2 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 index 4568f1ce9d..616e21b8c1 100644 --- a/netsim/ansible/templates/initial/csrx.j2 +++ b/netsim/ansible/templates/initial/csrx.j2 @@ -1,56 +1,6 @@ -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' %} -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 %} -} +{% include 'junos/container_interfaces.j2' %} security { zones { 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 %} +}