]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/addons/vxlan.py
addons: address: fix merge-indentation issue
[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
421e9573 18 from ifupdown2.ifupdown.iface import ifaceLinkKind, ifaceLinkPrivFlags, ifaceStatus, iface
d486dd0d 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
421e9573 34 from ifupdown.iface import ifaceLinkKind, ifaceLinkPrivFlags, ifaceStatus, iface
d486dd0d 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):
af8d5db2 1031 vnisd = {}
84c47c4f 1032 for vlan_vni_map in ifaceobj.get_attr_value("bridge-vlan-vni-map"):
7f0310a7
RP
1033 try:
1034 (vls, vis) = utils.get_vlan_vnis_in_map(vlan_vni_map)
af8d5db2
RP
1035 for v in utils.ranges_to_ints(vis):
1036 vnisd[v] = None
1037 except Exception as e:
1038 self.logger.error("%s: %s (%s)" %(ifaceobj.name, vlan_vni_map, str(e)))
1039 return
20aabf55 1040 if vxlan_mcast_grp:
af8d5db2 1041 try:
20aabf55 1042 for v, g in vxlan_mcast_grp.items():
af8d5db2 1043 if v not in vnisd.keys():
b3a93dfc
JF
1044 self.logger.error("%s: group %s configured for a vni (%s) not specified in vlan vni map"
1045 %(ifaceobj.name, g, v))
af8d5db2 1046 return
20aabf55 1047 vnisd[v] = str(g)
7f0310a7
RP
1048 except Exception as e:
1049 self.logger.error("%s: %s (%s)" %(ifaceobj.name, vlan_vni_map, str(e)))
1050 return
af8d5db2
RP
1051
1052 vnis_int = utils.ranges_to_ints(vnis)
1053 self.iproute2.bridge_link_update_vni_filter(ifaceobj.name, vnisd)
84c47c4f 1054
db4371de
JF
1055 def check_and_raise_svd_tvd_errors(self, ifaceobj):
1056 err = self.svd_tvd_errors.get(ifaceobj.name)
1057
1058 if err:
1059 self.log_error(err, ifaceobj)
1060
0500d5d8
JF
1061 def __get_vxlan_vni_list(self, ifaceobj, string=True):
1062 vxlan_vni_str = self.__get_vxlan_attribute(ifaceobj, "vxlan-vni")
1063
1064 if vxlan_vni_str:
1065 # validate range but return string to be used in bridge vni add cmd
1066 vxlan_vni_range = utils.ranges_to_ints(vxlan_vni_str.split())
1067 return vxlan_vni_str if string else vxlan_vni_range
1068
1069 return None
db4371de 1070
223ba5af 1071 def _up(self, ifaceobj):
db4371de
JF
1072 self.check_and_raise_svd_tvd_errors(ifaceobj)
1073
223ba5af
JF
1074 vxlan_id_str = ifaceobj.get_attr_value_first("vxlan-id")
1075
0500d5d8 1076 if not ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN and not ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI and not vxlan_id_str:
e537a6e6 1077 self.logger.warning("%s: missing vxlan-id attribute on vxlan device" % ifaceobj.name)
223ba5af
JF
1078 return
1079
1080 ifname = ifaceobj.name
1081 link_exists = self.cache.link_exists(ifname)
1082
1083 if link_exists:
1084 # if link already exists make sure this is a vxlan
1085 device_link_kind = self.cache.get_link_kind(ifname)
1086
1087 if device_link_kind != "vxlan":
1088 self.logger.error(
1089 "%s: device already exists and is not a vxlan (type %s)"
1090 % (ifname, device_link_kind)
d486dd0d 1091 )
223ba5af
JF
1092 ifaceobj.set_status(ifaceStatus.ERROR)
1093 return
d486dd0d 1094
223ba5af
JF
1095 # get vxlan running attributes
1096 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1097 else:
1098 cached_vxlan_ifla_info_data = {}
d486dd0d 1099
223ba5af 1100 user_request_vxlan_info_data = {}
d486dd0d 1101
e537a6e6
JF
1102 if vxlan_id_str:
1103 # for single vxlan device we don't have a vxlan-id
1104 self.__config_vxlan_id(ifname, ifaceobj, vxlan_id_str, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1105
223ba5af
JF
1106 self.__config_vxlan_learning(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1107 self.__config_vxlan_ageing(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1108 self.__config_vxlan_port(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
9144496d 1109 vxlan_ttl = self.__config_vxlan_ttl(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
e521508b 1110 self.__config_vxlan_tos(ifname, ifaceobj, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
a8dd54b0 1111 self.__config_vxlan_udp_csum(ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
223ba5af
JF
1112 local = self.__config_vxlan_local_tunnelip(ifname, ifaceobj, link_exists, user_request_vxlan_info_data, cached_vxlan_ifla_info_data)
1113
0500d5d8
JF
1114 vxlan_vni = self.__get_vxlan_vni_list(ifaceobj)
1115
40658337
JF
1116 vxlan_mcast_grp = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp")
1117 vxlan_svcnodeip = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip")
1118
1119 vxlan_mcast_grp6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-mcastgrp6")
1120 vxlan_svcnodeip6 = self.__get_vxlan_attribute(ifaceobj, "vxlan-svcnodeip6")
1121
6015cce2 1122 vxlan_mcast_grp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
1609696f
JF
1123
1124 vxlan_physdev = self.__get_vxlan_physdev(ifaceobj, vxlan_mcast_grp, vxlan_mcast_grp_map)
223ba5af 1125
84c47c4f
RP
1126 vxlan_vnifilter = self.__get_vxlan_attribute(ifaceobj, "vxlan-vnifilter")
1127
223ba5af
JF
1128 vxlan_physdev_changed = self.__config_vxlan_physdev(
1129 link_exists,
1130 ifaceobj,
1131 vxlan_physdev,
1132 user_request_vxlan_info_data,
1133 cached_vxlan_ifla_info_data
1134 )
1135
1136 group, multicast_group_changed = self.__config_vxlan_group(
1137 ifname,
1138 ifaceobj,
1139 link_exists,
1140 vxlan_mcast_grp,
1141 vxlan_svcnodeip,
1142 vxlan_physdev,
1143 user_request_vxlan_info_data,
1144 cached_vxlan_ifla_info_data
1145 )
1146
40658337
JF
1147 group6, multicast_group_changed6 = self.__config_vxlan_group6(
1148 ifname,
1149 ifaceobj,
1150 link_exists,
1151 vxlan_mcast_grp6,
1152 vxlan_svcnodeip6,
1153 vxlan_physdev,
1154 user_request_vxlan_info_data,
1155 cached_vxlan_ifla_info_data
1156 )
1157
1158 flap_vxlan_device = link_exists and (multicast_group_changed or multicast_group_changed6 or vxlan_physdev_changed)
223ba5af
JF
1159
1160 if user_request_vxlan_info_data:
1161
2b867068 1162 if link_exists and len(user_request_vxlan_info_data) == 1 and Link.IFLA_VXLAN_ID in user_request_vxlan_info_data:
223ba5af
JF
1163 # if the vxlan already exists it's already cached
1164 # user_request_vxlan_info_data always contains at least one
1165 # element: vxlan-id
1166 self.logger.info('%s: vxlan already exists - no change detected' % ifname)
1167 else:
e537a6e6 1168 if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
2b867068 1169 self.iproute2.link_add_single_vxlan(
e7ecab23 1170 link_exists,
2b867068
JF
1171 ifname,
1172 local.ip if local else None,
1173 group.ip if group else None,
1174 vxlan_physdev,
1175 user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT),
1176 vxlan_vnifilter,
1177 vxlan_ttl
1178 )
0500d5d8
JF
1179 elif ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
1180 self.iproute2.link_add_l3vxi(
1181 link_exists,
1182 ifname,
1183 local.ip if local else None,
1184 group.ip if group else None,
1185 vxlan_physdev,
1186 user_request_vxlan_info_data.get(Link.IFLA_VXLAN_PORT),
1187 vxlan_ttl
1188 )
e537a6e6
JF
1189 else:
1190 try:
1191 if flap_vxlan_device:
1192 self.netlink.link_down_force(ifname)
1193
1194 self.netlink.link_add_vxlan_with_info_data(ifname, user_request_vxlan_info_data)
1195
1196 if flap_vxlan_device:
1197 self.netlink.link_up_force(ifname)
1198 except Exception as e:
1199 if link_exists:
1200 self.log_error("%s: applying vxlan change failed: %s" % (ifname, str(e)), ifaceobj)
1201 else:
1202 self.log_error("%s: vxlan creation failed: %s" % (ifname, str(e)), ifaceobj)
1203 return
223ba5af 1204
2fbbfa72
JF
1205 if ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
1206 add_vni = True
1207 if link_exists:
1208 running_vxlan_vni_set = set()
1209
1210 for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
1211 for vni_obj in obj.get("vnis", []):
1212 start = vni_obj.get("vni")
1213 end = vni_obj.get("vniEnd")
1214
1215 for vni in utils.ranges_to_ints(["%s-%s" % (start, end if end else start)]):
1216 running_vxlan_vni_set.add(vni)
1217
1218 if running_vxlan_vni_set != set(utils.ranges_to_ints([vxlan_vni])):
1219 self.iproute2.bridge_vni_int_set_del(ifname, running_vxlan_vni_set)
1220 else:
1221 add_vni = False
1222
1223 if add_vni:
1224 try:
1225 self.iproute2.bridge_vni_add(ifname, vxlan_vni)
1226 except Exception as e:
1227 self.logger.warning("%s: l3 vxlan vni failure: %s" % (ifname, e))
1228
84c47c4f
RP
1229 if ifaceobj.link_privflags & ifaceLinkPrivFlags.SINGLE_VXLAN:
1230 if vxlan_vnifilter and utils.get_boolean_from_string(vxlan_vnifilter):
20aabf55 1231 self.single_vxlan_device_vni_filter(ifaceobj, vxlan_mcast_grp_map)
84c47c4f 1232
223ba5af
JF
1233 vxlan_purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
1234
1235 remoteips = ifaceobj.get_attr_value('vxlan-remoteip')
1236 if remoteips:
1237 try:
1238 for remoteip in remoteips:
0e936c3f 1239 ipnetwork.IPv4Address(remoteip)
223ba5af
JF
1240 except Exception as e:
1241 self.log_error('%s: vxlan-remoteip: %s' % (ifaceobj.name, str(e)))
1242
35a4278f
JF
1243 # get old remote ips to compare with new user config value and
1244 # purge any removed remote ip
1245 old_remoteips = self.get_old_remote_ips(ifaceobj.name)
1246
1247 if vxlan_purge_remotes or remoteips or (remoteips != old_remoteips):
223ba5af
JF
1248 # figure out the diff for remotes and do the bridge fdb updates
1249 # only if provisioned by user and not by an vxlan external
1250 # controller.
0e936c3f
JF
1251 local_str = str(local)
1252
1253 if local_str and remoteips and local_str in remoteips:
1254 remoteips.remove(local_str)
1255
1256 peers = self.iproute2.get_vxlan_peers(ifaceobj.name, str(group.ip) if group else None)
1257
223ba5af
JF
1258 cur_peers = set(peers)
1259 if remoteips:
1260 new_peers = set(remoteips)
1261 del_list = cur_peers.difference(new_peers)
1262 add_list = new_peers.difference(cur_peers)
1263 else:
1264 del_list = cur_peers
1265 add_list = []
d486dd0d 1266
223ba5af 1267 for addr in del_list:
d486dd0d 1268 try:
223ba5af
JF
1269 self.iproute2.bridge_fdb_del(
1270 ifaceobj.name,
1271 "00:00:00:00:00:00",
1272 None, True, addr
1273 )
3218f49d 1274 except Exception:
d486dd0d 1275 pass
d486dd0d 1276
223ba5af 1277 for addr in add_list:
d486dd0d 1278 try:
223ba5af
JF
1279 self.iproute2.bridge_fdb_append(
1280 ifaceobj.name,
1281 "00:00:00:00:00:00",
1282 None, True, addr
1283 )
3218f49d 1284 except Exception:
223ba5af 1285 pass
d486dd0d 1286
66eb9ce3
JF
1287 self.vxlan_remote_ip_map(ifaceobj, vxlan_mcast_grp_map)
1288
35a4278f
JF
1289 @staticmethod
1290 def get_old_remote_ips(ifname):
1291 old_remoteips = []
1292 for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
1293 for remote in old_ifaceobj.get_attr_value("vxlan-remoteip") or []:
1294 old_remoteips.append(remote)
1295 return old_remoteips
1296
66eb9ce3
JF
1297 def vxlan_remote_ip_map(self, ifaceobj, vxlan_mcast_grp_map):
1298 # get user configured remote ip map
1299 vxlan_remote_ip_map = self.__get_vxlan_remote_ip_map(ifaceobj) or {}
1300
238e0485
JF
1301 # if we have an older config we need to see what needs to be removed
1302 # and not check the running state as FRR or other component can add fdb entries
1303 old_vxlan_remote_ip_map = {}
1304
1305 for old_ifaceobj in statemanager.get_ifaceobjs(ifaceobj.name) or []:
1306 old_vxlan_remote_ip_map = {**old_vxlan_remote_ip_map, **self.__get_vxlan_remote_ip_map(old_ifaceobj)}
1307
3376c233 1308 # go through the user config and add new entries while removing existing entries from 'old_vxlan_remote_ip_map'
66eb9ce3
JF
1309 for vni, ips in vxlan_remote_ip_map.items():
1310 for ip in ips:
238e0485 1311 if ip not in old_vxlan_remote_ip_map.get(vni, []):
66eb9ce3
JF
1312 self.iproute2.bridge_fdb_append(ifaceobj.name, "00:00:00:00:00:00", remote=ip, src_vni=vni)
1313 else:
238e0485 1314 old_vxlan_remote_ip_map.get(vni, []).remove(ip)
66eb9ce3 1315
238e0485
JF
1316 # in old_vxlan_remote_ip_map we have the delta between user config and running config. We should delete those
1317 # extra fdb entries. First we need to make sure that those are not added by vxlan-mcastgrp-map
1318 if old_vxlan_remote_ip_map:
66eb9ce3
JF
1319 for vni, ip in (vxlan_mcast_grp_map or {}).items():
1320 try:
238e0485 1321 old_vxlan_remote_ip_map[vni].remove(ip)
7c8627f8 1322 except Exception:
66eb9ce3
JF
1323 pass
1324
238e0485
JF
1325 for vni, ips in old_vxlan_remote_ip_map.items():
1326 for ip in ips:
1327 try:
1328 self.iproute2.bridge_fdb_del_raw(ifaceobj.name, "00:00:00:00:00:00 dst %s src_vni %s" % (ip, vni))
7c8627f8 1329 except Exception:
238e0485 1330 pass
66eb9ce3 1331
ca436937
JF
1332 @staticmethod
1333 def get_vxlan_fdb_src_vni(vxlan_mcast_grp_map):
1334 fdbs = []
56f34349 1335 if vxlan_mcast_grp_map:
6015cce2 1336 for src_vni, dst_ip in vxlan_mcast_grp_map.items():
ca436937
JF
1337 fdbs.append(("00:00:00:00:00:00", src_vni, dst_ip))
1338 return fdbs
1339
56f34349
JF
1340 @staticmethod
1341 def get_svd_running_fdb(ifname):
1342 vxlan_fdb_data = utils.exec_command("bridge fdb show dev %s" % ifname)
1343 current_fdb = []
1344
1345 if vxlan_fdb_data:
1346 # each entry should look like the following:
1347 # 00:00:00:00:00:00 dst 239.1.1.100 src_vni 1000 self permanent
1348 for entry in [line for line in vxlan_fdb_data.strip().split("\n") if "src_vni" in line and "00:00:00:00:00:00" in line]:
1349 mac, _, dst, _, src_vni = entry.split()[0:5]
1350 current_fdb.append((mac, src_vni, dst))
1351
1352 return current_fdb
1353
1354 def single_vxlan_device_mcast_grp_map_fdb(self, ifaceobj, ifname, vxlan_mcast_grp_map):
1355 # in this piece of code we won't be checking the running state of the fdb table
1356 # dumping all fdb entries would cause scalability issues in certain cases.
1357
1358 # pulling old mcastgrp-map configuration
1359 old_user_config_fdb = []
1360
1361 for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
6015cce2 1362 old_user_config_fdb += self.get_vxlan_fdb_src_vni(self.__get_vxlan_mcastgrp_map(old_ifaceobj))
56f34349
JF
1363
1364 # new user configuration
1365 user_config_fdb = self.get_vxlan_fdb_src_vni(vxlan_mcast_grp_map)
1366
1367 # compare old and new config to know if we should remove any stale fdb entries.
1368 fdb_entries_to_remove = set(old_user_config_fdb) - set(user_config_fdb)
1369
1370 if fdb_entries_to_remove:
1371 for mac, src_vni, dst_ip in fdb_entries_to_remove:
1372 try:
1373 self.iproute2.bridge_fdb_del_src_vni(ifname, mac, src_vni)
1374 except Exception as e:
1375 if "no such file or directory" not in str(e).lower():
1376 self.logger.warning("%s: removing stale fdb entries failed: %s" % (ifname, str(e)))
af8d5db2
RP
1377
1378 if not user_config_fdb:
1379 # if vxlan-mcastgrp-map wasn't configure return
1380 return
1381
1382 for mac, src_vni, dst_ip in user_config_fdb:
1383 try:
1384 self.iproute2.bridge_fdb_add_src_vni(ifname, src_vni, dst_ip)
1385 except Exception as e:
1386 if "file exists" not in str(e).lower():
1387 ifaceobj.set_status(ifaceStatus.ERROR)
1388 self.log_error(
1389 "%s: vxlan-mcastgrp-map: %s=%s: %s"
1390 % (ifname, src_vni, dst_ip, str(e)), raise_error=False
1391 )
1392
1393 def single_vxlan_device_mcast_grp_map_vnifilter(self, ifaceobj, ifname, vxlan_mcast_grp_map):
1394 # in this piece of code we won't be checking the running state of the fdb table
1395 # dumping all fdb entries would cause scalability issues in certain cases.
1396
1397 # pulling old mcastgrp-map configuration
1398 old_user_config_fdb = []
1399
1400 for old_ifaceobj in statemanager.get_ifaceobjs(ifname) or []:
1401 old_user_config_fdb += self.get_vxlan_fdb_src_vni(self.__get_vxlan_mcastgrp_map(old_ifaceobj))
1402
1403 # new user configuration
1404 user_config_fdb = self.get_vxlan_fdb_src_vni(vxlan_mcast_grp_map)
1405
1406 # compare old and new config to know if we should remove any stale fdb entries.
1407 fdb_entries_to_remove = set(old_user_config_fdb) - set(user_config_fdb)
1408
1409 self.logger.info(old_user_config_fdb)
1410 self.logger.info(user_config_fdb)
1411 self.logger.info(fdb_entries_to_remove)
1412
1413 if fdb_entries_to_remove:
1414 for mac, src_vni, dst_ip in fdb_entries_to_remove:
1415 try:
1416 self.iproute2.bridge_fdb_del_src_vni(ifname, mac, src_vni)
1417 except Exception as e:
1418 if "no such file or directory" not in str(e).lower():
1419 self.logger.warning("%s: removing stale fdb entries failed: %s" % (ifname, str(e)))
56f34349
JF
1420
1421 if not user_config_fdb:
1422 # if vxlan-mcastgrp-map wasn't configure return
1423 return
1424
1425 for mac, src_vni, dst_ip in user_config_fdb:
1426 try:
1427 self.iproute2.bridge_fdb_add_src_vni(ifname, src_vni, dst_ip)
1428 except Exception as e:
1429 if "file exists" not in str(e).lower():
1430 ifaceobj.set_status(ifaceStatus.ERROR)
1431 self.log_error(
1432 "%s: vxlan-mcastgrp-map: %s=%s: %s"
1433 % (ifname, src_vni, dst_ip, str(e)), raise_error=False
1434 )
1435
d486dd0d
JF
1436 def _down(self, ifaceobj):
1437 try:
223ba5af 1438 self.netlink.link_del(ifaceobj.name)
3b01ed76 1439 except Exception as e:
d486dd0d
JF
1440 self.log_warn(str(e))
1441
223ba5af
JF
1442 @staticmethod
1443 def _query_check_n_update(ifaceobj, ifaceobjcurr, attrname, attrval, running_attrval):
d486dd0d
JF
1444 if not ifaceobj.get_attr_value_first(attrname):
1445 return
1446 if running_attrval and attrval == running_attrval:
223ba5af 1447 ifaceobjcurr.update_config_with_status(attrname, attrval, 0)
d486dd0d 1448 else:
223ba5af 1449 ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1)
d486dd0d 1450
223ba5af
JF
1451 @staticmethod
1452 def _query_check_n_update_addresses(ifaceobjcurr, attrname, addresses, running_addresses):
d486dd0d
JF
1453 if addresses:
1454 for a in addresses:
1455 if a in running_addresses:
1456 ifaceobjcurr.update_config_with_status(attrname, a, 0)
1457 else:
1458 ifaceobjcurr.update_config_with_status(attrname, a, 1)
3b01ed76
JF
1459 running_addresses = set(running_addresses).difference(
1460 set(addresses))
223ba5af 1461 [ifaceobjcurr.update_config_with_status(attrname, a, 1) for a in running_addresses]
d486dd0d
JF
1462
1463 def _query_check(self, ifaceobj, ifaceobjcurr):
223ba5af
JF
1464 ifname = ifaceobj.name
1465
1466 if not self.cache.link_exists(ifname):
d486dd0d 1467 return
d486dd0d 1468
223ba5af
JF
1469 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1470
1471 if not cached_vxlan_ifla_info_data:
1472 ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, self.get_mod_attrs(), -1)
1473 return
1474
1475 for vxlan_attr_str, vxlan_attr_nl, callable_type in (
1476 ('vxlan-id', Link.IFLA_VXLAN_ID, int),
1477 ('vxlan-ttl', Link.IFLA_VXLAN_TTL, int),
e521508b 1478 ('vxlan-tos', Link.IFLA_VXLAN_TOS, int),
223ba5af
JF
1479 ('vxlan-port', Link.IFLA_VXLAN_PORT, int),
1480 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, int),
0e936c3f 1481 ('vxlan-mcastgrp', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
40658337 1482 ('vxlan-mcastgrp6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
0e936c3f 1483 ('vxlan-svcnodeip', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address),
40658337 1484 ('vxlan-svcnodeip6', Link.IFLA_VXLAN_GROUP6, ipnetwork.IPv6Address),
223ba5af
JF
1485 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, lambda x: self.cache.get_ifindex(x)),
1486 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
e521508b 1487 ('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda boolean_str: utils.get_boolean_from_string(boolean_str)),
223ba5af
JF
1488 ):
1489 vxlan_attr_value = ifaceobj.get_attr_value_first(vxlan_attr_str)
d486dd0d 1490
223ba5af
JF
1491 if not vxlan_attr_value:
1492 continue
1493
1494 cached_vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
1495
1496 try:
1497 vxlan_attr_value_nl = callable_type(vxlan_attr_value)
1498 except Exception as e:
1499 self.logger.warning('%s: %s: %s' % (ifname, vxlan_attr_str, str(e)))
1500 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
1501 continue
1502
1503 if vxlan_attr_value_nl == cached_vxlan_attr_value:
1504 ifaceobjcurr.update_config_with_status(vxlan_attr_str, vxlan_attr_value, 0)
1505 else:
1506 ifaceobjcurr.update_config_with_status(vxlan_attr_str, cached_vxlan_attr_value or 'None', 1)
1507
1508 #
1509 # vxlan-local-tunnelip
1510 #
1511 running_attrval = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL)
d486dd0d
JF
1512 attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
1513 if not attrval:
223ba5af 1514 attrval = self._vxlan_local_tunnelip
0e936c3f 1515 # TODO: vxlan._vxlan_local_tunnelip should be a ipnetwork.IPNetwork obj
d486dd0d
JF
1516 ifaceobj.update_config('vxlan-local-tunnelip', attrval)
1517
223ba5af 1518 if str(running_attrval) == self._clagd_vxlan_anycast_ip:
d486dd0d
JF
1519 # if local ip is anycast_ip, then let query_check to go through
1520 attrval = self._clagd_vxlan_anycast_ip
d486dd0d 1521
223ba5af
JF
1522 self._query_check_n_update(
1523 ifaceobj,
1524 ifaceobjcurr,
1525 'vxlan-local-tunnelip',
1526 str(attrval),
0e936c3f 1527 str(running_attrval.ip) if running_attrval else None
223ba5af 1528 )
d486dd0d 1529
223ba5af
JF
1530 #
1531 # vxlan-remoteip
1532 #
1533 purge_remotes = self.__get_vlxan_purge_remotes(ifaceobj)
d486dd0d
JF
1534 if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'):
1535 # If purge remotes or if vxlan-remoteip's are set
1536 # in the config file, we are owners of the installed
1537 # remote-ip's, lets check and report any remote ips we don't
1538 # understand
223ba5af 1539 cached_svcnode = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
d486dd0d 1540
223ba5af
JF
1541 self._query_check_n_update_addresses(
1542 ifaceobjcurr,
1543 'vxlan-remoteip',
1544 ifaceobj.get_attr_value('vxlan-remoteip'),
0e936c3f 1545 self.iproute2.get_vxlan_peers(ifaceobj.name, str(cached_svcnode.ip) if cached_svcnode else None)
223ba5af 1546 )
a382b488 1547
0500d5d8
JF
1548 # not ideal but will work for now, l3vxi dev:
1549 if ifaceobj.link_privflags & ifaceLinkPrivFlags.L3VXI:
1550 user_config_vni_list = set(self.__get_vxlan_vni_list(ifaceobj, string=False))
1551 vxlan_vni_list = set()
1552
1553 for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
1554 for vni_obj in obj.get("vnis", []):
1555 start = vni_obj.get("vni")
1556 end = vni_obj.get("vniEnd")
1557
1558 for vni in utils.ranges_to_ints(["%s-%s" % (start, end if end else start)]):
1559 vxlan_vni_list.add(vni)
1560
1561 ifaceobjcurr.update_config_with_status(
1562 "vxlan-vni",
1563 " ".join(utils.compress_into_ranges(vxlan_vni_list)),
1564 vxlan_vni_list != user_config_vni_list
1565 )
1566
e6edcd21 1567 #
66eb9ce3 1568 # vxlan-mcastgrp-map & vxlan-remoteip-map
d4403f1e
JF
1569 # fdb entries can be added by FRR, so we won't be checking the running
1570 # state if there's no record of a user configuration in /e/n/i
1571 user_mcastgrp_map = self.__get_vxlan_mcastgrp_map(ifaceobj)
1572 user_remote_ip_map = self.__get_vxlan_remote_ip_map(ifaceobj)
1573
9ca87c5e 1574 if not user_mcastgrp_map and not user_remote_ip_map:
d4403f1e
JF
1575 return
1576
66eb9ce3
JF
1577 fdb_mcast = {}
1578 fdb_remote = {}
e6edcd21 1579
9ca87c5e
JF
1580 if user_remote_ip_map:
1581 for _, src_vni, dst in self.get_svd_running_fdb(ifname):
1582 ip = ipnetwork.IPv4Address(dst)
e6edcd21 1583
9ca87c5e
JF
1584 if not ip.is_multicast:
1585 fdb_remote.setdefault(int(src_vni), []).append(ip)
1586
1587 if user_mcastgrp_map:
1588 for obj in json.loads(utils.exec_command("bridge -j -p vni show dev %s" % ifname) or "[]"):
1589 for vni in obj.get("vnis", []):
1590 group = vni.get("group")
1591
1592 if not group:
1593 continue
1594
1595 # we need to reconvert back to ipaddress.IPv4Address because
1596 # the existing code uses this type of obj (namely: __get_vxlan_mcastgrp_map)
1597 fdb_mcast[vni.get("vni")] = IPv4Address(group)
e6edcd21 1598
66eb9ce3
JF
1599 #
1600 # vxlan-mcastgrp-map
1601 #
66eb9ce3
JF
1602 if not user_mcastgrp_map and fdb_mcast:
1603 ifaceobjcurr.update_config_with_status(
1604 "vxlan-mcastgrp-map",
1605 " ".join(["%s=%s" % (vni, ip) for vni, ip in fdb_mcast.items()]),
1606 1
1607 )
1608 elif user_mcastgrp_map and not fdb_mcast:
1609 ifaceobjcurr.update_config_with_status("vxlan-mcastgrp-map", "", 1)
1610 elif user_mcastgrp_map or fdb_mcast:
1611 ifaceobjcurr.update_config_with_status(
1612 "vxlan-mcastgrp-map",
1613 " ".join(["%s=%s" % (vni, ip) for vni, ip in fdb_mcast.items()]),
1614 user_mcastgrp_map != fdb_mcast
1615 )
e6edcd21 1616
66eb9ce3
JF
1617 #
1618 # vxlan-remoteip-map
1619 #
66eb9ce3 1620 if not user_remote_ip_map and fdb_remote:
e6edcd21 1621 ifaceobjcurr.update_config_with_status(
66eb9ce3
JF
1622 "vxlan-remoteip-map",
1623 " ".join(["%s=%s" % (vni, ",".join(map(str, ips))) for vni, ips in fdb_remote.items()]),
1624 1
e6edcd21 1625 )
66eb9ce3
JF
1626 elif user_remote_ip_map and not fdb_remote:
1627 ifaceobjcurr.update_config_with_status("vxlan-remoteip-map", "", 1)
1628 elif user_remote_ip_map or fdb_remote:
1629
1630 if user_remote_ip_map == fdb_remote:
1631 # display the user config with "pass"
1632 for config in ifaceobj.get_attr_value("vxlan-remoteip-map"):
1633 ifaceobjcurr.update_config_with_status(
1634 "vxlan-remoteip-map",
1635 config,
1636 0
1637 )
1638 else:
1639 # display current running state with ip ranges (but no vni ranges yet)
1640 ifaceobjcurr.update_config_with_status(
1641 "vxlan-remoteip-map",
1642 " ".join(["%s=%s" % (vni, ",".join(utils.compress_into_ip_ranges(ips))) for vni, ips in fdb_remote.items()]),
1643 1
1644 )
e6edcd21 1645
223ba5af
JF
1646 def _query_running(self, ifaceobjrunning):
1647 ifname = ifaceobjrunning.name
a382b488 1648
223ba5af
JF
1649 if not self.cache.link_exists(ifname):
1650 return
a382b488 1651
e8b9d3ab 1652 if self.cache.get_link_kind(ifname) != 'vxlan':
d486dd0d 1653 return
223ba5af
JF
1654
1655 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(ifname)
1656
1657 if not cached_vxlan_ifla_info_data:
d486dd0d
JF
1658 return
1659
223ba5af
JF
1660 #
1661 # vxlan-id
1662 #
1663 vxlan_id = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_ID)
1664
1665 if not vxlan_id:
1666 # no vxlan id, meaning this not a vxlan
1667 return
1668
1669 ifaceobjrunning.update_config('vxlan-id', str(vxlan_id))
1670
1671 #
1672 # vxlan-port
1673 #
1674 vxlan_port = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_PORT)
1675
1676 if vxlan_port:
1677 ifaceobjrunning.update_config('vxlan-port', vxlan_port)
1678
1679 #
1680 # vxlan-svcnode
1681 #
1682 vxlan_svcnode_value = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_GROUP)
1683
1684 if vxlan_svcnode_value:
1685 vxlan_svcnode_value = str(vxlan_svcnode_value)
1686 ifaceobjrunning.update_config('vxlan-svcnode', vxlan_svcnode_value)
d486dd0d 1687
223ba5af
JF
1688 #
1689 # vxlan-remoteip
1690 #
1691 purge_remotes = self.__get_vlxan_purge_remotes(None)
d486dd0d
JF
1692 if purge_remotes:
1693 # if purge_remotes is on, it means we own the
1694 # remote ips. Query them and add it to the running config
223ba5af 1695 attrval = self.iproute2.get_vxlan_peers(ifname, vxlan_svcnode_value)
d486dd0d 1696 if attrval:
223ba5af
JF
1697 [ifaceobjrunning.update_config('vxlan-remoteip', a) for a in attrval]
1698
1699 #
1700 # vxlan-link
1701 # vxlan-ageing
1702 # vxlan-learning
1703 # vxlan-local-tunnelip
1704 #
1705 for vxlan_attr_name, vxlan_attr_nl, callable_netlink_value_to_string in (
1706 ('vxlan-physdev', Link.IFLA_VXLAN_LINK, self._get_ifname_for_ifindex),
1707 ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, str),
1708 ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda value: 'on' if value else 'off'),
e521508b 1709 ('vxlan-udp-csum', Link.IFLA_VXLAN_UDP_CSUM, lambda value: 'on' if value else 'off'),
223ba5af
JF
1710 ('vxlan-local-tunnelip', Link.IFLA_VXLAN_LOCAL, str),
1711 ):
1712 vxlan_attr_value = cached_vxlan_ifla_info_data.get(vxlan_attr_nl)
1713
1714 if vxlan_attr_value is not None:
dc74ceda 1715 vxlan_attr_value_str = callable_netlink_value_to_string(vxlan_attr_value)
223ba5af
JF
1716
1717 if vxlan_attr_value:
1718 ifaceobjrunning.update_config(vxlan_attr_name, vxlan_attr_value_str)
1719
1720 def _get_ifname_for_ifindex(self, ifindex):
1721 """
1722 we need this middle-man function to query the cache
1723 cache.get_ifname can raise KeyError, we need to catch
1724 it and return None
1725 """
1726 try:
1727 return self.cache.get_ifname(ifindex)
1728 except KeyError:
1729 return None
a382b488 1730
223ba5af
JF
1731 _run_ops = {
1732 "pre-up": _up,
1733 "post-down": _down,
1734 "query-running": _query_running,
1735 "query-checkcurr": _query_check
1736 }
d486dd0d
JF
1737
1738 def get_ops(self):
3b01ed76 1739 return list(self._run_ops.keys())
d486dd0d 1740
d486dd0d
JF
1741 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
1742 op_handler = self._run_ops.get(operation)
1743 if not op_handler:
1744 return
223ba5af 1745
66eb9ce3
JF
1746 if not self._is_vxlan_device(ifaceobj):
1747 return
223ba5af 1748
66eb9ce3 1749 if "query" not in operation:
223ba5af
JF
1750 if not self.vxlan_mcastgrp_ref \
1751 and self.vxlan_physdev_mcast \
1752 and self.cache.link_exists(self.vxlan_physdev_mcast):
1753 self.netlink.link_del(self.vxlan_physdev_mcast)
1754 self.reset()
1755
d486dd0d
JF
1756 if operation == 'query-checkcurr':
1757 op_handler(self, ifaceobj, query_ifaceobj)
1758 else:
1759 op_handler(self, ifaceobj)