]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/addons/vxlan.py
addons: vxlan: add support for l3vxi via vxlan-vni
[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
66eb9ce3 7from ipaddress import IPv4Network, IPv4Address, AddressValueError, ip_address
d486dd0d 8try:
0e936c3f 9 import ifupdown2.nlmanager.ipnetwork as ipnetwork
d486dd0d 10 import ifupdown2.ifupdown.policymanager as policymanager
3fb83a7a 11 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
d486dd0d 12
970c72e4 13 from ifupdown2.lib.addon import Vxlan
223ba5af
JF
14 from ifupdown2.lib.nlcache import NetlinkCacheIfnameNotFoundError
15
d486dd0d
JF
16 from ifupdown2.nlmanager.nlmanager import Link
17
18 from ifupdown2.ifupdown.iface import *
19 from ifupdown2.ifupdown.utils import utils
56f34349 20 from ifupdown2.ifupdown.statemanager import statemanager_api as statemanager
d486dd0d 21 from ifupdown2.ifupdownaddons.cache import *
d486dd0d 22 from ifupdown2.ifupdownaddons.modulebase import moduleBase
56f34349 23
bd441a51 24except (ImportError, ModuleNotFoundError):
0e936c3f 25 import nlmanager.ipnetwork as ipnetwork
d486dd0d 26 import ifupdown.policymanager as policymanager
3fb83a7a 27 import ifupdown.ifupdownflags as ifupdownflags
d486dd0d 28
db4371de 29 from lib.addon import Vxlan
223ba5af
JF
30 from lib.nlcache import NetlinkCacheIfnameNotFoundError
31
d486dd0d
JF
32 from nlmanager.nlmanager import Link
33
34 from ifupdown.iface import *
35 from ifupdown.utils import utils
56f34349 36 from ifupdown.statemanager import statemanager_api as statemanager
d486dd0d
JF
37
38 from ifupdownaddons.cache import *
d486dd0d
JF
39 from ifupdownaddons.modulebase import moduleBase
40
41
db4371de 42class vxlan(Vxlan, moduleBase):
223ba5af
JF
43 _modinfo = {
44 "mhelp": "vxlan module configures vxlan interfaces.",
45 "attrs": {
46 "vxlan-id": {
47 "help": "vxlan id",
48 "validrange": ["1", "16777214"],
49 "required": True,
50 "example": ["vxlan-id 100"]
51 },
52 "vxlan-local-tunnelip": {
53 "help": "vxlan local tunnel ip",
54 "validvals": ["<ipv4>"],
55 "example": ["vxlan-local-tunnelip 172.16.20.103"]
56 },
57 "vxlan-svcnodeip": {
969257a4 58 "help": "vxlan svc node id",
223ba5af
JF
59 "validvals": ["<ipv4>"],
60 "example": ["vxlan-svcnodeip 172.16.22.125"]
61 },
40658337
JF
62 "vxlan-svcnodeip6": {
63 "help": "vxlan svc node ip",
64 "validvals": ["<ipv6>"],
65 "example": ["vxlan-svcnodeip6 2001:DB8:8086:6502::"]
66 },
223ba5af
JF
67 "vxlan-remoteip": {
68 "help": "vxlan remote ip",
69 "validvals": ["<ipv4>"],
70 "example": ["vxlan-remoteip 172.16.22.127"],
71 "multiline": True
72 },
73 "vxlan-learning": {
74 "help": "vxlan learning yes/no",
75 "validvals": ["yes", "no", "on", "off"],
76 "example": ["vxlan-learning no"],
77 "default": "yes"
78 },
79 "vxlan-ageing": {
80 "help": "vxlan aging timer",
81 "validrange": ["0", "4096"],
82 "example": ["vxlan-ageing 300"],
83 "default": "300"
84 },
85 "vxlan-purge-remotes": {
86 "help": "vxlan purge existing remote entries",
87 "validvals": ["yes", "no"],
88 "example": ["vxlan-purge-remotes yes"],
89 },
90 "vxlan-port": {
91 "help": "vxlan UDP port (transmitted to vxlan driver)",
92 "example": ["vxlan-port 4789"],
93 "validrange": ["1", "65536"],
94 "default": "4789",
95 },
96 "vxlan-physdev": {
97 "help": "vxlan physical device",
98 "example": ["vxlan-physdev eth1"]
99 },
100 "vxlan-ttl": {
101 "help": "specifies the TTL value to use in outgoing packets "
102 "(range 0..255), 0=auto",
103 "default": "0",
b20f9836
SO
104 "validrange": ["0", "255"],
105 "validvals": ["<number>", "auto"],
223ba5af
JF
106 "example": ['vxlan-ttl 42'],
107 },
e521508b 108 "vxlan-tos": {
a8dd54b0 109 "help": "specifies the ToS value (range 0..255), 1=inherit",
b20f9836
SO
110 "validrange": ["0", "255"],
111 "validvals": ["<number>", "inherit"],
e521508b
SO
112 "example": ['vxlan-tos 42'],
113 },
223ba5af
JF
114 "vxlan-mcastgrp": {
115 "help": "vxlan multicast group",
116 "validvals": ["<ip>"],
117 "example": ["vxlan-mcastgrp 172.16.22.127"],
40658337
JF
118 },
119 "vxlan-mcastgrp6": {
120 "help": "vxlan multicast group",
121 "validvals": ["<ip6>"],
122 "example": ["vxlan-mcastgrp ff02::15c"],
ca436937
JF
123 },
124 "vxlan-mcastgrp-map": {
56f34349
JF
125 "help": "vxlan multicast group for single-vxlan device -"
126 "doesn't support multiline attribute",
ca436937
JF
127 "example": ["vxlan-mcastgrp-map 1000=239.1.1.100 1001=239.1.1.200"],
128 },
84c47c4f
RP
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 },
66eb9ce3
JF
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 },
e521508b
SO
139 "vxlan-udp-csum": {
140 "help": "whether to perform checksumming or not",
e521508b
SO
141 "validvals": ["yes", "no"],
142 "example": ["vxlan-udp-csum no"]
0500d5d8
JF
143 },
144 "vxlan-vni": {
145 "help": "L3 VxLAN interface (vni list and range are supported)",
146 "validvals": ["<number>"],
147 "example": ["vxlan-vni 42"]
e521508b 148 }
223ba5af
JF
149 }
150 }
151
152 VXLAN_PHYSDEV_MCASTGRP_DEFAULT = "ipmr-lo"
d486dd0d
JF
153
154 def __init__(self, *args, **kargs):
db4371de 155 Vxlan.__init__(self)
d486dd0d 156 moduleBase.__init__(self, *args, **kargs)
223ba5af
JF
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
782aff35
JF
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
db4371de
JF
185 self.svd_tvd_errors = {}
186
223ba5af
JF
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
d486dd0d
JF
191
192 def syntax_check(self, ifaceobj, ifaceobj_getfunc):
193 if self._is_vxlan_device(ifaceobj):
223ba5af 194 if not ifaceobj.get_attr_value_first('vxlan-local-tunnelip') and not self._vxlan_local_tunnelip:
d486dd0d
JF
195 self.logger.warning('%s: missing vxlan-local-tunnelip' % ifaceobj.name)
196 return False
782aff35
JF
197
198 self.check_and_raise_svd_tvd_errors(ifaceobj)
199
d486dd0d
JF
200 return self.syntax_check_localip_anycastip_equal(
201 ifaceobj.name,
223ba5af
JF
202 ifaceobj.get_attr_value_first('vxlan-local-tunnelip') or self._vxlan_local_tunnelip,
203 self._clagd_vxlan_anycast_ip
d486dd0d
JF
204 )
205 return True
206
207 def syntax_check_localip_anycastip_equal(self, ifname, local_ip, anycast_ip):
208 try:
0e936c3f 209 if local_ip and anycast_ip and ipnetwork.IPNetwork(local_ip) == ipnetwork.IPNetwork(anycast_ip):
d486dd0d
JF
210 self.logger.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
211 % (ifname, local_ip))
212 return False
3218f49d 213 except Exception:
d486dd0d
JF
214 pass
215 return True
216
59ab29fb 217 def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None, old_ifaceobjs=False):
e537a6e6
JF
218 if ifaceobj.get_attr_value_first("bridge-vlan-vni-map"):
219 ifaceobj.link_privflags |= ifaceLinkPrivFlags.SINGLE_VXLAN
220
d486dd0d
JF
221 if self._is_vxlan_device(ifaceobj):
222 ifaceobj.link_kind |= ifaceLinkKind.VXLAN
223 self._set_global_local_ip(ifaceobj)
223ba5af 224
0500d5d8
JF
225 self.__check_and_tag_l3vxi(ifaceobj)
226
782aff35 227 if not old_ifaceobjs and not self.tvd_svd_mix_support:
db4371de
JF
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 )
0500d5d8
JF
240 elif ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
241 pass
db4371de
JF
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
223ba5af
JF
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.
1609696f 254 if not self.vxlan_mcastgrp_ref and (ifaceobj.get_attr_value("vxlan-mcastgrp") or ifaceobj.get_attr_value("vxlan-mcastgrp-map")):
223ba5af
JF
255 self.vxlan_mcastgrp_ref = True
256
59ab29fb 257 elif ifaceobj.name == 'lo' and not old_ifaceobjs:
d486dd0d
JF
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,))
223ba5af 263 self._clagd_vxlan_anycast_ip = clagd_vxlan_list[0]
d486dd0d
JF
264
265 self._set_global_local_ip(ifaceobj)
a382b488
JF
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
d486dd0d
JF
274 return None
275
0500d5d8
JF
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
d486dd0d
JF
284 def _set_global_local_ip(self, ifaceobj):
285 vxlan_local_tunnel_ip = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
223ba5af
JF
286 if vxlan_local_tunnel_ip and not self._vxlan_local_tunnelip:
287 self._vxlan_local_tunnelip = vxlan_local_tunnel_ip
d486dd0d 288
223ba5af
JF
289 @staticmethod
290 def _is_vxlan_device(ifaceobj):
e537a6e6
JF
291 return ifaceobj.link_kind & ifaceLinkKind.VXLAN \
292 or ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN \
0500d5d8 293 or ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI \
e537a6e6 294 or ifaceobj.get_attr_value_first("vxlan-id") \
0500d5d8 295 or ifaceobj.get_attr_value_first("vxlan-vni") \
e537a6e6 296 or ifaceobj.get_attr_value_first("bridge-vlan-vni-map")
d486dd0d 297
223ba5af 298 def __get_vlxan_purge_remotes(self, ifaceobj):
d486dd0d 299 if not ifaceobj:
223ba5af 300 return self._vxlan_purge_remotes
d486dd0d
JF
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:
223ba5af 305 purge_remotes = self._vxlan_purge_remotes
d486dd0d
JF
306 return purge_remotes
307
ec25a08c
JF
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
e521508b 317 def get_vxlan_tos_from_string(self, tos_config):
e521508b
SO
318 if tos_config:
319 if tos_config.lower() == "inherit":
a8dd54b0 320 return 1
e521508b 321 else:
a8dd54b0
JF
322 return int(tos_config)
323 return None
e521508b 324
223ba5af
JF
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)
3218f49d 373 except Exception:
223ba5af 374 self.log_error("%s: invalid vxlan-ageing '%s'" % (ifname, vxlan_ageing_str), ifaceobj)
d486dd0d 375
223ba5af
JF
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 )
d486dd0d 405
ec25a08c 406 try:
223ba5af
JF
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)
ec25a08c 426 )
ec25a08c
JF
427 return
428
223ba5af
JF
429 self.logger.info("%s: set vxlan-port %s" % (ifname, vxlan_port))
430 user_request_vxlan_info_data[Link.IFLA_VXLAN_PORT] = vxlan_port
3218f49d 431 except Exception:
223ba5af
JF
432 self.log_error("%s: invalid vxlan-port '%s'" % (ifname, vxlan_port_str), ifaceobj)
433
e521508b
SO
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
a8dd54b0
JF
455 if not vxlan_tos_str:
456 return
457
e521508b 458 cached_ifla_vxlan_tos = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_TOS)
a8dd54b0 459
e521508b
SO
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
223ba5af
JF
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
9144496d 501 return vxlan_ttl
3218f49d 502 except Exception:
223ba5af
JF
503 self.log_error("%s: invalid vxlan-ttl '%s'" % (ifname, vxlan_ttl_str), ifaceobj)
504
59ab29fb
JF
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
223ba5af
JF
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:
59ab29fb
JF
524 cached_ifla_vxlan_local = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
525
223ba5af
JF
526 # on ifreload do not overwrite anycast_ip to individual ip
527 # if clagd has modified
59ab29fb 528 if self._clagd_vxlan_anycast_ip and cached_ifla_vxlan_local:
0e936c3f 529 anycastip = ipnetwork.IPNetwork(self._clagd_vxlan_anycast_ip)
59ab29fb
JF
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
223ba5af
JF
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:
0e936c3f
JF
550 local = ipnetwork.IPv4Address(local)
551
552 if local.initialized_with_prefixlen:
223ba5af 553 self.logger.warning("%s: vxlan-local-tunnelip %s: netmask ignored" % (ifname, local))
0e936c3f
JF
554
555 except Exception as e:
556 raise Exception("%s: invalid vxlan-local-tunnelip %s: %s" % (ifname, local, str(e)))
223ba5af 557
223ba5af
JF
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
40658337
JF
572 def __get_vxlan_attribute(self, ifaceobj, attr_name):
573 vxlan_attr_value = ifaceobj.get_attr_value_first(attr_name)
d486dd0d 574
40658337
JF
575 if not vxlan_attr_value:
576 vxlan_attr_value = policymanager.policymanager_api.get_attr_default(
223ba5af 577 module_name=self.__class__.__name__,
40658337 578 attr=attr_name
223ba5af 579 )
d486dd0d 580
40658337 581 return vxlan_attr_value
d486dd0d 582
6015cce2
QZ
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
223ba5af
JF
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:
0e936c3f
JF
716 group = ipnetwork.IPv4Address(group)
717
718 if group.initialized_with_prefixlen:
223ba5af 719 self.logger.warning("%s: vxlan-svcnodeip %s: netmask ignored" % (ifname, group))
223ba5af 720
0e936c3f
JF
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:
223ba5af
JF
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:
0e936c3f
JF
735 mcast_grp = ipnetwork.IPv4Address(mcast_grp)
736
737 if mcast_grp.initialized_with_prefixlen:
223ba5af 738 self.logger.warning("%s: vxlan-mcastgrp %s: netmask ignored" % (ifname, mcast_grp))
223ba5af 739
0e936c3f
JF
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:
223ba5af
JF
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
d486dd0d 753 else:
223ba5af
JF
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:
0e936c3f 767 group = ipnetwork.IPNetwork("0.0.0.0")
223ba5af
JF
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
d486dd0d
JF
776
777 if link_exists:
223ba5af
JF
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 )
b067bba9 783 else:
223ba5af
JF
784 self.logger.info(
785 "%s: vxlan-mcastgrp configuration changed: flapping vxlan device required" % ifname
786 )
b067bba9 787
223ba5af 788 return group, multicast_group_change
d486dd0d 789
40658337
JF
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
1b7f1f34 821 except Exception:
40658337
JF
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
1b7f1f34 841 except Exception:
40658337
JF
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
223ba5af
JF
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)
d486dd0d 899
223ba5af
JF
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
a8dd54b0 904 def __config_vxlan_udp_csum(self, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data):
e521508b 905 vxlan_udp_csum = ifaceobj.get_attr_value_first('vxlan-udp-csum')
a8dd54b0
JF
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
e521508b
SO
916 if not vxlan_udp_csum:
917 vxlan_udp_csum = self.get_attr_default_value('vxlan-udp-csum')
e521508b 918
a8dd54b0
JF
919 if vxlan_udp_csum:
920 vxlan_udp_csum = utils.get_boolean_from_string(vxlan_udp_csum)
921 else:
922 return
e521508b
SO
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
1609696f 928 def __get_vxlan_physdev(self, ifaceobj, mcastgrp, mcastgrp_map):
223ba5af
JF
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")
d486dd0d 940
223ba5af
JF
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
1609696f 943 if physdev or (not mcastgrp and not mcastgrp_map):
223ba5af
JF
944 return physdev
945
946 physdev = self.vxlan_physdev_mcast
947
948 if not self.cache.link_exists(physdev):
1609696f
JF
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))
223ba5af
JF
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:
d486dd0d 967 try:
223ba5af 968 vxlan_physdev_ifindex = int(self.sysfs.read_file_oneline("/sys/class/net/%s/ifindex" % vxlan_physdev))
3218f49d 969 except Exception:
223ba5af
JF
970 self.logger.error("%s: physdev %s doesn't exists" % (ifaceobj.name, vxlan_physdev))
971 return
d486dd0d 972
223ba5af
JF
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
d486dd0d 976
223ba5af
JF
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
66eb9ce3
JF
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 )
238e0485
JF
994 if not maps:
995 return {}
66eb9ce3
JF
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
20aabf55 1030 def single_vxlan_device_vni_filter(self, ifaceobj, vxlan_mcast_grp):
84c47c4f 1031 vnis = []
af8d5db2 1032 vnisd = {}
84c47c4f 1033 for vlan_vni_map in ifaceobj.get_attr_value("bridge-vlan-vni-map"):
7f0310a7
RP
1034 try:
1035 (vls, vis) = utils.get_vlan_vnis_in_map(vlan_vni_map)
af8d5db2
RP
1036 for v in utils.ranges_to_ints(vis):
1037 vnisd[v] = None
1038 except Exception as e:
1039 self.logger.error("%s: %s (%s)" %(ifaceobj.name, vlan_vni_map, str(e)))
1040 return
20aabf55 1041 if vxlan_mcast_grp:
af8d5db2 1042 try:
20aabf55 1043 for v, g in vxlan_mcast_grp.items():
af8d5db2 1044 if v not in vnisd.keys():
b3a93dfc
JF
1045 self.logger.error("%s: group %s configured for a vni (%s) not specified in vlan vni map"
1046 %(ifaceobj.name, g, v))
af8d5db2 1047 return
20aabf55 1048 vnisd[v] = str(g)
7f0310a7
RP
1049 except Exception as e:
1050 self.logger.error("%s: %s (%s)" %(ifaceobj.name, vlan_vni_map, str(e)))
1051 return
af8d5db2
RP
1052
1053 vnis_int = utils.ranges_to_ints(vnis)
1054 self.iproute2.bridge_link_update_vni_filter(ifaceobj.name, vnisd)
84c47c4f 1055
db4371de
JF
1056 def check_and_raise_svd_tvd_errors(self, ifaceobj):
1057 err = self.svd_tvd_errors.get(ifaceobj.name)
1058
1059 if err:
1060 self.log_error(err, ifaceobj)
1061
0500d5d8
JF
1062 def __get_vxlan_vni_list(self, ifaceobj, string=True):
1063 vxlan_vni_str = self.__get_vxlan_attribute(ifaceobj, "vxlan-vni")
1064
1065 if vxlan_vni_str:
1066 # validate range but return string to be used in bridge vni add cmd
1067 vxlan_vni_range = utils.ranges_to_ints(vxlan_vni_str.split())
1068 return vxlan_vni_str if string else vxlan_vni_range
1069
1070 return None
db4371de 1071
223ba5af 1072 def _up(self, ifaceobj):
db4371de
JF
1073 self.check_and_raise_svd_tvd_errors(ifaceobj)
1074
223ba5af
JF
1075 vxlan_id_str = ifaceobj.get_attr_value_first("vxlan-id")
1076
0500d5d8 1077 if not ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN and not ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI and not vxlan_id_str:
e537a6e6 1078 self.logger.warning("%s: missing vxlan-id attribute on vxlan device" % ifaceobj.name)
223ba5af
JF
1079 return
1080
1081 ifname = ifaceobj.name
1082 link_exists = self.cache.link_exists(ifname)
1083
1084 if link_exists:
1085 # if link already exists make sure this is a vxlan
1086 device_link_kind = self.cache.get_link_kind(ifname)
1087
1088 if device_link_kind != "vxlan":
1089 self.logger.error(
1090 "%s: device already exists and is not a vxlan (type %s)"
1091 % (ifname, device_link_kind)
d486dd0d 1092 )
223ba5af
JF
1093 ifaceobj.set_status(ifaceStatus.ERROR)
1094 return
d486dd0d 1095
223ba5af
JF
1096 # get vxlan running attributes
1097 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1098 else:
1099 cached_vxlan_ifla_info_data = {}
d486dd0d 1100
223ba5af 1101 user_request_vxlan_info_data = {}
d486dd0d 1102
e537a6e6
JF
1103 if vxlan_id_str:
1104 # for single vxlan device we don't have a vxlan-id
1105 self.__config_vxlan_id(ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1106
223ba5af
JF
1107 self.__config_vxlan_learning(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1108 self.__config_vxlan_ageing(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1109 self.__config_vxlan_port(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
9144496d 1110 vxlan_ttl = self.__config_vxlan_ttl(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
e521508b 1111 self.__config_vxlan_tos(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
a8dd54b0 1112 self.__config_vxlan_udp_csum(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
223ba5af
JF
1113 local = self.__config_vxlan_local_tunnelip(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1114
0500d5d8
JF
1115 vxlan_vni = self.__get_vxlan_vni_list(ifaceobj)
1116
40658337
JF
1117 vxlan_mcast_grp = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp")
1118 vxlan_svcnodeip = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip")
1119
1120 vxlan_mcast_grp6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp6")
1121 vxlan_svcnodeip6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip6")
1122
6015cce2 1123 vxlan_mcast_grp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
1609696f
JF
1124
1125 vxlan_physdev = self.__get_vxlan_physdev(ifaceobj, vxlan_mcast_grp, vxlan_mcast_grp_map)
223ba5af 1126
84c47c4f
RP
1127 vxlan_vnifilter = self.__get_vxlan_attribute(ifaceobj, "vxlan-vnifilter")
1128
223ba5af
JF
1129 vxlan_physdev_changed = self.__config_vxlan_physdev(
1130 link_exists,
1131 ifaceobj,
1132 vxlan_physdev,
1133 user_request_vxlan_info_data,
1134 cached_vxlan_ifla_info_data
1135 )
1136
1137 group, multicast_group_changed = self.__config_vxlan_group(
1138 ifname,
1139 ifaceobj,
1140 link_exists,
1141 vxlan_mcast_grp,
1142 vxlan_svcnodeip,
1143 vxlan_physdev,
1144 user_request_vxlan_info_data,
1145 cached_vxlan_ifla_info_data
1146 )
1147
40658337
JF
1148 group6, multicast_group_changed6 = self.__config_vxlan_group6(
1149 ifname,
1150 ifaceobj,
1151 link_exists,
1152 vxlan_mcast_grp6,
1153 vxlan_svcnodeip6,
1154 vxlan_physdev,
1155 user_request_vxlan_info_data,
1156 cached_vxlan_ifla_info_data
1157 )
1158
1159 flap_vxlan_device = link_exists and (multicast_group_changed or multicast_group_changed6 or vxlan_physdev_changed)
223ba5af
JF
1160
1161 if user_request_vxlan_info_data:
1162
2b867068 1163 if link_exists and len(user_request_vxlan_info_data) == 1 and Link.IFLA_VXLAN_ID in user_request_vxlan_info_data:
223ba5af
JF
1164 # if the vxlan already exists it's already cached
1165 # user_request_vxlan_info_data always contains at least one
1166 # element: vxlan-id
1167 self.logger.info('%s: vxlan already exists - no change detected' % ifname)
1168 else:
e537a6e6 1169 if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
2b867068 1170 self.iproute2.link_add_single_vxlan(
e7ecab23 1171 link_exists,
2b867068
JF
1172 ifname,
1173 local.ip if local else None,
1174 group.ip if group else None,
1175 vxlan_physdev,
1176 user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT),
1177 vxlan_vnifilter,
1178 vxlan_ttl
1179 )
0500d5d8
JF
1180 elif ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
1181 self.iproute2.link_add_l3vxi(
1182 link_exists,
1183 ifname,
1184 local.ip if local else None,
1185 group.ip if group else None,
1186 vxlan_physdev,
1187 user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT),
1188 vxlan_ttl
1189 )
1190 try:
1191 self.iproute2.bridge_vni_add(ifname, vxlan_vni)
1192 except Exception as e:
1193 self.logger.warning("%s: l3 vxlan vni failure: %s" % (ifname, e))
e537a6e6
JF
1194 else:
1195 try:
1196 if flap_vxlan_device:
1197 self.netlink.link_down_force(ifname)
1198
1199 self.netlink.link_add_vxlan_with_info_data(ifname, user_request_vxlan_info_data)
1200
1201 if flap_vxlan_device:
1202 self.netlink.link_up_force(ifname)
1203 except Exception as e:
1204 if link_exists:
1205 self.log_error("%s: applying vxlan change failed: %s" % (ifname, str(e)), ifaceobj)
1206 else:
1207 self.log_error("%s: vxlan creation failed: %s" % (ifname, str(e)), ifaceobj)
1208 return
223ba5af 1209
84c47c4f
RP
1210 if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
1211 if vxlan_vnifilter and utils.get_boolean_from_string(vxlan_vnifilter):
20aabf55 1212 self.single_vxlan_device_vni_filter(ifaceobj, vxlan_mcast_grp_map)
84c47c4f 1213
223ba5af
JF
1214 vxlan_purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
1215
1216 remoteips = ifaceobj.get_attr_value('vxlan-remoteip')
1217 if remoteips:
1218 try:
1219 for remoteip in remoteips:
0e936c3f 1220 ipnetwork.IPv4Address(remoteip)
223ba5af
JF
1221 except Exception as e:
1222 self.log_error('%s: vxlan-remoteip: %s' % (ifaceobj.name, str(e)))
1223
1224 if vxlan_purge_remotes or remoteips:
1225 # figure out the diff for remotes and do the bridge fdb updates
1226 # only if provisioned by user and not by an vxlan external
1227 # controller.
0e936c3f
JF
1228 local_str = str(local)
1229
1230 if local_str and remoteips and local_str in remoteips:
1231 remoteips.remove(local_str)
1232
1233 peers = self.iproute2.get_vxlan_peers(ifaceobj.name, str(group.ip) if group else None)
1234
223ba5af
JF
1235 cur_peers = set(peers)
1236 if remoteips:
1237 new_peers = set(remoteips)
1238 del_list = cur_peers.difference(new_peers)
1239 add_list = new_peers.difference(cur_peers)
1240 else:
1241 del_list = cur_peers
1242 add_list = []
d486dd0d 1243
223ba5af 1244 for addr in del_list:
d486dd0d 1245 try:
223ba5af
JF
1246 self.iproute2.bridge_fdb_del(
1247 ifaceobj.name,
1248 "00:00:00:00:00:00",
1249 None, True, addr
1250 )
3218f49d 1251 except Exception:
d486dd0d 1252 pass
d486dd0d 1253
223ba5af 1254 for addr in add_list:
d486dd0d 1255 try:
223ba5af
JF
1256 self.iproute2.bridge_fdb_append(
1257 ifaceobj.name,
1258 "00:00:00:00:00:00",
1259 None, True, addr
1260 )
3218f49d 1261 except Exception:
223ba5af 1262 pass
d486dd0d 1263
66eb9ce3
JF
1264 self.vxlan_remote_ip_map(ifaceobj, vxlan_mcast_grp_map)
1265
1266 def vxlan_remote_ip_map(self, ifaceobj, vxlan_mcast_grp_map):
1267 # get user configured remote ip map
1268 vxlan_remote_ip_map = self.__get_vxlan_remote_ip_map(ifaceobj) or {}
1269
238e0485
JF
1270 # if we have an older config we need to see what needs to be removed
1271 # and not check the running state as FRR or other component can add fdb entries
1272 old_vxlan_remote_ip_map = {}
1273
1274 for old_ifaceobj in statemanager.get_ifaceobjs(ifaceobj.name) or []:
1275 old_vxlan_remote_ip_map = {**old_vxlan_remote_ip_map, **self.__get_vxlan_remote_ip_map(old_ifaceobj)}
1276
3376c233 1277 # go through the user config and add new entries while removing existing entries from 'old_vxlan_remote_ip_map'
66eb9ce3
JF
1278 for vni, ips in vxlan_remote_ip_map.items():
1279 for ip in ips:
238e0485 1280 if ip not in old_vxlan_remote_ip_map.get(vni, []):
66eb9ce3
JF
1281 self.iproute2.bridge_fdb_append(ifaceobj.name, "00:00:00:00:00:00", remote=ip, src_vni=vni)
1282 else:
238e0485 1283 old_vxlan_remote_ip_map.get(vni, []).remove(ip)
66eb9ce3 1284
238e0485
JF
1285 # in old_vxlan_remote_ip_map we have the delta between user config and running config. We should delete those
1286 # extra fdb entries. First we need to make sure that those are not added by vxlan-mcastgrp-map
1287 if old_vxlan_remote_ip_map:
66eb9ce3
JF
1288 for vni, ip in (vxlan_mcast_grp_map or {}).items():
1289 try:
238e0485 1290 old_vxlan_remote_ip_map[vni].remove(ip)
66eb9ce3
JF
1291 except:
1292 pass
1293
238e0485
JF
1294 for vni, ips in old_vxlan_remote_ip_map.items():
1295 for ip in ips:
1296 try:
1297 self.iproute2.bridge_fdb_del_raw(ifaceobj.name, "00:00:00:00:00:00 dst %s src_vni %s" % (ip, vni))
1298 except:
1299 pass
66eb9ce3 1300
ca436937
JF
1301 @staticmethod
1302 def get_vxlan_fdb_src_vni(vxlan_mcast_grp_map):
1303 fdbs = []
56f34349 1304 if vxlan_mcast_grp_map:
6015cce2 1305 for src_vni, dst_ip in vxlan_mcast_grp_map.items():
ca436937
JF
1306 fdbs.append(("00:00:00:00:00:00", src_vni, dst_ip))
1307 return fdbs
1308
56f34349
JF
1309 @staticmethod
1310 def get_svd_running_fdb(ifname):
1311 vxlan_fdb_data = utils.exec_command("bridge fdb show dev %s" % ifname)
1312 current_fdb = []
1313
1314 if vxlan_fdb_data:
1315 # each entry should look like the following:
1316 # 00:00:00:00:00:00 dst 239.1.1.100 src_vni 1000 self permanent
1317 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]:
1318 mac, _, dst, _, src_vni = entry.split()[0:5]
1319 current_fdb.append((mac, src_vni, dst))
1320
1321 return current_fdb
1322
1323 def single_vxlan_device_mcast_grp_map_fdb(self, ifaceobj, ifname, vxlan_mcast_grp_map):
1324 # in this piece of code we won't be checking the running state of the fdb table
1325 # dumping all fdb entries would cause scalability issues in certain cases.
1326
1327 # pulling old mcastgrp-map configuration
1328 old_user_config_fdb = []
1329
1330 for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
6015cce2 1331 old_user_config_fdb += self.get_vxlan_fdb_src_vni(self.__get_vxlan_mcastgrp_map(old_ifaceobj))
56f34349
JF
1332
1333 # new user configuration
1334 user_config_fdb = self.get_vxlan_fdb_src_vni(vxlan_mcast_grp_map)
1335
1336 # compare old and new config to know if we should remove any stale fdb entries.
1337 fdb_entries_to_remove = set(old_user_config_fdb) - set(user_config_fdb)
1338
1339 if fdb_entries_to_remove:
1340 for mac, src_vni, dst_ip in fdb_entries_to_remove:
1341 try:
1342 self.iproute2.bridge_fdb_del_src_vni(ifname, mac, src_vni)
1343 except Exception as e:
1344 if "no such file or directory" not in str(e).lower():
1345 self.logger.warning("%s: removing stale fdb entries failed: %s" % (ifname, str(e)))
af8d5db2
RP
1346
1347 if not user_config_fdb:
1348 # if vxlan-mcastgrp-map wasn't configure return
1349 return
1350
1351 for mac, src_vni, dst_ip in user_config_fdb:
1352 try:
1353 self.iproute2.bridge_fdb_add_src_vni(ifname, src_vni, dst_ip)
1354 except Exception as e:
1355 if "file exists" not in str(e).lower():
1356 ifaceobj.set_status(ifaceStatus.ERROR)
1357 self.log_error(
1358 "%s: vxlan-mcastgrp-map: %s=%s: %s"
1359 % (ifname, src_vni, dst_ip, str(e)), raise_error=False
1360 )
1361
1362 def single_vxlan_device_mcast_grp_map_vnifilter(self, ifaceobj, ifname, vxlan_mcast_grp_map):
1363 # in this piece of code we won't be checking the running state of the fdb table
1364 # dumping all fdb entries would cause scalability issues in certain cases.
1365
1366 # pulling old mcastgrp-map configuration
1367 old_user_config_fdb = []
1368
1369 for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
1370 old_user_config_fdb += self.get_vxlan_fdb_src_vni(self.__get_vxlan_mcastgrp_map(old_ifaceobj))
1371
1372 # new user configuration
1373 user_config_fdb = self.get_vxlan_fdb_src_vni(vxlan_mcast_grp_map)
1374
1375 # compare old and new config to know if we should remove any stale fdb entries.
1376 fdb_entries_to_remove = set(old_user_config_fdb) - set(user_config_fdb)
1377
1378 self.logger.info(old_user_config_fdb)
1379 self.logger.info(user_config_fdb)
1380 self.logger.info(fdb_entries_to_remove)
1381
1382 if fdb_entries_to_remove:
1383 for mac, src_vni, dst_ip in fdb_entries_to_remove:
1384 try:
1385 self.iproute2.bridge_fdb_del_src_vni(ifname, mac, src_vni)
1386 except Exception as e:
1387 if "no such file or directory" not in str(e).lower():
1388 self.logger.warning("%s: removing stale fdb entries failed: %s" % (ifname, str(e)))
56f34349
JF
1389
1390 if not user_config_fdb:
1391 # if vxlan-mcastgrp-map wasn't configure return
1392 return
1393
1394 for mac, src_vni, dst_ip in user_config_fdb:
1395 try:
1396 self.iproute2.bridge_fdb_add_src_vni(ifname, src_vni, dst_ip)
1397 except Exception as e:
1398 if "file exists" not in str(e).lower():
1399 ifaceobj.set_status(ifaceStatus.ERROR)
1400 self.log_error(
1401 "%s: vxlan-mcastgrp-map: %s=%s: %s"
1402 % (ifname, src_vni, dst_ip, str(e)), raise_error=False
1403 )
1404
d486dd0d
JF
1405 def _down(self, ifaceobj):
1406 try:
223ba5af 1407 self.netlink.link_del(ifaceobj.name)
3b01ed76 1408 except Exception as e:
d486dd0d
JF
1409 self.log_warn(str(e))
1410
223ba5af
JF
1411 @staticmethod
1412 def _query_check_n_update(ifaceobj, ifaceobjcurr, attrname, attrval, running_attrval):
d486dd0d
JF
1413 if not ifaceobj.get_attr_value_first(attrname):
1414 return
1415 if running_attrval and attrval == running_attrval:
223ba5af 1416 ifaceobjcurr.update_config_with_status(attrname, attrval, 0)
d486dd0d 1417 else:
223ba5af 1418 ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1)
d486dd0d 1419
223ba5af
JF
1420 @staticmethod
1421 def _query_check_n_update_addresses(ifaceobjcurr, attrname, addresses, running_addresses):
d486dd0d
JF
1422 if addresses:
1423 for a in addresses:
1424 if a in running_addresses:
1425 ifaceobjcurr.update_config_with_status(attrname, a, 0)
1426 else:
1427 ifaceobjcurr.update_config_with_status(attrname, a, 1)
3b01ed76
JF
1428 running_addresses = set(running_addresses).difference(
1429 set(addresses))
223ba5af 1430 [ifaceobjcurr.update_config_with_status(attrname, a, 1) for a in running_addresses]
d486dd0d
JF
1431
1432 def _query_check(self, ifaceobj, ifaceobjcurr):
223ba5af
JF
1433 ifname = ifaceobj.name
1434
1435 if not self.cache.link_exists(ifname):
d486dd0d 1436 return
d486dd0d 1437
223ba5af
JF
1438 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1439
1440 if not cached_vxlan_ifla_info_data:
1441 ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, self.get_mod_attrs(), -1)
1442 return
1443
1444 for vxlan_attr_str, vxlan_attr_nl, callable_type in (
1445 ('vxlan-id', Link.IFLA_VXLAN_ID, int),
1446 ('vxlan-ttl', Link.IFLA_VXLAN_TTL, int),
e521508b 1447 ('vxlan-tos', Link.IFLA_VXLAN_TOS, int),
223ba5af
JF
1448 ('vxlan-port', Link.IFLA_VXLAN_PORT, int),
1449 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, int),
0e936c3f 1450 ('vxlan-mcastgrp', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
40658337 1451 ('vxlan-mcastgrp6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
0e936c3f 1452 ('vxlan-svcnodeip', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
40658337 1453 ('vxlan-svcnodeip6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
223ba5af
JF
1454 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, lambda x: self.cache.get_ifindex(x)),
1455 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
e521508b 1456 ('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
223ba5af
JF
1457 ):
1458 vxlan_attr_value = ifaceobj.get_attr_value_first(vxlan_attr_str)
d486dd0d 1459
223ba5af
JF
1460 if not vxlan_attr_value:
1461 continue
1462
1463 cached_vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
1464
1465 try:
1466 vxlan_attr_value_nl = callable_type(vxlan_attr_value)
1467 except Exception as e:
1468 self.logger.warning('%s: %s: %s' % (ifname, vxlan_attr_str, str(e)))
1469 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
1470 continue
1471
1472 if vxlan_attr_value_nl == cached_vxlan_attr_value:
1473 ifaceobjcurr.update_config_with_status(vxlan_attr_str, vxlan_attr_value, 0)
1474 else:
1475 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
1476
1477 #
1478 # vxlan-local-tunnelip
1479 #
1480 running_attrval = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
d486dd0d
JF
1481 attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
1482 if not attrval:
223ba5af 1483 attrval = self._vxlan_local_tunnelip
0e936c3f 1484 # TODO: vxlan._vxlan_local_tunnelip should be a ipnetwork.IPNetwork obj
d486dd0d
JF
1485 ifaceobj.update_config('vxlan-local-tunnelip', attrval)
1486
223ba5af 1487 if str(running_attrval) == self._clagd_vxlan_anycast_ip:
d486dd0d
JF
1488 # if local ip is anycast_ip, then let query_check to go through
1489 attrval = self._clagd_vxlan_anycast_ip
d486dd0d 1490
223ba5af
JF
1491 self._query_check_n_update(
1492 ifaceobj,
1493 ifaceobjcurr,
1494 'vxlan-local-tunnelip',
1495 str(attrval),
0e936c3f 1496 str(running_attrval.ip) if running_attrval else None
223ba5af 1497 )
d486dd0d 1498
223ba5af
JF
1499 #
1500 # vxlan-remoteip
1501 #
1502 purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
d486dd0d
JF
1503 if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'):
1504 # If purge remotes or if vxlan-remoteip's are set
1505 # in the config file, we are owners of the installed
1506 # remote-ip's, lets check and report any remote ips we don't
1507 # understand
223ba5af 1508 cached_svcnode = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
d486dd0d 1509
223ba5af
JF
1510 self._query_check_n_update_addresses(
1511 ifaceobjcurr,
1512 'vxlan-remoteip',
1513 ifaceobj.get_attr_value('vxlan-remoteip'),
0e936c3f 1514 self.iproute2.get_vxlan_peers(ifaceobj.name, str(cached_svcnode.ip) if cached_svcnode else None)
223ba5af 1515 )
a382b488 1516
0500d5d8
JF
1517 # not ideal but will work for now, l3vxi dev:
1518 if ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
1519 user_config_vni_list = set(self.__get_vxlan_vni_list(ifaceobj, string=False))
1520 vxlan_vni_list = set()
1521
1522 for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
1523 for vni_obj in obj.get("vnis", []):
1524 start = vni_obj.get("vni")
1525 end = vni_obj.get("vniEnd")
1526
1527 for vni in utils.ranges_to_ints(["%s-%s" % (start, end if end else start)]):
1528 vxlan_vni_list.add(vni)
1529
1530 ifaceobjcurr.update_config_with_status(
1531 "vxlan-vni",
1532 " ".join(utils.compress_into_ranges(vxlan_vni_list)),
1533 vxlan_vni_list != user_config_vni_list
1534 )
1535
e6edcd21 1536 #
66eb9ce3 1537 # vxlan-mcastgrp-map & vxlan-remoteip-map
d4403f1e
JF
1538 # fdb entries can be added by FRR, so we won't be checking the running
1539 # state if there's no record of a user configuration in /e/n/i
1540 user_mcastgrp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
1541 user_remote_ip_map = self.__get_vxlan_remote_ip_map(ifaceobj)
1542
9ca87c5e 1543 if not user_mcastgrp_map and not user_remote_ip_map:
d4403f1e
JF
1544 return
1545
66eb9ce3
JF
1546 fdb_mcast = {}
1547 fdb_remote = {}
e6edcd21 1548
9ca87c5e
JF
1549 if user_remote_ip_map:
1550 for _, src_vni, dst in self.get_svd_running_fdb(ifname):
1551 ip = ipnetwork.IPv4Address(dst)
e6edcd21 1552
9ca87c5e
JF
1553 if not ip.is_multicast:
1554 fdb_remote.setdefault(int(src_vni), []).append(ip)
1555
1556 if user_mcastgrp_map:
1557 for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
1558 for vni in obj.get("vnis", []):
1559 group = vni.get("group")
1560
1561 if not group:
1562 continue
1563
1564 # we need to reconvert back to ipaddress.IPv4Address because
1565 # the existing code uses this type of obj (namely: __get_vxlan_mcastgrp_map)
1566 fdb_mcast[vni.get("vni")] = IPv4Address(group)
e6edcd21 1567
66eb9ce3
JF
1568 #
1569 # vxlan-mcastgrp-map
1570 #
66eb9ce3
JF
1571 if not user_mcastgrp_map and fdb_mcast:
1572 ifaceobjcurr.update_config_with_status(
1573 "vxlan-mcastgrp-map",
1574 " ".join(["%s=%s" % (vni, ip) for vni, ip in fdb_mcast.items()]),
1575 1
1576 )
1577 elif user_mcastgrp_map and not fdb_mcast:
1578 ifaceobjcurr.update_config_with_status("vxlan-mcastgrp-map", "", 1)
1579 elif user_mcastgrp_map or fdb_mcast:
1580 ifaceobjcurr.update_config_with_status(
1581 "vxlan-mcastgrp-map",
1582 " ".join(["%s=%s" % (vni, ip) for vni, ip in fdb_mcast.items()]),
1583 user_mcastgrp_map != fdb_mcast
1584 )
e6edcd21 1585
66eb9ce3
JF
1586 #
1587 # vxlan-remoteip-map
1588 #
66eb9ce3 1589 if not user_remote_ip_map and fdb_remote:
e6edcd21 1590 ifaceobjcurr.update_config_with_status(
66eb9ce3
JF
1591 "vxlan-remoteip-map",
1592 " ".join(["%s=%s" % (vni, ",".join(map(str, ips))) for vni, ips in fdb_remote.items()]),
1593 1
e6edcd21 1594 )
66eb9ce3
JF
1595 elif user_remote_ip_map and not fdb_remote:
1596 ifaceobjcurr.update_config_with_status("vxlan-remoteip-map", "", 1)
1597 elif user_remote_ip_map or fdb_remote:
1598
1599 if user_remote_ip_map == fdb_remote:
1600 # display the user config with "pass"
1601 for config in ifaceobj.get_attr_value("vxlan-remoteip-map"):
1602 ifaceobjcurr.update_config_with_status(
1603 "vxlan-remoteip-map",
1604 config,
1605 0
1606 )
1607 else:
1608 # display current running state with ip ranges (but no vni ranges yet)
1609 ifaceobjcurr.update_config_with_status(
1610 "vxlan-remoteip-map",
1611 " ".join(["%s=%s" % (vni, ",".join(utils.compress_into_ip_ranges(ips))) for vni, ips in fdb_remote.items()]),
1612 1
1613 )
e6edcd21 1614
223ba5af
JF
1615 def _query_running(self, ifaceobjrunning):
1616 ifname = ifaceobjrunning.name
a382b488 1617
223ba5af
JF
1618 if not self.cache.link_exists(ifname):
1619 return
a382b488 1620
223ba5af 1621 if not self.cache.get_link_kind(ifname) == 'vxlan':
d486dd0d 1622 return
223ba5af
JF
1623
1624 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1625
1626 if not cached_vxlan_ifla_info_data:
d486dd0d
JF
1627 return
1628
223ba5af
JF
1629 #
1630 # vxlan-id
1631 #
1632 vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
1633
1634 if not vxlan_id:
1635 # no vxlan id, meaning this not a vxlan
1636 return
1637
1638 ifaceobjrunning.update_config('vxlan-id', str(vxlan_id))
1639
1640 #
1641 # vxlan-port
1642 #
1643 vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
1644
1645 if vxlan_port:
1646 ifaceobjrunning.update_config('vxlan-port', vxlan_port)
1647
1648 #
1649 # vxlan-svcnode
1650 #
1651 vxlan_svcnode_value = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
1652
1653 if vxlan_svcnode_value:
1654 vxlan_svcnode_value = str(vxlan_svcnode_value)
1655 ifaceobjrunning.update_config('vxlan-svcnode', vxlan_svcnode_value)
d486dd0d 1656
223ba5af
JF
1657 #
1658 # vxlan-remoteip
1659 #
1660 purge_remotes = self.__get_vlxan_purge_remotes(None)
d486dd0d
JF
1661 if purge_remotes:
1662 # if purge_remotes is on, it means we own the
1663 # remote ips. Query them and add it to the running config
223ba5af 1664 attrval = self.iproute2.get_vxlan_peers(ifname, vxlan_svcnode_value)
d486dd0d 1665 if attrval:
223ba5af
JF
1666 [ifaceobjrunning.update_config('vxlan-remoteip', a) for a in attrval]
1667
1668 #
1669 # vxlan-link
1670 # vxlan-ageing
1671 # vxlan-learning
1672 # vxlan-local-tunnelip
1673 #
1674 for vxlan_attr_name, vxlan_attr_nl, callable_netlink_value_to_string in (
1675 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, self._get_ifname_for_ifindex),
1676 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, str),
1677 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda value: 'on' if value else 'off'),
e521508b 1678 ('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda value: 'on' if value else 'off'),
223ba5af
JF
1679 ('vxlan-local-tunnelip', Link.IFLA_VXLAN_LOCAL, str),
1680 ):
1681 vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
1682
1683 if vxlan_attr_value is not None:
dc74ceda 1684 vxlan_attr_value_str = callable_netlink_value_to_string(vxlan_attr_value)
223ba5af
JF
1685
1686 if vxlan_attr_value:
1687 ifaceobjrunning.update_config(vxlan_attr_name, vxlan_attr_value_str)
1688
1689 def _get_ifname_for_ifindex(self, ifindex):
1690 """
1691 we need this middle-man function to query the cache
1692 cache.get_ifname can raise KeyError, we need to catch
1693 it and return None
1694 """
1695 try:
1696 return self.cache.get_ifname(ifindex)
1697 except KeyError:
1698 return None
a382b488 1699
223ba5af
JF
1700 _run_ops = {
1701 "pre-up": _up,
1702 "post-down": _down,
1703 "query-running": _query_running,
1704 "query-checkcurr": _query_check
1705 }
d486dd0d
JF
1706
1707 def get_ops(self):
3b01ed76 1708 return list(self._run_ops.keys())
d486dd0d 1709
d486dd0d
JF
1710 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
1711 op_handler = self._run_ops.get(operation)
1712 if not op_handler:
1713 return
223ba5af 1714
66eb9ce3
JF
1715 if not self._is_vxlan_device(ifaceobj):
1716 return
223ba5af 1717
66eb9ce3 1718 if "query" not in operation:
223ba5af
JF
1719 if not self.vxlan_mcastgrp_ref \
1720 and self.vxlan_physdev_mcast \
1721 and self.cache.link_exists(self.vxlan_physdev_mcast):
1722 self.netlink.link_del(self.vxlan_physdev_mcast)
1723 self.reset()
1724
d486dd0d
JF
1725 if operation == 'query-checkcurr':
1726 op_handler(self, ifaceobj, query_ifaceobj)
1727 else:
1728 op_handler(self, ifaceobj)