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