]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/addons/vxlan.py
addons: vxlan: process mcast groups after the addition of vnifilter
[mirror_ifupdown2.git] / ifupdown2 / addons / vxlan.py
CommitLineData
35681c06 1#!/usr/bin/env python3
d486dd0d
JF
2#
3# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4# Author: Roopa Prabhu, roopa@cumulusnetworks.com
5#
6
6015cce2 7from ipaddress import IPv4Network, IPv4Address, AddressValueError
d486dd0d 8try:
0e936c3f 9 import ifupdown2.nlmanager.ipnetwork as ipnetwork
d486dd0d 10 import ifupdown2.ifupdown.policymanager as policymanager
3fb83a7a 11 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
d486dd0d 12
223ba5af
JF
13 from ifupdown2.lib.addon import Addon
14 from ifupdown2.lib.nlcache import NetlinkCacheIfnameNotFoundError
15
d486dd0d
JF
16 from ifupdown2.nlmanager.nlmanager import Link
17
18 from ifupdown2.ifupdown.iface import *
19 from ifupdown2.ifupdown.utils import utils
56f34349 20 from ifupdown2.ifupdown.statemanager import statemanager_api as statemanager
d486dd0d 21 from ifupdown2.ifupdownaddons.cache import *
d486dd0d 22 from ifupdown2.ifupdownaddons.modulebase import moduleBase
56f34349 23
bd441a51 24except (ImportError, ModuleNotFoundError):
0e936c3f 25 import nlmanager.ipnetwork as ipnetwork
d486dd0d 26 import ifupdown.policymanager as policymanager
3fb83a7a 27 import ifupdown.ifupdownflags as ifupdownflags
d486dd0d 28
223ba5af
JF
29 from lib.addon import Addon
30 from lib.nlcache import NetlinkCacheIfnameNotFoundError
31
d486dd0d
JF
32 from nlmanager.nlmanager import Link
33
34 from ifupdown.iface import *
35 from ifupdown.utils import utils
56f34349 36 from ifupdown.statemanager import statemanager_api as statemanager
d486dd0d
JF
37
38 from ifupdownaddons.cache import *
d486dd0d
JF
39 from ifupdownaddons.modulebase import moduleBase
40
41
223ba5af
JF
42class vxlan(Addon, moduleBase):
43 _modinfo = {
44 "mhelp": "vxlan module configures vxlan interfaces.",
45 "attrs": {
46 "vxlan-id": {
47 "help": "vxlan id",
48 "validrange": ["1", "16777214"],
49 "required": True,
50 "example": ["vxlan-id 100"]
51 },
52 "vxlan-local-tunnelip": {
53 "help": "vxlan local tunnel ip",
54 "validvals": ["<ipv4>"],
55 "example": ["vxlan-local-tunnelip 172.16.20.103"]
56 },
57 "vxlan-svcnodeip": {
969257a4 58 "help": "vxlan svc node id",
223ba5af
JF
59 "validvals": ["<ipv4>"],
60 "example": ["vxlan-svcnodeip 172.16.22.125"]
61 },
40658337
JF
62 "vxlan-svcnodeip6": {
63 "help": "vxlan svc node ip",
64 "validvals": ["<ipv6>"],
65 "example": ["vxlan-svcnodeip6 2001:DB8:8086:6502::"]
66 },
223ba5af
JF
67 "vxlan-remoteip": {
68 "help": "vxlan remote ip",
69 "validvals": ["<ipv4>"],
70 "example": ["vxlan-remoteip 172.16.22.127"],
71 "multiline": True
72 },
73 "vxlan-learning": {
74 "help": "vxlan learning yes/no",
75 "validvals": ["yes", "no", "on", "off"],
76 "example": ["vxlan-learning no"],
77 "default": "yes"
78 },
79 "vxlan-ageing": {
80 "help": "vxlan aging timer",
81 "validrange": ["0", "4096"],
82 "example": ["vxlan-ageing 300"],
83 "default": "300"
84 },
85 "vxlan-purge-remotes": {
86 "help": "vxlan purge existing remote entries",
87 "validvals": ["yes", "no"],
88 "example": ["vxlan-purge-remotes yes"],
89 },
90 "vxlan-port": {
91 "help": "vxlan UDP port (transmitted to vxlan driver)",
92 "example": ["vxlan-port 4789"],
93 "validrange": ["1", "65536"],
94 "default": "4789",
95 },
96 "vxlan-physdev": {
97 "help": "vxlan physical device",
98 "example": ["vxlan-physdev eth1"]
99 },
100 "vxlan-ttl": {
101 "help": "specifies the TTL value to use in outgoing packets "
102 "(range 0..255), 0=auto",
103 "default": "0",
b20f9836
SO
104 "validrange": ["0", "255"],
105 "validvals": ["<number>", "auto"],
223ba5af
JF
106 "example": ['vxlan-ttl 42'],
107 },
e521508b 108 "vxlan-tos": {
a8dd54b0 109 "help": "specifies the ToS value (range 0..255), 1=inherit",
b20f9836
SO
110 "validrange": ["0", "255"],
111 "validvals": ["<number>", "inherit"],
e521508b
SO
112 "example": ['vxlan-tos 42'],
113 },
223ba5af
JF
114 "vxlan-mcastgrp": {
115 "help": "vxlan multicast group",
116 "validvals": ["<ip>"],
117 "example": ["vxlan-mcastgrp 172.16.22.127"],
40658337
JF
118 },
119 "vxlan-mcastgrp6": {
120 "help": "vxlan multicast group",
121 "validvals": ["<ip6>"],
122 "example": ["vxlan-mcastgrp ff02::15c"],
ca436937
JF
123 },
124 "vxlan-mcastgrp-map": {
56f34349
JF
125 "help": "vxlan multicast group for single-vxlan device -"
126 "doesn't support multiline attribute",
ca436937
JF
127 "validvals": ["<number-ipv4-list>"],
128 "example": ["vxlan-mcastgrp-map 1000=239.1.1.100 1001=239.1.1.200"],
129 },
84c47c4f
RP
130 "vxlan-vnifilter": {
131 "help": "vxlan vni filter for single-vxlan device",
132 "validvals": ["on", "off"],
133 "default": "off",
134 "example": ["vxlan-vnifilter yes"],
135 },
e521508b
SO
136 "vxlan-udp-csum": {
137 "help": "whether to perform checksumming or not",
e521508b
SO
138 "validvals": ["yes", "no"],
139 "example": ["vxlan-udp-csum no"]
140 }
223ba5af
JF
141 }
142 }
143
144 VXLAN_PHYSDEV_MCASTGRP_DEFAULT = "ipmr-lo"
d486dd0d
JF
145
146 def __init__(self, *args, **kargs):
223ba5af 147 Addon.__init__(self)
d486dd0d 148 moduleBase.__init__(self, *args, **kargs)
223ba5af
JF
149
150 self._vxlan_purge_remotes = utils.get_boolean_from_string(
151 policymanager.policymanager_api.get_module_globals(
152 module_name=self.__class__.__name__,
153 attr="vxlan-purge-remotes"
154 )
155 )
156 self._vxlan_local_tunnelip = None
157 self._clagd_vxlan_anycast_ip = ""
158
159 # If mcastgrp is specified we need to rely on a user-configred device (via physdev)
160 # or via a policy variable "vxlan-physdev_mcastgrp". If the device doesn't exist we
161 # create it as a dummy device. We need to keep track of the user configuration to
162 # know when to delete this dummy device (when user remove mcastgrp from it's config)
163 self.vxlan_mcastgrp_ref = False
164 self.vxlan_physdev_mcast = policymanager.policymanager_api.get_module_globals(
165 module_name=self.__class__.__name__,
166 attr="vxlan-physdev-mcastgrp"
167 ) or self.VXLAN_PHYSDEV_MCASTGRP_DEFAULT
168
169 def reset(self):
170 # in daemon mode we need to reset mcastgrp_ref for every new command
171 # this variable has to be set in get_dependent_ifacenames
172 self.vxlan_mcastgrp_ref = False
d486dd0d
JF
173
174 def syntax_check(self, ifaceobj, ifaceobj_getfunc):
175 if self._is_vxlan_device(ifaceobj):
223ba5af 176 if not ifaceobj.get_attr_value_first('vxlan-local-tunnelip') and not self._vxlan_local_tunnelip:
d486dd0d
JF
177 self.logger.warning('%s: missing vxlan-local-tunnelip' % ifaceobj.name)
178 return False
179 return self.syntax_check_localip_anycastip_equal(
180 ifaceobj.name,
223ba5af
JF
181 ifaceobj.get_attr_value_first('vxlan-local-tunnelip') or self._vxlan_local_tunnelip,
182 self._clagd_vxlan_anycast_ip
d486dd0d
JF
183 )
184 return True
185
186 def syntax_check_localip_anycastip_equal(self, ifname, local_ip, anycast_ip):
187 try:
0e936c3f 188 if local_ip and anycast_ip and ipnetwork.IPNetwork(local_ip) == ipnetwork.IPNetwork(anycast_ip):
d486dd0d
JF
189 self.logger.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
190 % (ifname, local_ip))
191 return False
3218f49d 192 except Exception:
d486dd0d
JF
193 pass
194 return True
195
59ab29fb 196 def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None, old_ifaceobjs=False):
e537a6e6
JF
197 if ifaceobj.get_attr_value_first("bridge-vlan-vni-map"):
198 ifaceobj.link_privflags |= ifaceLinkPrivFlags.SINGLE_VXLAN
199
d486dd0d
JF
200 if self._is_vxlan_device(ifaceobj):
201 ifaceobj.link_kind |= ifaceLinkKind.VXLAN
202 self._set_global_local_ip(ifaceobj)
223ba5af
JF
203
204 # if we detect a vxlan we check if mcastgrp is set (if so we set vxlan_mcastgrp_ref)
205 # to know when to delete this device.
1609696f 206 if not self.vxlan_mcastgrp_ref and (ifaceobj.get_attr_value("vxlan-mcastgrp") or ifaceobj.get_attr_value("vxlan-mcastgrp-map")):
223ba5af
JF
207 self.vxlan_mcastgrp_ref = True
208
59ab29fb 209 elif ifaceobj.name == 'lo' and not old_ifaceobjs:
d486dd0d
JF
210 clagd_vxlan_list = ifaceobj.get_attr_value('clagd-vxlan-anycast-ip')
211 if clagd_vxlan_list:
212 if len(clagd_vxlan_list) != 1:
213 self.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one'
214 % (ifaceobj.name,))
223ba5af 215 self._clagd_vxlan_anycast_ip = clagd_vxlan_list[0]
d486dd0d
JF
216
217 self._set_global_local_ip(ifaceobj)
a382b488
JF
218
219 # If we should use a specific underlay device for the VXLAN
220 # tunnel make sure this device is set up before the VXLAN iface.
221 physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
222
223 if physdev:
224 return [physdev]
225
d486dd0d
JF
226 return None
227
228 def _set_global_local_ip(self, ifaceobj):
229 vxlan_local_tunnel_ip = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
223ba5af
JF
230 if vxlan_local_tunnel_ip and not self._vxlan_local_tunnelip:
231 self._vxlan_local_tunnelip = vxlan_local_tunnel_ip
d486dd0d 232
223ba5af
JF
233 @staticmethod
234 def _is_vxlan_device(ifaceobj):
e537a6e6
JF
235 return ifaceobj.link_kind & ifaceLinkKind.VXLAN \
236 or ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN \
237 or ifaceobj.get_attr_value_first("vxlan-id") \
238 or ifaceobj.get_attr_value_first("bridge-vlan-vni-map")
d486dd0d 239
223ba5af 240 def __get_vlxan_purge_remotes(self, ifaceobj):
d486dd0d 241 if not ifaceobj:
223ba5af 242 return self._vxlan_purge_remotes
d486dd0d
JF
243 purge_remotes = ifaceobj.get_attr_value_first('vxlan-purge-remotes')
244 if purge_remotes:
245 purge_remotes = utils.get_boolean_from_string(purge_remotes)
246 else:
223ba5af 247 purge_remotes = self._vxlan_purge_remotes
d486dd0d
JF
248 return purge_remotes
249
ec25a08c
JF
250 def get_vxlan_ttl_from_string(self, ttl_config):
251 ttl = 0
252 if ttl_config:
253 if ttl_config.lower() == "auto":
254 ttl = 0
255 else:
256 ttl = int(ttl_config)
257 return ttl
258
e521508b 259 def get_vxlan_tos_from_string(self, tos_config):
e521508b
SO
260 if tos_config:
261 if tos_config.lower() == "inherit":
a8dd54b0 262 return 1
e521508b 263 else:
a8dd54b0
JF
264 return int(tos_config)
265 return None
e521508b 266
223ba5af
JF
267 def __config_vxlan_id(self, ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
268 """
269 Get vxlan-id user config and check it's value before inserting it in our netlink dictionary
270 :param ifname:
271 :param ifaceobj:
272 :param vxlan_id_str:
273 :param user_request_vxlan_info_data:
274 :param cached_vxlan_ifla_info_data:
275 :return:
276 """
277 try:
278 vxlan_id = int(vxlan_id_str)
279 cached_vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
280
281 if cached_vxlan_id and cached_vxlan_id != vxlan_id:
282 self.log_error(
283 "%s: Cannot change running vxlan id (%s): Operation not supported"
284 % (ifname, cached_vxlan_id),
285 ifaceobj
286 )
287 user_request_vxlan_info_data[Link.IFLA_VXLAN_ID] = vxlan_id
288 except ValueError:
289 self.log_error("%s: invalid vxlan-id '%s'" % (ifname, vxlan_id_str), ifaceobj)
290
291 def __get_vxlan_ageing_int(self, ifname, ifaceobj, link_exists):
292 """
293 Get vxlan-ageing user config or via policy, return integer value, None or raise on error
294 :param ifname:
295 :param ifaceobj:
296 :param link_exists:
297 :return:
298 """
299 vxlan_ageing_str = ifaceobj.get_attr_value_first("vxlan-ageing")
300 try:
301 if vxlan_ageing_str:
302 return int(vxlan_ageing_str)
303
304 vxlan_ageing_str = policymanager.policymanager_api.get_attr_default(
305 module_name=self.__class__.__name__,
306 attr="vxlan-ageing"
307 )
308
309 if not vxlan_ageing_str and link_exists:
310 # if link doesn't exist we let the kernel define ageing
311 vxlan_ageing_str = self.get_attr_default_value("vxlan-ageing")
312
313 if vxlan_ageing_str:
314 return int(vxlan_ageing_str)
3218f49d 315 except Exception:
223ba5af 316 self.log_error("%s: invalid vxlan-ageing '%s'" % (ifname, vxlan_ageing_str), ifaceobj)
d486dd0d 317
223ba5af
JF
318 def __config_vxlan_ageing(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
319 """
320 Check user config vxlan-ageing and insert it in our netlink dictionary if needed
321 """
322 vxlan_ageing = self.__get_vxlan_ageing_int(ifname, ifaceobj, link_exists)
323
324 if not vxlan_ageing or (link_exists and vxlan_ageing == cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_AGEING)):
325 return
326
327 self.logger.info("%s: set vxlan-ageing %s" % (ifname, vxlan_ageing))
328 user_request_vxlan_info_data[Link.IFLA_VXLAN_AGEING] = vxlan_ageing
329
330 def __config_vxlan_port(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
331 """
332 Check vxlan-port user config, validate the integer value and insert it in the netlink dictionary if needed
333 :param ifname:
334 :param ifaceobj:
335 :param link_exists:
336 :param user_request_vxlan_info_data:
337 :param cached_vxlan_ifla_info_data:
338 :return:
339 """
340 vxlan_port_str = ifaceobj.get_attr_value_first("vxlan-port")
341 try:
342 if not vxlan_port_str:
343 vxlan_port_str = policymanager.policymanager_api.get_attr_default(
344 module_name=self.__class__.__name__,
345 attr="vxlan-port"
346 )
d486dd0d 347
ec25a08c 348 try:
223ba5af
JF
349 vxlan_port = int(vxlan_port_str)
350 except TypeError:
351 # TypeError means vxlan_port was None
352 # ie: not provided by the user or the policy
353 vxlan_port = self.netlink.VXLAN_UDP_PORT
354 except ValueError as e:
355 self.logger.warning(
356 "%s: vxlan-port: using default %s: invalid configured value %s"
357 % (ifname, self.netlink.VXLAN_UDP_PORT, str(e))
358 )
359 vxlan_port = self.netlink.VXLAN_UDP_PORT
360
361 cached_vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
362
363 if link_exists:
364 if vxlan_port != cached_vxlan_port:
365 self.logger.warning(
366 "%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s"
367 % (ifname, cached_vxlan_port, ifname, ifname)
ec25a08c 368 )
ec25a08c
JF
369 return
370
223ba5af
JF
371 self.logger.info("%s: set vxlan-port %s" % (ifname, vxlan_port))
372 user_request_vxlan_info_data[Link.IFLA_VXLAN_PORT] = vxlan_port
3218f49d 373 except Exception:
223ba5af
JF
374 self.log_error("%s: invalid vxlan-port '%s'" % (ifname, vxlan_port_str), ifaceobj)
375
e521508b
SO
376 def __config_vxlan_tos(self, ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
377 """
378 Get vxlan-tos from user config or policy, validate integer value and insert in netlink dict
379 :param ifname:
380 :param ifaceobj:
381 :param user_request_vxlan_info_data:
382 :param cached_vxlan_ifla_info_data:
383 :return:
384 """
385 vxlan_tos_str = ifaceobj.get_attr_value_first("vxlan-tos")
386 try:
387 if vxlan_tos_str:
388 vxlan_tos = self.get_vxlan_tos_from_string(vxlan_tos_str)
389 else:
390 vxlan_tos = self.get_vxlan_tos_from_string(
391 policymanager.policymanager_api.get_attr_default(
392 module_name=self.__class__.__name__,
393 attr="vxlan-tos"
394 )
395 )
396
a8dd54b0
JF
397 if not vxlan_tos_str:
398 return
399
e521508b 400 cached_ifla_vxlan_tos = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_TOS)
a8dd54b0 401
e521508b
SO
402 if vxlan_tos != cached_ifla_vxlan_tos:
403
404 if cached_ifla_vxlan_tos is not None:
405 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))
406 else:
407 self.logger.info("%s: set vxlan-tos %s" % (ifname, vxlan_tos_str if vxlan_tos_str else vxlan_tos))
408
409 user_request_vxlan_info_data[Link.IFLA_VXLAN_TOS] = vxlan_tos
410 except Exception:
411 self.log_error("%s: invalid vxlan-tos '%s'" % (ifname, vxlan_tos_str), ifaceobj)
412
223ba5af
JF
413 def __config_vxlan_ttl(self, ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
414 """
415 Get vxlan-ttl from user config or policy, validate integer value and insert in netlink dict
416 :param ifname:
417 :param ifaceobj:
418 :param user_request_vxlan_info_data:
419 :param cached_vxlan_ifla_info_data:
420 :return:
421 """
422 vxlan_ttl_str = ifaceobj.get_attr_value_first("vxlan-ttl")
423 try:
424 if vxlan_ttl_str:
425 vxlan_ttl = self.get_vxlan_ttl_from_string(vxlan_ttl_str)
426 else:
427 vxlan_ttl = self.get_vxlan_ttl_from_string(
428 policymanager.policymanager_api.get_attr_default(
429 module_name=self.__class__.__name__,
430 attr="vxlan-ttl"
431 )
432 )
433
434 cached_ifla_vxlan_ttl = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_TTL)
435 if vxlan_ttl != cached_ifla_vxlan_ttl:
436
437 if cached_ifla_vxlan_ttl is not None:
438 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))
439 else:
440 self.logger.info("%s: set vxlan-ttl %s" % (ifname, vxlan_ttl_str if vxlan_ttl_str else vxlan_ttl))
441
442 user_request_vxlan_info_data[Link.IFLA_VXLAN_TTL] = vxlan_ttl
9144496d 443 return vxlan_ttl
3218f49d 444 except Exception:
223ba5af
JF
445 self.log_error("%s: invalid vxlan-ttl '%s'" % (ifname, vxlan_ttl_str), ifaceobj)
446
59ab29fb
JF
447 def is_vxlan_on_a_clag_bridge(self, ifaceobj) -> bool:
448 return bool(ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT and self._clagd_vxlan_anycast_ip and self.is_process_running('clagd'))
449
223ba5af
JF
450 def __config_vxlan_local_tunnelip(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
451 """
452 Get vxlan-local-tunnelip user config or policy, validate ip address format and insert in netlink dict
453 :param ifname:
454 :param ifaceobj:
455 :param link_exists:
456 :param user_request_vxlan_info_data:
457 :param cached_vxlan_ifla_info_data:
458 :return:
459 """
460 local = ifaceobj.get_attr_value_first("vxlan-local-tunnelip")
461
462 if not local and self._vxlan_local_tunnelip:
463 local = self._vxlan_local_tunnelip
464
465 if link_exists:
59ab29fb
JF
466 cached_ifla_vxlan_local = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
467
223ba5af
JF
468 # on ifreload do not overwrite anycast_ip to individual ip
469 # if clagd has modified
59ab29fb 470 if self._clagd_vxlan_anycast_ip and cached_ifla_vxlan_local:
0e936c3f 471 anycastip = ipnetwork.IPNetwork(self._clagd_vxlan_anycast_ip)
59ab29fb
JF
472
473 if (
474 anycastip == cached_ifla_vxlan_local
475 # there's a change that the cache hasn't been updated in
476 # time to reflect the new anycast ip set by clagd, extra checks:
477 or self.is_vxlan_on_a_clag_bridge(ifaceobj)
478 ):
479 local = cached_ifla_vxlan_local = anycastip
480 self.logger.info("%s: clagd-vxlan-anycast-ip (%s) inherited from loopback interface" % (ifname, local))
481 else:
482 cached_ifla_vxlan_local = None
223ba5af
JF
483
484 if not local:
485 local = policymanager.policymanager_api.get_attr_default(
486 module_name=self.__class__.__name__,
487 attr="vxlan-local-tunnelip"
488 )
489
490 if local:
491 try:
0e936c3f
JF
492 local = ipnetwork.IPv4Address(local)
493
494 if local.initialized_with_prefixlen:
223ba5af 495 self.logger.warning("%s: vxlan-local-tunnelip %s: netmask ignored" % (ifname, local))
0e936c3f
JF
496
497 except Exception as e:
498 raise Exception("%s: invalid vxlan-local-tunnelip %s: %s" % (ifname, local, str(e)))
223ba5af 499
223ba5af
JF
500
501 if local:
502 if local != cached_ifla_vxlan_local:
503 self.logger.info("%s: set vxlan-local-tunnelip %s" % (ifname, local))
504 user_request_vxlan_info_data[Link.IFLA_VXLAN_LOCAL] = local
505
506 # if both local-ip and anycast-ip are identical the function prints a warning
507 self.syntax_check_localip_anycastip_equal(ifname, local, self._clagd_vxlan_anycast_ip)
508 elif cached_ifla_vxlan_local:
509 self.logger.info("%s: removing vxlan-local-tunnelip (cache %s)" % (ifname, cached_ifla_vxlan_local))
510 user_request_vxlan_info_data[Link.IFLA_VXLAN_LOCAL] = None
511
512 return local
513
40658337
JF
514 def __get_vxlan_attribute(self, ifaceobj, attr_name):
515 vxlan_attr_value = ifaceobj.get_attr_value_first(attr_name)
d486dd0d 516
40658337
JF
517 if not vxlan_attr_value:
518 vxlan_attr_value = policymanager.policymanager_api.get_attr_default(
223ba5af 519 module_name=self.__class__.__name__,
40658337 520 attr=attr_name
223ba5af 521 )
d486dd0d 522
40658337 523 return vxlan_attr_value
d486dd0d 524
6015cce2
QZ
525 def __syntax_check_vxlan_mcast_vni(self, ifaceobj, m, vni):
526 try:
527 int(vni)
528 except ValueError:
529 self.log_error('%s: vxlan-mcastgrp-map "%s" vni format is invalid' % (ifaceobj.name, m))
530
531 def __syntax_check_vxlan_mcast_vni_range(self, ifaceobj, m, vni_range):
532 if len(vni_range) != 2:
533 self.log_error('%s: vxlan-mcastgrp-map "%s" vni range format is invalid' % (ifaceobj.name, m))
534 for vni in vni_range:
535 self.__syntax_check_vxlan_mcast_vni(ifaceobj, m, vni)
536 if int(vni_range[0]) >= int(vni_range[1]):
537 self.log_error('%s: vxlan-mcastgrp-map "%s" vni range is invalid' % (ifaceobj.name, m))
538
539 def __syntax_check_vxlan_mcast_grp(self, ifaceobj, m, grp):
540 try:
541 ip = IPv4Address(grp)
542 except AddressValueError:
543 self.log_error('%s: vxlan-mcastgrp-map "%s" group format is invalid' % (ifaceobj.name, m))
544 if not ip.is_multicast:
545 self.log_error('%s: vxlan-mcastgrp-map "%s" group is not multicast' % (ifaceobj.name, m))
546
547 def __syntax_check_vxlan_mcast_grp_range(self, ifaceobj, m, grp_range):
548 if len(grp_range) != 2:
549 self.log_error('%s: vxlan-mcastgrp-map "%s" group format is invalid' % (ifaceobj.name, m))
550 for grp in grp_range:
551 self.__syntax_check_vxlan_mcast_grp(ifaceobj, m, grp)
552 if int(IPv4Address(grp_range[0])) >= int(IPv4Address(grp_range[1])):
553 self.log_error('%s: vxlan-mcastgrp-map "%s" group range is invalid' % (ifaceobj.name, m))
554
555 def __syntax_check_vxlan_mcast_network(self, ifaceobj, m, network, len_vni):
556 try:
557 ip = IPv4Network(network)
558 ip[0]
559 ip[len_vni - 1]
560 except IndexError:
561 self.log_error('%s: vxlan-mcastgrp-map "%s" network range is insufficient' % (ifaceobj.name, m))
562 except AddressValueError:
563 self.log_error('%s: vxlan-mcastgrp-map "%s" network format is invalid' % (ifaceobj.name, m))
564 if not ip.is_multicast:
565 self.log_error('%s: vxlan-mcastgrp-map "%s" network is not multicast' % (ifaceobj.name, m))
566
567 def __get_vxlan_mcastgrp_map(self, ifaceobj):
568 maps = ifaceobj.get_attr_value('vxlan-mcastgrp-map')
569 if not maps:
570 maps = policymanager.policymanager_api.get_attr_default(
571 module_name=self.__class__.__name__,
572 attr='vxlan-mcastgrp-map'
573 )
574 return maps
575
576 parsed_maps = {}
577 for m_line in maps:
578 # Cover single-line multi-entry case
579 map = m_line.split()
580 for m in map:
581 m_parts = m.split('=')
582 if len(m_parts) != 2:
583 self.log_error('%s: vxlan-mcastgrp-map %s format is invalid' % (ifaceobj.name, m))
584 vni = m_parts[0]
585 grp = m_parts[1]
586 _range = "-"
587 _network = "/"
588
589 # One to one mapping case
590 if _range not in vni and _range not in grp:
591 self.__syntax_check_vxlan_mcast_vni(ifaceobj, m, vni)
592 self.__syntax_check_vxlan_mcast_grp(ifaceobj, m, grp)
593 if int(vni) not in parsed_maps:
594 parsed_maps[int(vni)] = IPv4Address(grp)
595 else:
596 self.log_warn('%s: vxlan-mcastgrp-map %s vni %s duplicate' % (ifaceobj.name, vni, m))
597
598 # Many VNI case
599 if _range in vni:
600 v_parts = vni.split(_range)
601 self.__syntax_check_vxlan_mcast_vni_range(ifaceobj, m, v_parts)
602 vnis = list(range(int(v_parts[0]), int(v_parts[1]) + 1))
603
604 if _range not in grp and _network not in grp:
605 self.__syntax_check_vxlan_mcast_grp(ifaceobj, m, grp)
606 for i in vnis:
607 if i not in parsed_maps:
608 parsed_maps[i] = IPv4Address(grp)
609 else:
610 self.log_warn('%s: vxlan-mcastgrp-map %s vni %s duplicate' % (ifaceobj.name, vni, m))
611 else:
612 if _network in grp:
613 self.__syntax_check_vxlan_mcast_network(ifaceobj, m, grp, len(vnis))
614 network = IPv4Network(grp)
615 g_parts = [network[0], network[len(vnis) - 1]]
616 else:
617 g_parts = grp.split(_range)
618
619 self.__syntax_check_vxlan_mcast_grp_range(ifaceobj, m, g_parts)
620 grp_range = list(range(int(IPv4Address(g_parts[0])), int(IPv4Address(g_parts[1])) + 1))
621 if len(grp_range) != len(vnis):
622 self.log_error('%s: vxlan-mcastgrp-map "%s" range lengths do not match.'
623 % (ifaceobj.name, m))
624
625 for v, g in zip(vnis, grp_range):
626 if v not in parsed_maps:
627 parsed_maps[v] = IPv4Address(g)
628 else:
629 self.log_warn('%s: vxlan-mcastgrp-map %s vni %s duplicate' % (ifaceobj.name, v, m))
630
631 return parsed_maps
632
223ba5af
JF
633 def __config_vxlan_group(self, ifname, ifaceobj, link_exists, mcast_grp, group, physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
634 """
635 vxlan-mcastgrp and vxlan-svcnodeip are mutually exclusive
636 this function validates ip format for both attribute and tries to understand
637 what the user really want (remote or group option).
638
639 :param ifname:
640 :param ifaceobj:
641 :param mcast_grp:
642 :param group:
643 :param physdev:
644 :param user_request_vxlan_info_data:
645 :param cached_vxlan_ifla_info_data:
646 :return:
647 """
648 if mcast_grp and group:
649 self.log_error("%s: both group (vxlan-mcastgrp %s) and "
650 "remote (vxlan-svcnodeip %s) cannot be specified"
651 % (ifname, mcast_grp, group), ifaceobj)
652
653 attribute_name = "vxlan-svcnodeip"
654 multicast_group_change = False
655
656 if group:
657 try:
0e936c3f
JF
658 group = ipnetwork.IPv4Address(group)
659
660 if group.initialized_with_prefixlen:
223ba5af 661 self.logger.warning("%s: vxlan-svcnodeip %s: netmask ignored" % (ifname, group))
223ba5af 662
0e936c3f
JF
663 except Exception as e:
664 raise Exception("%s: invalid vxlan-svcnodeip %s: %s" % (ifname, group, str(e)))
665
666 if group.ip.is_multicast:
223ba5af
JF
667 self.logger.warning("%s: vxlan-svcnodeip %s: invalid group address, "
668 "for multicast IP please use attribute \"vxlan-mcastgrp\"" % (ifname, group))
669 # if svcnodeip is used instead of mcastgrp we warn the user
670 # if mcast_grp is not provided by the user we can instead
671 # use the svcnodeip value
672 if not physdev:
673 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp) requires 'vxlan-physdev' to be specified" % (ifname))
674
675 elif mcast_grp:
676 try:
0e936c3f
JF
677 mcast_grp = ipnetwork.IPv4Address(mcast_grp)
678
679 if mcast_grp.initialized_with_prefixlen:
223ba5af 680 self.logger.warning("%s: vxlan-mcastgrp %s: netmask ignored" % (ifname, mcast_grp))
223ba5af 681
0e936c3f
JF
682 except Exception as e:
683 raise Exception("%s: invalid vxlan-mcastgrp %s: %s" % (ifname, mcast_grp, str(e)))
684
685 if not mcast_grp.ip.is_multicast:
223ba5af
JF
686 self.logger.warning("%s: vxlan-mcastgrp %s: invalid group address, "
687 "for non-multicast IP please use attribute \"vxlan-svcnodeip\""
688 % (ifname, mcast_grp))
689 # if mcastgrp is specified with a non-multicast address
690 # we warn the user. If the svcnodeip wasn't specified by
691 # the user we can use the mcastgrp value as svcnodeip
692 if not group:
693 group = mcast_grp
694 mcast_grp = None
d486dd0d 695 else:
223ba5af
JF
696 attribute_name = "vxlan-mcastgrp"
697
698 if mcast_grp:
699 group = mcast_grp
700
701 if not physdev:
702 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp) requires 'vxlan-physdev' to be specified" % (ifname))
703
704 cached_ifla_vxlan_group = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
705
706 if group != cached_ifla_vxlan_group:
707
708 if not group:
0e936c3f 709 group = ipnetwork.IPNetwork("0.0.0.0")
223ba5af
JF
710 attribute_name = "vxlan-svcnodeip/vxlan-mcastgrp"
711
712 self.logger.info("%s: set %s %s" % (ifname, attribute_name, group))
713 user_request_vxlan_info_data[Link.IFLA_VXLAN_GROUP] = group
714
715 # if the mcastgrp address is changed we need to signal this to the upper function
716 # in this case vxlan needs to be down before applying changes then up'd
717 multicast_group_change = True
d486dd0d
JF
718
719 if link_exists:
223ba5af
JF
720 if cached_ifla_vxlan_group:
721 self.logger.info(
722 "%s: vxlan-mcastgrp configuration changed (cache %s): flapping vxlan device required"
723 % (ifname, cached_ifla_vxlan_group)
724 )
b067bba9 725 else:
223ba5af
JF
726 self.logger.info(
727 "%s: vxlan-mcastgrp configuration changed: flapping vxlan device required" % ifname
728 )
b067bba9 729
223ba5af 730 return group, multicast_group_change
d486dd0d 731
40658337
JF
732 def __config_vxlan_group6(self, ifname, ifaceobj, link_exists, mcast_grp, group, physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
733 """
734 vxlan-mcastgrp and vxlan-svcnodeip are mutually exclusive
735 this function validates ip format for both attribute and tries to understand
736 what the user really want (remote or group option).
737
738 :param ifname:
739 :param ifaceobj:
740 :param mcast_grp:
741 :param group:
742 :param physdev:
743 :param user_request_vxlan_info_data:
744 :param cached_vxlan_ifla_info_data:
745 :return:
746 """
747 if mcast_grp and group:
748 self.log_error("%s: both group (vxlan-mcastgrp6 %s) and "
749 "remote (vxlan-svcnodeip6 %s) cannot be specified"
750 % (ifname, mcast_grp, group), ifaceobj)
751
752 attribute_name = "vxlan-svcnodeip6"
753 multicast_group_change = False
754
755 if group:
756 try:
757 group = ipnetwork.IPv6Address(group)
758 except Exception:
759 try:
760 group_ip = ipnetwork.IPv6Network(group).ip
761 self.logger.warning("%s: vxlan-svcnodeip6 %s: netmask ignored" % (ifname, group))
762 group = group_ip
763 except:
764 raise Exception("%s: invalid vxlan-svcnodeip6 %s: must be in ipv4 format" % (ifname, group))
765
766 if group.is_multicast:
767 self.logger.warning("%s: vxlan-svcnodeip6 %s: invalid group address, "
768 "for multicast IP please use attribute \"vxlan-mcastgrp6\"" % (ifname, group))
769 # if svcnodeip is used instead of mcastgrp we warn the user
770 # if mcast_grp is not provided by the user we can instead
771 # use the svcnodeip value
772 if not physdev:
773 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp6) requires 'vxlan-physdev' to be specified" % (ifname))
774
775 elif mcast_grp:
776 try:
777 mcast_grp = ipnetwork.IPv6Address(mcast_grp)
778 except Exception:
779 try:
780 group_ip = ipnetwork.IPv6Network(mcast_grp).ip
781 self.logger.warning("%s: vxlan-mcastgrp6 %s: netmask ignored" % (ifname, mcast_grp))
782 mcast_grp = group_ip
783 except:
784 raise Exception("%s: invalid vxlan-mcastgrp6 %s: must be in ipv4 format" % (ifname, mcast_grp))
785
786 if not mcast_grp.is_multicast:
787 self.logger.warning("%s: vxlan-mcastgrp6 %s: invalid group address, "
788 "for non-multicast IP please use attribute \"vxlan-svcnodeip6\""
789 % (ifname, mcast_grp))
790 # if mcastgrp is specified with a non-multicast address
791 # we warn the user. If the svcnodeip wasn't specified by
792 # the user we can use the mcastgrp value as svcnodeip
793 if not group:
794 group = mcast_grp
795 mcast_grp = None
796 else:
797 attribute_name = "vxlan-mcastgrp6"
798
799 if mcast_grp:
800 group = mcast_grp
801
802 if not physdev:
803 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp6) requires 'vxlan-physdev' to be specified" % (ifname))
804
805 cached_ifla_vxlan_group = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP6)
806
807 if group != cached_ifla_vxlan_group:
808
809 if not group:
810 group = ipnetwork.IPNetwork("::0", family=6)
811 attribute_name = "vxlan-svcnodeip6/vxlan-mcastgrp6"
812
813 self.logger.info("%s: set %s %s" % (ifname, attribute_name, group))
814 user_request_vxlan_info_data[Link.IFLA_VXLAN_GROUP6] = group
815
816 # if the mcastgrp address is changed we need to signal this to the upper function
817 # in this case vxlan needs to be down before applying changes then up'd
818 multicast_group_change = True
819
820 if link_exists:
821 if cached_ifla_vxlan_group:
822 self.logger.info(
823 "%s: vxlan-mcastgrp6 configuration changed (cache %s): flapping vxlan device required"
824 % (ifname, cached_ifla_vxlan_group)
825 )
826 else:
827 self.logger.info(
828 "%s: vxlan-mcastgrp6 configuration changed: flapping vxlan device required" % ifname
829 )
830
831 return group, multicast_group_change
832
223ba5af
JF
833 def __config_vxlan_learning(self, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
834 if not link_exists or not ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT:
835 vxlan_learning = ifaceobj.get_attr_value_first('vxlan-learning')
836 if not vxlan_learning:
837 vxlan_learning = self.get_attr_default_value('vxlan-learning')
838 vxlan_learning = utils.get_boolean_from_string(vxlan_learning)
839 else:
840 vxlan_learning = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LEARNING)
d486dd0d 841
223ba5af
JF
842 if vxlan_learning != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LEARNING):
843 self.logger.info("%s: set vxlan-learning %s" % (ifaceobj.name, "on" if vxlan_learning else "off"))
844 user_request_vxlan_info_data[Link.IFLA_VXLAN_LEARNING] = vxlan_learning
845
a8dd54b0 846 def __config_vxlan_udp_csum(self, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
e521508b 847 vxlan_udp_csum = ifaceobj.get_attr_value_first('vxlan-udp-csum')
a8dd54b0
JF
848
849 if not vxlan_udp_csum:
850 vxlan_udp_csum = policymanager.policymanager_api.get_attr_default(
851 module_name=self.__class__.__name__,
852 attr="vxlan-udp-csum"
853 )
854
855 if not vxlan_udp_csum and not link_exists:
856 return
857
e521508b
SO
858 if not vxlan_udp_csum:
859 vxlan_udp_csum = self.get_attr_default_value('vxlan-udp-csum')
e521508b 860
a8dd54b0
JF
861 if vxlan_udp_csum:
862 vxlan_udp_csum = utils.get_boolean_from_string(vxlan_udp_csum)
863 else:
864 return
e521508b
SO
865
866 if vxlan_udp_csum != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_UDP_CSUM):
867 self.logger.info("%s: set vxlan-udp-csum %s" % (ifaceobj.name, "on" if vxlan_udp_csum else "off"))
868 user_request_vxlan_info_data[Link.IFLA_VXLAN_UDP_CSUM] = vxlan_udp_csum
869
1609696f 870 def __get_vxlan_physdev(self, ifaceobj, mcastgrp, mcastgrp_map):
223ba5af
JF
871 """
872 vxlan-physdev wrapper, special handling is required for mcastgrp is provided
873 the vxlan needs to use a dummy or real device for tunnel endpoint communication
874 This wrapper will get the physdev from user config or policy. IF the device
875 doesnt exists we create a dummy device.
876
877 :param ifaceobj:
878 :param mcastgrp:
879 :return physdev:
880 """
881 physdev = ifaceobj.get_attr_value_first("vxlan-physdev")
d486dd0d 882
223ba5af
JF
883 # if the user provided a physdev we need to honor his config
884 # or if mcastgrp wasn't specified we don't need to go further
1609696f 885 if physdev or (not mcastgrp and not mcastgrp_map):
223ba5af
JF
886 return physdev
887
888 physdev = self.vxlan_physdev_mcast
889
890 if not self.cache.link_exists(physdev):
1609696f
JF
891 if mcastgrp_map:
892 self.logger.info("%s: needs a dummy device (%s) to use for "
893 "multicast termination (vxlan-mcastgrp-map %s)"
894 % (ifaceobj.name, physdev, mcastgrp))
895 else:
896 self.logger.info("%s: needs a dummy device (%s) to use for "
897 "multicast termination (vxlan-mcastgrp %s)"
898 % (ifaceobj.name, physdev, mcastgrp))
223ba5af
JF
899 self.netlink.link_add_with_attributes(ifname=physdev, kind="dummy", ifla={Link.IFLA_MTU: 16000, Link.IFLA_LINKMODE: 1})
900 self.netlink.link_up(physdev)
901
902 return physdev
903
904 def __config_vxlan_physdev(self, link_exists, ifaceobj, vxlan_physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
905 if vxlan_physdev:
906 try:
907 vxlan_physdev_ifindex = self.cache.get_ifindex(vxlan_physdev)
908 except NetlinkCacheIfnameNotFoundError:
d486dd0d 909 try:
223ba5af 910 vxlan_physdev_ifindex = int(self.sysfs.read_file_oneline("/sys/class/net/%s/ifindex" % vxlan_physdev))
3218f49d 911 except Exception:
223ba5af
JF
912 self.logger.error("%s: physdev %s doesn't exists" % (ifaceobj.name, vxlan_physdev))
913 return
d486dd0d 914
223ba5af
JF
915 if vxlan_physdev_ifindex != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LINK):
916 self.logger.info("%s: set vxlan-physdev %s" % (ifaceobj.name, vxlan_physdev))
917 user_request_vxlan_info_data[Link.IFLA_VXLAN_LINK] = vxlan_physdev_ifindex
d486dd0d 918
223ba5af
JF
919 # if the vxlan exists we need to return True, meaning that the vxlan
920 # needs to be flapped because we detected a vxlan-physdev change
921 if link_exists:
922 self.logger.info("%s: vxlan-physdev configuration changed: flapping vxlan device required" % ifaceobj.name)
923 return True
924
925 return False
926
84c47c4f
RP
927 def single_vxlan_device_vni_filter(self, ifaceobj):
928 vnis = []
929 for vlan_vni_map in ifaceobj.get_attr_value("bridge-vlan-vni-map"):
7f0310a7
RP
930 try:
931 (vls, vis) = utils.get_vlan_vnis_in_map(vlan_vni_map)
932 vnis.extend(vis)
933 except Exception as e:
934 self.logger.error("%s: %s (%s)" %(ifaceobj.name, vlan_vni_map, str(e)))
935 return
84c47c4f
RP
936 self.iproute2.bridge_link_update_vni_filter(ifaceobj.name, vnis)
937
223ba5af
JF
938 def _up(self, ifaceobj):
939 vxlan_id_str = ifaceobj.get_attr_value_first("vxlan-id")
940
e537a6e6
JF
941 if not ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN and not vxlan_id_str:
942 self.logger.warning("%s: missing vxlan-id attribute on vxlan device" % ifaceobj.name)
223ba5af
JF
943 return
944
945 ifname = ifaceobj.name
946 link_exists = self.cache.link_exists(ifname)
947
948 if link_exists:
949 # if link already exists make sure this is a vxlan
950 device_link_kind = self.cache.get_link_kind(ifname)
951
952 if device_link_kind != "vxlan":
953 self.logger.error(
954 "%s: device already exists and is not a vxlan (type %s)"
955 % (ifname, device_link_kind)
d486dd0d 956 )
223ba5af
JF
957 ifaceobj.set_status(ifaceStatus.ERROR)
958 return
d486dd0d 959
223ba5af
JF
960 # get vxlan running attributes
961 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
962 else:
963 cached_vxlan_ifla_info_data = {}
d486dd0d 964
223ba5af 965 user_request_vxlan_info_data = {}
d486dd0d 966
e537a6e6
JF
967 if vxlan_id_str:
968 # for single vxlan device we don't have a vxlan-id
969 self.__config_vxlan_id(ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
970
223ba5af
JF
971 self.__config_vxlan_learning(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
972 self.__config_vxlan_ageing(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
973 self.__config_vxlan_port(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
9144496d 974 vxlan_ttl = self.__config_vxlan_ttl(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
e521508b 975 self.__config_vxlan_tos(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
a8dd54b0 976 self.__config_vxlan_udp_csum(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
223ba5af
JF
977 local = self.__config_vxlan_local_tunnelip(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
978
40658337
JF
979 vxlan_mcast_grp = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp")
980 vxlan_svcnodeip = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip")
981
982 vxlan_mcast_grp6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp6")
983 vxlan_svcnodeip6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip6")
984
6015cce2 985 vxlan_mcast_grp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
1609696f
JF
986
987 vxlan_physdev = self.__get_vxlan_physdev(ifaceobj, vxlan_mcast_grp, vxlan_mcast_grp_map)
223ba5af 988
84c47c4f
RP
989 vxlan_vnifilter = self.__get_vxlan_attribute(ifaceobj, "vxlan-vnifilter")
990
223ba5af
JF
991 vxlan_physdev_changed = self.__config_vxlan_physdev(
992 link_exists,
993 ifaceobj,
994 vxlan_physdev,
995 user_request_vxlan_info_data,
996 cached_vxlan_ifla_info_data
997 )
998
999 group, multicast_group_changed = self.__config_vxlan_group(
1000 ifname,
1001 ifaceobj,
1002 link_exists,
1003 vxlan_mcast_grp,
1004 vxlan_svcnodeip,
1005 vxlan_physdev,
1006 user_request_vxlan_info_data,
1007 cached_vxlan_ifla_info_data
1008 )
1009
40658337
JF
1010 group6, multicast_group_changed6 = self.__config_vxlan_group6(
1011 ifname,
1012 ifaceobj,
1013 link_exists,
1014 vxlan_mcast_grp6,
1015 vxlan_svcnodeip6,
1016 vxlan_physdev,
1017 user_request_vxlan_info_data,
1018 cached_vxlan_ifla_info_data
1019 )
1020
1021 flap_vxlan_device = link_exists and (multicast_group_changed or multicast_group_changed6 or vxlan_physdev_changed)
223ba5af
JF
1022
1023 if user_request_vxlan_info_data:
1024
1025 if link_exists and not len(user_request_vxlan_info_data) > 1:
1026 # if the vxlan already exists it's already cached
1027 # user_request_vxlan_info_data always contains at least one
1028 # element: vxlan-id
1029 self.logger.info('%s: vxlan already exists - no change detected' % ifname)
1030 else:
e537a6e6 1031 if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
223ba5af 1032 if link_exists:
aa4c8a2d 1033 self.logger.warning("%s: updating existing single vxlan device is not supported yet "
56f34349 1034 "(please run 'ifdown %s; ifup %s')" % (ifname, ifname, ifname))
223ba5af 1035 else:
e537a6e6
JF
1036 self.iproute2.link_add_single_vxlan(
1037 ifname,
a7e5fb25 1038 local.ip if local else None,
792b9a07
JF
1039 group.ip if group else None,
1040 vxlan_physdev,
84c47c4f 1041 user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT),
9144496d
RP
1042 vxlan_vnifilter,
1043 vxlan_ttl
e537a6e6
JF
1044 )
1045 else:
1046 try:
1047 if flap_vxlan_device:
1048 self.netlink.link_down_force(ifname)
1049
1050 self.netlink.link_add_vxlan_with_info_data(ifname, user_request_vxlan_info_data)
1051
1052 if flap_vxlan_device:
1053 self.netlink.link_up_force(ifname)
1054 except Exception as e:
1055 if link_exists:
1056 self.log_error("%s: applying vxlan change failed: %s" % (ifname, str(e)), ifaceobj)
1057 else:
1058 self.log_error("%s: vxlan creation failed: %s" % (ifname, str(e)), ifaceobj)
1059 return
223ba5af 1060
84c47c4f
RP
1061 if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
1062 if vxlan_vnifilter and utils.get_boolean_from_string(vxlan_vnifilter):
1063 self.single_vxlan_device_vni_filter(ifaceobj)
8e167b9e 1064 self.single_vxlan_device_mcast_grp_map_fdb(ifaceobj, ifname, vxlan_mcast_grp_map)
84c47c4f 1065
223ba5af
JF
1066 vxlan_purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
1067
1068 remoteips = ifaceobj.get_attr_value('vxlan-remoteip')
1069 if remoteips:
1070 try:
1071 for remoteip in remoteips:
0e936c3f 1072 ipnetwork.IPv4Address(remoteip)
223ba5af
JF
1073 except Exception as e:
1074 self.log_error('%s: vxlan-remoteip: %s' % (ifaceobj.name, str(e)))
1075
1076 if vxlan_purge_remotes or remoteips:
1077 # figure out the diff for remotes and do the bridge fdb updates
1078 # only if provisioned by user and not by an vxlan external
1079 # controller.
0e936c3f
JF
1080 local_str = str(local)
1081
1082 if local_str and remoteips and local_str in remoteips:
1083 remoteips.remove(local_str)
1084
1085 peers = self.iproute2.get_vxlan_peers(ifaceobj.name, str(group.ip) if group else None)
1086
223ba5af
JF
1087 cur_peers = set(peers)
1088 if remoteips:
1089 new_peers = set(remoteips)
1090 del_list = cur_peers.difference(new_peers)
1091 add_list = new_peers.difference(cur_peers)
1092 else:
1093 del_list = cur_peers
1094 add_list = []
d486dd0d 1095
223ba5af 1096 for addr in del_list:
d486dd0d 1097 try:
223ba5af
JF
1098 self.iproute2.bridge_fdb_del(
1099 ifaceobj.name,
1100 "00:00:00:00:00:00",
1101 None, True, addr
1102 )
3218f49d 1103 except Exception:
d486dd0d 1104 pass
d486dd0d 1105
223ba5af 1106 for addr in add_list:
d486dd0d 1107 try:
223ba5af
JF
1108 self.iproute2.bridge_fdb_append(
1109 ifaceobj.name,
1110 "00:00:00:00:00:00",
1111 None, True, addr
1112 )
3218f49d 1113 except Exception:
223ba5af 1114 pass
d486dd0d 1115
ca436937
JF
1116 @staticmethod
1117 def get_vxlan_fdb_src_vni(vxlan_mcast_grp_map):
1118 fdbs = []
56f34349 1119 if vxlan_mcast_grp_map:
6015cce2 1120 for src_vni, dst_ip in vxlan_mcast_grp_map.items():
ca436937
JF
1121 fdbs.append(("00:00:00:00:00:00", src_vni, dst_ip))
1122 return fdbs
1123
56f34349
JF
1124 @staticmethod
1125 def get_svd_running_fdb(ifname):
1126 vxlan_fdb_data = utils.exec_command("bridge fdb show dev %s" % ifname)
1127 current_fdb = []
1128
1129 if vxlan_fdb_data:
1130 # each entry should look like the following:
1131 # 00:00:00:00:00:00 dst 239.1.1.100 src_vni 1000 self permanent
1132 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]:
1133 mac, _, dst, _, src_vni = entry.split()[0:5]
1134 current_fdb.append((mac, src_vni, dst))
1135
1136 return current_fdb
1137
1138 def single_vxlan_device_mcast_grp_map_fdb(self, ifaceobj, ifname, vxlan_mcast_grp_map):
1139 # in this piece of code we won't be checking the running state of the fdb table
1140 # dumping all fdb entries would cause scalability issues in certain cases.
1141
1142 # pulling old mcastgrp-map configuration
1143 old_user_config_fdb = []
1144
1145 for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
6015cce2 1146 old_user_config_fdb += self.get_vxlan_fdb_src_vni(self.__get_vxlan_mcastgrp_map(old_ifaceobj))
56f34349
JF
1147
1148 # new user configuration
1149 user_config_fdb = self.get_vxlan_fdb_src_vni(vxlan_mcast_grp_map)
1150
1151 # compare old and new config to know if we should remove any stale fdb entries.
1152 fdb_entries_to_remove = set(old_user_config_fdb) - set(user_config_fdb)
1153
1154 if fdb_entries_to_remove:
1155 for mac, src_vni, dst_ip in fdb_entries_to_remove:
1156 try:
1157 self.iproute2.bridge_fdb_del_src_vni(ifname, mac, src_vni)
1158 except Exception as e:
1159 if "no such file or directory" not in str(e).lower():
1160 self.logger.warning("%s: removing stale fdb entries failed: %s" % (ifname, str(e)))
1161
1162 if not user_config_fdb:
1163 # if vxlan-mcastgrp-map wasn't configure return
1164 return
1165
1166 for mac, src_vni, dst_ip in user_config_fdb:
1167 try:
1168 self.iproute2.bridge_fdb_add_src_vni(ifname, src_vni, dst_ip)
1169 except Exception as e:
1170 if "file exists" not in str(e).lower():
1171 ifaceobj.set_status(ifaceStatus.ERROR)
1172 self.log_error(
1173 "%s: vxlan-mcastgrp-map: %s=%s: %s"
1174 % (ifname, src_vni, dst_ip, str(e)), raise_error=False
1175 )
1176
d486dd0d
JF
1177 def _down(self, ifaceobj):
1178 try:
223ba5af 1179 self.netlink.link_del(ifaceobj.name)
3b01ed76 1180 except Exception as e:
d486dd0d
JF
1181 self.log_warn(str(e))
1182
223ba5af
JF
1183 @staticmethod
1184 def _query_check_n_update(ifaceobj, ifaceobjcurr, attrname, attrval, running_attrval):
d486dd0d
JF
1185 if not ifaceobj.get_attr_value_first(attrname):
1186 return
1187 if running_attrval and attrval == running_attrval:
223ba5af 1188 ifaceobjcurr.update_config_with_status(attrname, attrval, 0)
d486dd0d 1189 else:
223ba5af 1190 ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1)
d486dd0d 1191
223ba5af
JF
1192 @staticmethod
1193 def _query_check_n_update_addresses(ifaceobjcurr, attrname, addresses, running_addresses):
d486dd0d
JF
1194 if addresses:
1195 for a in addresses:
1196 if a in running_addresses:
1197 ifaceobjcurr.update_config_with_status(attrname, a, 0)
1198 else:
1199 ifaceobjcurr.update_config_with_status(attrname, a, 1)
3b01ed76
JF
1200 running_addresses = set(running_addresses).difference(
1201 set(addresses))
223ba5af 1202 [ifaceobjcurr.update_config_with_status(attrname, a, 1) for a in running_addresses]
d486dd0d
JF
1203
1204 def _query_check(self, ifaceobj, ifaceobjcurr):
223ba5af
JF
1205 ifname = ifaceobj.name
1206
1207 if not self.cache.link_exists(ifname):
d486dd0d 1208 return
d486dd0d 1209
223ba5af
JF
1210 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1211
1212 if not cached_vxlan_ifla_info_data:
1213 ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, self.get_mod_attrs(), -1)
1214 return
1215
1216 for vxlan_attr_str, vxlan_attr_nl, callable_type in (
1217 ('vxlan-id', Link.IFLA_VXLAN_ID, int),
1218 ('vxlan-ttl', Link.IFLA_VXLAN_TTL, int),
e521508b 1219 ('vxlan-tos', Link.IFLA_VXLAN_TOS, int),
223ba5af
JF
1220 ('vxlan-port', Link.IFLA_VXLAN_PORT, int),
1221 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, int),
0e936c3f 1222 ('vxlan-mcastgrp', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
40658337 1223 ('vxlan-mcastgrp6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
0e936c3f 1224 ('vxlan-svcnodeip', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
40658337 1225 ('vxlan-svcnodeip6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
223ba5af
JF
1226 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, lambda x: self.cache.get_ifindex(x)),
1227 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
e521508b 1228 ('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
223ba5af
JF
1229 ):
1230 vxlan_attr_value = ifaceobj.get_attr_value_first(vxlan_attr_str)
d486dd0d 1231
223ba5af
JF
1232 if not vxlan_attr_value:
1233 continue
1234
1235 cached_vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
1236
1237 try:
1238 vxlan_attr_value_nl = callable_type(vxlan_attr_value)
1239 except Exception as e:
1240 self.logger.warning('%s: %s: %s' % (ifname, vxlan_attr_str, str(e)))
1241 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
1242 continue
1243
1244 if vxlan_attr_value_nl == cached_vxlan_attr_value:
1245 ifaceobjcurr.update_config_with_status(vxlan_attr_str, vxlan_attr_value, 0)
1246 else:
1247 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
1248
1249 #
1250 # vxlan-local-tunnelip
1251 #
1252 running_attrval = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
d486dd0d
JF
1253 attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
1254 if not attrval:
223ba5af 1255 attrval = self._vxlan_local_tunnelip
0e936c3f 1256 # TODO: vxlan._vxlan_local_tunnelip should be a ipnetwork.IPNetwork obj
d486dd0d
JF
1257 ifaceobj.update_config('vxlan-local-tunnelip', attrval)
1258
223ba5af 1259 if str(running_attrval) == self._clagd_vxlan_anycast_ip:
d486dd0d
JF
1260 # if local ip is anycast_ip, then let query_check to go through
1261 attrval = self._clagd_vxlan_anycast_ip
d486dd0d 1262
223ba5af
JF
1263 self._query_check_n_update(
1264 ifaceobj,
1265 ifaceobjcurr,
1266 'vxlan-local-tunnelip',
1267 str(attrval),
0e936c3f 1268 str(running_attrval.ip) if running_attrval else None
223ba5af 1269 )
d486dd0d 1270
223ba5af
JF
1271 #
1272 # vxlan-remoteip
1273 #
1274 purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
d486dd0d
JF
1275 if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'):
1276 # If purge remotes or if vxlan-remoteip's are set
1277 # in the config file, we are owners of the installed
1278 # remote-ip's, lets check and report any remote ips we don't
1279 # understand
223ba5af 1280 cached_svcnode = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
d486dd0d 1281
223ba5af
JF
1282 self._query_check_n_update_addresses(
1283 ifaceobjcurr,
1284 'vxlan-remoteip',
1285 ifaceobj.get_attr_value('vxlan-remoteip'),
0e936c3f 1286 self.iproute2.get_vxlan_peers(ifaceobj.name, str(cached_svcnode.ip) if cached_svcnode else None)
223ba5af 1287 )
a382b488 1288
e6edcd21
JF
1289 #
1290 # vxlan-mcastgrp-map
1291 #
1292 user_mcastgrp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
1293
1294 if user_mcastgrp_map:
1295 status = True
1296 ifquery_mcastgrp_map = dict(user_mcastgrp_map)
1297
1298 try:
1299 svd_fdb = self.get_svd_running_fdb(ifname)
1300
1301 if svd_fdb:
1302 svd_fdb_map = {}
1303
1304 for _, vni, ip in svd_fdb:
1305 svd_fdb_map[int(vni)] = ip
1306
1307 else:
1308 svd_fdb_map = {}
1309
1310 saved_running_config = dict(svd_fdb_map)
1311
1312 for vni, ip in dict(user_mcastgrp_map).items():
1313
1314 if vni not in svd_fdb_map or svd_fdb_map[vni] != str(ip):
1315 status = False
1316 break
1317
1318 del user_mcastgrp_map[vni]
1319 del svd_fdb_map[vni]
1320
1321 # if the user config map is not empty it means that not all mcastgrp are configured
1322 if user_mcastgrp_map:
1323 status = False
1324
1325 # if the running config map is not empty it means that we have extra mcastgrp configured
1326 if svd_fdb_map:
1327 status = False
1328
1329 if not status:
1330 ifquery_mcastgrp_map = saved_running_config
1331
1332 except Exception as e:
1333 self.logger.info("vxlan-mcastgrp-map: error: %s" % str(e))
1334 status = False
1335
1336 ifaceobjcurr.update_config_with_status(
1337 "vxlan-mcastgrp-map",
1338 " ".join(["%s=%s" % (vni, ip) for vni, ip in ifquery_mcastgrp_map.items()]),
1339 not status
1340 )
1341
223ba5af
JF
1342 def _query_running(self, ifaceobjrunning):
1343 ifname = ifaceobjrunning.name
a382b488 1344
223ba5af
JF
1345 if not self.cache.link_exists(ifname):
1346 return
a382b488 1347
223ba5af 1348 if not self.cache.get_link_kind(ifname) == 'vxlan':
d486dd0d 1349 return
223ba5af
JF
1350
1351 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1352
1353 if not cached_vxlan_ifla_info_data:
d486dd0d
JF
1354 return
1355
223ba5af
JF
1356 #
1357 # vxlan-id
1358 #
1359 vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
1360
1361 if not vxlan_id:
1362 # no vxlan id, meaning this not a vxlan
1363 return
1364
1365 ifaceobjrunning.update_config('vxlan-id', str(vxlan_id))
1366
1367 #
1368 # vxlan-port
1369 #
1370 vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
1371
1372 if vxlan_port:
1373 ifaceobjrunning.update_config('vxlan-port', vxlan_port)
1374
1375 #
1376 # vxlan-svcnode
1377 #
1378 vxlan_svcnode_value = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
1379
1380 if vxlan_svcnode_value:
1381 vxlan_svcnode_value = str(vxlan_svcnode_value)
1382 ifaceobjrunning.update_config('vxlan-svcnode', vxlan_svcnode_value)
d486dd0d 1383
223ba5af
JF
1384 #
1385 # vxlan-remoteip
1386 #
1387 purge_remotes = self.__get_vlxan_purge_remotes(None)
d486dd0d
JF
1388 if purge_remotes:
1389 # if purge_remotes is on, it means we own the
1390 # remote ips. Query them and add it to the running config
223ba5af 1391 attrval = self.iproute2.get_vxlan_peers(ifname, vxlan_svcnode_value)
d486dd0d 1392 if attrval:
223ba5af
JF
1393 [ifaceobjrunning.update_config('vxlan-remoteip', a) for a in attrval]
1394
1395 #
1396 # vxlan-link
1397 # vxlan-ageing
1398 # vxlan-learning
1399 # vxlan-local-tunnelip
1400 #
1401 for vxlan_attr_name, vxlan_attr_nl, callable_netlink_value_to_string in (
1402 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, self._get_ifname_for_ifindex),
1403 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, str),
1404 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda value: 'on' if value else 'off'),
e521508b 1405 ('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda value: 'on' if value else 'off'),
223ba5af
JF
1406 ('vxlan-local-tunnelip', Link.IFLA_VXLAN_LOCAL, str),
1407 ):
1408 vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
1409
1410 if vxlan_attr_value is not None:
dc74ceda 1411 vxlan_attr_value_str = callable_netlink_value_to_string(vxlan_attr_value)
223ba5af
JF
1412
1413 if vxlan_attr_value:
1414 ifaceobjrunning.update_config(vxlan_attr_name, vxlan_attr_value_str)
1415
1416 def _get_ifname_for_ifindex(self, ifindex):
1417 """
1418 we need this middle-man function to query the cache
1419 cache.get_ifname can raise KeyError, we need to catch
1420 it and return None
1421 """
1422 try:
1423 return self.cache.get_ifname(ifindex)
1424 except KeyError:
1425 return None
a382b488 1426
223ba5af
JF
1427 _run_ops = {
1428 "pre-up": _up,
1429 "post-down": _down,
1430 "query-running": _query_running,
1431 "query-checkcurr": _query_check
1432 }
d486dd0d
JF
1433
1434 def get_ops(self):
3b01ed76 1435 return list(self._run_ops.keys())
d486dd0d 1436
d486dd0d
JF
1437 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
1438 op_handler = self._run_ops.get(operation)
1439 if not op_handler:
1440 return
223ba5af
JF
1441
1442 if operation != 'query-running':
1443 if not self._is_vxlan_device(ifaceobj):
1444 return
1445
1446 if not self.vxlan_mcastgrp_ref \
1447 and self.vxlan_physdev_mcast \
1448 and self.cache.link_exists(self.vxlan_physdev_mcast):
1449 self.netlink.link_del(self.vxlan_physdev_mcast)
1450 self.reset()
1451
d486dd0d
JF
1452 if operation == 'query-checkcurr':
1453 op_handler(self, ifaceobj, query_ifaceobj)
1454 else:
1455 op_handler(self, ifaceobj)