Skip to content

Commit

Permalink
Initial VXLAN implementation:
Browse files Browse the repository at this point in the history
* Transformation module supporting VLAN-to-VXLAN bridging
* Simple VXLAN test case
* Arista EOS Jinja2 template
* Documentation
* Integration test
  • Loading branch information
ipspace committed Jul 22, 2022
1 parent 91a3cbf commit 8f3e5a4
Show file tree
Hide file tree
Showing 8 changed files with 884 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/module-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ Experimental modules are usually implemented on a small set of devices. We're al
module/evpn.md
module/srv6.md
module/vxlan.md
```
117 changes: 117 additions & 0 deletions docs/module/vxlan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# VXLAN Configuration Module

This configuration module configures VXLAN data plane, VLAN-to-VXLAN mapping, and static head-end replication.

The module supports the following features:

* VLAN-to-VXLAN bridging
* Single VXLAN interface per device
* Default loopback address used as the VTEP IP address
* Static per-VLAN or per-node ingress replication
* Mapping a subset of VLANs into VXLAN VNIs

The module requires VLAN module (to set up **vlans** dictionary) and should be used with a routing protocol module to establish VTEP-to-VTEP connectivity.

```eval_rst
.. contents:: Table of Contents
:depth: 2
:local:
:backlinks: none
```

## Platform Support

The following table describes per-platform support of individual VXLAN features:

| Operating system | VXLAN<br>bridging |
| ------------------ | :-: |
| Arista EOS ||

## Global Parameters

* **vxlan.domain** -- Ingress replication domain. Optional, default: **global**. Use this parameter when you want to build several isolated bridging domains within your lab.
* **vxlan.flooding** -- A mechanism used to implement VXLAN flooding. Optional, default: **static**.
* **vxlan.vlans** -- list of VLANs to be mapped into VXLAN VNIs. Optional, defaults to all VLANs with **vni** attribute. All VLANs listed in **vxlan.vlans** list must have a **vni** attribute.

The only supported value for **vxlan.flooding** parameter is **static** -- statically configured ingress replication

All global parameters can also be used as node parameters.

## Default Behavior

* All VLANs are mapped into VXLAN VNIs and bridged between VXLAN-enabled nodes.
* Without specifying **vxlan.domain** for individual nodes or groups of nodes, all VXLAN-enabled nodes belong to a single **global** bridging domain.
* VXLAN flooding is implemented with ingress replication. The VXLAN module builds per-VLAN VTEP replication lists for each node. Whether the device configuration uses VLAN-level or global replication lists is an implementation decision.

## Selecting VXLAN-enabled VLANs

You can select VLANs that should be extended with VXLAN transport in two ways:

* Specify a list of VLAN names in **vxlan.vlans** global- or node-level parameters. VLANs specified in that list must be valid VLAN names but do not have to be present on every node.
* Select VLANs based on the presence of **vni** attribute.

You can set the **vni** attribute for individual VLANs, or have it assigned automatically. By default, all global VLANs get a **vni** attribute, and are thus extended over VXLAN transport. This behavior is controlled with **defaults.vlan.auto_vni** global default.

## Building Ingress Replication Lists

The VXLAN module builds ingress replication lists for all nodes with **vxlan.flooding** set to **static**. Each VLAN-specific ingress replication list includes the VTEP IP addresses of all other nodes in the same **vxlan.domain** that have a VLAN with the same **vni** attribute.

All VLAN-specific ingress replication lists are merged into a node-level ingress replication list. Some devices support per-VLAN replication lists while others might use node-level replication list; the only difference is the amount of irrelevant traffic replicated across the VXLAN transport network.

## Example

We want to create a simple two-switch network transporting two VLANs across VXLAN backbone. We have to define the VLANs first:

```
vlans:
red:
mode: bridge
blue:
mode: bridge
```

Next, we'll define the *switches* and *hosts* groups to simplify node configuration. All switches will run Arista EOS and use VLAN, VXLAN, and OSPF modules.

```
groups:
hosts:
members: [ h1, h2, h3, h4 ]
device: linux
switches:
members: [ s1,s2 ]
device: eos
module: [ vlan,vxlan,ospf ]
```

We also have to define individual nodes. Please note that we set node parameters (modules and device type) within *switches* and *hosts* groups.

```
nodes: [ h1, h2, h3, h4, s1, s2 ]
```

Finally, we have to define the links in our lab:

```
links:
- h1:
s1:
vlan.access: red
- h2:
s2:
vlan.access: red
- h3:
s1:
vlan.access: blue
- h4:
s2:
vlan.access: blue
- s1:
s2:
```

Please note we did not have to define:

* VLAN tags or VXLAN VNIs
* VLAN-to-VXLAN mappings
* Any other VLAN or VXLAN parameters apart from VLAN names
* IP addresses or routing protocol parameters
10 changes: 10 additions & 0 deletions netsim/ansible/templates/vxlan/eos.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
interface vxlan 1
vxlan source-interface loopback 0
{% if vlans is defined %}
{% for vlan in vlans.values() if vlan.vni is defined %}
vxlan vlan {{ vlan.id }} vni {{ vlan.vni }}
{% if vlan.vtep_list is defined %}
vxlan vlan {{ vlan.id }} flood vtep {{ vlan.vtep_list|join(' ') }}
{% endif %}
{% endfor %}
{% endif %}
130 changes: 130 additions & 0 deletions netsim/modules/vxlan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#
# VXLAN module
#
import typing
from box import Box
import netaddr

from . import _Module,get_effective_module_attribute
from .. import common
from .. import data
from ..data import get_from_box
from ..augment import devices
from .. import addressing

#
# Check VXLAN-enabled VLANs
#
def node_vlan_check(node: Box, topology: Box) -> bool:
if not node.vxlan.vlans: # Create a default list of VLANs if needed
node.vxlan.vlans = [ name for name,value in node.vlans.items() if 'vni' in value ]

OK = True
vlan_list = []
for vname in node.vxlan.vlans:
#
# Report error for unknown VLAN names in VLAN list (they are not defined globally or on the node)
if not vname in node.vlans and not vname in topology.get('vlans',{}):
common.error(
f'Unknown VLAN {vname} is specified in the list of VXLAN-enabled VLANs in node {node.name}',
common.IncorrectValue,
'vxlan')
OK = False
continue
#
# Skip VLAN names that are valid but not used on this node
if not vname in node.vlans:
continue
if not 'vni' in node.vlans[vname]:
common.error(
f'VXLAN-enabled VLAN {vname} in node {node.name} does not have a VNI',
common.IncorrectValue,
'vxlan')
OK = False
else:
vlan_list.append(vname)

node.vxlan.vlans = vlan_list
return OK

#
# Set VTEP IPv4 address
#
def node_set_vtep(node: Box, topology: Box) -> bool:
if not 'ipv4' in node.loopback:
common.error(
f'VXLAN module needs an IPv4 address on loopback interface of {node.name}',
common.IncorrectValue,
'vxlan')
return False

vtep_ip = node.loopback.ipv4 # Assume we're using primarly loopback as VTEP
node.vxlan.vtep = str(netaddr.IPNetwork(vtep_ip).ip) # ... and convert IPv4 prefix into an IPv4 address
return True

#
# Build VLAN-specific VTEP list
#
def build_vtep_list(vlan: Box, node: str, nodes: typing.List[str], topology: Box) -> list:
vlan.vtep_list = [] # Start with an emtpy VTEP list
for n in nodes:
if n == node: # Skip own node
continue
ndata = topology.nodes[n]
if not 'vlans' in ndata: # No VLANs on remote node, skip it
continue
vni_match = filter(lambda x: x.vni == vlan.vni,ndata.vlans.values())
if list(vni_match): # Is there a VLAN with matching VNI on remote node?
vlan.vtep_list.append(ndata.vxlan.vtep) # ... if so, add remote VTEP to VLAN flood list

return_value = vlan.vtep_list # We'll return whatever we built
if not vlan.vtep_list: # ... but will remove empty VTEP list from VLAN data
vlan.pop('vtep_list')

return return_value

class VXLAN(_Module):

def node_pre_transform(self, node: Box, topology: Box) -> None:
data.must_be_string(
parent = node.vxlan,
key = 'flooding',
path = 'nodes.{node.name}.vxlan',
valid_values = [ 'static' ])

# We need multi-step post-transform node handling, so we have to do that in
# module_post_transform
#
def module_post_transform(self, topology: Box) -> None:
vxlan_domain_list: typing.Dict[str,list] = {}

for name,ndata in topology.nodes.items():
if not 'vxlan' in ndata.get('module',[]): # Skip nodes without VXLAN module
continue
if not 'vlans' in ndata: # Skip VXLAN-enabled nodes without VLANs
continue

if not node_vlan_check(ndata,topology): # Check VLANs
continue
if not node_set_vtep(ndata,topology): # Set VTEP IP address
continue

vxlan_domain = ndata.vxlan.domain # Find VXLAN flooding domain name
if not vxlan_domain in vxlan_domain_list: # Unknown VXLAN domain, prepare an empty list
vxlan_domain_list[vxlan_domain] = []
vxlan_domain_list[vxlan_domain].append(name) # Add current node to VXLAN domain list

for domain,nodes in vxlan_domain_list.items(): # Iterate over VXLAN flooding domains
for node in nodes: # ... and over all nodes in each domain
ndata = topology.nodes[node]
if ndata.vxlan.get('flooding') != 'static': # Skip nodes that are not using static replication lists
continue

vtep_set = set()
for vlan in ndata.vlans.values(): # Iterate over all VLANs defined in current node
if 'vni' in vlan: # Are we dealing with VXLAN-enabled VLAN?
vtep_list = build_vtep_list(vlan,node,nodes,topology) # Build VLAN-specific VTEP list
vtep_set.update(vtep_list) # ... and add it to node-level VTEP set


ndata.vxlan.vtep_list = sorted(list(vtep_set)) # Convert node-level VTEP set into a list
9 changes: 9 additions & 0 deletions netsim/topology-defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,15 @@ vlan: # VLAN support
global: [ vlans ]
node: [ vlans ]

vxlan: # VXLAN support
supported_on: [ eos ]
requires: [ vlan ]
domain: global
flooding: static
attributes:
global: [ domain, flooding, vlans ]
node: [ domain, flooding, vlans ]

mpls: # LDP and BGP LU support
supported_on: [ eos, iosv, csr, routeros, vyos ]
config_after: [ vlan, ospf, isis, bgp ]
Expand Down
47 changes: 47 additions & 0 deletions tests/integration/vxlan/vxlan-bridging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#
# The devices under test are VLAN-to-VXLAN bridges
#
# * h1 and h2 should be able to ping each other
# * h3 and h4 should be able to ping each other
# * h1 should not be able to reach h3
#
# Please note it might take a while for the lab to work due to
# STP learning phase
#
groups:
hosts:
members: [ h1, h2, h3, h4 ]
device: linux
switches:
members: [ s1,s2 ]
module: [ vlan,vxlan,ospf ]

vlans:
red:
mode: bridge
blue:
mode: bridge

nodes:
h1:
h2:
h3:
h4:
s1:
s2:

links:
- h1:
s1:
vlan.access: red
- h2:
s2:
vlan.access: red
- h3:
s1:
vlan.access: blue
- h4:
s2:
vlan.access: blue
- s1:
s2:
Loading

0 comments on commit 8f3e5a4

Please sign in to comment.