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