mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
new attribute: "bridge-vlan-vni-map": { "help": "Single vxlan support", "example": "bridge-vlan-vni-map 1000-1001=1000-1001", } example of config: auto bridge iface bridge bridge-vlan-aware yes bridge-ports vxlan0 swp1 bridge-stp on bridge-vids 1000-1001 bridge-pvid 1 auto vxlan0 iface vxlan0 vxlan-local-tunnelip 27.0.0.9 bridge-learning off # vlan 1000-1001 maps to vni 1000-1001 bridge-vlan-vni-map 1000-1001=1000-1001 Signed-off-by: Julien Fortin <julien@cumulusnetworks.com>
983 lines
40 KiB
Python
983 lines
40 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
|
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
|
#
|
|
|
|
try:
|
|
import ifupdown2.nlmanager.ipnetwork as ipnetwork
|
|
import ifupdown2.ifupdown.policymanager as policymanager
|
|
import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
|
|
|
|
from ifupdown2.lib.addon import Addon
|
|
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.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 Addon
|
|
from lib.nlcache import NetlinkCacheIfnameNotFoundError
|
|
|
|
from nlmanager.nlmanager import Link
|
|
|
|
from ifupdown.iface import *
|
|
from ifupdown.utils import utils
|
|
|
|
from ifupdownaddons.cache import *
|
|
from ifupdownaddons.modulebase import moduleBase
|
|
|
|
|
|
class vxlan(Addon, 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 id",
|
|
"validvals": ["<ipv4>"],
|
|
"example": ["vxlan-svcnodeip 172.16.22.125"]
|
|
},
|
|
"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",
|
|
"validvals": ["0", "255"],
|
|
"example": ['vxlan-ttl 42'],
|
|
},
|
|
"vxlan-mcastgrp": {
|
|
"help": "vxlan multicast group",
|
|
"validvals": ["<ip>"],
|
|
"example": ["vxlan-mcastgrp 172.16.22.127"],
|
|
}
|
|
}
|
|
}
|
|
|
|
VXLAN_PHYSDEV_MCASTGRP_DEFAULT = "ipmr-lo"
|
|
|
|
def __init__(self, *args, **kargs):
|
|
Addon.__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
|
|
|
|
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
|
|
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:
|
|
pass
|
|
return True
|
|
|
|
def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None):
|
|
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)
|
|
|
|
# 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"):
|
|
self.vxlan_mcastgrp_ref = True
|
|
|
|
elif ifaceobj.name == 'lo':
|
|
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 _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.get_attr_value_first("vxlan-id") \
|
|
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 __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:
|
|
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:
|
|
self.log_error("%s: invalid vxlan-port '%s'" % (ifname, vxlan_port_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
|
|
except:
|
|
self.log_error("%s: invalid vxlan-ttl '%s'" % (ifname, vxlan_ttl_str), ifaceobj)
|
|
|
|
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:
|
|
# on ifreload do not overwrite anycast_ip to individual ip
|
|
# if clagd has modified
|
|
running_localtunnelip = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
|
|
|
|
if self._clagd_vxlan_anycast_ip and running_localtunnelip:
|
|
anycastip = ipnetwork.IPNetwork(self._clagd_vxlan_anycast_ip)
|
|
if anycastip == running_localtunnelip:
|
|
local = running_localtunnelip
|
|
|
|
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)))
|
|
|
|
cached_ifla_vxlan_local = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
|
|
|
|
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_mcast_grp(self, ifaceobj):
|
|
"""
|
|
Get vxlan-mcastgrp user config or policy
|
|
:param ifaceobj:
|
|
:return:
|
|
"""
|
|
vxlan_mcast_grp = ifaceobj.get_attr_value_first("vxlan-mcastgrp")
|
|
|
|
if not vxlan_mcast_grp:
|
|
vxlan_mcast_grp = policymanager.policymanager_api.get_attr_default(
|
|
module_name=self.__class__.__name__,
|
|
attr="vxlan-mcastgrp"
|
|
)
|
|
|
|
return vxlan_mcast_grp
|
|
|
|
def __get_vxlan_svcnodeip(self, ifaceobj):
|
|
"""
|
|
Get vxlan-svcnodeip user config or policy
|
|
:param ifaceobj:
|
|
:return:
|
|
"""
|
|
vxlan_svcnodeip = ifaceobj.get_attr_value_first('vxlan-svcnodeip')
|
|
|
|
if not vxlan_svcnodeip:
|
|
vxlan_svcnodeip = policymanager.policymanager_api.get_attr_default(
|
|
module_name=self.__class__.__name__,
|
|
attr="vxlan-svcnodeip"
|
|
)
|
|
|
|
return vxlan_svcnodeip
|
|
|
|
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_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 __get_vxlan_physdev(self, ifaceobj, mcastgrp):
|
|
"""
|
|
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:
|
|
return physdev
|
|
|
|
physdev = self.vxlan_physdev_mcast
|
|
|
|
if not self.cache.link_exists(physdev):
|
|
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:
|
|
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 _up(self, ifaceobj):
|
|
vxlan_id_str = ifaceobj.get_attr_value_first("vxlan-id")
|
|
|
|
if not ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN 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)
|
|
self.__config_vxlan_ttl(ifname, ifaceobj, 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_mcast_grp = self.__get_vxlan_mcast_grp(ifaceobj)
|
|
vxlan_svcnodeip = self.__get_vxlan_svcnodeip(ifaceobj)
|
|
vxlan_physdev = self.__get_vxlan_physdev(ifaceobj, vxlan_mcast_grp)
|
|
|
|
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
|
|
)
|
|
|
|
flap_vxlan_device = link_exists and (multicast_group_changed or vxlan_physdev_changed)
|
|
|
|
if user_request_vxlan_info_data:
|
|
|
|
if link_exists and not len(user_request_vxlan_info_data) > 1:
|
|
# 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:
|
|
if link_exists:
|
|
self.logger.warning("%s: updating existing single vxlan device is not support yet" % ifname)
|
|
else:
|
|
self.iproute2.link_add_single_vxlan(
|
|
ifname,
|
|
local,
|
|
user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT)
|
|
)
|
|
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
|
|
|
|
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:
|
|
pass
|
|
|
|
for addr in add_list:
|
|
try:
|
|
self.iproute2.bridge_fdb_append(
|
|
ifaceobj.name,
|
|
"00:00:00:00:00:00",
|
|
None, True, addr
|
|
)
|
|
except:
|
|
pass
|
|
|
|
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-port', Link.IFLA_VXLAN_PORT, int),
|
|
('vxlan-ageing', Link.IFLA_VXLAN_AGEING, int),
|
|
('vxlan-mcastgrp', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
|
|
('vxlan-svcnodeip', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
|
|
('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_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)
|
|
)
|
|
|
|
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-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 operation != 'query-running':
|
|
if not self._is_vxlan_device(ifaceobj):
|
|
return
|
|
|
|
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)
|