summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2023-09-09 07:13:31 +0200
committerChristian Breunig <christian@breunig.cc>2023-09-09 07:15:58 +0200
commit7f6624f5a6f8bd1749b54103ea5ec9f010adf778 (patch)
treeb6db1e6b452bcf9cfbd2ede10a7f8fc1c8378f7f /python
parenta12559a728310f0e3bf6dc18f7bd2a0ba444b890 (diff)
downloadvyos-1x-7f6624f5a6f8bd1749b54103ea5ec9f010adf778.tar.gz
vyos-1x-7f6624f5a6f8bd1749b54103ea5ec9f010adf778.zip
vxlan: T3700: support VLAN tunnel mapping of VLAN aware bridges
FRR supports a new way of configuring VLAN-to-VNI mappings for EVPN-VXLAN, when working with the Linux kernel. In this new way, the mapping of a VLAN to a VNI is configured against a container VXLAN interface which is referred to as a 'Single VXLAN device (SVD)'. Multiple VLAN to VNI mappings can be configured against the same SVD. This allows for a significant scaling of the number of VNIs since a separate VXLAN interface is no longer required for each VNI. Sample configuration of SVD with VLAN to VNI mappings is shown below. set interfaces bridge br0 member interface vxlan0 set interfaces vxlan vxlan0 external set interfaces vxlan vxlan0 source-interface 'dum0' set interfaces vxlan vxlan0 vlan-to-vni 10 vni '10010' set interfaces vxlan vxlan0 vlan-to-vni 11 vni '10011' set interfaces vxlan vxlan0 vlan-to-vni 30 vni '10030' set interfaces vxlan vxlan0 vlan-to-vni 31 vni '10031'
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig/vxlan.py66
-rw-r--r--python/vyos/utils/network.py45
-rw-r--r--python/vyos/utils/process.py2
3 files changed, 110 insertions, 3 deletions
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 6a9911588..1fe5db7cd 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -13,9 +13,15 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+from json import loads
+
from vyos import ConfigError
+from vyos.configdict import list_diff
from vyos.ifconfig import Interface
+from vyos.utils.assertion import assert_list
from vyos.utils.dict import dict_search
+from vyos.utils.network import get_interface_config
+from vyos.utils.network import get_vxlan_vlan_tunnels
@Interface.register
class VXLANIf(Interface):
@@ -49,6 +55,13 @@ class VXLANIf(Interface):
}
}
+ _command_set = {**Interface._command_set, **{
+ 'vlan_tunnel': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': 'bridge link set dev {ifname} vlan_tunnel {value}',
+ },
+ }}
+
def _create(self):
# This table represents a mapping from VyOS internal config dict to
# arguments used by iproute2. For more information please refer to:
@@ -99,3 +112,54 @@ class VXLANIf(Interface):
cmd = f'bridge fdb append to 00:00:00:00:00:00 dst {remote} ' \
'port {port} dev {ifname}'
self._cmd(cmd.format(**self.config))
+
+ def set_vlan_vni_mapping(self, state):
+ """
+ Controls whether vlan to tunnel mapping is enabled on the port.
+ By default this flag is off.
+ """
+ if not isinstance(state, bool):
+ raise ValueError('Value out of range')
+
+ cur_vlan_ids = []
+ if 'vlan_to_vni_removed' in self.config:
+ cur_vlan_ids = self.config['vlan_to_vni_removed']
+ for vlan in cur_vlan_ids:
+ self._cmd(f'bridge vlan del dev {self.ifname} vid {vlan}')
+
+ # Determine current OS Kernel vlan_tunnel setting - only adjust when needed
+ tmp = get_interface_config(self.ifname)
+ cur_state = 'on' if dict_search(f'linkinfo.info_slave_data.vlan_tunnel', tmp) == True else 'off'
+ new_state = 'on' if state else 'off'
+ if cur_state != new_state:
+ self.set_interface('vlan_tunnel', new_state)
+
+ # Determine current OS Kernel configured VLANs
+ os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname)
+
+ if 'vlan_to_vni' in self.config:
+ add_vlan = list_diff(list(self.config['vlan_to_vni'].keys()), os_configured_vlan_ids)
+
+ for vlan, vlan_config in self.config['vlan_to_vni'].items():
+ # VLAN mapping already exists - skip
+ if vlan not in add_vlan:
+ continue
+
+ vni = vlan_config['vni']
+ # The following commands must be run one after another,
+ # they can not be combined with linux 6.1 and iproute2 6.1
+ self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan}')
+ self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan} tunnel_info id {vni}')
+
+ def update(self, config):
+ """ General helper function which works on a dictionary retrived by
+ get_config_dict(). It's main intention is to consolidate the scattered
+ interface setup code and provide a single point of entry when workin
+ on any interface. """
+
+ # call base class last
+ super().update(config)
+
+ # Enable/Disable VLAN tunnel mapping
+ # This is only possible after the interface was assigned to the bridge
+ self.set_vlan_vni_mapping(dict_search('vlan_to_vni', config) != None)
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 55ff29f0c..4c579c760 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -408,7 +408,7 @@ def is_subnet_connected(subnet, primary=False):
return False
-def is_afi_configured(interface, afi):
+def is_afi_configured(interface: str, afi):
""" Check if given address family is configured, or in other words - an IP
address is assigned to the interface. """
from netifaces import ifaddresses
@@ -425,3 +425,46 @@ def is_afi_configured(interface, afi):
return False
return afi in addresses
+
+def get_vxlan_vlan_tunnels(interface: str) -> list:
+ """ Return a list of strings with VLAN IDs configured in the Kernel """
+ from json import loads
+ from vyos.utils.process import cmd
+
+ if not interface.startswith('vxlan'):
+ raise ValueError('Only applicable for VXLAN interfaces!')
+
+ # Determine current OS Kernel configured VLANs
+ #
+ # $ bridge -j -p vlan tunnelshow dev vxlan0
+ # [ {
+ # "ifname": "vxlan0",
+ # "tunnels": [ {
+ # "vlan": 10,
+ # "vlanEnd": 11,
+ # "tunid": 10010,
+ # "tunidEnd": 10011
+ # },{
+ # "vlan": 20,
+ # "tunid": 10020
+ # } ]
+ # } ]
+ #
+ os_configured_vlan_ids = []
+ tmp = loads(cmd(f'bridge --json vlan tunnelshow dev {interface}'))
+ if tmp:
+ for tunnel in tmp[0].get('tunnels', {}):
+ vlanStart = tunnel['vlan']
+ if 'vlanEnd' in tunnel:
+ vlanEnd = tunnel['vlanEnd']
+ # Build a real list for user VLAN IDs
+ vlan_list = list(range(vlanStart, vlanEnd +1))
+ # Convert list of integers to list or strings
+ os_configured_vlan_ids.extend(map(str, vlan_list))
+ # Proceed with next tunnel - this one is complete
+ continue
+
+ # Add single tunel id - not part of a range
+ os_configured_vlan_ids.append(str(vlanStart))
+
+ return os_configured_vlan_ids
diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py
index c2ef98140..9ecdddf09 100644
--- a/python/vyos/utils/process.py
+++ b/python/vyos/utils/process.py
@@ -139,7 +139,7 @@ def cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
expect: a list of error codes to consider as normal
"""
decoded, code = popen(
- command, flag,
+ command.lstrip(), flag,
stdout=stdout, stderr=stderr,
input=input, timeout=timeout,
env=env, shell=shell,