From 5095ba1da22fb586b36ec36c0d38e5c1ad226def Mon Sep 17 00:00:00 2001 From: Stefano Sasso Date: Mon, 3 Mar 2025 14:13:24 +0100 Subject: [PATCH 1/6] vJunos-switch: Static VXLAN all tests passed except: - 07-vxlan-bridging-v6only.yml - 08-vxlan-alt-vtep.yml which seems not supported. --- .../ansible/templates/vxlan/vjunos-switch.j2 | 51 +++++++++++++++++++ netsim/devices/vjunos-switch.yml | 1 + 2 files changed, 52 insertions(+) create mode 100644 netsim/ansible/templates/vxlan/vjunos-switch.j2 diff --git a/netsim/ansible/templates/vxlan/vjunos-switch.j2 b/netsim/ansible/templates/vxlan/vjunos-switch.j2 new file mode 100644 index 0000000000..0f581bff1c --- /dev/null +++ b/netsim/ansible/templates/vxlan/vjunos-switch.j2 @@ -0,0 +1,51 @@ +{# + vJunos Switch VXLAN: different configuration required if VXLAN static flooding or EVPN-based +#} + +{# first of all, in any case, better to explicitly ECMP load balance per-flow #} +policy-options { + policy-statement ecmp { + then { + load-balance per-flow; + } + } +} +routing-options { + forwarding-table { + export ecmp; + } +} + +{% if vxlan.flooding|default('') == 'static' %} + +{# define basic VXLAN config in switch-options stanza #} +switch-options { + vtep-source-interface {{ vxlan.vtep_interface }}; +{# need to list all possible static vtep here - fine tuning will be done per-vlan #} +{% for remote_vtep in vxlan.vtep_list %} + remote-vtep-list {{ remote_vtep }}; +{% endfor %} +} + +{# define VLAN to VXLAN mapping and remote vteps #} +{% if vxlan.vlans is defined %} +vlans { +{% for vname in vxlan.vlans if vlans[vname].vni is defined %} +{% set vlan = vlans[vname] %} + {{ vname }} { + vxlan { + vni {{ vlan.vni }}; + ingress-node-replication; +{% if vlan.vtep_list is defined %} +{% for vtep in vlan.vtep_list %} + static-remote-vtep-list {{ vtep }}; +{% endfor %} +{% endif %} + } + } +{% endfor %} +} +{% endif %} + + +{% endif %} diff --git a/netsim/devices/vjunos-switch.yml b/netsim/devices/vjunos-switch.yml index 75bf48d223..cdb2b46ffb 100644 --- a/netsim/devices/vjunos-switch.yml +++ b/netsim/devices/vjunos-switch.yml @@ -11,6 +11,7 @@ features: svi_interface_name: irb.{vlan} subif_name: "{ifname}.{vlan.access_id}" native_routed: true + vxlan: true clab: image: vrnetlab/juniper_vjunos-switch:23.4R2-S2.1 From 4c57d06685a85ebc2174ee1e4f4e7729e92b1a13 Mon Sep 17 00:00:00 2001 From: Stefano Sasso Date: Mon, 3 Mar 2025 17:36:02 +0100 Subject: [PATCH 2/6] vJunos-switch: EVPN - simplified model with simplified model using `switch-options` config (with some drawbacks) Passes integration tests: - 0* (other tests in pending state) --- .../ansible/templates/evpn/vjunos-switch.j2 | 84 +++++++++++++++++++ .../ansible/templates/vxlan/vjunos-switch.j2 | 29 +++++++ netsim/devices/vjunos-switch.yml | 3 + 3 files changed, 116 insertions(+) create mode 100644 netsim/ansible/templates/evpn/vjunos-switch.j2 diff --git a/netsim/ansible/templates/evpn/vjunos-switch.j2 b/netsim/ansible/templates/evpn/vjunos-switch.j2 new file mode 100644 index 0000000000..5702758b10 --- /dev/null +++ b/netsim/ansible/templates/evpn/vjunos-switch.j2 @@ -0,0 +1,84 @@ +{# EVPN config using mac-vrf & co #} + +{# Enable BGP EVPN for specific neighbors #} +protocols { + bgp { +{% for af in ['ipv4','ipv6'] if bgp[af] is defined and loopback[af] is defined %} + group ibgp-peers-{{ af }} { +{% for n in bgp.neighbors if n[af] is defined and n.type == 'ibgp' and n.evpn|default(false) %} + neighbor {{ n[af] }} { + family evpn { + signaling; + } + } +{% endfor %} + } +{% endfor %} + group ebgp-peers { +{% for n in bgp.neighbors if n.type == 'ebgp' and n.evpn|default(false) %} +{% for af in ['ipv4','ipv6'] if n[af] is defined %} + neighbor {{ n[af] }} { + family evpn { + signaling; + } + } +{% endfor %} +{% endfor %} + } + } +} + +{# VERY DIRTY HACK here for initial testing - since we can specify only one vrf-target per vni-option, let's use the first import one #} +protocols { + evpn { + encapsulation vxlan; + default-gateway no-gateway-community; + extended-vni-list all; +{% if vxlan.vlans is defined %} + vni-options { +{% for vname in vxlan.vlans if vlans[vname].vni is defined %} +{% set vlan = vlans[vname] %} + vni {{ vlan.vni }} { + vrf-target target:{{ vlan.evpn.import[0] }}; + } +{% endfor %} + } +{% endif %} + } +} + + +{# define L3VNI on vrf routing-instance #} +{% if vrfs is defined %} +routing-instances { + +{% for n,v in vrfs.items() if v.af is defined and v.evpn is defined %} + {{ n }} { + protocols { + evpn { + irb-symmetric-routing { + vni {{ v.evpn.transit_vni }}; + } + ip-prefix-routes { + advertise direct-nexthop; + encapsulation vxlan; + vni {{ v.evpn.transit_vni }}; + } + } + } + route-distinguisher {{ v.evpn.rd }}; + } +{% endfor %} + +} + +{% for vname in vxlan.vlans|default([]) if vlans[vname].vni is defined and vlans[vname].mode|default('') == 'irb' and vlans[vname].vrf is defined %} +{% set vlan = vlans[vname] %} +interfaces { + irb.{{ vlan.id }} { + proxy-macip-advertisement; + } +} +{% endfor %} + +{% endif %} diff --git a/netsim/ansible/templates/vxlan/vjunos-switch.j2 b/netsim/ansible/templates/vxlan/vjunos-switch.j2 index 0f581bff1c..56e47b7d66 100644 --- a/netsim/ansible/templates/vxlan/vjunos-switch.j2 +++ b/netsim/ansible/templates/vxlan/vjunos-switch.j2 @@ -49,3 +49,32 @@ vlans { {% endif %} + + +{% if vxlan.flooding|default('') == 'evpn' %} + +{# use switch-options with "fake" RD and RT - the RT will be overwritten per-vni on the protocols->evpn #} +switch-options { + vtep-source-interface {{ vxlan.vtep_interface }}; + route-distinguisher {{ vxlan.vtep }}:65535; + vrf-target target:{{ bgp.as|default(65535) }}:65535; +} + +{# need to define this to avoid validation errors #} +protocols evpn encapsulation vxlan; + +{# define VLAN to VXLAN mapping #} +{% if vxlan.vlans is defined %} +vlans { +{% for vname in vxlan.vlans if vlans[vname].vni is defined %} +{% set vlan = vlans[vname] %} + {{ vname }} { + vxlan { + vni {{ vlan.vni }}; + } + } +{% endfor %} +} +{% endif %} + +{% endif %} diff --git a/netsim/devices/vjunos-switch.yml b/netsim/devices/vjunos-switch.yml index cdb2b46ffb..272fe8d5cc 100644 --- a/netsim/devices/vjunos-switch.yml +++ b/netsim/devices/vjunos-switch.yml @@ -6,6 +6,9 @@ group_vars: netlab_device_type: vjunos-switch features: + evpn: + asymmetrical_irb: true + irb: true vlan: model: l3-switch svi_interface_name: irb.{vlan} From 37066d4cf9b44f9be484c896998c1549bc6bc48e Mon Sep 17 00:00:00 2001 From: Stefano Sasso Date: Mon, 3 Mar 2025 18:58:15 +0100 Subject: [PATCH 3/6] vJunos-switch: fix vlan bug discovered during evpn testing --- netsim/ansible/templates/vlan/vjunos-switch.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netsim/ansible/templates/vlan/vjunos-switch.j2 b/netsim/ansible/templates/vlan/vjunos-switch.j2 index 1ecd6b9b80..f15be411b2 100644 --- a/netsim/ansible/templates/vlan/vjunos-switch.j2 +++ b/netsim/ansible/templates/vlan/vjunos-switch.j2 @@ -1,7 +1,7 @@ {# for any vlan != route, define it #} vlans { -{% for vlan,vdata in vlans.items() if vdata.mode != 'route' %} +{% for vlan,vdata in (vlans|default({})).items() if vdata.mode != 'route' %} {{ vlan }} { vlan-id {{ vdata.id }}; From 1b34de02d5028f17f82f0ede54dd6487580e26be Mon Sep 17 00:00:00 2001 From: Stefano Sasso Date: Tue, 4 Mar 2025 10:12:54 +0100 Subject: [PATCH 4/6] vJunos-Switch: fixes for EVPN integration --- netsim/ansible/templates/evpn/vjunos-switch.j2 | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/netsim/ansible/templates/evpn/vjunos-switch.j2 b/netsim/ansible/templates/evpn/vjunos-switch.j2 index 5702758b10..bb0db0af5e 100644 --- a/netsim/ansible/templates/evpn/vjunos-switch.j2 +++ b/netsim/ansible/templates/evpn/vjunos-switch.j2 @@ -18,6 +18,7 @@ protocols { {% for n in bgp.neighbors if n.type == 'ebgp' and n.evpn|default(false) %} {% for af in ['ipv4','ipv6'] if n[af] is defined %} neighbor {{ n[af] }} { + accept-remote-nexthop; family evpn { signaling; } @@ -29,6 +30,8 @@ protocols { } {# VERY DIRTY HACK here for initial testing - since we can specify only one vrf-target per vni-option, let's use the first import one #} +{# - do it only if vxlan is defined - no need if we are RR only #} +{% if vxlan is defined %} protocols { evpn { encapsulation vxlan; @@ -46,6 +49,7 @@ protocols { {% endif %} } } +{% endif %} {# define L3VNI on vrf routing-instance #} @@ -72,13 +76,16 @@ routing-instances { } +{# according to JunOS doc, in case NO ANYCAST GATEWAY is configured, enable 'proxy-macip-advertisement' on central IRB interfaces #} +interfaces { {% for vname in vxlan.vlans|default([]) if vlans[vname].vni is defined and vlans[vname].mode|default('') == 'irb' and vlans[vname].vrf is defined %} {% set vlan = vlans[vname] %} -interfaces { +{% if not vlan.gateway|default(false) %} irb.{{ vlan.id }} { proxy-macip-advertisement; } -} +{% endif %} {% endfor %} +} {% endif %} From 7971bfb66841e1d2b2cc98cee037706ed023e84f Mon Sep 17 00:00:00 2001 From: Stefano Sasso Date: Tue, 4 Mar 2025 12:36:03 +0100 Subject: [PATCH 5/6] vJunos-switch: EVPN fixes and redistribution --- docs/caveats.md | 4 +++ docs/module/evpn.md | 4 +++ docs/module/vxlan.md | 1 + .../ansible/templates/evpn/vjunos-switch.j2 | 29 +++++++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/docs/caveats.md b/docs/caveats.md index caa3c93d81..52ed70eff0 100644 --- a/docs/caveats.md +++ b/docs/caveats.md @@ -336,6 +336,10 @@ See also [](caveats-junos). * You can run Juniper vJunos-switch as a container packaged by Roman Dodin's fork of [vrnetlab](https://github.com/hellt/vrnetlab/). See [_containerlab_ documentation](https://containerlab.dev/manual/kinds/vr-vjunosswitch/) for further details. * Use a recent *vrnetlab* release that places the management interface into the **mgmt_junos** routing instance to avoid the conflict between [management IP subnet](clab-vrnetlab) `10.0.0.0/24` and **netlab** loopback addressing. +* vJunos-switch VLAN configuration uses the so-called *Enterprise Style VLAN configuration* (which uses `family ethernet-switching` on unit 0 of interfaces). +* For the above reason, the current netlab implementation of vJunos-switch EVPN configuration uses the `switch-options` (*default-switch*) configuration stanza, which leads to some drawbacks/limitations: + * All EVPN routes are announced with the same RD (configured under `switch-options`). A RT is configured as well under `switch-options`, but then it is overwritten per-VNI under the `protocol evpn` configuration. + * It is not possible to use multiple import/export RT. The *first* import RT is used on the configuration templates as the VNI RT. See also [](caveats-junos). diff --git a/docs/module/evpn.md b/docs/module/evpn.md index afe0a20d2c..9e6575cd59 100644 --- a/docs/module/evpn.md +++ b/docs/module/evpn.md @@ -32,6 +32,7 @@ The following table describes per-platform support of individual EVPN/VXLAN feat | FRR | ✅ | ❌ | ✅ | ✅ | | Nokia SR Linux | ✅ | ✅ | ✅ | ✅ | | Nokia SR OS | ✅ | ❌ | ✅ | ✅ | +| vJunos-switch [❗](caveats-vjunos-switch) | ✅ | ❌ | ✅ | ✅ | | VyOS | ✅ | ❌ | ✅ | ✅ | The following table describes per-platform support of individual EVPN/MPLS features: @@ -64,6 +65,7 @@ EVPN module supports IBGP- and EBGP-based EVPN: | FRR | ✅ | ✅ | ✅ | ✅ | | Nokia SR Linux | ✅ | ✅ | ❌ | ❌ | | Nokia SR OS | ✅ | ✅ | ✅ | ✅ | +| vJunos-switch | ✅ | ✅ | ❌ | ❌ | | VyOS | ✅ | ✅ | ❌ | ❌ | With additional nerd knobs ([more details](evpn-weird-designs)), it's possible to implement the more convoluted designs, including: @@ -82,6 +84,7 @@ With additional nerd knobs ([more details](evpn-weird-designs)), it's possible t | FRR | ✅ | ✅ | | Nokia SR Linux | ✅ | ❌ | | Nokia SR OS | ✅ | ❌ | +| vJunos-switch | ✅ | ❌ | | VyOS | ✅ | ❌ | Most EVPN/VXLAN implementations support only IPv4 VXLAN transport; some can run VXLAN-over-IPv6: @@ -97,6 +100,7 @@ Most EVPN/VXLAN implementations support only IPv4 VXLAN transport; some can run | FRR | ✅ | ✅ | | Nokia SR Linux | ✅ | ❌ | | Nokia SR OS | ✅ | ❌ | +| vJunos-switch | ✅ | ❌ | | VyOS | ✅ | ❌ [❗](caveats-vyos) | (evpn-global-parameters)= diff --git a/docs/module/vxlan.md b/docs/module/vxlan.md index 7d3fffd31a..0ee5a296f4 100644 --- a/docs/module/vxlan.md +++ b/docs/module/vxlan.md @@ -35,6 +35,7 @@ The following table describes per-platform support of individual VXLAN features: | FRR | ✅ | ✅ | ✅ | | Nokia SR Linux | ✅ [❗](caveats-srlinux) | ❌ | ❌ | | Nokia SR OS | ✅ | ❌ | ❌ | +| vJunos-switch | ✅ | ✅ | ❌ | | VyOS | ✅ | ✅ | ✅ | ```{tip} diff --git a/netsim/ansible/templates/evpn/vjunos-switch.j2 b/netsim/ansible/templates/evpn/vjunos-switch.j2 index bb0db0af5e..d076a1fd80 100644 --- a/netsim/ansible/templates/evpn/vjunos-switch.j2 +++ b/netsim/ansible/templates/evpn/vjunos-switch.j2 @@ -67,6 +67,7 @@ routing-instances { advertise direct-nexthop; encapsulation vxlan; vni {{ v.evpn.transit_vni }}; + export vrf-{{n}}-ebgp-export; } } } @@ -76,6 +77,34 @@ routing-instances { } +{# update ospf, ibgp and ebgp export policy options for VRFs #} +policy-options { +{% for n,v in vrfs.items() if v.af is defined and v.evpn is defined %} + policy-statement vrf-{{n}}-ospf-export { + term redis_evpn { + from protocol evpn; + then accept; + } + } + policy-statement vrf-{{n}}-ibgp-export { + term redis_evpn { + from protocol evpn; + then accept; + } + } + policy-statement vrf-{{n}}-ebgp-export { + term redis_evpn { + from protocol evpn; + then accept; + } + term redis_bgp { + from protocol bgp; + then accept; + } + } +{% endfor %} +} + {# according to JunOS doc, in case NO ANYCAST GATEWAY is configured, enable 'proxy-macip-advertisement' on central IRB interfaces #} interfaces { {% for vname in vxlan.vlans|default([]) if vlans[vname].vni is defined and vlans[vname].mode|default('') == 'irb' and vlans[vname].vrf is defined %} From 14679b762383e045284588eb9ae9557789a1a4d5 Mon Sep 17 00:00:00 2001 From: Stefano Sasso Date: Tue, 4 Mar 2025 13:13:37 +0100 Subject: [PATCH 6/6] vJunos-switch: vxlan template refactor --- .../ansible/templates/vxlan/vjunos-switch.j2 | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/netsim/ansible/templates/vxlan/vjunos-switch.j2 b/netsim/ansible/templates/vxlan/vjunos-switch.j2 index 56e47b7d66..51a38cb841 100644 --- a/netsim/ansible/templates/vxlan/vjunos-switch.j2 +++ b/netsim/ansible/templates/vxlan/vjunos-switch.j2 @@ -16,25 +16,43 @@ routing-options { } } -{% if vxlan.flooding|default('') == 'static' %} - {# define basic VXLAN config in switch-options stanza #} switch-options { vtep-source-interface {{ vxlan.vtep_interface }}; +} + +{# define basic VLAN to VXLAN mapping #} +{% if vxlan.vlans is defined %} +vlans { +{% for vname in vxlan.vlans if vlans[vname].vni is defined %} +{% set vlan = vlans[vname] %} + {{ vname }} { + vxlan { + vni {{ vlan.vni }}; + } + } +{% endfor %} +} +{% endif %} + +{# different options for vxlan flooding types (static vs evpn) #} + +{% if vxlan.flooding|default('') == 'static' %} + +switch-options { {# need to list all possible static vtep here - fine tuning will be done per-vlan #} {% for remote_vtep in vxlan.vtep_list %} remote-vtep-list {{ remote_vtep }}; {% endfor %} } -{# define VLAN to VXLAN mapping and remote vteps #} +{# define static remote vteps for VXLANs #} {% if vxlan.vlans is defined %} vlans { {% for vname in vxlan.vlans if vlans[vname].vni is defined %} {% set vlan = vlans[vname] %} {{ vname }} { vxlan { - vni {{ vlan.vni }}; ingress-node-replication; {% if vlan.vtep_list is defined %} {% for vtep in vlan.vtep_list %} @@ -47,15 +65,12 @@ vlans { } {% endif %} - {% endif %} - {% if vxlan.flooding|default('') == 'evpn' %} {# use switch-options with "fake" RD and RT - the RT will be overwritten per-vni on the protocols->evpn #} switch-options { - vtep-source-interface {{ vxlan.vtep_interface }}; route-distinguisher {{ vxlan.vtep }}:65535; vrf-target target:{{ bgp.as|default(65535) }}:65535; } @@ -63,18 +78,4 @@ switch-options { {# need to define this to avoid validation errors #} protocols evpn encapsulation vxlan; -{# define VLAN to VXLAN mapping #} -{% if vxlan.vlans is defined %} -vlans { -{% for vname in vxlan.vlans if vlans[vname].vni is defined %} -{% set vlan = vlans[vname] %} - {{ vname }} { - vxlan { - vni {{ vlan.vni }}; - } - } -{% endfor %} -} -{% endif %} - {% endif %}