]> git.proxmox.com Git - mirror_ifupdown2.git/blame_incremental - ifupdown2/addons/vxlan.py
addons: address: fix merge-indentation issue
[mirror_ifupdown2.git] / ifupdown2 / addons / vxlan.py
... / ...
CommitLineData
1#!/usr/bin/env python3
2#
3# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4# Author: Roopa Prabhu, roopa@cumulusnetworks.com
5#
6
7from ipaddress import IPv4Network, IPv4Address, AddressValueError, ip_address
8try:
9 import ifupdown2.nlmanager.ipnetwork as ipnetwork
10 import ifupdown2.ifupdown.policymanager as policymanager
11 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
12
13 from ifupdown2.lib.addon import Vxlan
14 from ifupdown2.lib.nlcache import NetlinkCacheIfnameNotFoundError
15
16 from ifupdown2.nlmanager.nlmanager import Link
17
18 from ifupdown2.ifupdown.iface import ifaceLinkKind, ifaceLinkPrivFlags, ifaceStatus, iface
19 from ifupdown2.ifupdown.utils import utils
20 from ifupdown2.ifupdown.statemanager import statemanager_api as statemanager
21 from ifupdown2.ifupdownaddons.cache import *
22 from ifupdown2.ifupdownaddons.modulebase import moduleBase
23
24except (ImportError, ModuleNotFoundError):
25 import nlmanager.ipnetwork as ipnetwork
26 import ifupdown.policymanager as policymanager
27 import ifupdown.ifupdownflags as ifupdownflags
28
29 from lib.addon import Vxlan
30 from lib.nlcache import NetlinkCacheIfnameNotFoundError
31
32 from nlmanager.nlmanager import Link
33
34 from ifupdown.iface import ifaceLinkKind, ifaceLinkPrivFlags, ifaceStatus, iface
35 from ifupdown.utils import utils
36 from ifupdown.statemanager import statemanager_api as statemanager
37
38 from ifupdownaddons.cache import *
39 from ifupdownaddons.modulebase import moduleBase
40
41
42class vxlan(Vxlan, 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": {
58 "help": "vxlan svc node id",
59 "validvals": ["<ipv4>"],
60 "example": ["vxlan-svcnodeip 172.16.22.125"]
61 },
62 "vxlan-svcnodeip6": {
63 "help": "vxlan svc node ip",
64 "validvals": ["<ipv6>"],
65 "example": ["vxlan-svcnodeip6 2001:DB8:8086:6502::"]
66 },
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",
104 "validrange": ["0", "255"],
105 "validvals": ["<number>", "auto"],
106 "example": ['vxlan-ttl 42'],
107 },
108 "vxlan-tos": {
109 "help": "specifies the ToS value (range 0..255), 1=inherit",
110 "validrange": ["0", "255"],
111 "validvals": ["<number>", "inherit"],
112 "example": ['vxlan-tos 42'],
113 },
114 "vxlan-mcastgrp": {
115 "help": "vxlan multicast group",
116 "validvals": ["<ip>"],
117 "example": ["vxlan-mcastgrp 172.16.22.127"],
118 },
119 "vxlan-mcastgrp6": {
120 "help": "vxlan multicast group",
121 "validvals": ["<ip6>"],
122 "example": ["vxlan-mcastgrp ff02::15c"],
123 },
124 "vxlan-mcastgrp-map": {
125 "help": "vxlan multicast group for single-vxlan device -"
126 "doesn't support multiline attribute",
127 "example": ["vxlan-mcastgrp-map 1000=239.1.1.100 1001=239.1.1.200"],
128 },
129 "vxlan-vnifilter": {
130 "help": "vxlan vni filter for single-vxlan device",
131 "validvals": ["on", "off"],
132 "default": "off",
133 "example": ["vxlan-vnifilter yes"],
134 },
135 "vxlan-remoteip-map": {
136 "help": "static HREP entries for static single vxlan device",
137 "example": ["vxlan-remoteip-map 1000-1002=27.0.0.10-27.0.0.12"],
138 },
139 "vxlan-udp-csum": {
140 "help": "whether to perform checksumming or not",
141 "validvals": ["yes", "no"],
142 "example": ["vxlan-udp-csum no"]
143 },
144 "vxlan-vni": {
145 "help": "L3 VxLAN interface (vni list and range are supported)",
146 "validvals": ["<number>"],
147 "example": ["vxlan-vni 42"]
148 }
149 }
150 }
151
152 VXLAN_PHYSDEV_MCASTGRP_DEFAULT = "ipmr-lo"
153
154 def __init__(self, *args, **kargs):
155 Vxlan.__init__(self)
156 moduleBase.__init__(self, *args, **kargs)
157
158 self._vxlan_purge_remotes = utils.get_boolean_from_string(
159 policymanager.policymanager_api.get_module_globals(
160 module_name=self.__class__.__name__,
161 attr="vxlan-purge-remotes"
162 )
163 )
164 self._vxlan_local_tunnelip = None
165 self._clagd_vxlan_anycast_ip = ""
166
167 # If mcastgrp is specified we need to rely on a user-configred device (via physdev)
168 # or via a policy variable "vxlan-physdev_mcastgrp". If the device doesn't exist we
169 # create it as a dummy device. We need to keep track of the user configuration to
170 # know when to delete this dummy device (when user remove mcastgrp from it's config)
171 self.vxlan_mcastgrp_ref = False
172 self.vxlan_physdev_mcast = policymanager.policymanager_api.get_module_globals(
173 module_name=self.__class__.__name__,
174 attr="vxlan-physdev-mcastgrp"
175 ) or self.VXLAN_PHYSDEV_MCASTGRP_DEFAULT
176
177 self.tvd_svd_mix_support = utils.get_boolean_from_string(
178 policymanager.policymanager_api.get_module_globals(
179 module_name=self.__class__.__name__,
180 attr="vxlan-support-mix-dev-types"
181 ),
182 default=True
183 )
184
185 self.svd_tvd_errors = {}
186
187 def reset(self):
188 # in daemon mode we need to reset mcastgrp_ref for every new command
189 # this variable has to be set in get_dependent_ifacenames
190 self.vxlan_mcastgrp_ref = False
191
192 def syntax_check(self, ifaceobj, ifaceobj_getfunc):
193 if self._is_vxlan_device(ifaceobj):
194 if not ifaceobj.get_attr_value_first('vxlan-local-tunnelip') and not self._vxlan_local_tunnelip:
195 self.logger.warning('%s: missing vxlan-local-tunnelip' % ifaceobj.name)
196 return False
197
198 self.check_and_raise_svd_tvd_errors(ifaceobj)
199
200 return self.syntax_check_localip_anycastip_equal(
201 ifaceobj.name,
202 ifaceobj.get_attr_value_first('vxlan-local-tunnelip') or self._vxlan_local_tunnelip,
203 self._clagd_vxlan_anycast_ip
204 )
205 return True
206
207 def syntax_check_localip_anycastip_equal(self, ifname, local_ip, anycast_ip):
208 try:
209 if local_ip and anycast_ip and ipnetwork.IPNetwork(local_ip) == ipnetwork.IPNetwork(anycast_ip):
210 self.logger.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
211 % (ifname, local_ip))
212 return False
213 except Exception:
214 pass
215 return True
216
217 def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None, old_ifaceobjs=False):
218 if ifaceobj.get_attr_value_first("bridge-vlan-vni-map"):
219 ifaceobj.link_privflags |= ifaceLinkPrivFlags.SINGLE_VXLAN
220
221 if self._is_vxlan_device(ifaceobj):
222 ifaceobj.link_kind |= ifaceLinkKind.VXLAN
223 self._set_global_local_ip(ifaceobj)
224
225 self.__check_and_tag_l3vxi(ifaceobj)
226
227 if not old_ifaceobjs and not self.tvd_svd_mix_support:
228 # mixing TVD and SVD is not supported - we need to warn the user
229 # we use a dictionary to make sure to only warn once and prevent each
230 # vxlan from being configured on the system
231
232 if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
233 self.single_vxlan_configured.add(ifaceobj.name)
234
235 if self.traditional_vxlan_configured:
236 self.svd_tvd_errors[ifaceobj.name] = (
237 "%s: mixing single-vxlan-device with tradional %s is not supported (TVD: %s)"
238 % (ifaceobj.name, "vxlans" if len(self.traditional_vxlan_configured) > 1 else "vxlan", ", ".join(self.traditional_vxlan_configured))
239 )
240 elif ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
241 pass
242 else:
243 self.traditional_vxlan_configured.add(ifaceobj.name)
244
245 if self.single_vxlan_configured:
246 self.svd_tvd_errors[ifaceobj.name] = (
247 "%s: mixing traditional vxlan with single vxlan %s is not supported (SVD: %s)"
248 % (ifaceobj.name, "devices" if len(self.single_vxlan_configured) > 1 else "device", ", ".join(self.single_vxlan_configured))
249 )
250
251
252 # if we detect a vxlan we check if mcastgrp is set (if so we set vxlan_mcastgrp_ref)
253 # to know when to delete this device.
254 if not self.vxlan_mcastgrp_ref and (ifaceobj.get_attr_value("vxlan-mcastgrp") or ifaceobj.get_attr_value("vxlan-mcastgrp-map")):
255 self.vxlan_mcastgrp_ref = True
256
257 elif ifaceobj.name == 'lo' and not old_ifaceobjs:
258 clagd_vxlan_list = ifaceobj.get_attr_value('clagd-vxlan-anycast-ip')
259 if clagd_vxlan_list:
260 if len(clagd_vxlan_list) != 1:
261 self.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one'
262 % (ifaceobj.name,))
263 self._clagd_vxlan_anycast_ip = clagd_vxlan_list[0]
264
265 self._set_global_local_ip(ifaceobj)
266
267 # If we should use a specific underlay device for the VXLAN
268 # tunnel make sure this device is set up before the VXLAN iface.
269 physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
270
271 if physdev:
272 return [physdev]
273
274 return None
275
276 def __check_and_tag_l3vxi(self, ifaceobj):
277 if ifaceobj.get_attr_value_first("vxlan-vni"):
278 # to validate the l3vxi interface we need to see the vrf attribute
279 if ifaceobj.get_attr_value_first("vrf"):
280 ifaceobj.link_privflags |= ifaceLinkPrivFlags.L3VXI
281 else:
282 self.logger.warning("%s: l3vxi misconfiguration? missing `vrf` attribute" % ifaceobj.name)
283
284 def _set_global_local_ip(self, ifaceobj):
285 vxlan_local_tunnel_ip = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
286 if vxlan_local_tunnel_ip and not self._vxlan_local_tunnelip:
287 self._vxlan_local_tunnelip = vxlan_local_tunnel_ip
288
289 @staticmethod
290 def _is_vxlan_device(ifaceobj):
291 return ifaceobj.link_kind & ifaceLinkKind.VXLAN \
292 or ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN \
293 or ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI \
294 or ifaceobj.get_attr_value_first("vxlan-id") \
295 or ifaceobj.get_attr_value_first("vxlan-vni") \
296 or ifaceobj.get_attr_value_first("bridge-vlan-vni-map")
297
298 def __get_vlxan_purge_remotes(self, ifaceobj):
299 if not ifaceobj:
300 return self._vxlan_purge_remotes
301 purge_remotes = ifaceobj.get_attr_value_first('vxlan-purge-remotes')
302 if purge_remotes:
303 purge_remotes = utils.get_boolean_from_string(purge_remotes)
304 else:
305 purge_remotes = self._vxlan_purge_remotes
306 return purge_remotes
307
308 def get_vxlan_ttl_from_string(self, ttl_config):
309 ttl = 0
310 if ttl_config:
311 if ttl_config.lower() == "auto":
312 ttl = 0
313 else:
314 ttl = int(ttl_config)
315 return ttl
316
317 def get_vxlan_tos_from_string(self, tos_config):
318 if tos_config:
319 if tos_config.lower() == "inherit":
320 return 1
321 else:
322 return int(tos_config)
323 return None
324
325 def __config_vxlan_id(self, ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
326 """
327 Get vxlan-id user config and check it's value before inserting it in our netlink dictionary
328 :param ifname:
329 :param ifaceobj:
330 :param vxlan_id_str:
331 :param user_request_vxlan_info_data:
332 :param cached_vxlan_ifla_info_data:
333 :return:
334 """
335 try:
336 vxlan_id = int(vxlan_id_str)
337 cached_vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
338
339 if cached_vxlan_id and cached_vxlan_id != vxlan_id:
340 self.log_error(
341 "%s: Cannot change running vxlan id (%s): Operation not supported"
342 % (ifname, cached_vxlan_id),
343 ifaceobj
344 )
345 user_request_vxlan_info_data[Link.IFLA_VXLAN_ID] = vxlan_id
346 except ValueError:
347 self.log_error("%s: invalid vxlan-id '%s'" % (ifname, vxlan_id_str), ifaceobj)
348
349 def __get_vxlan_ageing_int(self, ifname, ifaceobj, link_exists):
350 """
351 Get vxlan-ageing user config or via policy, return integer value, None or raise on error
352 :param ifname:
353 :param ifaceobj:
354 :param link_exists:
355 :return:
356 """
357 vxlan_ageing_str = ifaceobj.get_attr_value_first("vxlan-ageing")
358 try:
359 if vxlan_ageing_str:
360 return int(vxlan_ageing_str)
361
362 vxlan_ageing_str = policymanager.policymanager_api.get_attr_default(
363 module_name=self.__class__.__name__,
364 attr="vxlan-ageing"
365 )
366
367 if not vxlan_ageing_str and link_exists:
368 # if link doesn't exist we let the kernel define ageing
369 vxlan_ageing_str = self.get_attr_default_value("vxlan-ageing")
370
371 if vxlan_ageing_str:
372 return int(vxlan_ageing_str)
373 except Exception:
374 self.log_error("%s: invalid vxlan-ageing '%s'" % (ifname, vxlan_ageing_str), ifaceobj)
375
376 def __config_vxlan_ageing(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
377 """
378 Check user config vxlan-ageing and insert it in our netlink dictionary if needed
379 """
380 vxlan_ageing = self.__get_vxlan_ageing_int(ifname, ifaceobj, link_exists)
381
382 if not vxlan_ageing or (link_exists and vxlan_ageing == cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_AGEING)):
383 return
384
385 self.logger.info("%s: set vxlan-ageing %s" % (ifname, vxlan_ageing))
386 user_request_vxlan_info_data[Link.IFLA_VXLAN_AGEING] = vxlan_ageing
387
388 def __config_vxlan_port(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
389 """
390 Check vxlan-port user config, validate the integer value and insert it in the netlink dictionary if needed
391 :param ifname:
392 :param ifaceobj:
393 :param link_exists:
394 :param user_request_vxlan_info_data:
395 :param cached_vxlan_ifla_info_data:
396 :return:
397 """
398 vxlan_port_str = ifaceobj.get_attr_value_first("vxlan-port")
399 try:
400 if not vxlan_port_str:
401 vxlan_port_str = policymanager.policymanager_api.get_attr_default(
402 module_name=self.__class__.__name__,
403 attr="vxlan-port"
404 )
405
406 try:
407 vxlan_port = int(vxlan_port_str)
408 except TypeError:
409 # TypeError means vxlan_port was None
410 # ie: not provided by the user or the policy
411 vxlan_port = self.netlink.VXLAN_UDP_PORT
412 except ValueError as e:
413 self.logger.warning(
414 "%s: vxlan-port: using default %s: invalid configured value %s"
415 % (ifname, self.netlink.VXLAN_UDP_PORT, str(e))
416 )
417 vxlan_port = self.netlink.VXLAN_UDP_PORT
418
419 cached_vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
420
421 if link_exists:
422 if vxlan_port != cached_vxlan_port:
423 self.logger.warning(
424 "%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s"
425 % (ifname, cached_vxlan_port, ifname, ifname)
426 )
427 return
428
429 self.logger.info("%s: set vxlan-port %s" % (ifname, vxlan_port))
430 user_request_vxlan_info_data[Link.IFLA_VXLAN_PORT] = vxlan_port
431 except Exception:
432 self.log_error("%s: invalid vxlan-port '%s'" % (ifname, vxlan_port_str), ifaceobj)
433
434 def __config_vxlan_tos(self, ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
435 """
436 Get vxlan-tos from user config or policy, validate integer value and insert in netlink dict
437 :param ifname:
438 :param ifaceobj:
439 :param user_request_vxlan_info_data:
440 :param cached_vxlan_ifla_info_data:
441 :return:
442 """
443 vxlan_tos_str = ifaceobj.get_attr_value_first("vxlan-tos")
444 try:
445 if vxlan_tos_str:
446 vxlan_tos = self.get_vxlan_tos_from_string(vxlan_tos_str)
447 else:
448 vxlan_tos = self.get_vxlan_tos_from_string(
449 policymanager.policymanager_api.get_attr_default(
450 module_name=self.__class__.__name__,
451 attr="vxlan-tos"
452 )
453 )
454
455 if not vxlan_tos_str:
456 return
457
458 cached_ifla_vxlan_tos = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_TOS)
459
460 if vxlan_tos != cached_ifla_vxlan_tos:
461
462 if cached_ifla_vxlan_tos is not None:
463 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))
464 else:
465 self.logger.info("%s: set vxlan-tos %s" % (ifname, vxlan_tos_str if vxlan_tos_str else vxlan_tos))
466
467 user_request_vxlan_info_data[Link.IFLA_VXLAN_TOS] = vxlan_tos
468 except Exception:
469 self.log_error("%s: invalid vxlan-tos '%s'" % (ifname, vxlan_tos_str), ifaceobj)
470
471 def __config_vxlan_ttl(self, ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
472 """
473 Get vxlan-ttl from user config or policy, validate integer value and insert in netlink dict
474 :param ifname:
475 :param ifaceobj:
476 :param user_request_vxlan_info_data:
477 :param cached_vxlan_ifla_info_data:
478 :return:
479 """
480 vxlan_ttl_str = ifaceobj.get_attr_value_first("vxlan-ttl")
481 try:
482 if vxlan_ttl_str:
483 vxlan_ttl = self.get_vxlan_ttl_from_string(vxlan_ttl_str)
484 else:
485 vxlan_ttl = self.get_vxlan_ttl_from_string(
486 policymanager.policymanager_api.get_attr_default(
487 module_name=self.__class__.__name__,
488 attr="vxlan-ttl"
489 )
490 )
491
492 cached_ifla_vxlan_ttl = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_TTL)
493 if vxlan_ttl != cached_ifla_vxlan_ttl:
494
495 if cached_ifla_vxlan_ttl is not None:
496 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))
497 else:
498 self.logger.info("%s: set vxlan-ttl %s" % (ifname, vxlan_ttl_str if vxlan_ttl_str else vxlan_ttl))
499
500 user_request_vxlan_info_data[Link.IFLA_VXLAN_TTL] = vxlan_ttl
501 return vxlan_ttl
502 except Exception:
503 self.log_error("%s: invalid vxlan-ttl '%s'" % (ifname, vxlan_ttl_str), ifaceobj)
504
505 def is_vxlan_on_a_clag_bridge(self, ifaceobj) -> bool:
506 return bool(ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT and self._clagd_vxlan_anycast_ip and self.is_process_running('clagd'))
507
508 def __config_vxlan_local_tunnelip(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
509 """
510 Get vxlan-local-tunnelip user config or policy, validate ip address format and insert in netlink dict
511 :param ifname:
512 :param ifaceobj:
513 :param link_exists:
514 :param user_request_vxlan_info_data:
515 :param cached_vxlan_ifla_info_data:
516 :return:
517 """
518 local = ifaceobj.get_attr_value_first("vxlan-local-tunnelip")
519
520 if not local and self._vxlan_local_tunnelip:
521 local = self._vxlan_local_tunnelip
522
523 if link_exists:
524 cached_ifla_vxlan_local = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
525
526 # on ifreload do not overwrite anycast_ip to individual ip
527 # if clagd has modified
528 if self._clagd_vxlan_anycast_ip and cached_ifla_vxlan_local:
529 anycastip = ipnetwork.IPNetwork(self._clagd_vxlan_anycast_ip)
530
531 if (
532 anycastip == cached_ifla_vxlan_local
533 # there's a change that the cache hasn't been updated in
534 # time to reflect the new anycast ip set by clagd, extra checks:
535 or self.is_vxlan_on_a_clag_bridge(ifaceobj)
536 ):
537 local = cached_ifla_vxlan_local = anycastip
538 self.logger.info("%s: clagd-vxlan-anycast-ip (%s) inherited from loopback interface" % (ifname, local))
539 else:
540 cached_ifla_vxlan_local = None
541
542 if not local:
543 local = policymanager.policymanager_api.get_attr_default(
544 module_name=self.__class__.__name__,
545 attr="vxlan-local-tunnelip"
546 )
547
548 if local:
549 try:
550 local = ipnetwork.IPv4Address(local)
551
552 if local.initialized_with_prefixlen:
553 self.logger.warning("%s: vxlan-local-tunnelip %s: netmask ignored" % (ifname, local))
554
555 except Exception as e:
556 raise Exception("%s: invalid vxlan-local-tunnelip %s: %s" % (ifname, local, str(e)))
557
558
559 if local:
560 if local != cached_ifla_vxlan_local:
561 self.logger.info("%s: set vxlan-local-tunnelip %s" % (ifname, local))
562 user_request_vxlan_info_data[Link.IFLA_VXLAN_LOCAL] = local
563
564 # if both local-ip and anycast-ip are identical the function prints a warning
565 self.syntax_check_localip_anycastip_equal(ifname, local, self._clagd_vxlan_anycast_ip)
566 elif cached_ifla_vxlan_local:
567 self.logger.info("%s: removing vxlan-local-tunnelip (cache %s)" % (ifname, cached_ifla_vxlan_local))
568 user_request_vxlan_info_data[Link.IFLA_VXLAN_LOCAL] = None
569
570 return local
571
572 def __get_vxlan_attribute(self, ifaceobj, attr_name):
573 vxlan_attr_value = ifaceobj.get_attr_value_first(attr_name)
574
575 if not vxlan_attr_value:
576 vxlan_attr_value = policymanager.policymanager_api.get_attr_default(
577 module_name=self.__class__.__name__,
578 attr=attr_name
579 )
580
581 return vxlan_attr_value
582
583 def __syntax_check_vxlan_mcast_vni(self, ifaceobj, m, vni):
584 try:
585 int(vni)
586 except ValueError:
587 self.log_error('%s: vxlan-mcastgrp-map "%s" vni format is invalid' % (ifaceobj.name, m))
588
589 def __syntax_check_vxlan_mcast_vni_range(self, ifaceobj, m, vni_range):
590 if len(vni_range) != 2:
591 self.log_error('%s: vxlan-mcastgrp-map "%s" vni range format is invalid' % (ifaceobj.name, m))
592 for vni in vni_range:
593 self.__syntax_check_vxlan_mcast_vni(ifaceobj, m, vni)
594 if int(vni_range[0]) >= int(vni_range[1]):
595 self.log_error('%s: vxlan-mcastgrp-map "%s" vni range is invalid' % (ifaceobj.name, m))
596
597 def __syntax_check_vxlan_mcast_grp(self, ifaceobj, m, grp):
598 try:
599 ip = IPv4Address(grp)
600 except AddressValueError:
601 self.log_error('%s: vxlan-mcastgrp-map "%s" group format is invalid' % (ifaceobj.name, m))
602 if not ip.is_multicast:
603 self.log_error('%s: vxlan-mcastgrp-map "%s" group is not multicast' % (ifaceobj.name, m))
604
605 def __syntax_check_vxlan_mcast_grp_range(self, ifaceobj, m, grp_range):
606 if len(grp_range) != 2:
607 self.log_error('%s: vxlan-mcastgrp-map "%s" group format is invalid' % (ifaceobj.name, m))
608 for grp in grp_range:
609 self.__syntax_check_vxlan_mcast_grp(ifaceobj, m, grp)
610 if int(IPv4Address(grp_range[0])) >= int(IPv4Address(grp_range[1])):
611 self.log_error('%s: vxlan-mcastgrp-map "%s" group range is invalid' % (ifaceobj.name, m))
612
613 def __syntax_check_vxlan_mcast_network(self, ifaceobj, m, network, len_vni):
614 try:
615 ip = IPv4Network(network)
616 ip[0]
617 ip[len_vni - 1]
618 except IndexError:
619 self.log_error('%s: vxlan-mcastgrp-map "%s" network range is insufficient' % (ifaceobj.name, m))
620 except AddressValueError:
621 self.log_error('%s: vxlan-mcastgrp-map "%s" network format is invalid' % (ifaceobj.name, m))
622 if not ip.is_multicast:
623 self.log_error('%s: vxlan-mcastgrp-map "%s" network is not multicast' % (ifaceobj.name, m))
624
625 def __get_vxlan_mcastgrp_map(self, ifaceobj):
626 maps = ifaceobj.get_attr_value('vxlan-mcastgrp-map')
627 if not maps:
628 maps = policymanager.policymanager_api.get_attr_default(
629 module_name=self.__class__.__name__,
630 attr='vxlan-mcastgrp-map'
631 )
632 return maps
633
634 parsed_maps = {}
635 for m_line in maps:
636 # Cover single-line multi-entry case
637 map = m_line.split()
638 for m in map:
639 m_parts = m.split('=')
640 if len(m_parts) != 2:
641 self.log_error('%s: vxlan-mcastgrp-map %s format is invalid' % (ifaceobj.name, m))
642 vni = m_parts[0]
643 grp = m_parts[1]
644 _range = "-"
645 _network = "/"
646
647 # One to one mapping case
648 if _range not in vni and _range not in grp:
649 self.__syntax_check_vxlan_mcast_vni(ifaceobj, m, vni)
650 self.__syntax_check_vxlan_mcast_grp(ifaceobj, m, grp)
651 if int(vni) not in parsed_maps:
652 parsed_maps[int(vni)] = IPv4Address(grp)
653 else:
654 self.log_warn('%s: vxlan-mcastgrp-map %s vni %s duplicate' % (ifaceobj.name, vni, m))
655
656 # Many VNI case
657 if _range in vni:
658 v_parts = vni.split(_range)
659 self.__syntax_check_vxlan_mcast_vni_range(ifaceobj, m, v_parts)
660 vnis = list(range(int(v_parts[0]), int(v_parts[1]) + 1))
661
662 if _range not in grp and _network not in grp:
663 self.__syntax_check_vxlan_mcast_grp(ifaceobj, m, grp)
664 for i in vnis:
665 if i not in parsed_maps:
666 parsed_maps[i] = IPv4Address(grp)
667 else:
668 self.log_warn('%s: vxlan-mcastgrp-map %s vni %s duplicate' % (ifaceobj.name, vni, m))
669 else:
670 if _network in grp:
671 self.__syntax_check_vxlan_mcast_network(ifaceobj, m, grp, len(vnis))
672 network = IPv4Network(grp)
673 g_parts = [network[0], network[len(vnis) - 1]]
674 else:
675 g_parts = grp.split(_range)
676
677 self.__syntax_check_vxlan_mcast_grp_range(ifaceobj, m, g_parts)
678 grp_range = list(range(int(IPv4Address(g_parts[0])), int(IPv4Address(g_parts[1])) + 1))
679 if len(grp_range) != len(vnis):
680 self.log_error('%s: vxlan-mcastgrp-map "%s" range lengths do not match.'
681 % (ifaceobj.name, m))
682
683 for v, g in zip(vnis, grp_range):
684 if v not in parsed_maps:
685 parsed_maps[v] = IPv4Address(g)
686 else:
687 self.log_warn('%s: vxlan-mcastgrp-map %s vni %s duplicate' % (ifaceobj.name, v, m))
688
689 return parsed_maps
690
691 def __config_vxlan_group(self, ifname, ifaceobj, link_exists, mcast_grp, group, physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
692 """
693 vxlan-mcastgrp and vxlan-svcnodeip are mutually exclusive
694 this function validates ip format for both attribute and tries to understand
695 what the user really want (remote or group option).
696
697 :param ifname:
698 :param ifaceobj:
699 :param mcast_grp:
700 :param group:
701 :param physdev:
702 :param user_request_vxlan_info_data:
703 :param cached_vxlan_ifla_info_data:
704 :return:
705 """
706 if mcast_grp and group:
707 self.log_error("%s: both group (vxlan-mcastgrp %s) and "
708 "remote (vxlan-svcnodeip %s) cannot be specified"
709 % (ifname, mcast_grp, group), ifaceobj)
710
711 attribute_name = "vxlan-svcnodeip"
712 multicast_group_change = False
713
714 if group:
715 try:
716 group = ipnetwork.IPv4Address(group)
717
718 if group.initialized_with_prefixlen:
719 self.logger.warning("%s: vxlan-svcnodeip %s: netmask ignored" % (ifname, group))
720
721 except Exception as e:
722 raise Exception("%s: invalid vxlan-svcnodeip %s: %s" % (ifname, group, str(e)))
723
724 if group.ip.is_multicast:
725 self.logger.warning("%s: vxlan-svcnodeip %s: invalid group address, "
726 "for multicast IP please use attribute \"vxlan-mcastgrp\"" % (ifname, group))
727 # if svcnodeip is used instead of mcastgrp we warn the user
728 # if mcast_grp is not provided by the user we can instead
729 # use the svcnodeip value
730 if not physdev:
731 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp) requires 'vxlan-physdev' to be specified" % (ifname))
732
733 elif mcast_grp:
734 try:
735 mcast_grp = ipnetwork.IPv4Address(mcast_grp)
736
737 if mcast_grp.initialized_with_prefixlen:
738 self.logger.warning("%s: vxlan-mcastgrp %s: netmask ignored" % (ifname, mcast_grp))
739
740 except Exception as e:
741 raise Exception("%s: invalid vxlan-mcastgrp %s: %s" % (ifname, mcast_grp, str(e)))
742
743 if not mcast_grp.ip.is_multicast:
744 self.logger.warning("%s: vxlan-mcastgrp %s: invalid group address, "
745 "for non-multicast IP please use attribute \"vxlan-svcnodeip\""
746 % (ifname, mcast_grp))
747 # if mcastgrp is specified with a non-multicast address
748 # we warn the user. If the svcnodeip wasn't specified by
749 # the user we can use the mcastgrp value as svcnodeip
750 if not group:
751 group = mcast_grp
752 mcast_grp = None
753 else:
754 attribute_name = "vxlan-mcastgrp"
755
756 if mcast_grp:
757 group = mcast_grp
758
759 if not physdev:
760 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp) requires 'vxlan-physdev' to be specified" % (ifname))
761
762 cached_ifla_vxlan_group = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
763
764 if group != cached_ifla_vxlan_group:
765
766 if not group:
767 group = ipnetwork.IPNetwork("0.0.0.0")
768 attribute_name = "vxlan-svcnodeip/vxlan-mcastgrp"
769
770 self.logger.info("%s: set %s %s" % (ifname, attribute_name, group))
771 user_request_vxlan_info_data[Link.IFLA_VXLAN_GROUP] = group
772
773 # if the mcastgrp address is changed we need to signal this to the upper function
774 # in this case vxlan needs to be down before applying changes then up'd
775 multicast_group_change = True
776
777 if link_exists:
778 if cached_ifla_vxlan_group:
779 self.logger.info(
780 "%s: vxlan-mcastgrp configuration changed (cache %s): flapping vxlan device required"
781 % (ifname, cached_ifla_vxlan_group)
782 )
783 else:
784 self.logger.info(
785 "%s: vxlan-mcastgrp configuration changed: flapping vxlan device required" % ifname
786 )
787
788 return group, multicast_group_change
789
790 def __config_vxlan_group6(self, ifname, ifaceobj, link_exists, mcast_grp, group, physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
791 """
792 vxlan-mcastgrp and vxlan-svcnodeip are mutually exclusive
793 this function validates ip format for both attribute and tries to understand
794 what the user really want (remote or group option).
795
796 :param ifname:
797 :param ifaceobj:
798 :param mcast_grp:
799 :param group:
800 :param physdev:
801 :param user_request_vxlan_info_data:
802 :param cached_vxlan_ifla_info_data:
803 :return:
804 """
805 if mcast_grp and group:
806 self.log_error("%s: both group (vxlan-mcastgrp6 %s) and "
807 "remote (vxlan-svcnodeip6 %s) cannot be specified"
808 % (ifname, mcast_grp, group), ifaceobj)
809
810 attribute_name = "vxlan-svcnodeip6"
811 multicast_group_change = False
812
813 if group:
814 try:
815 group = ipnetwork.IPv6Address(group)
816 except Exception:
817 try:
818 group_ip = ipnetwork.IPv6Network(group).ip
819 self.logger.warning("%s: vxlan-svcnodeip6 %s: netmask ignored" % (ifname, group))
820 group = group_ip
821 except Exception:
822 raise Exception("%s: invalid vxlan-svcnodeip6 %s: must be in ipv4 format" % (ifname, group))
823
824 if group.is_multicast:
825 self.logger.warning("%s: vxlan-svcnodeip6 %s: invalid group address, "
826 "for multicast IP please use attribute \"vxlan-mcastgrp6\"" % (ifname, group))
827 # if svcnodeip is used instead of mcastgrp we warn the user
828 # if mcast_grp is not provided by the user we can instead
829 # use the svcnodeip value
830 if not physdev:
831 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp6) requires 'vxlan-physdev' to be specified" % (ifname))
832
833 elif mcast_grp:
834 try:
835 mcast_grp = ipnetwork.IPv6Address(mcast_grp)
836 except Exception:
837 try:
838 group_ip = ipnetwork.IPv6Network(mcast_grp).ip
839 self.logger.warning("%s: vxlan-mcastgrp6 %s: netmask ignored" % (ifname, mcast_grp))
840 mcast_grp = group_ip
841 except Exception:
842 raise Exception("%s: invalid vxlan-mcastgrp6 %s: must be in ipv4 format" % (ifname, mcast_grp))
843
844 if not mcast_grp.is_multicast:
845 self.logger.warning("%s: vxlan-mcastgrp6 %s: invalid group address, "
846 "for non-multicast IP please use attribute \"vxlan-svcnodeip6\""
847 % (ifname, mcast_grp))
848 # if mcastgrp is specified with a non-multicast address
849 # we warn the user. If the svcnodeip wasn't specified by
850 # the user we can use the mcastgrp value as svcnodeip
851 if not group:
852 group = mcast_grp
853 mcast_grp = None
854 else:
855 attribute_name = "vxlan-mcastgrp6"
856
857 if mcast_grp:
858 group = mcast_grp
859
860 if not physdev:
861 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp6) requires 'vxlan-physdev' to be specified" % (ifname))
862
863 cached_ifla_vxlan_group = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP6)
864
865 if group != cached_ifla_vxlan_group:
866
867 if not group:
868 group = ipnetwork.IPNetwork("::0", family=6)
869 attribute_name = "vxlan-svcnodeip6/vxlan-mcastgrp6"
870
871 self.logger.info("%s: set %s %s" % (ifname, attribute_name, group))
872 user_request_vxlan_info_data[Link.IFLA_VXLAN_GROUP6] = group
873
874 # if the mcastgrp address is changed we need to signal this to the upper function
875 # in this case vxlan needs to be down before applying changes then up'd
876 multicast_group_change = True
877
878 if link_exists:
879 if cached_ifla_vxlan_group:
880 self.logger.info(
881 "%s: vxlan-mcastgrp6 configuration changed (cache %s): flapping vxlan device required"
882 % (ifname, cached_ifla_vxlan_group)
883 )
884 else:
885 self.logger.info(
886 "%s: vxlan-mcastgrp6 configuration changed: flapping vxlan device required" % ifname
887 )
888
889 return group, multicast_group_change
890
891 def __config_vxlan_learning(self, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
892 if not link_exists or not ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT:
893 vxlan_learning = ifaceobj.get_attr_value_first('vxlan-learning')
894 if not vxlan_learning:
895 vxlan_learning = self.get_attr_default_value('vxlan-learning')
896 vxlan_learning = utils.get_boolean_from_string(vxlan_learning)
897 else:
898 vxlan_learning = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LEARNING)
899
900 if vxlan_learning != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LEARNING):
901 self.logger.info("%s: set vxlan-learning %s" % (ifaceobj.name, "on" if vxlan_learning else "off"))
902 user_request_vxlan_info_data[Link.IFLA_VXLAN_LEARNING] = vxlan_learning
903
904 def __config_vxlan_udp_csum(self, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
905 vxlan_udp_csum = ifaceobj.get_attr_value_first('vxlan-udp-csum')
906
907 if not vxlan_udp_csum:
908 vxlan_udp_csum = policymanager.policymanager_api.get_attr_default(
909 module_name=self.__class__.__name__,
910 attr="vxlan-udp-csum"
911 )
912
913 if not vxlan_udp_csum and not link_exists:
914 return
915
916 if not vxlan_udp_csum:
917 vxlan_udp_csum = self.get_attr_default_value('vxlan-udp-csum')
918
919 if vxlan_udp_csum:
920 vxlan_udp_csum = utils.get_boolean_from_string(vxlan_udp_csum)
921 else:
922 return
923
924 if vxlan_udp_csum != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_UDP_CSUM):
925 self.logger.info("%s: set vxlan-udp-csum %s" % (ifaceobj.name, "on" if vxlan_udp_csum else "off"))
926 user_request_vxlan_info_data[Link.IFLA_VXLAN_UDP_CSUM] = vxlan_udp_csum
927
928 def __get_vxlan_physdev(self, ifaceobj, mcastgrp, mcastgrp_map):
929 """
930 vxlan-physdev wrapper, special handling is required for mcastgrp is provided
931 the vxlan needs to use a dummy or real device for tunnel endpoint communication
932 This wrapper will get the physdev from user config or policy. IF the device
933 doesnt exists we create a dummy device.
934
935 :param ifaceobj:
936 :param mcastgrp:
937 :return physdev:
938 """
939 physdev = ifaceobj.get_attr_value_first("vxlan-physdev")
940
941 # if the user provided a physdev we need to honor his config
942 # or if mcastgrp wasn't specified we don't need to go further
943 if physdev or (not mcastgrp and not mcastgrp_map):
944 return physdev
945
946 physdev = self.vxlan_physdev_mcast
947
948 if not self.cache.link_exists(physdev):
949 if mcastgrp_map:
950 self.logger.info("%s: needs a dummy device (%s) to use for "
951 "multicast termination (vxlan-mcastgrp-map %s)"
952 % (ifaceobj.name, physdev, mcastgrp))
953 else:
954 self.logger.info("%s: needs a dummy device (%s) to use for "
955 "multicast termination (vxlan-mcastgrp %s)"
956 % (ifaceobj.name, physdev, mcastgrp))
957 self.netlink.link_add_with_attributes(ifname=physdev, kind="dummy", ifla={Link.IFLA_MTU: 16000, Link.IFLA_LINKMODE: 1})
958 self.netlink.link_up(physdev)
959
960 return physdev
961
962 def __config_vxlan_physdev(self, link_exists, ifaceobj, vxlan_physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
963 if vxlan_physdev:
964 try:
965 vxlan_physdev_ifindex = self.cache.get_ifindex(vxlan_physdev)
966 except NetlinkCacheIfnameNotFoundError:
967 try:
968 vxlan_physdev_ifindex = int(self.sysfs.read_file_oneline("/sys/class/net/%s/ifindex" % vxlan_physdev))
969 except Exception:
970 self.logger.error("%s: physdev %s doesn't exists" % (ifaceobj.name, vxlan_physdev))
971 return
972
973 if vxlan_physdev_ifindex != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LINK):
974 self.logger.info("%s: set vxlan-physdev %s" % (ifaceobj.name, vxlan_physdev))
975 user_request_vxlan_info_data[Link.IFLA_VXLAN_LINK] = vxlan_physdev_ifindex
976
977 # if the vxlan exists we need to return True, meaning that the vxlan
978 # needs to be flapped because we detected a vxlan-physdev change
979 if link_exists:
980 self.logger.info("%s: vxlan-physdev configuration changed: flapping vxlan device required" % ifaceobj.name)
981 return True
982
983 return False
984
985 def __get_vxlan_remote_ip_map(self, ifaceobj):
986 attr_name = "vxlan-remoteip-map"
987
988 maps = ifaceobj.get_attr_value(attr_name)
989 if not maps:
990 maps = policymanager.policymanager_api.get_attr_default(
991 module_name=self.__class__.__name__,
992 attr=attr_name
993 )
994 if not maps:
995 return {}
996
997 parsed_maps = {}
998 for m_line in maps:
999 # Cover single-line multi-entry case
1000 map = m_line.split()
1001 for m in map:
1002 m_parts = m.split('=')
1003 if len(m_parts) != 2:
1004 self.log_error('%s: %s %s format is invalid' % (ifaceobj.name, attr_name, m))
1005
1006 vnis = m_parts[0]
1007 _range = "-"
1008 remote_ips = []
1009
1010 for config_remote_ip in m_parts[1].split(","):
1011 if _range in config_remote_ip:
1012 ip_range = config_remote_ip.split("-")
1013 try:
1014 start = ip_address(ip_range[0])
1015 end = ip_address(ip_range[1])
1016 except Exception as e:
1017 self.log_error("%s: %s: invalid ip range '%s': %s" % (ifaceobj.name, attr_name, config_remote_ip, e), ifaceobj)
1018 return
1019 remote_ips.extend([ipnetwork.ip_address(i) for i in range(int(start), int(end) + 1)])
1020 else:
1021 remote_ips.append(ipnetwork.ip_address(config_remote_ip))
1022
1023 # vxlan-remoteip-map 42,84,1000-1005=10.0.0.1,10.0.0.42-45,222.0.0.1-5
1024 # higher priority is the comma
1025 for vni in utils.ranges_to_ints(vnis.split(",")) or []:
1026 parsed_maps.setdefault(vni, []).extend(remote_ips)
1027
1028 return parsed_maps
1029
1030 def single_vxlan_device_vni_filter(self, ifaceobj, vxlan_mcast_grp):
1031 vnisd = {}
1032 for vlan_vni_map in ifaceobj.get_attr_value("bridge-vlan-vni-map"):
1033 try:
1034 (vls, vis) = utils.get_vlan_vnis_in_map(vlan_vni_map)
1035 for v in utils.ranges_to_ints(vis):
1036 vnisd[v] = None
1037 except Exception as e:
1038 self.logger.error("%s: %s (%s)" %(ifaceobj.name, vlan_vni_map, str(e)))
1039 return
1040 if vxlan_mcast_grp:
1041 try:
1042 for v, g in vxlan_mcast_grp.items():
1043 if v not in vnisd.keys():
1044 self.logger.error("%s: group %s configured for a vni (%s) not specified in vlan vni map"
1045 %(ifaceobj.name, g, v))
1046 return
1047 vnisd[v] = str(g)
1048 except Exception as e:
1049 self.logger.error("%s: %s (%s)" %(ifaceobj.name, vlan_vni_map, str(e)))
1050 return
1051
1052 vnis_int = utils.ranges_to_ints(vnis)
1053 self.iproute2.bridge_link_update_vni_filter(ifaceobj.name, vnisd)
1054
1055 def check_and_raise_svd_tvd_errors(self, ifaceobj):
1056 err = self.svd_tvd_errors.get(ifaceobj.name)
1057
1058 if err:
1059 self.log_error(err, ifaceobj)
1060
1061 def __get_vxlan_vni_list(self, ifaceobj, string=True):
1062 vxlan_vni_str = self.__get_vxlan_attribute(ifaceobj, "vxlan-vni")
1063
1064 if vxlan_vni_str:
1065 # validate range but return string to be used in bridge vni add cmd
1066 vxlan_vni_range = utils.ranges_to_ints(vxlan_vni_str.split())
1067 return vxlan_vni_str if string else vxlan_vni_range
1068
1069 return None
1070
1071 def _up(self, ifaceobj):
1072 self.check_and_raise_svd_tvd_errors(ifaceobj)
1073
1074 vxlan_id_str = ifaceobj.get_attr_value_first("vxlan-id")
1075
1076 if not ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN and not ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI and not vxlan_id_str:
1077 self.logger.warning("%s: missing vxlan-id attribute on vxlan device" % ifaceobj.name)
1078 return
1079
1080 ifname = ifaceobj.name
1081 link_exists = self.cache.link_exists(ifname)
1082
1083 if link_exists:
1084 # if link already exists make sure this is a vxlan
1085 device_link_kind = self.cache.get_link_kind(ifname)
1086
1087 if device_link_kind != "vxlan":
1088 self.logger.error(
1089 "%s: device already exists and is not a vxlan (type %s)"
1090 % (ifname, device_link_kind)
1091 )
1092 ifaceobj.set_status(ifaceStatus.ERROR)
1093 return
1094
1095 # get vxlan running attributes
1096 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1097 else:
1098 cached_vxlan_ifla_info_data = {}
1099
1100 user_request_vxlan_info_data = {}
1101
1102 if vxlan_id_str:
1103 # for single vxlan device we don't have a vxlan-id
1104 self.__config_vxlan_id(ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1105
1106 self.__config_vxlan_learning(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1107 self.__config_vxlan_ageing(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1108 self.__config_vxlan_port(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1109 vxlan_ttl = self.__config_vxlan_ttl(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1110 self.__config_vxlan_tos(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1111 self.__config_vxlan_udp_csum(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1112 local = self.__config_vxlan_local_tunnelip(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1113
1114 vxlan_vni = self.__get_vxlan_vni_list(ifaceobj)
1115
1116 vxlan_mcast_grp = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp")
1117 vxlan_svcnodeip = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip")
1118
1119 vxlan_mcast_grp6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp6")
1120 vxlan_svcnodeip6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip6")
1121
1122 vxlan_mcast_grp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
1123
1124 vxlan_physdev = self.__get_vxlan_physdev(ifaceobj, vxlan_mcast_grp, vxlan_mcast_grp_map)
1125
1126 vxlan_vnifilter = self.__get_vxlan_attribute(ifaceobj, "vxlan-vnifilter")
1127
1128 vxlan_physdev_changed = self.__config_vxlan_physdev(
1129 link_exists,
1130 ifaceobj,
1131 vxlan_physdev,
1132 user_request_vxlan_info_data,
1133 cached_vxlan_ifla_info_data
1134 )
1135
1136 group, multicast_group_changed = self.__config_vxlan_group(
1137 ifname,
1138 ifaceobj,
1139 link_exists,
1140 vxlan_mcast_grp,
1141 vxlan_svcnodeip,
1142 vxlan_physdev,
1143 user_request_vxlan_info_data,
1144 cached_vxlan_ifla_info_data
1145 )
1146
1147 group6, multicast_group_changed6 = self.__config_vxlan_group6(
1148 ifname,
1149 ifaceobj,
1150 link_exists,
1151 vxlan_mcast_grp6,
1152 vxlan_svcnodeip6,
1153 vxlan_physdev,
1154 user_request_vxlan_info_data,
1155 cached_vxlan_ifla_info_data
1156 )
1157
1158 flap_vxlan_device = link_exists and (multicast_group_changed or multicast_group_changed6 or vxlan_physdev_changed)
1159
1160 if user_request_vxlan_info_data:
1161
1162 if link_exists and len(user_request_vxlan_info_data) == 1 and Link.IFLA_VXLAN_ID in user_request_vxlan_info_data:
1163 # if the vxlan already exists it's already cached
1164 # user_request_vxlan_info_data always contains at least one
1165 # element: vxlan-id
1166 self.logger.info('%s: vxlan already exists - no change detected' % ifname)
1167 else:
1168 if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
1169 self.iproute2.link_add_single_vxlan(
1170 link_exists,
1171 ifname,
1172 local.ip if local else None,
1173 group.ip if group else None,
1174 vxlan_physdev,
1175 user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT),
1176 vxlan_vnifilter,
1177 vxlan_ttl
1178 )
1179 elif ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
1180 self.iproute2.link_add_l3vxi(
1181 link_exists,
1182 ifname,
1183 local.ip if local else None,
1184 group.ip if group else None,
1185 vxlan_physdev,
1186 user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT),
1187 vxlan_ttl
1188 )
1189 else:
1190 try:
1191 if flap_vxlan_device:
1192 self.netlink.link_down_force(ifname)
1193
1194 self.netlink.link_add_vxlan_with_info_data(ifname, user_request_vxlan_info_data)
1195
1196 if flap_vxlan_device:
1197 self.netlink.link_up_force(ifname)
1198 except Exception as e:
1199 if link_exists:
1200 self.log_error("%s: applying vxlan change failed: %s" % (ifname, str(e)), ifaceobj)
1201 else:
1202 self.log_error("%s: vxlan creation failed: %s" % (ifname, str(e)), ifaceobj)
1203 return
1204
1205 if ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
1206 add_vni = True
1207 if link_exists:
1208 running_vxlan_vni_set = set()
1209
1210 for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
1211 for vni_obj in obj.get("vnis", []):
1212 start = vni_obj.get("vni")
1213 end = vni_obj.get("vniEnd")
1214
1215 for vni in utils.ranges_to_ints(["%s-%s" % (start, end if end else start)]):
1216 running_vxlan_vni_set.add(vni)
1217
1218 if running_vxlan_vni_set != set(utils.ranges_to_ints([vxlan_vni])):
1219 self.iproute2.bridge_vni_int_set_del(ifname, running_vxlan_vni_set)
1220 else:
1221 add_vni = False
1222
1223 if add_vni:
1224 try:
1225 self.iproute2.bridge_vni_add(ifname, vxlan_vni)
1226 except Exception as e:
1227 self.logger.warning("%s: l3 vxlan vni failure: %s" % (ifname, e))
1228
1229 if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
1230 if vxlan_vnifilter and utils.get_boolean_from_string(vxlan_vnifilter):
1231 self.single_vxlan_device_vni_filter(ifaceobj, vxlan_mcast_grp_map)
1232
1233 vxlan_purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
1234
1235 remoteips = ifaceobj.get_attr_value('vxlan-remoteip')
1236 if remoteips:
1237 try:
1238 for remoteip in remoteips:
1239 ipnetwork.IPv4Address(remoteip)
1240 except Exception as e:
1241 self.log_error('%s: vxlan-remoteip: %s' % (ifaceobj.name, str(e)))
1242
1243 # get old remote ips to compare with new user config value and
1244 # purge any removed remote ip
1245 old_remoteips = self.get_old_remote_ips(ifaceobj.name)
1246
1247 if vxlan_purge_remotes or remoteips or (remoteips != old_remoteips):
1248 # figure out the diff for remotes and do the bridge fdb updates
1249 # only if provisioned by user and not by an vxlan external
1250 # controller.
1251 local_str = str(local)
1252
1253 if local_str and remoteips and local_str in remoteips:
1254 remoteips.remove(local_str)
1255
1256 peers = self.iproute2.get_vxlan_peers(ifaceobj.name, str(group.ip) if group else None)
1257
1258 cur_peers = set(peers)
1259 if remoteips:
1260 new_peers = set(remoteips)
1261 del_list = cur_peers.difference(new_peers)
1262 add_list = new_peers.difference(cur_peers)
1263 else:
1264 del_list = cur_peers
1265 add_list = []
1266
1267 for addr in del_list:
1268 try:
1269 self.iproute2.bridge_fdb_del(
1270 ifaceobj.name,
1271 "00:00:00:00:00:00",
1272 None, True, addr
1273 )
1274 except Exception:
1275 pass
1276
1277 for addr in add_list:
1278 try:
1279 self.iproute2.bridge_fdb_append(
1280 ifaceobj.name,
1281 "00:00:00:00:00:00",
1282 None, True, addr
1283 )
1284 except Exception:
1285 pass
1286
1287 self.vxlan_remote_ip_map(ifaceobj, vxlan_mcast_grp_map)
1288
1289 @staticmethod
1290 def get_old_remote_ips(ifname):
1291 old_remoteips = []
1292 for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
1293 for remote in old_ifaceobj.get_attr_value("vxlan-remoteip") or []:
1294 old_remoteips.append(remote)
1295 return old_remoteips
1296
1297 def vxlan_remote_ip_map(self, ifaceobj, vxlan_mcast_grp_map):
1298 # get user configured remote ip map
1299 vxlan_remote_ip_map = self.__get_vxlan_remote_ip_map(ifaceobj) or {}
1300
1301 # if we have an older config we need to see what needs to be removed
1302 # and not check the running state as FRR or other component can add fdb entries
1303 old_vxlan_remote_ip_map = {}
1304
1305 for old_ifaceobj in statemanager.get_ifaceobjs(ifaceobj.name) or []:
1306 old_vxlan_remote_ip_map = {**old_vxlan_remote_ip_map, **self.__get_vxlan_remote_ip_map(old_ifaceobj)}
1307
1308 # go through the user config and add new entries while removing existing entries from 'old_vxlan_remote_ip_map'
1309 for vni, ips in vxlan_remote_ip_map.items():
1310 for ip in ips:
1311 if ip not in old_vxlan_remote_ip_map.get(vni, []):
1312 self.iproute2.bridge_fdb_append(ifaceobj.name, "00:00:00:00:00:00", remote=ip, src_vni=vni)
1313 else:
1314 old_vxlan_remote_ip_map.get(vni, []).remove(ip)
1315
1316 # in old_vxlan_remote_ip_map we have the delta between user config and running config. We should delete those
1317 # extra fdb entries. First we need to make sure that those are not added by vxlan-mcastgrp-map
1318 if old_vxlan_remote_ip_map:
1319 for vni, ip in (vxlan_mcast_grp_map or {}).items():
1320 try:
1321 old_vxlan_remote_ip_map[vni].remove(ip)
1322 except Exception:
1323 pass
1324
1325 for vni, ips in old_vxlan_remote_ip_map.items():
1326 for ip in ips:
1327 try:
1328 self.iproute2.bridge_fdb_del_raw(ifaceobj.name, "00:00:00:00:00:00 dst %s src_vni %s" % (ip, vni))
1329 except Exception:
1330 pass
1331
1332 @staticmethod
1333 def get_vxlan_fdb_src_vni(vxlan_mcast_grp_map):
1334 fdbs = []
1335 if vxlan_mcast_grp_map:
1336 for src_vni, dst_ip in vxlan_mcast_grp_map.items():
1337 fdbs.append(("00:00:00:00:00:00", src_vni, dst_ip))
1338 return fdbs
1339
1340 @staticmethod
1341 def get_svd_running_fdb(ifname):
1342 vxlan_fdb_data = utils.exec_command("bridge fdb show dev %s" % ifname)
1343 current_fdb = []
1344
1345 if vxlan_fdb_data:
1346 # each entry should look like the following:
1347 # 00:00:00:00:00:00 dst 239.1.1.100 src_vni 1000 self permanent
1348 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]:
1349 mac, _, dst, _, src_vni = entry.split()[0:5]
1350 current_fdb.append((mac, src_vni, dst))
1351
1352 return current_fdb
1353
1354 def single_vxlan_device_mcast_grp_map_fdb(self, ifaceobj, ifname, vxlan_mcast_grp_map):
1355 # in this piece of code we won't be checking the running state of the fdb table
1356 # dumping all fdb entries would cause scalability issues in certain cases.
1357
1358 # pulling old mcastgrp-map configuration
1359 old_user_config_fdb = []
1360
1361 for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
1362 old_user_config_fdb += self.get_vxlan_fdb_src_vni(self.__get_vxlan_mcastgrp_map(old_ifaceobj))
1363
1364 # new user configuration
1365 user_config_fdb = self.get_vxlan_fdb_src_vni(vxlan_mcast_grp_map)
1366
1367 # compare old and new config to know if we should remove any stale fdb entries.
1368 fdb_entries_to_remove = set(old_user_config_fdb) - set(user_config_fdb)
1369
1370 if fdb_entries_to_remove:
1371 for mac, src_vni, dst_ip in fdb_entries_to_remove:
1372 try:
1373 self.iproute2.bridge_fdb_del_src_vni(ifname, mac, src_vni)
1374 except Exception as e:
1375 if "no such file or directory" not in str(e).lower():
1376 self.logger.warning("%s: removing stale fdb entries failed: %s" % (ifname, str(e)))
1377
1378 if not user_config_fdb:
1379 # if vxlan-mcastgrp-map wasn't configure return
1380 return
1381
1382 for mac, src_vni, dst_ip in user_config_fdb:
1383 try:
1384 self.iproute2.bridge_fdb_add_src_vni(ifname, src_vni, dst_ip)
1385 except Exception as e:
1386 if "file exists" not in str(e).lower():
1387 ifaceobj.set_status(ifaceStatus.ERROR)
1388 self.log_error(
1389 "%s: vxlan-mcastgrp-map: %s=%s: %s"
1390 % (ifname, src_vni, dst_ip, str(e)), raise_error=False
1391 )
1392
1393 def single_vxlan_device_mcast_grp_map_vnifilter(self, ifaceobj, ifname, vxlan_mcast_grp_map):
1394 # in this piece of code we won't be checking the running state of the fdb table
1395 # dumping all fdb entries would cause scalability issues in certain cases.
1396
1397 # pulling old mcastgrp-map configuration
1398 old_user_config_fdb = []
1399
1400 for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
1401 old_user_config_fdb += self.get_vxlan_fdb_src_vni(self.__get_vxlan_mcastgrp_map(old_ifaceobj))
1402
1403 # new user configuration
1404 user_config_fdb = self.get_vxlan_fdb_src_vni(vxlan_mcast_grp_map)
1405
1406 # compare old and new config to know if we should remove any stale fdb entries.
1407 fdb_entries_to_remove = set(old_user_config_fdb) - set(user_config_fdb)
1408
1409 self.logger.info(old_user_config_fdb)
1410 self.logger.info(user_config_fdb)
1411 self.logger.info(fdb_entries_to_remove)
1412
1413 if fdb_entries_to_remove:
1414 for mac, src_vni, dst_ip in fdb_entries_to_remove:
1415 try:
1416 self.iproute2.bridge_fdb_del_src_vni(ifname, mac, src_vni)
1417 except Exception as e:
1418 if "no such file or directory" not in str(e).lower():
1419 self.logger.warning("%s: removing stale fdb entries failed: %s" % (ifname, str(e)))
1420
1421 if not user_config_fdb:
1422 # if vxlan-mcastgrp-map wasn't configure return
1423 return
1424
1425 for mac, src_vni, dst_ip in user_config_fdb:
1426 try:
1427 self.iproute2.bridge_fdb_add_src_vni(ifname, src_vni, dst_ip)
1428 except Exception as e:
1429 if "file exists" not in str(e).lower():
1430 ifaceobj.set_status(ifaceStatus.ERROR)
1431 self.log_error(
1432 "%s: vxlan-mcastgrp-map: %s=%s: %s"
1433 % (ifname, src_vni, dst_ip, str(e)), raise_error=False
1434 )
1435
1436 def _down(self, ifaceobj):
1437 try:
1438 self.netlink.link_del(ifaceobj.name)
1439 except Exception as e:
1440 self.log_warn(str(e))
1441
1442 @staticmethod
1443 def _query_check_n_update(ifaceobj, ifaceobjcurr, attrname, attrval, running_attrval):
1444 if not ifaceobj.get_attr_value_first(attrname):
1445 return
1446 if running_attrval and attrval == running_attrval:
1447 ifaceobjcurr.update_config_with_status(attrname, attrval, 0)
1448 else:
1449 ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1)
1450
1451 @staticmethod
1452 def _query_check_n_update_addresses(ifaceobjcurr, attrname, addresses, running_addresses):
1453 if addresses:
1454 for a in addresses:
1455 if a in running_addresses:
1456 ifaceobjcurr.update_config_with_status(attrname, a, 0)
1457 else:
1458 ifaceobjcurr.update_config_with_status(attrname, a, 1)
1459 running_addresses = set(running_addresses).difference(
1460 set(addresses))
1461 [ifaceobjcurr.update_config_with_status(attrname, a, 1) for a in running_addresses]
1462
1463 def _query_check(self, ifaceobj, ifaceobjcurr):
1464 ifname = ifaceobj.name
1465
1466 if not self.cache.link_exists(ifname):
1467 return
1468
1469 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1470
1471 if not cached_vxlan_ifla_info_data:
1472 ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, self.get_mod_attrs(), -1)
1473 return
1474
1475 for vxlan_attr_str, vxlan_attr_nl, callable_type in (
1476 ('vxlan-id', Link.IFLA_VXLAN_ID, int),
1477 ('vxlan-ttl', Link.IFLA_VXLAN_TTL, int),
1478 ('vxlan-tos', Link.IFLA_VXLAN_TOS, int),
1479 ('vxlan-port', Link.IFLA_VXLAN_PORT, int),
1480 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, int),
1481 ('vxlan-mcastgrp', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
1482 ('vxlan-mcastgrp6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
1483 ('vxlan-svcnodeip', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
1484 ('vxlan-svcnodeip6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
1485 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, lambda x: self.cache.get_ifindex(x)),
1486 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
1487 ('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
1488 ):
1489 vxlan_attr_value = ifaceobj.get_attr_value_first(vxlan_attr_str)
1490
1491 if not vxlan_attr_value:
1492 continue
1493
1494 cached_vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
1495
1496 try:
1497 vxlan_attr_value_nl = callable_type(vxlan_attr_value)
1498 except Exception as e:
1499 self.logger.warning('%s: %s: %s' % (ifname, vxlan_attr_str, str(e)))
1500 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
1501 continue
1502
1503 if vxlan_attr_value_nl == cached_vxlan_attr_value:
1504 ifaceobjcurr.update_config_with_status(vxlan_attr_str, vxlan_attr_value, 0)
1505 else:
1506 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
1507
1508 #
1509 # vxlan-local-tunnelip
1510 #
1511 running_attrval = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
1512 attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
1513 if not attrval:
1514 attrval = self._vxlan_local_tunnelip
1515 # TODO: vxlan._vxlan_local_tunnelip should be a ipnetwork.IPNetwork obj
1516 ifaceobj.update_config('vxlan-local-tunnelip', attrval)
1517
1518 if str(running_attrval) == self._clagd_vxlan_anycast_ip:
1519 # if local ip is anycast_ip, then let query_check to go through
1520 attrval = self._clagd_vxlan_anycast_ip
1521
1522 self._query_check_n_update(
1523 ifaceobj,
1524 ifaceobjcurr,
1525 'vxlan-local-tunnelip',
1526 str(attrval),
1527 str(running_attrval.ip) if running_attrval else None
1528 )
1529
1530 #
1531 # vxlan-remoteip
1532 #
1533 purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
1534 if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'):
1535 # If purge remotes or if vxlan-remoteip's are set
1536 # in the config file, we are owners of the installed
1537 # remote-ip's, lets check and report any remote ips we don't
1538 # understand
1539 cached_svcnode = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
1540
1541 self._query_check_n_update_addresses(
1542 ifaceobjcurr,
1543 'vxlan-remoteip',
1544 ifaceobj.get_attr_value('vxlan-remoteip'),
1545 self.iproute2.get_vxlan_peers(ifaceobj.name, str(cached_svcnode.ip) if cached_svcnode else None)
1546 )
1547
1548 # not ideal but will work for now, l3vxi dev:
1549 if ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
1550 user_config_vni_list = set(self.__get_vxlan_vni_list(ifaceobj, string=False))
1551 vxlan_vni_list = set()
1552
1553 for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
1554 for vni_obj in obj.get("vnis", []):
1555 start = vni_obj.get("vni")
1556 end = vni_obj.get("vniEnd")
1557
1558 for vni in utils.ranges_to_ints(["%s-%s" % (start, end if end else start)]):
1559 vxlan_vni_list.add(vni)
1560
1561 ifaceobjcurr.update_config_with_status(
1562 "vxlan-vni",
1563 " ".join(utils.compress_into_ranges(vxlan_vni_list)),
1564 vxlan_vni_list != user_config_vni_list
1565 )
1566
1567 #
1568 # vxlan-mcastgrp-map & vxlan-remoteip-map
1569 # fdb entries can be added by FRR, so we won't be checking the running
1570 # state if there's no record of a user configuration in /e/n/i
1571 user_mcastgrp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
1572 user_remote_ip_map = self.__get_vxlan_remote_ip_map(ifaceobj)
1573
1574 if not user_mcastgrp_map and not user_remote_ip_map:
1575 return
1576
1577 fdb_mcast = {}
1578 fdb_remote = {}
1579
1580 if user_remote_ip_map:
1581 for _, src_vni, dst in self.get_svd_running_fdb(ifname):
1582 ip = ipnetwork.IPv4Address(dst)
1583
1584 if not ip.is_multicast:
1585 fdb_remote.setdefault(int(src_vni), []).append(ip)
1586
1587 if user_mcastgrp_map:
1588 for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
1589 for vni in obj.get("vnis", []):
1590 group = vni.get("group")
1591
1592 if not group:
1593 continue
1594
1595 # we need to reconvert back to ipaddress.IPv4Address because
1596 # the existing code uses this type of obj (namely: __get_vxlan_mcastgrp_map)
1597 fdb_mcast[vni.get("vni")] = IPv4Address(group)
1598
1599 #
1600 # vxlan-mcastgrp-map
1601 #
1602 if not user_mcastgrp_map and fdb_mcast:
1603 ifaceobjcurr.update_config_with_status(
1604 "vxlan-mcastgrp-map",
1605 " ".join(["%s=%s" % (vni, ip) for vni, ip in fdb_mcast.items()]),
1606 1
1607 )
1608 elif user_mcastgrp_map and not fdb_mcast:
1609 ifaceobjcurr.update_config_with_status("vxlan-mcastgrp-map", "", 1)
1610 elif user_mcastgrp_map or fdb_mcast:
1611 ifaceobjcurr.update_config_with_status(
1612 "vxlan-mcastgrp-map",
1613 " ".join(["%s=%s" % (vni, ip) for vni, ip in fdb_mcast.items()]),
1614 user_mcastgrp_map != fdb_mcast
1615 )
1616
1617 #
1618 # vxlan-remoteip-map
1619 #
1620 if not user_remote_ip_map and fdb_remote:
1621 ifaceobjcurr.update_config_with_status(
1622 "vxlan-remoteip-map",
1623 " ".join(["%s=%s" % (vni, ",".join(map(str, ips))) for vni, ips in fdb_remote.items()]),
1624 1
1625 )
1626 elif user_remote_ip_map and not fdb_remote:
1627 ifaceobjcurr.update_config_with_status("vxlan-remoteip-map", "", 1)
1628 elif user_remote_ip_map or fdb_remote:
1629
1630 if user_remote_ip_map == fdb_remote:
1631 # display the user config with "pass"
1632 for config in ifaceobj.get_attr_value("vxlan-remoteip-map"):
1633 ifaceobjcurr.update_config_with_status(
1634 "vxlan-remoteip-map",
1635 config,
1636 0
1637 )
1638 else:
1639 # display current running state with ip ranges (but no vni ranges yet)
1640 ifaceobjcurr.update_config_with_status(
1641 "vxlan-remoteip-map",
1642 " ".join(["%s=%s" % (vni, ",".join(utils.compress_into_ip_ranges(ips))) for vni, ips in fdb_remote.items()]),
1643 1
1644 )
1645
1646 def _query_running(self, ifaceobjrunning):
1647 ifname = ifaceobjrunning.name
1648
1649 if not self.cache.link_exists(ifname):
1650 return
1651
1652 if self.cache.get_link_kind(ifname) != 'vxlan':
1653 return
1654
1655 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1656
1657 if not cached_vxlan_ifla_info_data:
1658 return
1659
1660 #
1661 # vxlan-id
1662 #
1663 vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
1664
1665 if not vxlan_id:
1666 # no vxlan id, meaning this not a vxlan
1667 return
1668
1669 ifaceobjrunning.update_config('vxlan-id', str(vxlan_id))
1670
1671 #
1672 # vxlan-port
1673 #
1674 vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
1675
1676 if vxlan_port:
1677 ifaceobjrunning.update_config('vxlan-port', vxlan_port)
1678
1679 #
1680 # vxlan-svcnode
1681 #
1682 vxlan_svcnode_value = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
1683
1684 if vxlan_svcnode_value:
1685 vxlan_svcnode_value = str(vxlan_svcnode_value)
1686 ifaceobjrunning.update_config('vxlan-svcnode', vxlan_svcnode_value)
1687
1688 #
1689 # vxlan-remoteip
1690 #
1691 purge_remotes = self.__get_vlxan_purge_remotes(None)
1692 if purge_remotes:
1693 # if purge_remotes is on, it means we own the
1694 # remote ips. Query them and add it to the running config
1695 attrval = self.iproute2.get_vxlan_peers(ifname, vxlan_svcnode_value)
1696 if attrval:
1697 [ifaceobjrunning.update_config('vxlan-remoteip', a) for a in attrval]
1698
1699 #
1700 # vxlan-link
1701 # vxlan-ageing
1702 # vxlan-learning
1703 # vxlan-local-tunnelip
1704 #
1705 for vxlan_attr_name, vxlan_attr_nl, callable_netlink_value_to_string in (
1706 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, self._get_ifname_for_ifindex),
1707 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, str),
1708 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda value: 'on' if value else 'off'),
1709 ('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda value: 'on' if value else 'off'),
1710 ('vxlan-local-tunnelip', Link.IFLA_VXLAN_LOCAL, str),
1711 ):
1712 vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
1713
1714 if vxlan_attr_value is not None:
1715 vxlan_attr_value_str = callable_netlink_value_to_string(vxlan_attr_value)
1716
1717 if vxlan_attr_value:
1718 ifaceobjrunning.update_config(vxlan_attr_name, vxlan_attr_value_str)
1719
1720 def _get_ifname_for_ifindex(self, ifindex):
1721 """
1722 we need this middle-man function to query the cache
1723 cache.get_ifname can raise KeyError, we need to catch
1724 it and return None
1725 """
1726 try:
1727 return self.cache.get_ifname(ifindex)
1728 except KeyError:
1729 return None
1730
1731 _run_ops = {
1732 "pre-up": _up,
1733 "post-down": _down,
1734 "query-running": _query_running,
1735 "query-checkcurr": _query_check
1736 }
1737
1738 def get_ops(self):
1739 return list(self._run_ops.keys())
1740
1741 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
1742 op_handler = self._run_ops.get(operation)
1743 if not op_handler:
1744 return
1745
1746 if not self._is_vxlan_device(ifaceobj):
1747 return
1748
1749 if "query" not in operation:
1750 if not self.vxlan_mcastgrp_ref \
1751 and self.vxlan_physdev_mcast \
1752 and self.cache.link_exists(self.vxlan_physdev_mcast):
1753 self.netlink.link_del(self.vxlan_physdev_mcast)
1754 self.reset()
1755
1756 if operation == 'query-checkcurr':
1757 op_handler(self, ifaceobj, query_ifaceobj)
1758 else:
1759 op_handler(self, ifaceobj)