Skip to content
Merged
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
8 changes: 8 additions & 0 deletions docs/caveats.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 9 additions & 3 deletions docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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) |
Expand Down Expand Up @@ -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 | ✅ | ✅ |
Expand All @@ -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

Expand Down Expand Up @@ -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) | ✅ | ✅ |
Expand Down Expand Up @@ -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 | ✅ | ❌ | ✅ | ✅ |
Expand Down Expand Up @@ -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 | ✅ | ✅ | ❌ | ❌ |
Expand Down Expand Up @@ -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 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
Expand Down
60 changes: 3 additions & 57 deletions netsim/ansible/templates/initial/crpd.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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' %}
15 changes: 15 additions & 0 deletions netsim/ansible/templates/initial/csrx.j2
Original file line number Diff line number Diff line change
@@ -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 %}
}
}
}
}
35 changes: 4 additions & 31 deletions netsim/ansible/templates/initial/junos.j2
Original file line number Diff line number Diff line change
@@ -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' %}
Expand Down Expand Up @@ -38,7 +27,7 @@ interfaces {
{% elif l.type|default("") == "stub" %}
description "Stub interface"
{% endif %}

{% if l.bandwidth is defined %}
bandwidth {{ l.bandwidth * 1000 }};
{% endif %}
Expand Down Expand Up @@ -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' %}
40 changes: 40 additions & 0 deletions netsim/ansible/templates/initial/junos/container_interfaces.j2
Original file line number Diff line number Diff line change
@@ -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 %}
}
12 changes: 12 additions & 0 deletions netsim/ansible/templates/initial/junos/hosts.j2
Original file line number Diff line number Diff line change
@@ -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 %}
}
}
17 changes: 17 additions & 0 deletions netsim/ansible/templates/initial/junos/lldp.j2
Original file line number Diff line number Diff line change
@@ -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 %}
}
3 changes: 3 additions & 0 deletions netsim/ansible/templates/routing/csrx.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% if routing.static is defined %}
Comment thread
leec-666 marked this conversation as resolved.
{% include 'junos/static.j2' %}
{% endif %}
23 changes: 23 additions & 0 deletions netsim/devices/csrx.py
Original file line number Diff line number Diff line change
@@ -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)
46 changes: 46 additions & 0 deletions netsim/devices/csrx.yml
Original file line number Diff line number Diff line change
@@ -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'
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.

Do we want to use this or should we just use NETCONF to configure it? Or is NETCONF not working?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I based it on cRPD. I thought the connection/deployment methods other than shell scripts relied on Ansible which I know you're not the biggest fan of.

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.

I thought the connection/deployment methods other than shell scripts relied on Ansible

  • Configuration deployment: YES
  • Connecting to the device: NO

which I know you're not the biggest fan of.

You're right ;) However, we need Ansible anyway to deploy configurations on other Junos devices, but admittedly the script-based configuration is faster. I'm OK with either one, just don't make it too complicated just to have a bash script.

netlab_config_path: /config/netlab/

graphite.icon: firewall
1 change: 1 addition & 0 deletions netsim/templates/provider/clab/csrx/hosts.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% include 'linux/hosts.j2' %}
19 changes: 19 additions & 0 deletions netsim/templates/provider/clab/csrx/netlab-config.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
tail -n +2 $1 > /tmp/config.conf # stop shebang being included in pushed config
cat <<CONFIG | cli | tee /tmp/cli-status
configure
load merge /tmp/config.conf
CONFIG
if grep -i error /tmp/cli-status >/dev/null; then
echo "Configuration load failed, aborting"
exit 1
else
cat <<CONFIG | cli | tee /tmp/cli-status
configure
commit and-quit
CONFIG
if grep -i error /tmp/cli-status | grep -vi "Device or resource busy" >/dev/null; then
echo "Commit failed, aborting"
exit 1
fi
fi
Loading