Skip to content

Commit

Permalink
srlinux refactor templates and add vxlan l3 vrf support (#406)
Browse files Browse the repository at this point in the history
* Update doc to show current interface naming

* Check transit_vni value range
* Add vni values from the topology to the list, such that duplicate check works
* Use vrf.vrfidx for transit evi value

* Move VLAN handling to vlan module, use macro for ip addressing

The data model makes it hard to associate an svi interface with the corresponding original vlan; this patch uses a vlan<nnnn> interface naming convention for the lookup

* Support L3 VXLAN interfaces; add them to their VRF

* Refactor vxlan interface configuration, for both l2 and l3 VNIs

* Add srlinux vxlan vrf support (l3) and vrf loopbacks

* Update docs

Co-authored-by: Jeroen van Bemmel <[email protected]>
  • Loading branch information
jbemmel and jbemmel authored Sep 8, 2022
1 parent c831408 commit 57b215a
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 77 deletions.
2 changes: 1 addition & 1 deletion docs/dev/config/vlan.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ devices:
features:
vlan:
model: router
svi_interface_name: "{ifname}.{vlan}"
svi_interface_name: "vlan{vlan}"
subif_name: "{ifname}.{subif_index}"
vyos:
features:
Expand Down
6 changes: 3 additions & 3 deletions docs/module/evpn.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The following table describes per-platform support of individual VXLAN features:
| Operating system | VXLAN<br>transport | VLAN-based<br>service | VLAN Bundle<br>service | Asymmetric<br>IRB | Symmetric<br>IRB |
| ------------------ | :-: | :-: | :-: | :-: | :-: |
| Arista EOS ||||||
| Nokia SR Linux ||||| |
| Nokia SR Linux ||||| |
| Nokia SR OS ||||||
| FRR ||||||
| VyOS ||||||
Expand Down Expand Up @@ -56,7 +56,7 @@ EVPN module supports these default/global/node parameters:

* **evpn.session** (global or node parameter): A list of BGP session types on which the EVPN address family is enabled (default: `ibgp`)
* **evpn.vlan_bundle_service** (global or node parameter): Use VLAN bundle service for VLANs within a VRF (default: `False`)
* **evpn.start_transit_vni** (system default parameter) -- the first symmetric IRB transit VNI
* **evpn.start_transit_vni** (system default parameter) -- the first symmetric IRB transit VNI, range 4096..16777215

### VLAN-Based Service Parameters

Expand Down Expand Up @@ -85,6 +85,6 @@ The default value of VRF EVPN Instance identifier is the VLAN ID of the first VL
IRB is configured whenever EVPN-enabled VLANs in a VRF contain IPv4 or IPv6 addresses:

* Asymmetric IRB requires no extra parameters[^NS]
* Symmetric IRB needs a transit VNI that has to be set with the **evpn.transit_vni** parameter. That parameter could be set to an integer value or to *True* in which case the EVPN configuration module assigns a VNI to the VRF.
* Symmetric IRB needs a transit VNI that has to be set with the **evpn.transit_vni** parameter. This parameter could be set to an integer value or to *True* in which case the EVPN configuration module auto-assigns a VNI to the VRF. Note that the EVI value used in this case is currently based on the VRF ID (vrfidx)

[^NS]: Asymmetric IRB is not supported at the moment
67 changes: 27 additions & 40 deletions netsim/ansible/templates/initial/srlinux.j2
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
{% macro ip_addresses(intf,ipv6_ra,is_system) %}
{% macro ip_addresses(name,index,intf,ipv6_ra=True,is_system=False) %}
- path: interface[name={{name}}]/subinterface[index={{index}}]
val:
description: {{ intf.name | default( "No description" )|replace('->','~')|regex_replace('[\\[\\]]','') }}
{% if 'ipv4' in intf and intf.ipv4 is string %}
ipv4:
address:
- ip-prefix: "{{ intf.ipv4 }}"
ipv4:
address:
- ip-prefix: "{{ intf.ipv4 }}"
{% if not is_system %}
primary: [null]
primary: [null]
{% endif %}
{% endif %}
{% if 'ipv6' in intf %}
ipv6:
ipv6:
{% if intf.ipv6 is string %}
address:
- ip-prefix: "{{ intf.ipv6 }}"
address:
- ip-prefix: "{{ intf.ipv6 }}"
{% endif %}
{% if ipv6_ra %}
neighbor-discovery:
learn-unsolicited: link-local
router-advertisement:
router-role:
admin-state: enable # no ipv6 nd suppress-ra
# min-advertisement-interval: 5 # Leave at platform default 200..600
# max-advertisement-interval: 5
neighbor-discovery:
learn-unsolicited: link-local
router-advertisement:
router-role:
admin-state: enable # no ipv6 nd suppress-ra
# min-advertisement-interval: 5 # Leave at platform default 200..600
# max-advertisement-interval: 5
{% endif %}
{% endif %}
{% endmacro %}
Expand All @@ -39,44 +42,28 @@ updates:
_annotate_default-ip-mtu: "Custom system wide setting, overrides default 1500"
{% endif %}
{% endif %}
- path: interface[name=system0]/subinterface[index=0]
val:
{{ ip_addresses(loopback,False,True) }}

{% for l in interfaces|default([]) %}
{{ ip_addresses('system0',0,loopback,False,True) }}

{% for l in interfaces|default([]) if l.vlan is not defined or l.vlan.mode|default('irb')=='route' %}
{% set if_name_index = l.ifname.split('.') %}
{% set if_name = if_name_index[0] %}
{% set if_index = if_name_index[1] if if_name_index|length > 1 else l.vlan.access_id|default(0) if l.vlan is defined else '0' %}
{% set if_index = if_name_index[1] if if_name_index|length > 1 else '0' %}
{% set vlan = l.vlan.access|default(l.vlan.access_id|default('routed')|string()) if l.vlan is defined else l.ifname %}
{% set if_desc = l.name|default( "vlan " + vlan )|replace('->','~')|regex_replace('[\\[\\]]','') %}
- path: interface[name={{ if_name }}]
val:
{% if l.mtu is defined %} # min 1500; max 9412 for 7220, 9500 for 7250 platforms
mtu {{ [l.mtu + 14,1500]|max }} # TODO not supported on loopback interfaces
{% endif %}
{% if l.subif_index is not defined %}{# Skip trunk parent interfaces #}
{% if l.type in ["vlan_member"] %}
vlan-tagging: True
{% endif %}
{% if l.subif_index is defined %}{# Trunk parent interfaces #}
description: "Trunk {{ if_desc }}"
{% else %}
subinterface:
index: {{ if_index }}
description: "{{ if_desc }}"
{% if l.vlan is defined and l.type in ["vlan_member","lan"] %}
type: {{ 'bridged' if l.vlan.access is defined and vlans[ l.vlan.access ].mode|default('irb') in ['bridge','irb'] else 'routed' }}
{% if l.type in ["vlan_member"] %}
vlan:
encap:
{% if l.vlan.access_id is defined %}
single-tagged:
vlan-id: "{{ l.vlan.access_id }}"
{% else %}
untagged: { }
{% endif %}
{% endif %}
{% endif %}
{{ ip_addresses(l,True,False) }}
{% else %}
description: "Trunk {{ if_desc }}"

{{ ip_addresses(if_name,if_index,l) }}
{% endif %}
{% endfor %}

Expand Down
9 changes: 2 additions & 7 deletions netsim/ansible/templates/ospf/srlinux.macro.j2
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,10 @@
- interface-name: system0.0
passive: True
{% endif %}
{% for l in vrf_interfaces if (l.vlan is not defined or l.vlan.mode|default('bridge')=='route') and l.subif_index is not defined %}
{% set ifname = l.ifname if '.' in l.ifname else (l.ifname+'.0') %}
{% for l in vrf_interfaces if (l.vlan is not defined or l.vlan.mode|default('irb')!='bridge') and l.subif_index is not defined %}
{% set ifname = l.ifname if '.' in l.ifname else l.ifname|replace('vlan','irb0.') if l.type=='svi' else (l.ifname+'.0') %}
{% if 'ospf' not in l %}
# OSPF not configured on external interface {{ ifname }}
- area-id: {{ ospf.area }}
interface:
- interface-name: {{ ifname }}
admin-state: disable
_annotate_admin-state: "Disabled: external interface"
{% else %}
- area-id: {{ l.ospf.area }}
interface:
Expand Down
63 changes: 52 additions & 11 deletions netsim/ansible/templates/vlan/srlinux.j2
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
{% from "templates/initial/srlinux.j2" import ip_addresses with context %}

updates:
{# Create mac-vrfs for L2 VLANs, add IRB interface if any #}
{% if vlans is defined %}
{% for vname,vdata in vlans.items() if vdata.mode|default('irb') != 'route' %}
{% set irb_ifname = "irb0." + vdata.id | string() %}
- path: network-instance[name=vlan_{{ vname }}]
{# Use only vlan.id in name, such that svi interfaces can be associated #}
- path: network-instance[name=vlan{{ vdata.id }}]
val:
type: mac-vrf
description: "VLAN {{ vname }}"
{% endfor %}
{% endif %}

{% macro add_interface(macvrf,ifname,vlan,i) %}
- path: interface[name={{ifname}}]
val:
{% if i.type in ["vlan_member"] %}
vlan-tagging: True
{% endif %}
subinterface:
- index: {{ vlan }}
{% if ifname!="irb0" %}
type: bridged
{% if i.type in ["vlan_member"] %}
vlan:
encap:
{% if i.vlan.access_id is defined %}
single-tagged:
vlan-id: "{{ i.vlan.access_id }}"
{% else %}
untagged: { }
{% endif %}
{% endif %}
{% endif %}

{{ ip_addresses(ifname,vlan,i) }}

- path: network-instance[name={{ macvrf }}]
val:
interface:
{% for l in interfaces|default([]) %}
{% if l.type in ['lan'] and l.vlan is defined and l.vlan.access == vname %}
- name: {{ l.ifname }}.{{ l.vlan.access_id }}
{% elif l.type in ['vlan_member'] and l.vlan is defined and l.vlan.access|default('?') == vname %}
- name: {{ l.ifname }}
{% elif l.type=='svi' and l.ifname == irb_ifname and (l.vlan is not defined or l.vlan.mode|default('irb') == 'irb') %}
- name: {{ l.ifname }}
- name: {{ ifname }}.{{ vlan }}

{% if ifname=="irb0" %}
- path: network-instance[name={{ i.vrf|default('default') }}]
val:
interface:
- name: {{ ifname }}.{{ vlan }}
{% endif %}

{% endmacro %}

{% for l in interfaces|default([]) if l.vlan is defined %}
{% if l.type in ['svi'] and l.vlan.mode|default('irb') == 'irb' %}
{% set vlan = l.ifname[4:]|int %}
{{ add_interface( l.ifname, "irb0", vlan, l ) }}
{% elif l.type in ['lan','vlan_member'] %}
{% set vlan = l.vlan.access_id %}
{{ add_interface( "vlan" + vlan|string, l.parent_ifname|default(l.ifname), vlan, l ) }}
{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
14 changes: 14 additions & 0 deletions netsim/ansible/templates/vrf/srlinux.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,24 @@
{% from "templates/bgp/srlinux.macro.j2" import bgp_config with context %}
updates:
{% for vname,vdata in vrfs.items() %}

- path: network-instance[name={{vname}}]
val:
type: ip-vrf

{% if 'ospf' in vdata %}
{{ ospf_config(0,'ipv4' if vdata.af.ipv4|default(0) else 'ipv6',vname,vdata.ospf,vdata.ospf.interfaces)}}
{% endif %}
{% if 'bgp' in vdata %}
{{ bgp_config(vname,vrf.as,bgp.router_id,vdata.bgp,vdata) }}
{% endif %}
{% endfor %}

# Associate irb interfaces with the corresponding vrf
{% for i in interfaces if i.type=='svi' and 'vrf' in i and i.vlan.mode|default('irb')=='irb' %}
{% set vlan = i.ifname[4:]|int %}
- path: network-instance[name={{ i.vrf }}]
val:
interface:
- name: irb0.{{ vlan }}
{% endfor %}
39 changes: 27 additions & 12 deletions netsim/ansible/templates/vxlan/srlinux.j2
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
updates:
{% if vxlan.vlans is defined %}
{% for vname in vxlan.vlans if vlans[vname].vni is defined %}
{% set vlan = vlans[vname] %}
- path: tunnel-interface[name=vxlan0]/vxlan-interface[index={{vlan.id}}]
{% macro vxlan_interface(vrf,index,type,vni,evi) %}
- path: tunnel-interface[name=vxlan0]/vxlan-interface[index={{index}}]
val:
type: bridged
type: {{ type }}
ingress:
vni: {{ vlan.vni }}
vni: {{ vni }}
egress:
source-ip: use-system-ipv4-address

- path: network-instance[name=vlan_{{vname}}]
- path: network-instance[name={{vrf}}]
val:
type: mac-vrf
type: {{ 'mac-vrf' if type=='bridged' else 'ip-vrf' }}
vxlan-interface:
- name: vxlan0.{{ vlan.id }}
- name: vxlan0.{{ index }}
protocols:
bgp-vpn:
bgp-instance:
- id: 1
route-target:
_annotate: "For compatibility with frr, override auto-derived RT based on EVI {{evi}} with VNI {{vni}}"
import-rt: "target:{{ bgp.as }}:{{ vni }}"
export-rt: "target:{{ bgp.as }}:{{ vni }}"
bgp-evpn:
bgp-instance:
- id: 1
evi: {{ vlan.evpn.evi }}
evi: {{ evi }}
ecmp: 8
vxlan-interface: vxlan0.{{ vlan.id }}
vxlan-interface: vxlan0.{{ index }}
{% endmacro %}

updates:
{% if vlans is defined and vxlan.vlans is defined %}
{% for vname in vxlan.vlans if vlans[vname].vni is defined %}
{% set vlan = vlans[vname] %}
{{ vxlan_interface('vlan'+vlan.id|string,vlan.id,'bridged',vlan.vni,vlan.evpn.evi) }}
{% endfor %}
{% endif %}

{# Symmetric IRB interfaces, note using VRF ID as transit EVI value #}
{% if vrfs is defined %}
{% for vname,vdata in vrfs.items() if 'evpn' in vdata and 'transit_vni' in vdata.evpn %}
{{ vxlan_interface(vname,vdata.vrfidx,'routed',vdata.evpn.transit_vni,vdata.vrfidx) }}
{% endfor %}
{% endif %}
3 changes: 3 additions & 0 deletions netsim/modules/evpn.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def vrf_transit_vni(topology: Box) -> None:
common.IncorrectValue,
'evpn')
continue
vni_list.append( vni ) # Insert it to detect duplicates elsewhere

vni_start = topology.defaults.evpn.start_transit_vni
for vrf_name,vrf_data in topology.vrfs.items(): # Second pass: set transit VNI values for VRFs with "transit_vni: True"
Expand All @@ -101,6 +102,8 @@ def vrf_transit_vni(topology: Box) -> None:
key='evpn.transit_vni',
path=f'vrfs.{vrf_name}',
module='evpn',
min_value=4096, # As recommended by Cisco, outside of VLAN range
max_value=16777215,
true_value=vni_start) # Make sure evpn.transit_vni is an integer
if transit_vni == vni_start: # If we had to assign the default value, increment the default transit VNI
vni_start = get_next_vni(vni_start,vni_list)
Expand Down
12 changes: 9 additions & 3 deletions netsim/topology-defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ vlan: # VLAN support
vxlan: # VXLAN support
supported_on: [ eos, nxos, vyos, dellos10, srlinux ]
requires: [ vlan ]
config_after: [ vrf ] # For platforms that suppport L3 VXLAN, vrfs must be created first
domain: global
flooding: static
attributes:
Expand Down Expand Up @@ -516,7 +517,7 @@ devices:
ipv4:
unnumbered: True
ipv6:
lla: True
lla: True
bgp:
activate_af: True
ipv6_lla: True
Expand Down Expand Up @@ -713,7 +714,7 @@ devices:
lla: True
vlan:
model: router
svi_interface_name: "irb0.{vlan}"
svi_interface_name: "vlan{vlan}" # Used as mac-vrf name
subif_name: "{ifname}.{vlan.access_id}"
mixed_trunk: True
bgp:
Expand All @@ -724,14 +725,19 @@ devices:
ipv6_lla: True
rfc8950: True
vxlan:
requires: [ evpn, vrf ]
requires: [ evpn ] # vrf for l3 vxlan
evpn:
irb: True
ospf:
unnumbered: False
isis:
unnumbered:
ipv4: False
ipv6: True
network: False
vrf:
loopback_interface_name: "lo0.{vrfidx}"
keep_module: True
external:
image: none
graphite.icon: router
Expand Down

0 comments on commit 57b215a

Please sign in to comment.