]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/addons/vxlan.py
setup.py: bump version to 3.0.0 and update changelog
[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
7
223ba5af 8from ipaddr import IPNetwork, IPAddress, IPv4Address, IPv4Network, AddressValueError
d486dd0d
JF
9
10try:
11 import ifupdown2.ifupdown.policymanager as policymanager
3fb83a7a 12 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
d486dd0d 13
223ba5af
JF
14 from ifupdown2.lib.addon import Addon
15 from ifupdown2.lib.nlcache import NetlinkCacheIfnameNotFoundError
16
d486dd0d
JF
17 from ifupdown2.nlmanager.nlmanager import Link
18
19 from ifupdown2.ifupdown.iface import *
20 from ifupdown2.ifupdown.utils import utils
d486dd0d 21 from ifupdown2.ifupdownaddons.cache import *
d486dd0d
JF
22 from ifupdown2.ifupdownaddons.modulebase import moduleBase
23except ImportError:
24 import ifupdown.policymanager as policymanager
3fb83a7a 25 import ifupdown.ifupdownflags as ifupdownflags
d486dd0d 26
223ba5af
JF
27 from lib.addon import Addon
28 from lib.nlcache import NetlinkCacheIfnameNotFoundError
29
d486dd0d
JF
30 from nlmanager.nlmanager import Link
31
32 from ifupdown.iface import *
33 from ifupdown.utils import utils
d486dd0d
JF
34
35 from ifupdownaddons.cache import *
d486dd0d
JF
36 from ifupdownaddons.modulebase import moduleBase
37
38
223ba5af
JF
39class vxlan(Addon, moduleBase):
40 _modinfo = {
41 "mhelp": "vxlan module configures vxlan interfaces.",
42 "attrs": {
43 "vxlan-id": {
44 "help": "vxlan id",
45 "validrange": ["1", "16777214"],
46 "required": True,
47 "example": ["vxlan-id 100"]
48 },
49 "vxlan-local-tunnelip": {
50 "help": "vxlan local tunnel ip",
51 "validvals": ["<ipv4>"],
52 "example": ["vxlan-local-tunnelip 172.16.20.103"]
53 },
54 "vxlan-svcnodeip": {
55 "help": "vxlan id",
56 "validvals": ["<ipv4>"],
57 "example": ["vxlan-svcnodeip 172.16.22.125"]
58 },
59 "vxlan-remoteip": {
60 "help": "vxlan remote ip",
61 "validvals": ["<ipv4>"],
62 "example": ["vxlan-remoteip 172.16.22.127"],
63 "multiline": True
64 },
65 "vxlan-learning": {
66 "help": "vxlan learning yes/no",
67 "validvals": ["yes", "no", "on", "off"],
68 "example": ["vxlan-learning no"],
69 "default": "yes"
70 },
71 "vxlan-ageing": {
72 "help": "vxlan aging timer",
73 "validrange": ["0", "4096"],
74 "example": ["vxlan-ageing 300"],
75 "default": "300"
76 },
77 "vxlan-purge-remotes": {
78 "help": "vxlan purge existing remote entries",
79 "validvals": ["yes", "no"],
80 "example": ["vxlan-purge-remotes yes"],
81 },
82 "vxlan-port": {
83 "help": "vxlan UDP port (transmitted to vxlan driver)",
84 "example": ["vxlan-port 4789"],
85 "validrange": ["1", "65536"],
86 "default": "4789",
87 },
88 "vxlan-physdev": {
89 "help": "vxlan physical device",
90 "example": ["vxlan-physdev eth1"]
91 },
92 "vxlan-ttl": {
93 "help": "specifies the TTL value to use in outgoing packets "
94 "(range 0..255), 0=auto",
95 "default": "0",
96 "validvals": ["0", "255"],
97 "example": ['vxlan-ttl 42'],
98 },
99 "vxlan-mcastgrp": {
100 "help": "vxlan multicast group",
101 "validvals": ["<ip>"],
102 "example": ["vxlan-mcastgrp 172.16.22.127"],
103 }
104 }
105 }
106
107 VXLAN_PHYSDEV_MCASTGRP_DEFAULT = "ipmr-lo"
d486dd0d
JF
108
109 def __init__(self, *args, **kargs):
223ba5af 110 Addon.__init__(self)
d486dd0d 111 moduleBase.__init__(self, *args, **kargs)
223ba5af
JF
112
113 self._vxlan_purge_remotes = utils.get_boolean_from_string(
114 policymanager.policymanager_api.get_module_globals(
115 module_name=self.__class__.__name__,
116 attr="vxlan-purge-remotes"
117 )
118 )
119 self._vxlan_local_tunnelip = None
120 self._clagd_vxlan_anycast_ip = ""
121
122 # If mcastgrp is specified we need to rely on a user-configred device (via physdev)
123 # or via a policy variable "vxlan-physdev_mcastgrp". If the device doesn't exist we
124 # create it as a dummy device. We need to keep track of the user configuration to
125 # know when to delete this dummy device (when user remove mcastgrp from it's config)
126 self.vxlan_mcastgrp_ref = False
127 self.vxlan_physdev_mcast = policymanager.policymanager_api.get_module_globals(
128 module_name=self.__class__.__name__,
129 attr="vxlan-physdev-mcastgrp"
130 ) or self.VXLAN_PHYSDEV_MCASTGRP_DEFAULT
131
132 def reset(self):
133 # in daemon mode we need to reset mcastgrp_ref for every new command
134 # this variable has to be set in get_dependent_ifacenames
135 self.vxlan_mcastgrp_ref = False
d486dd0d
JF
136
137 def syntax_check(self, ifaceobj, ifaceobj_getfunc):
138 if self._is_vxlan_device(ifaceobj):
223ba5af 139 if not ifaceobj.get_attr_value_first('vxlan-local-tunnelip') and not self._vxlan_local_tunnelip:
d486dd0d
JF
140 self.logger.warning('%s: missing vxlan-local-tunnelip' % ifaceobj.name)
141 return False
142 return self.syntax_check_localip_anycastip_equal(
143 ifaceobj.name,
223ba5af
JF
144 ifaceobj.get_attr_value_first('vxlan-local-tunnelip') or self._vxlan_local_tunnelip,
145 self._clagd_vxlan_anycast_ip
d486dd0d
JF
146 )
147 return True
148
149 def syntax_check_localip_anycastip_equal(self, ifname, local_ip, anycast_ip):
150 try:
223ba5af 151 if local_ip and anycast_ip and IPNetwork(local_ip) == IPNetwork(anycast_ip):
d486dd0d
JF
152 self.logger.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
153 % (ifname, local_ip))
154 return False
155 except:
156 pass
157 return True
158
159 def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None):
160 if self._is_vxlan_device(ifaceobj):
161 ifaceobj.link_kind |= ifaceLinkKind.VXLAN
162 self._set_global_local_ip(ifaceobj)
223ba5af
JF
163
164 # if we detect a vxlan we check if mcastgrp is set (if so we set vxlan_mcastgrp_ref)
165 # to know when to delete this device.
166 if not self.vxlan_mcastgrp_ref and ifaceobj.get_attr_value("vxlan-mcastgrp"):
167 self.vxlan_mcastgrp_ref = True
168
d486dd0d
JF
169 elif ifaceobj.name == 'lo':
170 clagd_vxlan_list = ifaceobj.get_attr_value('clagd-vxlan-anycast-ip')
171 if clagd_vxlan_list:
172 if len(clagd_vxlan_list) != 1:
173 self.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one'
174 % (ifaceobj.name,))
223ba5af 175 self._clagd_vxlan_anycast_ip = clagd_vxlan_list[0]
d486dd0d
JF
176
177 self._set_global_local_ip(ifaceobj)
a382b488
JF
178
179 # If we should use a specific underlay device for the VXLAN
180 # tunnel make sure this device is set up before the VXLAN iface.
181 physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
182
183 if physdev:
184 return [physdev]
185
d486dd0d
JF
186 return None
187
188 def _set_global_local_ip(self, ifaceobj):
189 vxlan_local_tunnel_ip = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
223ba5af
JF
190 if vxlan_local_tunnel_ip and not self._vxlan_local_tunnelip:
191 self._vxlan_local_tunnelip = vxlan_local_tunnel_ip
d486dd0d 192
223ba5af
JF
193 @staticmethod
194 def _is_vxlan_device(ifaceobj):
195 return ifaceobj.link_kind & ifaceLinkKind.VXLAN or ifaceobj.get_attr_value_first('vxlan-id')
d486dd0d 196
223ba5af 197 def __get_vlxan_purge_remotes(self, ifaceobj):
d486dd0d 198 if not ifaceobj:
223ba5af 199 return self._vxlan_purge_remotes
d486dd0d
JF
200 purge_remotes = ifaceobj.get_attr_value_first('vxlan-purge-remotes')
201 if purge_remotes:
202 purge_remotes = utils.get_boolean_from_string(purge_remotes)
203 else:
223ba5af 204 purge_remotes = self._vxlan_purge_remotes
d486dd0d
JF
205 return purge_remotes
206
ec25a08c
JF
207 def get_vxlan_ttl_from_string(self, ttl_config):
208 ttl = 0
209 if ttl_config:
210 if ttl_config.lower() == "auto":
211 ttl = 0
212 else:
213 ttl = int(ttl_config)
214 return ttl
215
223ba5af
JF
216 def __config_vxlan_id(self, ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
217 """
218 Get vxlan-id user config and check it's value before inserting it in our netlink dictionary
219 :param ifname:
220 :param ifaceobj:
221 :param vxlan_id_str:
222 :param user_request_vxlan_info_data:
223 :param cached_vxlan_ifla_info_data:
224 :return:
225 """
226 try:
227 vxlan_id = int(vxlan_id_str)
228 cached_vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
229
230 if cached_vxlan_id and cached_vxlan_id != vxlan_id:
231 self.log_error(
232 "%s: Cannot change running vxlan id (%s): Operation not supported"
233 % (ifname, cached_vxlan_id),
234 ifaceobj
235 )
236 user_request_vxlan_info_data[Link.IFLA_VXLAN_ID] = vxlan_id
237 except ValueError:
238 self.log_error("%s: invalid vxlan-id '%s'" % (ifname, vxlan_id_str), ifaceobj)
239
240 def __get_vxlan_ageing_int(self, ifname, ifaceobj, link_exists):
241 """
242 Get vxlan-ageing user config or via policy, return integer value, None or raise on error
243 :param ifname:
244 :param ifaceobj:
245 :param link_exists:
246 :return:
247 """
248 vxlan_ageing_str = ifaceobj.get_attr_value_first("vxlan-ageing")
249 try:
250 if vxlan_ageing_str:
251 return int(vxlan_ageing_str)
252
253 vxlan_ageing_str = policymanager.policymanager_api.get_attr_default(
254 module_name=self.__class__.__name__,
255 attr="vxlan-ageing"
256 )
257
258 if not vxlan_ageing_str and link_exists:
259 # if link doesn't exist we let the kernel define ageing
260 vxlan_ageing_str = self.get_attr_default_value("vxlan-ageing")
261
262 if vxlan_ageing_str:
263 return int(vxlan_ageing_str)
264 except:
265 self.log_error("%s: invalid vxlan-ageing '%s'" % (ifname, vxlan_ageing_str), ifaceobj)
d486dd0d 266
223ba5af
JF
267 def __config_vxlan_ageing(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
268 """
269 Check user config vxlan-ageing and insert it in our netlink dictionary if needed
270 """
271 vxlan_ageing = self.__get_vxlan_ageing_int(ifname, ifaceobj, link_exists)
272
273 if not vxlan_ageing or (link_exists and vxlan_ageing == cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_AGEING)):
274 return
275
276 self.logger.info("%s: set vxlan-ageing %s" % (ifname, vxlan_ageing))
277 user_request_vxlan_info_data[Link.IFLA_VXLAN_AGEING] = vxlan_ageing
278
279 def __config_vxlan_port(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
280 """
281 Check vxlan-port user config, validate the integer value and insert it in the netlink dictionary if needed
282 :param ifname:
283 :param ifaceobj:
284 :param link_exists:
285 :param user_request_vxlan_info_data:
286 :param cached_vxlan_ifla_info_data:
287 :return:
288 """
289 vxlan_port_str = ifaceobj.get_attr_value_first("vxlan-port")
290 try:
291 if not vxlan_port_str:
292 vxlan_port_str = policymanager.policymanager_api.get_attr_default(
293 module_name=self.__class__.__name__,
294 attr="vxlan-port"
295 )
d486dd0d 296
ec25a08c 297 try:
223ba5af
JF
298 vxlan_port = int(vxlan_port_str)
299 except TypeError:
300 # TypeError means vxlan_port was None
301 # ie: not provided by the user or the policy
302 vxlan_port = self.netlink.VXLAN_UDP_PORT
303 except ValueError as e:
304 self.logger.warning(
305 "%s: vxlan-port: using default %s: invalid configured value %s"
306 % (ifname, self.netlink.VXLAN_UDP_PORT, str(e))
307 )
308 vxlan_port = self.netlink.VXLAN_UDP_PORT
309
310 cached_vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
311
312 if link_exists:
313 if vxlan_port != cached_vxlan_port:
314 self.logger.warning(
315 "%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s"
316 % (ifname, cached_vxlan_port, ifname, ifname)
ec25a08c 317 )
ec25a08c
JF
318 return
319
223ba5af
JF
320 self.logger.info("%s: set vxlan-port %s" % (ifname, vxlan_port))
321 user_request_vxlan_info_data[Link.IFLA_VXLAN_PORT] = vxlan_port
322 except:
323 self.log_error("%s: invalid vxlan-port '%s'" % (ifname, vxlan_port_str), ifaceobj)
324
325 def __config_vxlan_ttl(self, ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
326 """
327 Get vxlan-ttl from user config or policy, validate integer value and insert in netlink dict
328 :param ifname:
329 :param ifaceobj:
330 :param user_request_vxlan_info_data:
331 :param cached_vxlan_ifla_info_data:
332 :return:
333 """
334 vxlan_ttl_str = ifaceobj.get_attr_value_first("vxlan-ttl")
335 try:
336 if vxlan_ttl_str:
337 vxlan_ttl = self.get_vxlan_ttl_from_string(vxlan_ttl_str)
338 else:
339 vxlan_ttl = self.get_vxlan_ttl_from_string(
340 policymanager.policymanager_api.get_attr_default(
341 module_name=self.__class__.__name__,
342 attr="vxlan-ttl"
343 )
344 )
345
346 cached_ifla_vxlan_ttl = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_TTL)
347 if vxlan_ttl != cached_ifla_vxlan_ttl:
348
349 if cached_ifla_vxlan_ttl is not None:
350 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))
351 else:
352 self.logger.info("%s: set vxlan-ttl %s" % (ifname, vxlan_ttl_str if vxlan_ttl_str else vxlan_ttl))
353
354 user_request_vxlan_info_data[Link.IFLA_VXLAN_TTL] = vxlan_ttl
355 except:
356 self.log_error("%s: invalid vxlan-ttl '%s'" % (ifname, vxlan_ttl_str), ifaceobj)
357
358 def __config_vxlan_local_tunnelip(self, ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
359 """
360 Get vxlan-local-tunnelip user config or policy, validate ip address format and insert in netlink dict
361 :param ifname:
362 :param ifaceobj:
363 :param link_exists:
364 :param user_request_vxlan_info_data:
365 :param cached_vxlan_ifla_info_data:
366 :return:
367 """
368 local = ifaceobj.get_attr_value_first("vxlan-local-tunnelip")
369
370 if not local and self._vxlan_local_tunnelip:
371 local = self._vxlan_local_tunnelip
372
373 if link_exists:
374 # on ifreload do not overwrite anycast_ip to individual ip
375 # if clagd has modified
376 running_localtunnelip = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
377
378 if self._clagd_vxlan_anycast_ip and running_localtunnelip:
379 anycastip = IPAddress(self._clagd_vxlan_anycast_ip)
380 if anycastip == running_localtunnelip:
381 local = running_localtunnelip
382
383 if not local:
384 local = policymanager.policymanager_api.get_attr_default(
385 module_name=self.__class__.__name__,
386 attr="vxlan-local-tunnelip"
387 )
388
389 if local:
390 try:
391 local = IPv4Address(local)
392 except AddressValueError:
393 try:
394 local_ip = IPv4Network(local).ip
395 self.logger.warning("%s: vxlan-local-tunnelip %s: netmask ignored" % (ifname, local))
396 local = local_ip
397 except:
398 raise Exception("%s: invalid vxlan-local-tunnelip %s: must be in ipv4 format" % (ifname, local))
399
400 cached_ifla_vxlan_local = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
401
402 if local:
403 if local != cached_ifla_vxlan_local:
404 self.logger.info("%s: set vxlan-local-tunnelip %s" % (ifname, local))
405 user_request_vxlan_info_data[Link.IFLA_VXLAN_LOCAL] = local
406
407 # if both local-ip and anycast-ip are identical the function prints a warning
408 self.syntax_check_localip_anycastip_equal(ifname, local, self._clagd_vxlan_anycast_ip)
409 elif cached_ifla_vxlan_local:
410 self.logger.info("%s: removing vxlan-local-tunnelip (cache %s)" % (ifname, cached_ifla_vxlan_local))
411 user_request_vxlan_info_data[Link.IFLA_VXLAN_LOCAL] = None
412
413 return local
414
415 def __get_vxlan_mcast_grp(self, ifaceobj):
416 """
417 Get vxlan-mcastgrp user config or policy
418 :param ifaceobj:
419 :return:
420 """
421 vxlan_mcast_grp = ifaceobj.get_attr_value_first("vxlan-mcastgrp")
422
423 if not vxlan_mcast_grp:
424 vxlan_mcast_grp = policymanager.policymanager_api.get_attr_default(
425 module_name=self.__class__.__name__,
426 attr="vxlan-mcastgrp"
427 )
428
429 return vxlan_mcast_grp
430
431 def __get_vxlan_svcnodeip(self, ifaceobj):
432 """
433 Get vxlan-svcnodeip user config or policy
434 :param ifaceobj:
435 :return:
436 """
437 vxlan_svcnodeip = ifaceobj.get_attr_value_first('vxlan-svcnodeip')
d486dd0d 438
223ba5af
JF
439 if not vxlan_svcnodeip:
440 vxlan_svcnodeip = policymanager.policymanager_api.get_attr_default(
441 module_name=self.__class__.__name__,
442 attr="vxlan-svcnodeip"
443 )
d486dd0d 444
223ba5af 445 return vxlan_svcnodeip
d486dd0d 446
223ba5af
JF
447 def __config_vxlan_group(self, ifname, ifaceobj, link_exists, mcast_grp, group, physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
448 """
449 vxlan-mcastgrp and vxlan-svcnodeip are mutually exclusive
450 this function validates ip format for both attribute and tries to understand
451 what the user really want (remote or group option).
452
453 :param ifname:
454 :param ifaceobj:
455 :param mcast_grp:
456 :param group:
457 :param physdev:
458 :param user_request_vxlan_info_data:
459 :param cached_vxlan_ifla_info_data:
460 :return:
461 """
462 if mcast_grp and group:
463 self.log_error("%s: both group (vxlan-mcastgrp %s) and "
464 "remote (vxlan-svcnodeip %s) cannot be specified"
465 % (ifname, mcast_grp, group), ifaceobj)
466
467 attribute_name = "vxlan-svcnodeip"
468 multicast_group_change = False
469
470 if group:
471 try:
472 group = IPv4Address(group)
473 except AddressValueError:
474 try:
475 group_ip = IPv4Network(group).ip
476 self.logger.warning("%s: vxlan-svcnodeip %s: netmask ignored" % (ifname, group))
477 group = group_ip
478 except:
479 raise Exception("%s: invalid vxlan-svcnodeip %s: must be in ipv4 format" % (ifname, group))
480
481 if group.is_multicast:
482 self.logger.warning("%s: vxlan-svcnodeip %s: invalid group address, "
483 "for multicast IP please use attribute \"vxlan-mcastgrp\"" % (ifname, group))
484 # if svcnodeip is used instead of mcastgrp we warn the user
485 # if mcast_grp is not provided by the user we can instead
486 # use the svcnodeip value
487 if not physdev:
488 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp) requires 'vxlan-physdev' to be specified" % (ifname))
489
490 elif mcast_grp:
491 try:
492 mcast_grp = IPv4Address(mcast_grp)
493 except AddressValueError:
494 try:
495 group_ip = IPv4Network(mcast_grp).ip
496 self.logger.warning("%s: vxlan-mcastgrp %s: netmask ignored" % (ifname, mcast_grp))
497 mcast_grp = group_ip
498 except:
499 raise Exception("%s: invalid vxlan-mcastgrp %s: must be in ipv4 format" % (ifname, mcast_grp))
500
501 if not mcast_grp.is_multicast:
502 self.logger.warning("%s: vxlan-mcastgrp %s: invalid group address, "
503 "for non-multicast IP please use attribute \"vxlan-svcnodeip\""
504 % (ifname, mcast_grp))
505 # if mcastgrp is specified with a non-multicast address
506 # we warn the user. If the svcnodeip wasn't specified by
507 # the user we can use the mcastgrp value as svcnodeip
508 if not group:
509 group = mcast_grp
510 mcast_grp = None
d486dd0d 511 else:
223ba5af
JF
512 attribute_name = "vxlan-mcastgrp"
513
514 if mcast_grp:
515 group = mcast_grp
516
517 if not physdev:
518 self.log_error("%s: vxlan: 'group' (vxlan-mcastgrp) requires 'vxlan-physdev' to be specified" % (ifname))
519
520 cached_ifla_vxlan_group = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
521
522 if group != cached_ifla_vxlan_group:
523
524 if not group:
525 group = IPAddress("0.0.0.0")
526 attribute_name = "vxlan-svcnodeip/vxlan-mcastgrp"
527
528 self.logger.info("%s: set %s %s" % (ifname, attribute_name, group))
529 user_request_vxlan_info_data[Link.IFLA_VXLAN_GROUP] = group
530
531 # if the mcastgrp address is changed we need to signal this to the upper function
532 # in this case vxlan needs to be down before applying changes then up'd
533 multicast_group_change = True
d486dd0d
JF
534
535 if link_exists:
223ba5af
JF
536 if cached_ifla_vxlan_group:
537 self.logger.info(
538 "%s: vxlan-mcastgrp configuration changed (cache %s): flapping vxlan device required"
539 % (ifname, cached_ifla_vxlan_group)
540 )
b067bba9 541 else:
223ba5af
JF
542 self.logger.info(
543 "%s: vxlan-mcastgrp configuration changed: flapping vxlan device required" % ifname
544 )
b067bba9 545
223ba5af 546 return group, multicast_group_change
d486dd0d 547
223ba5af
JF
548 def __config_vxlan_learning(self, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
549 if not link_exists or not ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT:
550 vxlan_learning = ifaceobj.get_attr_value_first('vxlan-learning')
551 if not vxlan_learning:
552 vxlan_learning = self.get_attr_default_value('vxlan-learning')
553 vxlan_learning = utils.get_boolean_from_string(vxlan_learning)
554 else:
555 vxlan_learning = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LEARNING)
d486dd0d 556
223ba5af
JF
557 if vxlan_learning != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LEARNING):
558 self.logger.info("%s: set vxlan-learning %s" % (ifaceobj.name, "on" if vxlan_learning else "off"))
559 user_request_vxlan_info_data[Link.IFLA_VXLAN_LEARNING] = vxlan_learning
560
561 def __get_vxlan_physdev(self, ifaceobj, mcastgrp):
562 """
563 vxlan-physdev wrapper, special handling is required for mcastgrp is provided
564 the vxlan needs to use a dummy or real device for tunnel endpoint communication
565 This wrapper will get the physdev from user config or policy. IF the device
566 doesnt exists we create a dummy device.
567
568 :param ifaceobj:
569 :param mcastgrp:
570 :return physdev:
571 """
572 physdev = ifaceobj.get_attr_value_first("vxlan-physdev")
d486dd0d 573
223ba5af
JF
574 # if the user provided a physdev we need to honor his config
575 # or if mcastgrp wasn't specified we don't need to go further
576 if physdev or not mcastgrp:
577 return physdev
578
579 physdev = self.vxlan_physdev_mcast
580
581 if not self.cache.link_exists(physdev):
582 self.logger.info("%s: needs a dummy device (%s) to use for "
583 "multicast termination (vxlan-mcastgrp %s)"
584 % (ifaceobj.name, physdev, mcastgrp))
585 self.netlink.link_add_with_attributes(ifname=physdev, kind="dummy", ifla={Link.IFLA_MTU: 16000, Link.IFLA_LINKMODE: 1})
586 self.netlink.link_up(physdev)
587
588 return physdev
589
590 def __config_vxlan_physdev(self, link_exists, ifaceobj, vxlan_physdev, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
591 if vxlan_physdev:
592 try:
593 vxlan_physdev_ifindex = self.cache.get_ifindex(vxlan_physdev)
594 except NetlinkCacheIfnameNotFoundError:
d486dd0d 595 try:
223ba5af
JF
596 vxlan_physdev_ifindex = int(self.sysfs.read_file_oneline("/sys/class/net/%s/ifindex" % vxlan_physdev))
597 except:
598 self.logger.error("%s: physdev %s doesn't exists" % (ifaceobj.name, vxlan_physdev))
599 return
d486dd0d 600
223ba5af
JF
601 if vxlan_physdev_ifindex != cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LINK):
602 self.logger.info("%s: set vxlan-physdev %s" % (ifaceobj.name, vxlan_physdev))
603 user_request_vxlan_info_data[Link.IFLA_VXLAN_LINK] = vxlan_physdev_ifindex
d486dd0d 604
223ba5af
JF
605 # if the vxlan exists we need to return True, meaning that the vxlan
606 # needs to be flapped because we detected a vxlan-physdev change
607 if link_exists:
608 self.logger.info("%s: vxlan-physdev configuration changed: flapping vxlan device required" % ifaceobj.name)
609 return True
610
611 return False
612
613 def _up(self, ifaceobj):
614 vxlan_id_str = ifaceobj.get_attr_value_first("vxlan-id")
615
616 if not vxlan_id_str:
617 return
618
619 ifname = ifaceobj.name
620 link_exists = self.cache.link_exists(ifname)
621
622 if link_exists:
623 # if link already exists make sure this is a vxlan
624 device_link_kind = self.cache.get_link_kind(ifname)
625
626 if device_link_kind != "vxlan":
627 self.logger.error(
628 "%s: device already exists and is not a vxlan (type %s)"
629 % (ifname, device_link_kind)
d486dd0d 630 )
223ba5af
JF
631 ifaceobj.set_status(ifaceStatus.ERROR)
632 return
d486dd0d 633
223ba5af
JF
634 # get vxlan running attributes
635 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
636 else:
637 cached_vxlan_ifla_info_data = {}
d486dd0d 638
223ba5af 639 user_request_vxlan_info_data = {}
d486dd0d 640
223ba5af
JF
641 self.__config_vxlan_id(ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
642 self.__config_vxlan_learning(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
643 self.__config_vxlan_ageing(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
644 self.__config_vxlan_port(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
645 self.__config_vxlan_ttl(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
646 local = self.__config_vxlan_local_tunnelip(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
647
648 vxlan_mcast_grp = self.__get_vxlan_mcast_grp(ifaceobj)
649 vxlan_svcnodeip = self.__get_vxlan_svcnodeip(ifaceobj)
650 vxlan_physdev = self.__get_vxlan_physdev(ifaceobj, vxlan_mcast_grp)
651
652 vxlan_physdev_changed = self.__config_vxlan_physdev(
653 link_exists,
654 ifaceobj,
655 vxlan_physdev,
656 user_request_vxlan_info_data,
657 cached_vxlan_ifla_info_data
658 )
659
660 group, multicast_group_changed = self.__config_vxlan_group(
661 ifname,
662 ifaceobj,
663 link_exists,
664 vxlan_mcast_grp,
665 vxlan_svcnodeip,
666 vxlan_physdev,
667 user_request_vxlan_info_data,
668 cached_vxlan_ifla_info_data
669 )
670
671 flap_vxlan_device = link_exists and (multicast_group_changed or vxlan_physdev_changed)
672
673 if user_request_vxlan_info_data:
674
675 if link_exists and not len(user_request_vxlan_info_data) > 1:
676 # if the vxlan already exists it's already cached
677 # user_request_vxlan_info_data always contains at least one
678 # element: vxlan-id
679 self.logger.info('%s: vxlan already exists - no change detected' % ifname)
680 else:
d486dd0d 681 try:
223ba5af
JF
682 if flap_vxlan_device:
683 self.netlink.link_down_force(ifname)
684
685 self.netlink.link_add_vxlan_with_info_data(ifname, user_request_vxlan_info_data)
686
687 if flap_vxlan_device:
688 self.netlink.link_up_force(ifname)
689 except Exception as e:
690 if link_exists:
691 self.log_error("%s: applying vxlan change failed: %s" % (ifname, str(e)), ifaceobj)
692 else:
693 self.log_error("%s: vxlan creation failed: %s" % (ifname, str(e)), ifaceobj)
694 return
695
696 vxlan_purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
697
698 remoteips = ifaceobj.get_attr_value('vxlan-remoteip')
699 if remoteips:
700 try:
701 for remoteip in remoteips:
702 IPv4Address(remoteip)
703 except Exception as e:
704 self.log_error('%s: vxlan-remoteip: %s' % (ifaceobj.name, str(e)))
705
706 if vxlan_purge_remotes or remoteips:
707 # figure out the diff for remotes and do the bridge fdb updates
708 # only if provisioned by user and not by an vxlan external
709 # controller.
710 peers = self.iproute2.get_vxlan_peers(ifaceobj.name, group)
711 if local and remoteips and local in remoteips:
712 remoteips.remove(local)
713 cur_peers = set(peers)
714 if remoteips:
715 new_peers = set(remoteips)
716 del_list = cur_peers.difference(new_peers)
717 add_list = new_peers.difference(cur_peers)
718 else:
719 del_list = cur_peers
720 add_list = []
d486dd0d 721
223ba5af 722 for addr in del_list:
d486dd0d 723 try:
223ba5af
JF
724 self.iproute2.bridge_fdb_del(
725 ifaceobj.name,
726 "00:00:00:00:00:00",
727 None, True, addr
728 )
d486dd0d
JF
729 except:
730 pass
d486dd0d 731
223ba5af 732 for addr in add_list:
d486dd0d 733 try:
223ba5af
JF
734 self.iproute2.bridge_fdb_append(
735 ifaceobj.name,
736 "00:00:00:00:00:00",
737 None, True, addr
738 )
739 except:
740 pass
d486dd0d
JF
741
742 def _down(self, ifaceobj):
743 try:
223ba5af 744 self.netlink.link_del(ifaceobj.name)
3b01ed76 745 except Exception as e:
d486dd0d
JF
746 self.log_warn(str(e))
747
223ba5af
JF
748 @staticmethod
749 def _query_check_n_update(ifaceobj, ifaceobjcurr, attrname, attrval, running_attrval):
d486dd0d
JF
750 if not ifaceobj.get_attr_value_first(attrname):
751 return
752 if running_attrval and attrval == running_attrval:
223ba5af 753 ifaceobjcurr.update_config_with_status(attrname, attrval, 0)
d486dd0d 754 else:
223ba5af 755 ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1)
d486dd0d 756
223ba5af
JF
757 @staticmethod
758 def _query_check_n_update_addresses(ifaceobjcurr, attrname, addresses, running_addresses):
d486dd0d
JF
759 if addresses:
760 for a in addresses:
761 if a in running_addresses:
762 ifaceobjcurr.update_config_with_status(attrname, a, 0)
763 else:
764 ifaceobjcurr.update_config_with_status(attrname, a, 1)
3b01ed76
JF
765 running_addresses = set(running_addresses).difference(
766 set(addresses))
223ba5af 767 [ifaceobjcurr.update_config_with_status(attrname, a, 1) for a in running_addresses]
d486dd0d
JF
768
769 def _query_check(self, ifaceobj, ifaceobjcurr):
223ba5af
JF
770 ifname = ifaceobj.name
771
772 if not self.cache.link_exists(ifname):
d486dd0d 773 return
d486dd0d 774
223ba5af
JF
775 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
776
777 if not cached_vxlan_ifla_info_data:
778 ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, self.get_mod_attrs(), -1)
779 return
780
781 for vxlan_attr_str, vxlan_attr_nl, callable_type in (
782 ('vxlan-id', Link.IFLA_VXLAN_ID, int),
783 ('vxlan-ttl', Link.IFLA_VXLAN_TTL, int),
784 ('vxlan-port', Link.IFLA_VXLAN_PORT, int),
785 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, int),
786 ('vxlan-mcastgrp', Link.IFLA_VXLAN_GROUP, IPv4Address),
787 ('vxlan-svcnodeip', Link.IFLA_VXLAN_GROUP, IPv4Address),
788 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, lambda x: self.cache.get_ifindex(x)),
789 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
790 ):
791 vxlan_attr_value = ifaceobj.get_attr_value_first(vxlan_attr_str)
d486dd0d 792
223ba5af
JF
793 if not vxlan_attr_value:
794 continue
795
796 cached_vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
797
798 try:
799 vxlan_attr_value_nl = callable_type(vxlan_attr_value)
800 except Exception as e:
801 self.logger.warning('%s: %s: %s' % (ifname, vxlan_attr_str, str(e)))
802 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
803 continue
804
805 if vxlan_attr_value_nl == cached_vxlan_attr_value:
806 ifaceobjcurr.update_config_with_status(vxlan_attr_str, vxlan_attr_value, 0)
807 else:
808 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
809
810 #
811 # vxlan-local-tunnelip
812 #
813 running_attrval = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
d486dd0d
JF
814 attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
815 if not attrval:
223ba5af
JF
816 attrval = self._vxlan_local_tunnelip
817 # TODO: vxlan._vxlan_local_tunnelip should be a IPNetwork obj
d486dd0d
JF
818 ifaceobj.update_config('vxlan-local-tunnelip', attrval)
819
223ba5af 820 if str(running_attrval) == self._clagd_vxlan_anycast_ip:
d486dd0d
JF
821 # if local ip is anycast_ip, then let query_check to go through
822 attrval = self._clagd_vxlan_anycast_ip
d486dd0d 823
223ba5af
JF
824 self._query_check_n_update(
825 ifaceobj,
826 ifaceobjcurr,
827 'vxlan-local-tunnelip',
828 str(attrval),
829 str(running_attrval)
830 )
d486dd0d 831
223ba5af
JF
832 #
833 # vxlan-remoteip
834 #
835 purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
d486dd0d
JF
836 if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'):
837 # If purge remotes or if vxlan-remoteip's are set
838 # in the config file, we are owners of the installed
839 # remote-ip's, lets check and report any remote ips we don't
840 # understand
223ba5af 841 cached_svcnode = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
d486dd0d 842
223ba5af
JF
843 self._query_check_n_update_addresses(
844 ifaceobjcurr,
845 'vxlan-remoteip',
846 ifaceobj.get_attr_value('vxlan-remoteip'),
847 self.iproute2.get_vxlan_peers(ifaceobj.name, str(cached_svcnode))
848 )
a382b488 849
223ba5af
JF
850 def _query_running(self, ifaceobjrunning):
851 ifname = ifaceobjrunning.name
a382b488 852
223ba5af
JF
853 if not self.cache.link_exists(ifname):
854 return
a382b488 855
223ba5af 856 if not self.cache.get_link_kind(ifname) == 'vxlan':
d486dd0d 857 return
223ba5af
JF
858
859 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
860
861 if not cached_vxlan_ifla_info_data:
d486dd0d
JF
862 return
863
223ba5af
JF
864 #
865 # vxlan-id
866 #
867 vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
868
869 if not vxlan_id:
870 # no vxlan id, meaning this not a vxlan
871 return
872
873 ifaceobjrunning.update_config('vxlan-id', str(vxlan_id))
874
875 #
876 # vxlan-port
877 #
878 vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
879
880 if vxlan_port:
881 ifaceobjrunning.update_config('vxlan-port', vxlan_port)
882
883 #
884 # vxlan-svcnode
885 #
886 vxlan_svcnode_value = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
887
888 if vxlan_svcnode_value:
889 vxlan_svcnode_value = str(vxlan_svcnode_value)
890 ifaceobjrunning.update_config('vxlan-svcnode', vxlan_svcnode_value)
d486dd0d 891
223ba5af
JF
892 #
893 # vxlan-remoteip
894 #
895 purge_remotes = self.__get_vlxan_purge_remotes(None)
d486dd0d
JF
896 if purge_remotes:
897 # if purge_remotes is on, it means we own the
898 # remote ips. Query them and add it to the running config
223ba5af 899 attrval = self.iproute2.get_vxlan_peers(ifname, vxlan_svcnode_value)
d486dd0d 900 if attrval:
223ba5af
JF
901 [ifaceobjrunning.update_config('vxlan-remoteip', a) for a in attrval]
902
903 #
904 # vxlan-link
905 # vxlan-ageing
906 # vxlan-learning
907 # vxlan-local-tunnelip
908 #
909 for vxlan_attr_name, vxlan_attr_nl, callable_netlink_value_to_string in (
910 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, self._get_ifname_for_ifindex),
911 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, str),
912 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda value: 'on' if value else 'off'),
913 ('vxlan-local-tunnelip', Link.IFLA_VXLAN_LOCAL, str),
914 ):
915 vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
916
917 if vxlan_attr_value is not None:
918 vxlan_attr_value_str = callable_netlink_value_to_string(vxlan_attr_nl)
919
920 if vxlan_attr_value:
921 ifaceobjrunning.update_config(vxlan_attr_name, vxlan_attr_value_str)
922
923 def _get_ifname_for_ifindex(self, ifindex):
924 """
925 we need this middle-man function to query the cache
926 cache.get_ifname can raise KeyError, we need to catch
927 it and return None
928 """
929 try:
930 return self.cache.get_ifname(ifindex)
931 except KeyError:
932 return None
a382b488 933
223ba5af
JF
934 _run_ops = {
935 "pre-up": _up,
936 "post-down": _down,
937 "query-running": _query_running,
938 "query-checkcurr": _query_check
939 }
d486dd0d
JF
940
941 def get_ops(self):
3b01ed76 942 return list(self._run_ops.keys())
d486dd0d 943
d486dd0d
JF
944 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
945 op_handler = self._run_ops.get(operation)
946 if not op_handler:
947 return
223ba5af
JF
948
949 if operation != 'query-running':
950 if not self._is_vxlan_device(ifaceobj):
951 return
952
953 if not self.vxlan_mcastgrp_ref \
954 and self.vxlan_physdev_mcast \
955 and self.cache.link_exists(self.vxlan_physdev_mcast):
956 self.netlink.link_del(self.vxlan_physdev_mcast)
957 self.reset()
958
d486dd0d
JF
959 if operation == 'query-checkcurr':
960 op_handler(self, ifaceobj, query_ifaceobj)
961 else:
962 op_handler(self, ifaceobj)