1
0
mirror of https://github.com/CumulusNetworks/ifupdown2.git synced 2024-05-06 15:54:50 +00:00
Julien Fortin 0500d5d882 addons: vxlan: add support for l3vxi via vxlan-vni
Signed-off-by: Julien Fortin <jfortin@nvidia.com>
2022-05-27 12:12:17 +02:00

1729 lines
74 KiB
Python

#!/usr/bin/env python3
#
# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
from ipaddress import IPv4Network, IPv4Address, AddressValueError, ip_address
try:
import ifupdown2.nlmanager.ipnetwork as ipnetwork
import ifupdown2.ifupdown.policymanager as policymanager
import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
from ifupdown2.lib.addon import Vxlan
from ifupdown2.lib.nlcache import NetlinkCacheIfnameNotFoundError
from ifupdown2.nlmanager.nlmanager import Link
from ifupdown2.ifupdown.iface import *
from ifupdown2.ifupdown.utils import utils
from ifupdown2.ifupdown.statemanager import statemanager_api as statemanager
from ifupdown2.ifupdownaddons.cache import *
from ifupdown2.ifupdownaddons.modulebase import moduleBase
except (ImportError, ModuleNotFoundError):
import nlmanager.ipnetwork as ipnetwork
import ifupdown.policymanager as policymanager
import ifupdown.ifupdownflags as ifupdownflags
from lib.addon import Vxlan
from lib.nlcache import NetlinkCacheIfnameNotFoundError
from nlmanager.nlmanager import Link
from ifupdown.iface import *
from ifupdown.utils import utils
from ifupdown.statemanager import statemanager_api as statemanager
from ifupdownaddons.cache import *
from ifupdownaddons.modulebase import moduleBase
class vxlan(Vxlan, moduleBase):
_modinfo = {
"mhelp": "vxlan module configures vxlan interfaces.",
"attrs": {
"vxlan-id": {
"help": "vxlan id",
"validrange": ["1", "16777214"],
"required": True,
"example": ["vxlan-id 100"]
},
"vxlan-local-tunnelip": {
"help": "vxlan local tunnel ip",
"validvals": ["<ipv4>"],
"example": ["vxlan-local-tunnelip 172.16.20.103"]
},
"vxlan-svcnodeip": {
"help": "vxlan svc node id",
"validvals": ["<ipv4>"],
"example": ["vxlan-svcnodeip 172.16.22.125"]
},
"vxlan-svcnodeip6": {
"help": "vxlan svc node ip",
"validvals": ["<ipv6>"],
"example": ["vxlan-svcnodeip6 2001:DB8:8086:6502::"]
},
"vxlan-remoteip": {
"help": "vxlan remote ip",
"validvals": ["<ipv4>"],
"example": ["vxlan-remoteip 172.16.22.127"],
"multiline": True
},
"vxlan-learning": {
"help": "vxlan learning yes/no",
"validvals": ["yes", "no", "on", "off"],
"example": ["vxlan-learning no"],
"default": "yes"
},
"vxlan-ageing": {
"help": "vxlan aging timer",
"validrange": ["0", "4096"],
"example": ["vxlan-ageing 300"],
"default": "300"
},
"vxlan-purge-remotes": {
"help": "vxlan purge existing remote entries",
"validvals": ["yes", "no"],
"example": ["vxlan-purge-remotes yes"],
},
"vxlan-port": {
"help": "vxlan UDP port (transmitted to vxlan driver)",
"example": ["vxlan-port 4789"],
"validrange": ["1", "65536"],
"default": "4789",
},
"vxlan-physdev": {
"help": "vxlan physical device",
"example": ["vxlan-physdev eth1"]
},
"vxlan-ttl": {
"help": "specifies the TTL value to use in outgoing packets "
"(range 0..255), 0=auto",
"default": "0",
"validrange": ["0", "255"],
"validvals": ["<number>", "auto"],
"example": ['vxlan-ttl 42'],
},
"vxlan-tos": {
"help": "specifies the ToS value (range 0..255), 1=inherit",
"validrange": ["0", "255"],
"validvals": ["<number>", "inherit"],
"example": ['vxlan-tos 42'],
},
"vxlan-mcastgrp": {
"help": "vxlan multicast group",
"validvals": ["<ip>"],
"example": ["vxlan-mcastgrp 172.16.22.127"],
},
"vxlan-mcastgrp6": {
"help": "vxlan multicast group",
"validvals": ["<ip6>"],
"example": ["vxlan-mcastgrp ff02::15c"],
},
"vxlan-mcastgrp-map": {
"help": "vxlan multicast group for single-vxlan device -"
"doesn't support multiline attribute",
"example": ["vxlan-mcastgrp-map 1000=239.1.1.100 1001=239.1.1.200"],
},
"vxlan-vnifilter": {
"help": "vxlan vni filter for single-vxlan device",
"validvals": ["on", "off"],
"default": "off",
"example": ["vxlan-vnifilter yes"],
},
"vxlan-remoteip-map": {
"help": "static HREP entries for static single vxlan device",
"example": ["vxlan-remoteip-map 1000-1002=27.0.0.10-27.0.0.12"],
},
"vxlan-udp-csum": {
"help": "whether to perform checksumming or not",
"validvals": ["yes", "no"],
"example": ["vxlan-udp-csum no"]
},
"vxlan-vni": {
"help": "L3 VxLAN interface (vni list and range are supported)",
"validvals": ["<number>"],
"example": ["vxlan-vni 42"]
}
}
}
VXLAN_PHYSDEV_MCASTGRP_DEFAULT = "ipmr-lo"
def __init__(self, *args, **kargs):
Vxlan.__init__(self)
moduleBase.__init__(self, *args, **kargs)
self._vxlan_purge_remotes = utils.get_boolean_from_string(
policymanager.policymanager_api.get_module_globals(
module_name=self.__class__.__name__,
attr="vxlan-purge-remotes"
)
)
self._vxlan_local_tunnelip = None
self._clagd_vxlan_anycast_ip = ""
# If mcastgrp is specified we need to rely on a user-configred device (via physdev)
# or via a policy variable "vxlan-physdev_mcastgrp". If the device doesn't exist we
# create it as a dummy device. We need to keep track of the user configuration to
# know when to delete this dummy device (when user remove mcastgrp from it's config)
self.vxlan_mcastgrp_ref = False
self.vxlan_physdev_mcast = policymanager.policymanager_api.get_module_globals(
module_name=self.__class__.__name__,
attr="vxlan-physdev-mcastgrp"
) or self.VXLAN_PHYSDEV_MCASTGRP_DEFAULT
self.tvd_svd_mix_support = utils.get_boolean_from_string(
policymanager.policymanager_api.get_module_globals(
module_name=self.__class__.__name__,
attr="vxlan-support-mix-dev-types"
),
default=True
)
self.svd_tvd_errors = {}
def reset(self):
# in daemon mode we need to reset mcastgrp_ref for every new command
# this variable has to be set in get_dependent_ifacenames
self.vxlan_mcastgrp_ref = False
def syntax_check(self, ifaceobj, ifaceobj_getfunc):
if self._is_vxlan_device(ifaceobj):
if not ifaceobj.get_attr_value_first('vxlan-local-tunnelip') and not self._vxlan_local_tunnelip:
self.logger.warning('%s: missing vxlan-local-tunnelip' % ifaceobj.name)
return False
self.check_and_raise_svd_tvd_errors(ifaceobj)
return self.syntax_check_localip_anycastip_equal(
ifaceobj.name,
ifaceobj.get_attr_value_first('vxlan-local-tunnelip') or self._vxlan_local_tunnelip,
self._clagd_vxlan_anycast_ip
)
return True
def syntax_check_localip_anycastip_equal(self, ifname, local_ip, anycast_ip):
try:
if local_ip and anycast_ip and ipnetwork.IPNetwork(local_ip) == ipnetwork.IPNetwork(anycast_ip):
self.logger.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
% (ifname, local_ip))
return False
except Exception:
pass
return True
def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None, old_ifaceobjs=False):
if ifaceobj.get_attr_value_first("bridge-vlan-vni-map"):
ifaceobj.link_privflags |= ifaceLinkPrivFlags.SINGLE_VXLAN
if self._is_vxlan_device(ifaceobj):
ifaceobj.link_kind |= ifaceLinkKind.VXLAN
self._set_global_local_ip(ifaceobj)
self.__check_and_tag_l3vxi(ifaceobj)
if not old_ifaceobjs and not self.tvd_svd_mix_support:
# mixing TVD and SVD is not supported - we need to warn the user
# we use a dictionary to make sure to only warn once and prevent each
# vxlan from being configured on the system
if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
self.single_vxlan_configured.add(ifaceobj.name)
if self.traditional_vxlan_configured:
self.svd_tvd_errors[ifaceobj.name] = (
"%s: mixing single-vxlan-device with tradional %s is not supported (TVD: %s)"
% (ifaceobj.name, "vxlans" if len(self.traditional_vxlan_configured) > 1 else "vxlan", ", ".join(self.traditional_vxlan_configured))
)
elif ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
pass
else:
self.traditional_vxlan_configured.add(ifaceobj.name)
if self.single_vxlan_configured:
self.svd_tvd_errors[ifaceobj.name] = (
"%s: mixing traditional vxlan with single vxlan %s is not supported (SVD: %s)"
% (ifaceobj.name, "devices" if len(self.single_vxlan_configured) > 1 else "device", ", ".join(self.single_vxlan_configured))
)
# if we detect a vxlan we check if mcastgrp is set (if so we set vxlan_mcastgrp_ref)
# to know when to delete this device.
if not self.vxlan_mcastgrp_ref and (ifaceobj.get_attr_value("vxlan-mcastgrp") or ifaceobj.get_attr_value("vxlan-mcastgrp-map")):
self.vxlan_mcastgrp_ref = True
elif ifaceobj.name == 'lo' and not old_ifaceobjs:
clagd_vxlan_list = ifaceobj.get_attr_value('clagd-vxlan-anycast-ip')
if clagd_vxlan_list:
if len(clagd_vxlan_list) != 1:
self.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one'
% (ifaceobj.name,))
self._clagd_vxlan_anycast_ip = clagd_vxlan_list[0]
self._set_global_local_ip(ifaceobj)
# If we should use a specific underlay device for the VXLAN
# tunnel make sure this device is set up before the VXLAN iface.
physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
if physdev:
return [physdev]
return None
def __check_and_tag_l3vxi(self, ifaceobj):
if ifaceobj.get_attr_value_first("vxlan-vni"):
# to validate the l3vxi interface we need to see the vrf attribute
if ifaceobj.get_attr_value_first("vrf"):
ifaceobj.link_privflags |= ifaceLinkPrivFlags.L3VXI
else:
self.logger.warning("%s: l3vxi misconfiguration? missing `vrf` attribute" % ifaceobj.name)
def _set_global_local_ip(self, ifaceobj):
vxlan_local_tunnel_ip = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
if vxlan_local_tunnel_ip and not self._vxlan_local_tunnelip:
self._vxlan_local_tunnelip = vxlan_local_tunnel_ip
@staticmethod
def _is_vxlan_device(ifaceobj):
return ifaceobj.link_kind & ifaceLinkKind.VXLAN \
or ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN \
or ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI \
or ifaceobj.get_attr_value_first("vxlan-id") \
or ifaceobj.get_attr_value_first("vxlan-vni") \
or ifaceobj.get_attr_value_first("bridge-vlan-vni-map")
def __get_vlxan_purge_remotes(self, ifaceobj):
if not ifaceobj:
return self._vxlan_purge_remotes
purge_remotes = ifaceobj.get_attr_value_first('vxlan-purge-remotes')
if purge_remotes:
purge_remotes = utils.get_boolean_from_string(purge_remotes)
else:
purge_remotes = self._vxlan_purge_remotes
return purge_remotes
def get_vxlan_ttl_from_string(self, ttl_config):
ttl = 0
if ttl_config:
if ttl_config.lower() == "auto":
ttl = 0
else:
ttl = int(ttl_config)
return ttl
def get_vxlan_tos_from_string(self, tos_config):
if tos_config:
if tos_config.lower() == "inherit":
return 1
else:
return int(tos_config)
return None
def __config_vxlan_id(self, ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
"""
Get vxlan-id user config and check it's value before inserting it in our netlink dictionary
:param ifname:
:param ifaceobj:
:param vxlan_id_str:
:param user_request_vxlan_info_data:
:param cached_vxlan_ifla_info_data:
:return:
"""
try:
vxlan_id = int(vxlan_id_str)
cached_vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
if cached_vxlan_id and cached_vxlan_id != vxlan_id:
self.log_error(
"%s: Cannot change running vxlan id (%s): Operation not supported"
% (ifname, cached_vxlan_id),
ifaceobj
)
user_request_vxlan_info_data[Link.IFLA_VXLAN_ID] = vxlan_id
except ValueError:
self.log_error("%s: invalid vxlan-id '%s'" % (ifname, vxlan_id_str), ifaceobj)
def __get_vxlan_ageing_int(self, ifname, ifaceobj, link_exists):
"""
Get vxlan-ageing user config or via policy, return integer value, None or raise on error
:param ifname:
:param ifaceobj:
:param link_exists:
:return:
"""
vxlan_ageing_str = ifaceobj.get_attr_value_first("vxlan-ageing")
try:
if vxlan_ageing_str:
return int(vxlan_ageing_str)
vxlan_ageing_str = policymanager.policymanager_api.get_attr_default(
module_name=self.__class__.__name__,
attr="vxlan-ageing"
)
if not vxlan_ageing_str and link_exists:
# if link doesn't exist we let the kernel define ageing
vxlan_ageing_str = self.get_attr_default_value("vxlan-ageing")
if vxlan_ageing_str:
return int(vxlan_ageing_str)
except Exception:
self.log_error("%s: invalid vxlan-ageing '%s'" % (ifname, vxlan_ageing_str), ifaceobj)
def __config_vxlan_ageing(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
"""
Check user config vxlan-ageing and insert it in our netlink dictionary if needed
"""
vxlan_ageing = self.__get_vxlan_ageing_int(ifname, ifaceobj, link_exists)
if not vxlan_ageing or (link_exists and vxlan_ageing == cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_AGEING)):
return
self.logger.info("%s: set vxlan-ageing %s" % (ifname, vxlan_ageing))
user_request_vxlan_info_data[Link.IFLA_VXLAN_AGEING] = vxlan_ageing
def __config_vxlan_port(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
"""
Check vxlan-port user config, validate the integer value and insert it in the netlink dictionary if needed
:param ifname:
:param ifaceobj:
:param link_exists:
:param user_request_vxlan_info_data:
:param cached_vxlan_ifla_info_data:
:return:
"""
vxlan_port_str = ifaceobj.get_attr_value_first("vxlan-port")
try:
if not vxlan_port_str:
vxlan_port_str = policymanager.policymanager_api.get_attr_default(
module_name=self.__class__.__name__,
attr="vxlan-port"
)
try:
vxlan_port = int(vxlan_port_str)
except TypeError:
# TypeError means vxlan_port was None
# ie: not provided by the user or the policy
vxlan_port = self.netlink.VXLAN_UDP_PORT
except ValueError as e:
self.logger.warning(
"%s: vxlan-port: using default %s: invalid configured value %s"
% (ifname, self.netlink.VXLAN_UDP_PORT, str(e))
)
vxlan_port = self.netlink.VXLAN_UDP_PORT
cached_vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
if link_exists:
if vxlan_port != cached_vxlan_port:
self.logger.warning(
"%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s"
% (ifname, cached_vxlan_port, ifname, ifname)
)
return
self.logger.info("%s: set vxlan-port %s" % (ifname, vxlan_port))
user_request_vxlan_info_data[Link.IFLA_VXLAN_PORT] = vxlan_port
except Exception:
self.log_error("%s: invalid vxlan-port '%s'" % (ifname, vxlan_port_str), ifaceobj)
def __config_vxlan_tos(self, ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
"""
Get vxlan-tos from user config or policy, validate integer value and insert in netlink dict
:param ifname:
:param ifaceobj:
:param user_request_vxlan_info_data:
:param cached_vxlan_ifla_info_data:
:return:
"""
vxlan_tos_str = ifaceobj.get_attr_value_first("vxlan-tos")
try:
if vxlan_tos_str:
vxlan_tos = self.get_vxlan_tos_from_string(vxlan_tos_str)
else:
vxlan_tos = self.get_vxlan_tos_from_string(
policymanager.policymanager_api.get_attr_default(
module_name=self.__class__.__name__,
attr="vxlan-tos"
)
)
if not vxlan_tos_str:
return
cached_ifla_vxlan_tos = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_TOS)
if vxlan_tos != cached_ifla_vxlan_tos:
if cached_ifla_vxlan_tos is not None:
self.logger.info("%s: set vxlan-tos %s (cache %s)" % (ifname, vxlan_tos_str if vxlan_tos_str else vxlan_tos, cached_ifla_vxlan_tos))
else:
self.logger.info("%s: set vxlan-tos %s" % (ifname, vxlan_tos_str if vxlan_tos_str else vxlan_tos))
user_request_vxlan_info_data[Link.IFLA_VXLAN_TOS] = vxlan_tos
except Exception:
self.log_error("%s: invalid vxlan-tos '%s'" % (ifname, vxlan_tos_str), ifaceobj)
def __config_vxlan_ttl(self, ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
"""
Get vxlan-ttl from user config or policy, validate integer value and insert in netlink dict
:param ifname:
:param ifaceobj:
:param user_request_vxlan_info_data:
:param cached_vxlan_ifla_info_data:
:return:
"""
vxlan_ttl_str = ifaceobj.get_attr_value_first("vxlan-ttl")
try:
if vxlan_ttl_str:
vxlan_ttl = self.get_vxlan_ttl_from_string(vxlan_ttl_str)
else:
vxlan_ttl = self.get_vxlan_ttl_from_string(
policymanager.policymanager_api.get_attr_default(
module_name=self.__class__.__name__,
attr="vxlan-ttl"
)
)
cached_ifla_vxlan_ttl = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_TTL)
if vxlan_ttl != cached_ifla_vxlan_ttl:
if cached_ifla_vxlan_ttl is not None:
self.logger.info("%s: set vxlan-ttl %s (cache %s)" % (ifname, vxlan_ttl_str if vxlan_ttl_str else vxlan_ttl, cached_ifla_vxlan_ttl))
else:
self.logger.info("%s: set vxlan-ttl %s" % (ifname, vxlan_ttl_str if vxlan_ttl_str else vxlan_ttl))
user_request_vxlan_info_data[Link.IFLA_VXLAN_TTL] = vxlan_ttl
return vxlan_ttl
except Exception:
self.log_error("%s: invalid vxlan-ttl '%s'" % (ifname, vxlan_ttl_str), ifaceobj)
def is_vxlan_on_a_clag_bridge(self, ifaceobj) -> bool:
return bool(ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT and self._clagd_vxlan_anycast_ip and self.is_process_running('clagd'))
def __config_vxlan_local_tunnelip(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
"""
Get vxlan-local-tunnelip user config or policy, validate ip address format and insert in netlink dict
:param ifname:
:param ifaceobj:
:param link_exists:
:param user_request_vxlan_info_data:
:param cached_vxlan_ifla_info_data:
:return:
"""
local = ifaceobj.get_attr_value_first("vxlan-local-tunnelip")
if not local and self._vxlan_local_tunnelip:
local = self._vxlan_local_tunnelip
if link_exists:
cached_ifla_vxlan_local = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
# on ifreload do not overwrite anycast_ip to individual ip
# if clagd has modified
if self._clagd_vxlan_anycast_ip and cached_ifla_vxlan_local:
anycastip = ipnetwork.IPNetwork(self._clagd_vxlan_anycast_ip)
if (
anycastip == cached_ifla_vxlan_local
# there's a change that the cache hasn't been updated in
# time to reflect the new anycast ip set by clagd, extra checks:
or self.is_vxlan_on_a_clag_bridge(ifaceobj)
):
local = cached_ifla_vxlan_local = anycastip
self.logger.info("%s: clagd-vxlan-anycast-ip (%s) inherited from loopback interface" % (ifname, local))
else:
cached_ifla_vxlan_local = None
if not local:
local = policymanager.policymanager_api.get_attr_default(
module_name=self.__class__.__name__,
attr="vxlan-local-tunnelip"
)
if local:
try:
local = ipnetwork.IPv4Address(local)
if local.initialized_with_prefixlen:
self.logger.warning("%s: vxlan-local-tunnelip %s: netmask ignored" % (ifname, local))
except Exception as e:
raise Exception("%s: invalid vxlan-local-tunnelip %s: %s" % (ifname, local, str(e)))
if local:
if local != cached_ifla_vxlan_local:
self.logger.info("%s: set vxlan-local-tunnelip %s" % (ifname, local))
user_request_vxlan_info_data[Link.IFLA_VXLAN_LOCAL] = local
# if both local-ip and anycast-ip are identical the function prints a warning
self.syntax_check_localip_anycastip_equal(ifname, local, self._clagd_vxlan_anycast_ip)
elif cached_ifla_vxlan_local:
self.logger.info("%s: removing vxlan-local-tunnelip (cache %s)" % (ifname, cached_ifla_vxlan_local))
user_request_vxlan_info_data[Link.IFLA_VXLAN_LOCAL] = None
return local
def __get_vxlan_attribute(self, ifaceobj, attr_name):
vxlan_attr_value = ifaceobj.get_attr_value_first(attr_name)
if not vxlan_attr_value:
vxlan_attr_value = policymanager.policymanager_api.get_attr_default(
module_name=self.__class__.__name__,
attr=attr_name
)
return vxlan_attr_value
def __syntax_check_vxlan_mcast_vni(self, ifaceobj, m, vni):
try:
int(vni)
except ValueError:
self.log_error('%s: vxlan-mcastgrp-map "%s" vni format is invalid' % (ifaceobj.name, m))
def __syntax_check_vxlan_mcast_vni_range(self, ifaceobj, m, vni_range):
if len(vni_range) != 2:
self.log_error('%s: vxlan-mcastgrp-map "%s" vni range format is invalid' % (ifaceobj.name, m))
for vni in vni_range:
self.__syntax_check_vxlan_mcast_vni(ifaceobj, m, vni)
if int(vni_range[0]) >= int(vni_range[1]):
self.log_error('%s: vxlan-mcastgrp-map "%s" vni range is invalid' % (ifaceobj.name, m))
def __syntax_check_vxlan_mcast_grp(self, ifaceobj, m, grp):
try:
ip = IPv4Address(grp)
except AddressValueError:
self.log_error('%s: vxlan-mcastgrp-map "%s" group format is invalid' % (ifaceobj.name, m))
if not ip.is_multicast:
self.log_error('%s: vxlan-mcastgrp-map "%s" group is not multicast' % (ifaceobj.name, m))
def __syntax_check_vxlan_mcast_grp_range(self, ifaceobj, m, grp_range):
if len(grp_range) != 2:
self.log_error('%s: vxlan-mcastgrp-map "%s" group format is invalid' % (ifaceobj.name, m))
for grp in grp_range:
self.__syntax_check_vxlan_mcast_grp(ifaceobj, m, grp)
if int(IPv4Address(grp_range[0])) >= int(IPv4Address(grp_range[1])):
self.log_error('%s: vxlan-mcastgrp-map "%s" group range is invalid' % (ifaceobj.name, m))
def __syntax_check_vxlan_mcast_network(self, ifaceobj, m, network, len_vni):
try:
ip = IPv4Network(network)
ip[0]
ip[len_vni - 1]
except IndexError:
self.log_error('%s: vxlan-mcastgrp-map "%s" network range is insufficient' % (ifaceobj.name, m))
except AddressValueError:
self.log_error('%s: vxlan-mcastgrp-map "%s" network format is invalid' % (ifaceobj.name, m))
if not ip.is_multicast:
self.log_error('%s: vxlan-mcastgrp-map "%s" network is not multicast' % (ifaceobj.name, m))
def __get_vxlan_mcastgrp_map(self, ifaceobj):
maps = ifaceobj.get_attr_value('vxlan-mcastgrp-map')
if not maps:
maps = policymanager.policymanager_api.get_attr_default(
module_name=self.__class__.__name__,
attr='vxlan-mcastgrp-map'
)
return maps
parsed_maps = {}
for m_line in maps:
# Cover single-line multi-entry case
map = m_line.split()
for m in map:
m_parts = m.split('=')
if len(m_parts) != 2:
self.log_error('%s: vxlan-mcastgrp-map %s format is invalid' % (ifaceobj.name, m))
vni = m_parts[0]
grp = m_parts[1]
_range = "-"
_network = "/"
# One to one mapping case
if _range not in vni and _range not in grp:
self.__syntax_check_vxlan_mcast_vni(ifaceobj, m, vni)
self.__syntax_check_vxlan_mcast_grp(ifaceobj, m, grp)
if int(vni) not in parsed_maps:
parsed_maps[int(vni)] = IPv4Address(grp)
else:
self.log_warn('%s: vxlan-mcastgrp-map %s vni %s duplicate' % (ifaceobj.name, vni, m))
# Many VNI case
if _range in vni:
v_parts = vni.split(_range)
self.__syntax_check_vxlan_mcast_vni_range(ifaceobj, m, v_parts)
vnis = list(range(int(v_parts[0]), int(v_parts[1]) + 1))
if _range not in grp and _network not in grp:
self.__syntax_check_vxlan_mcast_grp(ifaceobj, m, grp)
for i in vnis:
if i not in parsed_maps:
parsed_maps[i] = IPv4Address(grp)
else:
self.log_warn('%s: vxlan-mcastgrp-map %s vni %s duplicate' % (ifaceobj.name, vni, m))
else:
if _network in grp:
self.__syntax_check_vxlan_mcast_network(ifaceobj, m, grp, len(vnis))
network = IPv4Network(grp)
g_parts = [network[0], network[len(vnis) - 1]]
else:
g_parts = grp.split(_range)
self.__syntax_check_vxlan_mcast_grp_range(ifaceobj, m, g_parts)
grp_range = list(range(int(IPv4Address(g_parts[0])), int(IPv4Address(g_parts[1])) + 1))
if len(grp_range) != len(vnis):
self.log_error('%s: vxlan-mcastgrp-map "%s" range lengths do not match.'
% (ifaceobj.name, m))
for v, g in zip(vnis, grp_range):
if v not in parsed_maps:
parsed_maps[v] = IPv4Address(g)
else:
self.log_warn('%s: vxlan-mcastgrp-map %s vni %s duplicate' % (ifaceobj.name, v, m))
return parsed_maps
def __config_vxlan_group(self, ifname, ifaceobj, link_exists, mcast_grp, group, physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
"""
vxlan-mcastgrp and vxlan-svcnodeip are mutually exclusive
this function validates ip format for both attribute and tries to understand
what the user really want (remote or group option).
:param ifname:
:param ifaceobj:
:param mcast_grp:
:param group:
:param physdev:
:param user_request_vxlan_info_data:
:param cached_vxlan_ifla_info_data:
:return:
"""
if mcast_grp and group:
self.log_error("%s: both group (vxlan-mcastgrp %s) and "
"remote (vxlan-svcnodeip %s) cannot be specified"
% (ifname, mcast_grp, group), ifaceobj)
attribute_name = "vxlan-svcnodeip"
multicast_group_change = False
if group:
try:
group = ipnetwork.IPv4Address(group)
if group.initialized_with_prefixlen:
self.logger.warning("%s: vxlan-svcnodeip %s: netmask ignored" % (ifname, group))
except Exception as e:
raise Exception("%s: invalid vxlan-svcnodeip %s: %s" % (ifname, group, str(e)))
if group.ip.is_multicast:
self.logger.warning("%s: vxlan-svcnodeip %s: invalid group address, "
"for multicast IP please use attribute \"vxlan-mcastgrp\"" % (ifname, group))
# if svcnodeip is used instead of mcastgrp we warn the user
# if mcast_grp is not provided by the user we can instead
# use the svcnodeip value
if not physdev:
self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp) requires 'vxlan-physdev' to be specified" % (ifname))
elif mcast_grp:
try:
mcast_grp = ipnetwork.IPv4Address(mcast_grp)
if mcast_grp.initialized_with_prefixlen:
self.logger.warning("%s: vxlan-mcastgrp %s: netmask ignored" % (ifname, mcast_grp))
except Exception as e:
raise Exception("%s: invalid vxlan-mcastgrp %s: %s" % (ifname, mcast_grp, str(e)))
if not mcast_grp.ip.is_multicast:
self.logger.warning("%s: vxlan-mcastgrp %s: invalid group address, "
"for non-multicast IP please use attribute \"vxlan-svcnodeip\""
% (ifname, mcast_grp))
# if mcastgrp is specified with a non-multicast address
# we warn the user. If the svcnodeip wasn't specified by
# the user we can use the mcastgrp value as svcnodeip
if not group:
group = mcast_grp
mcast_grp = None
else:
attribute_name = "vxlan-mcastgrp"
if mcast_grp:
group = mcast_grp
if not physdev:
self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp) requires 'vxlan-physdev' to be specified" % (ifname))
cached_ifla_vxlan_group = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
if group != cached_ifla_vxlan_group:
if not group:
group = ipnetwork.IPNetwork("0.0.0.0")
attribute_name = "vxlan-svcnodeip/vxlan-mcastgrp"
self.logger.info("%s: set %s %s" % (ifname, attribute_name, group))
user_request_vxlan_info_data[Link.IFLA_VXLAN_GROUP] = group
# if the mcastgrp address is changed we need to signal this to the upper function
# in this case vxlan needs to be down before applying changes then up'd
multicast_group_change = True
if link_exists:
if cached_ifla_vxlan_group:
self.logger.info(
"%s: vxlan-mcastgrp configuration changed (cache %s): flapping vxlan device required"
% (ifname, cached_ifla_vxlan_group)
)
else:
self.logger.info(
"%s: vxlan-mcastgrp configuration changed: flapping vxlan device required" % ifname
)
return group, multicast_group_change
def __config_vxlan_group6(self, ifname, ifaceobj, link_exists, mcast_grp, group, physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
"""
vxlan-mcastgrp and vxlan-svcnodeip are mutually exclusive
this function validates ip format for both attribute and tries to understand
what the user really want (remote or group option).
:param ifname:
:param ifaceobj:
:param mcast_grp:
:param group:
:param physdev:
:param user_request_vxlan_info_data:
:param cached_vxlan_ifla_info_data:
:return:
"""
if mcast_grp and group:
self.log_error("%s: both group (vxlan-mcastgrp6 %s) and "
"remote (vxlan-svcnodeip6 %s) cannot be specified"
% (ifname, mcast_grp, group), ifaceobj)
attribute_name = "vxlan-svcnodeip6"
multicast_group_change = False
if group:
try:
group = ipnetwork.IPv6Address(group)
except Exception:
try:
group_ip = ipnetwork.IPv6Network(group).ip
self.logger.warning("%s: vxlan-svcnodeip6 %s: netmask ignored" % (ifname, group))
group = group_ip
except Exception:
raise Exception("%s: invalid vxlan-svcnodeip6 %s: must be in ipv4 format" % (ifname, group))
if group.is_multicast:
self.logger.warning("%s: vxlan-svcnodeip6 %s: invalid group address, "
"for multicast IP please use attribute \"vxlan-mcastgrp6\"" % (ifname, group))
# if svcnodeip is used instead of mcastgrp we warn the user
# if mcast_grp is not provided by the user we can instead
# use the svcnodeip value
if not physdev:
self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp6) requires 'vxlan-physdev' to be specified" % (ifname))
elif mcast_grp:
try:
mcast_grp = ipnetwork.IPv6Address(mcast_grp)
except Exception:
try:
group_ip = ipnetwork.IPv6Network(mcast_grp).ip
self.logger.warning("%s: vxlan-mcastgrp6 %s: netmask ignored" % (ifname, mcast_grp))
mcast_grp = group_ip
except Exception:
raise Exception("%s: invalid vxlan-mcastgrp6 %s: must be in ipv4 format" % (ifname, mcast_grp))
if not mcast_grp.is_multicast:
self.logger.warning("%s: vxlan-mcastgrp6 %s: invalid group address, "
"for non-multicast IP please use attribute \"vxlan-svcnodeip6\""
% (ifname, mcast_grp))
# if mcastgrp is specified with a non-multicast address
# we warn the user. If the svcnodeip wasn't specified by
# the user we can use the mcastgrp value as svcnodeip
if not group:
group = mcast_grp
mcast_grp = None
else:
attribute_name = "vxlan-mcastgrp6"
if mcast_grp:
group = mcast_grp
if not physdev:
self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp6) requires 'vxlan-physdev' to be specified" % (ifname))
cached_ifla_vxlan_group = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP6)
if group != cached_ifla_vxlan_group:
if not group:
group = ipnetwork.IPNetwork("::0", family=6)
attribute_name = "vxlan-svcnodeip6/vxlan-mcastgrp6"
self.logger.info("%s: set %s %s" % (ifname, attribute_name, group))
user_request_vxlan_info_data[Link.IFLA_VXLAN_GROUP6] = group
# if the mcastgrp address is changed we need to signal this to the upper function
# in this case vxlan needs to be down before applying changes then up'd
multicast_group_change = True
if link_exists:
if cached_ifla_vxlan_group:
self.logger.info(
"%s: vxlan-mcastgrp6 configuration changed (cache %s): flapping vxlan device required"
% (ifname, cached_ifla_vxlan_group)
)
else:
self.logger.info(
"%s: vxlan-mcastgrp6 configuration changed: flapping vxlan device required" % ifname
)
return group, multicast_group_change
def __config_vxlan_learning(self, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
if not link_exists or not ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT:
vxlan_learning = ifaceobj.get_attr_value_first('vxlan-learning')
if not vxlan_learning:
vxlan_learning = self.get_attr_default_value('vxlan-learning')
vxlan_learning = utils.get_boolean_from_string(vxlan_learning)
else:
vxlan_learning = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LEARNING)
if vxlan_learning != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LEARNING):
self.logger.info("%s: set vxlan-learning %s" % (ifaceobj.name, "on" if vxlan_learning else "off"))
user_request_vxlan_info_data[Link.IFLA_VXLAN_LEARNING] = vxlan_learning
def __config_vxlan_udp_csum(self, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
vxlan_udp_csum = ifaceobj.get_attr_value_first('vxlan-udp-csum')
if not vxlan_udp_csum:
vxlan_udp_csum = policymanager.policymanager_api.get_attr_default(
module_name=self.__class__.__name__,
attr="vxlan-udp-csum"
)
if not vxlan_udp_csum and not link_exists:
return
if not vxlan_udp_csum:
vxlan_udp_csum = self.get_attr_default_value('vxlan-udp-csum')
if vxlan_udp_csum:
vxlan_udp_csum = utils.get_boolean_from_string(vxlan_udp_csum)
else:
return
if vxlan_udp_csum != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_UDP_CSUM):
self.logger.info("%s: set vxlan-udp-csum %s" % (ifaceobj.name, "on" if vxlan_udp_csum else "off"))
user_request_vxlan_info_data[Link.IFLA_VXLAN_UDP_CSUM] = vxlan_udp_csum
def __get_vxlan_physdev(self, ifaceobj, mcastgrp, mcastgrp_map):
"""
vxlan-physdev wrapper, special handling is required for mcastgrp is provided
the vxlan needs to use a dummy or real device for tunnel endpoint communication
This wrapper will get the physdev from user config or policy. IF the device
doesnt exists we create a dummy device.
:param ifaceobj:
:param mcastgrp:
:return physdev:
"""
physdev = ifaceobj.get_attr_value_first("vxlan-physdev")
# if the user provided a physdev we need to honor his config
# or if mcastgrp wasn't specified we don't need to go further
if physdev or (not mcastgrp and not mcastgrp_map):
return physdev
physdev = self.vxlan_physdev_mcast
if not self.cache.link_exists(physdev):
if mcastgrp_map:
self.logger.info("%s: needs a dummy device (%s) to use for "
"multicast termination (vxlan-mcastgrp-map %s)"
% (ifaceobj.name, physdev, mcastgrp))
else:
self.logger.info("%s: needs a dummy device (%s) to use for "
"multicast termination (vxlan-mcastgrp %s)"
% (ifaceobj.name, physdev, mcastgrp))
self.netlink.link_add_with_attributes(ifname=physdev, kind="dummy", ifla={Link.IFLA_MTU: 16000, Link.IFLA_LINKMODE: 1})
self.netlink.link_up(physdev)
return physdev
def __config_vxlan_physdev(self, link_exists, ifaceobj, vxlan_physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
if vxlan_physdev:
try:
vxlan_physdev_ifindex = self.cache.get_ifindex(vxlan_physdev)
except NetlinkCacheIfnameNotFoundError:
try:
vxlan_physdev_ifindex = int(self.sysfs.read_file_oneline("/sys/class/net/%s/ifindex" % vxlan_physdev))
except Exception:
self.logger.error("%s: physdev %s doesn't exists" % (ifaceobj.name, vxlan_physdev))
return
if vxlan_physdev_ifindex != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LINK):
self.logger.info("%s: set vxlan-physdev %s" % (ifaceobj.name, vxlan_physdev))
user_request_vxlan_info_data[Link.IFLA_VXLAN_LINK] = vxlan_physdev_ifindex
# if the vxlan exists we need to return True, meaning that the vxlan
# needs to be flapped because we detected a vxlan-physdev change
if link_exists:
self.logger.info("%s: vxlan-physdev configuration changed: flapping vxlan device required" % ifaceobj.name)
return True
return False
def __get_vxlan_remote_ip_map(self, ifaceobj):
attr_name = "vxlan-remoteip-map"
maps = ifaceobj.get_attr_value(attr_name)
if not maps:
maps = policymanager.policymanager_api.get_attr_default(
module_name=self.__class__.__name__,
attr=attr_name
)
if not maps:
return {}
parsed_maps = {}
for m_line in maps:
# Cover single-line multi-entry case
map = m_line.split()
for m in map:
m_parts = m.split('=')
if len(m_parts) != 2:
self.log_error('%s: %s %s format is invalid' % (ifaceobj.name, attr_name, m))
vnis = m_parts[0]
_range = "-"
remote_ips = []
for config_remote_ip in m_parts[1].split(","):
if _range in config_remote_ip:
ip_range = config_remote_ip.split("-")
try:
start = ip_address(ip_range[0])
end = ip_address(ip_range[1])
except Exception as e:
self.log_error("%s: %s: invalid ip range '%s': %s" % (ifaceobj.name, attr_name, config_remote_ip, e), ifaceobj)
return
remote_ips.extend([ipnetwork.ip_address(i) for i in range(int(start), int(end) + 1)])
else:
remote_ips.append(ipnetwork.ip_address(config_remote_ip))
# vxlan-remoteip-map 42,84,1000-1005=10.0.0.1,10.0.0.42-45,222.0.0.1-5
# higher priority is the comma
for vni in utils.ranges_to_ints(vnis.split(",")) or []:
parsed_maps.setdefault(vni, []).extend(remote_ips)
return parsed_maps
def single_vxlan_device_vni_filter(self, ifaceobj, vxlan_mcast_grp):
vnis = []
vnisd = {}
for vlan_vni_map in ifaceobj.get_attr_value("bridge-vlan-vni-map"):
try:
(vls, vis) = utils.get_vlan_vnis_in_map(vlan_vni_map)
for v in utils.ranges_to_ints(vis):
vnisd[v] = None
except Exception as e:
self.logger.error("%s: %s (%s)" %(ifaceobj.name, vlan_vni_map, str(e)))
return
if vxlan_mcast_grp:
try:
for v, g in vxlan_mcast_grp.items():
if v not in vnisd.keys():
self.logger.error("%s: group %s configured for a vni (%s) not specified in vlan vni map"
%(ifaceobj.name, g, v))
return
vnisd[v] = str(g)
except Exception as e:
self.logger.error("%s: %s (%s)" %(ifaceobj.name, vlan_vni_map, str(e)))
return
vnis_int = utils.ranges_to_ints(vnis)
self.iproute2.bridge_link_update_vni_filter(ifaceobj.name, vnisd)
def check_and_raise_svd_tvd_errors(self, ifaceobj):
err = self.svd_tvd_errors.get(ifaceobj.name)
if err:
self.log_error(err, ifaceobj)
def __get_vxlan_vni_list(self, ifaceobj, string=True):
vxlan_vni_str = self.__get_vxlan_attribute(ifaceobj, "vxlan-vni")
if vxlan_vni_str:
# validate range but return string to be used in bridge vni add cmd
vxlan_vni_range = utils.ranges_to_ints(vxlan_vni_str.split())
return vxlan_vni_str if string else vxlan_vni_range
return None
def _up(self, ifaceobj):
self.check_and_raise_svd_tvd_errors(ifaceobj)
vxlan_id_str = ifaceobj.get_attr_value_first("vxlan-id")
if not ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN and not ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI and not vxlan_id_str:
self.logger.warning("%s: missing vxlan-id attribute on vxlan device" % ifaceobj.name)
return
ifname = ifaceobj.name
link_exists = self.cache.link_exists(ifname)
if link_exists:
# if link already exists make sure this is a vxlan
device_link_kind = self.cache.get_link_kind(ifname)
if device_link_kind != "vxlan":
self.logger.error(
"%s: device already exists and is not a vxlan (type %s)"
% (ifname, device_link_kind)
)
ifaceobj.set_status(ifaceStatus.ERROR)
return
# get vxlan running attributes
cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
else:
cached_vxlan_ifla_info_data = {}
user_request_vxlan_info_data = {}
if vxlan_id_str:
# for single vxlan device we don't have a vxlan-id
self.__config_vxlan_id(ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
self.__config_vxlan_learning(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
self.__config_vxlan_ageing(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
self.__config_vxlan_port(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
vxlan_ttl = self.__config_vxlan_ttl(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
self.__config_vxlan_tos(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
self.__config_vxlan_udp_csum(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
local = self.__config_vxlan_local_tunnelip(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
vxlan_vni = self.__get_vxlan_vni_list(ifaceobj)
vxlan_mcast_grp = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp")
vxlan_svcnodeip = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip")
vxlan_mcast_grp6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp6")
vxlan_svcnodeip6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip6")
vxlan_mcast_grp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
vxlan_physdev = self.__get_vxlan_physdev(ifaceobj, vxlan_mcast_grp, vxlan_mcast_grp_map)
vxlan_vnifilter = self.__get_vxlan_attribute(ifaceobj, "vxlan-vnifilter")
vxlan_physdev_changed = self.__config_vxlan_physdev(
link_exists,
ifaceobj,
vxlan_physdev,
user_request_vxlan_info_data,
cached_vxlan_ifla_info_data
)
group, multicast_group_changed = self.__config_vxlan_group(
ifname,
ifaceobj,
link_exists,
vxlan_mcast_grp,
vxlan_svcnodeip,
vxlan_physdev,
user_request_vxlan_info_data,
cached_vxlan_ifla_info_data
)
group6, multicast_group_changed6 = self.__config_vxlan_group6(
ifname,
ifaceobj,
link_exists,
vxlan_mcast_grp6,
vxlan_svcnodeip6,
vxlan_physdev,
user_request_vxlan_info_data,
cached_vxlan_ifla_info_data
)
flap_vxlan_device = link_exists and (multicast_group_changed or multicast_group_changed6 or vxlan_physdev_changed)
if user_request_vxlan_info_data:
if link_exists and len(user_request_vxlan_info_data) == 1 and Link.IFLA_VXLAN_ID in user_request_vxlan_info_data:
# if the vxlan already exists it's already cached
# user_request_vxlan_info_data always contains at least one
# element: vxlan-id
self.logger.info('%s: vxlan already exists - no change detected' % ifname)
else:
if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
self.iproute2.link_add_single_vxlan(
link_exists,
ifname,
local.ip if local else None,
group.ip if group else None,
vxlan_physdev,
user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT),
vxlan_vnifilter,
vxlan_ttl
)
elif ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
self.iproute2.link_add_l3vxi(
link_exists,
ifname,
local.ip if local else None,
group.ip if group else None,
vxlan_physdev,
user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT),
vxlan_ttl
)
try:
self.iproute2.bridge_vni_add(ifname, vxlan_vni)
except Exception as e:
self.logger.warning("%s: l3 vxlan vni failure: %s" % (ifname, e))
else:
try:
if flap_vxlan_device:
self.netlink.link_down_force(ifname)
self.netlink.link_add_vxlan_with_info_data(ifname, user_request_vxlan_info_data)
if flap_vxlan_device:
self.netlink.link_up_force(ifname)
except Exception as e:
if link_exists:
self.log_error("%s: applying vxlan change failed: %s" % (ifname, str(e)), ifaceobj)
else:
self.log_error("%s: vxlan creation failed: %s" % (ifname, str(e)), ifaceobj)
return
if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
if vxlan_vnifilter and utils.get_boolean_from_string(vxlan_vnifilter):
self.single_vxlan_device_vni_filter(ifaceobj, vxlan_mcast_grp_map)
vxlan_purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
remoteips = ifaceobj.get_attr_value('vxlan-remoteip')
if remoteips:
try:
for remoteip in remoteips:
ipnetwork.IPv4Address(remoteip)
except Exception as e:
self.log_error('%s: vxlan-remoteip: %s' % (ifaceobj.name, str(e)))
if vxlan_purge_remotes or remoteips:
# figure out the diff for remotes and do the bridge fdb updates
# only if provisioned by user and not by an vxlan external
# controller.
local_str = str(local)
if local_str and remoteips and local_str in remoteips:
remoteips.remove(local_str)
peers = self.iproute2.get_vxlan_peers(ifaceobj.name, str(group.ip) if group else None)
cur_peers = set(peers)
if remoteips:
new_peers = set(remoteips)
del_list = cur_peers.difference(new_peers)
add_list = new_peers.difference(cur_peers)
else:
del_list = cur_peers
add_list = []
for addr in del_list:
try:
self.iproute2.bridge_fdb_del(
ifaceobj.name,
"00:00:00:00:00:00",
None, True, addr
)
except Exception:
pass
for addr in add_list:
try:
self.iproute2.bridge_fdb_append(
ifaceobj.name,
"00:00:00:00:00:00",
None, True, addr
)
except Exception:
pass
self.vxlan_remote_ip_map(ifaceobj, vxlan_mcast_grp_map)
def vxlan_remote_ip_map(self, ifaceobj, vxlan_mcast_grp_map):
# get user configured remote ip map
vxlan_remote_ip_map = self.__get_vxlan_remote_ip_map(ifaceobj) or {}
# if we have an older config we need to see what needs to be removed
# and not check the running state as FRR or other component can add fdb entries
old_vxlan_remote_ip_map = {}
for old_ifaceobj in statemanager.get_ifaceobjs(ifaceobj.name) or []:
old_vxlan_remote_ip_map = {**old_vxlan_remote_ip_map, **self.__get_vxlan_remote_ip_map(old_ifaceobj)}
# go through the user config and add new entries while removing existing entries from 'old_vxlan_remote_ip_map'
for vni, ips in vxlan_remote_ip_map.items():
for ip in ips:
if ip not in old_vxlan_remote_ip_map.get(vni, []):
self.iproute2.bridge_fdb_append(ifaceobj.name, "00:00:00:00:00:00", remote=ip, src_vni=vni)
else:
old_vxlan_remote_ip_map.get(vni, []).remove(ip)
# in old_vxlan_remote_ip_map we have the delta between user config and running config. We should delete those
# extra fdb entries. First we need to make sure that those are not added by vxlan-mcastgrp-map
if old_vxlan_remote_ip_map:
for vni, ip in (vxlan_mcast_grp_map or {}).items():
try:
old_vxlan_remote_ip_map[vni].remove(ip)
except:
pass
for vni, ips in old_vxlan_remote_ip_map.items():
for ip in ips:
try:
self.iproute2.bridge_fdb_del_raw(ifaceobj.name, "00:00:00:00:00:00 dst %s src_vni %s" % (ip, vni))
except:
pass
@staticmethod
def get_vxlan_fdb_src_vni(vxlan_mcast_grp_map):
fdbs = []
if vxlan_mcast_grp_map:
for src_vni, dst_ip in vxlan_mcast_grp_map.items():
fdbs.append(("00:00:00:00:00:00", src_vni, dst_ip))
return fdbs
@staticmethod
def get_svd_running_fdb(ifname):
vxlan_fdb_data = utils.exec_command("bridge fdb show dev %s" % ifname)
current_fdb = []
if vxlan_fdb_data:
# each entry should look like the following:
# 00:00:00:00:00:00 dst 239.1.1.100 src_vni 1000 self permanent
for entry in [line for line in vxlan_fdb_data.strip().split("\n") if "src_vni" in line and "00:00:00:00:00:00" in line]:
mac, _, dst, _, src_vni = entry.split()[0:5]
current_fdb.append((mac, src_vni, dst))
return current_fdb
def single_vxlan_device_mcast_grp_map_fdb(self, ifaceobj, ifname, vxlan_mcast_grp_map):
# in this piece of code we won't be checking the running state of the fdb table
# dumping all fdb entries would cause scalability issues in certain cases.
# pulling old mcastgrp-map configuration
old_user_config_fdb = []
for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
old_user_config_fdb += self.get_vxlan_fdb_src_vni(self.__get_vxlan_mcastgrp_map(old_ifaceobj))
# new user configuration
user_config_fdb = self.get_vxlan_fdb_src_vni(vxlan_mcast_grp_map)
# compare old and new config to know if we should remove any stale fdb entries.
fdb_entries_to_remove = set(old_user_config_fdb) - set(user_config_fdb)
if fdb_entries_to_remove:
for mac, src_vni, dst_ip in fdb_entries_to_remove:
try:
self.iproute2.bridge_fdb_del_src_vni(ifname, mac, src_vni)
except Exception as e:
if "no such file or directory" not in str(e).lower():
self.logger.warning("%s: removing stale fdb entries failed: %s" % (ifname, str(e)))
if not user_config_fdb:
# if vxlan-mcastgrp-map wasn't configure return
return
for mac, src_vni, dst_ip in user_config_fdb:
try:
self.iproute2.bridge_fdb_add_src_vni(ifname, src_vni, dst_ip)
except Exception as e:
if "file exists" not in str(e).lower():
ifaceobj.set_status(ifaceStatus.ERROR)
self.log_error(
"%s: vxlan-mcastgrp-map: %s=%s: %s"
% (ifname, src_vni, dst_ip, str(e)), raise_error=False
)
def single_vxlan_device_mcast_grp_map_vnifilter(self, ifaceobj, ifname, vxlan_mcast_grp_map):
# in this piece of code we won't be checking the running state of the fdb table
# dumping all fdb entries would cause scalability issues in certain cases.
# pulling old mcastgrp-map configuration
old_user_config_fdb = []
for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
old_user_config_fdb += self.get_vxlan_fdb_src_vni(self.__get_vxlan_mcastgrp_map(old_ifaceobj))
# new user configuration
user_config_fdb = self.get_vxlan_fdb_src_vni(vxlan_mcast_grp_map)
# compare old and new config to know if we should remove any stale fdb entries.
fdb_entries_to_remove = set(old_user_config_fdb) - set(user_config_fdb)
self.logger.info(old_user_config_fdb)
self.logger.info(user_config_fdb)
self.logger.info(fdb_entries_to_remove)
if fdb_entries_to_remove:
for mac, src_vni, dst_ip in fdb_entries_to_remove:
try:
self.iproute2.bridge_fdb_del_src_vni(ifname, mac, src_vni)
except Exception as e:
if "no such file or directory" not in str(e).lower():
self.logger.warning("%s: removing stale fdb entries failed: %s" % (ifname, str(e)))
if not user_config_fdb:
# if vxlan-mcastgrp-map wasn't configure return
return
for mac, src_vni, dst_ip in user_config_fdb:
try:
self.iproute2.bridge_fdb_add_src_vni(ifname, src_vni, dst_ip)
except Exception as e:
if "file exists" not in str(e).lower():
ifaceobj.set_status(ifaceStatus.ERROR)
self.log_error(
"%s: vxlan-mcastgrp-map: %s=%s: %s"
% (ifname, src_vni, dst_ip, str(e)), raise_error=False
)
def _down(self, ifaceobj):
try:
self.netlink.link_del(ifaceobj.name)
except Exception as e:
self.log_warn(str(e))
@staticmethod
def _query_check_n_update(ifaceobj, ifaceobjcurr, attrname, attrval, running_attrval):
if not ifaceobj.get_attr_value_first(attrname):
return
if running_attrval and attrval == running_attrval:
ifaceobjcurr.update_config_with_status(attrname, attrval, 0)
else:
ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1)
@staticmethod
def _query_check_n_update_addresses(ifaceobjcurr, attrname, addresses, running_addresses):
if addresses:
for a in addresses:
if a in running_addresses:
ifaceobjcurr.update_config_with_status(attrname, a, 0)
else:
ifaceobjcurr.update_config_with_status(attrname, a, 1)
running_addresses = set(running_addresses).difference(
set(addresses))
[ifaceobjcurr.update_config_with_status(attrname, a, 1) for a in running_addresses]
def _query_check(self, ifaceobj, ifaceobjcurr):
ifname = ifaceobj.name
if not self.cache.link_exists(ifname):
return
cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
if not cached_vxlan_ifla_info_data:
ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, self.get_mod_attrs(), -1)
return
for vxlan_attr_str, vxlan_attr_nl, callable_type in (
('vxlan-id', Link.IFLA_VXLAN_ID, int),
('vxlan-ttl', Link.IFLA_VXLAN_TTL, int),
('vxlan-tos', Link.IFLA_VXLAN_TOS, int),
('vxlan-port', Link.IFLA_VXLAN_PORT, int),
('vxlan-ageing', Link.IFLA_VXLAN_AGEING, int),
('vxlan-mcastgrp', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
('vxlan-mcastgrp6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
('vxlan-svcnodeip', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
('vxlan-svcnodeip6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
('vxlan-physdev', Link.IFLA_VXLAN_LINK, lambda x: self.cache.get_ifindex(x)),
('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
):
vxlan_attr_value = ifaceobj.get_attr_value_first(vxlan_attr_str)
if not vxlan_attr_value:
continue
cached_vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
try:
vxlan_attr_value_nl = callable_type(vxlan_attr_value)
except Exception as e:
self.logger.warning('%s: %s: %s' % (ifname, vxlan_attr_str, str(e)))
ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
continue
if vxlan_attr_value_nl == cached_vxlan_attr_value:
ifaceobjcurr.update_config_with_status(vxlan_attr_str, vxlan_attr_value, 0)
else:
ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
#
# vxlan-local-tunnelip
#
running_attrval = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
if not attrval:
attrval = self._vxlan_local_tunnelip
# TODO: vxlan._vxlan_local_tunnelip should be a ipnetwork.IPNetwork obj
ifaceobj.update_config('vxlan-local-tunnelip', attrval)
if str(running_attrval) == self._clagd_vxlan_anycast_ip:
# if local ip is anycast_ip, then let query_check to go through
attrval = self._clagd_vxlan_anycast_ip
self._query_check_n_update(
ifaceobj,
ifaceobjcurr,
'vxlan-local-tunnelip',
str(attrval),
str(running_attrval.ip) if running_attrval else None
)
#
# vxlan-remoteip
#
purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'):
# If purge remotes or if vxlan-remoteip's are set
# in the config file, we are owners of the installed
# remote-ip's, lets check and report any remote ips we don't
# understand
cached_svcnode = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
self._query_check_n_update_addresses(
ifaceobjcurr,
'vxlan-remoteip',
ifaceobj.get_attr_value('vxlan-remoteip'),
self.iproute2.get_vxlan_peers(ifaceobj.name, str(cached_svcnode.ip) if cached_svcnode else None)
)
# not ideal but will work for now, l3vxi dev:
if ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
user_config_vni_list = set(self.__get_vxlan_vni_list(ifaceobj, string=False))
vxlan_vni_list = set()
for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
for vni_obj in obj.get("vnis", []):
start = vni_obj.get("vni")
end = vni_obj.get("vniEnd")
for vni in utils.ranges_to_ints(["%s-%s" % (start, end if end else start)]):
vxlan_vni_list.add(vni)
ifaceobjcurr.update_config_with_status(
"vxlan-vni",
" ".join(utils.compress_into_ranges(vxlan_vni_list)),
vxlan_vni_list != user_config_vni_list
)
#
# vxlan-mcastgrp-map & vxlan-remoteip-map
# fdb entries can be added by FRR, so we won't be checking the running
# state if there's no record of a user configuration in /e/n/i
user_mcastgrp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
user_remote_ip_map = self.__get_vxlan_remote_ip_map(ifaceobj)
if not user_mcastgrp_map and not user_remote_ip_map:
return
fdb_mcast = {}
fdb_remote = {}
if user_remote_ip_map:
for _, src_vni, dst in self.get_svd_running_fdb(ifname):
ip = ipnetwork.IPv4Address(dst)
if not ip.is_multicast:
fdb_remote.setdefault(int(src_vni), []).append(ip)
if user_mcastgrp_map:
for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
for vni in obj.get("vnis", []):
group = vni.get("group")
if not group:
continue
# we need to reconvert back to ipaddress.IPv4Address because
# the existing code uses this type of obj (namely: __get_vxlan_mcastgrp_map)
fdb_mcast[vni.get("vni")] = IPv4Address(group)
#
# vxlan-mcastgrp-map
#
if not user_mcastgrp_map and fdb_mcast:
ifaceobjcurr.update_config_with_status(
"vxlan-mcastgrp-map",
" ".join(["%s=%s" % (vni, ip) for vni, ip in fdb_mcast.items()]),
1
)
elif user_mcastgrp_map and not fdb_mcast:
ifaceobjcurr.update_config_with_status("vxlan-mcastgrp-map", "", 1)
elif user_mcastgrp_map or fdb_mcast:
ifaceobjcurr.update_config_with_status(
"vxlan-mcastgrp-map",
" ".join(["%s=%s" % (vni, ip) for vni, ip in fdb_mcast.items()]),
user_mcastgrp_map != fdb_mcast
)
#
# vxlan-remoteip-map
#
if not user_remote_ip_map and fdb_remote:
ifaceobjcurr.update_config_with_status(
"vxlan-remoteip-map",
" ".join(["%s=%s" % (vni, ",".join(map(str, ips))) for vni, ips in fdb_remote.items()]),
1
)
elif user_remote_ip_map and not fdb_remote:
ifaceobjcurr.update_config_with_status("vxlan-remoteip-map", "", 1)
elif user_remote_ip_map or fdb_remote:
if user_remote_ip_map == fdb_remote:
# display the user config with "pass"
for config in ifaceobj.get_attr_value("vxlan-remoteip-map"):
ifaceobjcurr.update_config_with_status(
"vxlan-remoteip-map",
config,
0
)
else:
# display current running state with ip ranges (but no vni ranges yet)
ifaceobjcurr.update_config_with_status(
"vxlan-remoteip-map",
" ".join(["%s=%s" % (vni, ",".join(utils.compress_into_ip_ranges(ips))) for vni, ips in fdb_remote.items()]),
1
)
def _query_running(self, ifaceobjrunning):
ifname = ifaceobjrunning.name
if not self.cache.link_exists(ifname):
return
if not self.cache.get_link_kind(ifname) == 'vxlan':
return
cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
if not cached_vxlan_ifla_info_data:
return
#
# vxlan-id
#
vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
if not vxlan_id:
# no vxlan id, meaning this not a vxlan
return
ifaceobjrunning.update_config('vxlan-id', str(vxlan_id))
#
# vxlan-port
#
vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
if vxlan_port:
ifaceobjrunning.update_config('vxlan-port', vxlan_port)
#
# vxlan-svcnode
#
vxlan_svcnode_value = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
if vxlan_svcnode_value:
vxlan_svcnode_value = str(vxlan_svcnode_value)
ifaceobjrunning.update_config('vxlan-svcnode', vxlan_svcnode_value)
#
# vxlan-remoteip
#
purge_remotes = self.__get_vlxan_purge_remotes(None)
if purge_remotes:
# if purge_remotes is on, it means we own the
# remote ips. Query them and add it to the running config
attrval = self.iproute2.get_vxlan_peers(ifname, vxlan_svcnode_value)
if attrval:
[ifaceobjrunning.update_config('vxlan-remoteip', a) for a in attrval]
#
# vxlan-link
# vxlan-ageing
# vxlan-learning
# vxlan-local-tunnelip
#
for vxlan_attr_name, vxlan_attr_nl, callable_netlink_value_to_string in (
('vxlan-physdev', Link.IFLA_VXLAN_LINK, self._get_ifname_for_ifindex),
('vxlan-ageing', Link.IFLA_VXLAN_AGEING, str),
('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda value: 'on' if value else 'off'),
('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda value: 'on' if value else 'off'),
('vxlan-local-tunnelip', Link.IFLA_VXLAN_LOCAL, str),
):
vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
if vxlan_attr_value is not None:
vxlan_attr_value_str = callable_netlink_value_to_string(vxlan_attr_value)
if vxlan_attr_value:
ifaceobjrunning.update_config(vxlan_attr_name, vxlan_attr_value_str)
def _get_ifname_for_ifindex(self, ifindex):
"""
we need this middle-man function to query the cache
cache.get_ifname can raise KeyError, we need to catch
it and return None
"""
try:
return self.cache.get_ifname(ifindex)
except KeyError:
return None
_run_ops = {
"pre-up": _up,
"post-down": _down,
"query-running": _query_running,
"query-checkcurr": _query_check
}
def get_ops(self):
return list(self._run_ops.keys())
def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
op_handler = self._run_ops.get(operation)
if not op_handler:
return
if not self._is_vxlan_device(ifaceobj):
return
if "query" not in operation:
if not self.vxlan_mcastgrp_ref \
and self.vxlan_physdev_mcast \
and self.cache.link_exists(self.vxlan_physdev_mcast):
self.netlink.link_del(self.vxlan_physdev_mcast)
self.reset()
if operation == 'query-checkcurr':
op_handler(self, ifaceobj, query_ifaceobj)
else:
op_handler(self, ifaceobj)